[Mlir-commits] [mlir] [mlir][func] Refactor FuncToLLVM discardable attributes algorithm (PR #188232)
Hocky Yudhiono
llvmlistbot at llvm.org
Tue Mar 24 06:04:13 PDT 2026
https://github.com/hockyy updated https://github.com/llvm/llvm-project/pull/188232
>From 84822cc1b93e391a26fe16b30c09b83497cf85d1 Mon Sep 17 00:00:00 2001
From: Hocky Yudhiono <hocky.yudhiono at gmail.com>
Date: Tue, 24 Mar 2026 20:05:41 +0800
Subject: [PATCH] [mlir][func] Refactor FuncToLLVM discardable attributes
algorithm
---
mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp | 159 +++++++++++++-----
.../Async/Transforms/AsyncToAsyncRuntime.cpp | 4 +-
.../Conversion/FuncToLLVM/convert-funcs.mlir | 39 ++++-
3 files changed, 160 insertions(+), 42 deletions(-)
diff --git a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
index 2220f61ed8a07..f0a768ee1d207 100644
--- a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
+++ b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
@@ -35,6 +35,7 @@
#include "mlir/Transforms/Passes.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/Type.h"
+#include "llvm/Support/DebugLog.h"
#include "llvm/Support/FormatVariadic.h"
#include <optional>
@@ -47,7 +48,15 @@ namespace mlir {
using namespace mlir;
#define PASS_NAME "convert-func-to-llvm"
-
+#define DEBUG_TYPE PASS_NAME
+
+// Attribute names on `func.func` consumed by this conversion.
+// - `llvm.linkage` is a discardable attribute on `func.func` that lowers to
+// inherent `linkage` on `llvm.func`.
+// - `llvm.bareptr` selects bare-pointer calling convention behavior during
+// function and call lowering.
+// - `llvm.emit_c_interface` is read only by this conversion (C wrapper); it is
+// not an LLVM function attribute and must not be forwarded to `llvm.func`.
static constexpr StringRef varargsAttrName = "func.varargs";
static constexpr StringRef linkageAttrName = "llvm.linkage";
static constexpr StringRef barePtrAttrName = "llvm.bareptr";
@@ -59,19 +68,111 @@ static bool shouldUseBarePtrCallConv(Operation *op,
typeConverter->getOptions().useBarePtrCallConv;
}
+static bool isDiscardableAttr(StringRef name) {
+ return name == linkageAttrName || name == varargsAttrName ||
+ name == LLVM::LLVMDialect::getReadnoneAttrName();
+}
+
/// Only retain those attributes that are not constructed by
/// `LLVMFuncOp::build`.
static void filterFuncAttributes(FunctionOpInterface func,
SmallVectorImpl<NamedAttribute> &result) {
for (const NamedAttribute &attr : func->getDiscardableAttrs()) {
- if (attr.getName() == linkageAttrName ||
- attr.getName() == varargsAttrName ||
- attr.getName() == LLVM::LLVMDialect::getReadnoneAttrName())
+ if (isDiscardableAttr(attr.getName().strref()))
continue;
result.push_back(attr);
}
}
+/// Add custom lowered funcOp to llvm.func attributes here.
+struct LoweredFuncAttrs {
+ LLVM::Linkage linkage = LLVM::Linkage::External;
+ bool hasReadnone = false;
+ SmallVector<NamedAttribute, 4> attrs;
+};
+
+static LogicalResult lowerLinkageAttr(FunctionOpInterface func,
+ Attribute attrValue,
+ LoweredFuncAttrs &lowered) {
+ auto linkageAttr = dyn_cast<mlir::LLVM::LinkageAttr>(attrValue);
+ if (!linkageAttr) {
+ func->emitError() << "Contains " << linkageAttrName
+ << " attribute not of type LLVM::LinkageAttr";
+ return failure();
+ }
+ lowered.linkage = linkageAttr.getLinkage();
+ return success();
+}
+
+static LogicalResult lowerReadnoneAttr(FunctionOpInterface func,
+ Attribute attrValue,
+ LoweredFuncAttrs &lowered) {
+ StringRef readnoneAttrName = LLVM::LLVMDialect::getReadnoneAttrName();
+ if (!isa<UnitAttr>(attrValue)) {
+ func->emitError() << "Contains " << readnoneAttrName
+ << " attribute not of type UnitAttr";
+ return failure();
+ }
+ lowered.hasReadnone = true;
+ return success();
+}
+
+/// Lower discardable function attributes on `func.func` to attributes expected
+/// by `llvm.func`.
+static FailureOr<LoweredFuncAttrs>
+lowerFuncAttributes(FunctionOpInterface func) {
+ MLIRContext *ctx = func->getContext();
+ LoweredFuncAttrs lowered;
+ llvm::SmallDenseSet<StringRef> odsAttrNames(
+ LLVM::LLVMFuncOp::getAttributeNames().begin(),
+ LLVM::LLVMFuncOp::getAttributeNames().end());
+
+ // Obtain specific attributes and add them to the lowered attributes.
+ for (const NamedAttribute &attr : func->getDiscardableAttrs()) {
+ StringRef attrName = attr.getName().strref();
+ if (attrName == linkageAttrName) {
+ if (failed(lowerLinkageAttr(func, attr.getValue(), lowered)))
+ return failure();
+ continue;
+ }
+
+ if (attrName == LLVM::LLVMDialect::getEmitCWrapperAttrName()) {
+ lowered.attrs.emplace_back(attr);
+ continue;
+ }
+
+ if (attrName == barePtrAttrName) {
+ lowered.attrs.emplace_back(attr);
+ continue;
+ }
+
+ if (attrName == LLVM::LLVMDialect::getReadnoneAttrName()) {
+ if (failed(lowerReadnoneAttr(func, attr.getValue(), lowered)))
+ return failure();
+ continue;
+ }
+
+ if (odsAttrNames.contains(attrName)) {
+ continue;
+ }
+
+ // Map llvm.<name> -> inherent <name> when <name> is an LLVMFuncOp ODS attr.
+ StringRef inherent = attrName;
+ LDBG() << "inherent: " << inherent;
+ if (inherent.consume_front("llvm.")) {
+ if (odsAttrNames.contains(inherent)) {
+ LDBG() << "inserting to attrs: " << inherent;
+ lowered.attrs.emplace_back(StringAttr::get(ctx, inherent),
+ attr.getValue());
+ }
+ } else {
+ lowered.attrs.push_back(attr);
+ }
+ }
+
+ return lowered;
+}
+
/// Propagate argument/results attributes.
static void propagateArgResAttrs(OpBuilder &builder, bool resultStructType,
FunctionOpInterface funcOp,
@@ -288,6 +389,7 @@ static void restoreByValRefArgumentType(
}
}
+/// TODO: Refactor this function to be more modular and easier to understand.
FailureOr<LLVM::LLVMFuncOp> mlir::convertFuncOpToLLVMFuncOp(
FunctionOpInterface funcOp, ConversionPatternRewriter &rewriter,
const LLVMTypeConverter &converter, SymbolTableCollection *symbolTables) {
@@ -320,35 +422,10 @@ FailureOr<LLVM::LLVMFuncOp> mlir::convertFuncOpToLLVMFuncOp(
return funcOp.emitError("C interface for variadic functions is not "
"supported yet.");
- // Create an LLVM function, use external linkage by default until MLIR
- // functions have linkage.
- LLVM::Linkage linkage = LLVM::Linkage::External;
- if (funcOp->hasAttr(linkageAttrName)) {
- auto attr =
- dyn_cast<mlir::LLVM::LinkageAttr>(funcOp->getAttr(linkageAttrName));
- if (!attr) {
- funcOp->emitError() << "Contains " << linkageAttrName
- << " attribute not of type LLVM::LinkageAttr";
- return rewriter.notifyMatchFailure(
- funcOp, "Contains linkage attribute not of type LLVM::LinkageAttr");
- }
- linkage = attr.getLinkage();
- }
-
- // Check for invalid attributes.
- StringRef readnoneAttrName = LLVM::LLVMDialect::getReadnoneAttrName();
- if (funcOp->hasAttr(readnoneAttrName)) {
- auto attr = funcOp->getAttrOfType<UnitAttr>(readnoneAttrName);
- if (!attr) {
- funcOp->emitError() << "Contains " << readnoneAttrName
- << " attribute not of type UnitAttr";
- return rewriter.notifyMatchFailure(
- funcOp, "Contains readnone attribute not of type UnitAttr");
- }
- }
-
- SmallVector<NamedAttribute, 4> attributes;
- filterFuncAttributes(funcOp, attributes);
+ FailureOr<LoweredFuncAttrs> loweredAttrs = lowerFuncAttributes(funcOp);
+ if (failed(loweredAttrs))
+ return rewriter.notifyMatchFailure(funcOp,
+ "failed to lower func attributes");
Operation *symbolTableOp = funcOp->getParentWithTrait<OpTrait::SymbolTable>();
@@ -357,10 +434,16 @@ FailureOr<LLVM::LLVMFuncOp> mlir::convertFuncOpToLLVMFuncOp(
symbolTable.remove(funcOp);
}
- auto newFuncOp = LLVM::LLVMFuncOp::create(
- rewriter, funcOp.getLoc(), funcOp.getName(), llvmType, linkage,
- /*dsoLocal=*/false, /*cconv=*/LLVM::CConv::C, /*comdat=*/nullptr,
- attributes);
+ /// TODO: Improve create function API
+ for (auto attr : loweredAttrs->attrs) {
+ LDBG() << "attr: " << attr.getName() << " value: " << attr.getValue();
+ }
+ LDBG() << "loweredAttrs->linkage: " << loweredAttrs->linkage;
+ auto newFuncOp =
+ LLVM::LLVMFuncOp::create(rewriter, funcOp.getLoc(), funcOp.getName(),
+ llvmType, loweredAttrs->linkage,
+ /*dsoLocal=*/false, /*cconv=*/LLVM::CConv::C,
+ /*comdat=*/nullptr, loweredAttrs->attrs);
if (symbolTables && symbolTableOp) {
auto ip = rewriter.getInsertionPoint();
@@ -372,7 +455,7 @@ FailureOr<LLVM::LLVMFuncOp> mlir::convertFuncOpToLLVMFuncOp(
.setVisibility(funcOp.getVisibility());
// Create a memory effect attribute corresponding to readnone.
- if (funcOp->hasAttr(readnoneAttrName)) {
+ if (loweredAttrs->hasReadnone) {
auto memoryAttr = LLVM::MemoryEffectsAttr::get(
rewriter.getContext(), {/*other=*/LLVM::ModRefInfo::NoModRef,
/*argMem=*/LLVM::ModRefInfo::NoModRef,
diff --git a/mlir/lib/Dialect/Async/Transforms/AsyncToAsyncRuntime.cpp b/mlir/lib/Dialect/Async/Transforms/AsyncToAsyncRuntime.cpp
index 112d69ce87f7f..0c5bcfe631c6c 100644
--- a/mlir/lib/Dialect/Async/Transforms/AsyncToAsyncRuntime.cpp
+++ b/mlir/lib/Dialect/Async/Transforms/AsyncToAsyncRuntime.cpp
@@ -242,8 +242,8 @@ static CoroMachinery setupCoroMachinery(func::FuncOp func) {
// The switch-resumed API based coroutine should be marked with
// presplitcoroutine attribute to mark the function as a coroutine.
- func->setAttr("passthrough", builder.getArrayAttr(
- StringAttr::get(ctx, "presplitcoroutine")));
+ func->setAttr("llvm.passthrough", builder.getArrayAttr(StringAttr::get(
+ ctx, "presplitcoroutine")));
CoroMachinery machinery;
machinery.func = func;
diff --git a/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir b/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
index 4cb31f8f92661..5cbb99b8895da 100644
--- a/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
+++ b/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
@@ -63,12 +63,18 @@ func.func @variadic_func(%arg0: i32) attributes { "func.varargs" = true } {
// CHECK-LABEL: llvm.func @target_cpu()
// CHECK-SAME: target_cpu = "gfx90a"
-func.func private @target_cpu() attributes { "target_cpu" = "gfx90a" }
+func.func private @target_cpu() attributes { "llvm.target_cpu" = "gfx90a" }
// CHECK-LABEL: llvm.func @target_features()
// CHECK-SAME: target_features = #llvm.target_features<["+sme", "+sve"]>
func.func private @target_features() attributes {
- "target_features" = #llvm.target_features<["+sme", "+sve"]>
+ "llvm.target_features" = #llvm.target_features<["+sme", "+sve"]>
+}
+
+// CHECK-LABEL: llvm.func @passthrough_attr()
+// CHECK-SAME: passthrough = ["presplitcoroutine"]
+func.func private @passthrough_attr() attributes {
+ "llvm.passthrough" = ["presplitcoroutine"]
}
// -----
@@ -103,3 +109,32 @@ func.func @variadic_func(%arg0: i32) attributes { "func.varargs" = true, "llvm.e
func.func @empty_res_attrs() attributes {res_attrs = []} {
return
}
+
+// -----
+
+// Regression: internal `llvm.linkage` must lower correctly when a `gpu.module` is a sibling in
+// the same parent `module` (host/device split in one file).
+// CHECK-LABEL: llvm.func internal @host_next_to_gpu_module
+// CHECK: gpu.module @gpu_mod
+func.func @host_next_to_gpu_module() attributes { llvm.linkage = #llvm.linkage<internal> } {
+ return
+}
+gpu.module @gpu_mod {
+ gpu.func @gpu_kernel() kernel {
+ gpu.return
+ }
+}
+
+// -----
+
+// Regression: unrecognized attributes without prefix will be ignored
+// CHECK-NOT: internal
+// CHECK: gpu.module @gpu_mod
+func.func @host_next_to_gpu_module() attributes { linkage = #llvm.linkage<internal> } {
+ return
+}
+gpu.module @gpu_mod {
+ gpu.func @gpu_kernel() kernel {
+ gpu.return
+ }
+}
\ No newline at end of file
More information about the Mlir-commits
mailing list