[lld] [lld][AArch64] Support R_AARCH64_TLSLE_LDST*_TPREL_LO12 (PR #227629)

dong jianqiang via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 1 03:12:27 PDT 2026


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

>From dc862b7dcf1267203cdb449e7d7b238bdf6b7578 Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Wed, 30 Sep 2026 09:55:43 +0000
Subject: [PATCH 1/2] [lld][AArch64] Support R_AARCH64_TLSLE_LDST*_TPREL_LO12

Add the non-NC TLS LE load/store relocations emitted for 'ldr/str xN,
[.., :tprel_lo12:sym]'. They encode bits 11:scale of the offset like
the _NC variants and additionally require the full offset to fit in an
unsigned 12-bit immediate, matching GNU ld. Also error for TLS LE
relocations against non-STT_TLS symbols, which previously resolved
silently with a wrong offset.
---
 lld/ELF/Arch/AArch64.cpp           | 26 +++++++++++
 lld/test/ELF/aarch64-tls-le-ldst.s | 73 ++++++++++++++++++++++++++++++
 lld/test/ELF/aarch64-tls-le.s      | 38 +++++++++++++++-
 3 files changed, 136 insertions(+), 1 deletion(-)
 create mode 100644 lld/test/ELF/aarch64-tls-le-ldst.s

diff --git a/lld/ELF/Arch/AArch64.cpp b/lld/ELF/Arch/AArch64.cpp
index 1d24084d82762..6f551b7fe02af 100644
--- a/lld/ELF/Arch/AArch64.cpp
+++ b/lld/ELF/Arch/AArch64.cpp
@@ -337,16 +337,27 @@ void AArch64::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
     case R_AARCH64_TLSLE_ADD_TPREL_HI12:
     case R_AARCH64_TLSLE_ADD_TPREL_LO12:
     case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
+    case R_AARCH64_TLSLE_LDST8_TPREL_LO12:
     case R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC:
+    case R_AARCH64_TLSLE_LDST16_TPREL_LO12:
     case R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC:
+    case R_AARCH64_TLSLE_LDST32_TPREL_LO12:
     case R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC:
+    case R_AARCH64_TLSLE_LDST64_TPREL_LO12:
     case R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC:
+    case R_AARCH64_TLSLE_LDST128_TPREL_LO12:
     case R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC:
     case R_AARCH64_TLSLE_MOVW_TPREL_G0:
     case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
     case R_AARCH64_TLSLE_MOVW_TPREL_G1:
     case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
     case R_AARCH64_TLSLE_MOVW_TPREL_G2:
+      if (LLVM_UNLIKELY(!sym.isTls())) {
+        Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
+                 << "relocation " << type << " against " << &sym
+                 << " cannot be used with a non-STT_TLS symbol";
+        continue;
+      }
       if (rs.checkTlsLe(offset, sym, type))
         continue;
       expr = R_TPREL;
@@ -730,20 +741,32 @@ void AArch64::relocate(uint8_t *loc, const Relocation &rel,
     checkInt(ctx, loc, val, 21, rel);
     writeMaskedBits32le(loc, (val & 0x1FFFFC) << 3, 0x1FFFFC << 3);
     break;
+  case R_AARCH64_TLSLE_LDST8_TPREL_LO12:
+    checkUInt(ctx, loc, val, 12, rel);
+    [[fallthrough]];
   case R_AARCH64_LDST8_ABS_LO12_NC:
   case R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC:
     write32Imm12(loc, getBits(val, 0, 11));
     break;
+  case R_AARCH64_TLSLE_LDST16_TPREL_LO12:
+    checkUInt(ctx, loc, val, 12, rel);
+    [[fallthrough]];
   case R_AARCH64_LDST16_ABS_LO12_NC:
   case R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC:
     checkAlignment(ctx, loc, val, 2, rel);
     write32Imm12(loc, getBits(val, 1, 11));
     break;
+  case R_AARCH64_TLSLE_LDST32_TPREL_LO12:
+    checkUInt(ctx, loc, val, 12, rel);
+    [[fallthrough]];
   case R_AARCH64_LDST32_ABS_LO12_NC:
   case R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC:
     checkAlignment(ctx, loc, val, 4, rel);
     write32Imm12(loc, getBits(val, 2, 11));
     break;
+  case R_AARCH64_TLSLE_LDST64_TPREL_LO12:
+    checkUInt(ctx, loc, val, 12, rel);
+    [[fallthrough]];
   case R_AARCH64_LDST64_ABS_LO12_NC:
   case R_AARCH64_LD64_GOT_LO12_NC:
   case R_AARCH64_AUTH_LD64_GOT_LO12_NC:
@@ -754,6 +777,9 @@ void AArch64::relocate(uint8_t *loc, const Relocation &rel,
     checkAlignment(ctx, loc, val, 8, rel);
     write32Imm12(loc, getBits(val, 3, 11));
     break;
+  case R_AARCH64_TLSLE_LDST128_TPREL_LO12:
+    checkUInt(ctx, loc, val, 12, rel);
+    [[fallthrough]];
   case R_AARCH64_LDST128_ABS_LO12_NC:
   case R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC:
     checkAlignment(ctx, loc, val, 16, rel);
diff --git a/lld/test/ELF/aarch64-tls-le-ldst.s b/lld/test/ELF/aarch64-tls-le-ldst.s
new file mode 100644
index 0000000000000..40f33257f9180
--- /dev/null
+++ b/lld/test/ELF/aarch64-tls-le-ldst.s
@@ -0,0 +1,73 @@
+# REQUIRES: aarch64
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t.o
+# RUN: ld.lld %t.o -o %t
+# RUN: llvm-objdump --no-print-imm-hex -d --no-show-raw-insn %t | FileCheck %s
+
+## Like R_AARCH64_TLSLE_ADD_TPREL_LO12, the non-NC LDST variants require the
+## full offset from the thread pointer to fit into an unsigned 12-bit
+## immediate; the imm12 field of the scaled load/store instruction holds bits
+## 11:log2(access-size) of the offset.
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -defsym=OOR=1 -o %t.bad.o
+# RUN: not ld.lld %t.bad.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=OOR --implicit-check-not=error:
+
+# OOR: error: {{.*}}:(.text+0x4): relocation R_AARCH64_TLSLE_LDST8_TPREL_LO12 out of range: 4096 is not in [0, 4095]; references 'v1'
+# OOR: error: {{.*}}:(.text+0x8): relocation R_AARCH64_TLSLE_LDST16_TPREL_LO12 out of range: 4096 is not in [0, 4095]; references 'v1'
+# OOR: error: {{.*}}:(.text+0xc): relocation R_AARCH64_TLSLE_LDST32_TPREL_LO12 out of range: 4096 is not in [0, 4095]; references 'v1'
+# OOR: error: {{.*}}:(.text+0x10): relocation R_AARCH64_TLSLE_LDST64_TPREL_LO12 out of range: 4096 is not in [0, 4095]; references 'v1'
+# OOR: error: {{.*}}:(.text+0x14): relocation R_AARCH64_TLSLE_LDST128_TPREL_LO12 out of range: 4096 is not in [0, 4095]; references 'v1'
+
+## An in-range but misaligned offset must be rejected by the alignment check
+## the non-NC cases share with their _NC siblings.
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -defsym=MISALIGNED=1 -o %t.mis.o
+# RUN: not ld.lld %t.mis.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=MISALIGN --implicit-check-not=error:
+
+# MISALIGN: error: {{.*}}:(.text+0x18): improper alignment for relocation R_AARCH64_TLSLE_LDST16_TPREL_LO12: 0x11 is not aligned to 2 bytes
+
+.globl _start
+_start:
+ mrs x0, TPIDR_EL0
+ ldrb w0, [x0, :tprel_lo12:v1]
+ ldrh w1, [x1, :tprel_lo12:v1]
+ ldr  w2, [x2, :tprel_lo12:v1]
+ ldr  x3, [x3, :tprel_lo12:v1]
+ ldr  q4, [x4, :tprel_lo12:v1]
+.ifdef MISALIGNED
+ ldrh w7, [x7, :tprel_lo12:v2]
+.endif
+
+#CHECK: Disassembly of section .text:
+#CHECK:      <_start>:
+#CHECK-NEXT:   mrs     x0, TPIDR_EL0
+#CHECK-NEXT:   ldrb    w0, [x0, #4080]
+#CHECK-NEXT:   ldrh    w1, [x1, #4080]
+#CHECK-NEXT:   ldr     w2, [x2, #4080]
+#CHECK-NEXT:   ldr     x3, [x3, #4080]
+#CHECK-NEXT:   ldr     q4, [x4, #4080]
+
+.section        .tbss,"awT", at nobits
+.p2align 4
+## v2 sits at an odd offset (0x11) from the thread pointer for the
+## misaligned-offset check above.
+.byte 0
+.type   v2, at object
+.globl  v2
+v2:
+.byte   0
+.size   v2, 1
+.p2align 4
+
+## v1 sits at 0xff0 (4080) from the thread pointer: bit 11 of the offset is
+## set, so a wrong shift or mask in the scaled immediate shows up in the
+## disassembly above. In the OOR variant it sits at 0x1000 (4096), one past
+## the 12-bit limit.
+.ifdef OOR
+.space (0xff0 - 0x10)
+.else
+.space (0xfe0 - 0x10)
+.endif
+
+.type   v1, at object
+.globl  v1
+v1:
+.space  16
+.size   v1, 16
diff --git a/lld/test/ELF/aarch64-tls-le.s b/lld/test/ELF/aarch64-tls-le.s
index 91c52525b9422..e5a841d2f7763 100644
--- a/lld/test/ELF/aarch64-tls-le.s
+++ b/lld/test/ELF/aarch64-tls-le.s
@@ -13,6 +13,14 @@
 ## Reject local-exec TLS relocations for -shared.
 # RUN: not ld.lld -shared %t.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR --implicit-check-not=error:
 
+## Reject local-exec TLS relocations against non-STT_TLS symbols. llvm-mc
+## marks a symbol referenced through ':tprel_lo12:' as STT_TLS, so use .reloc
+## to produce a reference that is genuinely non-STT_TLS.
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -defsym=NONTLS=1 -o %t.nontls.o
+# RUN: not ld.lld %t.nontls.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=NONTLS --implicit-check-not=error:
+
+# NONTLS: error: {{.*}}:(.text+0x40): relocation R_AARCH64_TLSLE_LDST8_TPREL_LO12 against nonTls cannot be used with a non-STT_TLS symbol
+
 # ERR: error: relocation R_AARCH64_TLSLE_ADD_TPREL_HI12 against v1 cannot be used with -shared
 # ERR: error: relocation R_AARCH64_TLSLE_ADD_TPREL_LO12_NC against v1 cannot be used with -shared
 # ERR: error: relocation R_AARCH64_TLSLE_ADD_TPREL_LO12 against v3 cannot be used with -shared
@@ -21,6 +29,11 @@
 # ERR: error: relocation R_AARCH64_TLSLE_ADD_TPREL_HI12 against v1 cannot be used with -shared
 # ERR: error: relocation R_AARCH64_TLSLE_ADD_TPREL_HI12 against v1 cannot be used with -shared
 # ERR: error: relocation R_AARCH64_TLSLE_ADD_TPREL_HI12 against v1 cannot be used with -shared
+# ERR: error: relocation R_AARCH64_TLSLE_LDST8_TPREL_LO12 against v1 cannot be used with -shared
+# ERR: error: relocation R_AARCH64_TLSLE_LDST16_TPREL_LO12 against v1 cannot be used with -shared
+# ERR: error: relocation R_AARCH64_TLSLE_LDST32_TPREL_LO12 against v1 cannot be used with -shared
+# ERR: error: relocation R_AARCH64_TLSLE_LDST64_TPREL_LO12 against v1 cannot be used with -shared
+# ERR: error: relocation R_AARCH64_TLSLE_LDST128_TPREL_LO12 against v1 cannot be used with -shared
 
 .globl _start
 _start:
@@ -35,8 +48,17 @@ _start:
  add x2, x1, :tprel_hi12:v1
  add w3, w3, :tprel_hi12:v1
  add sp, sp, :tprel_hi12:v1
+ ldrb w0, [x0, :tprel_lo12:v1]
+ ldrh w1, [x1, :tprel_lo12:v1]
+ ldr w2, [x2, :tprel_lo12:v1]
+ ldr x3, [x3, :tprel_lo12:v1]
+ ldr q4, [x4, :tprel_lo12:v1]
+.ifdef NONTLS
+ .reloc ., R_AARCH64_TLSLE_LDST8_TPREL_LO12, nonTls
+ nop
+.endif
 
-# TCB size = 0x16 and foo is first element from TLS register.
+# TCB size = 0x10 and v1 is first element from TLS register.
 #CHECK: Disassembly of section .text:
 #CHECK:      <_start>:
 #CHECK-NEXT:   mrs     x0, TPIDR_EL0
@@ -52,6 +74,11 @@ _start:
 #CHECK-NEXT:   add     w3, w3, #0, lsl #12
 #RELAX-NEXT:   nop
 #NORELAX-NEXT:   add     sp, sp, #0, lsl #12
+#CHECK-NEXT:   ldrb    w0, [x0, #16]
+#CHECK-NEXT:   ldrh    w1, [x1, #16]
+#CHECK-NEXT:   ldr     w2, [x2, #16]
+#CHECK-NEXT:   ldr     x3, [x3, #16]
+#CHECK-NEXT:   ldr     q4, [x4, #16]
 
 .section        .tbss,"awT", at nobits
 
@@ -79,3 +106,12 @@ v3:
 v2:
 .word  0
 .size  v2, 4
+
+.ifdef NONTLS
+.data
+.type   nonTls, at object
+.globl  nonTls
+nonTls:
+.word  0
+.size   nonTls, 4
+.endif

>From debfe7c26ce6c3a91c36cc5a38735841eeca8c3a Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Thu, 1 Oct 2026 14:15:14 +0800
Subject: [PATCH 2/2] [lld][AArch64] Remove AArch64-specific non-STT_TLS check

Address review feedback on #227629: the check only covered AArch64
local-exec TLS relocations. A generalized, architecture-independent
check will be added in a separate patch.
---
 lld/ELF/Arch/AArch64.cpp      |  6 ------
 lld/test/ELF/aarch64-tls-le.s | 21 ---------------------
 2 files changed, 27 deletions(-)

diff --git a/lld/ELF/Arch/AArch64.cpp b/lld/ELF/Arch/AArch64.cpp
index 6f551b7fe02af..5d0c5dbd0065c 100644
--- a/lld/ELF/Arch/AArch64.cpp
+++ b/lld/ELF/Arch/AArch64.cpp
@@ -352,12 +352,6 @@ void AArch64::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
     case R_AARCH64_TLSLE_MOVW_TPREL_G1:
     case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
     case R_AARCH64_TLSLE_MOVW_TPREL_G2:
-      if (LLVM_UNLIKELY(!sym.isTls())) {
-        Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
-                 << "relocation " << type << " against " << &sym
-                 << " cannot be used with a non-STT_TLS symbol";
-        continue;
-      }
       if (rs.checkTlsLe(offset, sym, type))
         continue;
       expr = R_TPREL;
diff --git a/lld/test/ELF/aarch64-tls-le.s b/lld/test/ELF/aarch64-tls-le.s
index e5a841d2f7763..bd72d66eb6155 100644
--- a/lld/test/ELF/aarch64-tls-le.s
+++ b/lld/test/ELF/aarch64-tls-le.s
@@ -13,14 +13,6 @@
 ## Reject local-exec TLS relocations for -shared.
 # RUN: not ld.lld -shared %t.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR --implicit-check-not=error:
 
-## Reject local-exec TLS relocations against non-STT_TLS symbols. llvm-mc
-## marks a symbol referenced through ':tprel_lo12:' as STT_TLS, so use .reloc
-## to produce a reference that is genuinely non-STT_TLS.
-# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -defsym=NONTLS=1 -o %t.nontls.o
-# RUN: not ld.lld %t.nontls.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=NONTLS --implicit-check-not=error:
-
-# NONTLS: error: {{.*}}:(.text+0x40): relocation R_AARCH64_TLSLE_LDST8_TPREL_LO12 against nonTls cannot be used with a non-STT_TLS symbol
-
 # ERR: error: relocation R_AARCH64_TLSLE_ADD_TPREL_HI12 against v1 cannot be used with -shared
 # ERR: error: relocation R_AARCH64_TLSLE_ADD_TPREL_LO12_NC against v1 cannot be used with -shared
 # ERR: error: relocation R_AARCH64_TLSLE_ADD_TPREL_LO12 against v3 cannot be used with -shared
@@ -53,10 +45,6 @@ _start:
  ldr w2, [x2, :tprel_lo12:v1]
  ldr x3, [x3, :tprel_lo12:v1]
  ldr q4, [x4, :tprel_lo12:v1]
-.ifdef NONTLS
- .reloc ., R_AARCH64_TLSLE_LDST8_TPREL_LO12, nonTls
- nop
-.endif
 
 # TCB size = 0x10 and v1 is first element from TLS register.
 #CHECK: Disassembly of section .text:
@@ -106,12 +94,3 @@ v3:
 v2:
 .word  0
 .size  v2, 4
-
-.ifdef NONTLS
-.data
-.type   nonTls, at object
-.globl  nonTls
-nonTls:
-.word  0
-.size   nonTls, 4
-.endif



More information about the llvm-commits mailing list