[llvm] [AArch64][Windows] Fix swift async context slot placement (PR #212922)
Sébastien Marchand via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 31 10:55:17 PDT 2026
https://github.com/sebmarchand updated https://github.com/llvm/llvm-project/pull/212922
>From 561d6117aec99229f3c9d9c7b19d8342f8e52c69 Mon Sep 17 00:00:00 2001
From: Sebastien Marchand <seb at thebrowser.company>
Date: Fri, 31 Jul 2026 13:37:56 -0400
Subject: [PATCH] [AArch64][Windows] Fix swift async context slot placement
On Windows, assignCalleeSavedSpillSlots creates the swift async context
object before the callee-save loop rather than inside it next to the FP
slot as every other target does. MachineFrameInfo therefore records it
above the frame record while computeCalleeSaveRegisterPairs has the
prologue store it below at FP-8. The two disagree by 8 bytes, which
leaves a hole in the middle of what MachineFrameInfo treats as the
callee-save area, and PrologEpilogInserter's stack slot scavenger fills
that hole with a live local, giving it the address of the saved caller
x29. The function then stores through [x29], reads it back, and the
epilogue restores the corrupted value. Scavenging is gated on the
optimization level, so this only reproduces at -O2 and above.
Create the object inside the loop so both views agree. Unwind data is
unaffected in shape: on a 208 function module the number of functions
with unwind information, the split between .xdata records and packed
encoding, and the .pdata size are all unchanged, and three functions get
a shorter unwind bytecode because the prologue no longer carries a dead
stack allocation.
The placement dates to 24ba1302b38b.
---
.../Target/AArch64/AArch64FrameLowering.cpp | 9 +----
.../swift-async-context-frame-record-win.ll | 38 +++++++++++++++++++
.../AArch64/swift-async-context-seh.ll | 2 +-
.../swift-async-context-slot-offset-win.ll | 17 +++++----
llvm/test/CodeGen/AArch64/swift-async-win.ll | 14 +++----
llvm/test/CodeGen/AArch64/win-sve.ll | 3 --
6 files changed, 56 insertions(+), 27 deletions(-)
create mode 100644 llvm/test/CodeGen/AArch64/swift-async-context-frame-record-win.ll
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index 6ef64b06932ab..5e9c22501f066 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -2815,12 +2815,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);
@@ -2858,8 +2852,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-commits
mailing list