[llvm] [IR] llvm.reloc.none intrinsic for no-op symbol references (PR #147427)

Daniel Thornburgh via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 28 15:01:02 PDT 2025


https://github.com/mysterymath updated https://github.com/llvm/llvm-project/pull/147427

>From 062fb65ac8da599d2c0c7d0484882b40f0e18190 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Mon, 7 Jul 2025 16:19:59 -0700
Subject: [PATCH 1/7] [IR] llvm.reloc.none intrinsic for no-op symbol
 references

This intrinsic emits a BFD_RELOC_NONE relocation at the point of call,
which allows optimizations and languages to explicitly pull in symbols
from static libraries without there being any code or data that has an
effectual relocation against such a symbol.

See issue #146159 for context.
---
 llvm/docs/LangRef.rst                         | 32 +++++++++++++++++++
 llvm/include/llvm/CodeGen/ISDOpcodes.h        |  3 ++
 llvm/include/llvm/CodeGen/SelectionDAGISel.h  |  1 +
 llvm/include/llvm/IR/Intrinsics.td            |  3 ++
 llvm/include/llvm/Support/TargetOpcodes.def   |  3 ++
 llvm/include/llvm/Target/Target.td            |  5 +++
 llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp    | 14 ++++++++
 .../SelectionDAG/SelectionDAGBuilder.cpp      | 13 ++++++++
 .../SelectionDAG/SelectionDAGDumper.cpp       |  2 ++
 .../CodeGen/SelectionDAG/SelectionDAGISel.cpp |  8 +++++
 10 files changed, 84 insertions(+)

diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index cc72a37f68599..187182e5357e7 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -30439,6 +30439,38 @@ This intrinsic does nothing, but optimizers must consider it a use of its single
 operand and should try to preserve the intrinsic and its position in the
 function.
 
+.. _llvm_reloc_none:
+
+'``llvm.reloc.none``' Intrinsic
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Syntax:
+"""""""
+
+::
+
+      declare void @llvm.reloc.none(ptrty %ptr)
+
+Overview:
+"""""""""
+
+The ``llvm.reloc.none`` intrinsic emits a no-op relocation against a given
+operand symbol. This can bring the symbol
+definition into the link without emitting any code or data to the binary for
+that purpose.
+
+Arguments:
+""""""""""
+
+The ``llvm.fake.use`` intrinsic takes one argument, which may be any global
+value.
+
+Semantics:
+""""""""""
+
+This intrinsic emits a no-op relocation at the location of the intrinsic call
+for the symbol that corresponds to the global value argument.
+
 
 Stack Map Intrinsics
 --------------------
diff --git a/llvm/include/llvm/CodeGen/ISDOpcodes.h b/llvm/include/llvm/CodeGen/ISDOpcodes.h
index 465e4a0a9d0d8..9c36f9c4fe525 100644
--- a/llvm/include/llvm/CodeGen/ISDOpcodes.h
+++ b/llvm/include/llvm/CodeGen/ISDOpcodes.h
@@ -1531,6 +1531,9 @@ enum NodeType {
 #define BEGIN_REGISTER_VP_SDNODE(VPSDID, ...) VPSDID,
 #include "llvm/IR/VPIntrinsics.def"
 
+  // Issue a no-op relocation against a given symbol at the current location.
+  RELOC_NONE,
+
   // The `llvm.experimental.convergence.*` intrinsics.
   CONVERGENCECTRL_ANCHOR,
   CONVERGENCECTRL_ENTRY,
diff --git a/llvm/include/llvm/CodeGen/SelectionDAGISel.h b/llvm/include/llvm/CodeGen/SelectionDAGISel.h
index 375d2f31e2835..3a399c39c496f 100644
--- a/llvm/include/llvm/CodeGen/SelectionDAGISel.h
+++ b/llvm/include/llvm/CodeGen/SelectionDAGISel.h
@@ -473,6 +473,7 @@ class SelectionDAGISel {
   void Select_WRITE_REGISTER(SDNode *Op);
   void Select_UNDEF(SDNode *N);
   void Select_FAKE_USE(SDNode *N);
+  void Select_RELOC_NONE(SDNode *N);
   void CannotYetSelect(SDNode *N);
 
   void Select_FREEZE(SDNode *N);
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 7add4a27ce9e9..131bc56c32362 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -1917,6 +1917,9 @@ def int_threadlocal_address : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [LLVMMatch
 def int_stepvector : DefaultAttrsIntrinsic<[llvm_anyvector_ty],
                                             [], [IntrNoMem]>;
 
+def int_reloc_none : DefaultAttrsIntrinsic<[], [llvm_ptr_ty],
+  [IntrHasSideEffects, IntrInaccessibleMemOnly, IntrWillReturn]>;
+
 //===---------------- Vector Predication Intrinsics --------------===//
 // Memory Intrinsics
 def int_vp_store : DefaultAttrsIntrinsic<[],
diff --git a/llvm/include/llvm/Support/TargetOpcodes.def b/llvm/include/llvm/Support/TargetOpcodes.def
index 92fd60e03112a..5fd0b2a06188e 100644
--- a/llvm/include/llvm/Support/TargetOpcodes.def
+++ b/llvm/include/llvm/Support/TargetOpcodes.def
@@ -233,6 +233,9 @@ HANDLE_TARGET_OPCODE(MEMBARRIER)
 // using.
 HANDLE_TARGET_OPCODE(JUMP_TABLE_DEBUG_INFO)
 
+// Issue a no-op relocation against a given symbol at the current location.
+HANDLE_TARGET_OPCODE(RELOC_NONE)
+
 HANDLE_TARGET_OPCODE(CONVERGENCECTRL_ENTRY)
 HANDLE_TARGET_OPCODE(CONVERGENCECTRL_ANCHOR)
 HANDLE_TARGET_OPCODE(CONVERGENCECTRL_LOOP)
diff --git a/llvm/include/llvm/Target/Target.td b/llvm/include/llvm/Target/Target.td
index ce9a2b2751968..fca69107c8b55 100644
--- a/llvm/include/llvm/Target/Target.td
+++ b/llvm/include/llvm/Target/Target.td
@@ -1532,6 +1532,11 @@ def JUMP_TABLE_DEBUG_INFO : StandardPseudoInstruction {
   let Size = 0;
   let isMeta = true;
 }
+def RELOC_NONE : StandardPseudoInstruction {
+  let OutOperandList = (outs);
+  let InOperandList = (ins unknown:$symbol);
+  let hasSideEffects = true;
+}
 
 let hasSideEffects = false, isMeta = true, isConvergent = true in {
 def CONVERGENCECTRL_ANCHOR : StandardPseudoInstruction {
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index a2c3b50b24670..e6206e6cbb6ae 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -1930,6 +1930,20 @@ void AsmPrinter::emitFunctionBody() {
         // This is only used to influence register allocation behavior, no
         // actual initialization is needed.
         break;
+      case TargetOpcode::RELOC_NONE: {
+        // Generate a temporary label for the current PC.
+        MCSymbol *Sym = OutContext.createTempSymbol("reloc_none");
+        OutStreamer->emitLabel(Sym);
+        const MCExpr *Dot = MCSymbolRefExpr::create(Sym, OutContext);
+
+        assert(MI.getNumOperands() == 1 &&
+               "RELOC_NONE can only have one operand");
+        const MCExpr *Value = MCSymbolRefExpr::create(
+            getSymbol(MI.getOperand(0).getGlobal()), OutContext);
+        OutStreamer->emitRelocDirective(*Dot, "BFD_RELOC_NONE", Value, SMLoc(),
+                                        *STI);
+        break;
+      }
       default:
         emitInstruction(&MI);
 
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index c01f1e7928477..bcfbca582d699 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -7757,6 +7757,19 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I,
     return;
   }
 
+  case Intrinsic::reloc_none: {
+    SDValue V = getValue(I.getArgOperand(0));
+    auto *GA = dyn_cast<GlobalAddressSDNode>(V);
+    if (!GA)
+      report_fatal_error("llvm.reloc.none operand must be a GlobalValue");
+    SDValue Ops[2];
+    Ops[0] = getRoot();
+    Ops[1] = DAG.getTargetGlobalAddress(GA->getGlobal(), sdl, V.getValueType(),
+                                        GA->getOffset());
+    DAG.setRoot(DAG.getNode(ISD::RELOC_NONE, sdl, MVT::Other, Ops));
+    return;
+  }
+
   case Intrinsic::eh_exceptionpointer:
   case Intrinsic::eh_exceptioncode: {
     // Get the exception pointer vreg, copy from it, and resize it to fit.
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
index 7fc15581c17e4..5296913e718c2 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
@@ -471,6 +471,8 @@ std::string SDNode::getOperationName(const SelectionDAG *G) const {
   case ISD::LIFETIME_END:               return "lifetime.end";
   case ISD::FAKE_USE:
     return "fake_use";
+  case ISD::RELOC_NONE:
+    return "reloc_none";
   case ISD::PSEUDO_PROBE:
     return "pseudoprobe";
   case ISD::GC_TRANSITION_START:        return "gc_transition.start";
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 4b98d87fcc63b..ccc583c6a2b8d 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -2498,6 +2498,11 @@ void SelectionDAGISel::Select_FAKE_USE(SDNode *N) {
                        N->getOperand(1), N->getOperand(0));
 }
 
+void SelectionDAGISel::Select_RELOC_NONE(SDNode *N) {
+  CurDAG->SelectNodeTo(N, TargetOpcode::RELOC_NONE, N->getValueType(0),
+                       N->getOperand(1), N->getOperand(0));
+}
+
 void SelectionDAGISel::Select_FREEZE(SDNode *N) {
   // TODO: We don't have FREEZE pseudo-instruction in MachineInstr-level now.
   // If FREEZE instruction is added later, the code below must be changed as
@@ -3273,6 +3278,9 @@ void SelectionDAGISel::SelectCodeCommon(SDNode *NodeToMatch,
   case ISD::FAKE_USE:
     Select_FAKE_USE(NodeToMatch);
     return;
+  case ISD::RELOC_NONE:
+    Select_RELOC_NONE(NodeToMatch);
+    return;
   case ISD::FREEZE:
     Select_FREEZE(NodeToMatch);
     return;

>From b8e772f818331a83117c76902a807e0aef5cc3c5 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Tue, 8 Jul 2025 14:59:30 -0700
Subject: [PATCH 2/7] fake.use -> reloc.none

---
 llvm/docs/LangRef.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 187182e5357e7..f3aba8755b20c 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -30462,7 +30462,7 @@ that purpose.
 Arguments:
 """"""""""
 
-The ``llvm.fake.use`` intrinsic takes one argument, which may be any global
+The ``llvm.reloc.none`` intrinsic takes one argument, which may be any global
 value.
 
 Semantics:

>From cc1668ff07dc5b33083ea661e6c07b47b9824963 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Mon, 21 Jul 2025 14:01:01 -0700
Subject: [PATCH 3/7] Take symbol name by metadata arg rather than ptr to
 GlobalValue

---
 llvm/docs/LangRef.rst                               |  8 ++++----
 llvm/include/llvm/IR/Intrinsics.td                  |  2 +-
 .../CodeGen/SelectionDAG/SelectionDAGBuilder.cpp    | 13 +++++++------
 3 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index f3aba8755b20c..b8d987317f5e7 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -30462,14 +30462,14 @@ that purpose.
 Arguments:
 """"""""""
 
-The ``llvm.reloc.none`` intrinsic takes one argument, which may be any global
-value.
+The ``llvm.reloc.none`` intrinsic takes the symbol as a metadata string
+argument.
 
 Semantics:
 """"""""""
 
-This intrinsic emits a no-op relocation at the location of the intrinsic call
-for the symbol that corresponds to the global value argument.
+This intrinsic emits a no-op relocation for the symbol the location of the
+intrinsic call.
 
 
 Stack Map Intrinsics
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 131bc56c32362..0b83ab430f9dc 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -1917,7 +1917,7 @@ def int_threadlocal_address : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [LLVMMatch
 def int_stepvector : DefaultAttrsIntrinsic<[llvm_anyvector_ty],
                                             [], [IntrNoMem]>;
 
-def int_reloc_none : DefaultAttrsIntrinsic<[], [llvm_ptr_ty],
+def int_reloc_none : DefaultAttrsIntrinsic<[], [llvm_metadata_ty],
   [IntrHasSideEffects, IntrInaccessibleMemOnly, IntrWillReturn]>;
 
 //===---------------- Vector Predication Intrinsics --------------===//
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index bcfbca582d699..a585305cb67fe 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -7758,14 +7758,15 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I,
   }
 
   case Intrinsic::reloc_none: {
-    SDValue V = getValue(I.getArgOperand(0));
-    auto *GA = dyn_cast<GlobalAddressSDNode>(V);
-    if (!GA)
-      report_fatal_error("llvm.reloc.none operand must be a GlobalValue");
+    Metadata *MD = cast<MetadataAsValue>(I.getArgOperand(0))->getMetadata();
+    StringRef SymbolName = cast<MDString>(MD)->getString();
+    auto *M = const_cast<Module *>(I.getModule());
+    auto *RelocSymbol = cast<GlobalVariable>(
+        M->getOrInsertGlobal(SymbolName, StructType::create(M->getContext())));
     SDValue Ops[2];
     Ops[0] = getRoot();
-    Ops[1] = DAG.getTargetGlobalAddress(GA->getGlobal(), sdl, V.getValueType(),
-                                        GA->getOffset());
+    Ops[1] = DAG.getTargetGlobalAddress(
+        RelocSymbol, sdl, TLI.getPointerTy(DAG.getDataLayout()), 0);
     DAG.setRoot(DAG.getNode(ISD::RELOC_NONE, sdl, MVT::Other, Ops));
     return;
   }

>From 79eeb132773f73172939a9571a9eda71bef6bcf4 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Wed, 23 Jul 2025 15:01:09 -0700
Subject: [PATCH 4/7] Add a generic reloc_none test

---
 llvm/test/CodeGen/Generic/reloc_none.ll | 10 ++++++++++
 1 file changed, 10 insertions(+)
 create mode 100644 llvm/test/CodeGen/Generic/reloc_none.ll

diff --git a/llvm/test/CodeGen/Generic/reloc_none.ll b/llvm/test/CodeGen/Generic/reloc_none.ll
new file mode 100644
index 0000000000000..0c8b7a57aca83
--- /dev/null
+++ b/llvm/test/CodeGen/Generic/reloc_none.ll
@@ -0,0 +1,10 @@
+; RUN: llc < %s | FileCheck %s
+
+; CHECK: .reloc {{.*}}, BFD_RELOC_NONE, foo
+
+define void @test_reloc_none() {
+  call void @llvm.reloc.none(metadata !"foo")
+  ret void
+}
+
+declare void @llvm.reloc.none(metadata)

>From a039b24921c288324ef8aba9af1be6502d54abf0 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Fri, 25 Jul 2025 14:09:45 -0700
Subject: [PATCH 5/7] IR verifier check and test

---
 llvm/lib/IR/Verifier.cpp         |  6 ++++++
 llvm/test/Verifier/reloc_none.ll | 13 +++++++++++++
 2 files changed, 19 insertions(+)
 create mode 100644 llvm/test/Verifier/reloc_none.ll

diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 1f1041b259736..d289913fc26e6 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -5786,6 +5786,12 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
     Check(cast<ConstantInt>(Call.getArgOperand(3))->getZExtValue() < 2,
           "cache type argument to llvm.prefetch must be 0-1", Call);
     break;
+  case Intrinsic::reloc_none: {
+    Check(isa<MDString>(
+              cast<MetadataAsValue>(Call.getArgOperand(0))->getMetadata()),
+          "llvm.reloc.none argument must be a metadata string", &Call);
+    break;
+  }
   case Intrinsic::stackprotector:
     Check(isa<AllocaInst>(Call.getArgOperand(1)->stripPointerCasts()),
           "llvm.stackprotector parameter #2 must resolve to an alloca.", Call);
diff --git a/llvm/test/Verifier/reloc_none.ll b/llvm/test/Verifier/reloc_none.ll
new file mode 100644
index 0000000000000..55c470a6a5fe6
--- /dev/null
+++ b/llvm/test/Verifier/reloc_none.ll
@@ -0,0 +1,13 @@
+; RUN: not opt -S -passes=verify 2>&1 < %s | FileCheck %s
+
+; CHECK: llvm.reloc.none argument must be a metadata string
+; CHECK-NEXT: call void @llvm.reloc.none(metadata !0)
+
+define void @test_reloc_none_bad_arg() {
+  call void @llvm.reloc.none(metadata !0)
+  ret void
+}
+
+declare void @llvm.reloc.none(metadata)
+
+!0 = !{}

>From 590569a5cc1886f7fb5935619f1c2728de08b4ea Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Mon, 28 Jul 2025 14:58:01 -0700
Subject: [PATCH 6/7] Remove unneeded assertion from AsmPrinter

---
 llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index e6206e6cbb6ae..fc3c487f7f2c6 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -1935,9 +1935,6 @@ void AsmPrinter::emitFunctionBody() {
         MCSymbol *Sym = OutContext.createTempSymbol("reloc_none");
         OutStreamer->emitLabel(Sym);
         const MCExpr *Dot = MCSymbolRefExpr::create(Sym, OutContext);
-
-        assert(MI.getNumOperands() == 1 &&
-               "RELOC_NONE can only have one operand");
         const MCExpr *Value = MCSymbolRefExpr::create(
             getSymbol(MI.getOperand(0).getGlobal()), OutContext);
         OutStreamer->emitRelocDirective(*Dot, "BFD_RELOC_NONE", Value, SMLoc(),

>From 086aa464206b0f7829611660eb4842ccc654452a Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Mon, 28 Jul 2025 14:59:56 -0700
Subject: [PATCH 7/7] Use llvm-as for test

---
 llvm/test/Verifier/reloc_none.ll | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/Verifier/reloc_none.ll b/llvm/test/Verifier/reloc_none.ll
index 55c470a6a5fe6..9c96799a36a36 100644
--- a/llvm/test/Verifier/reloc_none.ll
+++ b/llvm/test/Verifier/reloc_none.ll
@@ -1,4 +1,4 @@
-; RUN: not opt -S -passes=verify 2>&1 < %s | FileCheck %s
+; RUN: not llvm-as -disable-output 2>&1 %s | FileCheck %s
 
 ; CHECK: llvm.reloc.none argument must be a metadata string
 ; CHECK-NEXT: call void @llvm.reloc.none(metadata !0)



More information about the llvm-commits mailing list