[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