[llvm] [AArch64] Fix miscompilation due to integer overflow in immediate offset for stack store/load instructions with preserve_all (PR #207026)
Jerry Dang via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 8 19:12:09 PDT 2026
https://github.com/kuroyukiasuna updated https://github.com/llvm/llvm-project/pull/207026
>From ce8bffc8b7e1b140c1575caf512e3053577bd11f Mon Sep 17 00:00:00 2001
From: Jerry Dang <kuroyukiasuna at gmail.com>
Date: Wed, 1 Jul 2026 11:49:05 -0400
Subject: [PATCH 1/5] [AArch64 backend] Miscompile fix: Avoid out-of-range
STP/LDP for large callee-save areas. Only pair a register when the resulting
offset fits the LDP/STP immediate; otherwise leave it unpaired and
spill/reload it with a single STR/LDR, whose 12-bit unsigned scaled immediate
(up to 32760 bytes) easily covers any callee-save area.
Accounting fix: Don't double count sub/super register overlap in CSR size
---
.../Target/AArch64/AArch64FrameLowering.cpp | 33 ++++++++++++++++---
.../CodeGen/AArch64/framelayout-fpr128-csr.ll | 12 +++----
.../AArch64/framelayout-fpr128-spill.mir | 4 +--
.../CodeGen/AArch64/preserve-all-large-csr.ll | 31 +++++++++++++++++
4 files changed, 68 insertions(+), 12 deletions(-)
create mode 100644 llvm/test/CodeGen/AArch64/preserve-all-large-csr.ll
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index 026f807124d2f..777aaed23baef 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -1784,27 +1784,39 @@ void computeCalleeSaveRegisterPairs(const AArch64FrameLowering &AFL,
bool NeedsWinCFI = AFL.needsWinCFI(MF);
int Scale = TRI->getSpillSize(*RPI.RC);
+ // A paired LDP/STP can only encode a signed 7-bit scaled offset ([-64,
+ // 63]). When the callee-save area is large (e.g. preserve_all spills 30+
+ // registers), the highest pairs fall outside that range. Only pair if the
+ // resulting offset is encodable; otherwise leave the register unpaired and
+ // let it be spilled with a single STR/LDR, which has a much wider 12-bit
+ // immediate.
+ auto PairFitsImmRange = [&]() {
+ int PairOffset =
+ IsWindows ? ByteOffset : ByteOffset + StackFillDir * 2 * Scale;
+ int Scaled = PairOffset / Scale;
+ return Scaled >= -64 && Scaled <= 63;
+ };
// Add the next reg to the pair if it is in the same register class.
if (unsigned(i + RegInc) < Count && !HasCSHazardPadding) {
MCRegister NextReg = CSI[i + RegInc].getReg();
unsigned SpillCount = NeedsWinCFI ? FirstReg - i : i;
switch (RPI.Type) {
case RegPairInfo::GPR:
- if (AArch64::GPR64RegClass.contains(NextReg) &&
+ if (AArch64::GPR64RegClass.contains(NextReg) && PairFitsImmRange() &&
!invalidateRegisterPairing(SpillExtendedVolatile, SpillCount,
RPI.Reg1, NextReg, IsWindows,
NeedsWinCFI, NeedsFrameRecord, TRI))
RPI.Reg2 = NextReg;
break;
case RegPairInfo::FPR64:
- if (AArch64::FPR64RegClass.contains(NextReg) &&
+ if (AArch64::FPR64RegClass.contains(NextReg) && PairFitsImmRange() &&
!invalidateRegisterPairing(SpillExtendedVolatile, SpillCount,
RPI.Reg1, NextReg, IsWindows,
NeedsWinCFI, NeedsFrameRecord, TRI))
RPI.Reg2 = NextReg;
break;
case RegPairInfo::FPR128:
- if (AArch64::FPR128RegClass.contains(NextReg))
+ if (AArch64::FPR128RegClass.contains(NextReg) && PairFitsImmRange())
RPI.Reg2 = NextReg;
break;
case RegPairInfo::PPR:
@@ -2631,17 +2643,30 @@ void AArch64FrameLowering::determineCalleeSaves(MachineFunction &MF,
unsigned ZPRCSStackSize = 0;
unsigned PPRCSStackSize = 0;
const TargetRegisterInfo *TRI = MF.getSubtarget().getRegisterInfo();
+ // A register and its super-register can both appear in SavedRegs (e.g. for
+ // preserve_all, AAPCS contributes D8-D15 while the extended convention
+ // contributes the enclosing Q8-Q31). Only the widest register is actually
+ // spilled, skip such sub-registers here to avoid double-counting the overlap.
+ BitVector CSMask(SavedRegs.size());
+ for (unsigned i = 0; CSRegs[i]; ++i)
+ CSMask.set(CSRegs[i]);
for (unsigned Reg : SavedRegs.set_bits()) {
auto *RC = TRI->getMinimalPhysRegClass(MCRegister(Reg));
assert(RC && "expected register class!");
auto SpillSize = TRI->getSpillSize(*RC);
bool IsZPR = AArch64::ZPRRegClass.contains(Reg);
bool IsPPR = !IsZPR && AArch64::PPRRegClass.contains(Reg);
+ bool SavedSuper = false;
+ for (MCPhysReg SuperReg : TRI->superregs(MCRegister(Reg)))
+ if (SavedRegs.test(SuperReg) && CSMask.test(SuperReg)) {
+ SavedSuper = true;
+ break;
+ }
if (IsZPR)
ZPRCSStackSize += SpillSize;
else if (IsPPR)
PPRCSStackSize += SpillSize;
- else
+ else if (!SavedSuper)
CSStackSize += SpillSize;
}
diff --git a/llvm/test/CodeGen/AArch64/framelayout-fpr128-csr.ll b/llvm/test/CodeGen/AArch64/framelayout-fpr128-csr.ll
index 4cce7ec8a47cd..98275757cb5e5 100644
--- a/llvm/test/CodeGen/AArch64/framelayout-fpr128-csr.ll
+++ b/llvm/test/CodeGen/AArch64/framelayout-fpr128-csr.ll
@@ -3,18 +3,18 @@
; RUN: llc -verify-machineinstrs -mtriple=aarch64-windows-msvc < %s | FileCheck %s --check-prefix=CHECK-WINDOWS
; The purpose of this test is to verify q8 is assigned a 16-byte aligned offset
-; after the x10 is assigned an offset. The CSRs (on Linux) are assigned offsets
-; in the order GPRs then FPRs. The stack size of this function is 32
-; (alignTo((16 + 8), 16)), so after x8 is given the offset 24, q8 originally
-; would be assigned offset 8, which is not 16-byte aligned.
+; when a GPR (x10) is also callee-saved. The CSRs (on Linux) are assigned
+; offsets in the order GPRs then FPRs. The stack size of this function is 32
+; (alignTo((16 + 8), 16)); q8 must be assigned the 16-byte aligned offset 0,
+; with x10 in the 8-byte slot above it and the alignment padding at the top.
define preserve_allcc void @d(ptr %ptr) nounwind {
; CHECK-LABEL: d:
; CHECK: // %bb.0: // %entry
; CHECK-NEXT: str q8, [sp, #-32]! // 16-byte Folded Spill
-; CHECK-NEXT: str x10, [sp, #24] // 8-byte Spill
+; CHECK-NEXT: str x10, [sp, #16] // 8-byte Spill
; CHECK-NEXT: //APP
; CHECK-NEXT: //NO_APP
-; CHECK-NEXT: ldr x10, [sp, #24] // 8-byte Reload
+; CHECK-NEXT: ldr x10, [sp, #16] // 8-byte Reload
; CHECK-NEXT: ldr q8, [sp], #32 // 16-byte Folded Reload
; CHECK-NEXT: ret
;
diff --git a/llvm/test/CodeGen/AArch64/framelayout-fpr128-spill.mir b/llvm/test/CodeGen/AArch64/framelayout-fpr128-spill.mir
index a6236bd917129..dff5841442c45 100644
--- a/llvm/test/CodeGen/AArch64/framelayout-fpr128-spill.mir
+++ b/llvm/test/CodeGen/AArch64/framelayout-fpr128-spill.mir
@@ -15,10 +15,10 @@ body: |
; CHECK: liveins: $q8, $x10
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: early-clobber $sp = frame-setup STRQpre killed $q8, $sp, -32 :: (store (s128) into %stack.1)
- ; CHECK-NEXT: frame-setup STRXui killed $x10, $sp, 3 :: (store (s64) into %stack.0)
+ ; CHECK-NEXT: frame-setup STRXui killed $x10, $sp, 2 :: (store (s64) into %stack.0)
; CHECK-NEXT: $q8 = IMPLICIT_DEF
; CHECK-NEXT: $x10 = IMPLICIT_DEF
- ; CHECK-NEXT: $x10 = frame-destroy LDRXui $sp, 3 :: (load (s64) from %stack.0)
+ ; CHECK-NEXT: $x10 = frame-destroy LDRXui $sp, 2 :: (load (s64) from %stack.0)
; CHECK-NEXT: early-clobber $sp, $q8 = frame-destroy LDRQpost $sp, 32 :: (load (s128) from %stack.1)
; CHECK-NEXT: RET_ReallyLR
;
diff --git a/llvm/test/CodeGen/AArch64/preserve-all-large-csr.ll b/llvm/test/CodeGen/AArch64/preserve-all-large-csr.ll
new file mode 100644
index 0000000000000..2e621606d14c8
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/preserve-all-large-csr.ll
@@ -0,0 +1,31 @@
+; RUN: llc -mtriple=aarch64-linux-gnu -verify-machineinstrs < %s | FileCheck %s
+
+; The preserve_all calling convention promotes almost all registers to
+; callee-saved, so a function that clobbers them all spills a callee-save area
+; larger than the +/-504 byte reach of the paired LDP/STP scaled 7-bit
+; immediate. Registers whose offset exceeds that range must be spilled and
+; reloaded with a single STR/LDR (which has a wider 12-bit immediate) instead
+; of an out-of-range STP/LDP.
+;
+; This also covers the related callee-save size accounting: D8-D15 (AAPCS) and
+; the enclosing Q8-Q15 (preserve_all) must not be double-counted, otherwise the
+; frame is over-sized and the prologue emits a redundant SP adjustment.
+
+; CHECK-LABEL: trigger_stack_spill:
+; The last in-range pair sits at the +504 boundary; the registers above it are
+; spilled as single STR (offset > 504, unencodable as a pair).
+; CHECK: stp x22, x21, [sp, #504]
+; CHECK-NEXT: str x20, [sp, #520]
+; CHECK-NEXT: str x19, [sp, #528]
+; The unwind offsets must match where the registers are actually stored.
+; CHECK: .cfi_offset w19, -16
+; CHECK: .cfi_offset w20, -24
+; The epilogue reloads them symmetrically with single LDR.
+; CHECK-DAG: ldr x19, [sp, #528]
+; CHECK-DAG: ldr x20, [sp, #520]
+
+define preserve_allcc void @trigger_stack_spill() {
+entry:
+ call void asm sideeffect "", "~{x0},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x18},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{fp},~{lr},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31},~{memory},~{cc}"()
+ ret void
+}
>From e21a5857d789901f07ead88d93efe2ace51ccac8 Mon Sep 17 00:00:00 2001
From: Jerry Dang <kuroyukiasuna at gmail.com>
Date: Wed, 1 Jul 2026 15:59:30 -0400
Subject: [PATCH 2/5] Address comments on test cases
---
.../CodeGen/AArch64/framelayout-fpr128-csr.ll | 28 +++
.../CodeGen/AArch64/preserve-all-large-csr.ll | 220 ++++++++++++++++--
2 files changed, 235 insertions(+), 13 deletions(-)
diff --git a/llvm/test/CodeGen/AArch64/framelayout-fpr128-csr.ll b/llvm/test/CodeGen/AArch64/framelayout-fpr128-csr.ll
index 98275757cb5e5..c4e384277502e 100644
--- a/llvm/test/CodeGen/AArch64/framelayout-fpr128-csr.ll
+++ b/llvm/test/CodeGen/AArch64/framelayout-fpr128-csr.ll
@@ -31,3 +31,31 @@ entry:
tail call void asm sideeffect "", "~{x10},~{q8}"()
ret void
}
+
+; A GPR pair (x10, x11) plus q8 fills the frame exactly (16 + 8 + 8 = 32) with
+; no padding. q8 must still be assigned a 16-byte aligned offset, and the
+; d8/q8 sub/super-register overlap must not inflate the callee-save size.
+define preserve_allcc void @e(ptr %ptr) nounwind {
+; CHECK-LABEL: e:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: str q8, [sp, #-32]! // 16-byte Folded Spill
+; CHECK-NEXT: stp x11, x10, [sp, #16] // 16-byte Folded Spill
+; CHECK-NEXT: //APP
+; CHECK-NEXT: //NO_APP
+; CHECK-NEXT: ldp x11, x10, [sp, #16] // 16-byte Folded Reload
+; CHECK-NEXT: ldr q8, [sp], #32 // 16-byte Folded Reload
+; CHECK-NEXT: ret
+;
+; CHECK-WINDOWS-LABEL: e:
+; CHECK-WINDOWS: // %bb.0: // %entry
+; CHECK-WINDOWS-NEXT: stp x10, x11, [sp, #-32]! // 16-byte Folded Spill
+; CHECK-WINDOWS-NEXT: str q8, [sp, #16] // 16-byte Spill
+; CHECK-WINDOWS-NEXT: //APP
+; CHECK-WINDOWS-NEXT: //NO_APP
+; CHECK-WINDOWS-NEXT: ldr q8, [sp, #16] // 16-byte Reload
+; CHECK-WINDOWS-NEXT: ldp x10, x11, [sp], #32 // 16-byte Folded Reload
+; CHECK-WINDOWS-NEXT: ret
+entry:
+ tail call void asm sideeffect "", "~{x10},~{x11},~{q8}"()
+ ret void
+}
diff --git a/llvm/test/CodeGen/AArch64/preserve-all-large-csr.ll b/llvm/test/CodeGen/AArch64/preserve-all-large-csr.ll
index 2e621606d14c8..6426483ebbce9 100644
--- a/llvm/test/CodeGen/AArch64/preserve-all-large-csr.ll
+++ b/llvm/test/CodeGen/AArch64/preserve-all-large-csr.ll
@@ -1,4 +1,6 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
; RUN: llc -mtriple=aarch64-linux-gnu -verify-machineinstrs < %s | FileCheck %s
+; RUN: llc -mtriple=aarch64-pc-windows-msvc -verify-machineinstrs < %s | FileCheck %s --check-prefix=CHECK-WIN
; The preserve_all calling convention promotes almost all registers to
; callee-saved, so a function that clobbers them all spills a callee-save area
@@ -10,21 +12,213 @@
; This also covers the related callee-save size accounting: D8-D15 (AAPCS) and
; the enclosing Q8-Q15 (preserve_all) must not be double-counted, otherwise the
; frame is over-sized and the prologue emits a redundant SP adjustment.
-
-; CHECK-LABEL: trigger_stack_spill:
-; The last in-range pair sits at the +504 boundary; the registers above it are
-; spilled as single STR (offset > 504, unencodable as a pair).
-; CHECK: stp x22, x21, [sp, #504]
-; CHECK-NEXT: str x20, [sp, #520]
-; CHECK-NEXT: str x19, [sp, #528]
-; The unwind offsets must match where the registers are actually stored.
-; CHECK: .cfi_offset w19, -16
-; CHECK: .cfi_offset w20, -24
-; The epilogue reloads them symmetrically with single LDR.
-; CHECK-DAG: ldr x19, [sp, #528]
-; CHECK-DAG: ldr x20, [sp, #520]
+;
+; The Windows target is checked too: it spills in a different (bottom-up) order
+; that keeps every callee-save offset in range, so no unpairing is needed there
+; and the offset math must still be correct.
define preserve_allcc void @trigger_stack_spill() {
+; CHECK-LABEL: trigger_stack_spill:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: stp q31, q30, [sp, #-544]! // 32-byte Folded Spill
+; CHECK-NEXT: stp q29, q28, [sp, #32] // 32-byte Folded Spill
+; CHECK-NEXT: stp q27, q26, [sp, #64] // 32-byte Folded Spill
+; CHECK-NEXT: stp q25, q24, [sp, #96] // 32-byte Folded Spill
+; CHECK-NEXT: stp q23, q22, [sp, #128] // 32-byte Folded Spill
+; CHECK-NEXT: stp q21, q20, [sp, #160] // 32-byte Folded Spill
+; CHECK-NEXT: stp q19, q18, [sp, #192] // 32-byte Folded Spill
+; CHECK-NEXT: stp q17, q16, [sp, #224] // 32-byte Folded Spill
+; CHECK-NEXT: stp q15, q14, [sp, #256] // 32-byte Folded Spill
+; CHECK-NEXT: stp q13, q12, [sp, #288] // 32-byte Folded Spill
+; CHECK-NEXT: stp q11, q10, [sp, #320] // 32-byte Folded Spill
+; CHECK-NEXT: stp q9, q8, [sp, #352] // 32-byte Folded Spill
+; CHECK-NEXT: str x15, [sp, #384] // 8-byte Spill
+; CHECK-NEXT: stp x14, x13, [sp, #392] // 16-byte Folded Spill
+; CHECK-NEXT: stp x12, x11, [sp, #408] // 16-byte Folded Spill
+; CHECK-NEXT: stp x10, x9, [sp, #424] // 16-byte Folded Spill
+; CHECK-NEXT: stp x29, x30, [sp, #440] // 16-byte Folded Spill
+; CHECK-NEXT: stp x28, x27, [sp, #456] // 16-byte Folded Spill
+; CHECK-NEXT: stp x26, x25, [sp, #472] // 16-byte Folded Spill
+; CHECK-NEXT: stp x24, x23, [sp, #488] // 16-byte Folded Spill
+; CHECK-NEXT: stp x22, x21, [sp, #504] // 16-byte Folded Spill
+; CHECK-NEXT: str x20, [sp, #520] // 8-byte Spill
+; CHECK-NEXT: str x19, [sp, #528] // 8-byte Spill
+; CHECK-NEXT: sub sp, sp, #16
+; CHECK-NEXT: .cfi_def_cfa_offset 560
+; CHECK-NEXT: .cfi_offset w19, -16
+; CHECK-NEXT: .cfi_offset w20, -24
+; CHECK-NEXT: .cfi_offset w21, -32
+; CHECK-NEXT: .cfi_offset w22, -40
+; CHECK-NEXT: .cfi_offset w23, -48
+; CHECK-NEXT: .cfi_offset w24, -56
+; CHECK-NEXT: .cfi_offset w25, -64
+; CHECK-NEXT: .cfi_offset w26, -72
+; CHECK-NEXT: .cfi_offset w27, -80
+; CHECK-NEXT: .cfi_offset w28, -88
+; CHECK-NEXT: .cfi_offset w30, -96
+; CHECK-NEXT: .cfi_offset w29, -104
+; CHECK-NEXT: .cfi_offset w9, -112
+; CHECK-NEXT: .cfi_offset w10, -120
+; CHECK-NEXT: .cfi_offset w11, -128
+; CHECK-NEXT: .cfi_offset w12, -136
+; CHECK-NEXT: .cfi_offset w13, -144
+; CHECK-NEXT: .cfi_offset w14, -152
+; CHECK-NEXT: .cfi_offset w15, -160
+; CHECK-NEXT: .cfi_offset b8, -176
+; CHECK-NEXT: .cfi_offset b9, -192
+; CHECK-NEXT: .cfi_offset b10, -208
+; CHECK-NEXT: .cfi_offset b11, -224
+; CHECK-NEXT: .cfi_offset b12, -240
+; CHECK-NEXT: .cfi_offset b13, -256
+; CHECK-NEXT: .cfi_offset b14, -272
+; CHECK-NEXT: .cfi_offset b15, -288
+; CHECK-NEXT: .cfi_offset b16, -304
+; CHECK-NEXT: .cfi_offset b17, -320
+; CHECK-NEXT: .cfi_offset b18, -336
+; CHECK-NEXT: .cfi_offset b19, -352
+; CHECK-NEXT: .cfi_offset b20, -368
+; CHECK-NEXT: .cfi_offset b21, -384
+; CHECK-NEXT: .cfi_offset b22, -400
+; CHECK-NEXT: .cfi_offset b23, -416
+; CHECK-NEXT: .cfi_offset b24, -432
+; CHECK-NEXT: .cfi_offset b25, -448
+; CHECK-NEXT: .cfi_offset b26, -464
+; CHECK-NEXT: .cfi_offset b27, -480
+; CHECK-NEXT: .cfi_offset b28, -496
+; CHECK-NEXT: .cfi_offset b29, -512
+; CHECK-NEXT: .cfi_offset b30, -528
+; CHECK-NEXT: .cfi_offset b31, -544
+; CHECK-NEXT: //APP
+; CHECK-NEXT: //NO_APP
+; CHECK-NEXT: add sp, sp, #16
+; CHECK-NEXT: ldp x22, x21, [sp, #504] // 16-byte Folded Reload
+; CHECK-NEXT: ldr x19, [sp, #528] // 8-byte Reload
+; CHECK-NEXT: ldp x24, x23, [sp, #488] // 16-byte Folded Reload
+; CHECK-NEXT: ldr x20, [sp, #520] // 8-byte Reload
+; CHECK-NEXT: ldr x15, [sp, #384] // 8-byte Reload
+; CHECK-NEXT: ldp x26, x25, [sp, #472] // 16-byte Folded Reload
+; CHECK-NEXT: ldp x28, x27, [sp, #456] // 16-byte Folded Reload
+; CHECK-NEXT: ldp x29, x30, [sp, #440] // 16-byte Folded Reload
+; CHECK-NEXT: ldp x10, x9, [sp, #424] // 16-byte Folded Reload
+; CHECK-NEXT: ldp x12, x11, [sp, #408] // 16-byte Folded Reload
+; CHECK-NEXT: ldp x14, x13, [sp, #392] // 16-byte Folded Reload
+; CHECK-NEXT: ldp q9, q8, [sp, #352] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q11, q10, [sp, #320] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q13, q12, [sp, #288] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q15, q14, [sp, #256] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q17, q16, [sp, #224] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q19, q18, [sp, #192] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q21, q20, [sp, #160] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q23, q22, [sp, #128] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q25, q24, [sp, #96] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q27, q26, [sp, #64] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q29, q28, [sp, #32] // 32-byte Folded Reload
+; CHECK-NEXT: ldp q31, q30, [sp], #544 // 32-byte Folded Reload
+; CHECK-NEXT: ret
+;
+; CHECK-WIN-LABEL: trigger_stack_spill:
+; CHECK-WIN: .seh_proc trigger_stack_spill
+; CHECK-WIN-NEXT: // %bb.0: // %entry
+; CHECK-WIN-NEXT: sub sp, sp, #544
+; CHECK-WIN-NEXT: .seh_stackalloc 544
+; CHECK-WIN-NEXT: stp x19, x20, [sp] // 16-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_regp x19, 0
+; CHECK-WIN-NEXT: stp x21, x22, [sp, #16] // 16-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_regp x21, 16
+; CHECK-WIN-NEXT: stp x23, x24, [sp, #32] // 16-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_regp x23, 32
+; CHECK-WIN-NEXT: stp x25, x26, [sp, #48] // 16-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_regp x25, 48
+; CHECK-WIN-NEXT: stp x27, x28, [sp, #64] // 16-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_regp x27, 64
+; CHECK-WIN-NEXT: stp x29, x30, [sp, #80] // 16-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_fplr 80
+; CHECK-WIN-NEXT: stp x9, x10, [sp, #96] // 16-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p x9, 96
+; CHECK-WIN-NEXT: stp x11, x12, [sp, #112] // 16-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p x11, 112
+; CHECK-WIN-NEXT: stp x13, x14, [sp, #128] // 16-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p x13, 128
+; CHECK-WIN-NEXT: str x15, [sp, #144] // 8-byte Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg x15, 144
+; CHECK-WIN-NEXT: stp q8, q9, [sp, #160] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q8, 160
+; CHECK-WIN-NEXT: stp q10, q11, [sp, #192] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q10, 192
+; CHECK-WIN-NEXT: stp q12, q13, [sp, #224] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q12, 224
+; CHECK-WIN-NEXT: stp q14, q15, [sp, #256] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q14, 256
+; CHECK-WIN-NEXT: stp q16, q17, [sp, #288] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q16, 288
+; CHECK-WIN-NEXT: stp q18, q19, [sp, #320] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q18, 320
+; CHECK-WIN-NEXT: stp q20, q21, [sp, #352] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q20, 352
+; CHECK-WIN-NEXT: stp q22, q23, [sp, #384] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q22, 384
+; CHECK-WIN-NEXT: stp q24, q25, [sp, #416] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q24, 416
+; CHECK-WIN-NEXT: stp q26, q27, [sp, #448] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q26, 448
+; CHECK-WIN-NEXT: stp q28, q29, [sp, #480] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q28, 480
+; CHECK-WIN-NEXT: stp q30, q31, [sp, #512] // 32-byte Folded Spill
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q30, 512
+; CHECK-WIN-NEXT: .seh_endprologue
+; CHECK-WIN-NEXT: //APP
+; CHECK-WIN-NEXT: //NO_APP
+; CHECK-WIN-NEXT: .seh_startepilogue
+; CHECK-WIN-NEXT: ldp q30, q31, [sp, #512] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q30, 512
+; CHECK-WIN-NEXT: ldp q28, q29, [sp, #480] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q28, 480
+; CHECK-WIN-NEXT: ldp q26, q27, [sp, #448] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q26, 448
+; CHECK-WIN-NEXT: ldp q24, q25, [sp, #416] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q24, 416
+; CHECK-WIN-NEXT: ldp q22, q23, [sp, #384] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q22, 384
+; CHECK-WIN-NEXT: ldp q20, q21, [sp, #352] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q20, 352
+; CHECK-WIN-NEXT: ldp q18, q19, [sp, #320] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q18, 320
+; CHECK-WIN-NEXT: ldp q16, q17, [sp, #288] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q16, 288
+; CHECK-WIN-NEXT: ldp q14, q15, [sp, #256] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q14, 256
+; CHECK-WIN-NEXT: ldp q12, q13, [sp, #224] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q12, 224
+; CHECK-WIN-NEXT: ldp q10, q11, [sp, #192] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q10, 192
+; CHECK-WIN-NEXT: ldp q8, q9, [sp, #160] // 32-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p q8, 160
+; CHECK-WIN-NEXT: ldr x15, [sp, #144] // 8-byte Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg x15, 144
+; CHECK-WIN-NEXT: ldp x13, x14, [sp, #128] // 16-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p x13, 128
+; CHECK-WIN-NEXT: ldp x11, x12, [sp, #112] // 16-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p x11, 112
+; CHECK-WIN-NEXT: ldp x9, x10, [sp, #96] // 16-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_any_reg_p x9, 96
+; CHECK-WIN-NEXT: ldp x29, x30, [sp, #80] // 16-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_fplr 80
+; CHECK-WIN-NEXT: ldp x27, x28, [sp, #64] // 16-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_regp x27, 64
+; CHECK-WIN-NEXT: ldp x25, x26, [sp, #48] // 16-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_regp x25, 48
+; CHECK-WIN-NEXT: ldp x23, x24, [sp, #32] // 16-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_regp x23, 32
+; CHECK-WIN-NEXT: ldp x21, x22, [sp, #16] // 16-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_regp x21, 16
+; CHECK-WIN-NEXT: ldp x19, x20, [sp] // 16-byte Folded Reload
+; CHECK-WIN-NEXT: .seh_save_regp x19, 0
+; CHECK-WIN-NEXT: add sp, sp, #544
+; CHECK-WIN-NEXT: .seh_stackalloc 544
+; CHECK-WIN-NEXT: .seh_endepilogue
+; CHECK-WIN-NEXT: ret
+; CHECK-WIN-NEXT: .seh_endfunclet
+; CHECK-WIN-NEXT: .seh_endproc
entry:
call void asm sideeffect "", "~{x0},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x18},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{fp},~{lr},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31},~{memory},~{cc}"()
ret void
>From 70d78e03ea26a0bbf9c1e6e94b0a63e988c77fc0 Mon Sep 17 00:00:00 2001
From: Jerry Dang <kuroyukiasuna at gmail.com>
Date: Wed, 1 Jul 2026 17:23:31 -0400
Subject: [PATCH 3/5] Remove CSMask as it's redundant for AArch64
---
llvm/lib/Target/AArch64/AArch64FrameLowering.cpp | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index 777aaed23baef..9fc7a69f18837 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -2643,22 +2643,21 @@ void AArch64FrameLowering::determineCalleeSaves(MachineFunction &MF,
unsigned ZPRCSStackSize = 0;
unsigned PPRCSStackSize = 0;
const TargetRegisterInfo *TRI = MF.getSubtarget().getRegisterInfo();
- // A register and its super-register can both appear in SavedRegs (e.g. for
- // preserve_all, AAPCS contributes D8-D15 while the extended convention
- // contributes the enclosing Q8-Q31). Only the widest register is actually
- // spilled, skip such sub-registers here to avoid double-counting the overlap.
- BitVector CSMask(SavedRegs.size());
- for (unsigned i = 0; CSRegs[i]; ++i)
- CSMask.set(CSRegs[i]);
for (unsigned Reg : SavedRegs.set_bits()) {
auto *RC = TRI->getMinimalPhysRegClass(MCRegister(Reg));
assert(RC && "expected register class!");
auto SpillSize = TRI->getSpillSize(*RC);
bool IsZPR = AArch64::ZPRRegClass.contains(Reg);
bool IsPPR = !IsZPR && AArch64::PPRRegClass.contains(Reg);
+
+ // A register and its super-register can both appear in SavedRegs (e.g. for
+ // preserve_all, AAPCS contributes D8-D15 while the extended convention
+ // contributes the enclosing Q8-Q31). Only the widest register is actually
+ // spilled, skip such sub-registers here to avoid double-counting the
+ // overlap.
bool SavedSuper = false;
for (MCPhysReg SuperReg : TRI->superregs(MCRegister(Reg)))
- if (SavedRegs.test(SuperReg) && CSMask.test(SuperReg)) {
+ if (SavedRegs.test(SuperReg)) {
SavedSuper = true;
break;
}
>From 8c0aaf0af33c43d928a24acf09a5e8d59615ae15 Mon Sep 17 00:00:00 2001
From: Jerry Dang <kuroyukiasuna at gmail.com>
Date: Thu, 2 Jul 2026 13:23:29 -0400
Subject: [PATCH 4/5] truncate down the comments
---
llvm/lib/Target/AArch64/AArch64FrameLowering.cpp | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index 9fc7a69f18837..fb4cd521c6178 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -1784,12 +1784,7 @@ void computeCalleeSaveRegisterPairs(const AArch64FrameLowering &AFL,
bool NeedsWinCFI = AFL.needsWinCFI(MF);
int Scale = TRI->getSpillSize(*RPI.RC);
- // A paired LDP/STP can only encode a signed 7-bit scaled offset ([-64,
- // 63]). When the callee-save area is large (e.g. preserve_all spills 30+
- // registers), the highest pairs fall outside that range. Only pair if the
- // resulting offset is encodable; otherwise leave the register unpaired and
- // let it be spilled with a single STR/LDR, which has a much wider 12-bit
- // immediate.
+ // True when the pair's ldp/stp offset fits the signed 7-bit scaled imm.
auto PairFitsImmRange = [&]() {
int PairOffset =
IsWindows ? ByteOffset : ByteOffset + StackFillDir * 2 * Scale;
@@ -2650,11 +2645,9 @@ void AArch64FrameLowering::determineCalleeSaves(MachineFunction &MF,
bool IsZPR = AArch64::ZPRRegClass.contains(Reg);
bool IsPPR = !IsZPR && AArch64::PPRRegClass.contains(Reg);
- // A register and its super-register can both appear in SavedRegs (e.g. for
- // preserve_all, AAPCS contributes D8-D15 while the extended convention
- // contributes the enclosing Q8-Q31). Only the widest register is actually
- // spilled, skip such sub-registers here to avoid double-counting the
- // overlap.
+ // A register and its super-register can both appear in SavedRegs.
+ // Only the widest register is actually spilled, so skip such sub-registers
+ // here to avoid double-counting the overlap.
bool SavedSuper = false;
for (MCPhysReg SuperReg : TRI->superregs(MCRegister(Reg)))
if (SavedRegs.test(SuperReg)) {
>From 6a48d5d27c2d2a6af20c3e3d7f0b725035965762 Mon Sep 17 00:00:00 2001
From: Jerry Dang <kuroyukiasuna at gmail.com>
Date: Tue, 7 Jul 2026 21:15:57 -0400
Subject: [PATCH 5/5] Refactor; Use AlignOffset on ByteOffset.
---
.../Target/AArch64/AArch64FrameLowering.cpp | 29 ++++++++++---------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index 9805cd3390d9b..4c55e91fa6960 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -1796,8 +1796,8 @@ void computeCalleeSaveRegisterPairs(const AArch64FrameLowering &AFL,
int Scale = TRI->getSpillSize(*RPI.RC);
// True when the pair's ldp/stp offset fits the signed 7-bit scaled imm.
auto PairFitsImmRange = [&]() {
- int PairOffset =
- IsWindows ? ByteOffset : ByteOffset + StackFillDir * 2 * Scale;
+ int Aligned = AlignOffset(ByteOffset, Scale);
+ int PairOffset = IsWindows ? Aligned : Aligned + StackFillDir * 2 * Scale;
int Scaled = PairOffset / Scale;
return Scaled >= -64 && Scaled <= 63;
};
@@ -2654,22 +2654,23 @@ void AArch64FrameLowering::determineCalleeSaves(MachineFunction &MF,
auto SpillSize = TRI->getSpillSize(*RC);
bool IsZPR = AArch64::ZPRRegClass.contains(Reg);
bool IsPPR = !IsZPR && AArch64::PPRRegClass.contains(Reg);
-
- // A register and its super-register can both appear in SavedRegs.
- // Only the widest register is actually spilled, so skip such sub-registers
- // here to avoid double-counting the overlap.
- bool SavedSuper = false;
- for (MCPhysReg SuperReg : TRI->superregs(MCRegister(Reg)))
- if (SavedRegs.test(SuperReg)) {
- SavedSuper = true;
- break;
- }
if (IsZPR)
ZPRCSStackSize += SpillSize;
else if (IsPPR)
PPRCSStackSize += SpillSize;
- else if (!SavedSuper)
- CSStackSize += SpillSize;
+ else {
+ // A register and its super-register can both appear in SavedRegs.
+ // Only the widest register is actually spilled, so skip such
+ // sub-registers here to avoid double-counting the overlap.
+ bool SavedSuper = false;
+ for (MCPhysReg SuperReg : TRI->superregs(MCRegister(Reg)))
+ if (SavedRegs.test(SuperReg)) {
+ SavedSuper = true;
+ break;
+ }
+ if (!SavedSuper)
+ CSStackSize += SpillSize;
+ }
}
// Save number of saved regs, so we can easily update CSStackSize later to
More information about the llvm-commits
mailing list