[llvm] [win][x64] Unwind v2 4/4: Use chaining to split unwind info if needed, support functions with EH Handlers (PR #159206)

Daniel Paoliello via llvm-commits llvm-commits at lists.llvm.org
Fri Oct 10 13:25:38 PDT 2025


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

>From 8001c09e58dfacd708b39827f1d78d47ac2580be 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                    |  59 ++---
 llvm/lib/MC/MCWin64EH.cpp                     |  38 ++--
 .../X86/MCTargetDesc/X86WinCOFFStreamer.cpp   |   5 +-
 llvm/lib/Target/X86/X86InstrCompiler.td       |   6 +
 llvm/lib/Target/X86/X86MCInstLower.cpp        |   5 +
 llvm/lib/Target/X86/X86WinEHUnwindV2.cpp      | 147 ++++++++----
 .../win64-eh-unwindv2-too-many-epilogs.mir    |  38 ++--
 .../X86/win64-eh-unwindv2-too-many-instr.mir  |  89 ++++++++
 llvm/test/CodeGen/X86/win64-eh-unwindv2.ll    |  80 +++++++
 llvm/test/MC/AsmParser/directive_seh.s        |   6 +-
 llvm/test/MC/AsmParser/seh-directive-errors.s |   2 +
 llvm/test/MC/COFF/seh-unwindv2.s              | 209 ++++++++++++++++++
 llvm/test/MC/COFF/seh.s                       |   3 +-
 .../Inputs/win64-unwind.exe.coff-x86_64.asm   |   3 +-
 18 files changed, 583 insertions(+), 151 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..db22382dbe86b 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,
@@ -806,9 +807,10 @@ void MCStreamer::emitWinEHHandler(const MCSymbol *Sym, bool Unwind, bool Except,
   WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
   if (!CurFrame)
     return;
-  if (CurFrame->ChainedParent)
-    return getContext().reportError(
-        Loc, "Chained unwind areas can't have handlers!");
+
+  // Handlers are always associated with the parent frame.
+  CurFrame = CurFrame->ChainedParent ? CurFrame->ChainedParent : CurFrame;
+
   CurFrame->ExceptionHandler = Sym;
   if (!Except && !Unwind)
     getContext().reportError(Loc, "Don't know what kind of handler this is!");
@@ -822,8 +824,6 @@ void MCStreamer::emitWinEHHandlerData(SMLoc Loc) {
   WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
   if (!CurFrame)
     return;
-  if (CurFrame->ChainedParent)
-    getContext().reportError(Loc, "Chained unwind areas can't have handlers!");
 }
 
 void MCStreamer::emitCGProfileEntry(const MCSymbolRefExpr *From,
@@ -994,7 +994,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/MCTargetDesc/X86WinCOFFStreamer.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86WinCOFFStreamer.cpp
index 1ef10928c05d8..e9d1ce3aa593b 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86WinCOFFStreamer.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86WinCOFFStreamer.cpp
@@ -44,8 +44,11 @@ void X86WinCOFFStreamer::emitWinEHHandlerData(SMLoc Loc) {
 
   // We have to emit the unwind info now, because this directive
   // actually switches to the .xdata section.
-  if (WinEH::FrameInfo *CurFrame = getCurrentWinFrameInfo())
+  if (WinEH::FrameInfo *CurFrame = getCurrentWinFrameInfo()) {
+    // Handlers are always associated with the parent frame.
+    CurFrame = CurFrame->ChainedParent ? CurFrame->ChainedParent : CurFrame;
     EHStreamer.EmitUnwindInfo(*this, CurFrame, /* HandlerData = */ true);
+  }
 }
 
 void X86WinCOFFStreamer::emitWindowsUnwindTables(WinEH::FrameInfo *Frame) {
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..2da4003c98cf0 100644
--- a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
+++ b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
@@ -32,17 +32,35 @@ 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 {
 
+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 +75,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,15 +112,10 @@ 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) {
   // Current state of processing the function. We'll assume that all functions
   // start with a prolog.
   FunctionState State = FunctionState::InProlog;
@@ -108,10 +126,18 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
   bool HasSetFrame = false;
   unsigned ApproximatePrologCodeCount = 0;
 
-  // Requested changes.
-  SmallVector<MachineInstr *> UnwindV2StartLocations;
+  SmallVector<EpilogInfo> EpilogInfos;
+
+  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;
@@ -119,6 +145,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 +221,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 +335,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();
+      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 = 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 +402,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 +410,5 @@ bool X86WinEHUnwindV2::rejectCurrentFunctionInternalError(
                              MF.getName() + "': " + Reason);
 
   FailsUnwindV2Criteria++;
-  return false;
+  return std::nullopt;
 }
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/CodeGen/X86/win64-eh-unwindv2.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
index 0d92d044e1b94..4619c28768dd7 100644
--- a/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
@@ -204,11 +204,91 @@ 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
+; 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
+
 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}
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..822b7b954a5f0 100644
--- a/llvm/test/MC/COFF/seh-unwindv2.s
+++ b/llvm/test/MC/COFF/seh-unwindv2.s
@@ -152,3 +152,212 @@ 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 [[EndDisp1:\+0x[A-F0-9]+]] (0x34)
+// CHECK-NEXT:    UnwindInfoAddress: .xdata [[InfoDisp1:\+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 [[EndDisp1]] (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 [[EndDisp1]] (0x4C)
+// CHECK-NEXT:        UnwindInfoAddress: .xdata [[InfoDisp1]] (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   .has_ex_handler_data_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:   }
+
+has_ex_handler_data_and_chaining:
+    .seh_proc chained
+    .seh_handler __C_specific_handler, @unwind, @except
+    .seh_unwindversion 2
+    subq    $40, %rsp
+    .seh_stackalloc 40
+    .seh_endprologue
+    callq   c
+    testl   %eax, %eax
+    jle     .L_ELSE_4
+.has_ex_handler_data_and_chaining_callsite:
+    callq   *__imp_callme(%rip)
+    nop
+.has_ex_handler_data_and_chaining_finish:
+    movl    %eax, %ecx
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    jmp     c
+    .seh_splitchained
+.L_ELSE_4:
+    nop
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    jmp     b
+.has_ex_handler_data_and_chaining_handler:
+    jmp     .has_ex_handler_data_and_chaining_finish
+    .seh_handlerdata
+    .long   1  # Number of call sites
+    .long   .has_ex_handler_data_and_chaining_callsite at IMGREL   # LabelStart
+    .long   .has_ex_handler_data_and_chaining_finish at IMGREL     # LabelEnd
+    .long   1                                                   # CatchAll
+    .long   .has_ex_handler_data_and_chaining_handler at IMGREL    # ExceptionHandler
+    .text
+    .seh_endproc
+
+// CHECK:         RuntimeFunction {
+// CHECK-NEXT:    StartAddress: has_ex_handler_data_and_chaining (0x54)
+// CHECK-NEXT:    EndAddress: .has_ex_handler_data_and_chaining_finish [[EndDisp2:\+0x[A-F0-9]+]] (0x58)
+// CHECK-NEXT:    UnwindInfoAddress: .xdata [[InfoDisp2:\+0x[A-F0-9]+]] (0x5C)
+// CHECK-NEXT:    UnwindInfo {
+// CHECK-NEXT:      Version: 2
+// CHECK-NEXT:      Flags [ (0x3)
+// CHECK-NEXT:       ExceptionHandler (0x1)
+// CHECK-NEXT:       TerminateHandler (0x2)
+// 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:      Handler: __C_specific_handler
+// CHECK-NEXT:    }
+// CHECK-NEXT:  }
+// CHECK-NEXT:  RuntimeFunction {
+// CHECK-NEXT:    StartAddress: .has_ex_handler_data_and_chaining_finish [[EndDisp2]] (0x60)
+// CHECK-NEXT:    EndAddress: .has_ex_handler_data_and_chaining_handler +0x2 (0x64)
+// CHECK-NEXT:    UnwindInfoAddress: .xdata +0xA0 (0x68)
+// 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:        0x07: EPILOG offset=0x7
+// CHECK-NEXT:      ]
+// CHECK-NEXT:      Chained {
+// CHECK-NEXT:        StartAddress: has_ex_handler_data_and_chaining (0xA8)
+// CHECK-NEXT:        EndAddress: .has_ex_handler_data_and_chaining_finish [[EndDisp2]] (0xAC)
+// CHECK-NEXT:        UnwindInfoAddress: .xdata [[InfoDisp2]] (0xB0)
+// CHECK-NEXT:      }
+// CHECK-NEXT:    }
+// 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