Skip to content

Conversation

@dpaoliello
Copy link
Contributor

@dpaoliello dpaoliello commented Dec 18, 2025

LLVM's existing support for chaining x64 Windows SEH frame infos is broken. Windows requires that each frame info (including the parent) is adjacent, but not overlapping, and the whole function is covered (i.e., each instruction maps to exactly one frame info). Therefore, having start/end chaining pseudo instructions doesn't make any sense, as every "end chain" pseudo would need a start pseudo immediately after it or be at the end of function.

This change switches having a "split chain" pseudo instruction that ends the current frame and starts a new chained frame.

Added a release note about the replacement - to my knowledge there is no one actually using frame info chaining, so it is highly unlikely that it will break any code.

Split off from #159206

@llvmbot
Copy link
Member

llvmbot commented Dec 18, 2025

@llvm/pr-subscribers-platform-windows
@llvm/pr-subscribers-backend-arm
@llvm/pr-subscribers-backend-x86
@llvm/pr-subscribers-llvm-binary-utilities

@llvm/pr-subscribers-llvm-mc

Author: Daniel Paoliello (dpaoliello)

Changes

LLVM's existing support for chaining x64 Windows SEH frame infos is broken. Windows requires that each frame info (including the parent) is adjacent, but not overlapping, and the whole function is covered (i.e., each instruction maps to exactly one frame info). Therefore, having start/end chaining pseudo instructions doesn't make any sense, as every "end chain" pseudo would need a start pseudo immediately after it or be at the end of function.

This change switches having a "split chain" pseudo instruction that ends the current frame and starts a new chained frame.

Added a release note about the replacement - to my knowledge there is no one actually using frame info chaining, so it is highly unlikely that it will break any code.

Split off from #159206


Full diff: https://github.com/llvm/llvm-project/pull/172895.diff

10 Files Affected:

  • (modified) llvm/docs/ReleaseNotes.md (+2)
  • (modified) llvm/include/llvm/MC/MCStreamer.h (+1-2)
  • (modified) llvm/include/llvm/MC/MCWinEH.h (+3-3)
  • (modified) llvm/lib/MC/MCAsmStreamer.cpp (+4-12)
  • (modified) llvm/lib/MC/MCParser/COFFAsmParser.cpp (+5-14)
  • (modified) llvm/lib/MC/MCStreamer.cpp (+30-29)
  • (modified) llvm/test/MC/AsmParser/directive_seh.s (+2-4)
  • (modified) llvm/test/MC/AsmParser/seh-directive-errors.s (+2)
  • (modified) llvm/test/MC/COFF/seh.s (+1-2)
  • (modified) llvm/test/tools/llvm-objdump/COFF/Inputs/win64-unwind.exe.coff-x86_64.asm (+1-2)
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index ee285fe56b51a..ebc872985d2d9 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -164,6 +164,8 @@ Changes to the Windows Target
 -----------------------------
 
 * `-fpseudo-probe-for-profiling` is now supported for COFF.
+* The `.seh_startchained` and `.seh_endchained` assembly instructions have been removed and replaced
+  with a new `.seh_splitchained` instruction.
 
 Changes to the X86 Backend
 --------------------------
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 b3d5c9cc8620a..9fbfd34da8e64 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 e2543058394a2..269edb383087c 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 d9ec04c6b6100..fe8a15400035a 100644
--- a/llvm/lib/MC/MCParser/COFFAsmParser.cpp
+++ b/llvm/lib/MC/MCParser/COFFAsmParser.cpp
@@ -79,10 +79,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>(
@@ -142,8 +140,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);
@@ -684,15 +681,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/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.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

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a testcase showing how interactions like the following work.

.seh_pushreg %rbx
.seh_endprologue
.seh_splitchained
.seh_pushreg %rsi
.seh_endprologue

Please add a testcase showing the interaction between unwind-v2 and seh_splitchained. (I think you already had a testcase, but just didn't add it here for some reason?)

@dpaoliello
Copy link
Contributor Author

Please add a testcase showing how interactions like the following work.

.seh_pushreg %rbx
.seh_endprologue
.seh_splitchained
.seh_pushreg %rsi
.seh_endprologue

Done, added to seh.s.

Please add a testcase showing the interaction between unwind-v2 and seh_splitchained. (I think you already had a testcase, but just didn't add it here for some reason?)

I wasn't sure how much of the unwindv2 stuff you wanted in this PR, but it makes sense that using .seh_splitchained shouldn't break unwindv2, so I've added the changes to support chaining and the associated tests.

@@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do something to prevent someone from writing:

    .seh_endprologue
    .seh_splitchained
    pushq %rsi
    .seh_pushreg %rsi
    .seh_startepilogue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - added tests for this, added errors for other cases and ensured that the AsmParser will "recover" and not assert later while emitting the tables.

@github-actions
Copy link

github-actions bot commented Dec 19, 2025

🐧 Linux x64 Test Results

  • 188473 tests passed
  • 5001 tests skipped

✅ The build succeeded and all tests passed.

@github-actions
Copy link

github-actions bot commented Dec 19, 2025

🪟 Windows x64 Test Results

  • 129454 tests passed
  • 2863 tests skipped

✅ The build succeeded and all tests passed.

@dpaoliello
Copy link
Contributor Author

Ping @efriedma-quic any other concerns?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants