https://github.com/dyung updated https://github.com/llvm/llvm-project/pull/214277
>From 121d539a6e83609c943f054dcc5b2c5ed48c5281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Marchand?= <[email protected]> Date: Tue, 4 Aug 2026 11:36:18 -0400 Subject: [PATCH] [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) --- .../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 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 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
