[llvm] [ARM] support -mlong-calls -fPIC on arm32 #39970 (PR #147313)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 11 10:49:31 PDT 2026
https://github.com/jxz-hw updated https://github.com/llvm/llvm-project/pull/147313
>From 4f69ac3f1055f036b58c06dda1f4306e5bd07dfa Mon Sep 17 00:00:00 2001
From: jxz-hw <jiangxuezhi2 at huawei.com>
Date: Sun, 7 Jun 2026 20:27:36 +0800
Subject: [PATCH] [ARM] Support -mlong-calls with -fPIC on ARM ELF targets
fix issue #39970 and #142982
- GlobalAddress: use GOT-based addressing via WrapperPIC. DSO-local symbols use a plain PC-relative WrapperPIC; non-DSO-local symbols additionally load the target address from the GOT.
- ExternalSymbol: load the symbol address from the GOT via a GOT_PREL constant pool entry followed by a PICLDR.
- With -mexecute-only: movw/movt pairs are used instead of constant pools. Note: execute-only may not support PIC; this path is only reached for non-PIC targets.
---
llvm/lib/Target/ARM/ARMConstantPoolValue.cpp | 9 +-
llvm/lib/Target/ARM/ARMConstantPoolValue.h | 5 +-
llvm/lib/Target/ARM/ARMISelLowering.cpp | 45 ++-
.../ARM/subtarget-features-long-calls.ll | 261 ++++++++++++++++--
4 files changed, 282 insertions(+), 38 deletions(-)
diff --git a/llvm/lib/Target/ARM/ARMConstantPoolValue.cpp b/llvm/lib/Target/ARM/ARMConstantPoolValue.cpp
index 2a3f33fc44b59..1c1d66c83bb73 100644
--- a/llvm/lib/Target/ARM/ARMConstantPoolValue.cpp
+++ b/llvm/lib/Target/ARM/ARMConstantPoolValue.cpp
@@ -230,10 +230,11 @@ ARMConstantPoolSymbol::ARMConstantPoolSymbol(LLVMContext &C, StringRef s,
AddCurrentAddress),
S(std::string(s)) {}
-ARMConstantPoolSymbol *ARMConstantPoolSymbol::Create(LLVMContext &C,
- StringRef s, unsigned ID,
- unsigned char PCAdj) {
- return new ARMConstantPoolSymbol(C, s, ID, PCAdj, ARMCP::no_modifier, false);
+ARMConstantPoolSymbol *
+ARMConstantPoolSymbol::Create(LLVMContext &C, StringRef s, unsigned ID,
+ unsigned char PCAdj,
+ ARMCP::ARMCPModifier Modifier) {
+ return new ARMConstantPoolSymbol(C, s, ID, PCAdj, Modifier, false);
}
int ARMConstantPoolSymbol::getExistingMachineCPValue(MachineConstantPool *CP,
diff --git a/llvm/lib/Target/ARM/ARMConstantPoolValue.h b/llvm/lib/Target/ARM/ARMConstantPoolValue.h
index e21b2c935c010..2c24b6551c778 100644
--- a/llvm/lib/Target/ARM/ARMConstantPoolValue.h
+++ b/llvm/lib/Target/ARM/ARMConstantPoolValue.h
@@ -213,8 +213,9 @@ class ARMConstantPoolSymbol : public ARMConstantPoolValue {
bool AddCurrentAddress);
public:
- static ARMConstantPoolSymbol *Create(LLVMContext &C, StringRef s, unsigned ID,
- unsigned char PCAdj);
+ static ARMConstantPoolSymbol *
+ Create(LLVMContext &C, StringRef s, unsigned ID, unsigned char PCAdj,
+ ARMCP::ARMCPModifier Modifier = ARMCP::no_modifier);
StringRef getSymbol() const { return S; }
diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp
index e08a4f5ebda08..e95bcccb1a78a 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -2438,19 +2438,35 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
auto PtrVt = getPointerTy(DAG.getDataLayout());
if (Subtarget->genLongCalls()) {
- assert((!isPositionIndependent() || TT.isOSWindows()) &&
- "long-calls codegen is not position independent!");
+ bool isPIC = isPositionIndependent() && !TT.isOSWindows();
+ assert(!(isPIC && Subtarget->genExecuteOnly()) &&
+ "long-calls with execute-only and position-independent "
+ "code is not supported");
+
// Handle a global address or an external symbol. If it's not one of
// those, the target's already in a register, so we don't need to do
// anything extra.
if (isa<GlobalAddressSDNode>(Callee)) {
if (Subtarget->genExecuteOnly()) {
+ // Execute-only forbids constant pools in .text, so use movw/movt.
+ // fPIC is not supported with execute-only.
if (Subtarget->useMovt())
++NumMovwMovt;
Callee = DAG.getNode(ARMISD::Wrapper, dl, PtrVt,
DAG.getTargetGlobalAddress(GVal, dl, PtrVt));
+ } else if (isPIC) {
+ // PIC without execute-only: use GOT-based addressing.
+ // DSO-local symbols use a plain PC-relative WrapperPIC;
+ // non-DSO-local symbols additionally load the address from the GOT.
+ SDValue G = DAG.getTargetGlobalAddress(
+ GVal, dl, PtrVt, 0, GVal->isDSOLocal() ? 0 : ARMII::MO_GOT);
+ Callee = DAG.getNode(ARMISD::WrapperPIC, dl, PtrVt, G);
+ if (!GVal->isDSOLocal())
+ Callee =
+ DAG.getLoad(PtrVt, dl, DAG.getEntryNode(), Callee,
+ MachinePointerInfo::getGOT(DAG.getMachineFunction()));
} else {
- // Create a constant pool entry for the callee address
+ // Neither execute-only nor PIC: load the address from a constant pool.
unsigned ARMPCLabelIndex = AFI->createPICLabelUId();
ARMConstantPoolValue *CPV = ARMConstantPoolConstant::Create(
GVal, ARMPCLabelIndex, ARMCP::CPValue, 0);
@@ -2466,12 +2482,31 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
const char *Sym = S->getSymbol();
if (Subtarget->genExecuteOnly()) {
+ // Execute-only forbids constant pools in .text, so use movw/movt.
+ // fPIC is not supported with execute-only.
if (Subtarget->useMovt())
++NumMovwMovt;
Callee = DAG.getNode(ARMISD::Wrapper, dl, PtrVt,
- DAG.getTargetGlobalAddress(GVal, dl, PtrVt));
+ DAG.getTargetExternalSymbol(Sym, PtrVt, 0));
+ } else if (isPIC) {
+ // PIC without execute-only: use GOT-based addressing via a GOT_PREL
+ // constant pool entry.
+ unsigned ARMPCLabelIndex = AFI->createPICLabelUId();
+ ARMConstantPoolValue *CPV = ARMConstantPoolSymbol::Create(
+ *DAG.getContext(), Sym, ARMPCLabelIndex, /*PCAdj=*/0,
+ ARMCP::GOT_PREL);
+ SDValue CPAddr = DAG.getTargetConstantPool(CPV, PtrVt, Align(4));
+ CPAddr = DAG.getNode(ARMISD::Wrapper, dl, MVT::i32, CPAddr);
+ SDValue GOTOffset = DAG.getLoad(
+ PtrVt, dl, DAG.getEntryNode(), CPAddr,
+ MachinePointerInfo::getConstantPool(DAG.getMachineFunction()));
+ SDValue PICLabel = DAG.getConstant(ARMPCLabelIndex, dl, MVT::i32);
+ Callee = DAG.getNode(ARMISD::PIC_ADD, dl, PtrVt, GOTOffset, PICLabel);
+ Callee =
+ DAG.getLoad(PtrVt, dl, DAG.getEntryNode(), Callee,
+ MachinePointerInfo::getGOT(DAG.getMachineFunction()));
} else {
- // Create a constant pool entry for the callee address
+ // Neither execute-only nor PIC: load the address from a constant pool.
unsigned ARMPCLabelIndex = AFI->createPICLabelUId();
ARMConstantPoolValue *CPV = ARMConstantPoolSymbol::Create(
*DAG.getContext(), Sym, ARMPCLabelIndex, 0);
diff --git a/llvm/test/CodeGen/ARM/subtarget-features-long-calls.ll b/llvm/test/CodeGen/ARM/subtarget-features-long-calls.ll
index 112a840ba92ee..b8e0884acd67a 100644
--- a/llvm/test/CodeGen/ARM/subtarget-features-long-calls.ll
+++ b/llvm/test/CodeGen/ARM/subtarget-features-long-calls.ll
@@ -1,44 +1,127 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
; RUN: llc -mtriple=thumb-- -mcpu=cortex-a8 -relocation-model=static %s -o - | FileCheck -check-prefix=NO-OPTION %s
; RUN: llc -mtriple=thumb-- -mcpu=cortex-a8 -relocation-model=static %s -o - -mattr=+long-calls | FileCheck -check-prefix=LONGCALL %s
; RUN: llc -mtriple=thumb-- -mcpu=cortex-a8 -relocation-model=static %s -o - -mattr=-long-calls | FileCheck -check-prefix=NO-LONGCALL %s
; RUN: llc -mtriple=thumb-- -mcpu=cortex-a8 -relocation-model=static %s -o - -O0 | FileCheck -check-prefix=NO-OPTION %s
; RUN: llc -mtriple=thumb-- -mcpu=cortex-a8 -relocation-model=static %s -o - -O0 -mattr=+long-calls | FileCheck -check-prefix=LONGCALL %s
; RUN: llc -mtriple=thumb-- -mcpu=cortex-a8 -relocation-model=static %s -o - -O0 -mattr=-long-calls | FileCheck -check-prefix=NO-LONGCALL %s
-
-; NO-OPTION-LABEL: {{_?}}caller0
-; NO-OPTION: ldr [[R0:r[0-9]+]], [[L0:.*]]
-; NO-OPTION: blx [[R0]]
-; NO-OPTION: [[L0]]:
-; NO-OPTION: .long {{_?}}callee0
-
-; LONGCALL-LABEL: {{_?}}caller0
-; LONGCALL: ldr [[R0:r[0-9]+]], [[L0:.*]]
-; LONGCALL: blx [[R0]]
-; LONGCALL: [[L0]]:
-; LONGCALL: .long {{_?}}callee0
-
-; NO-LONGCALL-LABEL: {{_?}}caller0
-; NO-LONGCALL: bl {{_?}}callee0
+; RUN: llc -mtriple=arm-linux-gnueabi -mcpu=cortex-a8 -relocation-model=static %s -o - -mattr=+long-calls,+execute-only | FileCheck -check-prefix=XO-LONGCALL %s
+; RUN: llc -mtriple=arm-linux-gnueabi -mcpu=cortex-a8 -relocation-model=pic %s -o - -mattr=+long-calls | FileCheck -check-prefix=PIC-LONGCALL %s
define i32 @caller0() #0 {
+; NO-OPTION-LABEL: caller0:
+; NO-OPTION: @ %bb.0: @ %entry
+; NO-OPTION-NEXT: push {r7, lr}
+; NO-OPTION-NEXT: ldr r0, .LCPI0_0
+; NO-OPTION-NEXT: blx r0
+; NO-OPTION-NEXT: movs r0, #0
+; NO-OPTION-NEXT: pop {r7, pc}
+; NO-OPTION-NEXT: .p2align 2
+; NO-OPTION-NEXT: @ %bb.1:
+; NO-OPTION-NEXT: .LCPI0_0:
+; NO-OPTION-NEXT: .long callee0
+;
+; LONGCALL-LABEL: caller0:
+; LONGCALL: @ %bb.0: @ %entry
+; LONGCALL-NEXT: push {r7, lr}
+; LONGCALL-NEXT: ldr r0, .LCPI0_0
+; LONGCALL-NEXT: blx r0
+; LONGCALL-NEXT: movs r0, #0
+; LONGCALL-NEXT: pop {r7, pc}
+; LONGCALL-NEXT: .p2align 2
+; LONGCALL-NEXT: @ %bb.1:
+; LONGCALL-NEXT: .LCPI0_0:
+; LONGCALL-NEXT: .long callee0
+;
+; NO-LONGCALL-LABEL: caller0:
+; NO-LONGCALL: @ %bb.0: @ %entry
+; NO-LONGCALL-NEXT: push {r7, lr}
+; NO-LONGCALL-NEXT: bl callee0
+; NO-LONGCALL-NEXT: movs r0, #0
+; NO-LONGCALL-NEXT: pop {r7, pc}
+;
+; XO-LONGCALL-LABEL: caller0:
+; XO-LONGCALL: @ %bb.0: @ %entry
+; XO-LONGCALL-NEXT: .save {r11, lr}
+; XO-LONGCALL-NEXT: push {r11, lr}
+; XO-LONGCALL-NEXT: movw r0, :lower16:callee0
+; XO-LONGCALL-NEXT: movt r0, :upper16:callee0
+; XO-LONGCALL-NEXT: blx r0
+; XO-LONGCALL-NEXT: mov r0, #0
+; XO-LONGCALL-NEXT: pop {r11, pc}
+;
+; PIC-LONGCALL-LABEL: caller0:
+; PIC-LONGCALL: @ %bb.0: @ %entry
+; PIC-LONGCALL-NEXT: .save {r11, lr}
+; PIC-LONGCALL-NEXT: push {r11, lr}
+; PIC-LONGCALL-NEXT: ldr r0, .LCPI0_0
+; PIC-LONGCALL-NEXT: .LPC0_0:
+; PIC-LONGCALL-NEXT: ldr r0, [pc, r0]
+; PIC-LONGCALL-NEXT: blx r0
+; PIC-LONGCALL-NEXT: mov r0, #0
+; PIC-LONGCALL-NEXT: pop {r11, pc}
+; PIC-LONGCALL-NEXT: .p2align 2
+; PIC-LONGCALL-NEXT: @ %bb.1:
+; PIC-LONGCALL-NEXT: .LCPI0_0:
+; PIC-LONGCALL-NEXT: .Ltmp0:
+; PIC-LONGCALL-NEXT: .long callee0(GOT_PREL)-(.LPC0_0+8-.Ltmp0)
entry:
tail call void @callee0()
ret i32 0
}
-; NO-OPTION-LABEL: {{_?}}caller1
-; NO-OPTION: bl {{_?}}callee0
-
-; LONGCALL-LABEL: {{_?}}caller1
-; LONGCALL: ldr [[R0:r[0-9]+]], [[L0:.*]]
-; LONGCALL: blx [[R0]]
-; LONGCALL: [[L0]]:
-; LONGCALL: .long {{_?}}callee0
-
-; NO-LONGCALL-LABEL: {{_?}}caller1
-; NO-LONGCALL: bl {{_?}}callee0
-
define i32 @caller1() {
+; NO-OPTION-LABEL: caller1:
+; NO-OPTION: @ %bb.0: @ %entry
+; NO-OPTION-NEXT: push {r7, lr}
+; NO-OPTION-NEXT: bl callee0
+; NO-OPTION-NEXT: movs r0, #0
+; NO-OPTION-NEXT: pop {r7, pc}
+;
+; LONGCALL-LABEL: caller1:
+; LONGCALL: @ %bb.0: @ %entry
+; LONGCALL-NEXT: push {r7, lr}
+; LONGCALL-NEXT: ldr r0, .LCPI1_0
+; LONGCALL-NEXT: blx r0
+; LONGCALL-NEXT: movs r0, #0
+; LONGCALL-NEXT: pop {r7, pc}
+; LONGCALL-NEXT: .p2align 2
+; LONGCALL-NEXT: @ %bb.1:
+; LONGCALL-NEXT: .LCPI1_0:
+; LONGCALL-NEXT: .long callee0
+;
+; NO-LONGCALL-LABEL: caller1:
+; NO-LONGCALL: @ %bb.0: @ %entry
+; NO-LONGCALL-NEXT: push {r7, lr}
+; NO-LONGCALL-NEXT: bl callee0
+; NO-LONGCALL-NEXT: movs r0, #0
+; NO-LONGCALL-NEXT: pop {r7, pc}
+;
+; XO-LONGCALL-LABEL: caller1:
+; XO-LONGCALL: @ %bb.0: @ %entry
+; XO-LONGCALL-NEXT: .save {r11, lr}
+; XO-LONGCALL-NEXT: push {r11, lr}
+; XO-LONGCALL-NEXT: movw r0, :lower16:callee0
+; XO-LONGCALL-NEXT: movt r0, :upper16:callee0
+; XO-LONGCALL-NEXT: blx r0
+; XO-LONGCALL-NEXT: mov r0, #0
+; XO-LONGCALL-NEXT: pop {r11, pc}
+;
+; PIC-LONGCALL-LABEL: caller1:
+; PIC-LONGCALL: @ %bb.0: @ %entry
+; PIC-LONGCALL-NEXT: .save {r11, lr}
+; PIC-LONGCALL-NEXT: push {r11, lr}
+; PIC-LONGCALL-NEXT: ldr r0, .LCPI1_0
+; PIC-LONGCALL-NEXT: .LPC1_0:
+; PIC-LONGCALL-NEXT: ldr r0, [pc, r0]
+; PIC-LONGCALL-NEXT: blx r0
+; PIC-LONGCALL-NEXT: mov r0, #0
+; PIC-LONGCALL-NEXT: pop {r11, pc}
+; PIC-LONGCALL-NEXT: .p2align 2
+; PIC-LONGCALL-NEXT: @ %bb.1:
+; PIC-LONGCALL-NEXT: .LCPI1_0:
+; PIC-LONGCALL-NEXT: .Ltmp1:
+; PIC-LONGCALL-NEXT: .long callee0(GOT_PREL)-(.LPC1_0+8-.Ltmp1)
entry:
tail call void @callee0()
ret i32 0
@@ -47,3 +130,127 @@ entry:
declare void @callee0()
attributes #0 = { "target-features"="+long-calls" }
+
+define dso_local void @global_func() {
+; NO-OPTION-LABEL: global_func:
+; NO-OPTION: @ %bb.0: @ %entry
+; NO-OPTION-NEXT: bx lr
+;
+; LONGCALL-LABEL: global_func:
+; LONGCALL: @ %bb.0: @ %entry
+; LONGCALL-NEXT: bx lr
+;
+; NO-LONGCALL-LABEL: global_func:
+; NO-LONGCALL: @ %bb.0: @ %entry
+; NO-LONGCALL-NEXT: bx lr
+;
+; XO-LONGCALL-LABEL: global_func:
+; XO-LONGCALL: @ %bb.0: @ %entry
+; XO-LONGCALL-NEXT: bx lr
+entry:
+ ret void
+}
+
+define void @test_global() {
+; NO-OPTION-LABEL: test_global:
+; NO-OPTION: @ %bb.0: @ %entry
+; NO-OPTION-NEXT: push {r7, lr}
+; NO-OPTION-NEXT: bl global_func
+; NO-OPTION-NEXT: pop {r7, pc}
+;
+; LONGCALL-LABEL: test_global:
+; LONGCALL: @ %bb.0: @ %entry
+; LONGCALL-NEXT: push {r7, lr}
+; LONGCALL-NEXT: ldr r0, .LCPI3_0
+; LONGCALL-NEXT: blx r0
+; LONGCALL-NEXT: pop {r7, pc}
+; LONGCALL-NEXT: .p2align 2
+; LONGCALL-NEXT: @ %bb.1:
+; LONGCALL-NEXT: .LCPI3_0:
+; LONGCALL-NEXT: .long global_func
+;
+; NO-LONGCALL-LABEL: test_global:
+; NO-LONGCALL: @ %bb.0: @ %entry
+; NO-LONGCALL-NEXT: push {r7, lr}
+; NO-LONGCALL-NEXT: bl global_func
+; NO-LONGCALL-NEXT: pop {r7, pc}
+;
+; XO-LONGCALL-LABEL: test_global:
+; XO-LONGCALL: @ %bb.0: @ %entry
+; XO-LONGCALL-NEXT: .save {r11, lr}
+; XO-LONGCALL-NEXT: push {r11, lr}
+; XO-LONGCALL-NEXT: movw r0, :lower16:global_func
+; XO-LONGCALL-NEXT: movt r0, :upper16:global_func
+; XO-LONGCALL-NEXT: blx r0
+; XO-LONGCALL-NEXT: pop {r11, pc}
+;
+; PIC-LONGCALL-LABEL: test_global:
+; PIC-LONGCALL: @ %bb.0: @ %entry
+; PIC-LONGCALL-NEXT: .save {r11, lr}
+; PIC-LONGCALL-NEXT: push {r11, lr}
+; PIC-LONGCALL-NEXT: ldr r0, .LCPI3_0
+; PIC-LONGCALL-NEXT: .LPC3_0:
+; PIC-LONGCALL-NEXT: add r0, pc, r0
+; PIC-LONGCALL-NEXT: blx r0
+; PIC-LONGCALL-NEXT: pop {r11, pc}
+; PIC-LONGCALL-NEXT: .p2align 2
+; PIC-LONGCALL-NEXT: @ %bb.1:
+; PIC-LONGCALL-NEXT: .LCPI3_0:
+; PIC-LONGCALL-NEXT: .long .Lglobal_func$local-(.LPC3_0+8)
+entry:
+ call void @global_func()
+ ret void
+}
+
+declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg)
+
+define void @test_memset(ptr %dst, i8 %val, i32 %len) {
+; NO-OPTION-LABEL: test_memset:
+; NO-OPTION: @ %bb.0: @ %entry
+; NO-OPTION-NEXT: push {r7, lr}
+; NO-OPTION-NEXT: bl memset
+; NO-OPTION-NEXT: pop {r7, pc}
+;
+; LONGCALL-LABEL: test_memset:
+; LONGCALL: @ %bb.0: @ %entry
+; LONGCALL-NEXT: push {r7, lr}
+; LONGCALL-NEXT: ldr r3, .LCPI4_0
+; LONGCALL-NEXT: blx r3
+; LONGCALL-NEXT: pop {r7, pc}
+; LONGCALL-NEXT: .p2align 2
+; LONGCALL-NEXT: @ %bb.1:
+; LONGCALL-NEXT: .LCPI4_0:
+; LONGCALL-NEXT: .long memset
+;
+; NO-LONGCALL-LABEL: test_memset:
+; NO-LONGCALL: @ %bb.0: @ %entry
+; NO-LONGCALL-NEXT: push {r7, lr}
+; NO-LONGCALL-NEXT: bl memset
+; NO-LONGCALL-NEXT: pop {r7, pc}
+;
+; XO-LONGCALL-LABEL: test_memset:
+; XO-LONGCALL: @ %bb.0: @ %entry
+; XO-LONGCALL-NEXT: .save {r11, lr}
+; XO-LONGCALL-NEXT: push {r11, lr}
+; XO-LONGCALL-NEXT: movw r3, :lower16:memset
+; XO-LONGCALL-NEXT: movt r3, :upper16:memset
+; XO-LONGCALL-NEXT: blx r3
+; XO-LONGCALL-NEXT: pop {r11, pc}
+;
+; PIC-LONGCALL-LABEL: test_memset:
+; PIC-LONGCALL: @ %bb.0: @ %entry
+; PIC-LONGCALL-NEXT: .save {r11, lr}
+; PIC-LONGCALL-NEXT: push {r11, lr}
+; PIC-LONGCALL-NEXT: ldr r3, .LCPI4_0
+; PIC-LONGCALL-NEXT: .LPC4_0:
+; PIC-LONGCALL-NEXT: ldr r3, [pc, r3]
+; PIC-LONGCALL-NEXT: blx r3
+; PIC-LONGCALL-NEXT: pop {r11, pc}
+; PIC-LONGCALL-NEXT: .p2align 2
+; PIC-LONGCALL-NEXT: @ %bb.1:
+; PIC-LONGCALL-NEXT: .LCPI4_0:
+; PIC-LONGCALL-NEXT: .long memset(GOT_PREL)
+entry:
+ call void @llvm.memset.p0.i32(ptr %dst, i8 %val, i32 %len, i1 false)
+ ret void
+}
More information about the llvm-commits
mailing list