[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.
This commit is contained in:
Daniel Thornburgh 2025-07-07 16:19:59 -07:00
parent 8429f7faaa
commit f3fdeff3e3
10 changed files with 84 additions and 0 deletions

View File

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

View File

@ -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,

View File

@ -473,6 +473,7 @@ private:
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);

View File

@ -1913,6 +1913,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<[],

View File

@ -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)

View File

@ -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 {

View File

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

View File

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

View File

@ -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";

View File

@ -2521,6 +2521,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
@ -3296,6 +3301,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;