blob: e6e73e84d62611f5082176810a5a7207a8557340 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <gtest/gtest.h>
#include <tvm/ir/expr.h>
#include <tvm/runtime/logging.h>
#include <tvm/target/target.h>
#include <cmath>
#include <string>
using namespace tvm;
TVM_REGISTER_TARGET_KIND("TestTargetKind", kDLCPU)
.set_attr<std::string>("Attr1", "Value1")
.add_attr_option<bool>("my_bool")
.add_attr_option<ffi::Array<ffi::String>>("your_names")
.add_attr_option<ffi::Map<ffi::String, int64_t>>("her_maps");
ffi::Map<ffi::String, ffi::Any> TestTargetParser(ffi::Map<ffi::String, ffi::Any> target) {
ffi::String mcpu = Downcast<ffi::String>(target.at("mcpu"));
target.Set("mcpu", ffi::String("super_") + mcpu);
target.Set("keys", ffi::Array<ffi::String>({"super"}));
target.Set("feature.test", true);
return target;
}
ffi::Map<ffi::String, ffi::Any> TestAttrsPreProcessor(ffi::Map<ffi::String, ffi::Any> attrs) {
attrs.Set("mattr", ffi::String("woof"));
return attrs;
}
TVM_REGISTER_TARGET_KIND("TestTargetParser", kDLCPU)
.add_attr_option<ffi::String>("mattr")
.add_attr_option<ffi::String>("mcpu")
.set_default_keys({"cpu"})
.set_target_canonicalizer(TestTargetParser);
TVM_REGISTER_TARGET_KIND("TestAttrsPreprocessor", kDLCPU)
.add_attr_option<ffi::String>("mattr")
.set_default_keys({"cpu"})
.set_target_canonicalizer(TestAttrsPreProcessor);
TVM_REGISTER_TARGET_KIND("TestClashingPreprocessor", kDLCPU)
.add_attr_option<ffi::String>("mattr")
.add_attr_option<ffi::String>("mcpu")
.set_default_keys({"cpu"})
.set_target_canonicalizer(TestTargetParser);
TEST(TargetKind, GetAttrMap) {
auto map = tvm::TargetKind::GetAttrMap<std::string>("Attr1");
auto target_kind = tvm::TargetKind::Get("TestTargetKind").value();
std::string result = map[target_kind];
TVM_FFI_ICHECK_EQ(result, "Value1");
}
TEST(TargetCreation, NestedConfig) {
ffi::Map<ffi::String, ffi::Any> config = {
{"my_bool", true},
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
{"kind", ffi::String("TestTargetKind")},
{
"her_maps",
ffi::Map<ffi::String, int64_t>{
{"a", 1},
{"b", 2},
},
},
};
Target target = Target(config);
TVM_FFI_ICHECK_EQ(target->kind, TargetKind::Get("TestTargetKind").value());
TVM_FFI_ICHECK_EQ(target->tag, "");
TVM_FFI_ICHECK(target->keys.empty());
bool my_bool = target->GetAttr<bool>("my_bool").value();
TVM_FFI_ICHECK_EQ(my_bool, true);
ffi::Array<ffi::String> your_names =
target->GetAttr<ffi::Array<ffi::String>>("your_names").value();
TVM_FFI_ICHECK_EQ(your_names.size(), 2U);
TVM_FFI_ICHECK_EQ(your_names[0], "junru");
TVM_FFI_ICHECK_EQ(your_names[1], "jian");
ffi::Map<ffi::String, int64_t> her_maps =
target->GetAttr<ffi::Map<ffi::String, int64_t>>("her_maps").value();
TVM_FFI_ICHECK_EQ(her_maps.size(), 2U);
TVM_FFI_ICHECK_EQ(her_maps["a"], 1);
TVM_FFI_ICHECK_EQ(her_maps["b"], 2);
}
TEST(TargetCreationFail, UnrecognizedConfigOption) {
ffi::Map<ffi::String, ffi::Any> config = {
{"my_bool", true},
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
{"kind", ffi::String("TestTargetKind")},
{"bad", ffi::ObjectRef(nullptr)},
{
"her_maps",
ffi::Map<ffi::String, int64_t>{
{"a", 1},
{"b", 2},
},
},
};
bool failed = false;
try {
Target tgt(config);
} catch (...) {
failed = true;
}
ASSERT_EQ(failed, true);
}
TEST(TargetCreationFail, TypeMismatch) {
ffi::Map<ffi::String, ffi::Any> config = {
{"my_bool", ffi::String("true")},
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
{"kind", ffi::String("TestTargetKind")},
{
"her_maps",
ffi::Map<ffi::String, int64_t>{
{"a", 1},
{"b", 2},
},
},
};
bool failed = false;
try {
Target tgt(config);
} catch (...) {
failed = true;
}
ASSERT_EQ(failed, true);
}
TEST(TargetCreationFail, TargetKindNotFound) {
ffi::Map<ffi::String, ffi::Any> config = {
{"my_bool", "true"},
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
{
"her_maps",
ffi::Map<ffi::String, int64_t>{
{"a", 1},
{"b", 2},
},
},
};
bool failed = false;
try {
Target tgt(config);
} catch (...) {
failed = true;
}
ASSERT_EQ(failed, true);
}
TEST(TargetCreation, TargetParser) {
Target test_target(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
ASSERT_EQ(test_target->GetAttr<ffi::String>("mcpu").value(), "super_woof");
ASSERT_EQ(test_target->keys.size(), 1);
ASSERT_EQ(test_target->keys[0], "super");
}
TEST(TargetCreation, TargetFeatures) {
Target test_target_with_parser(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
// Features are stored as "feature.xxx" keys in attrs
ASSERT_EQ(test_target_with_parser->GetAttr<bool>("feature.test").value(), true);
Target test_target_no_parser("TestTargetKind");
ASSERT_EQ(test_target_no_parser->GetAttr<bool>("feature.test"), std::nullopt);
ASSERT_EQ(test_target_no_parser->GetAttr<bool>("feature.test", true).value(), true);
}
TEST(TargetCreation, TargetFeaturesSetByCanonicalizer) {
// feature.* keys are set by the canonicalizer, not by user input.
Target test_target(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
// TestTargetParser sets "feature.test" = true
ASSERT_EQ(test_target->GetAttr<bool>("feature.test").value(), true);
// Non-existent features return nullopt
ASSERT_EQ(test_target->GetAttr<bool>("feature.nonexistent"), std::nullopt);
}
TEST(TargetCreation, TargetAttrsPreProcessor) {
Target test_target(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestAttrsPreprocessor")},
{"mattr", ffi::String("cake")},
});
ASSERT_EQ(test_target->GetAttr<ffi::String>("mattr").value(), "woof");
}
TEST(TargetCreation, TargetParserProcessing) {
Target test_target(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestClashingPreprocessor")},
{"mcpu", ffi::String("woof")},
{"mattr", ffi::String("cake")},
});
ASSERT_EQ(test_target->GetAttr<ffi::String>("mcpu").value(), "super_woof");
ASSERT_EQ(test_target->GetAttr<ffi::String>("mattr").value(), "cake");
}
TEST(TargetCreation, RoundTripCanonicalizerFeatures) {
// Construct a target whose canonicalizer sets feature.test and transforms mcpu
Target original(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
ASSERT_EQ(original->GetAttr<ffi::String>("mcpu").value(), "super_woof");
ASSERT_EQ(original->GetAttr<bool>("feature.test").value(), true);
// Export to config and reconstruct
ffi::Map<ffi::String, ffi::Any> exported = original->ToConfig();
Target reconstructed(exported);
// Canonicalized attrs must survive the round-trip
// Note: mcpu gets canonicalized again (super_super_woof) because the canonicalizer runs
ASSERT_TRUE(reconstructed->GetAttr<ffi::String>("mcpu").has_value());
ASSERT_EQ(reconstructed->GetAttr<bool>("feature.test").value(), true);
ASSERT_EQ(reconstructed->keys.size(), 1);
ASSERT_EQ(reconstructed->keys[0], "super");
}
TEST(TargetCreation, RoundTripCanonicalizerFeaturesNestedHost) {
// Construct a host target whose canonicalizer sets feature.test
Target host(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
ASSERT_EQ(host->GetAttr<bool>("feature.test").value(), true);
// Attach it as host to another target
Target outer(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetKind")},
{"my_bool", true},
});
Target combined(outer, host);
// Export the outer target (includes nested host) and reconstruct
ffi::Map<ffi::String, ffi::Any> exported = combined->ToConfig();
Target reconstructed(exported);
// The nested host must reconstruct successfully with feature.* preserved
ffi::Optional<Target> reconstructed_host = reconstructed->GetHost();
ASSERT_TRUE(reconstructed_host.defined());
ASSERT_EQ(reconstructed_host.value()->GetAttr<bool>("feature.test").value(), true);
ASSERT_TRUE(reconstructed_host.value()->GetAttr<ffi::String>("mcpu").has_value());
}
TEST(TargetCreationFail, UnknownNonFeatureKeyStillFails) {
// Verify that unknown non-feature.* keys still fail schema validation
ffi::Map<ffi::String, ffi::Any> config = {
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
{"unknown_key", ffi::String("bad")},
};
ASSERT_THROW({ Target{config}; }, tvm::ffi::Error);
}
TVM_REGISTER_TARGET_KIND("TestStringKind", kDLCPU)
.add_attr_option<ffi::String>("single")
.add_attr_option<ffi::Array<ffi::String>>("array")
.add_attr_option<ffi::Array<ffi::Array<ffi::String>>>("nested-array")
.add_attr_option<ffi::Array<ffi::Array<ffi::Array<ffi::String>>>>("nested2-array");
TEST(TargetCreation, ProcessStrings) {
// Test single string attribute
Target test_target1(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestStringKind")},
{"single", ffi::String("'string with single quote")},
});
ASSERT_TRUE(test_target1->GetAttr<ffi::String>("single"));
ffi::String string1 = test_target1->GetAttr<ffi::String>("single").value();
ASSERT_EQ(string1, "'string with single quote");
// Test array of strings
Target test_target3(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestStringKind")},
{"array", ffi::Array<ffi::String>{"-danny", "-sammy=1", "-kirby=string with space"}},
});
ASSERT_TRUE(test_target3->GetAttr<ffi::Array<ffi::String>>("array"));
ffi::Array<ffi::String> array3 = test_target3->GetAttr<ffi::Array<ffi::String>>("array").value();
ASSERT_EQ(array3[0], "-danny");
ASSERT_EQ(array3[1], "-sammy=1");
ASSERT_EQ(array3[2], "-kirby=string with space");
// Test nested array of strings
Target test_target6(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestStringKind")},
{"nested-array",
ffi::Array<ffi::Array<ffi::String>>{
ffi::Array<ffi::String>{"foo0", "foo1", "foo2"},
ffi::Array<ffi::String>{"bar0", "bar1", "bar2"},
ffi::Array<ffi::String>{"baz0", "baz1"},
}},
});
ASSERT_TRUE(test_target6->GetAttr<ffi::Array<ffi::Array<ffi::String>>>("nested-array"));
ffi::Array<ffi::Array<ffi::String>> array6 =
test_target6->GetAttr<ffi::Array<ffi::Array<ffi::String>>>("nested-array").value();
ASSERT_EQ(array6[0][0], "foo0");
ASSERT_EQ(array6[0][1], "foo1");
ASSERT_EQ(array6[0][2], "foo2");
ASSERT_EQ(array6[1][0], "bar0");
ASSERT_EQ(array6[1][1], "bar1");
ASSERT_EQ(array6[1][2], "bar2");
ASSERT_EQ(array6[2][0], "baz0");
ASSERT_EQ(array6[2][1], "baz1");
// Test doubly-nested array of strings
Target test_target7(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestStringKind")},
{"nested2-array",
ffi::Array<ffi::Array<ffi::Array<ffi::String>>>{
ffi::Array<ffi::Array<ffi::String>>{
ffi::Array<ffi::String>{"foo0", "foo1"},
ffi::Array<ffi::String>{"bar0", "bar1"},
ffi::Array<ffi::String>{"baz0", "baz1"},
},
ffi::Array<ffi::Array<ffi::String>>{
ffi::Array<ffi::String>{"zing0", "zing1"},
ffi::Array<ffi::String>{"fred"},
},
}},
});
ASSERT_TRUE(
test_target7->GetAttr<ffi::Array<ffi::Array<ffi::Array<ffi::String>>>>("nested2-array"));
ffi::Array<ffi::Array<ffi::Array<ffi::String>>> array7 =
test_target7->GetAttr<ffi::Array<ffi::Array<ffi::Array<ffi::String>>>>("nested2-array")
.value();
ASSERT_EQ(array7.size(), 2);
ASSERT_EQ(array7[0].size(), 3);
ASSERT_EQ(array7[0][0].size(), 2);
ASSERT_EQ(array7[0][1].size(), 2);
ASSERT_EQ(array7[0][2].size(), 2);
ASSERT_EQ(array7[1].size(), 2);
ASSERT_EQ(array7[1][0].size(), 2);
ASSERT_EQ(array7[1][1].size(), 1);
ASSERT_EQ(array7[0][0][0], "foo0");
ASSERT_EQ(array7[0][0][1], "foo1");
ASSERT_EQ(array7[0][1][0], "bar0");
ASSERT_EQ(array7[0][1][1], "bar1");
ASSERT_EQ(array7[0][2][0], "baz0");
ASSERT_EQ(array7[0][2][1], "baz1");
ASSERT_EQ(array7[1][0][0], "zing0");
ASSERT_EQ(array7[1][0][1], "zing1");
ASSERT_EQ(array7[1][1][0], "fred");
}
TEST(TargetCreation, DeduplicateKeys) {
ffi::Map<ffi::String, ffi::Any> config = {
{"kind", ffi::String("llvm")},
{"keys", ffi::Array<ffi::String>{"cpu", "arm_cpu"}},
{"device", ffi::String("arm_cpu")},
};
Target target = Target(config);
TVM_FFI_ICHECK_EQ(target->kind, TargetKind::Get("llvm").value());
TVM_FFI_ICHECK_EQ(target->tag, "");
TVM_FFI_ICHECK_EQ(target->keys.size(), 2U);
TVM_FFI_ICHECK_EQ(target->keys[0], "cpu");
TVM_FFI_ICHECK_EQ(target->keys[1], "arm_cpu");
TVM_FFI_ICHECK_EQ(target->attrs.size(), 2U);
TVM_FFI_ICHECK_EQ(target->GetAttr<ffi::String>("device"), "arm_cpu");
}
TEST(TargetKindRegistry, ListTargetKinds) {
ffi::Array<ffi::String> names = TargetKindRegEntry::ListTargetKinds();
TVM_FFI_ICHECK_EQ(names.empty(), false);
TVM_FFI_ICHECK_EQ(std::count(std::begin(names), std::end(names), "llvm"), 1);
}
TEST(TargetKindRegistry, ListTargetOptions) {
TargetKind llvm = TargetKind::Get("llvm").value();
ffi::Map<ffi::String, ffi::String> attrs = TargetKindRegEntry::ListTargetKindOptions(llvm);
TVM_FFI_ICHECK_EQ(attrs.empty(), false);
}