[llvm] [ARM] Use GOT indirection for weak symbols in PIC mode (PR #198577)

dong jianqiang via llvm-commits llvm-commits at lists.llvm.org
Wed May 27 19:09:36 PDT 2026


https://github.com/dongjianqiang2 updated https://github.com/llvm/llvm-project/pull/198577

>From 0a6d18a6c1acf27b77e032540fdbdd6934280db9 Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Wed, 20 May 2026 00:22:03 +0800
Subject: [PATCH 1/2] [ARM] Use GOT indirection for weak symbols in PIC mode
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

On ARM ELF PIC, weak dso_local symbols were incorrectly accessed via
PC-relative constant pool entries (e.g. .long xxx-(.LPC+8)). The
assembler eagerly resolves this expression when both the symbol and
label are in the same section, preventing the linker from overriding
a weak definition with a strong one from another object file.

Fix #183916 by treating weak symbols as indirect (requiring GOT) on ELF
PIC, matching GCC's behavior. This affects:
- isGVIndirectSymbol (ARMTargetMachine) — Darwin, GlobalISel, stack prot.
- isGVInGOT (ARMSubtarget)
- LowerGlobalAddressELF (SelectionDAG path)
- ARMLowerPICELF (FastISel path)

Assisted-by: Claude Opus 4.7 <noreply at anthropic.com>
---
 llvm/lib/Target/ARM/ARMFastISel.cpp      |  3 ++-
 llvm/lib/Target/ARM/ARMISelLowering.cpp  | 17 +++++++++----
 llvm/lib/Target/ARM/ARMSubtarget.cpp     |  3 ++-
 llvm/lib/Target/ARM/ARMTargetMachine.h   |  9 +++++++
 llvm/test/CodeGen/ARM/elf-preemption.ll  | 32 ++++++------------------
 llvm/test/CodeGen/ARM/weak-hidden-pic.ll | 19 ++++++++++++++
 6 files changed, 52 insertions(+), 31 deletions(-)
 create 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 2c5d286e11c4f..1cf0d98f690aa 100644
--- a/llvm/lib/Target/ARM/ARMFastISel.cpp
+++ b/llvm/lib/Target/ARM/ARMFastISel.cpp
@@ -3033,7 +3033,8 @@ bool ARMFastISel::tryToFoldLoadIntoMI(MachineInstr *MI, unsigned OpNo,
 }
 
 Register ARMFastISel::ARMLowerPICELF(const GlobalValue *GV, MVT VT) {
-  bool UseGOT_PREL = !GV->isDSOLocal();
+  // Weak symbols need GOT indirection even when hidden/DSO-local.
+  bool UseGOT_PREL = !GV->isDSOLocal() || GV->isWeakForLinker();
   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 f38ad29bd96d5..f62e61d49d9c2 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -3643,16 +3643,23 @@ SDValue ARMTargetLowering::LowerGlobalAddressELF(SDValue Op,
   const GlobalValue *GV = cast<GlobalAddressSDNode>(Op)->getGlobal();
   bool IsRO = isReadOnly(GV);
 
-  // promoteToConstantPool only if not generating XO text section
-  if (GV->isDSOLocal() && !Subtarget->genExecuteOnly())
+  // promoteToConstantPool only if not generating XO text section.
+  // Weak symbols may be overridden at link time, so don't inline them.
+  if (GV->isDSOLocal() && !Subtarget->genExecuteOnly() &&
+      !GV->isWeakForLinker())
     if (SDValue V = promoteToConstantPool(this, GV, DAG, PtrVT, dl))
       return V;
 
   if (isPositionIndependent()) {
-    SDValue G = DAG.getTargetGlobalAddress(
-        GV, dl, PtrVT, 0, GV->isDSOLocal() ? 0 : ARMII::MO_GOT);
+    // 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 strong one.
+    bool UseGOT = !GV->isDSOLocal() || GV->isWeakForLinker();
+    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..0f324a6a31757 100644
--- a/llvm/lib/Target/ARM/ARMSubtarget.cpp
+++ b/llvm/lib/Target/ARM/ARMSubtarget.cpp
@@ -397,7 +397,8 @@ bool ARMSubtarget::isGVIndirectSymbol(const GlobalValue *GV) const {
 }
 
 bool ARMSubtarget::isGVInGOT(const GlobalValue *GV) const {
-  return isTargetELF() && TM.isPositionIndependent() && !GV->isDSOLocal();
+  return isTargetELF() && TM.isPositionIndependent() &&
+         (!GV->isDSOLocal() || GV->isWeakForLinker());
 }
 
 unsigned ARMSubtarget::getMispredictionPenalty() const {
diff --git a/llvm/lib/Target/ARM/ARMTargetMachine.h b/llvm/lib/Target/ARM/ARMTargetMachine.h
index 215b541620acb..026ac48339197 100644
--- a/llvm/lib/Target/ARM/ARMTargetMachine.h
+++ b/llvm/lib/Target/ARM/ARMTargetMachine.h
@@ -111,6 +111,15 @@ 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 strong
+    // 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 154c29c1c029c..c6aabd6c11124 100644
--- a/llvm/test/CodeGen/ARM/elf-preemption.ll
+++ b/llvm/test/CodeGen/ARM/elf-preemption.ll
@@ -59,12 +59,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:    .long weak_dsolocal_var-(.LPC2_0+8)
+; PIC-NEXT:  .Ltmp1:
+; PIC-NEXT:    .long weak_dsolocal_var(GOT_PREL)-(.LPC2_0+8-.Ltmp1)
   ret ptr @weak_dsolocal_var
 }
 
@@ -126,8 +127,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
 }
 
@@ -137,17 +138,6 @@ define dso_local ptr @dsolocal_func() nounwind {
 ; STATIC-NEXT:    movw r0, :lower16:dsolocal_func
 ; STATIC-NEXT:    movt r0, :upper16:dsolocal_func
 ; STATIC-NEXT:    bx lr
-;
-; PIC-LABEL: dsolocal_func:
-; PIC:       @ %bb.0:
-; PIC-NEXT:    ldr r0, .LCPI6_0
-; PIC-NEXT:  .LPC6_0:
-; PIC-NEXT:    add r0, pc, r0
-; PIC-NEXT:    bx lr
-; PIC-NEXT:    .p2align 2
-; PIC-NEXT:  @ %bb.1:
-; PIC-NEXT:  .LCPI6_0:
-; PIC-NEXT:    .long .Ldsolocal_func$local-(.LPC6_0+8)
   ret ptr @dsolocal_func
 }
 
@@ -162,12 +152,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:    .long weak_dsolocal_func-(.LPC7_0+8)
+; PIC-NEXT:  .Ltmp3:
+; PIC-NEXT:    .long weak_dsolocal_func(GOT_PREL)-(.LPC7_0+8-.Ltmp3)
   ret ptr @weak_dsolocal_func
 }
 
@@ -180,13 +171,6 @@ define dso_local void @call_dsolocal_func() nounwind {
 ; STATIC-NEXT:    push {r11, lr}
 ; STATIC-NEXT:    bl dsolocal_func
 ; STATIC-NEXT:    pop {r11, pc}
-;
-; PIC-LABEL: call_dsolocal_func:
-; PIC:       @ %bb.0:
-; PIC-NEXT:    .save {r11, lr}
-; PIC-NEXT:    push {r11, lr}
-; PIC-NEXT:    bl .Ldsolocal_func$local
-; PIC-NEXT:    pop {r11, pc}
   call ptr @dsolocal_func()
   ret void
 }
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..9948948693dfd
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/weak-hidden-pic.ll
@@ -0,0 +1,19 @@
+; RUN: llc < %s -mtriple=armv7-linux-gnueabi -relocation-model=pic | FileCheck %s -check-prefixes=CHECK,ARM
+; RUN: llc < %s -mtriple=thumbv7-linux-gnueabi -relocation-model=pic | FileCheck %s -check-prefixes=CHECK,THUMB
+
+; 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 strong one from another object file.
+
+define weak dso_local hidden void @weak_hidden_func() {
+  ret void
+}
+
+; CHECK-LABEL: weak_hidden_func_addr:
+; ARM:       .long weak_hidden_func(GOT_PREL)
+; THUMB:     .long weak_hidden_func(GOT_PREL)
+define i8* @weak_hidden_func_addr() {
+  ret i8* bitcast (void()* @weak_hidden_func to i8*)
+}

>From 38b660943bf3e6beba5ab24bcb388d1e9cecb80e Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Thu, 28 May 2026 10:05:11 +0800
Subject: [PATCH 2/2] [ARM] Address review feedback for weak symbol GOT
 indirection

Remove redundant promoteToConstantPool weak guard (weak symbols never
have local linkage, so it was dead code). Restore accidentally removed
PIC checks for non-weak dso_local functions. Simplify CHECK prefixes
and fix "strong" -> "non-weak" terminology.

Assisted-By: Claude Opus 4.7 <noreply at anthropic.com>
---
 llvm/lib/Target/ARM/ARMISelLowering.cpp  |  8 +++-----
 llvm/lib/Target/ARM/ARMTargetMachine.h   |  4 ++--
 llvm/test/CodeGen/ARM/elf-preemption.ll  | 18 ++++++++++++++++++
 llvm/test/CodeGen/ARM/weak-hidden-pic.ll |  9 ++++-----
 4 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp
index f62e61d49d9c2..1386159712578 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -3643,10 +3643,8 @@ SDValue ARMTargetLowering::LowerGlobalAddressELF(SDValue Op,
   const GlobalValue *GV = cast<GlobalAddressSDNode>(Op)->getGlobal();
   bool IsRO = isReadOnly(GV);
 
-  // promoteToConstantPool only if not generating XO text section.
-  // Weak symbols may be overridden at link time, so don't inline them.
-  if (GV->isDSOLocal() && !Subtarget->genExecuteOnly() &&
-      !GV->isWeakForLinker())
+  // promoteToConstantPool only if not generating XO text section
+  if (GV->isDSOLocal() && !Subtarget->genExecuteOnly())
     if (SDValue V = promoteToConstantPool(this, GV, DAG, PtrVT, dl))
       return V;
 
@@ -3654,7 +3652,7 @@ SDValue ARMTargetLowering::LowerGlobalAddressELF(SDValue Op,
     // 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 strong one.
+    // 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);
diff --git a/llvm/lib/Target/ARM/ARMTargetMachine.h b/llvm/lib/Target/ARM/ARMTargetMachine.h
index 026ac48339197..109d6fd5e6671 100644
--- a/llvm/lib/Target/ARM/ARMTargetMachine.h
+++ b/llvm/lib/Target/ARM/ARMTargetMachine.h
@@ -114,8 +114,8 @@ class ARMBaseTargetMachine : public CodeGenTargetMachineImpl {
     // 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 strong
-    // one. Use GOT indirection for weak symbols to avoid this.
+    // 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;
diff --git a/llvm/test/CodeGen/ARM/elf-preemption.ll b/llvm/test/CodeGen/ARM/elf-preemption.ll
index c6aabd6c11124..88b35683b97c0 100644
--- a/llvm/test/CodeGen/ARM/elf-preemption.ll
+++ b/llvm/test/CodeGen/ARM/elf-preemption.ll
@@ -138,6 +138,17 @@ define dso_local ptr @dsolocal_func() nounwind {
 ; STATIC-NEXT:    movw r0, :lower16:dsolocal_func
 ; STATIC-NEXT:    movt r0, :upper16:dsolocal_func
 ; STATIC-NEXT:    bx lr
+;
+; PIC-LABEL: dsolocal_func:
+; PIC:       @ %bb.0:
+; PIC-NEXT:    ldr r0, .LCPI6_0
+; PIC-NEXT:  .LPC6_0:
+; PIC-NEXT:    add r0, pc, r0
+; PIC-NEXT:    bx lr
+; PIC-NEXT:    .p2align 2
+; PIC-NEXT:  @ %bb.1:
+; PIC-NEXT:  .LCPI6_0:
+; PIC-NEXT:    .long .Ldsolocal_func$local-(.LPC6_0+8)
   ret ptr @dsolocal_func
 }
 
@@ -171,6 +182,13 @@ define dso_local void @call_dsolocal_func() nounwind {
 ; STATIC-NEXT:    push {r11, lr}
 ; STATIC-NEXT:    bl dsolocal_func
 ; STATIC-NEXT:    pop {r11, pc}
+;
+; PIC-LABEL: call_dsolocal_func:
+; PIC:       @ %bb.0:
+; PIC-NEXT:    .save {r11, lr}
+; PIC-NEXT:    push {r11, lr}
+; PIC-NEXT:    bl .Ldsolocal_func$local
+; PIC-NEXT:    pop {r11, pc}
   call ptr @dsolocal_func()
   ret void
 }
diff --git a/llvm/test/CodeGen/ARM/weak-hidden-pic.ll b/llvm/test/CodeGen/ARM/weak-hidden-pic.ll
index 9948948693dfd..c8190efd9d755 100644
--- a/llvm/test/CodeGen/ARM/weak-hidden-pic.ll
+++ b/llvm/test/CodeGen/ARM/weak-hidden-pic.ll
@@ -1,19 +1,18 @@
-; RUN: llc < %s -mtriple=armv7-linux-gnueabi -relocation-model=pic | FileCheck %s -check-prefixes=CHECK,ARM
-; RUN: llc < %s -mtriple=thumbv7-linux-gnueabi -relocation-model=pic | FileCheck %s -check-prefixes=CHECK,THUMB
+; RUN: llc < %s -mtriple=armv7-linux-gnueabi -relocation-model=pic | FileCheck %s
+; RUN: llc < %s -mtriple=thumbv7-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 strong one from another object file.
+; 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:
-; ARM:       .long weak_hidden_func(GOT_PREL)
-; THUMB:     .long weak_hidden_func(GOT_PREL)
+; CHECK:       .long weak_hidden_func(GOT_PREL)
 define i8* @weak_hidden_func_addr() {
   ret i8* bitcast (void()* @weak_hidden_func to i8*)
 }



More information about the llvm-commits mailing list