[Mlir-commits] [mlir] [mlir][llvm] Preserve function entry count metadata (PR #204707)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Jun 18 23:47:21 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
Author: sgtpepper (Kuhai9801)
<details>
<summary>Changes</summary>
Fixes #<!-- -->202374.
Preserve LLVM function `!prof` metadata for:
- `synthetic_function_entry_count`
- `function_entry_count` with import GUID operands
The importer now represents the synthetic bit and import GUIDs on `llvm.func`, validates GUID operands before mutating the op, and exports the metadata back through `llvm::Function::setEntryCount`.
Tests cover import/export, verifier failures, synthetic+imports, and malformed import operands.
Checks:
- `git diff --check`
- `git clang-format --diff origin/main -- ...`
- `llvm-as -disable-output` smoke check
Codex was used in making this PR.
---
Full diff: https://github.com/llvm/llvm-project/pull/204707.diff
8 Files Affected:
- (modified) mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td (+11)
- (modified) mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp (+28)
- (modified) mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp (+58-8)
- (modified) mlir/lib/Target/LLVMIR/ModuleTranslation.cpp (+20-3)
- (modified) mlir/test/Dialect/LLVMIR/invalid.mlir (+55)
- (modified) mlir/test/Target/LLVMIR/Import/function-attributes.ll (+78)
- (added) mlir/test/Target/LLVMIR/Import/function-entry-count-roundtrip.ll (+21)
- (modified) mlir/test/Target/LLVMIR/llvmir.mlir (+40-2)
``````````diff
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index 9d112e5ea227e..557e9881bd921 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -2018,6 +2018,15 @@ def LLVM_LLVMFuncOp : LLVM_Op<"func", [
llvm.return
}
```
+
+ The `function_entry_count` attribute models function-level `!prof`
+ entry-count metadata. The `function_entry_count_synthetic` unit attribute
+ selects the `"synthetic_function_entry_count"` metadata label. The
+ `function_entry_count_imports` attribute represents the non-empty set of
+ trailing import GUID operands on real `"function_entry_count"` metadata.
+ Those GUID operands are unsigned 64-bit LLVM GUID bit patterns stored in a
+ signed i64 array attribute in unsigned sorted-unique order. This attribute
+ must not be combined with `function_entry_count_synthetic`.
}];
let arguments = (ins
@@ -2035,6 +2044,8 @@ def LLVM_LLVMFuncOp : LLVM_Op<"func", [
OptionalAttr<DictArrayAttr>:$arg_attrs,
OptionalAttr<DictArrayAttr>:$res_attrs,
OptionalAttr<I64Attr>:$function_entry_count,
+ UnitAttr:$function_entry_count_synthetic,
+ OptionalAttr<DenseI64ArrayAttr>:$function_entry_count_imports,
OptionalAttr<LLVM_MemoryEffectsAttr>:$memory_effects,
DefaultValuedAttr<Visibility, "mlir::LLVM::Visibility::Default">:$visibility_,
UnitAttr:$arm_streaming, UnitAttr:$arm_locally_streaming,
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 58f569abff8ea..7627f7f65b9ed 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -26,6 +26,7 @@
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/Support/Error.h"
@@ -3266,6 +3267,33 @@ LogicalResult LLVMFuncOp::verify() {
if (failed(verifyComdat(*this, getComdat())))
return failure();
+ if (!getFunctionEntryCount()) {
+ if (getFunctionEntryCountSynthetic())
+ return emitOpError() << "requires function_entry_count when "
+ "function_entry_count_synthetic is set";
+ if (getFunctionEntryCountImportsAttr())
+ return emitOpError() << "requires function_entry_count when "
+ "function_entry_count_imports is set";
+ }
+ if (DenseI64ArrayAttr imports = getFunctionEntryCountImportsAttr()) {
+ if (getFunctionEntryCountSynthetic())
+ return emitOpError() << "does not support function_entry_count_imports "
+ "with function_entry_count_synthetic";
+
+ ArrayRef<int64_t> values = imports.asArrayRef();
+ if (values.empty())
+ return emitOpError() << "requires function_entry_count_imports to be "
+ "non-empty when set";
+
+ for (auto [previous, current] :
+ llvm::zip_equal(values.drop_back(), values.drop_front())) {
+ if (static_cast<uint64_t>(previous) >= static_cast<uint64_t>(current))
+ return emitOpError()
+ << "requires function_entry_count_imports to be sorted and "
+ "unique by unsigned GUID value";
+ }
+ }
+
if (isExternal()) {
if (getLinkage() != LLVM::Linkage::External &&
getLinkage() != LLVM::Linkage::ExternWeak)
diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp
index e9cd335835263..d15b940ca342c 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp
@@ -17,12 +17,17 @@
#include "mlir/Support/LLVM.h"
#include "mlir/Target/LLVMIR/ModuleImport.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/MemoryModelRelaxationAnnotations.h"
+#include <optional>
+
+#include <algorithm>
+#include <optional>
using namespace mlir;
using namespace mlir::LLVM;
@@ -102,6 +107,14 @@ getSupportedMetadataImpl(llvm::LLVMContext &llvmContext) {
/// Converts the given profiling metadata `node` to an MLIR profiling attribute
/// and attaches it to the imported operation if the translation succeeds.
/// Returns failure otherwise.
+static std::optional<uint64_t> getUInt64Metadata(llvm::Metadata *metadata) {
+ llvm::ConstantInt *constant =
+ llvm::mdconst::dyn_extract<llvm::ConstantInt>(metadata);
+ if (!constant)
+ return std::nullopt;
+ return constant->getValue().tryZExtValue();
+}
+
static LogicalResult setProfilingAttr(OpBuilder &builder, llvm::MDNode *node,
Operation *op,
LLVM::ModuleImport &moduleImport) {
@@ -112,27 +125,64 @@ static LogicalResult setProfilingAttr(OpBuilder &builder, llvm::MDNode *node,
auto *name = dyn_cast<llvm::MDString>(node->getOperand(0));
if (!name)
return failure();
+ StringRef profName = name->getString();
// Handle function entry count metadata.
- if (name->getString() == llvm::MDProfLabels::FunctionEntryCount) {
+ if (profName == llvm::MDProfLabels::FunctionEntryCount ||
+ profName == llvm::MDProfLabels::SyntheticFunctionEntryCount) {
+ if (node->getNumOperands() < 2)
+ return failure();
+
+ bool isSynthetic =
+ profName == llvm::MDProfLabels::SyntheticFunctionEntryCount;
- // TODO support function entry count metadata with GUID fields.
- if (node->getNumOperands() != 2)
+ // LLVM's semantic import-GUID API only reads trailing GUID operands from
+ // "function_entry_count" metadata. Do not model trailing operands on
+ // "synthetic_function_entry_count" as import GUIDs in MLIR.
+ if (isSynthetic && node->getNumOperands() > 2)
return failure();
- llvm::ConstantInt *entryCount =
- llvm::mdconst::dyn_extract<llvm::ConstantInt>(node->getOperand(1));
- if (!entryCount)
+ std::optional<uint64_t> entryCountValue =
+ getUInt64Metadata(node->getOperand(1));
+ if (!entryCountValue)
return failure();
+
+ SmallVector<uint64_t> importGUIDValues;
+ importGUIDValues.reserve(node->getNumOperands() - 2);
+ for (unsigned idx = 2, e = node->getNumOperands(); idx < e; ++idx) {
+ std::optional<uint64_t> guidValue =
+ getUInt64Metadata(node->getOperand(idx));
+ if (!guidValue)
+ return failure();
+ importGUIDValues.push_back(*guidValue);
+ }
+
+ // Import GUIDs are semantically a set in LLVM. Canonicalize them as
+ // unsigned sorted-unique values before storing the bit patterns in MLIR.
+ llvm::sort(importGUIDValues);
+ importGUIDValues.erase(
+ std::unique(importGUIDValues.begin(), importGUIDValues.end()),
+ importGUIDValues.end());
+
if (auto funcOp = dyn_cast<LLVMFuncOp>(op)) {
- funcOp.setFunctionEntryCount(entryCount->getZExtValue());
+ funcOp.setFunctionEntryCount(*entryCountValue);
+ if (isSynthetic)
+ funcOp.setFunctionEntryCountSynthetic(true);
+ if (!importGUIDValues.empty()) {
+ SmallVector<int64_t> importGUIDs;
+ importGUIDs.reserve(importGUIDValues.size());
+ for (uint64_t guid : importGUIDValues)
+ importGUIDs.push_back(static_cast<int64_t>(guid));
+ funcOp.setFunctionEntryCountImportsAttr(
+ DenseI64ArrayAttr::get(builder.getContext(), importGUIDs));
+ }
return success();
}
return op->emitWarning()
<< "expected function_entry_count to be attached to a function";
}
- if (name->getString() != llvm::MDProfLabels::BranchWeights)
+ if (profName != llvm::MDProfLabels::BranchWeights)
return failure();
// The branch_weights metadata must have at least 2 operands.
if (node->getNumOperands() < 2)
diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
index 442e6e16e955e..1dbed59dae278 100644
--- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
@@ -31,6 +31,7 @@
#include "mlir/Target/LLVMIR/LLVMTranslationInterface.h"
#include "mlir/Target/LLVMIR/TypeToLLVM.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/TypeSwitch.h"
@@ -2003,9 +2004,25 @@ LogicalResult ModuleTranslation::convertFunctionSignatures() {
// Convert function kernel attributes to metadata.
convertFunctionKernelAttributes(function, llvmFunc, *this);
- // Convert function_entry_count attribute to metadata.
- if (std::optional<uint64_t> entryCount = function.getFunctionEntryCount())
- llvmFunc->setEntryCount(entryCount.value());
+ // Convert function_entry_count attributes to metadata.
+ if (std::optional<uint64_t> entryCount = function.getFunctionEntryCount()) {
+ llvm::Function::ProfileCount profileCount(
+ entryCount.value(), function.getFunctionEntryCountSynthetic()
+ ? llvm::Function::PCT_Synthetic
+ : llvm::Function::PCT_Real);
+ std::optional<llvm::DenseSet<llvm::GlobalValue::GUID>> importGUIDs;
+ if (DenseI64ArrayAttr imports =
+ function.getFunctionEntryCountImportsAttr()) {
+ importGUIDs.emplace();
+ for (int64_t guid : imports.asArrayRef()) {
+ // The MLIR attribute preserves the unsigned GUID bit pattern in a
+ // signed i64 element.
+ importGUIDs->insert(static_cast<uint64_t>(guid));
+ }
+ }
+ llvmFunc->setEntryCount(profileCount,
+ importGUIDs ? &*importGUIDs : nullptr);
+ }
// Convert result attributes.
if (ArrayAttr allResultAttrs = function.getAllResultAttrs()) {
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index d5ea5c8de862e..6acc7a410a5ce 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -44,6 +44,61 @@ llvm.mlir.global_dtors dtors = [@dtor], priorities = [0 : i32], data = [0 : i32]
////////////////////////////////////////////////////////////////////////////////
+// expected-error at +1{{requires function_entry_count when function_entry_count_synthetic is set}}
+llvm.func @function_entry_count_synthetic_requires_count() attributes {
+ function_entry_count_synthetic
+}
+
+// -----
+
+// expected-error at +1{{requires function_entry_count when function_entry_count_imports is set}}
+llvm.func @function_entry_count_imports_requires_count() attributes {
+ function_entry_count_imports = array<i64: 1234>
+}
+
+// -----
+
+// expected-error at +1{{does not support function_entry_count_imports with function_entry_count_synthetic}}
+llvm.func @function_entry_count_imports_requires_real_count() attributes {
+ function_entry_count = 7 : i64,
+ function_entry_count_imports = array<i64: 1234>,
+ function_entry_count_synthetic
+}
+
+// -----
+
+// expected-error at +1{{requires function_entry_count_imports to be non-empty when set}}
+llvm.func @function_entry_count_imports_non_empty() attributes {
+ function_entry_count = 7 : i64,
+ function_entry_count_imports = array<i64>
+}
+
+// -----
+
+// expected-error at +1{{requires function_entry_count_imports to be sorted and unique by unsigned GUID value}}
+llvm.func @function_entry_count_imports_sorted() attributes {
+ function_entry_count = 7 : i64,
+ function_entry_count_imports = array<i64: 9, 4>
+}
+
+// -----
+
+// expected-error at +1{{requires function_entry_count_imports to be sorted and unique by unsigned GUID value}}
+llvm.func @function_entry_count_imports_unique() attributes {
+ function_entry_count = 7 : i64,
+ function_entry_count_imports = array<i64: 4, 4>
+}
+
+// -----
+
+// expected-error at +1{{requires function_entry_count_imports to be sorted and unique by unsigned GUID value}}
+llvm.func @function_entry_count_imports_unsigned_order() attributes {
+ function_entry_count = 7 : i64,
+ function_entry_count_imports = array<i64: -1, 4>
+}
+
+// -----
+
// Check that parser errors are properly produced and do not crash the compiler.
// -----
diff --git a/mlir/test/Target/LLVMIR/Import/function-attributes.ll b/mlir/test/Target/LLVMIR/Import/function-attributes.ll
index 5d664519e100c..e5584ee6d3824 100644
--- a/mlir/test/Target/LLVMIR/Import/function-attributes.ll
+++ b/mlir/test/Target/LLVMIR/Import/function-attributes.ll
@@ -180,6 +180,84 @@ define void @entry_count() !prof !1 {
; // -----
+; CHECK-LABEL: @synthetic_entry_count
+; CHECK-SAME: attributes {function_entry_count = 7 : i64
+; CHECK-SAME: function_entry_count_synthetic
+define void @synthetic_entry_count() !prof !2 {
+ ret void
+}
+
+!2 = !{!"synthetic_function_entry_count", i64 7}
+
+; // -----
+
+; CHECK-LABEL: @entry_count_imports
+; CHECK-SAME: attributes {function_entry_count = 7 : i64
+; CHECK-SAME: function_entry_count_imports = array<i64: 4, 1234, -1>
+define void @entry_count_imports() !prof !3 {
+ ret void
+}
+
+!3 = !{!"function_entry_count", i64 7, i64 1234, i64 -1, i64 4, i64 1234}
+
+; // -----
+
+; CHECK-LABEL: @synthetic_entry_count_imports
+; CHECK-NOT: function_entry_count
+; expected-warning @unknown {{unhandled function metadata}}
+define void @synthetic_entry_count_imports() !prof !4 {
+ ret void
+}
+
+!4 = !{!"synthetic_function_entry_count", i64 7, i64 1234}
+
+; // -----
+
+; CHECK-LABEL: @entry_count_malformed_import
+; CHECK-NOT: function_entry_count
+; expected-warning @unknown {{unhandled function metadata}}
+define void @entry_count_malformed_import() !prof !5 {
+ ret void
+}
+
+!5 = !{!"function_entry_count", i64 7, !"bad"}
+
+; // -----
+
+; CHECK-LABEL: @entry_count_too_wide_count
+; CHECK-NOT: function_entry_count
+; expected-warning @unknown {{unhandled function metadata}}
+define void @entry_count_too_wide_count() !prof !6 {
+ ret void
+}
+
+!6 = !{!"function_entry_count", i128 18446744073709551616}
+
+; // -----
+
+; CHECK-LABEL: @entry_count_too_wide_import
+; CHECK-NOT: function_entry_count
+; expected-warning @unknown {{unhandled function metadata}}
+define void @entry_count_too_wide_import() !prof !7 {
+ ret void
+}
+
+!7 = !{!"function_entry_count", i64 7, i128 18446744073709551616}
+
+; // -----
+
+; Preserve the raw i64 metadata bit pattern. LLVM's semantic getEntryCount()
+; treats real uint64_t(-1) as unknown, but translation preserves the metadata.
+; CHECK-LABEL: @entry_count_negative_count
+; CHECK-SAME: attributes {function_entry_count = -1 : i64}
+define void @entry_count_negative_count() !prof !8 {
+ ret void
+}
+
+!8 = !{!"function_entry_count", i64 -1}
+
+; // -----
+
; CHECK-LABEL: @func_memory
; CHECK-SAME: attributes {memory_effects = #llvm.memory_effects<other = readwrite, argMem = none, inaccessibleMem = readwrite, errnoMem = readwrite, targetMem0 = readwrite, targetMem1 = readwrite>}
; CHECK: llvm.return
diff --git a/mlir/test/Target/LLVMIR/Import/function-entry-count-roundtrip.ll b/mlir/test/Target/LLVMIR/Import/function-entry-count-roundtrip.ll
new file mode 100644
index 0000000000000..e2b33d3179cd9
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/Import/function-entry-count-roundtrip.ll
@@ -0,0 +1,21 @@
+; RUN: mlir-translate -import-llvm %s | mlir-translate -mlir-to-llvmir | FileCheck %s
+
+define void @synthetic() !prof !0 {
+ ret void
+}
+
+define void @with_import_guid() !prof !1 {
+ ret void
+}
+
+!0 = !{!"synthetic_function_entry_count", i64 7}
+!1 = !{!"function_entry_count", i64 7, i64 1234}
+
+; CHECK-LABEL: define void @synthetic()
+; CHECK-SAME: !prof ![[SYNTH:[0-9]+]]
+
+; CHECK-LABEL: define void @with_import_guid()
+; CHECK-SAME: !prof ![[IMPORTS:[0-9]+]]
+
+; CHECK-DAG: ![[SYNTH]] = !{!"synthetic_function_entry_count", i64 7}
+; CHECK-DAG: ![[IMPORTS]] = !{!"function_entry_count", i64 7, i64 1234}
diff --git a/mlir/test/Target/LLVMIR/llvmir.mlir b/mlir/test/Target/LLVMIR/llvmir.mlir
index 5eca8f19154a1..7a9aedb262333 100644
--- a/mlir/test/Target/LLVMIR/llvmir.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir.mlir
@@ -1893,12 +1893,50 @@ llvm.func @my_allocator(i64) attributes {passthrough = [["allocsize", "429496729
// -----
// CHECK-LABEL: @functionEntryCount
-// CHECK-SAME: !prof ![[PROF_ID:[0-9]*]]
+// CHECK-SAME: !prof ![[PROF_ID:[0-9]+]]
llvm.func @functionEntryCount() attributes {function_entry_count = 4242 : i64} {
llvm.return
}
-// CHECK: ![[PROF_ID]] = !{!"function_entry_count", i64 4242}
+// CHECK-DAG: ![[PROF_ID]] = !{!"function_entry_count", i64 4242}
+
+// -----
+
+// CHECK-LABEL: @syntheticFunctionEntryCount
+// CHECK-SAME: !prof ![[SYNTH_PROF_ID:[0-9]+]]
+llvm.func @syntheticFunctionEntryCount() attributes {
+ function_entry_count = 7 : i64,
+ function_entry_count_synthetic
+} {
+ llvm.return
+}
+
+// CHECK-DAG: ![[SYNTH_PROF_ID]] = !{!"synthetic_function_entry_count", i64 7}
+
+// -----
+
+// CHECK-LABEL: @functionEntryCountWithImports
+// CHECK-SAME: !prof ![[IMPORTS_PROF_ID:[0-9]+]]
+llvm.func @functionEntryCountWithImports() attributes {
+ function_entry_count = 7 : i64,
+ function_entry_count_imports = array<i64: 4, 1234, -1>
+} {
+ llvm.return
+}
+
+// CHECK-DAG: ![[IMPORTS_PROF_ID]] = !{!"function_entry_count", i64 7, i64 4, i64 1234, i64 -1}
+
+// -----
+
+// CHECK-LABEL: @functionEntryCountNegativeCount
+// CHECK-SAME: !prof ![[NEG_PROF_ID:[0-9]+]]
+llvm.func @functionEntryCountNegativeCount() attributes {
+ function_entry_count = -1 : i64
+} {
+ llvm.return
+}
+
+// CHECK-DAG: ![[NEG_PROF_ID]] = !{!"function_entry_count", i64 -1}
// -----
``````````
</details>
https://github.com/llvm/llvm-project/pull/204707
More information about the Mlir-commits
mailing list