[llvm-branch-commits] [llvm] 121d539 - [AArch64][Windows] Fix swift async context slot placement (#212922)
Douglas Yung via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Aug 5 17:48:14 PDT 2026
Author: Sébastien Marchand
Date: 2026-08-06T00:48:00Z
New Revision: 121d539a6e83609c943f054dcc5b2c5ed48c5281
URL: https://github.com/llvm/llvm-project/commit/121d539a6e83609c943f054dcc5b2c5ed48c5281
DIFF: https://github.com/llvm/llvm-project/commit/121d539a6e83609c943f054dcc5b2c5ed48c5281.diff
LOG: [AArch64][Windows] Fix swift async context slot placement (#212922)
Swift async functions can miscompile on Windows ARM64 at `-O2`, when
there's enough register pressure that a local gets scavenged into the
callee-save area: the local ends up sharing an address with the saved
caller x29, so the epilogue restores a value the function has already
overwritten. swiftlang/swift#90920 has a reduced repro.
`assignCalleeSavedSpillSlots` creates the swift async context object
before the callee-save loop instead of inside it next to the FP slot.
MachineFrameInfo ends up with it above the frame record while the
prologue stores it below at FP-8, and the 8 byte disagreement leaves a
hole in the middle of the callee-save area. PEI's scavenger hands that
hole to the local. Only reproduces at -O2 and up since scavenging is
gated on the opt level.
```
sub sp, sp, #112
str x19, [sp, #16] // 8-byte Spill
str x21, [sp, #24] // 8-byte Spill
stp x23, x24, [sp, #32] // 16-byte Folded Spill
stp x25, x26, [sp, #48] // 16-byte Folded Spill
stp x27, x28, [sp, #64] // 16-byte Folded Spill
stp x29, x30, [sp, #88] // 16-byte Folded Spill
str xzr, [sp, #80]
add x29, sp, #88
...
str x7, [x29] // 8-byte Spill
...
ldr x1, [x29] // 8-byte Reload
ldp x29, x30, [sp, #88] // 16-byte Folded Reload
```
This creates the object inside the loop so the two agree. The other
option was leaving the creation site alone and teaching MachineFrameInfo
about the expanded 24 byte FP/LR slot, but that puts the layout in two
places. Not sure which is preferred here, I don't know this code well.
This also asserts the saved FP object resolves to FP+0, since nothing
checks that today. Reverting the fix makes it fire on the same funclet.
`store-swift-async-context-clobber-live-reg.ll` already miscompiles with
`-regalloc=fast`, so this isn't Swift specific.
(cherry picked from commit c358e8d90b3b26db86046f19de29bc848f255ac8)
Added:
llvm/test/CodeGen/AArch64/swift-async-context-frame-record-win.ll
Modified:
llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
llvm/test/CodeGen/AArch64/swift-async-context-seh.ll
llvm/test/CodeGen/AArch64/swift-async-context-slot-offset-win.ll
llvm/test/CodeGen/AArch64/swift-async-win.ll
llvm/test/CodeGen/AArch64/win-sve.ll
Removed:
################################################################################
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index a38b9798ac1e5..c2a0ab9cc09ca 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -2814,12 +2814,6 @@ bool AArch64FrameLowering::assignCalleeSavedSpillSlots(
MachineFrameInfo &MFI = MF.getFrameInfo();
auto *AFI = MF.getInfo<AArch64FunctionInfo>();
- if (IsWindows && hasFP(MF) && AFI->hasSwiftAsyncContext()) {
- int FrameIdx = MFI.CreateStackObject(8, Align(16), true);
- AFI->setSwiftAsyncContextFrameIdx(FrameIdx);
- MFI.setIsCalleeSavedObjectIndex(FrameIdx, true);
- }
-
// Insert VG into the list of CSRs, immediately before LR if saved.
if (requiresSaveVG(MF)) {
CalleeSavedInfo VGInfo(AArch64::VG);
@@ -2857,8 +2851,7 @@ bool AArch64FrameLowering::assignCalleeSavedSpillSlots(
MFI.setIsCalleeSavedObjectIndex(FrameIdx, true);
// Grab 8 bytes below FP for the extended asynchronous frame info.
- if (hasFP(MF) && AFI->hasSwiftAsyncContext() && !IsWindows &&
- Reg == AArch64::FP) {
+ if (hasFP(MF) && AFI->hasSwiftAsyncContext() && Reg == AArch64::FP) {
FrameIdx = MFI.CreateStackObject(8, Alignment, true);
AFI->setSwiftAsyncContextFrameIdx(FrameIdx);
MFI.setIsCalleeSavedObjectIndex(FrameIdx, true);
diff --git a/llvm/test/CodeGen/AArch64/swift-async-context-frame-record-win.ll b/llvm/test/CodeGen/AArch64/swift-async-context-frame-record-win.ll
new file mode 100644
index 0000000000000..95595a38a66aa
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/swift-async-context-frame-record-win.ll
@@ -0,0 +1,38 @@
+; NOTE: Do not autogenerate. This test is about four frame offsets, and full
+; generated assertions bury them in the rest of the function.
+; RUN: llc -mtriple=aarch64-unknown-windows-msvc -O2 < %s | FileCheck %s
+
+; The async context slot has to sit directly below the frame record. If it is
+; placed above the record instead, MachineFrameInfo's view of the callee-save
+; area disagrees with the prologue by 8 bytes, and PEI's scavenger fills the
+; resulting hole with a live local that then shares an address with the saved
+; caller x29.
+;
+; Check the record, the context slot below it, and that the scavenged spill goes
+; below the callee-save area rather than into it. Before the fix the last one
+; was `str x7, [x29]`, aliasing the saved x29 stored at sp+88.
+
+declare ptr @llvm.swift.async.context.addr() nounwind
+declare swiftcc void @swift_task_dealloc()
+
+define swifttailcc void @test(ptr %ctx, ptr %vw0, ptr %vw1, ptr %vw2, ptr %vw3, ptr %obj0, ptr %obj1, ptr %obj2, ptr %obj3, ptr %obj4) {
+; CHECK-LABEL: test:
+; CHECK: stp x29, x30, [sp, #88]
+; CHECK: str xzr, [sp, #80]
+; CHECK-NEXT: .seh_nop
+; CHECK: add x29, sp, #88
+; CHECK: str x7, [sp, #8]
+entryresume.0:
+ %ctxaddr = tail call ptr @llvm.swift.async.context.addr()
+ %reloaded = load ptr, ptr null, align 8
+ call swiftcc void @swift_task_dealloc()
+ %destroy0 = load ptr, ptr %ctx, align 8
+ tail call void %destroy0(ptr %reloaded, ptr %obj4)
+ %destroy1 = load ptr, ptr %obj1, align 8
+ tail call void %destroy1(ptr %vw2, ptr null)
+ %destroy2 = load ptr, ptr %obj3, align 8
+ tail call void %destroy2(ptr %vw1, ptr %obj2)
+ %destroy3 = load ptr, ptr %vw3, align 8
+ tail call void %destroy3(ptr %vw0, ptr %obj0)
+ ret void
+}
diff --git a/llvm/test/CodeGen/AArch64/swift-async-context-seh.ll b/llvm/test/CodeGen/AArch64/swift-async-context-seh.ll
index 852c97743a10c..0d1cd593f4729 100644
--- a/llvm/test/CodeGen/AArch64/swift-async-context-seh.ll
+++ b/llvm/test/CodeGen/AArch64/swift-async-context-seh.ll
@@ -8,7 +8,7 @@
; CHECK: orr x29, x29, #0x1000000000000000
; CHECK-NEXT: .seh_nop
-; CHECK: str x22, [sp, #16]
+; CHECK: str x22, [sp]
; CHECK-NEXT: .seh_nop
; CHECK: and x29, x29, #0xefffffffffffffff
; CHECK-NEXT: .seh_nop
diff --git a/llvm/test/CodeGen/AArch64/swift-async-context-slot-offset-win.ll b/llvm/test/CodeGen/AArch64/swift-async-context-slot-offset-win.ll
index 86e459a4af717..4e9d0d6f65d6b 100644
--- a/llvm/test/CodeGen/AArch64/swift-async-context-slot-offset-win.ll
+++ b/llvm/test/CodeGen/AArch64/swift-async-context-slot-offset-win.ll
@@ -5,13 +5,16 @@
; saving it won't overwrite the saved value of the callee-saved
; register.
;
-; CHECK: sub sp, sp, #64
-; CHECK: str x19, [sp, #16]
-; CHECK: str x21, [sp, #24]
-; CHECK-NOT: stp x29, x30, [sp, #32]
-; CHECK: stp x29, x30, [sp, #40]
-; CHECK-NOT: str x22, [sp, #24]
-; CHECK: str x22, [sp, #32]
+; The async context slot sits directly below the frame record, so the callee
+; saves below it stay clear of both.
+;
+; CHECK: str x19, [sp, #-48]!
+; CHECK: str x21, [sp, #8]
+; CHECK-NOT: stp x29, x30, [sp, #16]
+; CHECK: stp x29, x30, [sp, #24]
+; CHECK-NOT: str x22, [sp, #8]
+; CHECK: str x22, [sp, #16]
+; CHECK: add x29, sp, #24
declare ptr @llvm.swift.async.context.addr()
declare swiftcc i64 @foo(i64 %0, i64 %1)
diff --git a/llvm/test/CodeGen/AArch64/swift-async-win.ll b/llvm/test/CodeGen/AArch64/swift-async-win.ll
index c74e9667f9c75..b6035377d8149 100644
--- a/llvm/test/CodeGen/AArch64/swift-async-win.ll
+++ b/llvm/test/CodeGen/AArch64/swift-async-win.ll
@@ -18,12 +18,11 @@ declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #0
define hidden swifttailcc void @"$ss23withCheckedContinuation8function_xSS_yScCyxs5NeverOGXEtYalFTQ0_"(ptr nocapture readonly %0) #1 {
; CHECK-LABEL: $ss23withCheckedContinuation8function_xSS_yScCyxs5NeverOGXEtYalFTQ0_:
; CHECK: // %bb.0: // %entryresume.0
-; CHECK-NEXT: sub sp, sp, #48
-; CHECK-NEXT: str x19, [sp, #16] // 8-byte Spill
+; CHECK-NEXT: str x19, [sp, #-32]! // 8-byte Folded Spill
+; CHECK-NEXT: stp x29, x30, [sp, #16] // 16-byte Folded Spill
+; CHECK-NEXT: add x29, sp, #16
; CHECK-NEXT: adrp x19, __imp_swift_task_dealloc
-; CHECK-NEXT: stp x29, x30, [sp, #32] // 16-byte Folded Spill
-; CHECK-NEXT: add x29, sp, #32
-; CHECK-NEXT: str xzr, [sp, #24]
+; CHECK-NEXT: str xzr, [sp, #8]
; CHECK-NEXT: ldr x8, [x0]
; CHECK-NEXT: stur x8, [x29, #-8]
; CHECK-NEXT: ldr x20, [x0]
@@ -33,11 +32,10 @@ define hidden swifttailcc void @"$ss23withCheckedContinuation8function_xSS_yScCy
; CHECK-NEXT: blr x19
; CHECK-NEXT: mov x0, x22
; CHECK-NEXT: blr x19
-; CHECK-NEXT: ldp x29, x30, [sp, #32] // 16-byte Folded Reload
+; CHECK-NEXT: ldp x29, x30, [sp, #16] // 16-byte Folded Reload
; CHECK-NEXT: mov x0, x20
; CHECK-NEXT: ldr x1, [x20, #8]
-; CHECK-NEXT: ldr x19, [sp, #16] // 8-byte Reload
-; CHECK-NEXT: add sp, sp, #48
+; CHECK-NEXT: ldr x19, [sp], #32 // 8-byte Folded Reload
; CHECK-NEXT: br x1
entryresume.0:
%1 = load ptr, ptr %0, align 8
diff --git a/llvm/test/CodeGen/AArch64/win-sve.ll b/llvm/test/CodeGen/AArch64/win-sve.ll
index dea7781ba16e2..797c4de27704b 100644
--- a/llvm/test/CodeGen/AArch64/win-sve.ll
+++ b/llvm/test/CodeGen/AArch64/win-sve.ll
@@ -1582,14 +1582,11 @@ define void @f16(ptr swiftasync %ctx, <vscale x 2 x i64> %foo) {
; CHECK-NEXT: add x29, sp, #8
; CHECK-NEXT: .seh_add_fp 8
; CHECK-NEXT: .seh_endprologue
-; CHECK-NEXT: sub sp, sp, #16
; CHECK-NEXT: //APP
; CHECK-NEXT: //NO_APP
; CHECK-NEXT: ldr x8, [x22]
; CHECK-NEXT: stur x8, [x29, #-8]
; CHECK-NEXT: .seh_startepilogue
-; CHECK-NEXT: add sp, sp, #16
-; CHECK-NEXT: .seh_stackalloc 16
; CHECK-NEXT: ldp x29, x30, [sp, #8] // 16-byte Folded Reload
; CHECK-NEXT: .seh_save_fplr 8
; CHECK-NEXT: add sp, sp, #32
More information about the llvm-branch-commits
mailing list