[Mlir-commits] [flang] [mlir] [mlir][func] Refactor FuncToLLVM discardable attributes algorithm (PR #188232)
Hocky Yudhiono
llvmlistbot at llvm.org
Wed Mar 25 00:54:56 PDT 2026
https://github.com/hockyy updated https://github.com/llvm/llvm-project/pull/188232
>From a05f21c434403ff80e9cb299f0fb0dc76fafa642 Mon Sep 17 00:00:00 2001
From: Hocky Yudhiono <hocky.yudhiono at gmail.com>
Date: Tue, 24 Mar 2026 21:16:07 +0800
Subject: [PATCH] [mlir][func] Refactor FuncToLLVM discardable attributes
conventions
---
flang/lib/Optimizer/CodeGen/TargetRewrite.cpp | 6 +-
mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp | 158 ++++++++++++++----
.../Async/Transforms/AsyncToAsyncRuntime.cpp | 4 +-
mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 7 +
.../Conversion/FuncToLLVM/convert-funcs.mlir | 25 ++-
5 files changed, 156 insertions(+), 44 deletions(-)
diff --git a/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp b/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp
index 3ef4703fb41d6..a4847288459b4 100644
--- a/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp
+++ b/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp
@@ -780,13 +780,13 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
for (auto fn : mod.getOps<mlir::func::FuncOp>()) {
if (targetCPUAttr)
- fn->setAttr("target_cpu", targetCPUAttr);
+ fn->setAttr("llvm.target_cpu", targetCPUAttr);
if (tuneCPUAttr)
- fn->setAttr("tune_cpu", tuneCPUAttr);
+ fn->setAttr("llvm.tune_cpu", tuneCPUAttr);
if (targetFeaturesAttr)
- fn->setAttr("target_features", targetFeaturesAttr);
+ fn->setAttr("llvm.target_features", targetFeaturesAttr);
convertSignature<mlir::func::ReturnOp, mlir::func::FuncOp>(fn);
}
diff --git a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
index 2220f61ed8a07..ce974b0e1c253 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,6 +48,7 @@ namespace mlir {
using namespace mlir;
#define PASS_NAME "convert-func-to-llvm"
+#define DEBUG_TYPE PASS_NAME
static constexpr StringRef varargsAttrName = "func.varargs";
static constexpr StringRef linkageAttrName = "llvm.linkage";
@@ -59,19 +61,118 @@ 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 parseLinkageAttr(Attribute attrValue,
+ LLVM::Linkage &linkage) {
+ if (auto linkageAttr = dyn_cast<mlir::LLVM::LinkageAttr>(attrValue)) {
+ linkage = linkageAttr.getLinkage();
+ return success();
+ }
+ return failure();
+}
+
+static LogicalResult parseReadnoneAttr(Attribute attrValue, bool &hasReadnone) {
+ if (isa<UnitAttr>(attrValue)) {
+ hasReadnone = true;
+ return success();
+ }
+ return failure();
+}
+
+/// 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(parseLinkageAttr(attr.getValue(), lowered.linkage))) {
+ func->emitError() << "Contains " << linkageAttrName
+ << " attribute not of type LLVM::LinkageAttr";
+ return failure();
+ }
+ continue;
+ }
+
+ if (attrName == LLVM::LLVMDialect::getReadnoneAttrName()) {
+ if (failed(parseReadnoneAttr(attr.getValue(), lowered.hasReadnone))) {
+ func->emitError() << "Contains "
+ << LLVM::LLVMDialect::getReadnoneAttrName()
+ << " attribute not of type UnitAttr";
+ return failure();
+ }
+ continue;
+ }
+
+ // TODO: discardable attributes should not be used again after this lowering
+ if (attrName == LLVM::LLVMDialect::getEmitCWrapperAttrName()) {
+ lowered.attrs.emplace_back(attr);
+ continue;
+ }
+
+ if (attrName == barePtrAttrName) {
+ lowered.attrs.emplace_back(attr);
+ continue;
+ }
+
+ // TODO: discarding this operation is very breaking, make sure all inherent
+ // attributes have `llvm.*` prefix.
+ if (odsAttrNames.contains(attrName)) {
+ LDBG()
+ << "LLVM specific attributes should use llvm. prefix in the future";
+ // continue;
+ }
+
+ if (isDiscardableAttr(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,17 @@ 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 +456,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/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index d7e844b98dc92..b945cb7fc993a 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -3035,6 +3035,13 @@ void LLVMFuncOp::build(OpBuilder &builder, OperationState &result,
if (functionEntryCount)
result.addAttribute(getFunctionEntryCountAttrName(result.name),
builder.getI64IntegerAttr(functionEntryCount.value()));
+ std::optional<NamedAttribute> duplicate = result.attributes.findDuplicate();
+ if (duplicate.has_value()) {
+ llvm::report_fatal_error(
+ Twine("LLVMFuncOp propagated an attribute that is meant "
+ "to be constructed by the builder: ") +
+ duplicate->getName().str());
+ }
if (argAttrs.empty())
return;
diff --git a/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir b/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
index 4cb31f8f92661..d8fda5c553aeb 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,18 @@ 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
+ }
+}
More information about the Mlir-commits
mailing list