[llvm] [win][x64] Unwind v2 4/4: Use chaining to split unwind info if needed (PR #159206)

Daniel Paoliello via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 2 12:50:25 PDT 2025


https://github.com/dpaoliello updated https://github.com/llvm/llvm-project/pull/159206

>From 7ab29e32e2ba80609272c2086f355f748d3529a0 Mon Sep 17 00:00:00 2001
From: Daniel Paoliello <danpao at microsoft.com>
Date: Fri, 12 Sep 2025 13:48:54 -0700
Subject: [PATCH] [win][x64] Unwind v2 4/4: Use chaining to split unwind info
 if needed

---
 llvm/include/llvm/MC/MCStreamer.h             |   3 +-
 llvm/include/llvm/MC/MCWinEH.h                |   6 +-
 llvm/lib/MC/MCAsmStreamer.cpp                 |  16 +--
 llvm/lib/MC/MCParser/COFFAsmParser.cpp        |  19 +--
 llvm/lib/MC/MCStreamer.cpp                    |  50 +++----
 llvm/lib/MC/MCWin64EH.cpp                     |  38 ++++--
 llvm/lib/Target/X86/X86InstrCompiler.td       |   6 +
 llvm/lib/Target/X86/X86MCInstLower.cpp        |   5 +
 llvm/lib/Target/X86/X86WinEHUnwindV2.cpp      |  78 ++++++-----
 .../win64-eh-unwindv2-too-many-epilogs.mir    |  38 ++----
 .../X86/win64-eh-unwindv2-too-many-instr.mir  |  89 +++++++++++++
 llvm/test/MC/AsmParser/directive_seh.s        |   6 +-
 llvm/test/MC/AsmParser/seh-directive-errors.s |   2 +
 llvm/test/MC/COFF/seh-unwindv2.s              | 123 ++++++++++++++++++
 llvm/test/MC/COFF/seh.s                       |   3 +-
 .../Inputs/win64-unwind.exe.coff-x86_64.asm   |   3 +-
 16 files changed, 355 insertions(+), 130 deletions(-)
 create mode 100644 llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-instr.mir

diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h
index 79c715e3820a6..2a0095c9b7613 100644
--- a/llvm/include/llvm/MC/MCStreamer.h
+++ b/llvm/include/llvm/MC/MCStreamer.h
@@ -1024,8 +1024,7 @@ class LLVM_ABI MCStreamer {
   /// for the frame.  We cannot use the End marker, as it is not set at the
   /// point of emitting .xdata, in order to indicate that the frame is active.
   virtual void emitWinCFIFuncletOrFuncEnd(SMLoc Loc = SMLoc());
-  virtual void emitWinCFIStartChained(SMLoc Loc = SMLoc());
-  virtual void emitWinCFIEndChained(SMLoc Loc = SMLoc());
+  virtual void emitWinCFISplitChained(SMLoc Loc = SMLoc());
   virtual void emitWinCFIPushReg(MCRegister Register, SMLoc Loc = SMLoc());
   virtual void emitWinCFISetFrame(MCRegister Register, unsigned Offset,
                                   SMLoc Loc = SMLoc());
diff --git a/llvm/include/llvm/MC/MCWinEH.h b/llvm/include/llvm/MC/MCWinEH.h
index 1bbfb9f59bc4c..1118f5b1def97 100644
--- a/llvm/include/llvm/MC/MCWinEH.h
+++ b/llvm/include/llvm/MC/MCWinEH.h
@@ -59,7 +59,7 @@ struct FrameInfo {
   uint8_t Version = DefaultVersion;
 
   int LastFrameInst = -1;
-  const FrameInfo *ChainedParent = nullptr;
+  FrameInfo *ChainedParent = nullptr;
   std::vector<Instruction> Instructions;
   struct Epilog {
     std::vector<Instruction> Instructions;
@@ -90,9 +90,9 @@ struct FrameInfo {
   FrameInfo(const MCSymbol *Function, const MCSymbol *BeginFuncEHLabel)
       : Begin(BeginFuncEHLabel), Function(Function) {}
   FrameInfo(const MCSymbol *Function, const MCSymbol *BeginFuncEHLabel,
-            const FrameInfo *ChainedParent)
+            FrameInfo *ChainedParent)
       : Begin(BeginFuncEHLabel), Function(Function),
-        ChainedParent(ChainedParent) {}
+        Version(ChainedParent->Version), ChainedParent(ChainedParent) {}
 
   bool empty() const {
     if (!Instructions.empty())
diff --git a/llvm/lib/MC/MCAsmStreamer.cpp b/llvm/lib/MC/MCAsmStreamer.cpp
index be8c022f39ad1..bee7badbe0bc9 100644
--- a/llvm/lib/MC/MCAsmStreamer.cpp
+++ b/llvm/lib/MC/MCAsmStreamer.cpp
@@ -375,8 +375,7 @@ class MCAsmStreamer final : public MCStreamer {
   void emitWinCFIStartProc(const MCSymbol *Symbol, SMLoc Loc) override;
   void emitWinCFIEndProc(SMLoc Loc) override;
   void emitWinCFIFuncletOrFuncEnd(SMLoc Loc) override;
-  void emitWinCFIStartChained(SMLoc Loc) override;
-  void emitWinCFIEndChained(SMLoc Loc) override;
+  void emitWinCFISplitChained(SMLoc Loc) override;
   void emitWinCFIPushReg(MCRegister Register, SMLoc Loc) override;
   void emitWinCFISetFrame(MCRegister Register, unsigned Offset,
                           SMLoc Loc) override;
@@ -2175,17 +2174,10 @@ void MCAsmStreamer::emitWinCFIFuncletOrFuncEnd(SMLoc Loc) {
   EmitEOL();
 }
 
-void MCAsmStreamer::emitWinCFIStartChained(SMLoc Loc) {
-  MCStreamer::emitWinCFIStartChained(Loc);
+void MCAsmStreamer::emitWinCFISplitChained(SMLoc Loc) {
+  MCStreamer::emitWinCFISplitChained(Loc);
 
-  OS << "\t.seh_startchained";
-  EmitEOL();
-}
-
-void MCAsmStreamer::emitWinCFIEndChained(SMLoc Loc) {
-  MCStreamer::emitWinCFIEndChained(Loc);
-
-  OS << "\t.seh_endchained";
+  OS << "\t.seh_splitchained";
   EmitEOL();
 }
 
diff --git a/llvm/lib/MC/MCParser/COFFAsmParser.cpp b/llvm/lib/MC/MCParser/COFFAsmParser.cpp
index 5dd79946d8779..b795eca73cce5 100644
--- a/llvm/lib/MC/MCParser/COFFAsmParser.cpp
+++ b/llvm/lib/MC/MCParser/COFFAsmParser.cpp
@@ -80,10 +80,8 @@ class COFFAsmParser : public MCAsmParserExtension {
         ".seh_endproc");
     addDirectiveHandler<&COFFAsmParser::parseSEHDirectiveEndFuncletOrFunc>(
         ".seh_endfunclet");
-    addDirectiveHandler<&COFFAsmParser::parseSEHDirectiveStartChained>(
-        ".seh_startchained");
-    addDirectiveHandler<&COFFAsmParser::parseSEHDirectiveEndChained>(
-        ".seh_endchained");
+    addDirectiveHandler<&COFFAsmParser::parseSEHDirectiveSplitChained>(
+        ".seh_splitchained");
     addDirectiveHandler<&COFFAsmParser::parseSEHDirectiveHandler>(
         ".seh_handler");
     addDirectiveHandler<&COFFAsmParser::parseSEHDirectiveHandlerData>(
@@ -143,8 +141,7 @@ class COFFAsmParser : public MCAsmParserExtension {
   bool parseSEHDirectiveStartProc(StringRef, SMLoc);
   bool parseSEHDirectiveEndProc(StringRef, SMLoc);
   bool parseSEHDirectiveEndFuncletOrFunc(StringRef, SMLoc);
-  bool parseSEHDirectiveStartChained(StringRef, SMLoc);
-  bool parseSEHDirectiveEndChained(StringRef, SMLoc);
+  bool parseSEHDirectiveSplitChained(StringRef, SMLoc);
   bool parseSEHDirectiveHandler(StringRef, SMLoc);
   bool parseSEHDirectiveHandlerData(StringRef, SMLoc);
   bool parseSEHDirectiveAllocStack(StringRef, SMLoc);
@@ -685,15 +682,9 @@ bool COFFAsmParser::parseSEHDirectiveEndFuncletOrFunc(StringRef, SMLoc Loc) {
   return false;
 }
 
-bool COFFAsmParser::parseSEHDirectiveStartChained(StringRef, SMLoc Loc) {
+bool COFFAsmParser::parseSEHDirectiveSplitChained(StringRef, SMLoc Loc) {
   Lex();
-  getStreamer().emitWinCFIStartChained(Loc);
-  return false;
-}
-
-bool COFFAsmParser::parseSEHDirectiveEndChained(StringRef, SMLoc Loc) {
-  Lex();
-  getStreamer().emitWinCFIEndChained(Loc);
+  getStreamer().emitWinCFISplitChained(Loc);
   return false;
 }
 
diff --git a/llvm/lib/MC/MCStreamer.cpp b/llvm/lib/MC/MCStreamer.cpp
index bc7398120096e..0773a15b0efc7 100644
--- a/llvm/lib/MC/MCStreamer.cpp
+++ b/llvm/lib/MC/MCStreamer.cpp
@@ -749,13 +749,14 @@ void MCStreamer::emitWinCFIEndProc(SMLoc Loc) {
   WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
   if (!CurFrame)
     return;
-  if (CurFrame->ChainedParent)
-    getContext().reportError(Loc, "Not all chained regions terminated!");
 
   MCSymbol *Label = emitCFILabel();
   CurFrame->End = Label;
-  if (!CurFrame->FuncletOrFuncEnd)
-    CurFrame->FuncletOrFuncEnd = CurFrame->End;
+  const MCSymbol **FuncletOrFuncEndPtr =
+      CurFrame->ChainedParent ? &CurFrame->ChainedParent->FuncletOrFuncEnd
+                              : &CurFrame->FuncletOrFuncEnd;
+  if (!*FuncletOrFuncEndPtr)
+    *FuncletOrFuncEndPtr = CurFrame->End;
 
   for (size_t I = CurrentProcWinFrameInfoStartIndex, E = WinFrameInfos.size();
        I != E; ++I)
@@ -767,38 +768,38 @@ void MCStreamer::emitWinCFIFuncletOrFuncEnd(SMLoc Loc) {
   WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
   if (!CurFrame)
     return;
-  if (CurFrame->ChainedParent)
-    getContext().reportError(Loc, "Not all chained regions terminated!");
 
   MCSymbol *Label = emitCFILabel();
-  CurFrame->FuncletOrFuncEnd = Label;
+  const MCSymbol **FuncletOrFuncEndPtr =
+      CurFrame->ChainedParent ? &CurFrame->ChainedParent->FuncletOrFuncEnd
+                              : &CurFrame->FuncletOrFuncEnd;
+  *FuncletOrFuncEndPtr = Label;
 }
 
-void MCStreamer::emitWinCFIStartChained(SMLoc Loc) {
+void MCStreamer::emitWinCFISplitChained(SMLoc Loc) {
   WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
   if (!CurFrame)
     return;
 
-  MCSymbol *StartProc = emitCFILabel();
-
-  WinFrameInfos.emplace_back(std::make_unique<WinEH::FrameInfo>(
-      CurFrame->Function, StartProc, CurFrame));
-  CurrentWinFrameInfo = WinFrameInfos.back().get();
-  CurrentWinFrameInfo->TextSection = getCurrentSectionOnly();
-}
-
-void MCStreamer::emitWinCFIEndChained(SMLoc Loc) {
-  WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
-  if (!CurFrame)
-    return;
-  if (!CurFrame->ChainedParent)
+  if (!CurFrame->PrologEnd && !CurFrame->ChainedParent)
     return getContext().reportError(
-        Loc, "End of a chained region outside a chained region!");
+        Loc, "can't split into a new chained region (.seh_splitchained) in the "
+             "middle of a prolog in " +
+                 CurFrame->Function->getName());
 
   MCSymbol *Label = emitCFILabel();
 
+  // Complete the current frame before starting a new, chained one.
   CurFrame->End = Label;
-  CurrentWinFrameInfo = const_cast<WinEH::FrameInfo *>(CurFrame->ChainedParent);
+
+  // All chained frames point to the same parent.
+  WinEH::FrameInfo *ChainedParent =
+      CurFrame->ChainedParent ? CurFrame->ChainedParent : CurFrame;
+
+  WinFrameInfos.emplace_back(std::make_unique<WinEH::FrameInfo>(
+      CurFrame->Function, Label, ChainedParent));
+  CurrentWinFrameInfo = WinFrameInfos.back().get();
+  CurrentWinFrameInfo->TextSection = getCurrentSectionOnly();
 }
 
 void MCStreamer::emitWinEHHandler(const MCSymbol *Sym, bool Unwind, bool Except,
@@ -994,7 +995,8 @@ void MCStreamer::emitWinCFIBeginEpilogue(SMLoc Loc) {
   if (!CurFrame)
     return;
 
-  if (!CurFrame->PrologEnd)
+  // Chained unwinds aren't guaranteed to have a prolog.
+  if (!CurFrame->PrologEnd && !CurFrame->ChainedParent)
     return getContext().reportError(
         Loc, "starting epilogue (.seh_startepilogue) before prologue has ended "
              "(.seh_endprologue) in " +
diff --git a/llvm/lib/MC/MCWin64EH.cpp b/llvm/lib/MC/MCWin64EH.cpp
index 8111ccb8bc69c..75271b08d3252 100644
--- a/llvm/lib/MC/MCWin64EH.cpp
+++ b/llvm/lib/MC/MCWin64EH.cpp
@@ -22,8 +22,7 @@ class MCSection;
 
 /// MCExpr that represents the epilog unwind code in an unwind table.
 class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {
-  const MCSymbol *Function;
-  const MCSymbol *FunctionEnd;
+  const WinEH::FrameInfo &FrameInfo;
   const MCSymbol *UnwindV2Start;
   const MCSymbol *EpilogEnd;
   uint8_t EpilogSize;
@@ -32,9 +31,11 @@ class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {
   MCUnwindV2EpilogTargetExpr(const WinEH::FrameInfo &FrameInfo,
                              const WinEH::FrameInfo::Epilog &Epilog,
                              uint8_t EpilogSize_)
-      : Function(FrameInfo.Function), FunctionEnd(FrameInfo.FuncletOrFuncEnd),
-        UnwindV2Start(Epilog.UnwindV2Start), EpilogEnd(Epilog.End),
-        EpilogSize(EpilogSize_), Loc(Epilog.Loc) {}
+      : FrameInfo(FrameInfo), UnwindV2Start(Epilog.UnwindV2Start),
+        EpilogEnd(Epilog.End), EpilogSize(EpilogSize_), Loc(Epilog.Loc) {
+    assert(UnwindV2Start && "Epilog must have a start");
+    assert(EpilogEnd && "Epilog must have an end");
+  }
 
 public:
   static MCUnwindV2EpilogTargetExpr *
@@ -250,6 +251,10 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
     // so, although there are terminators that are large than 1 byte, the
     // starting address of the terminator instruction will always be considered
     // inside the epilog).
+    assert(
+        LastEpilog.UnwindV2Start &&
+        "If unwind v2 is enabled, epilog must have a unwind v2 start marker");
+    assert(LastEpilog.End && "Epilog must have an end");
     auto MaybeSize = GetOptionalAbsDifference(
         OS->getAssembler(), LastEpilog.End, LastEpilog.UnwindV2Start);
     if (!MaybeSize) {
@@ -270,9 +275,14 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
     // If the last epilog is at the end of the function, we can use a special
     // encoding for it. Because of our +1 trick for the size, this will only
     // work where that final terminator instruction is 1 byte long.
-    auto LastEpilogToFuncEnd = GetOptionalAbsDifference(
-        OS->getAssembler(), info->FuncletOrFuncEnd, LastEpilog.UnwindV2Start);
-    LastEpilogIsAtEnd = (LastEpilogToFuncEnd == EpilogSize);
+    // NOTE: At the point where the unwind info is emitted, the function may not
+    // have ended yet (e.g., if there is EH Handler Data), so assume that we
+    // aren't at the end (since we can't calculate it).
+    if (info->End) {
+      auto LastEpilogToFuncEnd = GetOptionalAbsDifference(
+          OS->getAssembler(), info->End, LastEpilog.UnwindV2Start);
+      LastEpilogIsAtEnd = (LastEpilogToFuncEnd == EpilogSize);
+    }
 
     // If we have an odd number of epilog codes, we need to add a padding code.
     size_t numEpilogCodes =
@@ -384,28 +394,28 @@ bool MCUnwindV2EpilogTargetExpr::evaluateAsRelocatableImpl(
     MCValue &Res, const MCAssembler *Asm) const {
   // Calculate the offset to this epilog, and validate it's within the allowed
   // range.
-  auto Offset = GetOptionalAbsDifference(*Asm, FunctionEnd, UnwindV2Start);
+  auto Offset = GetOptionalAbsDifference(*Asm, FrameInfo.End, UnwindV2Start);
   if (!Offset) {
     Asm->getContext().reportError(
         Loc, "Failed to evaluate epilog offset for Unwind v2 in " +
-                 Function->getName());
+                 FrameInfo.Function->getName());
     return false;
   }
   assert(*Offset > 0);
   constexpr uint16_t MaxEpilogOffset = 0x0fff;
   if (*Offset > MaxEpilogOffset) {
     Asm->getContext().reportError(
-        Loc,
-        "Epilog offset is too large for Unwind v2 in " + Function->getName());
+        Loc, "Epilog offset is too large for Unwind v2 in " +
+                 FrameInfo.Function->getName());
     return false;
   }
 
-  // Sanity check that all epilogs are the same size.
+  // Validate that all epilogs are the same size.
   auto Size = GetOptionalAbsDifference(*Asm, EpilogEnd, UnwindV2Start);
   if (Size != (EpilogSize - 1)) {
     Asm->getContext().reportError(
         Loc, "Size of this epilog does not match size of last epilog in " +
-                 Function->getName());
+                 FrameInfo.Function->getName());
     return false;
   }
 
diff --git a/llvm/lib/Target/X86/X86InstrCompiler.td b/llvm/lib/Target/X86/X86InstrCompiler.td
index 0fd44b74fd449..7d58fa5652048 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 481a9be8374ab..46bd8e0332650 100644
--- a/llvm/lib/Target/X86/X86MCInstLower.cpp
+++ b/llvm/lib/Target/X86/X86MCInstLower.cpp
@@ -1795,6 +1795,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");
   }
@@ -2526,6 +2530,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..123f3b0f8cd82 100644
--- a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
+++ b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
@@ -32,15 +32,22 @@ 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."));
 
+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(1000));
+
 namespace {
 
 class X86WinEHUnwindV2 : public MachineFunctionPass {
@@ -69,6 +76,11 @@ enum class FunctionState {
   FinishedEpilog,
 };
 
+struct EpilogInfo {
+  MachineInstr *UnwindV2StartLocation;
+  unsigned ApproximateInstructionPosition;
+};
+
 } // end anonymous namespace
 
 char X86WinEHUnwindV2::ID = 0;
@@ -109,7 +121,9 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
   unsigned ApproximatePrologCodeCount = 0;
 
   // Requested changes.
-  SmallVector<MachineInstr *> UnwindV2StartLocations;
+  SmallVector<EpilogInfo> EpilogInfos;
+
+  unsigned ApproximateInstructionCount = 0;
 
   for (MachineBasicBlock &MBB : MF) {
     // Current epilog information. We assume that epilogs cannot cross basic
@@ -119,6 +133,9 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
     MachineInstr *UnwindV2StartLocation = nullptr;
 
     for (MachineInstr &MI : MBB) {
+      if (!MI.isPseudo() && !MI.isMetaInstruction())
+        ApproximateInstructionCount++;
+
       switch (MI.getOpcode()) {
       //
       // Prolog handling.
@@ -192,7 +209,8 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
         // epilog.
         if (!UnwindV2StartLocation)
           UnwindV2StartLocation = &MI;
-        UnwindV2StartLocations.push_back(UnwindV2StartLocation);
+        EpilogInfos.push_back(
+            {UnwindV2StartLocation, ApproximateInstructionCount});
         State = FunctionState::FinishedEpilog;
         break;
 
@@ -305,37 +323,39 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
     }
   }
 
-  if (UnwindV2StartLocations.empty())
-    return false;
-
-  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++;
+  if (EpilogInfos.empty())
     return false;
-  }
 
   MeetsUnwindV2Criteria++;
 
-  // Emit the pseudo instruction that marks the start of each epilog.
+  // 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 = ApproximateInstructionCount;
+  unsigned UnwindCodeCount = ApproximatePrologCodeCount + 1;
   const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
-  for (MachineInstr *MI : UnwindV2StartLocations) {
-    BuildMI(*MI->getParent(), MI, MI->getDebugLoc(),
+  for (auto &Info : llvm::reverse(EpilogInfos)) {
+    MachineBasicBlock &MBB = *Info.UnwindV2StartLocation->getParent();
+    BuildMI(MBB, Info.UnwindV2StartLocation,
+            Info.UnwindV2StartLocation->getDebugLoc(),
             TII->get(X86::SEH_UnwindV2Start));
+
+    if ((LastUnwindInfoEndPosition - Info.ApproximateInstructionPosition >=
+         InstructionCountThreshold) ||
+        (UnwindCodeCount >= UnwindCodeThreshold)) {
+      BuildMI(&MBB, Info.UnwindV2StartLocation->getDebugLoc(),
+              TII->get(X86::SEH_SplitChained));
+      LastUnwindInfoEndPosition = Info.ApproximateInstructionPosition;
+      // Doesn't reset to 0, as the prolog unwind codes are now in this info.
+      UnwindCodeCount = 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);
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..ac54ec9f0652d 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,22 @@
-# 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
+# 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..5ffe7b48ba1ff
--- /dev/null
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-instr.mir
@@ -0,0 +1,89 @@
+# 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
+# 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
+# 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/MC/AsmParser/directive_seh.s b/llvm/test/MC/AsmParser/directive_seh.s
index 8eb50ed996dd3..0344e7ea3ad1f 100644
--- a/llvm/test/MC/AsmParser/directive_seh.s
+++ b/llvm/test/MC/AsmParser/directive_seh.s
@@ -42,13 +42,11 @@ func:
 # CHECK: .seh_handlerdata
     .long 0
     .text
-    .seh_startchained
     .seh_endprologue
-    .seh_endchained
+    .seh_splitchained
 # CHECK: .text
-# CHECK: .seh_startchained
 # CHECK: .seh_endprologue
-# CHECK: .seh_endchained
+# CHECK: .seh_splitchained
     .seh_startepilogue
 # CHECK: .seh_startepilogue
     lea (%rbx), %rsp
diff --git a/llvm/test/MC/AsmParser/seh-directive-errors.s b/llvm/test/MC/AsmParser/seh-directive-errors.s
index d9dfe4b4182b9..5ed477b16c7f2 100644
--- a/llvm/test/MC/AsmParser/seh-directive-errors.s
+++ b/llvm/test/MC/AsmParser/seh-directive-errors.s
@@ -42,6 +42,8 @@ f:                                      # @f
 	.seh_stackalloc 32
 	.seh_startepilogue
 	# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: starting epilogue (.seh_startepilogue) before prologue has ended (.seh_endprologue) in f
+	.seh_splitchained
+	# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: can't split into a new chained region (.seh_splitchained) in the middle of a prolog in f
 	.seh_endprologue
 	nop
 	.seh_endepilogue
diff --git a/llvm/test/MC/COFF/seh-unwindv2.s b/llvm/test/MC/COFF/seh-unwindv2.s
index a746a5ffa5e5b..4789c5b41f00c 100644
--- a/llvm/test/MC/COFF/seh-unwindv2.s
+++ b/llvm/test/MC/COFF/seh-unwindv2.s
@@ -152,3 +152,126 @@ mismatched_terminators:
 // CHECK-NEXT:       0x04: ALLOC_SMALL size=40
 // CHECK-NEXT:     ]
 // CHECK-NEXT:   }
+
+chained:
+    .seh_proc chained
+    .seh_unwindversion 2
+    subq    $40, %rsp
+    .seh_stackalloc 40
+    .seh_endprologue
+    callq   c
+    testl   %eax, %eax
+    jle     .L_ELSE_3
+    movl    %eax, %ecx
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    jmp     c
+    .seh_splitchained
+.L_ELSE_3:
+    nop
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    jmp     b
+    .seh_endproc
+
+// CHECK:         RuntimeFunction {
+// CHECK-NEXT:    StartAddress: chained (0x30)
+// CHECK-NEXT:    EndAddress: chained [[EndDisp:\+0x[A-F0-9]+]] (0x34)
+// CHECK-NEXT:    UnwindInfoAddress: .xdata [[InfoDisp:\+0x[A-F0-9]+]] (0x38)
+// CHECK-NEXT:    UnwindInfo {
+// CHECK-NEXT:      Version: 2
+// CHECK-NEXT:      Flags [ (0x0)
+// CHECK-NEXT:      ]
+// CHECK-NEXT:      PrologSize: 4
+// CHECK-NEXT:      FrameRegister: -
+// CHECK-NEXT:      FrameOffset: -
+// CHECK-NEXT:      UnwindCodeCount: 3
+// CHECK-NEXT:      UnwindCodes [
+// CHECK-NEXT:        0x01: EPILOG atend=no, length=0x1
+// CHECK-NEXT:        0x05: EPILOG offset=0x5
+// CHECK-NEXT:        0x04: ALLOC_SMALL size=40
+// CHECK-NEXT:      ]
+// CHECK-NEXT:    }
+// CHECK-NEXT:  }
+// CHECK-NEXT:  RuntimeFunction {
+// CHECK-NEXT:    StartAddress: chained [[EndDisp]] (0x3C)
+// CHECK-NEXT:    EndAddress: chained +0x22 (0x40)
+// CHECK-NEXT:    UnwindInfoAddress: .xdata +0x40 (0x44)
+// CHECK-NEXT:    UnwindInfo {
+// CHECK-NEXT:      Version: 2
+// CHECK-NEXT:      Flags [ (0x4)
+// CHECK-NEXT:        ChainInfo (0x4)
+// CHECK-NEXT:      ]
+// CHECK-NEXT:      PrologSize: 0
+// CHECK-NEXT:      FrameRegister: -
+// CHECK-NEXT:      FrameOffset: -
+// CHECK-NEXT:      UnwindCodeCount: 2
+// CHECK-NEXT:      UnwindCodes [
+// CHECK-NEXT:        0x01: EPILOG atend=no, length=0x1
+// CHECK-NEXT:        0x05: EPILOG offset=0x5
+// CHECK-NEXT:      ]
+// CHECK-NEXT:      Chained {
+// CHECK-NEXT:        StartAddress: chained (0x48)
+// CHECK-NEXT:        EndAddress: chained [[EndDisp]] (0x4C)
+// CHECK-NEXT:        UnwindInfoAddress: .xdata [[InfoDisp]] (0x50)
+// CHECK-NEXT:      }
+// CHECK-NEXT:    }
+// CHECK-NEXT:  }
+
+has_ex_handler_data:
+    .seh_proc has_ex_handler_data
+    .seh_handler __C_specific_handler, @unwind, @except
+    .seh_unwindversion 2
+    pushq   %rbp
+    .seh_pushreg %rbp
+    subq    $32, %rsp
+    .seh_stackalloc 32
+    leaq    32(%rsp), %rbp
+    .seh_setframe %rbp, 32
+    .seh_endprologue
+.has_ex_handler_data_callsite:
+    callq   *__imp_callme(%rip)
+    nop
+.has_ex_handler_data_finish:
+    .seh_startepilogue
+    addq    $32, %rsp
+    .seh_unwindv2start
+    popq    %rbp
+    .seh_endepilogue
+    retq
+.has_ex_handler_data_handler:
+    jmp     .has_ex_handler_data_finish
+    .seh_handlerdata
+    .long   1  # Number of call sites
+    .long   .callsite at IMGREL                    # LabelStart
+    .long   .has_ex_handler_data_finish at IMGREL  # LabelEnd
+    .long   1                                   # CatchAll
+    .long   .has_ex_handler_data_handler at IMGREL # ExceptionHandler
+    .text
+    .seh_endproc
+// CHECK-LABEL:  StartAddress: has_ex_handler_data
+// CHECK-NEXT:   EndAddress: .has_ex_handler_data_handler +0x2
+// CHECK-NEXT:   UnwindInfoAddress: .xdata +0x54
+// CHECK-NEXT:   UnwindInfo {
+// CHECK-NEXT:     Version: 2
+// CHECK-NEXT:     Flags [ (0x3)
+// CHECK-NEXT:      ExceptionHandler (0x1)
+// CHECK-NEXT:      TerminateHandler (0x2)
+// CHECK-NEXT:     ]
+// CHECK-NEXT:     PrologSize: 10
+// CHECK-NEXT:     FrameRegister: RBP
+// CHECK-NEXT:     FrameOffset: 0x2
+// CHECK-NEXT:     UnwindCodeCount: 5
+// CHECK-NEXT:     UnwindCodes [
+// CHECK-NEXT:       0x02: EPILOG atend=no, length=0x2
+// CHECK-NEXT:       0x04: EPILOG offset=0x4
+// CHECK-NEXT:       0x0A: SET_FPREG reg=RBP, offset=0x20
+// CHECK-NEXT:       0x05: ALLOC_SMALL size=32
+// CHECK-NEXT:       0x01: PUSH_NONVOL reg=RBP
+// CHECK-NEXT:     ]
+// CHECK-NEXT:     Handler: __C_specific_handler
+// CHECK-NEXT:   }
diff --git a/llvm/test/MC/COFF/seh.s b/llvm/test/MC/COFF/seh.s
index 31e845397f2d0..18ad1d692838c 100644
--- a/llvm/test/MC/COFF/seh.s
+++ b/llvm/test/MC/COFF/seh.s
@@ -141,9 +141,8 @@ func:
     .seh_handlerdata
     .long 0
     .text
-    .seh_startchained
     .seh_endprologue
-    .seh_endchained
+    .seh_splitchained
     .seh_startepilogue
     lea (%rbx), %rsp
     pop %rbx
diff --git a/llvm/test/tools/llvm-objdump/COFF/Inputs/win64-unwind.exe.coff-x86_64.asm b/llvm/test/tools/llvm-objdump/COFF/Inputs/win64-unwind.exe.coff-x86_64.asm
index c44d59b324036..9c301652f8ea4 100644
--- a/llvm/test/tools/llvm-objdump/COFF/Inputs/win64-unwind.exe.coff-x86_64.asm
+++ b/llvm/test/tools/llvm-objdump/COFF/Inputs/win64-unwind.exe.coff-x86_64.asm
@@ -19,9 +19,8 @@ func:
     .seh_handlerdata
     .long 0
     .text
-    .seh_startchained
     .seh_endprologue
-    .seh_endchained
+    .seh_splitchained
     lea (%rbx), %rsp
     pop %rbx
     addq $24, %rsp



More information about the llvm-commits mailing list