[llvm] Arm ropi rwpi dynreloc consts (PR #207931)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 7 01:54:32 PDT 2026
https://github.com/vjonaswolf updated https://github.com/llvm/llvm-project/pull/207931
>From ca320f547b0a29194f66ea6fe641afe82a5d125d Mon Sep 17 00:00:00 2001
From: Jonas Wolf <jonas.wolf at vector.com>
Date: Tue, 7 Jul 2026 09:00:18 +0200
Subject: [PATCH 1/3] [placement] ropi-rwpi: put reloc-bearing constants in
.data.rel.ro
Under ROPI-RWPI, LLVM demotes a `const` whose initializer needs a
relocation (e.g. a Rust `dyn` vtable, or any const containing a pointer
to another symbol) down to plain SectionKind::ReadOnly, i.e. `.rodata`.
The comment justifies this by assuming "the linker will resolve all
addresses" -- true only when the module's final placement is fixed at
static-link time. For a placement-independent XIP image whose flash slot
and/or RAM window are chosen later (bundle or spawn time), those slots
must instead be reloc-fixable, which requires them to live in a writable,
relocation-bearing section (.data.rel.ro).
This removes ROPI_RWPI from the demote-to-ReadOnly disjunction so such
constants fall through to SectionKind::ReadOnlyWithRel. Static, ROPI-only
and RWPI-only, and the !needsDynamicRelocation() fast path are left
untouched.
Carve-out (2nd review round): globals with an EXPLICIT SECTION keep stock
behavior even under ROPI_RWPI. Linker scripts route pinned sections by
name (conventionally into the RO region); diverting the kind would stamp
SHF_WRITE onto the user-named section while the matching isReadOnly
change would address it r9-relative -- reintroducing the
addressing-vs-placement mismatch for #[link_section] users. The
loader-fixup model covers only compiler-placed constants.
NOTE: this is the PLACEMENT half only. It is necessary but NOT sufficient:
the ARM backend addresses a `const` global PC-relative regardless of its
section (see the matching ARMTargetLowering::isReadOnly commit). Apply
both together or you reproduce the rust#95871 class of defect
(addressing region != placement region).
Co-Authored-By: Claude Fable 5 <noreply at anthropic.com>
---
llvm/lib/Target/TargetLoweringObjectFile.cpp | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Target/TargetLoweringObjectFile.cpp b/llvm/lib/Target/TargetLoweringObjectFile.cpp
index 43649a0cd95c7..e0242e06a900e 100644
--- a/llvm/lib/Target/TargetLoweringObjectFile.cpp
+++ b/llvm/lib/Target/TargetLoweringObjectFile.cpp
@@ -352,9 +352,26 @@ SectionKind TargetLoweringObjectFile::getKindForGlobal(const GlobalObject *GO,
// the time the app starts up. However, we can't put this into a
// mergable section, because the linker doesn't take relocations into
// consideration when it tries to merge entries in the section.
+ //
+ // ROPI_RWPI is deliberately excluded below: under a two-base
+ // (text via PC, data via r9) model used for placement-independent XIP
+ // images, a reloc-bearing constant (e.g. a dyn vtable) must be fixable
+ // by a loader after placement is known, so it has to land in a writable
+ // relocation-bearing section (.data.rel.ro), addressed via r9 -- see the
+ // matching ARMTargetLowering::isReadOnly change.
+ //
+ // Exception: a global with an explicit section keeps stock behavior
+ // even under ROPI_RWPI. The user pinned the placement, and linker
+ // scripts route pinned sections by NAME; diverting the kind to
+ // ReadOnlyWithRel would stamp SHF_WRITE onto the named section and
+ // (via the matching isReadOnly change) address it r9-relative while
+ // the bytes sit wherever the script put the name -- reintroducing the
+ // addressing-vs-placement mismatch. The loader-fixup model only covers
+ // compiler-placed constants.
Reloc::Model ReloModel = TM.getRelocationModel();
if (ReloModel == Reloc::Static || ReloModel == Reloc::ROPI ||
- ReloModel == Reloc::RWPI || ReloModel == Reloc::ROPI_RWPI ||
+ ReloModel == Reloc::RWPI ||
+ (ReloModel == Reloc::ROPI_RWPI && GVar->hasSection()) ||
!C->needsDynamicRelocation())
return SectionKind::getReadOnly();
>From 28d159c0548ebc185fb7b92d0339790e2b9d4654 Mon Sep 17 00:00:00 2001
From: Jonas Wolf <jonas.wolf at vector.com>
Date: Tue, 7 Jul 2026 09:00:19 +0200
Subject: [PATCH 2/3] [addressing] ropi-rwpi: address dyn-reloc-bearing
constants via r9
This is the load-bearing half. ARMTargetLowering::isReadOnly decides,
for a global, whether LowerGlobalAddressELF materializes its address
PC-relative (ROPI arm) or SB-relative through r9 (RWPI arm). Stock
returns `V->isConstant()` for a GlobalVariable, IGNORING whether the
initializer carries a dynamic relocation. So a `const` vtable is
IsRO==true and is always addressed PC-relative, even after the placement
commit moves its bytes into a writable .data.rel.ro section. That is the
rust#95871 class of defect (addressing region != placement region).
Fix: under the combined ROPI_RWPI model only, a constant GlobalVariable
without an explicit section whose initializer needsDynamicRelocation()
is treated as NOT read-only, so its address is formed `r9 + MO_SBREL(GV)`.
Combined with the placement commit (bytes in .data.rel.ro, copied
LMA->VMA into the r9-addressed RAM window and reloc-fixed by the loader
at spawn), placement and addressing agree.
Gate rationale (fixed after critical review; earlier revision used
isRWPI() + needsRelocation() and had two confirmed bugs):
- Model: isROPI() && isRWPI() == exactly Reloc::ROPI_RWPI. Plain RWPI
keeps a link-time-fixed RO region: stock .rodata placement + ABS
addressing are already correct there, and SB-addressing a .rodata
datum computes S+Delta_rw for a datum that did not move -> garbage.
- Predicate: needsDynamicRelocation(), the SAME predicate the
placement decision (getKindForGlobal) and promoteToConstantPool
use. The wider needsRelocation() also fires on link-time-final
relative pointers (ptrtoint(A)-ptrtoint(B), dso_local), which
placement keeps in .rodata; r9-addressing those recreates the
mismatch (empirically confirmed before the fix).
- Explicit-section carve-out (2nd review round): !hasSection(),
mirroring the placement carve-out -- a user-pinned section keeps
stock placement, so it must keep stock PC-relative addressing.
GlobalISel parity is automatic: ARMInstructionSelector::selectGlobal
consults this same hook, so it inherits both the diversion and the
carve-out (regression-tested with a -global-isel RUN line).
Co-Authored-By: Claude Fable 5 <noreply at anthropic.com>
---
llvm/lib/Target/ARM/ARMISelLowering.cpp | 34 +++++++++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp
index 577e97e736c25..36fc5d629e0e0 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -3620,8 +3620,38 @@ bool ARMTargetLowering::isReadOnly(const GlobalValue *GV) const {
if (const GlobalAlias *GA = dyn_cast<GlobalAlias>(GV))
if (!(GV = GA->getAliaseeObject()))
return false;
- if (const auto *V = dyn_cast<GlobalVariable>(GV))
- return V->isConstant();
+ if (const auto *V = dyn_cast<GlobalVariable>(GV)) {
+ if (!V->isConstant())
+ return false;
+ // Under ROPI-RWPI (both flags set -- exactly the combined model), a
+ // `const` whose initializer needs a dynamic relocation (e.g. a dyn
+ // vtable or any const holding a pointer to another symbol) is placed in
+ // a writable, relocation-bearing section (.data.rel.ro) by
+ // getKindForGlobal so a loader can fix it up after placement is known.
+ // Such a datum lives in the r9-addressed RW image, so it must NOT be
+ // treated as read-only here or its address would be formed PC-relative
+ // (pointing at where text expects rodata, not where the datum actually
+ // is). Report it as non-read-only so LowerGlobalAddressELF takes the
+ // SB-relative (r9) arm, matching the section placement.
+ //
+ // The gate mirrors the placement decision exactly: same model (only
+ // ROPI_RWPI -- under plain RWPI the RO region is link-time fixed, stock
+ // placement AND addressing are already correct, and SB-addressing a
+ // .rodata datum would break), same predicate
+ // (needsDynamicRelocation(), matching getKindForGlobal and
+ // promoteToConstantPool; the wider needsRelocation() would also fire on
+ // link-time-final relative pointers, which placement keeps in .rodata --
+ // addressing those via r9 would recreate the very
+ // addressing-vs-placement mismatch this hook exists to prevent), and
+ // same explicit-section carve-out (a user-pinned section keeps stock
+ // placement in getKindForGlobal, so it must keep stock PC-relative
+ // addressing here; linker scripts route pinned sections by name, and
+ // those names conventionally live in the RO region).
+ if (Subtarget->isROPI() && Subtarget->isRWPI() && !V->hasSection() &&
+ V->hasInitializer() && V->getInitializer()->needsDynamicRelocation())
+ return false;
+ return true;
+ }
return isa<Function>(GV);
}
>From 3e290ef3858695b6e2d3d7291b552b73b664ea44 Mon Sep 17 00:00:00 2001
From: Jonas Wolf <jonas.wolf at vector.com>
Date: Tue, 7 Jul 2026 09:00:20 +0200
Subject: [PATCH 3/3] [test] ARM: placement+addressing test for reloc-bearing
constants under ROPI/RWPI family
One focused test, ropi-rwpi-reloc-const.ll, locking down the lockstep
property across static/ropi/rwpi/ropi-rwpi, NINE RUN configs
(armv7a x 4 models, thumbv7m rwpi/ropi-rwpi, plus ropi-rwpi on
armv7a+no-movt, thumbv6m, and armv7a -global-isel -global-isel-abort=1)
over SIX shapes:
- @relconst (direct pointer, needsDynamicRelocation): .data.rel.ro +
r9/sbrel ONLY under ropi-rwpi -- in movw/movt, literal-pool
`.long sym(sbrel)` (no-movt/Thumb1), and GlobalISel forms; stock
(.rodata + ABS/PC) elsewhere.
- @reltab (relative pointer, link-time-final): .rodata + RO addressing
in EVERY model -- regression tests for the two bugs found in review
(plain-rwpi overreach; needsRelocation vs needsDynamicRelocation).
- @ext / @extmut (the DECLARATION CONTRACT, both arms, incl. no-movt/
Thumb1 literal-pool and GlobalISel rows): `external constant` ->
PC-relative (the arm rustc's declaration marking relies on for
pure-data statics; wrong iff the definition is actually reloc-bearing
-- the documented cross-TU limitation); `external global` -> SB via
r9 (mutable and reloc-bearing statics).
- @pinned (explicit section): stock placement (named section, "a"
flags, no SHF_WRITE stamping) AND stock PC-relative addressing under
ropi-rwpi -- the hasSection() carve-out regression test, on both
SelectionDAG and GlobalISel.
- @mix (mixed direct+relative initializer, relative target in TEXT):
wholesale .data.rel.ro + r9, with the REL32 loader-contract
requirement documented in-test.
All nine RUN configs verified green via llc+FileCheck and the full
CodeGen/ARM lit run (1556 passed / 0 failed) against the patched llc.
Co-Authored-By: Claude Fable 5 <noreply at anthropic.com>
---
.../test/CodeGen/ARM/ropi-rwpi-reloc-const.ll | 343 ++++++++++++++++++
1 file changed, 343 insertions(+)
create mode 100644 llvm/test/CodeGen/ARM/ropi-rwpi-reloc-const.ll
diff --git a/llvm/test/CodeGen/ARM/ropi-rwpi-reloc-const.ll b/llvm/test/CodeGen/ARM/ropi-rwpi-reloc-const.ll
new file mode 100644
index 0000000000000..d6ae07ca17ed6
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/ropi-rwpi-reloc-const.ll
@@ -0,0 +1,343 @@
+; Placement + addressing for `constant` globals under the ROPI/RWPI family,
+; distinguishing six shapes:
+;
+; * @relconst -- a const holding a DIRECT POINTER to another symbol (the
+; reduced shape of a Rust `dyn` vtable). needsDynamicRelocation() == true.
+; Under ropi-rwpi ONLY, it must (a) be placed in the writable,
+; relocation-bearing .data.rel.ro (so a loader can fix the slot after
+; placement is known) and (b) be addressed SB-relative via r9 -- the two
+; decisions must agree. Under static/ropi/rwpi it keeps stock behavior.
+;
+; * @reltab -- a const holding a RELATIVE POINTER (ptrtoint(A)-ptrtoint(B),
+; both dso_local). needsRelocation() == true but needsDynamicRelocation()
+; == false: the entry is link-time-final, so it must stay in .rodata and
+; be addressed like read-only data (PC-relative under ropi/ropi-rwpi,
+; absolute otherwise) in EVERY model. Addressing this via r9 while it sits
+; in .rodata would compute S+Delta(rw) for a datum that moved with the RO
+; region -- the rust#95871 class of defect.
+;
+; * @ext / @extmut -- the DECLARATION CONTRACT. A declaration exposes no
+; initializer, so the region classification must ride on the `constant`
+; flag, which the frontend asserts:
+; - `external constant` (@ext): the frontend promises the definition is
+; RO-region data -> PC-relative. rustc emits this under ropi-rwpi for
+; cross-CGU/cross-crate declarations of pure-data immutable statics
+; (whose definitions stay in .rodata).
+; - `external global` (@extmut): definition lives in the RW image
+; (mutable data, or a reloc-bearing const diverted to .data.rel.ro)
+; -> SB-relative via r9.
+; A `constant` declaration whose definition is actually reloc-bearing
+; (e.g. C `extern const`, or ThinLTO's EliminateAvailableExternally
+; stripping an initializer while keeping the constant bit) is
+; misclassified by this contract -- that remains the documented cross-TU
+; limitation: the truth is not computable from such a declaration.
+;
+; * @pinned -- a reloc-bearing const with an EXPLICIT SECTION. The user owns
+; placement (linker scripts route pinned sections by NAME, conventionally
+; into the RO region), so BOTH halves keep stock behavior under ropi-rwpi:
+; the named section keeps "a" flags (no SHF_WRITE stamping) and the
+; address stays PC-relative, NOT r9. Diverting either half alone would
+; reintroduce the addressing-vs-placement mismatch.
+;
+; * @mix -- a const holding BOTH a direct pointer and a relative pointer
+; whose target is a FUNCTION (text). One dynamic slot poisons the whole
+; object (max-over-operands), so the entire datum goes to .data.rel.ro +
+; r9 under ropi-rwpi. NOTE the loader contract implication: the
+; link-time-final `take_addr_relconst-mix` word then lives in the RW-slid
+; region while its target stays in text, so a loader for independently
+; sliding regions must rewrite such surviving REL32-class entries by
+; (Delta(target region) - Delta_rw) -- here (Delta_text - Delta_rw) --
+; or reject mixed objects. (A relative entry whose target is in the SAME
+; region as the slot, e.g. data-to-data, survives sliding unchanged.)
+;
+; RUN: llc -relocation-model=static -mtriple=armv7a--none-eabi < %s | FileCheck %s --check-prefixes=CHECK,ARM_ABS,RODATA
+; RUN: llc -relocation-model=ropi -mtriple=armv7a--none-eabi < %s | FileCheck %s --check-prefixes=CHECK,ARM_PC,RODATA
+; RUN: llc -relocation-model=rwpi -mtriple=armv7a--none-eabi < %s | FileCheck %s --check-prefixes=CHECK,ARM_ABS,RODATA
+; RUN: llc -relocation-model=ropi-rwpi -mtriple=armv7a--none-eabi < %s | FileCheck %s --check-prefixes=CHECK,ARM_RR,RELRO
+; RUN: llc -relocation-model=rwpi -mtriple=thumbv7m--none-eabi < %s | FileCheck %s --check-prefixes=CHECK,T2_ABS,RODATA
+; RUN: llc -relocation-model=ropi-rwpi -mtriple=thumbv7m--none-eabi < %s | FileCheck %s --check-prefixes=CHECK,T2_RR,RELRO
+;
+; No-movt targets must take the literal-pool SBREL path for the diverted
+; class; Thumb1 (v6m) additionally has no register-offset ADD with r9.
+; RUN: llc -relocation-model=ropi-rwpi -mtriple=armv7a--none-eabi -mattr=+no-movt < %s | FileCheck %s --check-prefixes=NOMOVT_RR,RELRO
+; RUN: llc -relocation-model=ropi-rwpi -mtriple=thumbv6m--none-eabi < %s | FileCheck %s --check-prefixes=T1_RR,RELRO
+;
+; GlobalISel resolves RO-ness through the same ARMTargetLowering::isReadOnly
+; hook, so parity (including the explicit-section carve-out) is automatic;
+; abort=1 also asserts nothing in this file falls back to SelectionDAG.
+; RUN: llc -relocation-model=ropi-rwpi -mtriple=armv7a--none-eabi -global-isel -global-isel-abort=1 < %s | FileCheck %s --check-prefixes=GISEL_RR,RELRO
+
+target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
+
+ at target = external global i32, align 4
+ at relconst = constant ptr @target, align 4
+
+ at f = dso_local global i32 1, align 4
+ at reltab = dso_local constant i32 sub (i32 ptrtoint (ptr @f to i32), i32 ptrtoint (ptr @reltab to i32)), align 4
+
+ at ext = external constant ptr
+ at extmut = external global i32
+
+ at pinned = dso_local constant ptr @target, section ".mysec", align 4
+
+ at mix = dso_local constant { ptr, i32 } { ptr @target, i32 sub (i32 ptrtoint (ptr @take_addr_relconst to i32), i32 ptrtoint (ptr @mix to i32)) }, align 4
+
+define ptr @take_addr_relconst() {
+entry:
+ ret ptr @relconst
+; CHECK-LABEL: take_addr_relconst:
+
+; ARM_ABS: movw r0, :lower16:relconst{{$}}
+; ARM_ABS: movt r0, :upper16:relconst{{$}}
+
+; ARM_PC: movw r0, :lower16:(relconst-([[LPC:.LPC[0-9]+_[0-9]+]]+8))
+; ARM_PC: movt r0, :upper16:(relconst-([[LPC]]+8))
+; ARM_PC: [[LPC]]:
+; ARM_PC-NEXT: add r0, pc, r0
+
+; ARM_RR: movw r0, :lower16:relconst(sbrel)
+; ARM_RR: movt r0, :upper16:relconst(sbrel)
+; ARM_RR: add r0, r9, r0
+
+; T2_ABS: movw r0, :lower16:relconst{{$}}
+; T2_ABS: movt r0, :upper16:relconst{{$}}
+
+; T2_RR: movw r0, :lower16:relconst(sbrel)
+; T2_RR: movt r0, :upper16:relconst(sbrel)
+; T2_RR: add r0, r9
+
+; NOMOVT_RR-LABEL: take_addr_relconst:
+; NOMOVT_RR: ldr r0, [[CPI:.LCPI[0-9]+_[0-9]+]]
+; NOMOVT_RR-NEXT: add r0, r9, r0
+; NOMOVT_RR: [[CPI]]:
+; NOMOVT_RR-NEXT: .long relconst(sbrel)
+
+; T1_RR-LABEL: take_addr_relconst:
+; T1_RR: ldr r0, [[CPI:.LCPI[0-9]+_[0-9]+]]
+; T1_RR-NEXT: mov r1, r9
+; T1_RR-NEXT: adds r0, r1, r0
+; T1_RR: [[CPI]]:
+; T1_RR-NEXT: .long relconst(sbrel)
+
+; GISEL_RR-LABEL: take_addr_relconst:
+; GISEL_RR: movw r0, :lower16:relconst(sbrel)
+; GISEL_RR: movt r0, :upper16:relconst(sbrel)
+; GISEL_RR: add r0, r9, r0
+
+; CHECK: bx lr
+}
+
+define ptr @take_addr_reltab() {
+entry:
+ ret ptr @reltab
+; CHECK-LABEL: take_addr_reltab:
+
+; ARM_ABS: movw r0, :lower16:{{(\.Lreltab\$local|reltab)$}}
+; ARM_ABS: movt r0, :upper16:{{(\.Lreltab\$local|reltab)$}}
+
+; ARM_PC: movw r0, :lower16:(.Lreltab$local-([[LPC1:.LPC[0-9]+_[0-9]+]]+8))
+; ARM_PC: movt r0, :upper16:(.Lreltab$local-([[LPC1]]+8))
+; ARM_PC: [[LPC1]]:
+; ARM_PC-NEXT: add r0, pc, r0
+
+; The relative-pointer const stays RO under ropi-rwpi: PC-relative, NOT r9.
+; ARM_RR: movw r0, :lower16:(.Lreltab$local-([[LPC1:.LPC[0-9]+_[0-9]+]]+8))
+; ARM_RR: movt r0, :upper16:(.Lreltab$local-([[LPC1]]+8))
+; ARM_RR: [[LPC1]]:
+; ARM_RR-NEXT: add r0, pc, r0
+
+; T2_ABS: movw r0, :lower16:.Lreltab$local{{$}}
+; T2_ABS: movt r0, :upper16:.Lreltab$local{{$}}
+
+; T2_RR: movw r0, :lower16:(.Lreltab$local-([[LPC1:.LPC[0-9]+_[0-9]+]]+4))
+; T2_RR: movt r0, :upper16:(.Lreltab$local-([[LPC1]]+4))
+; T2_RR: [[LPC1]]:
+; T2_RR-NEXT: add r0, pc
+
+; CHECK: bx lr
+}
+
+define ptr @take_addr_ext() {
+entry:
+ ret ptr @ext
+; CHECK-LABEL: take_addr_ext:
+
+; ARM_ABS: movw r0, :lower16:ext{{$}}
+; ARM_ABS: movt r0, :upper16:ext{{$}}
+
+; ARM_PC: movw r0, :lower16:(ext-([[LPCE:.LPC[0-9]+_[0-9]+]]+8))
+; ARM_PC: movt r0, :upper16:(ext-([[LPCE]]+8))
+; ARM_PC: [[LPCE]]:
+; ARM_PC-NEXT: add r0, pc, r0
+
+; Declaration contract, RO arm: `external constant` -> PC-relative. This is
+; what rustc's decl-marking relies on for pure-data statics; it is WRONG for
+; a reloc-bearing definition (the documented cross-TU limitation).
+; ARM_RR: movw r0, :lower16:(ext-([[LPCE:.LPC[0-9]+_[0-9]+]]+8))
+; ARM_RR: movt r0, :upper16:(ext-([[LPCE]]+8))
+; ARM_RR: [[LPCE]]:
+; ARM_RR-NEXT: add r0, pc, r0
+
+; T2_ABS: movw r0, :lower16:ext{{$}}
+; T2_ABS: movt r0, :upper16:ext{{$}}
+
+; T2_RR: movw r0, :lower16:(ext-([[LPCE:.LPC[0-9]+_[0-9]+]]+4))
+; T2_RR: movt r0, :upper16:(ext-([[LPCE]]+4))
+; T2_RR: [[LPCE]]:
+; T2_RR-NEXT: add r0, pc
+
+; NOMOVT_RR-LABEL: take_addr_ext:
+; NOMOVT_RR: ldr r0, [[CPIE:.LCPI[0-9]+_[0-9]+]]
+; NOMOVT_RR: [[LPCE:.LPC[0-9]+_[0-9]+]]:
+; NOMOVT_RR-NEXT: add r0, pc, r0
+; NOMOVT_RR: [[CPIE]]:
+; NOMOVT_RR-NEXT: .long ext-([[LPCE]]+8)
+
+; T1_RR-LABEL: take_addr_ext:
+; T1_RR: ldr r0, [[CPIE:.LCPI[0-9]+_[0-9]+]]
+; T1_RR: [[LPCE:.LPC[0-9]+_[0-9]+]]:
+; T1_RR-NEXT: add r0, pc
+; T1_RR: [[CPIE]]:
+; T1_RR-NEXT: .long ext-([[LPCE]]+4)
+
+; GISEL_RR-LABEL: take_addr_ext:
+; GISEL_RR: movw r0, :lower16:(ext-([[LPCGE:.LPC[0-9]+_[0-9]+]]+8))
+; GISEL_RR: movt r0, :upper16:(ext-([[LPCGE]]+8))
+; GISEL_RR: [[LPCGE]]:
+; GISEL_RR-NEXT: add r0, pc, r0
+
+; CHECK: bx lr
+}
+
+define ptr @take_addr_extmut() {
+entry:
+ ret ptr @extmut
+; CHECK-LABEL: take_addr_extmut:
+
+; Declaration contract, RW arm: `external global` (no constant flag) -> SB
+; via r9. rustc emits this shape for declarations of mutable statics AND of
+; reloc-bearing immutable statics (whose definitions are diverted to
+; .data.rel.ro) -- both live in the r9-addressed RW image.
+; ARM_RR: movw r0, :lower16:extmut(sbrel)
+; ARM_RR: movt r0, :upper16:extmut(sbrel)
+; ARM_RR: add r0, r9, r0
+
+; T2_RR: movw r0, :lower16:extmut(sbrel)
+; T2_RR: movt r0, :upper16:extmut(sbrel)
+; T2_RR: add r0, r9
+
+; GISEL_RR-LABEL: take_addr_extmut:
+; GISEL_RR: movw r0, :lower16:extmut(sbrel)
+; GISEL_RR: movt r0, :upper16:extmut(sbrel)
+; GISEL_RR: add r0, r9, r0
+
+; NOMOVT_RR-LABEL: take_addr_extmut:
+; NOMOVT_RR: ldr r0, [[CPIEM:.LCPI[0-9]+_[0-9]+]]
+; NOMOVT_RR-NEXT: add r0, r9, r0
+; NOMOVT_RR: [[CPIEM]]:
+; NOMOVT_RR-NEXT: .long extmut(sbrel)
+
+; T1_RR-LABEL: take_addr_extmut:
+; T1_RR: ldr r0, [[CPIEM:.LCPI[0-9]+_[0-9]+]]
+; T1_RR-NEXT: mov r1, r9
+; T1_RR-NEXT: adds r0, r1, r0
+; T1_RR: [[CPIEM]]:
+; T1_RR-NEXT: .long extmut(sbrel)
+
+; CHECK: bx lr
+}
+
+define ptr @take_addr_pinned() {
+entry:
+ ret ptr @pinned
+; CHECK-LABEL: take_addr_pinned:
+
+; ARM_ABS: movw r0, :lower16:{{(\.Lpinned\$local|pinned)$}}
+; ARM_ABS: movt r0, :upper16:{{(\.Lpinned\$local|pinned)$}}
+
+; ARM_PC: movw r0, :lower16:(.Lpinned$local-([[LPCP:.LPC[0-9]+_[0-9]+]]+8))
+; ARM_PC: movt r0, :upper16:(.Lpinned$local-([[LPCP]]+8))
+; ARM_PC: [[LPCP]]:
+; ARM_PC-NEXT: add r0, pc, r0
+
+; Explicit-section carve-out: the user pinned the section, so BOTH halves
+; keep stock behavior under ropi-rwpi -- PC-relative address (NOT r9), and
+; the named section below keeps "a" flags (no SHF_WRITE stamping).
+; ARM_RR: movw r0, :lower16:(.Lpinned$local-([[LPCP:.LPC[0-9]+_[0-9]+]]+8))
+; ARM_RR: movt r0, :upper16:(.Lpinned$local-([[LPCP]]+8))
+; ARM_RR: [[LPCP]]:
+; ARM_RR-NEXT: add r0, pc, r0
+
+; T2_ABS: movw r0, :lower16:.Lpinned$local{{$}}
+; T2_ABS: movt r0, :upper16:.Lpinned$local{{$}}
+
+; T2_RR: movw r0, :lower16:(.Lpinned$local-([[LPCP:.LPC[0-9]+_[0-9]+]]+4))
+; T2_RR: movt r0, :upper16:(.Lpinned$local-([[LPCP]]+4))
+; T2_RR: [[LPCP]]:
+; T2_RR-NEXT: add r0, pc
+
+; GISEL_RR-LABEL: take_addr_pinned:
+; GISEL_RR: movw r0, :lower16:(.Lpinned$local-([[LPCGP:.LPC[0-9]+_[0-9]+]]+8))
+; GISEL_RR: movt r0, :upper16:(.Lpinned$local-([[LPCGP]]+8))
+; GISEL_RR: [[LPCGP]]:
+; GISEL_RR-NEXT: add r0, pc, r0
+
+; CHECK: bx lr
+}
+
+define ptr @take_addr_mix() {
+entry:
+ ret ptr @mix
+; CHECK-LABEL: take_addr_mix:
+
+; ARM_ABS: movw r0, :lower16:{{(\.Lmix\$local|mix)$}}
+; ARM_ABS: movt r0, :upper16:{{(\.Lmix\$local|mix)$}}
+
+; ARM_PC: movw r0, :lower16:(.Lmix$local-([[LPCM:.LPC[0-9]+_[0-9]+]]+8))
+; ARM_PC: movt r0, :upper16:(.Lmix$local-([[LPCM]]+8))
+; ARM_PC: [[LPCM]]:
+; ARM_PC-NEXT: add r0, pc, r0
+
+; One dynamic slot poisons the whole object: .data.rel.ro + r9.
+; ARM_RR: movw r0, :lower16:.Lmix$local(sbrel)
+; ARM_RR: movt r0, :upper16:.Lmix$local(sbrel)
+; ARM_RR: add r0, r9, r0
+
+; T2_ABS: movw r0, :lower16:.Lmix$local{{$}}
+; T2_ABS: movt r0, :upper16:.Lmix$local{{$}}
+
+; T2_RR: movw r0, :lower16:.Lmix$local(sbrel)
+; T2_RR: movt r0, :upper16:.Lmix$local(sbrel)
+; T2_RR: add r0, r9
+
+; CHECK: bx lr
+}
+
+; Section placement. Emission order in all configs:
+; relconst, f (.data), reltab, pinned (.mysec), mix.
+
+; RODATA: .section .rodata,"a",%progbits
+; RODATA: relconst:
+; RODATA-NEXT: .long target
+; RODATA: reltab:
+; RODATA: .long f-reltab
+; RODATA: .section .mysec,"a",%progbits
+; RODATA: pinned:
+; RODATA: .long target
+; RODATA: mix:
+; RODATA: .long target
+; RODATA-NEXT: .long take_addr_relconst-mix
+
+; RELRO: .section .data.rel.ro,"aw",%progbits
+; RELRO: relconst:
+; RELRO-NEXT: .long target
+; RELRO: .section .rodata,"a",%progbits
+; RELRO: reltab:
+; RELRO: .long f-reltab
+; RELRO: .section .mysec,"a",%progbits
+; RELRO: pinned:
+; RELRO: .long target
+; RELRO: .section .data.rel.ro,"aw",%progbits
+; RELRO: mix:
+; RELRO: .long target
+; RELRO-NEXT: .long take_addr_relconst-mix
More information about the llvm-commits
mailing list