[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 15:53:29 PDT 2025
https://github.com/dpaoliello updated https://github.com/llvm/llvm-project/pull/159206
>From fe7a7270f162bfef9fed92b7609be3f794751c5f 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 ++++++++----
.../Inputs/reference_x86_vocab_print.txt | 1 +
.../reference_x86_vocab_wo=0.5_print.txt | 1 +
.../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 +-
20 files changed, 585 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/MIR2Vec/Inputs/reference_x86_vocab_print.txt b/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_print.txt
index 6327cffb77e74..19263a6968ef0 100644
--- a/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_print.txt
+++ b/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_print.txt
@@ -1604,6 +1604,7 @@ Key: SEH_PushReg: [ 0.00 0.00 ]
Key: SEH_SaveReg: [ 0.00 0.00 ]
Key: SEH_SaveXMM: [ 0.00 0.00 ]
Key: SEH_SetFrame: [ 0.00 0.00 ]
+Key: SEH_SplitChained: [ 0.00 0.00 ]
Key: SEH_StackAlign: [ 0.00 0.00 ]
Key: SEH_StackAlloc: [ 0.00 0.00 ]
Key: SEH_UnwindV: [ 0.00 0.00 ]
diff --git a/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_wo=0.5_print.txt b/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_wo=0.5_print.txt
index 4409e6dbd5c72..ce3e3e72ceedc 100644
--- a/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_wo=0.5_print.txt
+++ b/llvm/test/CodeGen/MIR2Vec/Inputs/reference_x86_vocab_wo=0.5_print.txt
@@ -1604,6 +1604,7 @@ Key: SEH_PushReg: [ 0.00 0.00 ]
Key: SEH_SaveReg: [ 0.00 0.00 ]
Key: SEH_SaveXMM: [ 0.00 0.00 ]
Key: SEH_SetFrame: [ 0.00 0.00 ]
+Key: SEH_SplitChained: [ 0.00 0.00 ]
Key: SEH_StackAlign: [ 0.00 0.00 ]
Key: SEH_StackAlloc: [ 0.00 0.00 ]
Key: SEH_UnwindV: [ 0.00 0.00 ]
diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-epilogs.mir b/llvm/test/CodeGen/X86/win64-eh-unwindv2-too-many-epilogs.mir
index 70c87ad87f792..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