[llvm] [ARM] Use .reloc for dso_local weak symbols in PIC mode instead of GOT indirection (PR #209660)
dong jianqiang via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 17 20:47:19 PDT 2026
dongjianqiang2 wrote:
> Thanks for the update. Where I'm up to so far. Will need to get back to this over the weekend as I'm on holiday next week and I don't want to block this review if other's are happy.
>
> I reproduce the R_ARM_REL32 relocation to `__libc_stack_end` in sanitizer_linux.cpp by compiling it for Arm on the commit that triggered the test failure.
>
> `__libc_stack_end` was just an global default visibility weak reference.
>
> I made a small patch that applies on top of #76b035c3b00e072334f6f516b0ccb31140f5975f which was the commit before the put all weak definitions through the GOT.
>
> ```
> diff --git a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
> index 8b8d0bc9ada1..f888c4822ca5 100644
> --- a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
> +++ b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
> @@ -1006,7 +1006,7 @@ void ARMAsmPrinter::emitMachineConstantPoolValue(
> MCSym = getMBBExceptionSym(MF->front());
> } else if (ACPV->isBlockAddress()) {
> const BlockAddress *BA =
> - cast<ARMConstantPoolConstant>(ACPV)->getBlockAddress();
> + cast<ARMConstantPoolConstant>(ACPV)->getBlockAddress();
> MCSym = GetBlockAddressSymbol(BA);
> } else if (ACPV->isGlobalValue()) {
> const GlobalValue *GV = cast<ARMConstantPoolConstant>(ACPV)->getGV();
> @@ -1016,6 +1016,31 @@ void ARMAsmPrinter::emitMachineConstantPoolValue(
> unsigned char TF =
> TM.getTargetTriple().isOSBinFormatMachO() ? ARMII::MO_NONLAZY : 0;
> MCSym = GetARMGVSymbol(GV, TF);
> + if (GV->isWeakForLinker() &&
> + (GV->isDSOLocal() || !GV->hasDefaultVisibility()) &&
> + TM.getTargetTriple().isOSBinFormatELF() && TM.isPositionIndependent() &&
> + ACPV->getPCAdjustment() != 0) {
> + MCSymbol *CPILabel = OutContext.createTempSymbol();
> + OutStreamer->emitLabel(CPILabel);
> + // Emit local-only expression: CPILabel - (LPC+PCAdj)
> + const MCExpr *LocalExpr = MCSymbolRefExpr::create(CPILabel, OutContext);
> + MCSymbol *PCLabel =
> + getPICLabel(DL.getInternalSymbolPrefix(), getFunctionNumber(),
> + ACPV->getLabelId(), OutContext);
> + const MCExpr *PCRelExpr = MCSymbolRefExpr::create(PCLabel, OutContext);
> + PCRelExpr = MCBinaryExpr::createAdd(
> + PCRelExpr,
> + MCConstantExpr::create(ACPV->getPCAdjustment(), OutContext),
> + OutContext);
> + LocalExpr = MCBinaryExpr::createSub(LocalExpr, PCRelExpr, OutContext);
> + OutStreamer->emitValue(LocalExpr, Size);
> + // Emit .reloc to force linker resolution of the weak symbol.
> + const MCExpr *CPIExpr = MCSymbolRefExpr::create(CPILabel, OutContext);
> + const MCExpr *SymExpr = MCSymbolRefExpr::create(MCSym, OutContext);
> + OutStreamer->emitRelocDirective(*CPIExpr, "R_ARM_REL32", SymExpr,
> + SMLoc());
> + return;
> + }
> }
> ```
>
> That is the patch from here, but instead of isGVinGOT it does `(GV->isDSOLocal() || !GV->hasDefaultVisibility())`
>
> I haven't tested it beyond checking that it makes R_ARM_GOTPREL references to the symbols that had failed the test before.
>
> I still need to test on your original use case, to make sure we don't get a R_ARM_GOTPREL or a resolved pc-relative expression for a hidden weak definition in the same section.
>
> I also need to understand your comments about "The normal constant-pool path (the else in emitMachineConstantPoolValue that emits sym-(LPC+8)) still produces one when the symbol's in a different section from the pool entry, say a weak global in .data referenced from a .text pool entry"
Thanks again for the reproduction and the patch - that was the nudge I needed to re-examine this.
On the constant-pool point you wanted to understand: the address of a weak symbol gets materialized through a PC-relative constant pool entry that lives in `.text`, and whether the assembler emits an `R_ARM_REL32` depends on which section the symbol itself is in. A weak global variable (in .data):
` .long w-(.LPC0_0+8) @ pool in .text, w in .data -> cross-section -> R_ARM_REL32`
That comes out of the normal fixup path (the `else` in `emitMachineConstantPoolValue`), not the `.reloc` branch - so tightening the `.reloc` condition doesn't get rid of it for the variable case. A weak function (in `.text`) is the same expression but same-section, so in the original code it's resolved eagerly at assembly time and produces no relocation (the #183916 override problem).
On your other open item, the hidden weak definition in the same section: with the .reloc patch it gets `R_ARM_REL32` via the .reloc directive - not GOT_PREL, not eagerly resolved. I checked define weak dso_local hidden void @f with its address taken from a .text pool: it emits .reloc .Ltmp0, R_ARM_REL32, f and links cleanly on both lld and GNU ld. That's the #183916 linker-override behavior preserved.
That cross-section `R_ARM_REL32` for weak variables is what originally led me to add the lowering change routing default-visibility weak through the GOT - to keep `R_ARM_REL32` off them regardless of how the symbol became `dso_local`. But having dug into it more, the reported failure is `__libc_stack_end`, an extern_weak reference (undefined), and the `.reloc` `isDSOLocal()` gate already sends that through the GOT. The cross-section case only bites a defined default-visibility dso_local weak symbol, which Clang doesn't produce for shared libraries (it only marks weak definitions dso_local for PIE, where the link succeeds anyway) - and which the original code already handled the same way. So the lowering change isn't needed for the reported bug; the `.reloc` gate is enough. I've reverted the lowering change in a follow-up commit.
On the condition itself: mine is `isDSOLocal()`, yours is `isDSOLocal() || !hasDefaultVisibility()`. They're equivalent on every case reachable from C/C++ - extern_weak, defined hidden weak, defined default-vis weak all behave identically. The only place they differ is extern_weak hidden (an undefined hidden weak, a somewhat contradictory construct C/C++ can't produce): mine goes through the GOT, yours emits `R_ARM_REL32`. Both link; I left it at `isDSOLocal()` since the GOT is cleaner there, but it doesn't really matter.
One last thing, since I'd only tested with lld earlier: for a defined default-visibility weak symbol, lld rejects `R_ARM_REL32` but GNU ld accepts it (as a dynamic relocation in .text, i.e. a `TEXTREL`). Both reject it for an undefined one (the `__libc_stack_end` case), and both accept it for hidden weak (resolved statically at link time, no `TEXTREL`). So under GNU ld there are no .so failures either way; the only lld rejection is the defined default-vis weak case, which isn't reachable from C/C++ in a shared library anyway.
https://github.com/llvm/llvm-project/pull/209660
More information about the llvm-commits
mailing list