[llvm] [X86] Fix missing CFI after the Swift async context push (PR #202570)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 9 03:24:51 PDT 2026
https://github.com/adalal-2441 created https://github.com/llvm/llvm-project/pull/202570
The Swift async prologue pushes the context slot (pushq %r14 / $0) but doesn't touch the CFA until the later .cfi_def_cfa_register %rbp. So the CFA still describes the stack from before the push and stays stale all the way through the leaq and the subq. If something unwinds in that window (debugger, profiler, signal) it reads the wrong slot. Normal execution is fine.
FIX: account for the push with .cfi_adjust_cfa_offset 8, then switch to an %rbp-relative CFA (.cfi_def_cfa %rbp, 16) right after the leaq and before the subq, so the rule is correct before rsp moves again.
Adds swift-async-cfi-prologue.ll (directives + .eh_frame rows, plus a locals case for the subq) and updates swift-async.ll.
Found via @jlebar's X86 LLVM bug hunt / FuzzX effort: https://github.com/SemiAnalysisAI/FuzzX/tree/master/x86/bugs/036-frame-swift-async-cfi-missing
cc @jlebar
>From b196be6c2ffab84ebe0eee1c1619829d33b5ea03 Mon Sep 17 00:00:00 2001
From: Akshat <adalal at amd.com>
Date: Tue, 9 Jun 2026 15:42:29 +0530
Subject: [PATCH] [X86] Fix missing CFI after the Swift async context push
The Swift async prologue pushes the context slot (pushq %r14 / $0) but
doesn't touch the CFA until the later .cfi_def_cfa_register %rbp. So the
CFA still describes the stack from before the push and stays stale all
the way through the leaq and the subq. If something unwinds in that
window (debugger, profiler, signal) it reads the wrong slot. Normal
execution is fine.
FIX: account for the push with .cfi_adjust_cfa_offset 8, then switch to
an %rbp-relative CFA (.cfi_def_cfa %rbp, 16) right after the leaq and
before the subq, so the rule is correct before rsp moves again.
Adds swift-async-cfi-prologue.ll (directives + .eh_frame rows, plus a
locals case for the subq) and updates swift-async.ll.
Found via @jlebar's X86 LLVM bug hunt / FuzzX effort:
https://github.com/SemiAnalysisAI/FuzzX/tree/master/x86/bugs/036-frame-swift-async-cfi-missing
cc @jlebar
---
llvm/lib/Target/X86/X86FrameLowering.cpp | 21 +++++++++-
.../CodeGen/X86/swift-async-cfi-prologue.ll | 41 +++++++++++++++++++
llvm/test/CodeGen/X86/swift-async.ll | 13 +++++-
3 files changed, 73 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/CodeGen/X86/swift-async-cfi-prologue.ll
diff --git a/llvm/lib/Target/X86/X86FrameLowering.cpp b/llvm/lib/Target/X86/X86FrameLowering.cpp
index 3dec37bb87ea9..6881e29c8d4e5 100644
--- a/llvm/lib/Target/X86/X86FrameLowering.cpp
+++ b/llvm/lib/Target/X86/X86FrameLowering.cpp
@@ -1860,6 +1860,14 @@ void X86FrameLowering::emitPrologue(MachineFunction &MF,
.setMIFlag(MachineInstr::FrameSetup);
}
+ // Update CFA offset for the async-context push.
+ if (NeedsDwarfCFI && !ArgBaseReg.isValid()) {
+ BuildCFI(
+ MBB, MBBI, DL,
+ MCCFIInstruction::createAdjustCfaOffset(nullptr, -stackGrowth),
+ MachineInstr::FrameSetup);
+ }
+
if (NeedsWinCFI) {
HasWinCFI = true;
BuildMI(MBB, MBBI, DL, TII.get(X86::SEH_PushReg))
@@ -1874,6 +1882,17 @@ void X86FrameLowering::emitPrologue(MachineFunction &MF,
.addImm(8)
.addUse(X86::NoRegister)
.setMIFlag(MachineInstr::FrameSetup);
+
+ // Switch to an FP-relative CFA before adjusting RSP below.
+ if (NeedsDwarfCFI && !ArgBaseReg.isValid()) {
+ unsigned DwarfFramePtr = TRI->getDwarfRegNum(MachineFramePtr, true);
+ BuildCFI(MBB, MBBI, DL,
+ MCCFIInstruction::cfiDefCfa(nullptr, DwarfFramePtr,
+ -2 * stackGrowth +
+ (int)TailCallArgReserveSize),
+ MachineInstr::FrameSetup);
+ }
+
BuildMI(MBB, MBBI, DL, TII.get(X86::SUB64ri32), X86::RSP)
.addUse(X86::RSP)
.addImm(8)
@@ -1903,7 +1922,7 @@ void X86FrameLowering::emitPrologue(MachineFunction &MF,
BuildCFI(MBB, MBBI, DL,
MCCFIInstruction::createEscape(nullptr, CfaExpr.str()),
MachineInstr::FrameSetup);
- } else {
+ } else if (!X86FI->hasSwiftAsyncContext()) {
// Mark effective beginning of when frame pointer becomes valid.
// Define the current CFA to use the EBP/RBP register.
unsigned DwarfFramePtr = TRI->getDwarfRegNum(MachineFramePtr, true);
diff --git a/llvm/test/CodeGen/X86/swift-async-cfi-prologue.ll b/llvm/test/CodeGen/X86/swift-async-cfi-prologue.ll
new file mode 100644
index 0000000000000..f572595ad2118
--- /dev/null
+++ b/llvm/test/CodeGen/X86/swift-async-cfi-prologue.ll
@@ -0,0 +1,41 @@
+; RUN: llc -mtriple=x86_64-apple-macosx -O0 %s -o - | FileCheck %s --check-prefix=ASM
+; RUN: llc -mtriple=x86_64-apple-macosx -O0 -filetype=obj %s -o - | \
+; RUN: llvm-dwarfdump --eh-frame - | FileCheck %s --check-prefix=UNWIND
+
+; Verify Swift async prologue CFA updates after the context push.
+
+; ASM-LABEL: foo:
+; ASM: pushq %rbp
+; ASM-NEXT: .cfi_def_cfa_offset 16
+; ASM-NEXT: .cfi_offset %rbp, -16
+; ASM-NEXT: pushq %r14
+; ASM-NEXT: .cfi_adjust_cfa_offset 8
+; ASM-NEXT: leaq 8(%rsp), %rbp
+; ASM-NEXT: .cfi_def_cfa %rbp, 16
+; ASM-NOT: .cfi_def_cfa_register
+
+; UNWIND: 0x0: CFA=RSP+8:
+; UNWIND-NEXT: 0x6: CFA=RSP+16:
+; UNWIND-NEXT: 0x8: CFA=RSP+24:
+; UNWIND-NEXT: 0xd: CFA=RBP+16:
+
+define void @foo(ptr swiftasync %ctx) "frame-pointer"="all" {
+ call void asm sideeffect "int3", ""()
+ ret void
+}
+
+; A frame with locals emits a real stack adjustment after the FP is set up.
+; The CFA must already be %rbp-relative before that subq, so no CFI directive
+; appears between the .cfi_def_cfa and the subq.
+; ASM-LABEL: with_locals:
+; ASM: pushq %r14
+; ASM-NEXT: .cfi_adjust_cfa_offset 8
+; ASM-NEXT: leaq 8(%rsp), %rbp
+; ASM-NEXT: .cfi_def_cfa %rbp, 16
+; ASM-NEXT: subq ${{[0-9]+}}, %rsp
+
+define void @with_locals(ptr swiftasync %ctx) "frame-pointer"="all" {
+ %a = alloca [128 x i8]
+ call void asm sideeffect "int3", "r"(ptr %a)
+ ret void
+}
diff --git a/llvm/test/CodeGen/X86/swift-async.ll b/llvm/test/CodeGen/X86/swift-async.ll
index 0017d2af85878..c31cceeb88f17 100644
--- a/llvm/test/CodeGen/X86/swift-async.ll
+++ b/llvm/test/CodeGen/X86/swift-async.ll
@@ -27,9 +27,12 @@ define void @more_csrs(ptr swiftasync %ctx) "frame-pointer"="all" {
; CHECK-LABEL: more_csrs:
; CHECK: btsq $60, %rbp
; CHECK: pushq %rbp
+; CHECK: .cfi_def_cfa_offset 16
; CHECK: .cfi_offset %rbp, -16
; CHECK: pushq %r14
+; CHECK: .cfi_adjust_cfa_offset 8
; CHECK: leaq 8(%rsp), %rbp
+; CHECK: .cfi_def_cfa %rbp, 16
; CHECK: subq $8, %rsp
; CHECK: pushq %r15
; CHECK: .cfi_offset %r15, -40
@@ -52,8 +55,9 @@ define void @locals(ptr swiftasync %ctx) "frame-pointer"="all" {
; CHECK: .cfi_def_cfa_offset 16
; CHECK: .cfi_offset %rbp, -16
; CHECK: pushq %r14
+; CHECK: .cfi_adjust_cfa_offset 8
; CHECK: leaq 8(%rsp), %rbp
-; CHECK: .cfi_def_cfa_register %rbp
+; CHECK: .cfi_def_cfa %rbp, 16
; CHECK: subq $56, %rsp
; CHECK: leaq -48(%rbp), %rdi
@@ -80,6 +84,13 @@ define void @use_input_context(ptr swiftasync %ctx, ptr %ptr) "frame-pointer"="a
define ptr @context_in_func() "frame-pointer"="non-leaf" {
; CHECK-LABEL: context_in_func:
+; CHECK: pushq %rbp
+; CHECK: .cfi_def_cfa_offset 16
+; CHECK: .cfi_offset %rbp, -16
+; CHECK: pushq $0
+; CHECK: .cfi_adjust_cfa_offset 8
+; CHECK: leaq 8(%rsp), %rbp
+; CHECK: .cfi_def_cfa %rbp, 16
; CHECK: leaq -8(%rbp), %rax
; CHECK-32-LABEL: context_in_func
More information about the llvm-commits
mailing list