[llvm] [win][x64] Unwind v2 4/4: Use chaining to split unwind info if needed, and allow dealloc to be elided if setframe + frame pointer is used (PR #159206)
Daniel Paoliello via llvm-commits
llvm-commits at lists.llvm.org
Fri Jan 16 15:37:09 PST 2026
https://github.com/dpaoliello updated https://github.com/llvm/llvm-project/pull/159206
>From b2b48cce669a6a23b8552ea4801b41ccb3396eca Mon Sep 17 00:00:00 2001
From: "Daniel Paoliello (HE/HIM)" <danpao at microsoft.com>
Date: Mon, 15 Dec 2025 17:49:51 -0800
Subject: [PATCH] [win][x64] Unwind v2 4/4: Use chaining to split unwind info
if needed
---
llvm/lib/Target/X86/X86InstrCompiler.td | 6 +
llvm/lib/Target/X86/X86MCInstLower.cpp | 5 +
llvm/lib/Target/X86/X86WinEHUnwindV2.cpp | 176 +++++++++++++-----
.../Inputs/reference_x86_vocab_print.txt | 1 +
.../reference_x86_vocab_wo=0.5_print.txt | 1 +
.../win64-eh-unwindv2-too-many-epilogs.mir | 39 ++--
.../X86/win64-eh-unwindv2-too-many-instr.mir | 91 +++++++++
llvm/test/CodeGen/X86/win64-eh-unwindv2.ll | 105 +++++++++++
8 files changed, 357 insertions(+), 67 deletions(-)
create mode 100644 llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-instr.mir
diff --git a/llvm/lib/Target/X86/X86InstrCompiler.td b/llvm/lib/Target/X86/X86InstrCompiler.td
index 3e07db678809d..f1bf7cf38dfb2 100644
--- a/llvm/lib/Target/X86/X86InstrCompiler.td
+++ b/llvm/lib/Target/X86/X86InstrCompiler.td
@@ -272,6 +272,12 @@ let isPseudo = 1, isMeta = 1, SchedRW = [WriteSystem] in {
"#SEH_UnwindV2Start", []>;
}
+// Chain instructions:
+let isPseudo = 1, isMeta = 1, SchedRW = [WriteSystem] in {
+ def SEH_SplitChained : I<0, Pseudo, (outs), (ins),
+ "#SEH_SplitChained", []>;
+}
+
//===----------------------------------------------------------------------===//
// Pseudo instructions used by KCFI.
//===----------------------------------------------------------------------===//
diff --git a/llvm/lib/Target/X86/X86MCInstLower.cpp b/llvm/lib/Target/X86/X86MCInstLower.cpp
index 2287a921a19c0..6c6131535aae8 100644
--- a/llvm/lib/Target/X86/X86MCInstLower.cpp
+++ b/llvm/lib/Target/X86/X86MCInstLower.cpp
@@ -1801,6 +1801,10 @@ void X86AsmPrinter::EmitSEHInstruction(const MachineInstr *MI) {
OutStreamer->emitWinCFIUnwindVersion(MI->getOperand(0).getImm());
break;
+ case X86::SEH_SplitChained:
+ OutStreamer->emitWinCFISplitChained();
+ break;
+
default:
llvm_unreachable("expected SEH_ instruction");
}
@@ -2529,6 +2533,7 @@ void X86AsmPrinter::emitInstruction(const MachineInstr *MI) {
case X86::SEH_EndEpilogue:
case X86::SEH_UnwindV2Start:
case X86::SEH_UnwindVersion:
+ case X86::SEH_SplitChained:
EmitSEHInstruction(MI);
return;
diff --git a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
index 9bf0abb018c99..30fa597f69557 100644
--- a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
+++ b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
@@ -18,6 +18,7 @@
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/CodeGen/TargetFrameLowering.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/IR/DiagnosticInfo.h"
@@ -32,17 +33,37 @@ STATISTIC(MeetsUnwindV2Criteria,
STATISTIC(FailsUnwindV2Criteria,
"Number of functions that fail Unwind v2 criteria");
-static cl::opt<unsigned> MaximumUnwindCodes(
- "x86-wineh-unwindv2-max-unwind-codes", cl::Hidden,
- cl::desc("Maximum number of unwind codes permitted in each unwind info."),
- cl::init(UINT8_MAX));
+static cl::opt<unsigned>
+ UnwindCodeThreshold("x86-wineh-unwindv2-unwind-codes-threshold", cl::Hidden,
+ cl::desc("Maximum number of unwind codes before "
+ "splitting into a new unwind info."),
+ cl::init(UINT8_MAX));
static cl::opt<unsigned>
ForceMode("x86-wineh-unwindv2-force-mode", cl::Hidden,
cl::desc("Overwrites the Unwind v2 mode for testing purposes."));
+// This threshold is for the *approximate* number of instructions, see the
+// comment in runAnalysisOnFuncOrFunclet for more details.
+static cl::opt<unsigned> InstructionCountThreshold(
+ "x86-wineh-unwindv2-instruction-count-threshold", cl::Hidden,
+ cl::desc("Maximum number of (approximate) instructions before splitting "
+ "into a new unwind info."),
+ cl::init(600));
+
namespace {
+struct EpilogInfo {
+ MachineInstr *UnwindV2StartLocation;
+ unsigned ApproximateInstructionPosition;
+};
+
+struct FrameInfo {
+ unsigned ApproximatePrologCodeCount;
+ unsigned ApproximateInstructionCount;
+ SmallVector<EpilogInfo> EpilogInfos;
+};
+
class X86WinEHUnwindV2 : public MachineFunctionPass {
public:
static char ID;
@@ -57,9 +78,14 @@ class X86WinEHUnwindV2 : public MachineFunctionPass {
private:
/// Rejects the current function due to an internal error within LLVM.
- static bool rejectCurrentFunctionInternalError(const MachineFunction &MF,
- WinX64EHUnwindV2Mode Mode,
- StringRef Reason);
+ static std::nullopt_t rejectCurrentFunctionInternalError(
+ const MachineFunction &MF, WinX64EHUnwindV2Mode Mode, StringRef Reason);
+
+ // Continues running the analysis on the given function or funclet.
+ static std::optional<FrameInfo>
+ runAnalysisOnFuncOrFunclet(MachineFunction &MF,
+ MachineFunction::iterator &Iter,
+ WinX64EHUnwindV2Mode Mode);
};
enum class FunctionState {
@@ -89,14 +115,11 @@ DebugLoc findDebugLoc(const MachineBasicBlock &MBB) {
return DebugLoc::getUnknown();
}
-bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
- WinX64EHUnwindV2Mode Mode =
- ForceMode.getNumOccurrences()
- ? static_cast<WinX64EHUnwindV2Mode>(ForceMode.getValue())
- : MF.getFunction().getParent()->getWinX64EHUnwindV2Mode();
-
- if (Mode == WinX64EHUnwindV2Mode::Disabled)
- return false;
+std::optional<FrameInfo>
+X86WinEHUnwindV2::runAnalysisOnFuncOrFunclet(MachineFunction &MF,
+ MachineFunction::iterator &Iter,
+ WinX64EHUnwindV2Mode Mode) {
+ const TargetFrameLowering &TFL = *MF.getSubtarget().getFrameLowering();
// Current state of processing the function. We'll assume that all functions
// start with a prolog.
@@ -108,17 +131,47 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
bool HasSetFrame = false;
unsigned ApproximatePrologCodeCount = 0;
- // Requested changes.
- SmallVector<MachineInstr *> UnwindV2StartLocations;
+ SmallVector<EpilogInfo> EpilogInfos;
+
+ // Unwind v2 requires that the epilog is no more than 4Kb away from the last
+ // instruction that the current unwind info covers. If we believe that we are
+ // going over that limit then we need to split the unwind info. Ideally we'd
+ // do this at the point where we actually know how far away we are from the
+ // last instruction, but that's not possible here and splitting unwind infos
+ // in MC would be difficult. However, the cost of splitting an unwind info is
+ // fairly cheap (in the other of bytes in the xdata section), so we can
+ // instead use a heuristic based on the number of MachineInstrs to decide when
+ // to split unwind infos, and allow users to tune the threshold if needed.
+ // This is not a perfect solution, but 1) it is cheap to calculate, 2) allows
+ // the common case for small functions or large functions with multiple
+ // returns at the end to have a single unwind info, and 3) allows unwind v2 to
+ // be used in large functions (that would otherwise be rejected) for a small
+ // binary size cost.
+ unsigned ApproximateInstructionCount = 0;
+
+ for (; Iter != MF.end(); ++Iter) {
+ MachineBasicBlock &MBB = *Iter;
+
+ // If we're already been processing a function, then come across a funclet
+ // then break since the funclet will get a fresh frame info.
+ if (MBB.isEHFuncletEntry() && State != FunctionState::InProlog)
+ break;
- for (MachineBasicBlock &MBB : MF) {
// Current epilog information. We assume that epilogs cannot cross basic
// block boundaries.
unsigned PoppedRegCount = 0;
bool HasStackDealloc = false;
+ bool HasSetFrameBack = false;
MachineInstr *UnwindV2StartLocation = nullptr;
for (MachineInstr &MI : MBB) {
+ // This is an *approximation* of the number of instructions that will be
+ // emitted. It is not the actual number of instructions, but that doesn't
+ // matter: see the comment at the declaration of
+ // ApproximateInstructionCount.
+ if (!MI.isPseudo() && !MI.isMetaInstruction())
+ ApproximateInstructionCount++;
+
switch (MI.getOpcode()) {
//
// Prolog handling.
@@ -192,7 +245,8 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
// epilog.
if (!UnwindV2StartLocation)
UnwindV2StartLocation = &MI;
- UnwindV2StartLocations.push_back(UnwindV2StartLocation);
+ EpilogInfos.push_back(
+ {UnwindV2StartLocation, ApproximateInstructionCount});
State = FunctionState::FinishedEpilog;
break;
@@ -214,6 +268,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
MF, Mode,
"Cannot set the frame back after the stack "
"allocation has been deallocated");
+ HasSetFrameBack = true;
} else if (State == FunctionState::FinishedEpilog)
return rejectCurrentFunctionInternalError(
MF, Mode, "Unexpected mov instruction after the epilog");
@@ -251,6 +306,12 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
// registers, then assume it was used to adjust the stack pointer.
HasStackDealloc = true;
} else {
+ // Special case: no explicit stack dealloc is required if SetFrame
+ // was used and the function has a frame pointer.
+ if (PoppedRegCount == 0 && HasStackAlloc && !HasStackDealloc &&
+ HasSetFrameBack && TFL.hasFP(MF))
+ HasStackDealloc = true;
+
// After the stack pointer has been adjusted, the epilog must
// POP each register in reverse order of the PUSHes in the prolog.
PoppedRegCount++;
@@ -305,37 +366,66 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
}
}
- if (UnwindV2StartLocations.empty())
- return false;
+ return FrameInfo{ApproximatePrologCodeCount, ApproximateInstructionCount,
+ EpilogInfos};
+}
- MachineBasicBlock &FirstMBB = MF.front();
- // Assume +1 for the "header" UOP_Epilog that contains the epilog size, and
- // that we won't be able to use the "last epilog at the end of function"
- // optimization.
- if (ApproximatePrologCodeCount + UnwindV2StartLocations.size() + 1 >
- static_cast<unsigned>(MaximumUnwindCodes)) {
- if (Mode == WinX64EHUnwindV2Mode::Required)
- MF.getFunction().getContext().diagnose(DiagnosticInfoGenericWithLoc(
- "Windows x64 Unwind v2 is required, but the function '" +
- MF.getName() +
- "' has too many unwind codes. Try splitting the function or "
- "reducing the number of places where it exits early with a tail "
- "call.",
- MF.getFunction(), findDebugLoc(FirstMBB)));
-
- FailsUnwindV2Criteria++;
+bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
+ WinX64EHUnwindV2Mode Mode =
+ ForceMode.getNumOccurrences()
+ ? static_cast<WinX64EHUnwindV2Mode>(ForceMode.getValue())
+ : MF.getFunction().getParent()->getWinX64EHUnwindV2Mode();
+
+ if (Mode == WinX64EHUnwindV2Mode::Disabled)
return false;
+
+ // Requested changes.
+ SmallVector<FrameInfo> FrameInfos;
+ MachineFunction::iterator Iter = MF.begin();
+ while (Iter != MF.end()) {
+ auto FI = runAnalysisOnFuncOrFunclet(MF, Iter, Mode);
+ if (!FI)
+ return false;
+ if (!FI->EpilogInfos.empty())
+ FrameInfos.push_back(std::move(*FI));
}
+ if (FrameInfos.empty())
+ return false;
+
MeetsUnwindV2Criteria++;
- // Emit the pseudo instruction that marks the start of each epilog.
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
- for (MachineInstr *MI : UnwindV2StartLocations) {
- BuildMI(*MI->getParent(), MI, MI->getDebugLoc(),
- TII->get(X86::SEH_UnwindV2Start));
+ for (auto &FI : FrameInfos) {
+ // Walk the list of epilogs backwards and add new SEH pseudo instructions:
+ // * SEH_UnwindV2Start at the start of each epilog.
+ // * If the current instruction is too far away from where the last unwind
+ // info ended OR there are too many unwind codes in the info, then add
+ // SEH_SplitChained to finish the current info.
+ unsigned LastUnwindInfoEndPosition = FI.ApproximateInstructionCount;
+ unsigned UnwindCodeCount = FI.ApproximatePrologCodeCount + 1;
+ for (auto &Info : llvm::reverse(FI.EpilogInfos)) {
+ MachineBasicBlock &MBB = *Info.UnwindV2StartLocation->getParent();
+ const DebugLoc &DL = Info.UnwindV2StartLocation->getDebugLoc();
+ BuildMI(MBB, Info.UnwindV2StartLocation, DL,
+ TII->get(X86::SEH_UnwindV2Start));
+
+ if ((LastUnwindInfoEndPosition - Info.ApproximateInstructionPosition >=
+ InstructionCountThreshold) ||
+ (UnwindCodeCount >= UnwindCodeThreshold)) {
+ BuildMI(&MBB, DL, TII->get(X86::SEH_SplitChained));
+ BuildMI(&MBB, DL, TII->get(X86::SEH_EndPrologue));
+ LastUnwindInfoEndPosition = Info.ApproximateInstructionPosition;
+ // Doesn't reset to 0, as the prolog unwind codes are now in this info.
+ UnwindCodeCount = FI.ApproximatePrologCodeCount + 1;
+ }
+
+ UnwindCodeCount++;
+ }
}
+
// Note that the function is using Unwind v2.
+ MachineBasicBlock &FirstMBB = MF.front();
BuildMI(FirstMBB, FirstMBB.front(), findDebugLoc(FirstMBB),
TII->get(X86::SEH_UnwindVersion))
.addImm(2);
@@ -343,7 +433,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
return true;
}
-bool X86WinEHUnwindV2::rejectCurrentFunctionInternalError(
+std::nullopt_t X86WinEHUnwindV2::rejectCurrentFunctionInternalError(
const MachineFunction &MF, WinX64EHUnwindV2Mode Mode, StringRef Reason) {
if (Mode == WinX64EHUnwindV2Mode::Required)
reportFatalInternalError("Windows x64 Unwind v2 is required, but LLVM has "
@@ -351,5 +441,5 @@ bool X86WinEHUnwindV2::rejectCurrentFunctionInternalError(
MF.getName() + "': " + Reason);
FailsUnwindV2Criteria++;
- return false;
+ return std::nullopt;
}
diff --git a/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_print.txt b/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_print.txt
index 62e07445ad12e..f41ae173f3299 100644
--- a/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_print.txt
+++ b/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_print.txt
@@ -1598,6 +1598,7 @@ Key: SEH_PushReg: [ 0.00 0.00 ]
Key: SEH_SaveReg: [ 0.00 0.00 ]
Key: SEH_SaveXMM: [ 0.00 0.00 ]
Key: SEH_SetFrame: [ 0.00 0.00 ]
+Key: SEH_SplitChained: [ 0.00 0.00 ]
Key: SEH_StackAlign: [ 0.00 0.00 ]
Key: SEH_StackAlloc: [ 0.00 0.00 ]
Key: SEH_UnwindV: [ 0.00 0.00 ]
diff --git a/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_wo=0.5_print.txt b/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_wo=0.5_print.txt
index 03a3fafc6b801..1a99bcbef6c33 100644
--- a/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_wo=0.5_print.txt
+++ b/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_wo=0.5_print.txt
@@ -1598,6 +1598,7 @@ Key: SEH_PushReg: [ 0.00 0.00 ]
Key: SEH_SaveReg: [ 0.00 0.00 ]
Key: SEH_SaveXMM: [ 0.00 0.00 ]
Key: SEH_SetFrame: [ 0.00 0.00 ]
+Key: SEH_SplitChained: [ 0.00 0.00 ]
Key: SEH_StackAlign: [ 0.00 0.00 ]
Key: SEH_StackAlloc: [ 0.00 0.00 ]
Key: SEH_UnwindV: [ 0.00 0.00 ]
diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-epilogs.mir b/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-epilogs.mir
index 70c87ad87f792..86bd7cb4d8ae4 100644
--- a/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-epilogs.mir
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-epilogs.mir
@@ -1,32 +1,23 @@
-# Require V2 and restrict the number of unwind codes to 8
-# RUN: not llc -mtriple=x86_64-pc-windows-msvc -o - %s \
-# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-max-unwind-codes=8 \
-# RUN: 2>&1 | FileCheck %s -check-prefix=REQUIREV2
-
-# Force best-effort and restrict the number of unwind codes to 8
+# Restrict the number of unwind codes to 8
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %s \
-# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-max-unwind-codes=8 \
-# RUN: -x86-wineh-unwindv2-force-mode=1 | \
-# RUN: FileCheck %s -check-prefix=BESTEFFORT
+# RUN: -x86-wineh-unwindv2-unwind-codes-threshold=8 \
+# RUN: -run-pass=x86-wineh-unwindv2 | FileCheck %s \
+# RUN: -check-prefixes=ALLOWLESS,CHECK
-# Require V2, but allow the default number of unwind codes (255)
+# Allow the default number of unwind codes (255)
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %s \
-# RUN: -run-pass=x86-wineh-unwindv2 | FileCheck %s -check-prefix=ALLOWMORE
-
-# Usually 255 unwind codes are permitted, but we passed an arg to llc to limit
-# it to 8.
-# REQUIREV2: error: example.c:2:1: Windows x64 Unwind v2 is required, but the function 'too_many_epilogs' has too many unwind codes.
-# REQUIREV2-SAME: Try splitting the function or reducing the number of places where it exits early with a tail call.
+# RUN: -run-pass=x86-wineh-unwindv2 | FileCheck %s \
+# RUN: -check-prefixes=ALLOWMORE,CHECK
-# If we force "best effort" mode, then we won't see any errors, but we won't use
-# v2.
-# BESTEFFORT-NOT: SEH_UnwindVersion
-# BESTEFFORT-NOT: SEH_UnwindV2Start
+# CHECK-LABEL: too_many_epilogs
+# CHECK: SEH_UnwindVersion 2
+# CHECK: SEH_UnwindV2Start
-# If we allow more epilogs then too_many_epilogs will compile with v2.
-# ALLOWMORE-LABEL: too_many_epilogs
-# ALLOWMORE: SEH_UnwindVersion 2
-# ALLOWMORE: SEH_UnwindV2Start
+# If we restricted the number of unwind codes, then we expect to use chained
+# infos.
+# ALLOWLESS: SEH_SplitChained
+# ALLOWLESS-NEXT: SEH_EndPrologue
+# ALLOWMORE-NOT: SEH_SplitChained
--- |
define dso_local void @too_many_epilogs() local_unnamed_addr !dbg !9 {
diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-instr.mir b/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-instr.mir
new file mode 100644
index 0000000000000..3e007a7d7a155
--- /dev/null
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-instr.mir
@@ -0,0 +1,91 @@
+# Restrict the number of instructions per info to 4
+# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %s \
+# RUN: -x86-wineh-unwindv2-instruction-count-threshold=4 \
+# RUN: -run-pass=x86-wineh-unwindv2 | FileCheck %s \
+# RUN: -check-prefixes=ALLOWLESS,CHECK
+
+# Allow the default number of instructions per info
+# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %s \
+# RUN: -run-pass=x86-wineh-unwindv2 | FileCheck %s \
+# RUN: -check-prefixes=ALLOWMORE,CHECK
+
+--- |
+ define dso_local void @too_many_instr() local_unnamed_addr !dbg !9 {
+ entry:
+ ret void, !dbg !10
+ }
+
+ !llvm.dbg.cu = !{!0}
+ !llvm.module.flags = !{!2, !3, !4, !5}
+
+ !0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+ !1 = !DIFile(filename: "/app/example.c", directory: "/app")
+ !2 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+ !3 = !{i32 7, !"Dwarf Version", i32 4}
+ !4 = !{i32 2, !"CodeView", i32 1}
+ !5 = !{i32 2, !"Debug Info Version", i32 3}
+ !6 = !DIFile(filename: "example.c", directory: "/app")
+ !7 = !DISubroutineType(types: !8)
+ !8 = !{null}
+ !9 = distinct !DISubprogram(name: "too_many_instr", scope: !6, file: !6, line: 1, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+ !10 = !DILocation(line: 2, column: 1, scope: !9)
+ !11 = !DILocation(line: 3, column: 1, scope: !9)
+ !12 = !DILocation(line: 4, column: 1, scope: !9)
+ !13 = !DILocation(line: 5, column: 1, scope: !9)
+...
+---
+name: too_many_instr
+body: |
+ bb.0.entry:
+ frame-setup SEH_EndPrologue
+ SEH_BeginEpilogue
+ SEH_EndEpilogue
+ RET64 debug-location !10
+ bb.1:
+ $rcx = MOV64rr $rax
+ SEH_BeginEpilogue
+ SEH_EndEpilogue
+ RET64 debug-location !11
+ bb.2:
+ $rcx = MOV64rr $rax
+ SEH_BeginEpilogue
+ SEH_EndEpilogue
+ RET64 debug-location !12
+ bb.3:
+ $rcx = MOV64rr $rax
+ $rcx = MOV64rr $rax
+ $rcx = MOV64rr $rax
+ SEH_BeginEpilogue
+ SEH_EndEpilogue
+ RET64 debug-location !13
+
+...
+
+# CHECK-LABEL: too_many_instr
+# CHECK-LABEL: bb.0.entry:
+# CHECK: SEH_UnwindV2Start
+# CHECK: RET64 debug-location !10
+# bb.1 + bb.2 have enough instructions that bb.0 has its own info.
+# ALLOWLESS-NEXT: SEH_SplitChained
+# ALLOWLESS-NEXT: SEH_EndPrologue
+# ALLOWMORE-NOT: SEH_SplitChained
+
+# CHECK-LABEL: bb.1
+# CHECK: SEH_UnwindV2Start
+# CHECK: RET64 debug-location !DILocation(line: 3, column: 1, scope: !6)
+# bb.2 doesn't fill the current info, so bb.1 gets added as well.
+# CHECK-NOT: SEH_SplitChained
+
+# CHECK-LABEL: bb.2
+# CHECK: SEH_UnwindV2Start
+# CHECK: RET64 debug-location !DILocation(line: 4, column: 1, scope: !6)
+# bb.3 has enough instructions by itself that bb.2 needs to split.
+# ALLOWLESS-NEXT: SEH_SplitChained
+# ALLOWLESS-NEXT: SEH_EndPrologue
+# ALLOWMORE-NOT: SEH_SplitChained
+
+# CHECK-LABEL: bb.3
+# CHECK: SEH_UnwindV2Start
+# CHECK: RET64 debug-location !DILocation(line: 5, column: 1, scope: !6)
+# Never split at the end.
+# CHECK-NOT: SEH_SplitChained
diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
index 0d92d044e1b94..747f6c2e1bb3b 100644
--- a/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
@@ -1,4 +1,7 @@
; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s
+; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s \
+; RUN: -x86-wineh-unwindv2-unwind-codes-threshold=8 | FileCheck %s \
+; RUN: -check-prefixes=ALLOWLESS,CHECK
define dso_local void @no_epilog() local_unnamed_addr {
entry:
@@ -204,11 +207,113 @@ entry:
; CHECK: int3
; CHECK-NEXT: .seh_endproc
+define dso_local i32 @has_funclet(i32 %x) local_unnamed_addr personality ptr @__C_specific_handler {
+entry:
+ %call = invoke i32 @c(i32 %x)
+ to label %call.block.1 unwind label %cleanup
+
+call.block.1:
+ %call1 = invoke i32 @c(i32 %x)
+ to label %call.block.2 unwind label %cleanup
+
+call.block.2:
+ %add = add nsw i32 %call1, %call
+ %call2 = invoke i32 @c(i32 %x)
+ to label %call.block.3 unwind label %cleanup
+
+call.block.3:
+ %call3 = invoke i32 @c(i32 %call2)
+ to label %call.block.4 unwind label %cleanup
+
+call.block.4:
+ %add4 = add nsw i32 %add, %call3
+ ret i32 %add4
+
+cleanup:
+ %cleanup_token = cleanuppad within none []
+ call fastcc void @is_funclet(i32 %x) #18 [ "funclet"(token %cleanup_token) ]
+ cleanupret from %cleanup_token unwind to caller
+}
+
+define internal fastcc void @is_funclet(i32 %x) local_unnamed_addr {
+entry:
+ %y = alloca i32, i32 %x
+ ret void
+}
+
+; CHECK-LABEL: has_funclet:
+; CHECK: .seh_pushreg %rbp
+; CHECK: .seh_pushreg %rsi
+; CHECK: .seh_pushreg %rdi
+; CHECK: .seh_stackalloc 48
+; CHECK: .seh_setframe %rbp, 48
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_unwindv2start
+; CHECK-NEXT: popq %rdi
+; CHECK-NEXT: popq %rsi
+; CHECK-NEXT: popq %rbp
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: retq
+; ALLOWLESS-NEXT: .seh_splitchained
+; ALLOWLESS-NEXT: .seh_endprologue
+; CHECK-NEXT: .seh_handlerdata
+; CHECK: .text
+; CHECK-NEXT: .seh_endproc
+; CHECK-LABEL: "?dtor$5@?0?has_funclet at 4HA":
+; CHECK: .seh_pushreg %rbp
+; CHECK: .seh_pushreg %rsi
+; CHECK: .seh_pushreg %rdi
+; CHECK: .seh_stackalloc 32
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_unwindv2start
+; CHECK-NEXT: popq %rdi
+; CHECK-NEXT: popq %rsi
+; CHECK-NEXT: popq %rbp
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: retq
+; CHECK: .seh_handlerdata
+; CHECK-NEXT: .text
+; CHECK-NEXT: .seh_endproc
+; CHECK-LABEL: is_funclet:
+; CHECK: .seh_unwindversion 2
+; CHECK: .seh_pushreg %rbp
+; CHECK: .seh_setframe %rbp, 0
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_unwindv2start
+; CHECK: .seh_endepilogue
+; CHECK-NEXT: retq
+; CHECK-NEXT: .seh_endproc
+
+define dso_local void @set_frame_and_alloc(i32 %x) local_unnamed_addr {
+ %y = alloca i32, i32 %x
+ %z = alloca ptr, align 8
+ ret void
+}
+
+; CHECK-LABEL: set_frame_and_alloc:
+; CHECK: .seh_unwindversion 2
+; CHECK: .seh_pushreg %rbp
+; CHECK: .seh_stackalloc 16
+; CHECK: .seh_setframe %rbp, 16
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK-NEXT: movq %rbp, %rsp
+; CHECK-NEXT: .seh_unwindv2start
+; CHECK-NEXT: popq %rbp
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: retq
+; CHECK-NEXT: .seh_endproc
+
declare i64 @llvm.x86.flags.read.u64()
declare void @a() local_unnamed_addr
declare i32 @b() local_unnamed_addr
declare i32 @c(i32) local_unnamed_addr
declare void @d() local_unnamed_addr #1
+declare dso_local i32 @__C_specific_handler(...)
+
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
More information about the llvm-commits
mailing list