[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