/* * 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 #include #include #include #include #include using namespace tvm; TVM_REGISTER_TARGET_KIND("TestTargetKind", kDLCPU) .set_attr("Attr1", "Value1") .add_attr_option("my_bool") .add_attr_option>("your_names") .add_attr_option>("her_maps"); ffi::Map TestTargetParser(ffi::Map target) { ffi::String mcpu = target.at("mcpu").as_or_throw(); target.Set("mcpu", ffi::String("super_") + mcpu); target.Set("keys", ffi::Array({"super"})); target.Set("feature.test", true); return target; } ffi::Map TestAttrsPreProcessor(ffi::Map attrs) { attrs.Set("mattr", ffi::String("woof")); return attrs; } TVM_REGISTER_TARGET_KIND("TestTargetParser", kDLCPU) .add_attr_option("mattr") .add_attr_option("mcpu") .set_default_keys({"cpu"}) .set_target_canonicalizer(TestTargetParser); TVM_REGISTER_TARGET_KIND("TestAttrsPreprocessor", kDLCPU) .add_attr_option("mattr") .set_default_keys({"cpu"}) .set_target_canonicalizer(TestAttrsPreProcessor); TVM_REGISTER_TARGET_KIND("TestClashingPreprocessor", kDLCPU) .add_attr_option("mattr") .add_attr_option("mcpu") .set_default_keys({"cpu"}) .set_target_canonicalizer(TestTargetParser); TEST(TargetKind, GetAttrMap) { auto map = tvm::TargetKind::GetAttrMap("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 config = { {"my_bool", true}, {"your_names", ffi::Array{"junru", "jian"}}, {"kind", ffi::String("TestTargetKind")}, { "her_maps", ffi::Map{ {"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("my_bool").value(); TVM_FFI_ICHECK_EQ(my_bool, true); ffi::Array your_names = target->GetAttr>("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 her_maps = target->GetAttr>("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 config = { {"my_bool", true}, {"your_names", ffi::Array{"junru", "jian"}}, {"kind", ffi::String("TestTargetKind")}, {"bad", ffi::ObjectRef(nullptr)}, { "her_maps", ffi::Map{ {"a", 1}, {"b", 2}, }, }, }; bool failed = false; try { Target tgt(config); } catch (...) { failed = true; } ASSERT_EQ(failed, true); } TEST(TargetCreationFail, TypeMismatch) { ffi::Map config = { {"my_bool", ffi::String("true")}, {"your_names", ffi::Array{"junru", "jian"}}, {"kind", ffi::String("TestTargetKind")}, { "her_maps", ffi::Map{ {"a", 1}, {"b", 2}, }, }, }; bool failed = false; try { Target tgt(config); } catch (...) { failed = true; } ASSERT_EQ(failed, true); } TEST(TargetCreationFail, TargetKindNotFound) { ffi::Map config = { {"my_bool", "true"}, {"your_names", ffi::Array{"junru", "jian"}}, { "her_maps", ffi::Map{ {"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{ {"kind", ffi::String("TestTargetParser")}, {"mcpu", ffi::String("woof")}, }); ASSERT_EQ(test_target->GetAttr("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{ {"kind", ffi::String("TestTargetParser")}, {"mcpu", ffi::String("woof")}, }); // Features are stored as "feature.xxx" keys in attrs ASSERT_EQ(test_target_with_parser->GetAttr("feature.test").value(), true); Target test_target_no_parser("TestTargetKind"); ASSERT_EQ(test_target_no_parser->GetAttr("feature.test"), std::nullopt); ASSERT_EQ(test_target_no_parser->GetAttr("feature.test", true).value(), true); } TEST(TargetCreation, TargetFeaturesSetByCanonicalizer) { // feature.* keys are set by the canonicalizer, not by user input. Target test_target(ffi::Map{ {"kind", ffi::String("TestTargetParser")}, {"mcpu", ffi::String("woof")}, }); // TestTargetParser sets "feature.test" = true ASSERT_EQ(test_target->GetAttr("feature.test").value(), true); // Non-existent features return nullopt ASSERT_EQ(test_target->GetAttr("feature.nonexistent"), std::nullopt); } TEST(TargetCreation, TargetAttrsPreProcessor) { Target test_target(ffi::Map{ {"kind", ffi::String("TestAttrsPreprocessor")}, {"mattr", ffi::String("cake")}, }); ASSERT_EQ(test_target->GetAttr("mattr").value(), "woof"); } TEST(TargetCreation, TargetParserProcessing) { Target test_target(ffi::Map{ {"kind", ffi::String("TestClashingPreprocessor")}, {"mcpu", ffi::String("woof")}, {"mattr", ffi::String("cake")}, }); ASSERT_EQ(test_target->GetAttr("mcpu").value(), "super_woof"); ASSERT_EQ(test_target->GetAttr("mattr").value(), "cake"); } TEST(TargetCreation, RoundTripCanonicalizerFeatures) { // Construct a target whose canonicalizer sets feature.test and transforms mcpu Target original(ffi::Map{ {"kind", ffi::String("TestTargetParser")}, {"mcpu", ffi::String("woof")}, }); ASSERT_EQ(original->GetAttr("mcpu").value(), "super_woof"); ASSERT_EQ(original->GetAttr("feature.test").value(), true); // Export to config and reconstruct ffi::Map 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("mcpu").has_value()); ASSERT_EQ(reconstructed->GetAttr("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{ {"kind", ffi::String("TestTargetParser")}, {"mcpu", ffi::String("woof")}, }); ASSERT_EQ(host->GetAttr("feature.test").value(), true); // Attach it as host to another target Target outer(ffi::Map{ {"kind", ffi::String("TestTargetKind")}, {"my_bool", true}, }); Target combined(outer, host); // Export the outer target (includes nested host) and reconstruct ffi::Map exported = combined->ToConfig(); Target reconstructed(exported); // The nested host must reconstruct successfully with feature.* preserved ffi::Optional reconstructed_host = reconstructed->GetHost(); ASSERT_TRUE(reconstructed_host.has_value()); ASSERT_EQ(reconstructed_host.value()->GetAttr("feature.test").value(), true); ASSERT_TRUE(reconstructed_host.value()->GetAttr("mcpu").has_value()); } TEST(TargetCreationFail, UnknownNonFeatureKeyStillFails) { // Verify that unknown non-feature.* keys still fail schema validation ffi::Map 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("single") .add_attr_option>("array") .add_attr_option>>("nested-array") .add_attr_option>>>("nested2-array"); TEST(TargetCreation, ProcessStrings) { // Test single string attribute Target test_target1(ffi::Map{ {"kind", ffi::String("TestStringKind")}, {"single", ffi::String("'string with single quote")}, }); ASSERT_TRUE(test_target1->GetAttr("single")); ffi::String string1 = test_target1->GetAttr("single").value(); ASSERT_EQ(string1, "'string with single quote"); // Test array of strings Target test_target3(ffi::Map{ {"kind", ffi::String("TestStringKind")}, {"array", ffi::Array{"-danny", "-sammy=1", "-kirby=string with space"}}, }); ASSERT_TRUE(test_target3->GetAttr>("array")); ffi::Array array3 = test_target3->GetAttr>("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{ {"kind", ffi::String("TestStringKind")}, {"nested-array", ffi::Array>{ ffi::Array{"foo0", "foo1", "foo2"}, ffi::Array{"bar0", "bar1", "bar2"}, ffi::Array{"baz0", "baz1"}, }}, }); ASSERT_TRUE(test_target6->GetAttr>>("nested-array")); ffi::Array> array6 = test_target6->GetAttr>>("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{ {"kind", ffi::String("TestStringKind")}, {"nested2-array", ffi::Array>>{ ffi::Array>{ ffi::Array{"foo0", "foo1"}, ffi::Array{"bar0", "bar1"}, ffi::Array{"baz0", "baz1"}, }, ffi::Array>{ ffi::Array{"zing0", "zing1"}, ffi::Array{"fred"}, }, }}, }); ASSERT_TRUE( test_target7->GetAttr>>>("nested2-array")); ffi::Array>> array7 = test_target7->GetAttr>>>("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 config = { {"kind", ffi::String("llvm")}, {"keys", ffi::Array{"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.count("keys"), 0U); TVM_FFI_ICHECK_EQ(target->GetAttr("device"), "arm_cpu"); } TEST(TargetKindRegistry, ListTargetKinds) { ffi::Array 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 attrs = TargetKindRegEntry::ListTargetKindOptions(llvm); TVM_FFI_ICHECK_EQ(attrs.empty(), false); }