Skip to content

Commit

Permalink
[Target] Use LLVM target parser for determining Arm(R) A-Profile Arch…
Browse files Browse the repository at this point in the history
…itecture features

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.

Change-Id: Ic2bf3b68c8af74025ec388d304bd014624c0c585
  • Loading branch information
lhutton1 committed Jan 17, 2024
1 parent fe9814c commit dfcdb3e
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 149 deletions.
37 changes: 19 additions & 18 deletions src/target/llvm/llvm_instance.cc
Original file line number Diff line number Diff line change
Expand Up @@ -199,21 +199,23 @@ 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 =
Expand All @@ -224,7 +226,7 @@ LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance, const Target& target) {
}
}

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()) {
Expand All @@ -245,7 +247,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;
Expand All @@ -268,14 +270,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) {
Expand Down Expand Up @@ -312,7 +314,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
Expand Down Expand Up @@ -831,7 +833,7 @@ 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);
Expand All @@ -845,10 +847,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, "");
}
}

Expand All @@ -858,9 +861,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;
}

Expand Down
13 changes: 11 additions & 2 deletions src/target/llvm/llvm_instance.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -285,11 +293,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
Expand Down
7 changes: 3 additions & 4 deletions src/target/llvm/llvm_module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -545,12 +545,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>();
Expand All @@ -570,8 +570,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;
});

Expand Down
72 changes: 17 additions & 55 deletions src/target/parsers/aprofile.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@

#include "aprofile.h"

#include <memory>
#include <string>

#include "../../support/utils.h"
#include "../llvm/llvm_instance.h"

namespace tvm {
namespace target {
Expand All @@ -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");
Expand All @@ -102,38 +77,25 @@ bool IsArch(TargetJSON attrs) {
}

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"));

const double arch_version = GetArchVersion(mattr);

const bool is_aarch64 = IsAArch64(mtriple);
String kind = Downcast<String>(target.Get("kind"));
ICHECK_EQ(kind, "llvm") << "Expected target kind 'llvm', but got '" << kind << "'";

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");

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);
Optional<String> mtriple = Downcast<Optional<String>>(target.Get("mtriple"));

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 llvm_instance = std::make_unique<codegen::LLVMInstance>();
codegen::LLVMTargetInfo llvm_target(*llvm_instance, target);
Map<String, String> features = llvm_target.GetAllLLVMCpuFeatures();

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);
auto has_feature = [features](const String& feature) {
return features.find(feature) != features.end();
};

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)}};
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"))}};
}

static Array<String> MergeKeys(Optional<Array<String>> existing_keys) {
Expand Down
Loading

0 comments on commit dfcdb3e

Please sign in to comment.