[Mlir-commits] [flang] [mlir] [mlir][func] Refactor FuncToLLVM discardable attributes algorithm (PR #188232)
Hocky Yudhiono
llvmlistbot at llvm.org
Thu Mar 26 03:30:10 PDT 2026
https://github.com/hockyy updated https://github.com/llvm/llvm-project/pull/188232
>From 6c744b349f762cf9f8b4733aa527664224a5f62c 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 1/7] [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 6546e74514c74..37bb10ddff12a 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
+ }
+}
>From 7690e9d66d5192281d9ad1632b59f3dcc6d4df66 Mon Sep 17 00:00:00 2001
From: Hocky Yudhiono <hocky.yudhiono at gmail.com>
Date: Wed, 25 Mar 2026 21:40:39 +0800
Subject: [PATCH 2/7] [mlir][func] Refactor build function
---
mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td | 9 +-
mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp | 124 ++++++------------
.../Conversion/FuncToLLVM/convert-funcs.mlir | 3 +-
3 files changed, 53 insertions(+), 83 deletions(-)
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index 75c47f087f78e..475f0590c4cd8 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -2066,7 +2066,14 @@ def LLVM_LLVMFuncOp : LLVM_Op<"func", [
CArg<"SymbolRefAttr", "{}">:$comdat,
CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs,
CArg<"ArrayRef<DictionaryAttr>", "{}">:$argAttrs,
- CArg<"std::optional<uint64_t>", "{}">:$functionEntryCount)>
+ CArg<"std::optional<uint64_t>", "{}">:$functionEntryCount)>,
+
+ // new properties-based builder
+ OpBuilder<(ins "const Properties &":$properties,
+ CArg<"ArrayRef<NamedAttribute>", "{}">:$discardableAttributes), [{
+ $_state.addRegion();
+ $_state.getOrAddProperties<Properties>() = properties;
+ $_state.addAttributes(discardableAttributes);}]>
];
let extraClassDeclaration = [{
diff --git a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
index 37bb10ddff12a..013a5236628cf 100644
--- a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
+++ b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
@@ -79,100 +79,69 @@ static void filterFuncAttributes(FunctionOpInterface func,
/// Add custom lowered funcOp to llvm.func attributes here.
struct LoweredFuncAttrs {
- LLVM::Linkage linkage = LLVM::Linkage::External;
- bool hasReadnone = false;
- SmallVector<NamedAttribute, 4> attrs;
+ LLVM::LLVMFuncOp::Properties properties;
+ NamedAttrList discardableAttrs;
};
-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`.
+/// by `llvm.func`.static FailureOr<LoweredFuncAttrs>
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.
+ NamedAttrList inherentAttrs;
+ for (auto &odsName : odsAttrNames) {
+ LDBG() << odsName;
+ }
+
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)) {
+ LDBG() << "LLVM specific attributes should use llvm.* prefix";
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());
- }
+ if (inherent.consume_front("llvm.") && odsAttrNames.contains(inherent)) {
+ LDBG() << "Setting " << inherent << " " << attr.getValue() << "\n";
+ inherentAttrs.set(inherent, attr.getValue()); // collect inherent attrs
} else {
- lowered.attrs.push_back(attr);
+ lowered.discardableAttrs.push_back(attr);
}
}
+ // Convert collected inherent attrs into typed properties.
+ if (!inherentAttrs.empty()) {
+ DictionaryAttr dict = inherentAttrs.getDictionary(ctx);
+ auto emitError = [&] {
+ return func.emitOpError("invalid llvm.func property");
+ };
+ if (failed(LLVM::LLVMFuncOp::setPropertiesFromAttr(lowered.properties, dict,
+ emitError))) {
+ return failure();
+ }
+ }
return lowered;
}
+// In your pass .cpp (anonymous namespace), next to other lowering helpers.
+static void buildLLVMFuncProperties(PatternRewriter &rewriter,
+ FunctionOpInterface srcFunc,
+ Type llvmFuncType,
+ LLVM::LLVMFuncOp::Properties &props) {
+ MLIRContext *ctx = rewriter.getContext();
+ props.sym_name = rewriter.getStringAttr(srcFunc.getName());
+ props.function_type = TypeAttr::get(llvmFuncType);
+ props.setCConv(LLVM::CConvAttr::get(ctx, LLVM::CConv::C));
+ LDBG() << props.getLinkage() << "\n";
+}
+
/// Propagate argument/results attributes.
static void propagateArgResAttrs(OpBuilder &builder, bool resultStructType,
FunctionOpInterface funcOp,
@@ -433,18 +402,10 @@ FailureOr<LLVM::LLVMFuncOp> mlir::convertFuncOpToLLVMFuncOp(
SymbolTable &symbolTable = symbolTables->getSymbolTable(symbolTableOp);
symbolTable.remove(funcOp);
}
-
- /// 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);
+ buildLLVMFuncProperties(rewriter, funcOp, llvmType, loweredAttrs->properties);
+ auto newFuncOp = LLVM::LLVMFuncOp::create(rewriter, funcOp.getLoc(),
+ loweredAttrs->properties,
+ loweredAttrs->discardableAttrs);
if (symbolTables && symbolTableOp) {
auto ip = rewriter.getInsertionPoint();
@@ -456,7 +417,8 @@ FailureOr<LLVM::LLVMFuncOp> mlir::convertFuncOpToLLVMFuncOp(
.setVisibility(funcOp.getVisibility());
// Create a memory effect attribute corresponding to readnone.
- if (loweredAttrs->hasReadnone) {
+ StringRef readnoneAttrName = LLVM::LLVMDialect::getReadnoneAttrName();
+ if (funcOp->hasAttr(readnoneAttrName)) {
auto memoryAttr = LLVM::MemoryEffectsAttr::get(
rewriter.getContext(), {/*other=*/LLVM::ModRefInfo::NoModRef,
/*argMem=*/LLVM::ModRefInfo::NoModRef,
diff --git a/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir b/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
index d8fda5c553aeb..830d5716f2354 100644
--- a/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
+++ b/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
@@ -94,7 +94,8 @@ func.func @caller_private_callee(%arg1: f32) -> i32 {
// -----
-func.func private @badllvmlinkage(i32) attributes { "llvm.linkage" = 3 : i64 } // expected-error {{Contains llvm.linkage attribute not of type LLVM::LinkageAttr}}
+// expected-error at +1{{'func.func' op invalid llvm.func propertyInvalid attribute `linkage` in property conversion: 3 : i64}}
+func.func private @badllvmlinkage(i32) attributes { "llvm.linkage" = 3 : i64 }
// -----
>From 005768b5e7517c753396bae0e7ae7aeb1714680e Mon Sep 17 00:00:00 2001
From: Hocky Yudhiono <hocky.yudhiono at gmail.com>
Date: Thu, 26 Mar 2026 11:33:09 +0800
Subject: [PATCH 3/7] [flang] Fix llvm legacy unprefixed attributes
---
.../lib/Optimizer/Transforms/FunctionAttr.cpp | 51 ++++++++++++++-----
flang/lib/Optimizer/Transforms/VScaleAttr.cpp | 20 ++++++--
flang/test/Transforms/vscale-attr.fir | 10 ++--
3 files changed, 60 insertions(+), 21 deletions(-)
diff --git a/flang/lib/Optimizer/Transforms/FunctionAttr.cpp b/flang/lib/Optimizer/Transforms/FunctionAttr.cpp
index 4655ed6ed0d40..da88374809afe 100644
--- a/flang/lib/Optimizer/Transforms/FunctionAttr.cpp
+++ b/flang/lib/Optimizer/Transforms/FunctionAttr.cpp
@@ -15,6 +15,8 @@
#include "flang/Optimizer/Transforms/Passes.h"
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "llvm/ADT/Twine.h"
+#include <string>
namespace fir {
#define GEN_PASS_DEF_FUNCTIONATTR
@@ -25,6 +27,15 @@ namespace fir {
namespace {
+/// Names of LLVM dialect function properties on `func.func` must use the
+/// `llvm.` prefix so convert-func-to-llvm can recognize them and lower them
+/// into `llvm.func` properties (bare ODS names are ignored as legacy spellings)
+static mlir::StringAttr getLlvmFuncPropertyAttrName(mlir::MLIRContext *ctx,
+ mlir::StringAttr baseName) {
+ std::string storage = (llvm::Twine("llvm.") + baseName.getValue()).str();
+ return mlir::StringAttr::get(ctx, storage);
+}
+
class FunctionAttrPass : public fir::impl::FunctionAttrBase<FunctionAttrPass> {
public:
FunctionAttrPass(const fir::FunctionAttrOptions &options) : Base{options} {}
@@ -73,31 +84,45 @@ void FunctionAttrPass::runOnOperation() {
}
mlir::MLIRContext *context = &getContext();
- if (framePointerKind != mlir::LLVM::framePointerKind::FramePointerKind::None)
- func->setAttr("frame_pointer", mlir::LLVM::FramePointerKindAttr::get(
- context, framePointerKind));
-
auto llvmFuncOpName =
mlir::OperationName(mlir::LLVM::LLVMFuncOp::getOperationName(), context);
+
+ if (framePointerKind != mlir::LLVM::framePointerKind::FramePointerKind::None)
+ func->setAttr(
+ getLlvmFuncPropertyAttrName(
+ context,
+ mlir::LLVM::LLVMFuncOp::getFramePointerAttrName(llvmFuncOpName)),
+ mlir::LLVM::FramePointerKindAttr::get(context, framePointerKind));
+
if (!instrumentFunctionEntry.empty())
- func->setAttr(mlir::LLVM::LLVMFuncOp::getInstrumentFunctionEntryAttrName(
- llvmFuncOpName),
- mlir::StringAttr::get(context, instrumentFunctionEntry));
+ func->setAttr(
+ getLlvmFuncPropertyAttrName(
+ context, mlir::LLVM::LLVMFuncOp::getInstrumentFunctionEntryAttrName(
+ llvmFuncOpName)),
+ mlir::StringAttr::get(context, instrumentFunctionEntry));
if (!instrumentFunctionExit.empty())
- func->setAttr(mlir::LLVM::LLVMFuncOp::getInstrumentFunctionExitAttrName(
- llvmFuncOpName),
- mlir::StringAttr::get(context, instrumentFunctionExit));
+ func->setAttr(
+ getLlvmFuncPropertyAttrName(
+ context, mlir::LLVM::LLVMFuncOp::getInstrumentFunctionExitAttrName(
+ llvmFuncOpName)),
+ mlir::StringAttr::get(context, instrumentFunctionExit));
if (noSignedZerosFPMath)
func->setAttr(
- mlir::LLVM::LLVMFuncOp::getNoSignedZerosFpMathAttrName(llvmFuncOpName),
+ getLlvmFuncPropertyAttrName(
+ context, mlir::LLVM::LLVMFuncOp::getNoSignedZerosFpMathAttrName(
+ llvmFuncOpName)),
mlir::BoolAttr::get(context, true));
if (!reciprocals.empty())
func->setAttr(
- mlir::LLVM::LLVMFuncOp::getReciprocalEstimatesAttrName(llvmFuncOpName),
+ getLlvmFuncPropertyAttrName(
+ context, mlir::LLVM::LLVMFuncOp::getReciprocalEstimatesAttrName(
+ llvmFuncOpName)),
mlir::StringAttr::get(context, reciprocals));
if (!preferVectorWidth.empty())
func->setAttr(
- mlir::LLVM::LLVMFuncOp::getPreferVectorWidthAttrName(llvmFuncOpName),
+ getLlvmFuncPropertyAttrName(
+ context, mlir::LLVM::LLVMFuncOp::getPreferVectorWidthAttrName(
+ llvmFuncOpName)),
mlir::StringAttr::get(context, preferVectorWidth));
LLVM_DEBUG(llvm::dbgs() << "=== End " DEBUG_TYPE " ===\n");
diff --git a/flang/lib/Optimizer/Transforms/VScaleAttr.cpp b/flang/lib/Optimizer/Transforms/VScaleAttr.cpp
index d0e83effbbc45..d18f7a97b540e 100644
--- a/flang/lib/Optimizer/Transforms/VScaleAttr.cpp
+++ b/flang/lib/Optimizer/Transforms/VScaleAttr.cpp
@@ -32,12 +32,14 @@
#include "mlir/Transforms/DialectConversion.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "mlir/Transforms/RegionUtils.h"
+#include "llvm/ADT/Twine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
+#include <mlir/IR/Diagnostics.h>
#include <algorithm>
-#include <mlir/IR/Diagnostics.h>
+#include <string>
namespace fir {
#define GEN_PASS_DEF_VSCALEATTR
@@ -48,6 +50,14 @@ namespace fir {
namespace {
+/// See FunctionAttr.cpp: `llvm.func` properties on `func.func` need the `llvm.`
+/// prefix for convert-func-to-llvm.
+static mlir::StringAttr getLlvmFuncPropertyAttrName(mlir::MLIRContext *ctx,
+ mlir::StringAttr baseName) {
+ std::string storage = (llvm::Twine("llvm.") + baseName.getValue()).str();
+ return mlir::StringAttr::get(ctx, storage);
+}
+
class VScaleAttrPass : public fir::impl::VScaleAttrBase<VScaleAttrPass> {
public:
VScaleAttrPass(const fir::VScaleAttrOptions &options) {
@@ -80,11 +90,15 @@ void VScaleAttrPass::runOnOperation() {
return signalPassFailure();
}
- auto context = &getContext();
+ mlir::MLIRContext *context = &getContext();
+ auto llvmFuncOpName =
+ mlir::OperationName(mlir::LLVM::LLVMFuncOp::getOperationName(), context);
auto intTy = mlir::IntegerType::get(context, 32);
- func->setAttr("vscale_range",
+ func->setAttr(getLlvmFuncPropertyAttrName(
+ context, mlir::LLVM::LLVMFuncOp::getVscaleRangeAttrName(
+ llvmFuncOpName)),
mlir::LLVM::VScaleRangeAttr::get(
context, mlir::IntegerAttr::get(intTy, vscaleMin),
mlir::IntegerAttr::get(intTy, vscaleMax)));
diff --git a/flang/test/Transforms/vscale-attr.fir b/flang/test/Transforms/vscale-attr.fir
index 52b6bbba7c9c9..146b1af7c489f 100644
--- a/flang/test/Transforms/vscale-attr.fir
+++ b/flang/test/Transforms/vscale-attr.fir
@@ -9,11 +9,11 @@
// RUN: not fir-opt --vscale-attr="vscale-min=16 vscale-max=8" %s 2>&1 | FileCheck %s --check-prefix=VSCALE-MIN-GREATER
-// CHECK-DEFAULT: attributes {vscale_range = #llvm.vscale_range<minRange = 1 : i32, maxRange = 0 : i32>}
-// CHECK-MIN: attributes {vscale_range = #llvm.vscale_range<minRange = 4 : i32, maxRange = 0 : i32>}
-// CHECK-MAX: attributes {vscale_range = #llvm.vscale_range<minRange = 1 : i32, maxRange = 16 : i32>}
-// CHECK-BOTH: attributes {vscale_range = #llvm.vscale_range<minRange = 8 : i32, maxRange = 16 : i32>}
-// CHECK-EQUAL: attributes {vscale_range = #llvm.vscale_range<minRange = 8 : i32, maxRange = 8 : i32>}
+// CHECK-DEFAULT: attributes {llvm.vscale_range = #llvm.vscale_range<minRange = 1 : i32, maxRange = 0 : i32>}
+// CHECK-MIN: attributes {llvm.vscale_range = #llvm.vscale_range<minRange = 4 : i32, maxRange = 0 : i32>}
+// CHECK-MAX: attributes {llvm.vscale_range = #llvm.vscale_range<minRange = 1 : i32, maxRange = 16 : i32>}
+// CHECK-BOTH: attributes {llvm.vscale_range = #llvm.vscale_range<minRange = 8 : i32, maxRange = 16 : i32>}
+// CHECK-EQUAL: attributes {llvm.vscale_range = #llvm.vscale_range<minRange = 8 : i32, maxRange = 8 : i32>}
// VSCALE-MIN-0: VScaleAttr: vscaleMin has to be a power-of-two greater than 0
// VSCALE-MIN-NO-PO2: VScaleAttr: vscaleMin has to be a power-of-two greater than 0
>From 8d0090b27281d7033fa9a3b55442941111f5120b Mon Sep 17 00:00:00 2001
From: Hocky Yudhiono <hocky.yudhiono at gmail.com>
Date: Thu, 26 Mar 2026 14:24:45 +0800
Subject: [PATCH 4/7] Clean up codes
---
flang/lib/Optimizer/Transforms/VScaleAttr.cpp | 2 +-
mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp | 9 ++-------
mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir | 3 +--
3 files changed, 4 insertions(+), 10 deletions(-)
diff --git a/flang/lib/Optimizer/Transforms/VScaleAttr.cpp b/flang/lib/Optimizer/Transforms/VScaleAttr.cpp
index d18f7a97b540e..4791e48c826de 100644
--- a/flang/lib/Optimizer/Transforms/VScaleAttr.cpp
+++ b/flang/lib/Optimizer/Transforms/VScaleAttr.cpp
@@ -26,6 +26,7 @@
#include "flang/Optimizer/Transforms/Passes.h"
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/TypeUtilities.h"
#include "mlir/Pass/Pass.h"
@@ -36,7 +37,6 @@
#include "llvm/Support/Debug.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
-#include <mlir/IR/Diagnostics.h>
#include <algorithm>
#include <string>
diff --git a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
index 013a5236628cf..72a4c807a5d4a 100644
--- a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
+++ b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
@@ -95,21 +95,18 @@ lowerFuncAttributes(FunctionOpInterface func) {
LLVM::LLVMFuncOp::getAttributeNames().end());
NamedAttrList inherentAttrs;
- for (auto &odsName : odsAttrNames) {
- LDBG() << odsName;
- }
for (const NamedAttribute &attr : func->getDiscardableAttrs()) {
StringRef attrName = attr.getName().strref();
if (odsAttrNames.contains(attrName)) {
- LDBG() << "LLVM specific attributes should use llvm.* prefix";
+ LDBG() << "LLVM specific attributes: " << attrName
+ << "should use llvm.* prefix, discarding it";
continue;
}
StringRef inherent = attrName;
if (inherent.consume_front("llvm.") && odsAttrNames.contains(inherent)) {
- LDBG() << "Setting " << inherent << " " << attr.getValue() << "\n";
inherentAttrs.set(inherent, attr.getValue()); // collect inherent attrs
} else {
lowered.discardableAttrs.push_back(attr);
@@ -130,7 +127,6 @@ lowerFuncAttributes(FunctionOpInterface func) {
return lowered;
}
-// In your pass .cpp (anonymous namespace), next to other lowering helpers.
static void buildLLVMFuncProperties(PatternRewriter &rewriter,
FunctionOpInterface srcFunc,
Type llvmFuncType,
@@ -139,7 +135,6 @@ static void buildLLVMFuncProperties(PatternRewriter &rewriter,
props.sym_name = rewriter.getStringAttr(srcFunc.getName());
props.function_type = TypeAttr::get(llvmFuncType);
props.setCConv(LLVM::CConvAttr::get(ctx, LLVM::CConv::C));
- LDBG() << props.getLinkage() << "\n";
}
/// Propagate argument/results attributes.
diff --git a/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir b/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
index 830d5716f2354..a648c415bb031 100644
--- a/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
+++ b/mlir/test/Conversion/FuncToLLVM/convert-funcs.mlir
@@ -113,8 +113,7 @@ func.func @empty_res_attrs() attributes {res_attrs = []} {
// -----
-// 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).
+// Internal `llvm.linkage` must lower correctly
// 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> } {
>From 2874fcffa14a1f18cda0e5500c7784016f80249e Mon Sep 17 00:00:00 2001
From: Hocky Yudhiono <hocky.yudhiono at gmail.com>
Date: Thu, 26 Mar 2026 18:28:58 +0800
Subject: [PATCH 5/7] Update mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
Co-authored-by: Mehdi Amini <joker.eph at gmail.com>
---
mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index b945cb7fc993a..ce51884368b69 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -3035,6 +3035,7 @@ void LLVMFuncOp::build(OpBuilder &builder, OperationState &result,
if (functionEntryCount)
result.addAttribute(getFunctionEntryCountAttrName(result.name),
builder.getI64IntegerAttr(functionEntryCount.value()));
+#ifndef NDEBUG
std::optional<NamedAttribute> duplicate = result.attributes.findDuplicate();
if (duplicate.has_value()) {
llvm::report_fatal_error(
@@ -3042,6 +3043,7 @@ void LLVMFuncOp::build(OpBuilder &builder, OperationState &result,
"to be constructed by the builder: ") +
duplicate->getName().str());
}
+#endif
if (argAttrs.empty())
return;
>From c9be336edaec3c459cbe30568051da95038dffbb Mon Sep 17 00:00:00 2001
From: Hocky Yudhiono <hocky.yudhiono at gmail.com>
Date: Thu, 26 Mar 2026 18:29:44 +0800
Subject: [PATCH 6/7] Update mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
Co-authored-by: Mehdi Amini <joker.eph at gmail.com>
---
mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
index 72a4c807a5d4a..17c64ec0061bc 100644
--- a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
+++ b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
@@ -106,11 +106,10 @@ lowerFuncAttributes(FunctionOpInterface func) {
}
StringRef inherent = attrName;
- if (inherent.consume_front("llvm.") && odsAttrNames.contains(inherent)) {
+ if (inherent.consume_front("llvm.") && odsAttrNames.contains(inherent))
inherentAttrs.set(inherent, attr.getValue()); // collect inherent attrs
- } else {
+ else
lowered.discardableAttrs.push_back(attr);
- }
}
// Convert collected inherent attrs into typed properties.
>From 82b8ede0228de8d05d608cd2c13f4496294c32f1 Mon Sep 17 00:00:00 2001
From: Hocky Yudhiono <hocky.yudhiono at gmail.com>
Date: Thu, 26 Mar 2026 18:29:58 +0800
Subject: [PATCH 7/7] Update flang/lib/Optimizer/Transforms/FunctionAttr.cpp
Co-authored-by: Mehdi Amini <joker.eph at gmail.com>
---
flang/lib/Optimizer/Transforms/FunctionAttr.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/flang/lib/Optimizer/Transforms/FunctionAttr.cpp b/flang/lib/Optimizer/Transforms/FunctionAttr.cpp
index da88374809afe..857491fc90cc3 100644
--- a/flang/lib/Optimizer/Transforms/FunctionAttr.cpp
+++ b/flang/lib/Optimizer/Transforms/FunctionAttr.cpp
@@ -32,8 +32,7 @@ namespace {
/// into `llvm.func` properties (bare ODS names are ignored as legacy spellings)
static mlir::StringAttr getLlvmFuncPropertyAttrName(mlir::MLIRContext *ctx,
mlir::StringAttr baseName) {
- std::string storage = (llvm::Twine("llvm.") + baseName.getValue()).str();
- return mlir::StringAttr::get(ctx, storage);
+ return mlir::StringAttr::get(ctx, llvm::Twine("llvm.") + baseName.getValue());
}
class FunctionAttrPass : public fir::impl::FunctionAttrBase<FunctionAttrPass> {
More information about the Mlir-commits
mailing list