[mlir][llvm] Add llvm.target_features features attribute (#71510)

This patch adds a target_features (TargetFeaturesAttr) to the LLVM
dialect to allow setting and querying the features in use on a function.

The motivation for this comes from the Arm SME dialect where we would
like a convenient way to check what variants of an operation are
available based on the CPU features.

Intended usage:

The target_features attribute is populated manually or by a pass:

```mlir
func.func @example() attributes {
   target_features = #llvm.target_features<["+sme", "+sve", "+sme-f64f64"]>
} {
 // ...
}
```

Then within a later rewrite the attribute can be checked, and used to
make lowering decisions.

```c++
// Finds the "target_features" attribute on the parent
// FunctionOpInterface.
auto targetFeatures = LLVM::TargetFeaturesAttr::featuresAt(op);

// Check a feature.
// Returns false if targetFeatures is null or the feature is not in
// the list.
if (!targetFeatures.contains("+sme-f64f64"))
    return failure();
```

For now, this is rather simple just checks if the exact feature is in
the list, though it could be possible to extend with implied features
using information from LLVM.
This commit is contained in:
Benjamin Maxwell 2023-12-05 11:29:31 +00:00 committed by GitHub
parent 77249546aa
commit 17de468df1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 180 additions and 1 deletions

View File

@ -933,4 +933,68 @@ def LLVM_VScaleRangeAttr : LLVM_Attr<"VScaleRange", "vscale_range"> {
"IntegerAttr":$maxRange);
let assemblyFormat = "`<` struct(params) `>`";
}
//===----------------------------------------------------------------------===//
// TargetFeaturesAttr
//===----------------------------------------------------------------------===//
def LLVM_TargetFeaturesAttr : LLVM_Attr<"TargetFeatures", "target_features">
{
let summary = "LLVM target features attribute";
let description = [{
Represents the LLVM target features as a list that can be checked within
passes/rewrites.
Example:
```mlir
#llvm.target_features<["+sme", "+sve", "+sme-f64f64"]>
```
Then within a pass or rewrite the features active at an op can be queried:
```c++
auto targetFeatures = LLVM::TargetFeaturesAttr::featuresAt(op);
if (!targetFeatures.contains("+sme-f64f64"))
return failure();
```
}];
let parameters = (ins OptionalArrayRefParameter<"StringAttr">:$features);
let builders = [
TypeBuilder<(ins "::llvm::StringRef":$features)>,
TypeBuilder<(ins "::llvm::ArrayRef<::llvm::StringRef>":$features)>
];
let extraClassDeclaration = [{
/// Checks if a feature is contained within the features list.
/// Note: Using a StringAttr allows doing pointer-comparisons.
bool contains(::mlir::StringAttr feature) const;
bool contains(::llvm::StringRef feature) const;
bool nullOrEmpty() const {
// Checks if this attribute is null, or the features are empty.
return !bool(*this) || getFeatures().empty();
}
/// Returns the list of features as an LLVM-compatible string.
std::string getFeaturesString() const;
/// Finds the target features on the parent FunctionOpInterface.
/// Note: This assumes the attribute name matches the return value of
/// `getAttributeName()`.
static TargetFeaturesAttr featuresAt(Operation* op);
/// Canonical name for this attribute within MLIR.
static constexpr StringLiteral getAttributeName() {
return StringLiteral("target_features");
}
}];
let assemblyFormat = "`<` `[` (`]`) : ($features^ `]`)? `>`";
let genVerifyDecl = 1;
}
#endif // LLVMIR_ATTRDEFS

View File

@ -1394,7 +1394,8 @@ def LLVM_LLVMFuncOp : LLVM_Op<"func", [
OptionalAttr<UnnamedAddr>:$unnamed_addr,
OptionalAttr<I64Attr>:$alignment,
OptionalAttr<LLVM_VScaleRangeAttr>:$vscale_range,
OptionalAttr<FramePointerKindAttr>:$frame_pointer
OptionalAttr<FramePointerKindAttr>:$frame_pointer,
OptionalAttr<LLVM_TargetFeaturesAttr>:$target_features
);
let regions = (region AnyRegion:$body);

View File

@ -14,6 +14,7 @@
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/DialectImplementation.h"
#include "mlir/Interfaces/FunctionInterfaces.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/BinaryFormat/Dwarf.h"
@ -183,3 +184,67 @@ void printExpressionArg(AsmPrinter &printer, uint64_t opcode,
i++;
});
}
//===----------------------------------------------------------------------===//
// TargetFeaturesAttr
//===----------------------------------------------------------------------===//
TargetFeaturesAttr TargetFeaturesAttr::get(MLIRContext *context,
llvm::ArrayRef<StringRef> features) {
return Base::get(context,
llvm::map_to_vector(features, [&](StringRef feature) {
return StringAttr::get(context, feature);
}));
}
TargetFeaturesAttr TargetFeaturesAttr::get(MLIRContext *context,
StringRef targetFeatures) {
SmallVector<StringRef> features;
targetFeatures.split(features, ',', /*MaxSplit=*/-1,
/*KeepEmpty=*/false);
return get(context, features);
}
LogicalResult
TargetFeaturesAttr::verify(function_ref<InFlightDiagnostic()> emitError,
llvm::ArrayRef<StringAttr> features) {
for (StringAttr featureAttr : features) {
if (!featureAttr || featureAttr.empty())
return emitError() << "target features can not be null or empty";
auto feature = featureAttr.strref();
if (feature[0] != '+' && feature[0] != '-')
return emitError() << "target features must start with '+' or '-'";
if (feature.contains(','))
return emitError() << "target features can not contain ','";
}
return success();
}
bool TargetFeaturesAttr::contains(StringAttr feature) const {
if (nullOrEmpty())
return false;
// Note: Using StringAttr does pointer comparisons.
return llvm::is_contained(getFeatures(), feature);
}
bool TargetFeaturesAttr::contains(StringRef feature) const {
if (nullOrEmpty())
return false;
return llvm::is_contained(getFeatures(), feature);
}
std::string TargetFeaturesAttr::getFeaturesString() const {
std::string featuresString;
llvm::raw_string_ostream ss(featuresString);
llvm::interleave(
getFeatures(), ss, [&](auto &feature) { ss << feature.strref(); }, ",");
return ss.str();
}
TargetFeaturesAttr TargetFeaturesAttr::featuresAt(Operation *op) {
auto parentFunction = op->getParentOfType<FunctionOpInterface>();
if (!parentFunction)
return {};
return parentFunction.getOperation()->getAttrOfType<TargetFeaturesAttr>(
getAttributeName());
}

View File

@ -1627,6 +1627,7 @@ static constexpr std::array ExplicitAttributes{
StringLiteral("aarch64_pstate_za_new"),
StringLiteral("vscale_range"),
StringLiteral("frame-pointer"),
StringLiteral("target-features"),
};
static void processPassthroughAttrs(llvm::Function *func, LLVMFuncOp funcOp) {
@ -1717,6 +1718,12 @@ void ModuleImport::processFunctionAttributes(llvm::Function *func,
stringRefFramePointerKind)
.value()));
}
if (llvm::Attribute attr = func->getFnAttribute("target-features");
attr.isStringAttribute()) {
funcOp.setTargetFeaturesAttr(
LLVM::TargetFeaturesAttr::get(context, attr.getValueAsString()));
}
}
DictionaryAttr

View File

@ -968,6 +968,9 @@ LogicalResult ModuleTranslation::convertOneFunction(LLVMFuncOp func) {
if (func.getArmNewZa())
llvmFunc->addFnAttr("aarch64_pstate_za_new");
if (auto targetFeatures = func.getTargetFeatures())
llvmFunc->addFnAttr("target-features", targetFeatures->getFeaturesString());
if (auto attr = func.getVscaleRange())
llvmFunc->addFnAttr(llvm::Attribute::getWithVScaleRangeArgs(
getLLVMContext(), attr->getMinRange().getInt(),

View File

@ -0,0 +1,9 @@
; RUN: mlir-translate -import-llvm -split-input-file %s | FileCheck %s
; CHECK-LABEL: llvm.func @target_features()
; CHECK-SAME: #llvm.target_features<["+sme", "+sme-f64f64", "+sve"]>
define void @target_features() #0 {
ret void
}
attributes #0 = { "target-features"="+sme,+sme-f64f64,+sve" }

View File

@ -261,6 +261,27 @@ llvm.func @stepvector_intr_wrong_type() -> vector<7xf32> {
// -----
// expected-error @below{{target features can not contain ','}}
llvm.func @invalid_target_feature() attributes { target_features = #llvm.target_features<["+bad,feature", "+test"]> }
{
}
// -----
// expected-error @below{{target features must start with '+' or '-'}}
llvm.func @missing_target_feature_prefix() attributes { target_features = #llvm.target_features<["sme"]> }
{
}
// -----
// expected-error @below{{target features can not be null or empty}}
llvm.func @empty_target_feature() attributes { target_features = #llvm.target_features<["", "+sve"]> }
{
}
// -----
llvm.comdat @__llvm_comdat {
llvm.comdat_selector @foo any
}

View File

@ -0,0 +1,9 @@
// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
// CHECK-LABEL: define void @target_features
// CHECK: attributes #{{.*}} = { "target-features"="+sme,+sve,+sme-f64f64" }
llvm.func @target_features() attributes {
target_features = #llvm.target_features<["+sme", "+sve", "+sme-f64f64"]>
} {
llvm.return
}