[llvm] [AArch64][COFF] Fix HI12 SECREL miscompile in adjustFixupValue (PR #199602)
via llvm-commits
llvm-commits at lists.llvm.org
Tue May 26 16:48:36 PDT 2026
https://github.com/hotschmoe updated https://github.com/llvm/llvm-project/pull/199602
>From 44d761fd7889b863dbf0b4d9aff9a1c8b19a76a0 Mon Sep 17 00:00:00 2001
From: hotschmoe <stronggarner66 at gmail.com>
Date: Mon, 25 May 2026 20:19:09 -0700
Subject: [PATCH] [AArch64][COFF] Fix HI12 SECREL miscompile in
adjustFixupValue
`Value &= 0xfff` in adjustFixupValue's COFF !IsResolved path clips bits 0-11
of the resolved section offset. That is correct for LO12 specifiers but wrong
for HI12 specifiers (S_SECREL_HI12, S_TPREL_HI12, S_DTPREL_HI12) where the
bits we want are 12-23 -- the value must be right-shifted by 12 before
masking.
fixup_aarch64_add_imm12 / fixup_aarch64_ldst_imm12_scale1 are shared between
HI12 and LO12 because WinCOFFObjectWriter picks
IMAGE_REL_ARM64_SECREL_{HIGH,LOW}12A from the MCSpecifierExpr only AFTER
adjustFixupValue has run -- so the mask has destroyed the high bits the HI12
relocation needs.
Impact on Windows-ARM64: any `private` thread_local at a non-zero offset
within its .tls$ section produces a miscompiled HI12 add. For a symbol at
offset N the assembler encodes imm12 = (N & 0xfff) instead of
((N >> 12) & 0xfff). The corrupt value propagates through the linker into
the final binary as a TLS access at the wrong address.
Tests:
- llvm/test/CodeGen/AArch64/win-tls-private.ll reproduces the codegen-level
miscompile (private TLS at non-zero .tls$ offset).
- llvm/test/MC/AArch64/coff-secrel-hi12.s exercises the MC layer directly
with a local symbol at section offset 0x12345 referenced via
:secrel_hi12: and :secrel_lo12:. Without the fix, both instructions
would encode imm12 = 0x345 (837); with the fix, the HI12 instruction
correctly encodes imm12 = 0x12 (18).
Fixes #199581
Assisted-by: Claude (Anthropic)
---
.../MCTargetDesc/AArch64AsmBackend.cpp | 6 +++-
llvm/test/CodeGen/AArch64/win-tls-private.ll | 29 +++++++++++++++
llvm/test/MC/AArch64/coff-secrel-hi12.s | 35 +++++++++++++++++++
3 files changed, 69 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/CodeGen/AArch64/win-tls-private.ll
create mode 100644 llvm/test/MC/AArch64/coff-secrel-hi12.s
diff --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
index eece54692e18c..fd67253696acc 100644
--- a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
+++ b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
@@ -165,8 +165,12 @@ static uint64_t adjustFixupValue(const MCFixup &Fixup, const MCValue &Target,
return (Value >> 2) & 0x7ffff;
case AArch64::fixup_aarch64_add_imm12:
case AArch64::fixup_aarch64_ldst_imm12_scale1:
- if (TheTriple.isOSBinFormatCOFF() && !IsResolved)
+ if (TheTriple.isOSBinFormatCOFF() && !IsResolved) {
+ if (AArch64::getAddressFrag(static_cast<AArch64::Specifier>(
+ Target.getSpecifier())) == AArch64::S_HI12)
+ Value >>= 12;
Value &= 0xfff;
+ }
// Unsigned 12-bit immediate
if (!isUInt<12>(Value))
Ctx.reportError(Fixup.getLoc(), "fixup value out of range");
diff --git a/llvm/test/CodeGen/AArch64/win-tls-private.ll b/llvm/test/CodeGen/AArch64/win-tls-private.ll
new file mode 100644
index 0000000000000..15c2887290754
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/win-tls-private.ll
@@ -0,0 +1,29 @@
+; RUN: llc -mtriple=aarch64-unknown-windows-gnu -filetype=obj %s -o %t.obj
+; RUN: llvm-objdump --no-print-imm-hex -d --disassemble-symbols=foo %t.obj | FileCheck %s
+
+; Regression test for https://github.com/llvm/llvm-project/issues/199581 and
+; https://codeberg.org/ziglang/zig/issues/31865.
+;
+; When a 'private' thread_local global sits at a non-zero offset within the
+; .tls$ section (because a non-'private' thread_local precedes it), the COFF
+; backend emits an IMAGE_REL_ARM64_SECREL_HIGH12A relocation. Before this
+; fix, AArch64AsmBackend::adjustFixupValue masked the value with 0xfff
+; without shifting by 12 first, leaving the low 12 bits of the section
+; offset in the imm12 field of the HI12 add. The COFF linker then OR-ed in
+; the high 12 bits, corrupting the final TLS address. Verify the HI12 add's
+; imm12 field is encoded as zero (the assembler-side contribution) so the
+; linker's SECREL_HIGH12A fixup writes the correct value uncorrupted.
+
+ at debug.panic_stage = thread_local global i64 0
+ at repro.x = private thread_local global i32 0
+
+ at foo = alias i32 (), ptr @repro.foo
+
+define i32 @repro.foo() {
+ %1 = load i32, ptr @repro.x, align 4
+ ret i32 %1
+}
+
+; CHECK-LABEL: <foo>:
+; CHECK: ldr x{{[0-9]+}}, [x{{[0-9]+}}, x{{[0-9]+}}, lsl #3]
+; CHECK-NEXT: add x{{[0-9]+}}, x{{[0-9]+}}, #0, lsl #12
diff --git a/llvm/test/MC/AArch64/coff-secrel-hi12.s b/llvm/test/MC/AArch64/coff-secrel-hi12.s
new file mode 100644
index 0000000000000..3de22275fdd66
--- /dev/null
+++ b/llvm/test/MC/AArch64/coff-secrel-hi12.s
@@ -0,0 +1,35 @@
+// RUN: llvm-mc -triple aarch64-windows -filetype obj -o %t.obj %s
+// RUN: llvm-mc -triple arm64ec-windows -filetype obj -o %t-ec.obj %s
+// RUN: llvm-readobj -r %t.obj | FileCheck %s --check-prefix=RELOC
+// RUN: llvm-readobj -r %t-ec.obj | FileCheck %s --check-prefix=RELOC
+// RUN: llvm-objdump --no-print-imm-hex -d %t.obj | FileCheck %s --check-prefix=DISASM
+// RUN: llvm-objdump --no-print-imm-hex -d %t-ec.obj | FileCheck %s --check-prefix=DISASM
+
+// Regression test for https://github.com/llvm/llvm-project/issues/199581.
+// adjustFixupValue's COFF !IsResolved path must right-shift Value by 12
+// before masking to 12 bits for HI12 specifiers; LO12 must not.
+//
+// .Lfoo sits at section offset 0x12345 in .bss. The HI12 add encodes
+// imm12 = (0x12345 >> 12) & 0xfff = 0x12 (18); the LO12 add encodes 0x345.
+
+ .bss
+ .zero 0x12345
+.Lfoo:
+ .long 0
+
+ .text
+f:
+ add x0, x0, :secrel_hi12:.Lfoo
+ add x0, x0, :secrel_lo12:.Lfoo
+ ret
+
+// RELOC: Relocations [
+// RELOC: Section ({{[0-9]+}}) .text {
+// RELOC-NEXT: 0x0 IMAGE_REL_ARM64_SECREL_HIGH12A
+// RELOC-NEXT: 0x4 IMAGE_REL_ARM64_SECREL_LOW12A
+// RELOC-NEXT: }
+
+// DISASM-LABEL: <f>:
+// DISASM-NEXT: add x0, x0, #18, lsl #12
+// DISASM-NEXT: add x0, x0, #837
+// DISASM-NEXT: ret
More information about the llvm-commits
mailing list