Compare commits

...

2 commits

Author SHA1 Message Date
mencotton
396eb66627 [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.
2026-08-01 22:43:29 +09:00
mencotton
c938d3b85f [MLIR][LLVM] Preserve global value metadata operands on import
Import global value references inside LLVM metadata operands as LLVM dialect metadata symbol-reference attributes. Add llvm.read_register import coverage for function, global, alias, and nameless-global metadata operands.
2026-08-01 22:43:09 +09:00
9 changed files with 257 additions and 18 deletions

View file

@ -1810,6 +1810,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_MDNodeAttr],

View file

@ -2077,6 +2077,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
);

View file

@ -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
//===----------------------------------------------------------------------===//

View file

@ -150,6 +150,29 @@ static LogicalResult convertInstructionImpl(OpBuilder &odsBuilder,
return failure();
}
static FlatSymbolRefAttr getMetadataGlobalValueSymbolRef(
MLIRContext *ctx, LLVMImportInterface &iface, llvm::GlobalValue *global,
llvm::function_ref<FlatSymbolRefAttr(llvm::GlobalVariable *)>
getNamelessGlobalSymbol) {
if (auto *globalVar = dyn_cast<llvm::GlobalVariable>(global)) {
StringRef name = globalVar->getName();
if (name.empty())
return getNamelessGlobalSymbol(globalVar);
if (name == getGlobalCtorsVarName() || name == getGlobalDtorsVarName())
return {};
}
if (auto *func = dyn_cast<llvm::Function>(global)) {
if (func->isIntrinsic() &&
iface.isConvertibleIntrinsic(func->getIntrinsicID()))
return {};
}
if (global->getName().empty())
return {};
return FlatSymbolRefAttr::get(ctx, global->getName());
}
/// Depth-first conversion of the metadata node `md` to the matching LLVM
/// dialect metadata attribute. Returns a null attribute for shapes that the
/// dialect's metadata-attribute hierarchy does not currently model. `path`
@ -162,24 +185,31 @@ static LogicalResult convertInstructionImpl(OpBuilder &odsBuilder,
static Attribute convertMetadataToAttrImpl(
MLIRContext *ctx, const llvm::Metadata *md,
SmallPtrSetImpl<const llvm::Metadata *> &path,
DenseMap<const llvm::Metadata *, Attribute> &attrMap) {
DenseMap<const llvm::Metadata *, Attribute> &attrMap,
LLVMImportInterface &iface,
llvm::function_ref<FlatSymbolRefAttr(llvm::GlobalVariable *)>
getNamelessGlobalSymbol) {
if (!md)
return {};
if (auto *mdStr = dyn_cast<llvm::MDString>(md))
return MDStringAttr::get(ctx, StringAttr::get(ctx, mdStr->getString()));
if (auto *cam = dyn_cast<llvm::ConstantAsMetadata>(md)) {
auto *ci = dyn_cast<llvm::ConstantInt>(cam->getValue());
llvm::Constant *constant = cam->getValue();
if (auto *global = dyn_cast<llvm::GlobalValue>(constant)) {
if (FlatSymbolRefAttr symbolRef = getMetadataGlobalValueSymbolRef(
ctx, iface, global, getNamelessGlobalSymbol))
return MDGlobalValueAttr::get(ctx, symbolRef);
}
auto *ci = dyn_cast<llvm::ConstantInt>(constant);
if (!ci)
return {};
auto intType = IntegerType::get(ctx, ci->getBitWidth());
return MDConstantAttr::get(ctx, IntegerAttr::get(intType, ci->getValue()));
}
if (auto *vam = dyn_cast<llvm::ValueAsMetadata>(md)) {
auto *fn = dyn_cast<llvm::Function>(vam->getValue());
if (!fn)
return {};
return MDGlobalValueAttr::get(ctx,
FlatSymbolRefAttr::get(ctx, fn->getName()));
if (isa<llvm::GlobalValue>(vam->getValue()))
llvm_unreachable("global values should be ConstantAsMetadata");
return {};
}
if (auto *node = dyn_cast<llvm::MDNode>(md)) {
// Metadata attributes cannot preserve distinctness, so bail out.
@ -194,8 +224,8 @@ static Attribute convertMetadataToAttrImpl(
SmallVector<Attribute> operands;
operands.reserve(node->getNumOperands());
for (const llvm::MDOperand &op : node->operands()) {
Attribute opAttr =
convertMetadataToAttrImpl(ctx, op.get(), path, attrMap);
Attribute opAttr = convertMetadataToAttrImpl(
ctx, op.get(), path, attrMap, iface, getNamelessGlobalSymbol);
if (!opAttr)
return {};
operands.push_back(opAttr);
@ -213,11 +243,14 @@ static Attribute convertMetadataToAttrImpl(
/// metadata-attribute hierarchy does not currently model, including distinct
/// nodes and cyclic metadata graphs that the immutable metadata attributes
/// cannot express.
static Attribute convertMetadataToAttr(MLIRContext *ctx,
const llvm::Metadata *md) {
static Attribute convertMetadataToAttr(
MLIRContext *ctx, const llvm::Metadata *md, LLVMImportInterface &iface,
llvm::function_ref<FlatSymbolRefAttr(llvm::GlobalVariable *)>
getNamelessGlobalSymbol) {
SmallPtrSet<const llvm::Metadata *, 8> path;
DenseMap<const llvm::Metadata *, Attribute> attrMap;
return convertMetadataToAttrImpl(ctx, md, path, attrMap);
return convertMetadataToAttrImpl(ctx, md, path, attrMap, iface,
getNamelessGlobalSymbol);
}
/// Get a topologically sorted list of blocks for the given basic blocks.
@ -1990,7 +2023,12 @@ FailureOr<Value> ModuleImport::convertValue(llvm::Value *value) {
// attribute.
if (auto *mdAsVal = dyn_cast<llvm::MetadataAsValue>(value)) {
llvm::Metadata *md = mdAsVal->getMetadata();
Attribute mdAttr = convertMetadataToAttr(context, md);
auto getNamelessGlobalSymbol =
[this](llvm::GlobalVariable *globalVar) -> FlatSymbolRefAttr {
return getOrCreateNamelessSymbolName(globalVar);
};
Attribute mdAttr =
convertMetadataToAttr(context, md, iface, getNamelessGlobalSymbol);
if (!mdAttr)
return emitError(mlirModule.getLoc())
<< "unsupported metadata: " << diagMD(md, llvmModule.get());

View file

@ -2063,6 +2063,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.

View file

@ -1847,6 +1847,26 @@ llvm.mlir.alias external @y5 : i32 {
// -----
// expected-error@+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@+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@+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@+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@+1{{attribute 'nodes' failed to satisfy constraint: array of #llvm.md_node attributes}}
llvm.named_metadata "not_node" [#llvm.md_string<"int">]

View file

@ -1244,3 +1244,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">>>
]
}

View file

@ -493,3 +493,48 @@ define i32 @cyclic_metadata_as_value() {
}
!0 = distinct !{!0}
; // -----
; CHECK: error: unsupported metadata: ptr @llvm.memcpy.p0.p0.i64
declare i32 @llvm.read_register.i32(metadata)
declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly, ptr noalias readonly, i64, i1 immarg)
define i32 @skipped_intrinsic_metadata() {
%r = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %r
}
!0 = !{ptr @llvm.memcpy.p0.p0.i64}
; // -----
; CHECK: error: unsupported metadata: ptr @llvm.global_ctors
declare i32 @llvm.read_register.i32(metadata)
define void @ctor() {
ret void
}
@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @ctor, ptr null }]
define i32 @metadata_ref_global_ctors() {
%r = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %r
}
!0 = !{ptr @llvm.global_ctors}
; // -----
; CHECK: error: unsupported metadata: ptr @llvm.global_dtors
declare i32 @llvm.read_register.i32(metadata)
define void @dtor() {
ret void
}
@llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @dtor, ptr null }]
define i32 @metadata_ref_global_dtors() {
%r = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %r
}
!0 = !{ptr @llvm.global_dtors}

View file

@ -11,7 +11,6 @@ define dso_local void @t0(ptr %a) {
; CHECK-LABEL: llvm.func @t0
; CHECK: llvm.call_intrinsic "llvm.aarch64.ldxr.p0"({{.*}}) : (!llvm.ptr {llvm.elementtype = i8}) -> i64
; CHECK: llvm.return
; CHECK: }
; // -----
@ -22,10 +21,9 @@ define dso_local <8 x i8> @t1(<8 x i8> %lhs, <8 x i8> %rhs) {
ret <8 x i8> %r
}
; CHECK: llvm.func @t1(%[[A0:.*]]: vector<8xi8>, %[[A1:.*]]: vector<8xi8>) -> vector<8xi8> {{.*}} {
; CHECK: llvm.func @t1(%[[A0:.*]]: vector<8xi8>, %[[A1:.*]]: vector<8xi8>) -> vector<8xi8> {{.*}}
; CHECK: %[[R:.*]] = llvm.call_intrinsic "llvm.aarch64.neon.uabd.v8i8"(%[[A0]], %[[A1]]) : (vector<8xi8>, vector<8xi8>) -> vector<8xi8>
; CHECK: llvm.return %[[R]] : vector<8xi8>
; CHECK: }
; // -----
@ -36,10 +34,9 @@ define dso_local void @t2(<8 x i8> %lhs, <8 x i8> %rhs, ptr %a) {
ret void
}
; CHECK: llvm.func @t2(%[[A0:.*]]: vector<8xi8>, %[[A1:.*]]: vector<8xi8>, %[[A2:.*]]: !llvm.ptr) {{.*}} {
; CHECK: llvm.func @t2(%[[A0:.*]]: vector<8xi8>, %[[A1:.*]]: vector<8xi8>, %[[A2:.*]]: !llvm.ptr) {{.*}}
; CHECK: llvm.call_intrinsic "llvm.aarch64.neon.st2.v8i8.p0"(%[[A0]], %[[A1]], %[[A2]]) : (vector<8xi8>, vector<8xi8>, !llvm.ptr) -> ()
; CHECK: llvm.return
; CHECK: }
; // -----
@ -115,3 +112,76 @@ define i32 @read_named_register() {
}
!0 = !{!"sp"}
; // -----
declare i32 @llvm.read_register.i32(metadata)
@global = global i32 0
; CHECK-LABEL: llvm.func @read_global_metadata
define i32 @read_global_metadata() {
; CHECK: %[[MD:.*]] = llvm.mlir.metadata_as_value #llvm.md_global_value<@global>
; CHECK: llvm.call_intrinsic "llvm.read_register.i32"(%[[MD]]) : (!llvm.metadata) -> i32
%r = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %r
}
!0 = !{ptr @global}
; // -----
declare i32 @llvm.read_register.i32(metadata)
define void @alias_target() {
ret void
}
@alias = alias void (), ptr @alias_target
; CHECK-LABEL: llvm.func @read_alias_metadata
define i32 @read_alias_metadata() {
; CHECK: %[[MD:.*]] = llvm.mlir.metadata_as_value #llvm.md_global_value<@alias>
; CHECK: llvm.call_intrinsic "llvm.read_register.i32"(%[[MD]]) : (!llvm.metadata) -> i32
%r = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %r
}
!0 = !{ptr @alias}
; // -----
declare i32 @llvm.read_register.i32(metadata)
@ifunc = ifunc void (), ptr @ifunc_resolver
define ptr @ifunc_resolver() {
ret ptr @ifunc_target
}
define void @ifunc_target() {
ret void
}
; CHECK-LABEL: llvm.func @read_ifunc_metadata
define i32 @read_ifunc_metadata() {
; CHECK: %[[MD:.*]] = llvm.mlir.metadata_as_value #llvm.md_global_value<@ifunc>
; CHECK: llvm.call_intrinsic "llvm.read_register.i32"(%[[MD]]) : (!llvm.metadata) -> i32
%r = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %r
}
!0 = !{ptr @ifunc}
; // -----
declare i32 @llvm.read_register.i32(metadata)
@0 = global i32 0
; CHECK-LABEL: llvm.func @read_nameless_global_metadata
define i32 @read_nameless_global_metadata() {
; CHECK: %[[MD:.*]] = llvm.mlir.metadata_as_value #llvm.md_global_value<@{{mlir\.llvm\.nameless_global_[0-9]+}}>
; CHECK: llvm.call_intrinsic "llvm.read_register.i32"(%[[MD]]) : (!llvm.metadata) -> i32
%r = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %r
}
!0 = !{ptr @0}