diff --git a/python/tvm/target/codegen.py b/python/tvm/target/codegen.py index b2a92c2ca21b4..82385e3b684f1 100644 --- a/python/tvm/target/codegen.py +++ b/python/tvm/target/codegen.py @@ -183,7 +183,8 @@ def llvm_get_cpu_features(target=None): List of available CPU features. """ assert isinstance(target, Target) or target is None - return _ffi_api.llvm_get_cpu_features(target) + feature_map = _ffi_api.llvm_get_cpu_features(target) + return set(feature_map.keys()) def llvm_cpu_has_features(cpu_features, target=None): diff --git a/src/target/llvm/llvm_instance.cc b/src/target/llvm/llvm_instance.cc index a1359b7850a4d..b3f55594a25f0 100644 --- a/src/target/llvm/llvm_instance.cc +++ b/src/target/llvm/llvm_instance.cc @@ -199,32 +199,37 @@ std::ostream& operator<<(std::ostream& os, const LLVMTargetInfo::Option& opt) { return os; } -LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const Target& target) { - triple_ = target->GetAttr("mtriple").value_or("default"); +LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const Target& target) + : LLVMTargetInfo(instance, target->Export()) {} +LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const TargetJSON& target) { + triple_ = Downcast(target.Get("mtriple").value_or(String("default"))); if (triple_.empty() || triple_ == "default") { triple_ = llvm::sys::getDefaultTargetTriple(); } - cpu_ = target->GetAttr("mcpu").value_or(defaults::cpu); + cpu_ = Downcast(target.Get("mcpu").value_or(String(defaults::cpu))); - if (const Optional>& v = target->GetAttr>("mattr")) { + if (const auto& v = Downcast>>(target.Get("mattr"))) { for (const String& s : v.value()) { attrs_.push_back(s); } } // llvm module target - if (target->kind->name == "llvm") { + if (Downcast(target.Get("kind")) == "llvm") { // legalize -mcpu with the target -mtriple auto arches = GetAllLLVMTargetArches(); bool has_arch = std::any_of(arches.begin(), arches.end(), [&](const auto& var) { return var == cpu_; }); if (!has_arch) { - LOG(FATAL) << "LLVM cpu architecture `-mcpu=" << cpu_ - << "` is not valid in `-mtriple=" << triple_ << "`"; + // Flag an error, but don't abort. This mimicks the behaviour of 'llc' to + // give the code a chance to run with a less-specific target. + LOG(ERROR) << "LLVM cpu architecture `-mcpu=" << cpu_ + << "` is not valid in `-mtriple=" << triple_ << "`" + << ", using default `-mcpu=" << String(defaults::cpu) << "`"; } } - if (const Optional>& v = target->GetAttr>("cl-opt")) { + if (const auto& v = Downcast>>(target.Get("cl-opt"))) { llvm::StringMap& options = llvm::cl::getRegisteredOptions(); bool parse_error = false; for (const String& s : v.value()) { @@ -245,7 +250,7 @@ LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const Target& target) { } llvm::FloatABI::ABIType float_abi = llvm::FloatABI::Default; - if (const Optional& v = target->GetAttr("mfloat-abi")) { + if (const auto& v = Downcast>(target.Get("mfloat-abi"))) { String value = v.value(); if (value == "hard") { float_abi = llvm::FloatABI::Hard; @@ -257,7 +262,7 @@ LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const Target& target) { } // LLVM JIT engine options - if (const Optional& v = target->GetAttr("jit")) { + if (const auto& v = Downcast>(target.Get("jit"))) { String value = v.value(); if ((value == "mcjit") || (value == "orcjit")) { jit_engine_ = value; @@ -283,14 +288,14 @@ LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const Target& target) { target_options_.NoInfsFPMath = false; target_options_.NoNaNsFPMath = true; target_options_.FloatABIType = float_abi; - if (const Optional& v = target->GetAttr("mabi")) { - target_options_.MCOptions.ABIName = v.value(); + if (target.find("mabi") != target.end()) { + target_options_.MCOptions.ABIName = Downcast(target.Get("mabi")); } - auto maybe_level = target->GetAttr("opt-level"); + auto maybe_level = Downcast(target.Get("opt-level")); #if TVM_LLVM_VERSION <= 170 if (maybe_level.defined()) { - int level = maybe_level.value()->value; + int level = maybe_level->value; if (level <= 0) { opt_level_ = llvm::CodeGenOpt::None; } else if (level == 1) { @@ -327,7 +332,7 @@ LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const Target& target) { // Fast math options auto GetBoolFlag = [&target](llvm::StringRef flag) -> bool { - return target->GetAttr(flag.str()).value_or(Bool(false)); + return Downcast(target.Get(flag.str()).value_or(Bool(false))); }; if (GetBoolFlag("fast-math")) { #if TVM_LLVM_VERSION >= 60 @@ -381,41 +386,21 @@ static const llvm::Target* CreateLLVMTargetInstance(const std::string triple, return llvm_instance; } -static llvm::TargetMachine* CreateLLVMTargetMachine( +static std::unique_ptr CreateLLVMTargetMachine( const llvm::Target* llvm_instance, const std::string& triple, const std::string& cpu, - const std::string& features, const llvm::TargetOptions& target_options, - const llvm::Reloc::Model& reloc_model, const llvm::CodeModel::Model& code_model, + const std::string& features, const llvm::TargetOptions& target_options = {}, + const llvm::Reloc::Model& reloc_model = llvm::Reloc::Static, + const llvm::CodeModel::Model& code_model = llvm::CodeModel::Small, #if TVM_LLVM_VERSION <= 170 - const llvm::CodeGenOpt::Level& opt_level) { + const llvm::CodeGenOpt::Level& opt_level = llvm::CodeGenOpt::Level(0)) { #else - const llvm::CodeGenOptLevel& opt_level) { + const llvm::CodeGenOptLevel& opt_level = llvm::CodeGenOptLevel(0)) { #endif llvm::TargetMachine* tm = llvm_instance->createTargetMachine( triple, cpu, features, target_options, reloc_model, code_model, opt_level); ICHECK(tm != nullptr); - return tm; -} - -static const llvm::MCSubtargetInfo* GetLLVMSubtargetInfo(const std::string& triple, - const std::string& cpu_name, - const std::string& feats) { - // create a LLVM instance - auto llvm_instance = CreateLLVMTargetInstance(triple, true); - // create a target machine - // required minimum: llvm::InitializeAllTargetMCs() - llvm::TargetOptions target_options; - auto tm = CreateLLVMTargetMachine(llvm_instance, triple, cpu_name, feats, target_options, - llvm::Reloc::Static, llvm::CodeModel::Small, -#if TVM_LLVM_VERSION <= 170 - llvm::CodeGenOpt::Level(0)); -#else - llvm::CodeGenOptLevel(0)); -#endif - // create subtarget info module - const llvm::MCSubtargetInfo* MCInfo = tm->getMCSubtargetInfo(); - - return MCInfo; + return std::unique_ptr(tm); } llvm::TargetMachine* LLVMTargetInfo::GetOrCreateTargetMachine(bool allow_missing) { @@ -423,10 +408,9 @@ llvm::TargetMachine* LLVMTargetInfo::GetOrCreateTargetMachine(bool allow_missing std::string error; if (const llvm::Target* llvm_instance = CreateLLVMTargetInstance(triple_, allow_missing)) { - llvm::TargetMachine* tm = + target_machine_ = CreateLLVMTargetMachine(llvm_instance, triple_, cpu_, GetTargetFeatureString(), target_options_, reloc_model_, code_model_, opt_level_); - target_machine_ = std::unique_ptr(tm); } ICHECK(target_machine_ != nullptr); return target_machine_.get(); @@ -832,7 +816,11 @@ const Array LLVMTargetInfo::GetAllLLVMTargets() const { const Array LLVMTargetInfo::GetAllLLVMTargetArches() const { Array cpu_arches; // get the subtarget info module - const auto MCInfo = GetLLVMSubtargetInfo(triple_, "", ""); + auto llvm_instance = CreateLLVMTargetInstance(triple_, true); + std::unique_ptr target_machine = + CreateLLVMTargetMachine(llvm_instance, triple_, "", ""); + const auto MCInfo = target_machine->getMCSubtargetInfo(); + if (!MCInfo) { return cpu_arches; } @@ -850,13 +838,17 @@ const Array LLVMTargetInfo::GetAllLLVMTargetArches() const { return cpu_arches; } -const Array LLVMTargetInfo::GetAllLLVMCpuFeatures() const { +const Map LLVMTargetInfo::GetAllLLVMCpuFeatures() const { std::string feats = ""; for (const auto& attr : attrs_) { feats += feats.empty() ? attr : ("," + attr); } // get the subtarget info module - const auto MCInfo = GetLLVMSubtargetInfo(triple_, cpu_.c_str(), feats); + auto llvm_instance = CreateLLVMTargetInstance(triple_, true); + std::unique_ptr target_machine = + CreateLLVMTargetMachine(llvm_instance, triple_, cpu_.c_str(), feats); + const auto MCInfo = target_machine->getMCSubtargetInfo(); + // get all features for CPU llvm::ArrayRef llvm_features = #if TVM_LLVM_VERSION < 180 @@ -864,10 +856,11 @@ const Array LLVMTargetInfo::GetAllLLVMCpuFeatures() const { #else MCInfo->getAllProcessorFeatures(); #endif - Array cpu_features; + // TVM doesn't have an FFI friendly Set, so use a Map instead for now + Map cpu_features; for (const auto& feat : llvm_features) { if (MCInfo->checkFeatures("+" + std::string(feat.Key))) { - cpu_features.push_back(feat.Key); + cpu_features.Set(feat.Key, ""); } } @@ -877,9 +870,7 @@ const Array LLVMTargetInfo::GetAllLLVMCpuFeatures() const { const bool LLVMTargetInfo::TargetHasCPUFeature(const std::string& feature) const { // lookup features for `-mcpu` auto feats = GetAllLLVMCpuFeatures(); - bool has_feature = - std::any_of(feats.begin(), feats.end(), [&](const auto& var) { return var == feature; }); - + bool has_feature = feats.find(feature) != feats.end(); return has_feature; } diff --git a/src/target/llvm/llvm_instance.h b/src/target/llvm/llvm_instance.h index f3948b7a01d29..fd63140a0b37d 100644 --- a/src/target/llvm/llvm_instance.h +++ b/src/target/llvm/llvm_instance.h @@ -156,6 +156,14 @@ class LLVMTargetInfo { */ // NOLINTNEXTLINE(runtime/references) LLVMTargetInfo(LLVMInstance& scope, const std::string& target_str); + /*! + * \brief Constructs LLVMTargetInfo from `Target` + * \param scope LLVMInstance object + * \param target TVM JSON Target object for target "llvm" + */ + // NOLINTNEXTLINE(runtime/references) + LLVMTargetInfo(LLVMInstance& instance, const TargetJSON& target); + /*! * \brief Destroys LLVMTargetInfo object */ @@ -290,11 +298,12 @@ class LLVMTargetInfo { /*! * \brief Get all CPU features from target - * \return list with all valid cpu features + * \return Map with all valid cpu features as keys and empty string as value. The Map + * is intended to be used as a Set, which TVM does not currently support. * \note The features are fetched from the LLVM backend using the target `-mtriple` * and the `-mcpu` architecture, but also consider the `-mattr` attributes. */ - const Array GetAllLLVMCpuFeatures() const; + const Map GetAllLLVMCpuFeatures() const; /*! * \brief Check the target if has a specific cpu feature diff --git a/src/target/llvm/llvm_module.cc b/src/target/llvm/llvm_module.cc index c332314a3e6c7..baa68feedfa21 100644 --- a/src/target/llvm/llvm_module.cc +++ b/src/target/llvm/llvm_module.cc @@ -697,12 +697,12 @@ TVM_REGISTER_GLOBAL("target.llvm_get_cpu_archlist") }); TVM_REGISTER_GLOBAL("target.llvm_get_cpu_features") - .set_body_typed([](const Target& target) -> Array { + .set_body_typed([](const Target& target) -> Map { auto use_target = target.defined() ? target : Target::Current(false); // ignore non "llvm" target if (target.defined()) { if (target->kind->name != "llvm") { - return Array{}; + return {}; } } auto llvm_instance = std::make_unique(); @@ -722,8 +722,7 @@ TVM_REGISTER_GLOBAL("target.llvm_cpu_has_feature") auto llvm_instance = std::make_unique(); LLVMTargetInfo llvm_backend(*llvm_instance, use_target); auto cpu_features = llvm_backend.GetAllLLVMCpuFeatures(); - bool has_feature = std::any_of(cpu_features.begin(), cpu_features.end(), - [&](auto& var) { return var == feature; }); + bool has_feature = cpu_features.find(feature) != cpu_features.end(); return has_feature; }); diff --git a/src/target/parsers/aprofile.cc b/src/target/parsers/aprofile.cc index 622ec5cc3fbf1..907e0cae72d27 100644 --- a/src/target/parsers/aprofile.cc +++ b/src/target/parsers/aprofile.cc @@ -24,9 +24,11 @@ #include "aprofile.h" +#include #include #include "../../support/utils.h" +#include "../llvm/llvm_instance.h" namespace tvm { namespace target { @@ -52,33 +54,6 @@ double GetArchVersion(Optional> attr) { return GetArchVersion(attr.value()); } -static inline bool HasFlag(String attr, std::string flag) { - std::string attr_str = attr; - return attr_str.find(flag) != std::string::npos; -} - -static inline bool HasFlag(Optional attr, std::string flag) { - if (!attr) { - return false; - } - return HasFlag(attr.value(), flag); -} - -static inline bool HasFlag(Optional> attr, std::string flag) { - if (!attr) { - return false; - } - Array attr_array = attr.value(); - - auto matching_attr = std::find_if(attr_array.begin(), attr_array.end(), - [flag](String attr_str) { return HasFlag(attr_str, flag); }); - return matching_attr != attr_array.end(); -} - -static bool HasFlag(Optional mcpu, Optional> mattr, std::string flag) { - return HasFlag(mcpu, flag) || HasFlag(mattr, flag); -} - bool IsAArch32(Optional mtriple, Optional mcpu) { if (mtriple) { bool is_mprofile = mcpu && support::StartsWith(mcpu.value(), "cortex-m"); @@ -101,39 +76,46 @@ bool IsArch(TargetJSON attrs) { return IsAArch32(mtriple, mcpu) || IsAArch64(mtriple); } -static TargetFeatures GetFeatures(TargetJSON target) { - Optional mcpu = Downcast>(target.Get("mcpu")); - Optional mtriple = Downcast>(target.Get("mtriple")); - Optional> mattr = Downcast>>(target.Get("mattr")); +bool CheckContains(Array array, String predicate) { + return std::any_of(array.begin(), array.end(), [&](String var) { return var == predicate; }); +} - const double arch_version = GetArchVersion(mattr); +static TargetFeatures GetFeatures(TargetJSON target) { +#ifdef TVM_LLVM_VERSION + String kind = Downcast(target.Get("kind")); + ICHECK_EQ(kind, "llvm") << "Expected target kind 'llvm', but got '" << kind << "'"; - const bool is_aarch64 = IsAArch64(mtriple); + Optional mtriple = Downcast>(target.Get("mtriple")); + Optional mcpu = Downcast>(target.Get("mcpu")); - const bool simd_flag = HasFlag(mcpu, mattr, "+neon") || HasFlag(mcpu, mattr, "+simd"); - const bool has_asimd = is_aarch64 || simd_flag; - const bool has_sve = HasFlag(mcpu, mattr, "+sve"); + // Check that LLVM has been compiled with the correct target support + auto llvm_instance = std::make_unique(); + codegen::LLVMTargetInfo llvm_backend(*llvm_instance, {{"kind", String("llvm")}}); + Array targets = llvm_backend.GetAllLLVMTargets(); + if ((IsAArch64(mtriple) && !CheckContains(targets, "aarch64")) || + (IsAArch32(mtriple, mcpu) && !CheckContains(targets, "arm"))) { + LOG(WARNING) << "Cannot parse target features. LLVM was not compiled with support for " + "Arm(R)-based targets."; + return {}; + } - const bool i8mm_flag = HasFlag(mcpu, mattr, "+i8mm"); - const bool i8mm_disable = HasFlag(mcpu, mattr, "+noi8mm"); - const bool i8mm_default = arch_version >= 8.6; - const bool i8mm_support = arch_version >= 8.2 && arch_version <= 8.5; - const bool has_i8mm = (i8mm_default && !i8mm_disable) || (i8mm_support && i8mm_flag); + codegen::LLVMTargetInfo llvm_target(*llvm_instance, target); + Map features = llvm_target.GetAllLLVMCpuFeatures(); - const bool dotprod_flag = HasFlag(mcpu, mattr, "+dotprod"); - const bool dotprod_disable = HasFlag(mcpu, mattr, "+nodotprod"); - const bool dotprod_default = arch_version >= 8.4; - const bool dotprod_support = arch_version >= 8.2 && arch_version <= 8.3; - const bool has_dotprod = - (dotprod_default && !dotprod_disable) || (dotprod_support && dotprod_flag); + auto has_feature = [features](const String& feature) { + return features.find(feature) != features.end(); + }; - const bool fp16_flag = HasFlag(mcpu, mattr, "+fullfp16"); - const bool fp16_support = arch_version >= 8.2; - const bool has_fp16_simd = fp16_support && (fp16_flag || has_sve); + return {{"is_aarch64", Bool(IsAArch64(mtriple))}, + {"has_asimd", Bool(has_feature("neon"))}, + {"has_sve", Bool(has_feature("sve"))}, + {"has_dotprod", Bool(has_feature("dotprod"))}, + {"has_matmul_i8", Bool(has_feature("i8mm"))}, + {"has_fp16_simd", Bool(has_feature("fullfp16"))}}; +#endif - return {{"is_aarch64", Bool(is_aarch64)}, {"has_asimd", Bool(has_asimd)}, - {"has_sve", Bool(has_sve)}, {"has_dotprod", Bool(has_dotprod)}, - {"has_matmul_i8", Bool(has_i8mm)}, {"has_fp16_simd", Bool(has_fp16_simd)}}; + LOG(WARNING) << "Cannot parse Arm(R)-based target features without LLVM support."; + return {}; } static Array MergeKeys(Optional> existing_keys) { diff --git a/tests/cpp/target/parsers/aprofile_test.cc b/tests/cpp/target/parsers/aprofile_test.cc index fa85d1c32989c..a134e162fc2d6 100644 --- a/tests/cpp/target/parsers/aprofile_test.cc +++ b/tests/cpp/target/parsers/aprofile_test.cc @@ -19,42 +19,89 @@ #include "../src/target/parsers/aprofile.h" +#include #include #include #include +#include "../src/target/llvm/llvm_instance.h" + namespace tvm { namespace target { namespace parsers { namespace aprofile { +using ::testing::HasSubstr; + static float defaultI8MM = 8.6; static float optionalI8MM[] = {8.2, 8.3, 8.4, 8.5}; static float defaultDotProd = 8.4; static float optionalDotProd[] = {8.2, 8.3}; -class AProfileOptionalI8MM : public testing::TestWithParam {}; -class AProfileOptionalDotProd : public testing::TestWithParam {}; +static bool CheckArchitectureAvailability() { +#if TVM_LLVM_VERSION > 120 + auto llvm_instance = std::make_unique(); + codegen::LLVMTargetInfo llvm_backend(*llvm_instance, "llvm"); + Array targets = llvm_backend.GetAllLLVMTargets(); + int expected_target_count = 0; + for (String target : targets) { + if (target == "aarch64" || target == "arm") { + expected_target_count += 1; + } + } + if (expected_target_count >= 2) { + return true; + } +#endif + return false; +} +static bool has_aarch64_and_arm_targets = CheckArchitectureAvailability(); + +class AProfileParser : public ::testing::Test { + public: + // Check that LLVM has been compiled with the required targets, otherwise skip the test. + // Unfortunately, googletest doesn't let you call GTEST_SKIP in SetUpTestSuite() to skip + // the whole suite of tests, so a cached result is checked before each test is run instead. + void SetUp() override { + if (!has_aarch64_and_arm_targets) { + GTEST_SKIP() << "Skipping as LLVM has not been built for Arm(R)-based targets."; + } + } +}; + +class AProfileParserTestWithParam : public AProfileParser, + public testing::WithParamInterface {}; static TargetFeatures ParseTargetWithAttrs(String mcpu, String mtriple, Array mattr) { - return ParseTarget({ - {"mcpu", mcpu}, + TargetJSON target_json = { + {"kind", String("llvm")}, {"mtriple", mtriple}, {"mattr", mattr}, - }); + }; + if (mcpu != "") { + target_json.Set("mcpu", mcpu); + } + return ParseTarget(target_json); +} + +std::string FloatToStringWithoutTrailingZeros(float value) { + std::stringstream ss; + ss << value; + return ss.str(); } -TEST(AProfileParser, ParseTargetKeys) { - TargetJSON target = ParseTarget({}); +TEST_F(AProfileParser, ParseTargetKeys) { + TargetJSON target = ParseTarget({{"kind", String("llvm")}}); Array keys = Downcast>(target.at("keys")); ASSERT_EQ(keys.size(), 2); ASSERT_EQ(keys[0], "arm_cpu"); ASSERT_EQ(keys[1], "cpu"); } -TEST(AProfileParser, ParseTargetWithExistingKeys) { +TEST_F(AProfileParser, ParseTargetWithExistingKeys) { TargetJSON target = ParseTarget({ + {"kind", String("llvm")}, {"keys", Array{"cpu"}}, }); TargetFeatures features = Downcast(target.at("features")); @@ -64,8 +111,9 @@ TEST(AProfileParser, ParseTargetWithExistingKeys) { ASSERT_EQ(keys[1], "arm_cpu"); } -TEST(AProfileParser, ParseTargetWithDuplicateKey) { +TEST_F(AProfileParser, ParseTargetWithDuplicateKey) { TargetJSON target = ParseTarget({ + {"kind", String("llvm")}, {"keys", Array{"cpu", "arm_cpu"}}, }); TargetFeatures features = Downcast(target.at("features")); @@ -75,24 +123,21 @@ TEST(AProfileParser, ParseTargetWithDuplicateKey) { ASSERT_EQ(keys[1], "arm_cpu"); } -TEST(AProfileParser, ParseTargetDefaults) { - TargetJSON target = ParseTarget({}); +TEST_F(AProfileParser, ParseTargetDefaults) { + TargetJSON target = ParseTarget({{"kind", String("llvm")}}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(Downcast(features.at("is_aarch64")), false); - ASSERT_EQ(Downcast(features.at("has_asimd")), false); - ASSERT_EQ(Downcast(features.at("has_dotprod")), false); - ASSERT_EQ(Downcast(features.at("has_matmul_i8")), false); } -TEST(AProfileParser, IsAArch64Triple) { +TEST_F(AProfileParser, IsAArch64Triple) { TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {""}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("is_aarch64")), true); } -TEST(AProfileParser, IsAArch32Triple) { +TEST_F(AProfileParser, IsAArch32Triple) { TargetJSON target = ParseTargetWithAttrs("", "armv7a-arm-none-eabi", {""}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); @@ -109,15 +154,16 @@ TEST(AProfileParser, IsAArch32Triple) { ASSERT_EQ(Downcast(features.at("is_aarch64")), false); } -TEST(AProfileParser, IsAArch32BlankCPU) { +TEST_F(AProfileParser, IsAArch32BlankCPU) { TargetJSON target = ParseTarget({ + {"kind", String("llvm")}, {"mtriple", String("arm-unknown-linux-gnu")}, }); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); } -TEST(AProfileParser, IsAArch32TripleWithAProfile) { +TEST_F(AProfileParser, IsAArch32TripleWithAProfile) { TargetJSON target = ParseTargetWithAttrs("cortex-a53", "armv7a-arm-none-eabi", {""}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); @@ -134,7 +180,7 @@ TEST(AProfileParser, IsAArch32TripleWithAProfile) { ASSERT_EQ(Downcast(features.at("is_aarch64")), false); } -TEST(AProfileParser, IsAArch32TripleWithMProfile) { +TEST_F(AProfileParser, IsAArch32TripleWithMProfile) { TargetJSON target = ParseTargetWithAttrs("cortex-m33", "armv7a-arm-none-eabi", {""}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), false); @@ -148,75 +194,53 @@ TEST(AProfileParser, IsAArch32TripleWithMProfile) { ASSERT_EQ(IsArch(target), false); } -TEST(AProfileParser, AArch64HasASIMD) { +TEST_F(AProfileParser, AArch64HasASIMD) { TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {""}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_asimd")), true); } -TEST(AProfileParser, AArch32NoASIMD) { +TEST_F(AProfileParser, AArch32ASIMD) { TargetJSON target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", {}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); - ASSERT_EQ(Downcast(features.at("has_asimd")), false); + ASSERT_EQ(Downcast(features.at("has_asimd")), true); } -TEST(AProfileParser, AArch32HasASIMDWithOption) { +TEST_F(AProfileParser, AArch32HasASIMDWithOption) { TargetJSON target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", {"+simd"}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_asimd")), true); - - target = ParseTargetWithAttrs("cortex-a+simd", "armv8a-arm-none-eabi", {""}); - features = Downcast(target.at("features")); - ASSERT_EQ(IsArch(target), true); - ASSERT_EQ(Downcast(features.at("has_asimd")), true); } -TEST(AProfileParser, AArch32HasASIMDWithAlternativeOption) { +TEST_F(AProfileParser, AArch32HasASIMDWithAlternativeOption) { TargetJSON target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", {"+neon"}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_asimd")), true); - - target = ParseTargetWithAttrs("cortex-a+neon", "armv8a-arm-none-eabi", {""}); - features = Downcast(target.at("features")); - ASSERT_EQ(IsArch(target), true); - ASSERT_EQ(Downcast(features.at("has_asimd")), true); -} - -TEST(AProfileParser, NoI8MMSupport) { - std::string attr = "+v8.0a"; - TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {attr, "+i8mm"}); - TargetFeatures features = Downcast(target.at("features")); - ASSERT_EQ(IsArch(target), true); - ASSERT_EQ(Downcast(features.at("has_matmul_i8")), false); } -TEST(AProfileParser, DefaultI8MMSupport) { - std::string arch_attr = "+v" + std::to_string(defaultI8MM) + "a"; +TEST_F(AProfileParser, DefaultI8MMSupport) { + std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultI8MM) + "a"; TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_matmul_i8")), true); } -TEST(AProfileParser, DefaultI8MMSupportDisable) { - std::string arch_attr = "+v" + std::to_string(defaultI8MM) + "a"; - TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+noi8mm"}); +TEST_F(AProfileParser, DefaultI8MMSupportDisable) { + std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultI8MM) + "a"; + TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "-i8mm"}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_matmul_i8")), false); - - target = ParseTargetWithAttrs("cortex-a+noi8mm", "aarch64-arm-none-eabi", {arch_attr}); - features = Downcast(target.at("features")); - ASSERT_EQ(IsArch(target), true); - ASSERT_EQ(Downcast(features.at("has_matmul_i8")), false); } +using AProfileOptionalI8MM = AProfileParserTestWithParam; TEST_P(AProfileOptionalI8MM, OptionalI8MMSupport) { - std::string arch_attr = "+v" + std::to_string(GetParam()) + "a"; + std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(GetParam()) + "a"; TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr}); TargetFeatures features = Downcast(target.at("features")); @@ -227,44 +251,27 @@ TEST_P(AProfileOptionalI8MM, OptionalI8MMSupport) { features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_matmul_i8")), true); - - target = ParseTargetWithAttrs("cortex-a+i8mm", "aarch64-arm-none-eabi", {arch_attr}); - features = Downcast(target.at("features")); - ASSERT_EQ(IsArch(target), true); - ASSERT_EQ(Downcast(features.at("has_matmul_i8")), true); -} - -TEST(AProfileParser, NoDotProdSupport) { - std::string attr = "+v8.0a"; - TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {attr, "+dotprod"}); - TargetFeatures features = Downcast(target.at("features")); - ASSERT_EQ(IsArch(target), true); - ASSERT_EQ(Downcast(features.at("has_dotprod")), false); } -TEST(AProfileParser, DefaultDotProdSupport) { - std::string arch_attr = "+v" + std::to_string(defaultDotProd) + "a"; +TEST_F(AProfileParser, DefaultDotProdSupport) { + std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultDotProd) + "a"; TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_dotprod")), true); } -TEST(AProfileParser, DefaultDotProdSupportDisable) { - std::string arch_attr = "+v" + std::to_string(defaultDotProd) + "a"; - TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+nodotprod"}); +TEST_F(AProfileParser, DefaultDotProdSupportDisable) { + std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultDotProd) + "a"; + TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "-dotprod"}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_dotprod")), false); - - target = ParseTargetWithAttrs("cortex-a+nodotprod", "aarch64-arm-none-eabi", {arch_attr}); - features = Downcast(target.at("features")); - ASSERT_EQ(IsArch(target), true); - ASSERT_EQ(Downcast(features.at("has_dotprod")), false); } +using AProfileOptionalDotProd = AProfileParserTestWithParam; TEST_P(AProfileOptionalDotProd, OptionalDotProdSupport) { - std::string arch_attr = "+v" + std::to_string(GetParam()) + "a"; + std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(GetParam()) + "a"; TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr}); TargetFeatures features = Downcast(target.at("features")); @@ -275,24 +282,19 @@ TEST_P(AProfileOptionalDotProd, OptionalDotProdSupport) { features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_dotprod")), true); - - target = ParseTargetWithAttrs("cortex-a+dotprod", "aarch64-arm-none-eabi", {arch_attr}); - features = Downcast(target.at("features")); - ASSERT_EQ(IsArch(target), true); - ASSERT_EQ(Downcast(features.at("has_dotprod")), true); } -TEST(AProfileParser, ArchVersionInvalidLetter) { - std::string arch_attr = "+v" + std::to_string(defaultDotProd) + "b"; +TEST_F(AProfileParser, ArchVersionInvalidLetter) { + std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultDotProd) + "b"; TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr}); TargetFeatures features = Downcast(target.at("features")); ASSERT_EQ(IsArch(target), true); ASSERT_EQ(Downcast(features.at("has_dotprod")), false); } -using AProfileOptionalSVE = testing::TestWithParam; +using AProfileOptionalSVE = AProfileParserTestWithParam; TEST_P(AProfileOptionalSVE, OptionalSVESupport) { - const std::string arch_attr = "+v" + std::to_string(GetParam()) + "a"; + const std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(GetParam()) + "a"; // Check that the "has_sve" feature is not set by default when "+sve" isn't set as an attribute. TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr}); @@ -307,9 +309,25 @@ TEST_P(AProfileOptionalSVE, OptionalSVESupport) { EXPECT_TRUE(Downcast(features.at("has_sve"))); } -using AProfileOptionalFP16 = testing::TestWithParam; +TEST_F(AProfileParser, DefaultSVESupportSVESupport) { + const std::string arch_attr = "+v9a"; + + // Check that the "has_sve" feature is not set by default when "+sve" isn't set as an attribute. + TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr}); + TargetFeatures features = Downcast(target.at("features")); + EXPECT_TRUE(IsArch(target)); + EXPECT_TRUE(Downcast(features.at("has_sve"))); + + // Check that the "has_sve" feature is set when "+sve" is explicitly set as an attribute. + target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+sve"}); + features = Downcast(target.at("features")); + EXPECT_TRUE(IsArch(target)); + EXPECT_TRUE(Downcast(features.at("has_sve"))); +} + +using AProfileOptionalFP16 = AProfileParserTestWithParam; TEST_P(AProfileOptionalFP16, OptionalFP16Support) { - const std::string arch_attr = "+v" + std::to_string(GetParam()) + "a"; + const std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(GetParam()) + "a"; // Check that the "has_fp16_simd" feature is not set by default when "+fullfp16" isn't set as an // attribute. @@ -332,13 +350,68 @@ TEST_P(AProfileOptionalFP16, OptionalFP16Support) { EXPECT_TRUE(Downcast(features.at("has_fp16_simd"))); } -INSTANTIATE_TEST_CASE_P(AProfileParser, AProfileOptionalI8MM, ::testing::ValuesIn(optionalI8MM)); -INSTANTIATE_TEST_CASE_P(AProfileParser, AProfileOptionalDotProd, - ::testing::ValuesIn(optionalDotProd)); -INSTANTIATE_TEST_CASE_P(AProfileParser, AProfileOptionalSVE, - ::testing::Values(8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 9.0)); -INSTANTIATE_TEST_CASE_P(AProfileParser, AProfileOptionalFP16, - ::testing::Values(8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 9.0)); +TEST_F(AProfileParser, DefaultFP16Support) { + const std::string arch_attr = "+v9a"; + + // Check that the "has_fp16_simd" feature is not set by default when "+fullfp16" isn't set as an + // attribute. + TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr}); + TargetFeatures features = Downcast(target.at("features")); + EXPECT_TRUE(IsArch(target)); + EXPECT_TRUE(Downcast(features.at("has_fp16_simd"))); + + // Check that the "has_fp16_simd" feature is set when "+fullfp16" is explicitly set as an + // attribute. + target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+fullfp16"}); + features = Downcast(target.at("features")); + EXPECT_TRUE(IsArch(target)); + EXPECT_TRUE(Downcast(features.at("has_fp16_simd"))); + + // Check that the "has_fp16_simd" feature is set when "+sve" is explicitly set as an attribute. + target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+sve"}); + features = Downcast(target.at("features")); + EXPECT_TRUE(IsArch(target)); + EXPECT_TRUE(Downcast(features.at("has_fp16_simd"))); +} + +TEST_F(AProfileParser, ImpliedFeature) { + TargetJSON target = ParseTargetWithAttrs("", "aarch64-linux-gnu", {"+sve2"}); + TargetFeatures features = Downcast(target.at("features")); + EXPECT_TRUE(Downcast(features.at("has_sve"))); + EXPECT_TRUE(Downcast(features.at("has_asimd"))); +} + +TEST_F(AProfileParser, UnexpectedTargetKind) { + EXPECT_THROW( + { + try { + ParseTarget({{"kind", String("c")}}); + } catch (const tvm::InternalError& e) { + EXPECT_THAT(e.what(), HasSubstr("Expected target kind 'llvm', but got 'c'")); + throw; + } + }, + tvm::InternalError); +} + +TEST(AProfileParserInvalid, LLVMUnsupportedArchitecture) { + if (has_aarch64_and_arm_targets) { + GTEST_SKIP() << "LLVM has been compiled for the correct targets."; + } + TargetJSON target = ParseTarget({{"kind", String("llvm")}}); + TargetFeatures features = Downcast(target.at("features")); + for (auto feature : features) { + ASSERT_EQ(Downcast(feature.second), false); + } +} + +INSTANTIATE_TEST_SUITE_P(AProfileParser, AProfileOptionalI8MM, ::testing::ValuesIn(optionalI8MM)); +INSTANTIATE_TEST_SUITE_P(AProfileParser, AProfileOptionalDotProd, + ::testing::ValuesIn(optionalDotProd)); +INSTANTIATE_TEST_SUITE_P(AProfileParser, AProfileOptionalSVE, + ::testing::Values(8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9)); +INSTANTIATE_TEST_SUITE_P(AProfileParser, AProfileOptionalFP16, + ::testing::Values(8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9)); } // namespace aprofile } // namespace parsers diff --git a/tests/python/relay/strategy/test_select_implementation.py b/tests/python/relay/strategy/test_select_implementation.py index 0ab00e550895e..d0767175d3d8d 100644 --- a/tests/python/relay/strategy/test_select_implementation.py +++ b/tests/python/relay/strategy/test_select_implementation.py @@ -27,6 +27,7 @@ from tvm.relay.testing import run_infer_type, run_opt_pass import tvm.testing from tvm import topi +from tvm.target.codegen import llvm_version_major @pytest.mark.parametrize( @@ -90,6 +91,9 @@ def _get_conv2d_impl(dtype, target): return impl.name +@pytest.mark.skipif( + llvm_version_major() < 15, reason=f"Requires LLVM 15+, got {llvm_version_major()}" +) @pytest.mark.parametrize( "target,expected_impl", [ @@ -119,7 +123,7 @@ def _get_conv2d_impl(dtype, target): ), ( "llvm --device=arm_cpu --mtriple=aarch64-linux-gnu -mattr=+v9a", - "conv2d_NHWC_quantized_interleaved_without_transform.arm_cpu", + "conv2d_NHWC_quantized_native_without_transform.arm_cpu", ), ], ) @@ -131,6 +135,9 @@ def test_int8_conv2d(target, expected_impl): assert selected_impl == expected_impl +@pytest.mark.skipif( + llvm_version_major() < 15, reason=f"Requires LLVM 15+, got {llvm_version_major()}" +) @pytest.mark.parametrize( "target,expected_impl", [ @@ -164,6 +171,9 @@ def test_fp32_conv2d(target, expected_impl): assert selected_impl == expected_impl +@pytest.mark.skipif( + llvm_version_major() < 15, reason=f"Requires LLVM 15+, got {llvm_version_major()}" +) @pytest.mark.parametrize( "target,expected_impl", [ diff --git a/tests/python/target/test_llvm_features_info.py b/tests/python/target/test_llvm_features_info.py index edcbc891c90d2..34e9a582313ad 100644 --- a/tests/python/target/test_llvm_features_info.py +++ b/tests/python/target/test_llvm_features_info.py @@ -22,7 +22,7 @@ LLVM_VERSION = codegen.llvm_version_major() -def test_llvm_targets(): +def test_llvm_targets(capfd): ## ## check LLVM backend @@ -39,20 +39,14 @@ def test_llvm_targets(): assert codegen.llvm_get_system_x86_vendor() == _ffi_api.llvm_get_system_x86_vendor() assert str(codegen.llvm_get_targets()) == str(_ffi_api.llvm_get_targets()) - # check LLVM target -mcpu legality - try: - tvm.target.codegen.llvm_get_cpu_features( - tvm.target.Target("llvm -mtriple=x86_64-linux-gnu -mcpu=dummy") - ) - assert False - except tvm.error.TVMError as e: - msg = str(e) - assert ( - msg.find( - "TVMError: LLVM cpu architecture `-mcpu=dummy` is not valid in `-mtriple=x86_64-linux-gnu`" - ) - != -1 - ) + tvm.target.codegen.llvm_get_cpu_features( + tvm.target.Target("llvm -mtriple=x86_64-linux-gnu -mcpu=dummy") + ) + expected_str = ( + "Error: LLVM cpu architecture `-mcpu=dummy` is not valid in " + "`-mtriple=x86_64-linux-gnu`, using default `-mcpu=generic`" + ) + assert expected_str in capfd.readouterr().err min_llvm_version, llvm_target, cpu_arch, cpu_features, is_supported = tvm.testing.parameters(