[Mlir-commits] [mlir] [MLIR][LLVM] Add function metadata to LLVMFuncOp (PR #203018)

Akimasa Watanuki llvmlistbot at llvm.org
Wed Aug 19 02:08:36 PDT 2026


https://github.com/Men-cotton updated https://github.com/llvm/llvm-project/pull/203018

>From 6f68a64b290604d546f7a7c2585f50f8cab3fc36 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Mon, 8 Jun 2026 20:51:57 +0900
Subject: [PATCH 1/2] [MLIR][LLVM] Add function metadata to LLVMFuncOp

Add a generic LLVM dialect carrier for LLVM IR function metadata on LLVMFuncOp.
Represent attachments as an ordered list so repeated metadata kinds, such as
multiple type metadata attachments, can be preserved while keeping metadata names
language-agnostic.
---
 .../mlir/Dialect/LLVMIR/LLVMAttrDefs.td       | 22 +++++++++++++++++++
 mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td   |  1 +
 mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp      | 18 +++++++++++++++
 mlir/lib/Target/LLVMIR/ModuleTranslation.cpp  |  3 +++
 mlir/test/Dialect/LLVMIR/invalid.mlir         | 20 +++++++++++++++++
 mlir/test/Dialect/LLVMIR/roundtrip.mlir       | 22 +++++++++++++++++++
 6 files changed, 86 insertions(+)

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
index 0c8f90b0de557..a9fd24e605120 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
@@ -1873,6 +1873,28 @@ def LLVM_MDNodeArrayAttr
     : TypedArrayAttrBase<LLVM_MDNodeAttr,
                          "array of #llvm.md_node attributes">;
 
+def LLVM_FunctionMetadataAttr
+    : LLVM_Attr<"FunctionMetadata", "func_metadata"> {
+  let summary = "LLVM function metadata attachment";
+  let description = [{
+    Models one LLVM IR function metadata attachment. LLVM IR allows several
+    attachments with the same metadata kind, so function metadata is represented
+    as an ordered list of these entries instead of a dictionary.
+
+    Example:
+    ```mlir
+    #llvm.func_metadata<"type", <#llvm.md_string<"id">>>
+    ```
+  }];
+  let parameters = (ins "StringAttr":$metadataName, "MDNodeAttr":$node);
+  let assemblyFormat = "`<` $metadataName `,` $node `>`";
+  let genVerifyDecl = 1;
+}
+
+def LLVM_FunctionMetadataArrayAttr
+    : TypedArrayAttrBase<LLVM_FunctionMetadataAttr,
+                         "array of #llvm.func_metadata attributes">;
+
 def LLVM_AnyMDAttr : AnyAttrOf<[
     LLVM_MDStringAttr, LLVM_MDConstantAttr, LLVM_MDGlobalValueAttr,
     LLVM_MDNullAttr, LLVM_MDAddrSpaceCastAttr, LLVM_MDNodeAttr],
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index e670e6699e57d..768c0eee302f5 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -2105,6 +2105,7 @@ def LLVM_LLVMFuncOp : LLVM_Op<"func", [
     OptionalAttr<DenseI32ArrayAttr>:$work_group_size_hint,
     OptionalAttr<DenseI32ArrayAttr>:$reqd_work_group_size,
     OptionalAttr<I32Attr>:$intel_reqd_sub_group_size,
+    OptionalAttr<LLVM_FunctionMetadataArrayAttr>:$function_metadata,
     OptionalAttr<UWTableKindAttr>:$uwtable_kind,
     OptionalAttr<BoolAttr>:$use_sample_profile
   );
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
index b3a7d2e46030d..13482ee6a5392 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
@@ -135,6 +135,24 @@ bool AddressSpaceAttr::isValidPtrIntCast(
   return false;
 }
 
+//===----------------------------------------------------------------------===//
+// FunctionMetadataAttr
+//===----------------------------------------------------------------------===//
+
+LogicalResult
+FunctionMetadataAttr::verify(function_ref<InFlightDiagnostic()> emitError,
+                             StringAttr metadataName, MDNodeAttr node) {
+  (void)node;
+  StringRef name = metadataName.getValue();
+  if (name.empty())
+    return emitError() << "function_metadata entry name must not be empty";
+  if (name == "dbg" || name == "prof") {
+    return emitError() << "reserved function_metadata entry '" << name
+                       << "' must use a dedicated LLVM dialect representation";
+  }
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // AliasScopeAttr
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
index b87a581a5185e..7c01a475029e2 100644
--- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
@@ -2076,6 +2076,9 @@ LogicalResult ModuleTranslation::convertFunctionSignatures() {
     llvmFunc->setLinkage(convertLinkageToLLVM(function.getLinkage()));
     llvmFunc->setCallingConv(convertCConvToLLVM(function.getCConv()));
     mapFunction(function.getName(), llvmFunc);
+    if (function.getFunctionMetadataAttr())
+      return function.emitError()
+             << "not yet implemented: translating function_metadata to LLVM IR";
     addRuntimePreemptionSpecifier(function.getDsoLocal(), llvmFunc);
 
     // Convert function attributes.
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index cf91beb67a96c..e63a8a501ebc6 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -1861,6 +1861,26 @@ llvm.mlir.alias external @y5 : i32 {
 
 // -----
 
+// expected-error at +1{{attribute 'function_metadata' failed to satisfy constraint: array of #llvm.func_metadata attributes}}
+llvm.func @function_metadata_value() attributes {function_metadata = [#llvm.md_string<"int">]}
+
+// -----
+
+// expected-error at +1{{function_metadata entry name must not be empty}}
+llvm.func @empty_function_metadata_name() attributes {function_metadata = [#llvm.func_metadata<"", #llvm.md_node<#llvm.md_string<"x">>>]}
+
+// -----
+
+// expected-error at +1{{reserved function_metadata entry 'dbg' must use a dedicated LLVM dialect representation}}
+llvm.func @reserved_dbg_function_metadata() attributes {function_metadata = [#llvm.func_metadata<"dbg", #llvm.md_node<#llvm.md_string<"x">>>]}
+
+// -----
+
+// expected-error at +1{{reserved function_metadata entry 'prof' must use a dedicated LLVM dialect representation}}
+llvm.func @reserved_prof_function_metadata() attributes {function_metadata = [#llvm.func_metadata<"prof", #llvm.md_node<#llvm.md_string<"function_entry_count">, #llvm.md_const<1 : i64>>>]}
+
+// -----
+
 // expected-error at +1{{attribute 'nodes' failed to satisfy constraint: array of #llvm.md_node attributes}}
 llvm.named_metadata "not_node" [#llvm.md_string<"int">]
 
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index f4805c5541f91..e8490d55f2c05 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -1260,3 +1260,25 @@ llvm.named_metadata "foo.kernel" [
     >
   >
 ]
+
+// CHECK-LABEL: llvm.func @generic_function_metadata
+// CHECK-SAME: function_metadata
+// CHECK-SAME: #llvm.func_metadata<"annotation", <#llvm.md_string<"function annotation">>>
+// CHECK-SAME: #llvm.func_metadata<"type", <#llvm.md_const<0 : i64>, #llvm.md_string<"typeid">>>
+llvm.func @generic_function_metadata() attributes {
+  function_metadata = [
+    #llvm.func_metadata<"annotation", #llvm.md_node<#llvm.md_string<"function annotation">>>,
+    #llvm.func_metadata<"type", #llvm.md_node<#llvm.md_const<0 : i64>, #llvm.md_string<"typeid">>>
+  ]
+}
+
+// CHECK-LABEL: llvm.func @repeated_function_metadata
+// CHECK-SAME: function_metadata
+// CHECK-SAME: #llvm.func_metadata<"type", <#llvm.md_const<0 : i64>, #llvm.md_string<"typeid0">>>
+// CHECK-SAME: #llvm.func_metadata<"type", <#llvm.md_const<0 : i64>, #llvm.md_string<"typeid1">>>
+llvm.func @repeated_function_metadata() attributes {
+  function_metadata = [
+    #llvm.func_metadata<"type", #llvm.md_node<#llvm.md_const<0 : i64>, #llvm.md_string<"typeid0">>>,
+    #llvm.func_metadata<"type", #llvm.md_node<#llvm.md_const<0 : i64>, #llvm.md_string<"typeid1">>>
+  ]
+}

>From 2db4f35b34f1e007193303b86b6260fb84244582 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Wed, 19 Aug 2026 16:44:40 +0900
Subject: [PATCH 2/2] fix: allow generic profile function metadata

---
 mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp   |  4 ++-
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 16 +++++++++++
 mlir/test/Dialect/LLVMIR/invalid.mlir      | 32 ++++++++++++++++++++--
 mlir/test/Dialect/LLVMIR/roundtrip.mlir    | 11 ++++++++
 4 files changed, 60 insertions(+), 3 deletions(-)

diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
index 13482ee6a5392..1391b7f12c296 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
@@ -146,7 +146,9 @@ FunctionMetadataAttr::verify(function_ref<InFlightDiagnostic()> emitError,
   StringRef name = metadataName.getValue();
   if (name.empty())
     return emitError() << "function_metadata entry name must not be empty";
-  if (name == "dbg" || name == "prof") {
+  // MDNodeAttr cannot represent the DISubprogram required for a function's
+  // !dbg attachment.
+  if (name == "dbg") {
     return emitError() << "reserved function_metadata entry '" << name
                        << "' must use a dedicated LLVM dialect representation";
   }
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 374ad4a9dcb83..2654f0a39dbcd 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -3325,6 +3325,22 @@ LogicalResult LLVMFuncOp::verify() {
   if (failed(verifyComdat(*this, getComdat())))
     return failure();
 
+  unsigned numProfileMetadata = 0;
+  if (ArrayAttr functionMetadata = getFunctionMetadataAttr()) {
+    for (auto entry : functionMetadata.getAsRange<FunctionMetadataAttr>()) {
+      if (entry.getMetadataName().getValue() == "prof")
+        ++numProfileMetadata;
+    }
+  }
+  if (numProfileMetadata > 1)
+    return emitOpError("expects at most one 'prof' function_metadata entry");
+  if (numProfileMetadata && isExternal())
+    return emitOpError("function declarations cannot have 'prof' metadata");
+  if (numProfileMetadata && getFunctionEntryCountAttr()) {
+    return emitOpError("cannot have both a 'prof' function_metadata entry and "
+                       "the 'function_entry_count' attribute");
+  }
+
   if (isExternal()) {
     if (getLinkage() != LLVM::Linkage::External &&
         getLinkage() != LLVM::Linkage::ExternWeak)
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index e63a8a501ebc6..5cdb2186a8509 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -1876,8 +1876,36 @@ llvm.func @reserved_dbg_function_metadata() attributes {function_metadata = [#ll
 
 // -----
 
-// expected-error at +1{{reserved function_metadata entry 'prof' must use a dedicated LLVM dialect representation}}
-llvm.func @reserved_prof_function_metadata() attributes {function_metadata = [#llvm.func_metadata<"prof", #llvm.md_node<#llvm.md_string<"function_entry_count">, #llvm.md_const<1 : i64>>>]}
+// expected-error at below{{function declarations cannot have 'prof' metadata}}
+llvm.func @profile_metadata_declaration() attributes {
+  function_metadata = [
+    #llvm.func_metadata<"prof", #llvm.md_node<#llvm.md_string<"unknown">, #llvm.md_string<"sample-pass">>>
+  ]
+}
+
+// -----
+
+// expected-error at below{{expects at most one 'prof' function_metadata entry}}
+llvm.func @repeated_profile_metadata() attributes {
+  function_metadata = [
+    #llvm.func_metadata<"prof", #llvm.md_node<#llvm.md_string<"unknown">, #llvm.md_string<"sample-pass-0">>>,
+    #llvm.func_metadata<"prof", #llvm.md_node<#llvm.md_string<"unknown">, #llvm.md_string<"sample-pass-1">>>
+  ]
+} {
+  llvm.return
+}
+
+// -----
+
+// expected-error at below{{cannot have both a 'prof' function_metadata entry and the 'function_entry_count' attribute}}
+llvm.func @conflicting_profile_metadata() attributes {
+  function_entry_count = #llvm.function_entry_count<entry_count = 1>,
+  function_metadata = [
+    #llvm.func_metadata<"prof", #llvm.md_node<#llvm.md_string<"unknown">, #llvm.md_string<"sample-pass">>>
+  ]
+} {
+  llvm.return
+}
 
 // -----
 
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index e8490d55f2c05..2a200b0151835 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -1272,6 +1272,17 @@ llvm.func @generic_function_metadata() attributes {
   ]
 }
 
+// CHECK-LABEL: llvm.func @generic_profile_metadata
+// CHECK-SAME: function_metadata
+// CHECK-SAME: #llvm.func_metadata<"prof", <#llvm.md_string<"unknown">, #llvm.md_string<"sample-pass">>>
+llvm.func @generic_profile_metadata() attributes {
+  function_metadata = [
+    #llvm.func_metadata<"prof", #llvm.md_node<#llvm.md_string<"unknown">, #llvm.md_string<"sample-pass">>>
+  ]
+} {
+  llvm.return
+}
+
 // CHECK-LABEL: llvm.func @repeated_function_metadata
 // CHECK-SAME: function_metadata
 // CHECK-SAME: #llvm.func_metadata<"type", <#llvm.md_const<0 : i64>, #llvm.md_string<"typeid0">>>



More information about the Mlir-commits mailing list