[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