[flang-commits] [flang] [mlir] [mlir][func] Refactor FuncToLLVM discardable attributes algorithm (PR #188232)

Hocky Yudhiono via flang-commits flang-commits at lists.llvm.org
Wed Mar 25 22:12:46 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/3] [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/3] [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/3] [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



More information about the flang-commits mailing list