diff --git a/mlir/docs/Dialects/LLVM.md b/mlir/docs/Dialects/LLVM.md --- a/mlir/docs/Dialects/LLVM.md +++ b/mlir/docs/Dialects/LLVM.md @@ -79,6 +79,32 @@ ``` +#### Attribute pass-through + +An LLVM IR dialect function provides a mechanism to forward function-level +attributes to LLVM IR using the `passthrough` attribute. This is an array +attribute containing either string attributes or array attributes. In the former +case, the value of the string is interpreted as the name of LLVM IR function +attribute. In the latter case, the array is expected to contain exactly two +string attributes, the first corresponding to the name of LLVM IR function +attribute, and the second corresponding to its value. Note that even integer +LLVM IR function attributes have their value represented in the string form. + +Example: + +```mlir +llvm.func @func() attributes { + passthrough = ["noinline", // value-less attribute + ["alignstack", "4"], // integer attribute with value + ["other", "attr"]] // attrbute unknown to LLVM +} { + llvm.return +} +``` + +If the attribute is not known to LLVM IR, it will be attached as a string +attribute. + ### LLVM IR operations The following operations are currently supported. The semantics of these diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td @@ -663,7 +663,8 @@ : LLVM_ZeroResultOp<"func", [IsolatedFromAbove, FunctionLike, Symbol]>, Arguments<(ins DefaultValuedAttr:$linkage, - OptionalAttr:$personality)> { + OptionalAttr:$personality, + OptionalAttr:$passthrough)> { let summary = "LLVM dialect function, has wrapped LLVM IR function type"; let regions = (region AnyRegion:$body); diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp --- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp +++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp @@ -560,6 +560,83 @@ return blocks; } +/// Attempts to add an attribute identified by `key`, optionally with the given +/// `value` to LLVM function `llvmFunc`. Reports errors at `loc` if any. If the +/// attribute has a kind known to LLVM IR, create the attribute of this kind, +/// otherwise keep it as a string attribute. Performs additional checks for +/// attributes known to have or not have a value in order to avoid assertions +/// inside LLVM upon construction. +static LogicalResult checkedAddLLVMFnAttribute(Location loc, + llvm::Function *llvmFunc, + StringRef key, + StringRef value = StringRef()) { + auto kind = llvm::Attribute::getAttrKindFromName(key); + if (kind == llvm::Attribute::None) { + llvmFunc->addFnAttr(key, value); + return success(); + } + + if (llvm::Attribute::doesAttrKindHaveArgument(kind)) { + if (value.empty()) + return emitError(loc) << "LLVM attribute '" << key << "' expects a value"; + + int result; + if (!value.getAsInteger(/*Radix=*/0, result)) + llvmFunc->addFnAttr( + llvm::Attribute::get(llvmFunc->getContext(), kind, result)); + else + llvmFunc->addFnAttr(key, value); + return success(); + } + + if (!value.empty()) + return emitError(loc) << "LLVM attribute '" << key + << "' does not expect a value, found '" << value + << "'"; + + llvmFunc->addFnAttr(kind); + return success(); +} + +/// Attaches the attributes listed in the given array attribute to `llvmFunc`. +/// Reports error to `loc` if any and returns immediately. Expects `attributes` +/// to be an array attribute containing either string attributes, treated as +/// value-less LLVM attributes, or array attributes containing two string +/// attributes, with the first string being the name of the corresponding LLVM +/// attribute and the second string beings its value. Note that even integer +/// attributes are expected to have their values expressed as strings. +static LogicalResult +forwardPassthroughAttributes(Location loc, Optional attributes, + llvm::Function *llvmFunc) { + if (!attributes) + return success(); + + for (Attribute attr : *attributes) { + if (auto stringAttr = attr.dyn_cast()) { + if (failed( + checkedAddLLVMFnAttribute(loc, llvmFunc, stringAttr.getValue()))) + return failure(); + continue; + } + + auto arrayAttr = attr.dyn_cast(); + if (!arrayAttr || arrayAttr.size() != 2) + return emitError(loc) + << "expected 'passthrough' to contain string or array attributes"; + + auto keyAttr = arrayAttr[0].dyn_cast(); + auto valueAttr = arrayAttr[1].dyn_cast(); + if (!keyAttr || !valueAttr) + return emitError(loc) + << "expected arrays within 'passthrough' to contain two strings"; + + if (failed(checkedAddLLVMFnAttribute(loc, llvmFunc, keyAttr.getValue(), + valueAttr.getValue()))) + return failure(); + } + return success(); +} + LogicalResult ModuleTranslation::convertOneFunction(LLVMFuncOp func) { // Clear the block and value mappings, they are only relevant within one // function. @@ -637,9 +714,13 @@ llvm::FunctionCallee llvmFuncCst = llvmModule->getOrInsertFunction( function.getName(), cast(function.getType().getUnderlyingType())); - assert(isa(llvmFuncCst.getCallee())); - functionMapping[function.getName()] = - cast(llvmFuncCst.getCallee()); + llvm::Function *llvmFunc = cast(llvmFuncCst.getCallee()); + functionMapping[function.getName()] = llvmFunc; + + // Forward the pass-through attributes to LLVM. + if (failed(forwardPassthroughAttributes(function.getLoc(), + function.passthrough(), llvmFunc))) + return failure(); } // Convert functions. diff --git a/mlir/test/Target/llvmir-invalid.mlir b/mlir/test/Target/llvmir-invalid.mlir --- a/mlir/test/Target/llvmir-invalid.mlir +++ b/mlir/test/Target/llvmir-invalid.mlir @@ -17,3 +17,23 @@ // expected-error @+1 {{unsupported constant value}} llvm.mlir.global internal constant @test([2.5, 7.4]) : !llvm<"[2 x double]"> + +// ----- + +// expected-error @+1 {{LLVM attribute 'noinline' does not expect a value}} +llvm.func @passthrough_unexpected_value() attributes {passthrough = [["noinline", "42"]]} + +// ----- + +// expected-error @+1 {{LLVM attribute 'alignstack' expects a value}} +llvm.func @passthrough_expected_value() attributes {passthrough = ["alignstack"]} + +// ----- + +// expected-error @+1 {{expected 'passthrough' to contain string or array attributes}} +llvm.func @passthrough_wrong_type() attributes {passthrough = [42]} + +// ----- + +// expected-error @+1 {{expected arrays within 'passthrough' to contain two strings}} +llvm.func @passthrough_wrong_type() attributes {passthrough = [[42, 42]]} diff --git a/mlir/test/Target/llvmir.mlir b/mlir/test/Target/llvmir.mlir --- a/mlir/test/Target/llvmir.mlir +++ b/mlir/test/Target/llvmir.mlir @@ -1201,3 +1201,15 @@ llvm.fence syncscope("") release llvm.return } + +// CHECK-LABEL @passthrough +// CHECK: #[[ATTR_GROUP:[0-9]*]] +llvm.func @passthrough() attributes {passthrough = ["noinline", ["alignstack", "4"], "null-pointer-is-valid", ["foo", "bar"]]} { + llvm.return +} + +// CHECK: attributes #[[ATTR_GROUP]] = { +// CHECK-DAG: noinline +// CHECK-DAG: alignstack=4 +// CHECK-DAG: "null-pointer-is-valid" +// CHECK-DAG: "foo"="bar"