Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8577,6 +8577,35 @@ Example:
The ``nofree`` metadata indicates the memory pointed by the pointer will not be
freed after the attached instruction.

'``implicit.ref``' Metadata
^^^^^^^^^^^^^^^^^^^^^^^^^^^

The ``implicit.ref`` metadata may be attached to a function or global variable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the metadata propagated from an inlined callee to a caller? Is there a test for that?

definition with a single argument that references a global object.
This is typically used when there is some implicit dependence between the
symbols that is otherwise opaque to the linker. One such example is metadata
which is accessed by a runtime with associated ``__start_<section_name>`` and
``__stop_<section_name>`` symbols.

It does not have any effect on non-XCOFF targets.

This metadata lowers to the .ref assembly directive which will add a relocation
representing an implicit reference from the section the global belongs to, to
the associated symbol. This link will keep the referenced symbol alive if the
section is not garbage collected. More than one ref node can be attached
to the same function or global variable.


Example:

.. code-block:: text

@a = global i32 1
@b = global i32 2
@c = global i32 3, section "abc", !implicit.ref !0, !implicit.ref !1
!0 = !{ptr @a}
!1 = !{ptr @b}


Module Flags Metadata
=====================
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/IR/FixedMetadataKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ LLVM_FIXED_MD_KIND(MD_noalias_addrspace, "noalias.addrspace", 41)
LLVM_FIXED_MD_KIND(MD_callee_type, "callee_type", 42)
LLVM_FIXED_MD_KIND(MD_nofree, "nofree", 43)
LLVM_FIXED_MD_KIND(MD_captures, "captures", 44)
LLVM_FIXED_MD_KIND(MD_implicit_ref, "implicit.ref", 45)
25 changes: 25 additions & 0 deletions llvm/lib/IR/Verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,31 @@ void Verifier::visitGlobalValue(const GlobalValue &GV) {
DL.getIntPtrType(GO->getType()),
RangeLikeMetadataKind::AbsoluteSymbol);
}

if (GO->hasMetadata(LLVMContext::MD_implicit_ref)) {
Check(!GO->isDeclaration(),
"ref metadata must not be placed on a declaration", GO);

SmallVector<MDNode *> MDs;
GO->getMetadata(LLVMContext::MD_implicit_ref, MDs);
for (const MDNode *MD : MDs) {
Check(MD->getNumOperands() == 1, "ref metadata must have one operand",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we're allowed to have multiple !ref, it's more compact to allow one !ref that references multiple globals?

@a = global i32 3, !ref !1
!1 = !{ptr @b, ptr @c}

rather than

@a = global i32 3, !ref !1, !ref !2
!1 = !{ptr @b}
!2 = !{ptr @c}

https://llvm.org/docs/LangRef.html#callees-metadata does that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One difference with the callees metadata though is it is added in one place and never modified afterwards. For this metadata it is possible you could have 2 different features that require implicit references to be added. In that case would you have to get the existing array of refs, duplicate and append to it, then create a new metadata node with that array? Or are you suggesting we support both arrays of refs, and multiple individual refs as well? I'm not sure I'm particularly fond of either approach.

Copy link
Contributor

@w2yehia w2yehia Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this metadata it is possible you could have 2 different features that require implicit references to be added. In that case would you have to get the existing array of refs, duplicate and append to it, then create a new metadata node with that array?

yes you cannot mutate an existing node, so you'll have to create a new one, but that's not too much work as we constantly create new IR and throw old one in LLVM, and it will not happen often.

Or are you suggesting we support both arrays of refs, and multiple individual refs as well?

No, not both because that would complicate the handling on the consumer side.
I'm in favor of supporting only the array of refs (and enforcing one !ref MD) just because it's more compact in textual IR (and I cannot think of other benefits).

&GV, MD);
const Metadata *Op = MD->getOperand(0).get();
const auto *VM = dyn_cast_or_null<ValueAsMetadata>(Op);
Check(VM, "ref metadata must be ValueAsMetadata", GO, MD);
if (VM) {
Check(isa<PointerType>(VM->getValue()->getType()),
"ref value must be pointer typed", GV, MD);

const Value *Stripped = VM->getValue()->stripPointerCastsAndAliases();
Check(isa<GlobalObject>(Stripped) || isa<Constant>(Stripped),
"ref metadata must point to a GlobalObject", GO, Stripped);
Check(Stripped != GO, "values should not reference themselves", GO,
MD);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LangRef says the metadata can only be applied to a definition. If that's correct, the verifier should be enforcing it as well.

}

Check(!GV.hasAppendingLinkage() || isa<GlobalVariable>(GV),
Expand Down
27 changes: 26 additions & 1 deletion llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ class PPCAIXAsmPrinter : public PPCAsmPrinter {
void emitTTypeReference(const GlobalValue *GV, unsigned Encoding) override;

void emitModuleCommandLines(Module &M) override;

void emitRefMetadata(const GlobalObject *);
};

} // end anonymous namespace
Expand Down Expand Up @@ -2797,6 +2799,10 @@ void PPCAIXAsmPrinter::emitGlobalVariableHelper(const GlobalVariable *GV) {
// Switch to the containing csect.
OutStreamer->switchSection(Csect);

if (GV->hasMetadata(LLVMContext::MD_implicit_ref)) {
emitRefMetadata(GV);
}

const DataLayout &DL = GV->getDataLayout();

// Handle common and zero-initialized local symbols.
Expand Down Expand Up @@ -2889,10 +2895,16 @@ void PPCAIXAsmPrinter::emitFunctionEntryLabel() {
if (!TM.getFunctionSections() || MF->getFunction().hasSection())
PPCAsmPrinter::emitFunctionEntryLabel();

const Function *F = &MF->getFunction();

// Emit aliasing label for function entry point label.
for (const GlobalAlias *Alias : GOAliasMap[&MF->getFunction()])
for (const GlobalAlias *Alias : GOAliasMap[F])
OutStreamer->emitLabel(
getObjFileLowering().getFunctionEntryPointSymbol(Alias, TM));

if (F->hasMetadata(LLVMContext::MD_implicit_ref)) {
emitRefMetadata(F);
}
}

void PPCAIXAsmPrinter::emitPGORefs(Module &M) {
Expand Down Expand Up @@ -3332,6 +3344,19 @@ void PPCAIXAsmPrinter::emitTTypeReference(const GlobalValue *GV,
OutStreamer->emitIntValue(0, GetSizeOfEncodedValue(Encoding));
}

void PPCAIXAsmPrinter::emitRefMetadata(const GlobalObject *GO) {
SmallVector<MDNode *> MDs;
GO->getMetadata(LLVMContext::MD_implicit_ref, MDs);
assert(MDs.size() && "Expected asscoiated metadata nodes");

for (const MDNode *MD : MDs) {
const ValueAsMetadata *VAM = cast<ValueAsMetadata>(MD->getOperand(0).get());
const GlobalValue *GV = cast<GlobalValue>(VAM->getValue());
MCSymbol *Referenced = TM.getSymbol(GV);
OutStreamer->emitXCOFFRefDirective(Referenced);
}
}

// Return a pass that prints the PPC assembly code for a MachineFunction to the
// given output stream.
static AsmPrinter *
Expand Down
9 changes: 9 additions & 0 deletions llvm/lib/Transforms/Utils/InlineFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2830,6 +2830,15 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI,
// Propagate metadata on the callsite if necessary.
PropagateCallSiteMetadata(CB, FirstNewBlock, Caller->end());

// Propagate implicit ref metadata.
if (CalledFunc->hasMetadata(LLVMContext::MD_implicit_ref)) {
SmallVector<MDNode *> MDs;
CalledFunc->getMetadata(LLVMContext::MD_implicit_ref, MDs);
for (MDNode *MD : MDs) {
Caller->addMetadata(LLVMContext::MD_implicit_ref, *MD);
}
}

// Register any cloned assumptions.
if (IFI.GetAssumptionCache)
for (BasicBlock &NewBlock :
Expand Down
55 changes: 55 additions & 0 deletions llvm/test/CodeGen/PowerPC/aix-func-ref.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff < %s | \
; RUN: FileCheck %s -check-prefixes=NOFSECTS,CHECK

; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff --function-sections < %s | \
; RUN: FileCheck %s -check-prefixes=FSECTS,CHECK

; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff -filetype=obj -o %t.o < %s
; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck -check-prefix=OBJ %s

; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff --function-sections -filetype=obj -o %t.o < %s
; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck -check-prefix=FSECTOBJ %s

@a = global i32 1
@b = global i32 2
@c = global i32 3

define i32 @foo() !implicit.ref !0 {
ret i32 0
}

define i32 @bar() !implicit.ref !1 !implicit.ref !2 {
ret i32 0
}

!0 = !{ptr @a}
!1 = !{ptr @b}
!2 = !{ptr @c}

; NOFSECTS: .foo:
; FSECTS: .csect .foo[PR]
; CHECK: .ref a[RW]

; NOFSECTS: .bar:
; FSECTS: .csect .bar[PR]
; CHECK: .ref b[RW]
; CHECK: .ref c[RW]

; OBJ: Disassembly of section .text:
; OBJ: .foo:
; OBJ: li 3, 0
; OBJ: R_REF {{.*}} a[RW]
; OBJ: R_REF {{.*}} b[RW]
; OBJ: R_REF {{.*}} c[RW]
; OBJ: blr
; OBJ: .bar

; FSECTOBJ: .foo[PR]:
; FSECTOBJ: li 3, 0
; FSECTOBJ: R_REF {{.*}} a[RW]
; FSECTOBJ: blr
; FSECTOBJ: .bar[PR]:
; FSECTOBJ: li 3, 0
; FSECTOBJ: R_REF {{.*}} b[RW]
; FSECTOBJ: R_REF {{.*}} c[RW]
; FSECTOBJ: blr
77 changes: 77 additions & 0 deletions llvm/test/CodeGen/PowerPC/aix-ref-metadata.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff < %s | FileCheck %s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we test -fno-data-sections?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a test for the object file output

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this MD is to apply to functions and ifuncs, then will need tests for functions now, and I can add ifuncs tests as part of my ifuncs PR.

Note: When working on the ifunc PR I encountered an issue (that I'm yet to fix) with -fno-functions-sections -fintegrated-as when emiting .ref from inside functions. The issue was that relocations in the object file were not generated in increasing address order.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added textual assembly testing and started working on adding the matching object generation tests. Do you know if the out of order relocations in an issue for function as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if the out of order relocations in an issue for function as well?

yes, if you generate a .ref in the midst of generating a function body, and -fno-function-sections is ON (so there's just the singleton .text csect), the relocation gets generated at offset zero in that section.
Eg.

                        ***Relocation Information***
             Vaddr      Symndx  Sign  Fixup     Len      Type  Name
.text:
        0x000000c2  0x00000025     0      0  0x000f TOC_ILodx  _system_configuration
        0x000000d6  0x00000027     0      0  0x000f TOC_ILodx  foo.cpu_pwr9
        0x000000de  0x00000025     0      0  0x000f TOC_ILodx  _system_configuration
        0x000000f2  0x00000029     0      0  0x000f TOC_ILodx  foo.cpu_pwr8
        0x000000fa  0x0000002b     0      0  0x000f TOC_ILodx  foo.default
        0x00000000  0x00000015     0      0  0x0000   Non_Rel  ifunc_sec
        0x00000000  0x00000005     0      0  0x0000   Non_Rel  .__init_ifuncs
        0x0000017e  0x0000002d     0      0  0x000f TOC_ILodx  foo
....
                        ***Symbol Table Information***
[Index] m        Value       Scn   Aux  Sclass    Type            Name
[Index] a0                                                        Fname
[Index] a1      Tagndx      Lnno  Size  Lnoptr    Endndx
[Index] a2      Tagndx            Fsiz  Lnoptr    Endndx
[Index] a3      Tagndx      Lnno  Size  Dimensions
[Index] a4   CSlen     PARMhsh SNhash SMtype SMclass Stab SNstab
[Index] a5      SECTlen    #RELent    #LINnums

[7]     m   0x00000000     .text     1  unamex                    
[8]     a4  0x00000190       0    0     SD       PR    -    -
[9]     m   0x00000000     .text     1  unamex                    .foo.default
[10]    a4  0x00000007       0    0     LD       PR    -    -
[11]    m   0x00000040     .text     1  unamex                    .foo.cpu_pwr9
[12]    a4  0x00000007       0    0     LD       PR    -    -
[13]    m   0x00000080     .text     1  unamex                    .foo.cpu_pwr8
[14]    a4  0x00000007       0    0     LD       PR    -    -
[15]    m   0x000000c0     .text     1  unamex                    .foo.resolver
[16]    a4  0x00000007       0    0     LD       PR    -    -
[17]    m   0x00000120     .text     1  extern                    .main
[18]    a4  0x00000007       0    0     LD       PR    -    -
[19]    m   0x0000017c     .text     1  extern                    .foo
[20]    a4  0x00000007       0    0     LD       PR    -    -       

The bug in the object gen is that it puts the relocation at the start of whatever csect the function is in, instead of (I imagine) the current offset. You can probably reproduce with -ffunction-sections if you call emitXCOFFRefDirective while in the middle of a function body generation, and there's been prior relocations emitted in that function.

; RUN: llc -data-sections=false -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff < %s | FileCheck -check-prefix=NODATA %s

; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff -filetype=obj -o %t.o < %s
; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck -check-prefix=OBJ %s

; RUN: llc -data-sections=false -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff -filetype=obj -o %t.o < %s
; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck -check-prefix=OBJ-NODATA %s

@a = global i32 1
@b = global i32 2
@c = global i32 3, section "custom_section_c"
@d = global i32 4, !implicit.ref !0
@e = constant i32 5, !implicit.ref !1, !implicit.ref!2
@f = global i32 6, section "custom_section_f", !implicit.ref !1


!0 = !{ptr @a}
!1 = !{ptr @b}
!2 = !{ptr @c}

; CHECK: .csect d[RW]
; CHECK: .ref a[RW]

; CHECK: .csect e[RO]
; CHECK: .ref b[RW]
; CHECK: .ref c

; CHECK: .csect custom_section_f[RW]
; CHECK: .ref b[RW]

; NODATA: .csect .data[RW]
; NODATA-NOT: .csect
; NODATA: .globl a
; NODATA-NOT: .csect
; NODATA: .globl b
; NODATA: .csect custom_section_c[RW]
; NODATA: .globl c
; NODATA: .csect .data[RW]
; NODATA: .ref a
; NODATA: .globl d
; NODATA: .csect .rodata[RO]
; NODATA: .ref b
; NODATA: .ref c
; NODATA: .globl e
; NODATA: .csect custom_section_f[RW]
; NODATA: .ref b
; NODATA: .globl f

; OBJ: Disassembly of section .text:

; OBJ: e[RO]:
; OBJ: R_REF {{.*}} b[RW]
; OBJ: R_REF {{.*}} c

; OBJ: Disassembly of section .data:
; OBJ: a[RW]:
; OBJ: b[RW]:
; OBJ: c:
; OBJ: d[RW]:
; OBJ: R_REF {{.*}} a[RW]
; OBJ: f:
; OBJ: R_REF {{.*}} b[RW]

; OBJ-NODATA: Disassembly of section .text:
; OBJ-NODATA: e:
; OBJ-NODATA: R_REF {{.*}} b
; OBJ-NODATA: R_REF {{.*}} c

; OBJ-NODATA: Disassembly of section .data:
; OBJ-NODATA: a:
; OBJ-NODATA: R_REF {{.*}} a
; OBJ-NODATA: b:
; OBJ-NODATA: d:
; OBJ-NODATA: c:
; OBJ-NODATA: f:
; OBJ-NODATA: R_REF {{.*}} b
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
; RUN: opt -passes="ipsccp<func-spec>" -S -force-specialization < %s 2>&1 | FileCheck %s

@a = global i32 1

define i64 @main(i64 %x, i1 %flag) {
entry:
br i1 %flag, label %plus, label %minus

plus:
%tmp0 = call i64 @compute(i64 %x, ptr @plus)
br label %merge

minus:
%tmp1 = call i64 @compute(i64 %x, ptr @minus)
br label %merge

merge:
%tmp2 = phi i64 [ %tmp0, %plus ], [ %tmp1, %minus]
ret i64 %tmp2
}

define internal i64 @compute(i64 %x, ptr %binop) !implicit.ref !0 {
entry:
%tmp0 = call i64 %binop(i64 %x)
ret i64 %tmp0
}

define internal i64 @plus(i64 %x) {
entry:
%tmp0 = add i64 %x, 1
ret i64 %tmp0
}

define internal i64 @minus(i64 %x) {
entry:
%tmp0 = sub i64 %x, 1
ret i64 %tmp0
}

!0 = !{ptr @a}

; CHECK: @compute.specialized.1(i64 %x, ptr %binop) !implicit.ref !0
; CHECK: @compute.specialized.2(i64 %x, ptr %binop) !implicit.ref !0
50 changes: 50 additions & 0 deletions llvm/test/Transforms/Inline/inline-ref-metadata.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
; RUN: opt < %s -passes='cgscc(inline)' -S | FileCheck %s

@a = global i32 1
@b = global i32 2
@c = global double 3.141593e+00

define i32 @callee1() !implicit.ref !0 {
ret i32 0
}

define i32 @callee2() !implicit.ref !1 {
ret i32 1
}

define i32 @callee3() {
%i = call i32 @callee2()
ret i32 %i
}
; CHECK: @callee3() !implicit.ref !1

define i32 @caller1() {
%i = call i32 @callee1()
ret i32 %i
}
; CHECK: @caller1() !implicit.ref !0

define i32 @caller2() !implicit.ref !2 {
%i = call i32 @callee1()
ret i32 %i
}
; CHECK: @caller2() !implicit.ref !2 !implicit.ref !0

define i32 @caller3() {
%i = call i32 @caller4()
ret i32 %i
}
; CHECK: @caller3() !implicit.ref !0 !implicit.ref !1

define i32 @caller4() {
%a = call i32 @callee1()
%b = call i32 @callee2()
%add = add i32 %a, %b
ret i32 %add
}
; CHECK: @caller4() !implicit.ref !0 !implicit.ref !1

!0 = !{ptr @a}
!1 = !{ptr @b}
!2 = !{ptr @c}

12 changes: 12 additions & 0 deletions llvm/test/Verifier/ref-func.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
; RUN: llvm-as < %s -o /dev/null 2>&1

@a = global i32 1
@b = global i32 2
@c = global i32 3, !implicit.ref !0

define i32 @foo() !implicit.ref !1 {
ret i32 0
}

!0 = !{ptr @a}
!1 = !{ptr @b}
Loading