This is an automated email from the ASF dual-hosted git repository.

ekalda pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new 726a141649 [Target] Use LLVM target parser for determining Arm(R) 
A-Profile Architecture features (#16425)
726a141649 is described below

commit 726a1416497eeca7bfb7dcdbd799d00b33c39f79
Author: Luke Hutton <[email protected]>
AuthorDate: Wed Mar 27 15:53:46 2024 +0000

    [Target] Use LLVM target parser for determining Arm(R) A-Profile 
Architecture features (#16425)
    
    Currently, target features are determined by a set of fixed checks on
    the target string. This works well for checking support of a small
    number of simple features, but it doesn't scale. Some problems include:
    - There are many non-trivial conditions for which a feature may(not) be
      available. It is easy to miss these with the current implementation.
    - The inclusion of some features in a target string can imply other
      features. For example, "+sve" implies "+neon". This currently isn't
      taken into account.
    - The tests in tests/cpp/target/parsers/aprofile_test.c suggest that
      targets such as "llvm -mcpu=cortex-a+neon" and "llvm -mattr=+noneon"
      are supported target strings. The features will be correctly parsed in
      TVM, however, they are not valid in LLVM. Therefore, it's possible
      that TVM and LLVM have different understanding of the features
      available.
    
    This commit uses the more robust LLVM target parser to determine support
    for the features in TVM. It leverages previous infrastructure added to
    TVM for obtaining a list of all supported features given an input
    target, and uses this to check the existance of certain features we're
    interested in. It should be trivial to grow this list over time. As a
    result of this change, the problems mentioned above are solved.
    
    In the current form, this commit drops support for target strings such
    as "llvm -mcpu=cortex-a+neon" and "llvm -mattr=+noneon". A scan of the
    codebase suggests this functionality is not in use (only in test cases).
    Should we feel the need to support them, or have a smoother migration
    for downstream users of TVM we can add a translator to the parser to
    convert these into LLVM compatible targets.
---
 python/tvm/target/codegen.py                       |   3 +-
 src/target/llvm/llvm_instance.cc                   |  95 ++++----
 src/target/llvm/llvm_instance.h                    |  13 +-
 src/target/llvm/llvm_module.cc                     |   7 +-
 src/target/parsers/aprofile.cc                     |  88 +++----
 tests/cpp/target/parsers/aprofile_test.cc          | 263 +++++++++++++--------
 .../relay/strategy/test_select_implementation.py   |  12 +-
 tests/python/target/test_llvm_features_info.py     |  24 +-
 8 files changed, 282 insertions(+), 223 deletions(-)

diff --git a/python/tvm/target/codegen.py b/python/tvm/target/codegen.py
index b2a92c2ca2..82385e3b68 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 a1359b7850..b3f55594a2 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<String>("mtriple").value_or("default");
+LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const Target& target)
+    : LLVMTargetInfo(instance, target->Export()) {}
 
+LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const TargetJSON& 
target) {
+  triple_ = 
Downcast<String>(target.Get("mtriple").value_or(String("default")));
   if (triple_.empty() || triple_ == "default") {
     triple_ = llvm::sys::getDefaultTargetTriple();
   }
-  cpu_ = target->GetAttr<String>("mcpu").value_or(defaults::cpu);
+  cpu_ = Downcast<String>(target.Get("mcpu").value_or(String(defaults::cpu)));
 
-  if (const Optional<Array<String>>& v = 
target->GetAttr<Array<String>>("mattr")) {
+  if (const auto& v = Downcast<Optional<Array<String>>>(target.Get("mattr"))) {
     for (const String& s : v.value()) {
       attrs_.push_back(s);
     }
   }
   // llvm module target
-  if (target->kind->name == "llvm") {
+  if (Downcast<String>(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<Array<String>>& v = 
target->GetAttr<Array<String>>("cl-opt")) {
+  if (const auto& v = Downcast<Optional<Array<String>>>(target.Get("cl-opt"))) 
{
     llvm::StringMap<llvm::cl::Option*>& 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<String>& v = target->GetAttr<String>("mfloat-abi")) {
+  if (const auto& v = Downcast<Optional<String>>(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<String>& v = target->GetAttr<String>("jit")) {
+  if (const auto& v = Downcast<Optional<String>>(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<String>& v = target->GetAttr<String>("mabi")) {
-    target_options_.MCOptions.ABIName = v.value();
+  if (target.find("mabi") != target.end()) {
+    target_options_.MCOptions.ABIName = Downcast<String>(target.Get("mabi"));
   }
 
-  auto maybe_level = target->GetAttr<Integer>("opt-level");
+  auto maybe_level = Downcast<Integer>(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<Bool>(flag.str()).value_or(Bool(false));
+    return Downcast<Bool>(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<llvm::TargetMachine> 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<llvm::TargetMachine>(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<llvm::TargetMachine>(tm);
   }
   ICHECK(target_machine_ != nullptr);
   return target_machine_.get();
@@ -832,7 +816,11 @@ const Array<String> LLVMTargetInfo::GetAllLLVMTargets() 
const {
 const Array<String> LLVMTargetInfo::GetAllLLVMTargetArches() const {
   Array<String> cpu_arches;
   // get the subtarget info module
-  const auto MCInfo = GetLLVMSubtargetInfo(triple_, "", "");
+  auto llvm_instance = CreateLLVMTargetInstance(triple_, true);
+  std::unique_ptr<llvm::TargetMachine> target_machine =
+      CreateLLVMTargetMachine(llvm_instance, triple_, "", "");
+  const auto MCInfo = target_machine->getMCSubtargetInfo();
+
   if (!MCInfo) {
     return cpu_arches;
   }
@@ -850,13 +838,17 @@ const Array<String> 
LLVMTargetInfo::GetAllLLVMTargetArches() const {
   return cpu_arches;
 }
 
-const Array<String> LLVMTargetInfo::GetAllLLVMCpuFeatures() const {
+const Map<String, String> 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<llvm::TargetMachine> target_machine =
+      CreateLLVMTargetMachine(llvm_instance, triple_, cpu_.c_str(), feats);
+  const auto MCInfo = target_machine->getMCSubtargetInfo();
+
   // get all features for CPU
   llvm::ArrayRef<llvm::SubtargetFeatureKV> llvm_features =
 #if TVM_LLVM_VERSION < 180
@@ -864,10 +856,11 @@ const Array<String> 
LLVMTargetInfo::GetAllLLVMCpuFeatures() const {
 #else
       MCInfo->getAllProcessorFeatures();
 #endif
-  Array<String> cpu_features;
+  // TVM doesn't have an FFI friendly Set, so use a Map instead for now
+  Map<String, String> 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<String> 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 f3948b7a01..fd63140a0b 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<String> GetAllLLVMCpuFeatures() const;
+  const Map<String, String> 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 c332314a3e..baa68feedf 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<String> {
+    .set_body_typed([](const Target& target) -> Map<String, String> {
       auto use_target = target.defined() ? target : Target::Current(false);
       // ignore non "llvm" target
       if (target.defined()) {
         if (target->kind->name != "llvm") {
-          return Array<String>{};
+          return {};
         }
       }
       auto llvm_instance = std::make_unique<LLVMInstance>();
@@ -722,8 +722,7 @@ TVM_REGISTER_GLOBAL("target.llvm_cpu_has_feature")
       auto llvm_instance = std::make_unique<LLVMInstance>();
       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 622ec5cc3f..907e0cae72 100644
--- a/src/target/parsers/aprofile.cc
+++ b/src/target/parsers/aprofile.cc
@@ -24,9 +24,11 @@
 
 #include "aprofile.h"
 
+#include <memory>
 #include <string>
 
 #include "../../support/utils.h"
+#include "../llvm/llvm_instance.h"
 
 namespace tvm {
 namespace target {
@@ -52,33 +54,6 @@ double GetArchVersion(Optional<Array<String>> 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<String> attr, std::string flag) {
-  if (!attr) {
-    return false;
-  }
-  return HasFlag(attr.value(), flag);
-}
-
-static inline bool HasFlag(Optional<Array<String>> attr, std::string flag) {
-  if (!attr) {
-    return false;
-  }
-  Array<String> 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<String> mcpu, Optional<Array<String>> mattr, 
std::string flag) {
-  return HasFlag(mcpu, flag) || HasFlag(mattr, flag);
-}
-
 bool IsAArch32(Optional<String> mtriple, Optional<String> 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<String> mcpu = Downcast<Optional<String>>(target.Get("mcpu"));
-  Optional<String> mtriple = Downcast<Optional<String>>(target.Get("mtriple"));
-  Optional<Array<String>> mattr = 
Downcast<Optional<Array<String>>>(target.Get("mattr"));
+bool CheckContains(Array<String> 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<String>(target.Get("kind"));
+  ICHECK_EQ(kind, "llvm") << "Expected target kind 'llvm', but got '" << kind 
<< "'";
 
-  const bool is_aarch64 = IsAArch64(mtriple);
+  Optional<String> mtriple = Downcast<Optional<String>>(target.Get("mtriple"));
+  Optional<String> mcpu = Downcast<Optional<String>>(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::LLVMInstance>();
+  codegen::LLVMTargetInfo llvm_backend(*llvm_instance, {{"kind", 
String("llvm")}});
+  Array<String> 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<String, String> 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<String> MergeKeys(Optional<Array<String>> existing_keys) {
diff --git a/tests/cpp/target/parsers/aprofile_test.cc 
b/tests/cpp/target/parsers/aprofile_test.cc
index fa85d1c329..a134e162fc 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 <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include <cmath>
 #include <string>
 
+#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<float> {};
-class AProfileOptionalDotProd : public testing::TestWithParam<float> {};
+static bool CheckArchitectureAvailability() {
+#if TVM_LLVM_VERSION > 120
+  auto llvm_instance = std::make_unique<codegen::LLVMInstance>();
+  codegen::LLVMTargetInfo llvm_backend(*llvm_instance, "llvm");
+  Array<String> 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<float> 
{};
 
 static TargetFeatures ParseTargetWithAttrs(String mcpu, String mtriple, 
Array<String> 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<String> keys = Downcast<Array<String>>(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<String>{"cpu"}},
   });
   TargetFeatures features = Downcast<TargetFeatures>(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<String>{"cpu", "arm_cpu"}},
   });
   TargetFeatures features = Downcast<TargetFeatures>(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<TargetFeatures>(target.at("features"));
 
   ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), false);
-  ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), false);
-  ASSERT_EQ(Downcast<Bool>(features.at("has_dotprod")), false);
-  ASSERT_EQ(Downcast<Bool>(features.at("has_matmul_i8")), false);
 }
 
-TEST(AProfileParser, IsAArch64Triple) {
+TEST_F(AProfileParser, IsAArch64Triple) {
   TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {""});
   TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), true);
 }
 
-TEST(AProfileParser, IsAArch32Triple) {
+TEST_F(AProfileParser, IsAArch32Triple) {
   TargetJSON target = ParseTargetWithAttrs("", "armv7a-arm-none-eabi", {""});
   TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
@@ -109,15 +154,16 @@ TEST(AProfileParser, IsAArch32Triple) {
   ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(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<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
@@ -134,7 +180,7 @@ TEST(AProfileParser, IsAArch32TripleWithAProfile) {
   ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), false);
 }
 
-TEST(AProfileParser, IsAArch32TripleWithMProfile) {
+TEST_F(AProfileParser, IsAArch32TripleWithMProfile) {
   TargetJSON target = ParseTargetWithAttrs("cortex-m33", 
"armv7a-arm-none-eabi", {""});
   TargetFeatures features = Downcast<TargetFeatures>(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<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), true);
 }
 
-TEST(AProfileParser, AArch32NoASIMD) {
+TEST_F(AProfileParser, AArch32ASIMD) {
   TargetJSON target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", {});
   TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
-  ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), false);
+  ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), true);
 }
 
-TEST(AProfileParser, AArch32HasASIMDWithOption) {
+TEST_F(AProfileParser, AArch32HasASIMDWithOption) {
   TargetJSON target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", 
{"+simd"});
   TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), true);
-
-  target = ParseTargetWithAttrs("cortex-a+simd", "armv8a-arm-none-eabi", {""});
-  features = Downcast<TargetFeatures>(target.at("features"));
-  ASSERT_EQ(IsArch(target), true);
-  ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), true);
 }
 
-TEST(AProfileParser, AArch32HasASIMDWithAlternativeOption) {
+TEST_F(AProfileParser, AArch32HasASIMDWithAlternativeOption) {
   TargetJSON target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", 
{"+neon"});
   TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), true);
-
-  target = ParseTargetWithAttrs("cortex-a+neon", "armv8a-arm-none-eabi", {""});
-  features = Downcast<TargetFeatures>(target.at("features"));
-  ASSERT_EQ(IsArch(target), true);
-  ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
-  ASSERT_EQ(IsArch(target), true);
-  ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(features.at("has_matmul_i8")), false);
-
-  target = ParseTargetWithAttrs("cortex-a+noi8mm", "aarch64-arm-none-eabi", 
{arch_attr});
-  features = Downcast<TargetFeatures>(target.at("features"));
-  ASSERT_EQ(IsArch(target), true);
-  ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
@@ -227,44 +251,27 @@ TEST_P(AProfileOptionalI8MM, OptionalI8MMSupport) {
   features = Downcast<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(features.at("has_matmul_i8")), true);
-
-  target = ParseTargetWithAttrs("cortex-a+i8mm", "aarch64-arm-none-eabi", 
{arch_attr});
-  features = Downcast<TargetFeatures>(target.at("features"));
-  ASSERT_EQ(IsArch(target), true);
-  ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
-  ASSERT_EQ(IsArch(target), true);
-  ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(features.at("has_dotprod")), false);
-
-  target = ParseTargetWithAttrs("cortex-a+nodotprod", "aarch64-arm-none-eabi", 
{arch_attr});
-  features = Downcast<TargetFeatures>(target.at("features"));
-  ASSERT_EQ(IsArch(target), true);
-  ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
@@ -275,24 +282,19 @@ TEST_P(AProfileOptionalDotProd, OptionalDotProdSupport) {
   features = Downcast<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(features.at("has_dotprod")), true);
-
-  target = ParseTargetWithAttrs("cortex-a+dotprod", "aarch64-arm-none-eabi", 
{arch_attr});
-  features = Downcast<TargetFeatures>(target.at("features"));
-  ASSERT_EQ(IsArch(target), true);
-  ASSERT_EQ(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
   ASSERT_EQ(IsArch(target), true);
   ASSERT_EQ(Downcast<Bool>(features.at("has_dotprod")), false);
 }
 
-using AProfileOptionalSVE = testing::TestWithParam<float>;
+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<Bool>(features.at("has_sve")));
 }
 
-using AProfileOptionalFP16 = testing::TestWithParam<float>;
+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<TargetFeatures>(target.at("features"));
+  EXPECT_TRUE(IsArch(target));
+  EXPECT_TRUE(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
+  EXPECT_TRUE(IsArch(target));
+  EXPECT_TRUE(Downcast<Bool>(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<Bool>(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<TargetFeatures>(target.at("features"));
+  EXPECT_TRUE(IsArch(target));
+  EXPECT_TRUE(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
+  EXPECT_TRUE(IsArch(target));
+  EXPECT_TRUE(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
+  EXPECT_TRUE(IsArch(target));
+  EXPECT_TRUE(Downcast<Bool>(features.at("has_fp16_simd")));
+}
+
+TEST_F(AProfileParser, ImpliedFeature) {
+  TargetJSON target = ParseTargetWithAttrs("", "aarch64-linux-gnu", {"+sve2"});
+  TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
+  EXPECT_TRUE(Downcast<Bool>(features.at("has_sve")));
+  EXPECT_TRUE(Downcast<Bool>(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<TargetFeatures>(target.at("features"));
+  for (auto feature : features) {
+    ASSERT_EQ(Downcast<Bool>(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 0ab00e5508..d0767175d3 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 import te
 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
 
 
[email protected](
+    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
 
 
[email protected](
+    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
 
 
[email protected](
+    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 edcbc891c9..34e9a58231 100644
--- a/tests/python/target/test_llvm_features_info.py
+++ b/tests/python/target/test_llvm_features_info.py
@@ -22,7 +22,7 @@ from tvm.target import _ffi_api, codegen, Target
 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(


Reply via email to