[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:41 PDT 2026
https://github.com/dongjianqiang2 updated https://github.com/llvm/llvm-project/pull/209660
>From 3cb23f5a41328703f0cb9180503d63b29325eb48 Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Tue, 14 Jul 2026 10:56:23 +0800
Subject: [PATCH 1/4] Revert "[ARM] Use GOT indirection for weak symbols in PIC
mode (#198577)"
This reverts commit 31f0e25074627bd081130f81ba6ffcb0ede1aaab.
---
llvm/lib/Target/ARM/ARMFastISel.cpp | 3 +--
llvm/lib/Target/ARM/ARMISelLowering.cpp | 11 +++--------
llvm/lib/Target/ARM/ARMSubtarget.cpp | 3 +--
llvm/lib/Target/ARM/ARMTargetMachine.h | 9 ---------
llvm/test/CodeGen/ARM/elf-preemption.ll | 14 ++++++--------
llvm/test/CodeGen/ARM/weak-hidden-pic.ll | 19 -------------------
6 files changed, 11 insertions(+), 48 deletions(-)
delete mode 100644 llvm/test/CodeGen/ARM/weak-hidden-pic.ll
diff --git a/llvm/lib/Target/ARM/ARMFastISel.cpp b/llvm/lib/Target/ARM/ARMFastISel.cpp
index 1cf0d98f690aa..2c5d286e11c4f 100644
--- a/llvm/lib/Target/ARM/ARMFastISel.cpp
+++ b/llvm/lib/Target/ARM/ARMFastISel.cpp
@@ -3033,8 +3033,7 @@ bool ARMFastISel::tryToFoldLoadIntoMI(MachineInstr *MI, unsigned OpNo,
}
Register ARMFastISel::ARMLowerPICELF(const GlobalValue *GV, MVT VT) {
- // Weak symbols need GOT indirection even when hidden/DSO-local.
- bool UseGOT_PREL = !GV->isDSOLocal() || GV->isWeakForLinker();
+ bool UseGOT_PREL = !GV->isDSOLocal();
LLVMContext *Context = &MF->getFunction().getContext();
unsigned ARMPCLabelIndex = AFI->createPICLabelUId();
unsigned PCAdj = Subtarget->isThumb() ? 4 : 8;
diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp
index 35c46c3f0a75a..28d4c0ff22703 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -3651,15 +3651,10 @@ SDValue ARMTargetLowering::LowerGlobalAddressELF(SDValue Op,
return V;
if (isPositionIndependent()) {
- // Weak symbols need GOT indirection even when hidden/DSO-local.
- // The assembler eagerly resolves PC-relative expressions when the
- // symbol and reference are in the same section, which prevents the
- // linker from overriding a weak definition with a non-weak one.
- bool UseGOT = !GV->isDSOLocal() || GV->isWeakForLinker();
- SDValue G = DAG.getTargetGlobalAddress(GV, dl, PtrVT, 0,
- UseGOT ? ARMII::MO_GOT : 0);
+ SDValue G = DAG.getTargetGlobalAddress(
+ GV, dl, PtrVT, 0, GV->isDSOLocal() ? 0 : ARMII::MO_GOT);
SDValue Result = DAG.getNode(ARMISD::WrapperPIC, dl, PtrVT, G);
- if (UseGOT)
+ if (!GV->isDSOLocal())
Result =
DAG.getLoad(PtrVT, dl, DAG.getEntryNode(), Result,
MachinePointerInfo::getGOT(DAG.getMachineFunction()));
diff --git a/llvm/lib/Target/ARM/ARMSubtarget.cpp b/llvm/lib/Target/ARM/ARMSubtarget.cpp
index 0f324a6a31757..4893d8d3a9ef1 100644
--- a/llvm/lib/Target/ARM/ARMSubtarget.cpp
+++ b/llvm/lib/Target/ARM/ARMSubtarget.cpp
@@ -397,8 +397,7 @@ bool ARMSubtarget::isGVIndirectSymbol(const GlobalValue *GV) const {
}
bool ARMSubtarget::isGVInGOT(const GlobalValue *GV) const {
- return isTargetELF() && TM.isPositionIndependent() &&
- (!GV->isDSOLocal() || GV->isWeakForLinker());
+ return isTargetELF() && TM.isPositionIndependent() && !GV->isDSOLocal();
}
unsigned ARMSubtarget::getMispredictionPenalty() const {
diff --git a/llvm/lib/Target/ARM/ARMTargetMachine.h b/llvm/lib/Target/ARM/ARMTargetMachine.h
index 109d6fd5e6671..215b541620acb 100644
--- a/llvm/lib/Target/ARM/ARMTargetMachine.h
+++ b/llvm/lib/Target/ARM/ARMTargetMachine.h
@@ -111,15 +111,6 @@ class ARMBaseTargetMachine : public CodeGenTargetMachineImpl {
(GV->isDeclarationForLinker() || GV->hasCommonLinkage()))
return true;
- // In ELF PIC mode, weak symbols referenced via the constant pool use a
- // PC-relative expression (e.g. .long xxx-(.LPC+8)) that the assembler
- // eagerly resolves when both the symbol and label are in the same section.
- // This prevents the linker from overriding a weak definition with a
- // non-weak one. Use GOT indirection for weak symbols to avoid this.
- if (getTargetTriple().isOSBinFormatELF() && isPositionIndependent() &&
- GV->isWeakForLinker())
- return true;
-
return false;
}
diff --git a/llvm/test/CodeGen/ARM/elf-preemption.ll b/llvm/test/CodeGen/ARM/elf-preemption.ll
index 88b35683b97c0..154c29c1c029c 100644
--- a/llvm/test/CodeGen/ARM/elf-preemption.ll
+++ b/llvm/test/CodeGen/ARM/elf-preemption.ll
@@ -59,13 +59,12 @@ define ptr @get_weak_dsolocal_var() nounwind {
; PIC: @ %bb.0:
; PIC-NEXT: ldr r0, .LCPI2_0
; PIC-NEXT: .LPC2_0:
-; PIC-NEXT: ldr r0, [pc, r0]
+; PIC-NEXT: add r0, pc, r0
; PIC-NEXT: bx lr
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI2_0:
-; PIC-NEXT: .Ltmp1:
-; PIC-NEXT: .long weak_dsolocal_var(GOT_PREL)-(.LPC2_0+8-.Ltmp1)
+; PIC-NEXT: .long weak_dsolocal_var-(.LPC2_0+8)
ret ptr @weak_dsolocal_var
}
@@ -127,8 +126,8 @@ define dso_preemptable ptr @preemptable_func() nounwind {
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI5_0:
-; PIC-NEXT: .Ltmp2:
-; PIC-NEXT: .long preemptable_func(GOT_PREL)-(.LPC5_0+8-.Ltmp2)
+; PIC-NEXT: .Ltmp1:
+; PIC-NEXT: .long preemptable_func(GOT_PREL)-(.LPC5_0+8-.Ltmp1)
ret ptr @preemptable_func
}
@@ -163,13 +162,12 @@ define weak dso_local ptr @weak_dsolocal_func() nounwind {
; PIC: @ %bb.0:
; PIC-NEXT: ldr r0, .LCPI7_0
; PIC-NEXT: .LPC7_0:
-; PIC-NEXT: ldr r0, [pc, r0]
+; PIC-NEXT: add r0, pc, r0
; PIC-NEXT: bx lr
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI7_0:
-; PIC-NEXT: .Ltmp3:
-; PIC-NEXT: .long weak_dsolocal_func(GOT_PREL)-(.LPC7_0+8-.Ltmp3)
+; PIC-NEXT: .long weak_dsolocal_func-(.LPC7_0+8)
ret ptr @weak_dsolocal_func
}
diff --git a/llvm/test/CodeGen/ARM/weak-hidden-pic.ll b/llvm/test/CodeGen/ARM/weak-hidden-pic.ll
deleted file mode 100644
index 4d6eca77af855..0000000000000
--- a/llvm/test/CodeGen/ARM/weak-hidden-pic.ll
+++ /dev/null
@@ -1,19 +0,0 @@
-; RUN: llc < %s -mtriple=armv7-linux-gnueabi -relocation-model=pic | FileCheck %s
-; RUN: llc < %s -mtriple=thumbv7-linux-gnueabi -relocation-model=pic | FileCheck %s
-; RUN: llc < %s -O0 -fast-isel-abort=2 -mtriple=armv7-linux-gnueabi -relocation-model=pic | FileCheck %s
-
-; Hidden weak function with dso_local must still use GOT indirection
-; in PIC mode on ARM. The assembler eagerly resolves PC-relative
-; expressions like .long xxx-(.LPC+8) when both are in the same section,
-; which prevents the linker from overriding the weak definition with
-; a non-weak one from another object file.
-
-define weak dso_local hidden void @weak_hidden_func() {
- ret void
-}
-
-; CHECK-LABEL: weak_hidden_func_addr:
-; CHECK: .long weak_hidden_func(GOT_PREL)
-define i8* @weak_hidden_func_addr() {
- ret i8* bitcast (void()* @weak_hidden_func to i8*)
-}
>From 435a36bc08259657fcc6540e6bfbf013ee67ed04 Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Tue, 14 Jul 2026 10:59:11 +0800
Subject: [PATCH 2/4] [ARM] Use .reloc for dso_local weak symbols in PIC mode
In ELF PIC mode, a PC-relative constant-pool reference to a weak symbol
would be eagerly resolved by the assembler when the symbol and its
reference land in the same section, preventing the linker from overriding
a weak definition with a non-weak definition from another object. Use a
.reloc directive to force an R_ARM_REL32 relocation so the linker can
perform the override, instead of forcing weak symbols through the GOT
(which requires an extra memory load).
Restrict this to dso_local weak symbols, the only ones that reach the
direct (non-GOT) constant-pool path where R_ARM_REL32 is valid.
Preemptible or external weak symbols (e.g. extern_weak references such
as glibc's __libc_stack_end) must keep using the GOT: R_ARM_REL32
against an external or undefined symbol cannot be used when producing a
shared object, and would break -shared links such as the compiler-rt
sanitizer runtime shared libraries on armv7.
Fixes #183916 #209244
---
llvm/lib/Target/ARM/ARMAsmPrinter.cpp | 34 ++++++++++++++
llvm/test/CodeGen/ARM/elf-preemption.ll | 60 ++++++++++++++++++++++--
llvm/test/CodeGen/ARM/weak-hidden-pic.ll | 21 +++++++++
3 files changed, 111 insertions(+), 4 deletions(-)
create mode 100644 llvm/test/CodeGen/ARM/weak-hidden-pic.ll
diff --git a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
index c25a2cfeff8d6..49c5c983e2934 100644
--- a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
+++ b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
@@ -1016,6 +1016,40 @@ void ARMAsmPrinter::emitMachineConstantPoolValue(
unsigned char TF =
TM.getTargetTriple().isOSBinFormatMachO() ? ARMII::MO_NONLAZY : 0;
MCSym = GetARMGVSymbol(GV, TF);
+
+ // For dso_local weak symbols in ELF PIC mode, the assembler would eagerly
+ // resolve a PC-relative expression like sym-(LPC+8) when the symbol and
+ // reference are in the same section, preventing the linker from overriding
+ // a weak definition with a non-weak definition from another section. Use a
+ // .reloc directive rather than a fixup to force the generation of a
+ // relocation (R_ARM_REL32) so the linker can perform the override. This is
+ // restricted to dso_local symbols: a preemptible/external weak symbol
+ // (e.g. an extern_weak reference) must use the GOT, as R_ARM_REL32 against
+ // an external symbol cannot be used when making a shared object.
+ if (GV->isWeakForLinker() && GV->isDSOLocal() &&
+ 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;
+ }
} else if (ACPV->isMachineBasicBlock()) {
const MachineBasicBlock *MBB = cast<ARMConstantPoolMBB>(ACPV)->getMBB();
MCSym = MBB->getSymbol();
diff --git a/llvm/test/CodeGen/ARM/elf-preemption.ll b/llvm/test/CodeGen/ARM/elf-preemption.ll
index 154c29c1c029c..caefff7ea43bd 100644
--- a/llvm/test/CodeGen/ARM/elf-preemption.ll
+++ b/llvm/test/CodeGen/ARM/elf-preemption.ll
@@ -64,7 +64,9 @@ define ptr @get_weak_dsolocal_var() nounwind {
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI2_0:
-; PIC-NEXT: .long weak_dsolocal_var-(.LPC2_0+8)
+; PIC-NEXT: .Ltmp1:
+; PIC-NEXT: .long .Ltmp1-(.LPC2_0+8)
+; PIC-NEXT: .reloc .Ltmp1, R_ARM_REL32, weak_dsolocal_var
ret ptr @weak_dsolocal_var
}
@@ -126,8 +128,8 @@ define dso_preemptable ptr @preemptable_func() nounwind {
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI5_0:
-; PIC-NEXT: .Ltmp1:
-; PIC-NEXT: .long preemptable_func(GOT_PREL)-(.LPC5_0+8-.Ltmp1)
+; PIC-NEXT: .Ltmp2:
+; PIC-NEXT: .long preemptable_func(GOT_PREL)-(.LPC5_0+8-.Ltmp2)
ret ptr @preemptable_func
}
@@ -167,7 +169,9 @@ define weak dso_local ptr @weak_dsolocal_func() nounwind {
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI7_0:
-; PIC-NEXT: .long weak_dsolocal_func-(.LPC7_0+8)
+; PIC-NEXT: .Ltmp3:
+; PIC-NEXT: .long .Ltmp3-(.LPC7_0+8)
+; PIC-NEXT: .reloc .Ltmp3, R_ARM_REL32, weak_dsolocal_func
ret ptr @weak_dsolocal_func
}
@@ -190,3 +194,51 @@ define dso_local void @call_dsolocal_func() nounwind {
call ptr @dsolocal_func()
ret void
}
+
+;; extern_weak (undefined) symbols are external/preemptible and must go through
+;; the GOT in PIC mode. R_ARM_REL32 against an external or undefined symbol
+;; cannot be used when making a shared object, so the .reloc path must not
+;; apply to them.
+ at extern_weak_var = extern_weak global i32
+define ptr @get_extern_weak_var() nounwind {
+; STATIC-LABEL: get_extern_weak_var:
+; STATIC: @ %bb.0:
+; STATIC-NEXT: movw r0, :lower16:extern_weak_var
+; STATIC-NEXT: movt r0, :upper16:extern_weak_var
+; STATIC-NEXT: bx lr
+;
+; PIC-LABEL: get_extern_weak_var:
+; PIC: @ %bb.0:
+; PIC-NEXT: ldr r0, .LCPI9_0
+; PIC-NEXT: .LPC9_0:
+; PIC-NEXT: ldr r0, [pc, r0]
+; PIC-NEXT: bx lr
+; PIC-NEXT: .p2align 2
+; PIC-NEXT: @ %bb.1:
+; PIC-NEXT: .LCPI9_0:
+; PIC-NEXT: .Ltmp4:
+; PIC-NEXT: .long extern_weak_var(GOT_PREL)-(.LPC9_0+8-.Ltmp4)
+ ret ptr @extern_weak_var
+}
+
+declare extern_weak ptr @extern_weak_func()
+define ptr @get_extern_weak_func() nounwind {
+; STATIC-LABEL: get_extern_weak_func:
+; STATIC: @ %bb.0:
+; STATIC-NEXT: movw r0, :lower16:extern_weak_func
+; STATIC-NEXT: movt r0, :upper16:extern_weak_func
+; STATIC-NEXT: bx lr
+;
+; PIC-LABEL: get_extern_weak_func:
+; PIC: @ %bb.0:
+; PIC-NEXT: ldr r0, .LCPI10_0
+; PIC-NEXT: .LPC10_0:
+; PIC-NEXT: ldr r0, [pc, r0]
+; PIC-NEXT: bx lr
+; PIC-NEXT: .p2align 2
+; PIC-NEXT: @ %bb.1:
+; PIC-NEXT: .LCPI10_0:
+; PIC-NEXT: .Ltmp5:
+; PIC-NEXT: .long extern_weak_func(GOT_PREL)-(.LPC10_0+8-.Ltmp5)
+ ret ptr @extern_weak_func
+}
diff --git a/llvm/test/CodeGen/ARM/weak-hidden-pic.ll b/llvm/test/CodeGen/ARM/weak-hidden-pic.ll
new file mode 100644
index 0000000000000..26a779f2dc9f9
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/weak-hidden-pic.ll
@@ -0,0 +1,21 @@
+; RUN: llc < %s -mtriple=armv7-linux-gnueabi -relocation-model=pic | FileCheck %s
+; RUN: llc < %s -mtriple=thumbv7-linux-gnueabi -relocation-model=pic | FileCheck %s
+; RUN: llc < %s -O0 -fast-isel-abort=2 -mtriple=armv7-linux-gnueabi -relocation-model=pic | FileCheck %s
+
+; Weak dso_local hidden functions must be overridable at link time
+; (a non-weak definition in another object should override the weak one).
+; Instead of using GOT indirection, we use a PC-relative constant pool
+; entry with a .reloc directive to force the assembler to emit a proper
+; relocation (R_ARM_REL32), preventing eager resolution when the symbol
+; and reference are in the same section.
+
+define weak dso_local hidden void @weak_hidden_func() {
+ ret void
+}
+
+; CHECK-LABEL: weak_hidden_func_addr:
+; CHECK: .long .Ltmp{{[0-9]+}}-(.LPC{{[0-9]+}}_0+{{[48]}})
+; CHECK: .reloc .Ltmp{{[0-9]+}}, R_ARM_REL32, weak_hidden_func
+define i8* @weak_hidden_func_addr() {
+ ret i8* bitcast (void()* @weak_hidden_func to i8*)
+}
>From a5ee1dc91a7ff0315a84695b7e5d0f6e9929ac43 Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Tue, 14 Jul 2026 21:05:21 +0800
Subject: [PATCH 3/4] [ARM] Route default-visibility weak symbols through the
GOT in PIC mode
A default-visibility weak symbol stays exported and preemptible in a
shared object even when LLVM treats it as dso_local (e.g. compiled with
-fno-semantic-interposition). Referencing it directly emits a static
R_ARM_REL32, which the linker rejects when making a shared object
("relocation R_ARM_REL32 cannot be used against symbol ...; recompile
with -fPIC"), breaking -shared links that take the address of such a
symbol.
Route default-visibility weak symbols through the GOT by keying the ARM
GOT decision on a single predicate, ARMSubtarget::isGVInGOT, now true
for them. This covers all three instruction-selection paths
(LowerGlobalAddressELF, FastISel's ARMLowerPICELF, and GlobalISel), and
the AsmPrinter .reloc/R_ARM_REL32 path is restricted to non-GOT
(non-preemptible, hidden/protected) weak entries so it no longer fires
on -- and clobbers -- GOT entries. Hidden/protected weak symbols keep
the efficient direct .reloc reference (#183916).
Follow-up to #208372 covering the default-visibility weak-symbol case.
---
llvm/lib/Target/ARM/ARMAsmPrinter.cpp | 23 +++++----
llvm/lib/Target/ARM/ARMFastISel.cpp | 2 +-
llvm/lib/Target/ARM/ARMISelLowering.cpp | 7 +--
llvm/lib/Target/ARM/ARMSubtarget.cpp | 10 +++-
llvm/test/CodeGen/ARM/elf-preemption.ll | 63 ++++++++++++++++++++++---
5 files changed, 84 insertions(+), 21 deletions(-)
diff --git a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
index 49c5c983e2934..b8b52f4c5e7a5 100644
--- a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
+++ b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
@@ -1017,16 +1017,19 @@ void ARMAsmPrinter::emitMachineConstantPoolValue(
TM.getTargetTriple().isOSBinFormatMachO() ? ARMII::MO_NONLAZY : 0;
MCSym = GetARMGVSymbol(GV, TF);
- // For dso_local weak symbols in ELF PIC mode, the assembler would eagerly
- // resolve a PC-relative expression like sym-(LPC+8) when the symbol and
- // reference are in the same section, preventing the linker from overriding
- // a weak definition with a non-weak definition from another section. Use a
- // .reloc directive rather than a fixup to force the generation of a
- // relocation (R_ARM_REL32) so the linker can perform the override. This is
- // restricted to dso_local symbols: a preemptible/external weak symbol
- // (e.g. an extern_weak reference) must use the GOT, as R_ARM_REL32 against
- // an external symbol cannot be used when making a shared object.
- if (GV->isWeakForLinker() && GV->isDSOLocal() &&
+ // For weak symbols referenced directly (not via the GOT) in ELF PIC mode,
+ // the assembler would eagerly resolve a PC-relative expression like
+ // sym-(LPC+8) when the symbol and reference are in the same section,
+ // preventing the linker from overriding a weak definition with a non-weak
+ // definition from another section. Use a .reloc directive rather than a
+ // fixup to force the generation of a relocation (R_ARM_REL32) so the
+ // linker can perform the override. This only applies to non-GOT entries:
+ // a weak symbol that goes through the GOT (preemptible/external, including
+ // default-visibility weak symbols even when dso_local) must keep its
+ // R_ARM_GOT_PREL, as R_ARM_REL32 against an external symbol cannot be used
+ // when making a shared object.
+ if (GV->isWeakForLinker() &&
+ !MF->getSubtarget<ARMSubtarget>().isGVInGOT(GV) &&
TM.getTargetTriple().isOSBinFormatELF() && TM.isPositionIndependent() &&
ACPV->getPCAdjustment() != 0) {
MCSymbol *CPILabel = OutContext.createTempSymbol();
diff --git a/llvm/lib/Target/ARM/ARMFastISel.cpp b/llvm/lib/Target/ARM/ARMFastISel.cpp
index 2c5d286e11c4f..460ecdc2a9d70 100644
--- a/llvm/lib/Target/ARM/ARMFastISel.cpp
+++ b/llvm/lib/Target/ARM/ARMFastISel.cpp
@@ -3033,7 +3033,7 @@ bool ARMFastISel::tryToFoldLoadIntoMI(MachineInstr *MI, unsigned OpNo,
}
Register ARMFastISel::ARMLowerPICELF(const GlobalValue *GV, MVT VT) {
- bool UseGOT_PREL = !GV->isDSOLocal();
+ bool UseGOT_PREL = Subtarget->isGVInGOT(GV);
LLVMContext *Context = &MF->getFunction().getContext();
unsigned ARMPCLabelIndex = AFI->createPICLabelUId();
unsigned PCAdj = Subtarget->isThumb() ? 4 : 8;
diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp
index 28d4c0ff22703..b3f8c47ce6e76 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -3651,10 +3651,11 @@ SDValue ARMTargetLowering::LowerGlobalAddressELF(SDValue Op,
return V;
if (isPositionIndependent()) {
- SDValue G = DAG.getTargetGlobalAddress(
- GV, dl, PtrVT, 0, GV->isDSOLocal() ? 0 : ARMII::MO_GOT);
+ bool UseGOT = Subtarget->isGVInGOT(GV);
+ SDValue G = DAG.getTargetGlobalAddress(GV, dl, PtrVT, 0,
+ UseGOT ? ARMII::MO_GOT : 0);
SDValue Result = DAG.getNode(ARMISD::WrapperPIC, dl, PtrVT, G);
- if (!GV->isDSOLocal())
+ if (UseGOT)
Result =
DAG.getLoad(PtrVT, dl, DAG.getEntryNode(), Result,
MachinePointerInfo::getGOT(DAG.getMachineFunction()));
diff --git a/llvm/lib/Target/ARM/ARMSubtarget.cpp b/llvm/lib/Target/ARM/ARMSubtarget.cpp
index 4893d8d3a9ef1..80a45d0c5045c 100644
--- a/llvm/lib/Target/ARM/ARMSubtarget.cpp
+++ b/llvm/lib/Target/ARM/ARMSubtarget.cpp
@@ -397,7 +397,15 @@ bool ARMSubtarget::isGVIndirectSymbol(const GlobalValue *GV) const {
}
bool ARMSubtarget::isGVInGOT(const GlobalValue *GV) const {
- return isTargetELF() && TM.isPositionIndependent() && !GV->isDSOLocal();
+ // Default-visibility weak symbols stay exported and preemptible in a shared
+ // object even when LLVM treats them as dso_local (e.g. compiled with
+ // -fno-semantic-interposition), so they must go through the GOT: a static
+ // PC-relative R_ARM_REL32 against an external symbol cannot be used when
+ // making a shared object. Hidden/protected weak symbols are non-preemptible
+ // and may be referenced directly.
+ return isTargetELF() && TM.isPositionIndependent() &&
+ (!GV->isDSOLocal() ||
+ (GV->isWeakForLinker() && GV->hasDefaultVisibility()));
}
unsigned ARMSubtarget::getMispredictionPenalty() const {
diff --git a/llvm/test/CodeGen/ARM/elf-preemption.ll b/llvm/test/CodeGen/ARM/elf-preemption.ll
index caefff7ea43bd..37a874efb9965 100644
--- a/llvm/test/CodeGen/ARM/elf-preemption.ll
+++ b/llvm/test/CodeGen/ARM/elf-preemption.ll
@@ -47,6 +47,9 @@ define ptr @get_dsolocal_var() nounwind {
ret ptr @dsolocal_var
}
+;; A default-visibility dso_local weak symbol is still exported and preemptible,
+;; so in PIC mode it must go through the GOT: R_ARM_REL32 against an external
+;; symbol cannot be used when making a shared object.
@weak_dsolocal_var = weak dso_local global i32 42
define ptr @get_weak_dsolocal_var() nounwind {
; STATIC-LABEL: get_weak_dsolocal_var:
@@ -59,14 +62,13 @@ define ptr @get_weak_dsolocal_var() nounwind {
; PIC: @ %bb.0:
; PIC-NEXT: ldr r0, .LCPI2_0
; PIC-NEXT: .LPC2_0:
-; PIC-NEXT: add r0, pc, r0
+; PIC-NEXT: ldr r0, [pc, r0]
; PIC-NEXT: bx lr
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI2_0:
; PIC-NEXT: .Ltmp1:
-; PIC-NEXT: .long .Ltmp1-(.LPC2_0+8)
-; PIC-NEXT: .reloc .Ltmp1, R_ARM_REL32, weak_dsolocal_var
+; PIC-NEXT: .long weak_dsolocal_var(GOT_PREL)-(.LPC2_0+8-.Ltmp1)
ret ptr @weak_dsolocal_var
}
@@ -164,14 +166,13 @@ define weak dso_local ptr @weak_dsolocal_func() nounwind {
; PIC: @ %bb.0:
; PIC-NEXT: ldr r0, .LCPI7_0
; PIC-NEXT: .LPC7_0:
-; PIC-NEXT: add r0, pc, r0
+; PIC-NEXT: ldr r0, [pc, r0]
; PIC-NEXT: bx lr
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI7_0:
; PIC-NEXT: .Ltmp3:
-; PIC-NEXT: .long .Ltmp3-(.LPC7_0+8)
-; PIC-NEXT: .reloc .Ltmp3, R_ARM_REL32, weak_dsolocal_func
+; PIC-NEXT: .long weak_dsolocal_func(GOT_PREL)-(.LPC7_0+8-.Ltmp3)
ret ptr @weak_dsolocal_func
}
@@ -242,3 +243,53 @@ define ptr @get_extern_weak_func() nounwind {
; PIC-NEXT: .long extern_weak_func(GOT_PREL)-(.LPC10_0+8-.Ltmp5)
ret ptr @extern_weak_func
}
+
+;; A default-visibility dso_local weak symbol is still exported and preemptible,
+;; so in PIC mode it must go through the GOT (see get_weak_dsolocal_var above).
+;; A hidden (or protected) dso_local weak symbol is non-preemptible and may be
+;; referenced directly: a .reloc/R_ARM_REL32 lets the linker override the weak
+;; definition with a non-weak one, and is valid in a shared object.
+ at weak_dsolocal_hidden_var = weak dso_local hidden global i32 42
+define ptr @get_weak_dsolocal_hidden_var() nounwind {
+; STATIC-LABEL: get_weak_dsolocal_hidden_var:
+; STATIC: @ %bb.0:
+; STATIC-NEXT: movw r0, :lower16:weak_dsolocal_hidden_var
+; STATIC-NEXT: movt r0, :upper16:weak_dsolocal_hidden_var
+; STATIC-NEXT: bx lr
+;
+; PIC-LABEL: get_weak_dsolocal_hidden_var:
+; PIC: @ %bb.0:
+; PIC-NEXT: ldr r0, .LCPI11_0
+; PIC-NEXT: .LPC11_0:
+; PIC-NEXT: add r0, pc, r0
+; PIC-NEXT: bx lr
+; PIC-NEXT: .p2align 2
+; PIC-NEXT: @ %bb.1:
+; PIC-NEXT: .LCPI11_0:
+; PIC-NEXT: .Ltmp6:
+; PIC-NEXT: .long .Ltmp6-(.LPC11_0+8)
+; PIC-NEXT: .reloc .Ltmp6, R_ARM_REL32, weak_dsolocal_hidden_var
+ ret ptr @weak_dsolocal_hidden_var
+}
+
+define weak dso_local hidden ptr @weak_dsolocal_hidden_func() nounwind {
+; STATIC-LABEL: weak_dsolocal_hidden_func:
+; STATIC: @ %bb.0:
+; STATIC-NEXT: movw r0, :lower16:weak_dsolocal_hidden_func
+; STATIC-NEXT: movt r0, :upper16:weak_dsolocal_hidden_func
+; STATIC-NEXT: bx lr
+;
+; PIC-LABEL: weak_dsolocal_hidden_func:
+; PIC: @ %bb.0:
+; PIC-NEXT: ldr r0, .LCPI12_0
+; PIC-NEXT: .LPC12_0:
+; PIC-NEXT: add r0, pc, r0
+; PIC-NEXT: bx lr
+; PIC-NEXT: .p2align 2
+; PIC-NEXT: @ %bb.1:
+; PIC-NEXT: .LCPI12_0:
+; PIC-NEXT: .Ltmp7:
+; PIC-NEXT: .long .Ltmp7-(.LPC12_0+8)
+; PIC-NEXT: .reloc .Ltmp7, R_ARM_REL32, weak_dsolocal_hidden_func
+ ret ptr @weak_dsolocal_hidden_func
+}
>From 6ad0945ad0b7b2aca246b49fb07b8ff28ae750c0 Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Sat, 18 Jul 2026 11:04:45 +0800
Subject: [PATCH 4/4] Revert "[ARM] Route default-visibility weak symbols
through the GOT in PIC mode"
This reverts commit a5ee1dc91a7f.
Per review (smithp35), the minimal fix in the preceding commit - restricting
the .reloc/R_ARM_REL32 path to dso_local weak symbols via isDSOLocal() - is
sufficient for the reported extern_weak regression. The lowering change
(isGVInGOT / LowerGlobalAddressELF / ARMLowerPICELF routing default-visibility
weak through the GOT) is being dropped for now to keep the change minimal.
The case the lowering change additionally covered - a defined default-visibility
dso_local weak symbol in a shared object - is not reachable from C/C++ (Clang
does not mark weak definitions dso_local for shared libraries; only for PIE,
where the link succeeds), and matches the pre-existing behavior, so it is not a
regression. It can be re-applied later if that case needs covering.
Co-Authored-By: Claude <noreply at anthropic.com>
---
llvm/lib/Target/ARM/ARMAsmPrinter.cpp | 23 ++++-----
llvm/lib/Target/ARM/ARMFastISel.cpp | 2 +-
llvm/lib/Target/ARM/ARMISelLowering.cpp | 7 ++-
llvm/lib/Target/ARM/ARMSubtarget.cpp | 10 +---
llvm/test/CodeGen/ARM/elf-preemption.ll | 63 +++----------------------
5 files changed, 21 insertions(+), 84 deletions(-)
diff --git a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
index b8b52f4c5e7a5..49c5c983e2934 100644
--- a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
+++ b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
@@ -1017,19 +1017,16 @@ void ARMAsmPrinter::emitMachineConstantPoolValue(
TM.getTargetTriple().isOSBinFormatMachO() ? ARMII::MO_NONLAZY : 0;
MCSym = GetARMGVSymbol(GV, TF);
- // For weak symbols referenced directly (not via the GOT) in ELF PIC mode,
- // the assembler would eagerly resolve a PC-relative expression like
- // sym-(LPC+8) when the symbol and reference are in the same section,
- // preventing the linker from overriding a weak definition with a non-weak
- // definition from another section. Use a .reloc directive rather than a
- // fixup to force the generation of a relocation (R_ARM_REL32) so the
- // linker can perform the override. This only applies to non-GOT entries:
- // a weak symbol that goes through the GOT (preemptible/external, including
- // default-visibility weak symbols even when dso_local) must keep its
- // R_ARM_GOT_PREL, as R_ARM_REL32 against an external symbol cannot be used
- // when making a shared object.
- if (GV->isWeakForLinker() &&
- !MF->getSubtarget<ARMSubtarget>().isGVInGOT(GV) &&
+ // For dso_local weak symbols in ELF PIC mode, the assembler would eagerly
+ // resolve a PC-relative expression like sym-(LPC+8) when the symbol and
+ // reference are in the same section, preventing the linker from overriding
+ // a weak definition with a non-weak definition from another section. Use a
+ // .reloc directive rather than a fixup to force the generation of a
+ // relocation (R_ARM_REL32) so the linker can perform the override. This is
+ // restricted to dso_local symbols: a preemptible/external weak symbol
+ // (e.g. an extern_weak reference) must use the GOT, as R_ARM_REL32 against
+ // an external symbol cannot be used when making a shared object.
+ if (GV->isWeakForLinker() && GV->isDSOLocal() &&
TM.getTargetTriple().isOSBinFormatELF() && TM.isPositionIndependent() &&
ACPV->getPCAdjustment() != 0) {
MCSymbol *CPILabel = OutContext.createTempSymbol();
diff --git a/llvm/lib/Target/ARM/ARMFastISel.cpp b/llvm/lib/Target/ARM/ARMFastISel.cpp
index 460ecdc2a9d70..2c5d286e11c4f 100644
--- a/llvm/lib/Target/ARM/ARMFastISel.cpp
+++ b/llvm/lib/Target/ARM/ARMFastISel.cpp
@@ -3033,7 +3033,7 @@ bool ARMFastISel::tryToFoldLoadIntoMI(MachineInstr *MI, unsigned OpNo,
}
Register ARMFastISel::ARMLowerPICELF(const GlobalValue *GV, MVT VT) {
- bool UseGOT_PREL = Subtarget->isGVInGOT(GV);
+ bool UseGOT_PREL = !GV->isDSOLocal();
LLVMContext *Context = &MF->getFunction().getContext();
unsigned ARMPCLabelIndex = AFI->createPICLabelUId();
unsigned PCAdj = Subtarget->isThumb() ? 4 : 8;
diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp
index b3f8c47ce6e76..28d4c0ff22703 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -3651,11 +3651,10 @@ SDValue ARMTargetLowering::LowerGlobalAddressELF(SDValue Op,
return V;
if (isPositionIndependent()) {
- bool UseGOT = Subtarget->isGVInGOT(GV);
- SDValue G = DAG.getTargetGlobalAddress(GV, dl, PtrVT, 0,
- UseGOT ? ARMII::MO_GOT : 0);
+ SDValue G = DAG.getTargetGlobalAddress(
+ GV, dl, PtrVT, 0, GV->isDSOLocal() ? 0 : ARMII::MO_GOT);
SDValue Result = DAG.getNode(ARMISD::WrapperPIC, dl, PtrVT, G);
- if (UseGOT)
+ if (!GV->isDSOLocal())
Result =
DAG.getLoad(PtrVT, dl, DAG.getEntryNode(), Result,
MachinePointerInfo::getGOT(DAG.getMachineFunction()));
diff --git a/llvm/lib/Target/ARM/ARMSubtarget.cpp b/llvm/lib/Target/ARM/ARMSubtarget.cpp
index 80a45d0c5045c..4893d8d3a9ef1 100644
--- a/llvm/lib/Target/ARM/ARMSubtarget.cpp
+++ b/llvm/lib/Target/ARM/ARMSubtarget.cpp
@@ -397,15 +397,7 @@ bool ARMSubtarget::isGVIndirectSymbol(const GlobalValue *GV) const {
}
bool ARMSubtarget::isGVInGOT(const GlobalValue *GV) const {
- // Default-visibility weak symbols stay exported and preemptible in a shared
- // object even when LLVM treats them as dso_local (e.g. compiled with
- // -fno-semantic-interposition), so they must go through the GOT: a static
- // PC-relative R_ARM_REL32 against an external symbol cannot be used when
- // making a shared object. Hidden/protected weak symbols are non-preemptible
- // and may be referenced directly.
- return isTargetELF() && TM.isPositionIndependent() &&
- (!GV->isDSOLocal() ||
- (GV->isWeakForLinker() && GV->hasDefaultVisibility()));
+ return isTargetELF() && TM.isPositionIndependent() && !GV->isDSOLocal();
}
unsigned ARMSubtarget::getMispredictionPenalty() const {
diff --git a/llvm/test/CodeGen/ARM/elf-preemption.ll b/llvm/test/CodeGen/ARM/elf-preemption.ll
index 37a874efb9965..caefff7ea43bd 100644
--- a/llvm/test/CodeGen/ARM/elf-preemption.ll
+++ b/llvm/test/CodeGen/ARM/elf-preemption.ll
@@ -47,9 +47,6 @@ define ptr @get_dsolocal_var() nounwind {
ret ptr @dsolocal_var
}
-;; A default-visibility dso_local weak symbol is still exported and preemptible,
-;; so in PIC mode it must go through the GOT: R_ARM_REL32 against an external
-;; symbol cannot be used when making a shared object.
@weak_dsolocal_var = weak dso_local global i32 42
define ptr @get_weak_dsolocal_var() nounwind {
; STATIC-LABEL: get_weak_dsolocal_var:
@@ -62,13 +59,14 @@ define ptr @get_weak_dsolocal_var() nounwind {
; PIC: @ %bb.0:
; PIC-NEXT: ldr r0, .LCPI2_0
; PIC-NEXT: .LPC2_0:
-; PIC-NEXT: ldr r0, [pc, r0]
+; PIC-NEXT: add r0, pc, r0
; PIC-NEXT: bx lr
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI2_0:
; PIC-NEXT: .Ltmp1:
-; PIC-NEXT: .long weak_dsolocal_var(GOT_PREL)-(.LPC2_0+8-.Ltmp1)
+; PIC-NEXT: .long .Ltmp1-(.LPC2_0+8)
+; PIC-NEXT: .reloc .Ltmp1, R_ARM_REL32, weak_dsolocal_var
ret ptr @weak_dsolocal_var
}
@@ -166,13 +164,14 @@ define weak dso_local ptr @weak_dsolocal_func() nounwind {
; PIC: @ %bb.0:
; PIC-NEXT: ldr r0, .LCPI7_0
; PIC-NEXT: .LPC7_0:
-; PIC-NEXT: ldr r0, [pc, r0]
+; PIC-NEXT: add r0, pc, r0
; PIC-NEXT: bx lr
; PIC-NEXT: .p2align 2
; PIC-NEXT: @ %bb.1:
; PIC-NEXT: .LCPI7_0:
; PIC-NEXT: .Ltmp3:
-; PIC-NEXT: .long weak_dsolocal_func(GOT_PREL)-(.LPC7_0+8-.Ltmp3)
+; PIC-NEXT: .long .Ltmp3-(.LPC7_0+8)
+; PIC-NEXT: .reloc .Ltmp3, R_ARM_REL32, weak_dsolocal_func
ret ptr @weak_dsolocal_func
}
@@ -243,53 +242,3 @@ define ptr @get_extern_weak_func() nounwind {
; PIC-NEXT: .long extern_weak_func(GOT_PREL)-(.LPC10_0+8-.Ltmp5)
ret ptr @extern_weak_func
}
-
-;; A default-visibility dso_local weak symbol is still exported and preemptible,
-;; so in PIC mode it must go through the GOT (see get_weak_dsolocal_var above).
-;; A hidden (or protected) dso_local weak symbol is non-preemptible and may be
-;; referenced directly: a .reloc/R_ARM_REL32 lets the linker override the weak
-;; definition with a non-weak one, and is valid in a shared object.
- at weak_dsolocal_hidden_var = weak dso_local hidden global i32 42
-define ptr @get_weak_dsolocal_hidden_var() nounwind {
-; STATIC-LABEL: get_weak_dsolocal_hidden_var:
-; STATIC: @ %bb.0:
-; STATIC-NEXT: movw r0, :lower16:weak_dsolocal_hidden_var
-; STATIC-NEXT: movt r0, :upper16:weak_dsolocal_hidden_var
-; STATIC-NEXT: bx lr
-;
-; PIC-LABEL: get_weak_dsolocal_hidden_var:
-; PIC: @ %bb.0:
-; PIC-NEXT: ldr r0, .LCPI11_0
-; PIC-NEXT: .LPC11_0:
-; PIC-NEXT: add r0, pc, r0
-; PIC-NEXT: bx lr
-; PIC-NEXT: .p2align 2
-; PIC-NEXT: @ %bb.1:
-; PIC-NEXT: .LCPI11_0:
-; PIC-NEXT: .Ltmp6:
-; PIC-NEXT: .long .Ltmp6-(.LPC11_0+8)
-; PIC-NEXT: .reloc .Ltmp6, R_ARM_REL32, weak_dsolocal_hidden_var
- ret ptr @weak_dsolocal_hidden_var
-}
-
-define weak dso_local hidden ptr @weak_dsolocal_hidden_func() nounwind {
-; STATIC-LABEL: weak_dsolocal_hidden_func:
-; STATIC: @ %bb.0:
-; STATIC-NEXT: movw r0, :lower16:weak_dsolocal_hidden_func
-; STATIC-NEXT: movt r0, :upper16:weak_dsolocal_hidden_func
-; STATIC-NEXT: bx lr
-;
-; PIC-LABEL: weak_dsolocal_hidden_func:
-; PIC: @ %bb.0:
-; PIC-NEXT: ldr r0, .LCPI12_0
-; PIC-NEXT: .LPC12_0:
-; PIC-NEXT: add r0, pc, r0
-; PIC-NEXT: bx lr
-; PIC-NEXT: .p2align 2
-; PIC-NEXT: @ %bb.1:
-; PIC-NEXT: .LCPI12_0:
-; PIC-NEXT: .Ltmp7:
-; PIC-NEXT: .long .Ltmp7-(.LPC12_0+8)
-; PIC-NEXT: .reloc .Ltmp7, R_ARM_REL32, weak_dsolocal_hidden_func
- ret ptr @weak_dsolocal_hidden_func
-}
More information about the llvm-commits
mailing list