[clang] [llvm] [win][x64] Unwind v2 3/n: Add support for emitting unwind v2 information (equivalent to MSVC /d2epilogunwind) (PR #129142)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Feb 27 15:24:21 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Daniel Paoliello (dpaoliello)
<details>
<summary>Changes</summary>
Adds support for emitting Windows x64 Unwind V2 information, includes support `/d2epilogunwind` in clang-cl.
Unwind v2 adds information about the epilogs in functions such that the unwinder can unwind even in the middle of an epilog, without having to disassembly the function to see what has or has not been cleaned up.
Unwind v2 requires that all epilogs are in "canonical" form:
* If there was a stack allocation (fixed or dynamic) in the prolog, then the first instruction in the epilog must be a stack deallocation.
* Next, for each `PUSH` in the prolog there must be a corresponding `POP` instruction in exact reverse order.
* Finally, the epilog must end with the terminator.
This change adds a pass to validate epilogs in modules that have Unwind v2 enabled and, if they pass, emits new pseudo instructions to MC that 1) note that the function is using unwind v2 and 2) mark the start of the epilog (this is either the first `POP` if there is one, otherwise the terminator instruction). If a function does not meet these requirements, it is downgraded to Unwind v1 (i.e., these new pseudo instructions are not emitted).
Note that the unwind v2 table only marks the size of the epilog in the "header" unwind code, but it's possible for epilogs to use different terminator instructions thus they are not all the same size. As a work around for this, MC will assume that all terminator instructions are 1-byte long - this still works correctly with the Windows unwinder as it is only using the size to do a range check to see if a thread is in an epilog or not, and since the instruction pointer will never be in the middle of an instruction and the terminator is always at the end of an epilog the range check will function correctly. This does mean, however, that the "at end" optimization (where an epilog unwind code can be elided if the last epilog is at the end of the function) can only be used if the terminator is 1-byte long.
One other complication with the implementation is that the unwind table for a function is emitted during streaming, however we can't calculate the distance between an epilog and the end of the function at that time as layout hasn't been completed yet (thus some instructions may be relaxed). To work around this, epilog unwind codes are emitted via a fixup. This also means that we can't pre-emptively downgrade a function to Unwind v1 if one of these offsets is too large, so instead we raise an error (but I've passed through the location information, so the user will know which of their functions is problematic).
---
Patch is 45.91 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/129142.diff
22 Files Affected:
- (modified) clang/include/clang/Basic/CodeGenOptions.def (+3)
- (modified) clang/include/clang/Driver/Options.td (+6)
- (modified) clang/lib/CodeGen/CodeGenModule.cpp (+4)
- (modified) clang/lib/Driver/ToolChains/Clang.cpp (+3)
- (added) clang/test/CodeGen/epilog-unwind.c (+5)
- (modified) clang/test/Driver/cl-options.c (+3)
- (modified) llvm/include/llvm/MC/MCStreamer.h (+2)
- (modified) llvm/include/llvm/MC/MCWinEH.h (+7-1)
- (modified) llvm/lib/MC/MCAsmStreamer.cpp (+16)
- (modified) llvm/lib/MC/MCParser/COFFAsmParser.cpp (+28)
- (modified) llvm/lib/MC/MCStreamer.cpp (+45-1)
- (modified) llvm/lib/MC/MCWin64EH.cpp (+183-14)
- (modified) llvm/lib/Target/X86/CMakeLists.txt (+1)
- (modified) llvm/lib/Target/X86/X86.h (+4)
- (modified) llvm/lib/Target/X86/X86InstrCompiler.td (+4)
- (modified) llvm/lib/Target/X86/X86MCInstLower.cpp (+10)
- (modified) llvm/lib/Target/X86/X86TargetMachine.cpp (+6)
- (added) llvm/lib/Target/X86/X86WinEHUnwindV2.cpp (+221)
- (added) llvm/test/CodeGen/X86/win64-eh-unwindv2.ll (+160)
- (modified) llvm/test/MC/AsmParser/seh-directive-errors.s (+27)
- (modified) llvm/test/MC/COFF/bad-parse.s (+11)
- (added) llvm/test/MC/COFF/seh-unwindv2.s (+154)
``````````diff
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index a7f5f1abbb825..399bce726e08f 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -476,6 +476,9 @@ CODEGENOPT(ImportCallOptimization, 1, 0)
/// (BlocksRuntime) on Windows.
CODEGENOPT(StaticClosure, 1, 0)
+/// Enables unwind v2 (epilog) information for x64 Windows.
+CODEGENOPT(WinX64EHUnwindV2, 1, 0)
+
/// FIXME: Make DebugOptions its own top-level .def file.
#include "DebugOptions.def"
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 1cf62ab466134..4061d313970d3 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -7613,6 +7613,10 @@ def import_call_optimization : Flag<["-"], "import-call-optimization">,
"by the Windows kernel to enable import call optimization">,
MarshallingInfoFlag<CodeGenOpts<"ImportCallOptimization">>;
+def epilog_unwind : Flag<["-"], "winx64-eh-unwindv2">,
+ HelpText<"Enable unwind v2 (epilog) information for x64 Windows">,
+ MarshallingInfoFlag<CodeGenOpts<"WinX64EHUnwindV2">>;
+
} // let Visibility = [CC1Option]
//===----------------------------------------------------------------------===//
@@ -8725,6 +8729,8 @@ def _SLASH_M_Group : OptionGroup<"</M group>">, Group<cl_compile_Group>;
def _SLASH_volatile_Group : OptionGroup<"</volatile group>">,
Group<cl_compile_Group>;
+def _SLASH_d2epilogunwind : CLFlag<"d2epilogunwind">,
+ HelpText<"Enable unwind v2 (epilog) information for x64 Windows">;
def _SLASH_EH : CLJoined<"EH">, HelpText<"Set exception handling model">;
def _SLASH_EP : CLFlag<"EP">,
HelpText<"Disable linemarker output and preprocess to stdout">;
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 7924c32fcf633..1f53a2d055516 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1302,6 +1302,10 @@ void CodeGenModule::Release() {
getModule().addModuleFlag(llvm::Module::Warning, "import-call-optimization",
1);
+ // Enable unwind v2 (epilog).
+ if (CodeGenOpts.WinX64EHUnwindV2)
+ getModule().addModuleFlag(llvm::Module::Warning, "winx64-eh-unwindv2", 1);
+
// Indicate whether this Module was compiled with -fopenmp
if (getLangOpts().OpenMP && !getLangOpts().OpenMPSimd)
getModule().addModuleFlag(llvm::Module::Max, "openmp", LangOpts.OpenMP);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 5deafa2ad0f4a..e96a271cb47d9 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8532,6 +8532,9 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType,
if (Args.hasArg(options::OPT__SLASH_kernel))
CmdArgs.push_back("-fms-kernel");
+ if (Args.hasArg(options::OPT__SLASH_d2epilogunwind))
+ CmdArgs.push_back("-winx64-eh-unwindv2");
+
for (const Arg *A : Args.filtered(options::OPT__SLASH_guard)) {
StringRef GuardArgs = A->getValue();
// The only valid options are "cf", "cf,nochecks", "cf-", "ehcont" and
diff --git a/clang/test/CodeGen/epilog-unwind.c b/clang/test/CodeGen/epilog-unwind.c
new file mode 100644
index 0000000000000..73bba28f94b29
--- /dev/null
+++ b/clang/test/CodeGen/epilog-unwind.c
@@ -0,0 +1,5 @@
+// RUN: %clang_cc1 -winx64-eh-unwindv2 -emit-llvm %s -o - | FileCheck %s
+
+void f(void) {}
+
+// CHECK: !"winx64-eh-unwindv2", i32 1}
diff --git a/clang/test/Driver/cl-options.c b/clang/test/Driver/cl-options.c
index 9f9ca1bf1a8fd..c0031e9d96d09 100644
--- a/clang/test/Driver/cl-options.c
+++ b/clang/test/Driver/cl-options.c
@@ -817,4 +817,7 @@
// RUN: %clang_cl -vctoolsdir "" /arm64EC /c -target x86_64-pc-windows-msvc -### -- %s 2>&1 | FileCheck --check-prefix=ARM64EC_OVERRIDE %s
// ARM64EC_OVERRIDE: warning: /arm64EC has been overridden by specified target: x86_64-pc-windows-msvc; option ignored
+// RUN: %clang_cl /d2epilogunwind /c -### -- %s 2>&1 | FileCheck %s --check-prefix=EPILOGUNWIND
+// EPILOGUNWIND: -winx64-eh-unwindv2
+
void f(void) { }
diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h
index 4fb2158f26e1e..1d6ed8e159e13 100644
--- a/llvm/include/llvm/MC/MCStreamer.h
+++ b/llvm/include/llvm/MC/MCStreamer.h
@@ -1066,6 +1066,8 @@ class MCStreamer {
virtual void emitWinCFIEndProlog(SMLoc Loc = SMLoc());
virtual void emitWinCFIBeginEpilogue(SMLoc Loc = SMLoc());
virtual void emitWinCFIEndEpilogue(SMLoc Loc = SMLoc());
+ virtual void emitWinCFIUnwindV2Start(SMLoc Loc = SMLoc());
+ virtual void emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc = SMLoc());
virtual void emitWinEHHandler(const MCSymbol *Sym, bool Unwind, bool Except,
SMLoc Loc = SMLoc());
virtual void emitWinEHHandlerData(SMLoc Loc = SMLoc());
diff --git a/llvm/include/llvm/MC/MCWinEH.h b/llvm/include/llvm/MC/MCWinEH.h
index fcce2dcd54837..c603aa896883d 100644
--- a/llvm/include/llvm/MC/MCWinEH.h
+++ b/llvm/include/llvm/MC/MCWinEH.h
@@ -10,6 +10,7 @@
#define LLVM_MC_MCWINEH_H
#include "llvm/ADT/MapVector.h"
+#include "llvm/Support/SMLoc.h"
#include <vector>
namespace llvm {
@@ -42,6 +43,7 @@ struct FrameInfo {
const MCSymbol *FuncletOrFuncEnd = nullptr;
const MCSymbol *ExceptionHandler = nullptr;
const MCSymbol *Function = nullptr;
+ SMLoc FunctionLoc;
const MCSymbol *PrologEnd = nullptr;
const MCSymbol *Symbol = nullptr;
MCSection *TextSection = nullptr;
@@ -52,6 +54,8 @@ struct FrameInfo {
bool HandlesExceptions = false;
bool EmitAttempted = false;
bool Fragment = false;
+ constexpr static uint8_t DefaultVersion = 1;
+ uint8_t Version = DefaultVersion;
int LastFrameInst = -1;
const FrameInfo *ChainedParent = nullptr;
@@ -59,7 +63,9 @@ struct FrameInfo {
struct Epilog {
std::vector<Instruction> Instructions;
unsigned Condition;
- MCSymbol *End;
+ const MCSymbol *End = nullptr;
+ const MCSymbol *UnwindV2Start = nullptr;
+ SMLoc Loc;
};
MapVector<MCSymbol *, Epilog> EpilogMap;
diff --git a/llvm/lib/MC/MCAsmStreamer.cpp b/llvm/lib/MC/MCAsmStreamer.cpp
index 4db405051f7f3..1b74b8d73165b 100644
--- a/llvm/lib/MC/MCAsmStreamer.cpp
+++ b/llvm/lib/MC/MCAsmStreamer.cpp
@@ -399,6 +399,8 @@ class MCAsmStreamer final : public MCStreamer {
void emitWinCFIEndProlog(SMLoc Loc) override;
void emitWinCFIBeginEpilogue(SMLoc Loc) override;
void emitWinCFIEndEpilogue(SMLoc Loc) override;
+ void emitWinCFIUnwindV2Start(SMLoc Loc) override;
+ void emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc) override;
void emitWinEHHandler(const MCSymbol *Sym, bool Unwind, bool Except,
SMLoc Loc) override;
@@ -2351,6 +2353,20 @@ void MCAsmStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
EmitEOL();
}
+void MCAsmStreamer::emitWinCFIUnwindV2Start(SMLoc Loc) {
+ MCStreamer::emitWinCFIUnwindV2Start(Loc);
+
+ OS << "\t.seh_unwindv2start";
+ EmitEOL();
+}
+
+void MCAsmStreamer::emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc) {
+ MCStreamer::emitWinCFIUnwindVersion(Version, Loc);
+
+ OS << "\t.seh_unwindversion " << (unsigned)Version;
+ EmitEOL();
+}
+
void MCAsmStreamer::emitCGProfileEntry(const MCSymbolRefExpr *From,
const MCSymbolRefExpr *To,
uint64_t Count) {
diff --git a/llvm/lib/MC/MCParser/COFFAsmParser.cpp b/llvm/lib/MC/MCParser/COFFAsmParser.cpp
index 4618e5675e47b..4632df9f32540 100644
--- a/llvm/lib/MC/MCParser/COFFAsmParser.cpp
+++ b/llvm/lib/MC/MCParser/COFFAsmParser.cpp
@@ -96,6 +96,10 @@ class COFFAsmParser : public MCAsmParserExtension {
".seh_startepilogue");
addDirectiveHandler<&COFFAsmParser::ParseSEHDirectiveEndEpilog>(
".seh_endepilogue");
+ addDirectiveHandler<&COFFAsmParser::ParseSEHDirectiveUnwindV2Start>(
+ ".seh_unwindv2start");
+ addDirectiveHandler<&COFFAsmParser::ParseSEHDirectiveUnwindVersion>(
+ ".seh_unwindversion");
}
bool parseSectionDirectiveText(StringRef, SMLoc) {
@@ -147,6 +151,8 @@ class COFFAsmParser : public MCAsmParserExtension {
bool parseSEHDirectiveEndProlog(StringRef, SMLoc);
bool ParseSEHDirectiveBeginEpilog(StringRef, SMLoc);
bool ParseSEHDirectiveEndEpilog(StringRef, SMLoc);
+ bool ParseSEHDirectiveUnwindV2Start(StringRef, SMLoc);
+ bool ParseSEHDirectiveUnwindVersion(StringRef, SMLoc);
bool parseAtUnwindOrAtExcept(bool &unwind, bool &except);
bool parseDirectiveSymbolAttribute(StringRef Directive, SMLoc);
@@ -767,6 +773,28 @@ bool COFFAsmParser::ParseSEHDirectiveEndEpilog(StringRef, SMLoc Loc) {
return false;
}
+bool COFFAsmParser::ParseSEHDirectiveUnwindV2Start(StringRef, SMLoc Loc) {
+ Lex();
+ getStreamer().emitWinCFIUnwindV2Start(Loc);
+ return false;
+}
+
+bool COFFAsmParser::ParseSEHDirectiveUnwindVersion(StringRef, SMLoc Loc) {
+ int64_t Version;
+ if (getParser().parseIntToken(Version, "expected unwind version number"))
+ return true;
+
+ if ((Version < 1) || (Version > UINT8_MAX))
+ return Error(Loc, "invalid unwind version");
+
+ if (getLexer().isNot(AsmToken::EndOfStatement))
+ return TokError("unexpected token in directive");
+
+ Lex();
+ getStreamer().emitWinCFIUnwindVersion(Version, Loc);
+ return false;
+}
+
bool COFFAsmParser::parseAtUnwindOrAtExcept(bool &unwind, bool &except) {
StringRef identifier;
if (getLexer().isNot(AsmToken::At) && getLexer().isNot(AsmToken::Percent))
diff --git a/llvm/lib/MC/MCStreamer.cpp b/llvm/lib/MC/MCStreamer.cpp
index dd04e1e30720a..dab98feac1cb1 100644
--- a/llvm/lib/MC/MCStreamer.cpp
+++ b/llvm/lib/MC/MCStreamer.cpp
@@ -500,7 +500,8 @@ MCSymbol *MCStreamer::emitLineTableLabel() {
MCSymbol *MCStreamer::emitCFILabel() {
// Return a dummy non-null value so that label fields appear filled in when
// generating textual assembly.
- return (MCSymbol *)1;
+ static size_t DummyLabelValue = 0;
+ return (MCSymbol *)(++DummyLabelValue);
}
void MCStreamer::emitCFIDefCfa(int64_t Register, int64_t Offset, SMLoc Loc) {
@@ -767,6 +768,7 @@ void MCStreamer::emitWinCFIStartProc(const MCSymbol *Symbol, SMLoc Loc) {
std::make_unique<WinEH::FrameInfo>(Symbol, StartProc));
CurrentWinFrameInfo = WinFrameInfos.back().get();
CurrentWinFrameInfo->TextSection = getCurrentSectionOnly();
+ CurrentWinFrameInfo->FunctionLoc = Loc;
}
void MCStreamer::emitWinCFIEndProc(SMLoc Loc) {
@@ -1026,6 +1028,7 @@ void MCStreamer::emitWinCFIBeginEpilogue(SMLoc Loc) {
InEpilogCFI = true;
CurrentEpilog = emitCFILabel();
+ CurFrame->EpilogMap[CurrentEpilog].Loc = Loc;
}
void MCStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
@@ -1037,12 +1040,53 @@ void MCStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
return getContext().reportError(Loc, "Stray .seh_endepilogue in " +
CurFrame->Function->getName());
+ assert(CurrentEpilog);
+ if ((CurFrame->Version >= 2) &&
+ !CurFrame->EpilogMap[CurrentEpilog].UnwindV2Start)
+ return getContext().reportError(Loc, "Missing .seh_unwindv2start in " +
+ CurFrame->Function->getName());
+
InEpilogCFI = false;
MCSymbol *Label = emitCFILabel();
CurFrame->EpilogMap[CurrentEpilog].End = Label;
CurrentEpilog = nullptr;
}
+void MCStreamer::emitWinCFIUnwindV2Start(SMLoc Loc) {
+ WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
+ if (!CurFrame)
+ return;
+
+ if (!InEpilogCFI)
+ return getContext().reportError(Loc, "Stray .seh_unwindv2start in " +
+ CurFrame->Function->getName());
+
+ assert(CurrentEpilog);
+ if (CurFrame->EpilogMap[CurrentEpilog].UnwindV2Start)
+ return getContext().reportError(Loc, "Duplicate .seh_unwindv2start in " +
+ CurFrame->Function->getName());
+
+ MCSymbol *Label = emitCFILabel();
+ CurFrame->EpilogMap[CurrentEpilog].UnwindV2Start = Label;
+}
+
+void MCStreamer::emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc) {
+ WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
+ if (!CurFrame)
+ return;
+
+ if (CurFrame->Version != WinEH::FrameInfo::DefaultVersion)
+ return getContext().reportError(Loc, "Duplicate .seh_unwindversion in " +
+ CurFrame->Function->getName());
+
+ if (Version != 2)
+ return getContext().reportError(
+ Loc, "Unsupported version specified in .seh_unwindversion in " +
+ CurFrame->Function->getName());
+
+ CurFrame->Version = Version;
+}
+
void MCStreamer::emitCOFFSafeSEH(MCSymbol const *Symbol) {}
void MCStreamer::emitCOFFSymbolIndex(MCSymbol const *Symbol) {}
diff --git a/llvm/lib/MC/MCWin64EH.cpp b/llvm/lib/MC/MCWin64EH.cpp
index bd5cf354659b6..253af0c048e5a 100644
--- a/llvm/lib/MC/MCWin64EH.cpp
+++ b/llvm/lib/MC/MCWin64EH.cpp
@@ -8,14 +8,61 @@
#include "llvm/MC/MCWin64EH.h"
#include "llvm/ADT/Twine.h"
+#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCObjectStreamer.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSymbol.h"
+#include "llvm/MC/MCValue.h"
#include "llvm/Support/Win64EH.h"
+
namespace llvm {
class MCSection;
+
+/// MCExpr that represents the epilog unwind code in an unwind table.
+class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {
+ const MCSymbol *FunctionEnd;
+ const MCSymbol *UnwindV2Start;
+ const MCSymbol *EpilogEnd;
+ uint8_t EpilogSize;
+ SMLoc Loc;
+
+ MCUnwindV2EpilogTargetExpr(const WinEH::FrameInfo &FrameInfo,
+ const WinEH::FrameInfo::Epilog &Epilog,
+ uint8_t EpilogSize_)
+ : FunctionEnd(FrameInfo.FuncletOrFuncEnd),
+ UnwindV2Start(Epilog.UnwindV2Start), EpilogEnd(Epilog.End),
+ EpilogSize(EpilogSize_), Loc(Epilog.Loc) {}
+
+public:
+ static MCUnwindV2EpilogTargetExpr *
+ create(const WinEH::FrameInfo &FrameInfo,
+ const WinEH::FrameInfo::Epilog &Epilog, uint8_t EpilogSize_,
+ MCContext &Ctx) {
+ return new (Ctx) MCUnwindV2EpilogTargetExpr(FrameInfo, Epilog, EpilogSize_);
+ }
+
+ void printImpl(raw_ostream &OS, const MCAsmInfo *MAI) const override {
+ OS << ":epilog:";
+ UnwindV2Start->print(OS, MAI);
+ }
+
+ bool evaluateAsRelocatableImpl(MCValue &Res, const MCAssembler *Asm,
+ const MCFixup *Fixup) const override;
+
+ void visitUsedExpr(MCStreamer &Streamer) const override {
+ // Contains no sub-expressions.
+ }
+
+ MCFragment *findAssociatedFragment() const override {
+ return UnwindV2Start->getFragment();
+ }
+
+ void fixELFSymbolsInTLSFixups(MCAssembler &) const override {
+ llvm_unreachable("Not supported for ELF");
+ }
+};
}
using namespace llvm;
@@ -163,20 +210,91 @@ static void EmitRuntimeFunction(MCStreamer &streamer,
context), 4);
}
+static std::optional<int64_t>
+GetOptionalAbsDifference(const MCAssembler &Assembler, const MCSymbol *LHS,
+ const MCSymbol *RHS) {
+ MCContext &Context = Assembler.getContext();
+ const MCExpr *Diff =
+ MCBinaryExpr::createSub(MCSymbolRefExpr::create(LHS, Context),
+ MCSymbolRefExpr::create(RHS, Context), Context);
+ // It should normally be possible to calculate the length of a function
+ // at this point, but it might not be possible in the presence of certain
+ // unusual constructs, like an inline asm with an alignment directive.
+ int64_t value;
+ if (!Diff->evaluateAsAbsolute(value, Assembler))
+ return std::nullopt;
+ return value;
+}
+
static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
// If this UNWIND_INFO already has a symbol, it's already been emitted.
if (info->Symbol)
return;
MCContext &context = streamer.getContext();
+ MCObjectStreamer *OS = (MCObjectStreamer *)(&streamer);
MCSymbol *Label = context.createTempSymbol();
streamer.emitValueToAlignment(Align(4));
streamer.emitLabel(Label);
info->Symbol = Label;
- // Upper 3 bits are the version number (currently 1).
- uint8_t flags = 0x01;
+ uint8_t numCodes = CountOfUnwindCodes(info->Instructions);
+ bool LastEpilogIsAtEnd = false;
+ bool AddPaddingEpilogCode = false;
+ uint8_t EpilogSize = 0;
+ bool EnableUnwindV2 = (info->Version >= 2) && !info->EpilogMap.empty();
+ if (EnableUnwindV2) {
+ auto &LastEpilog = info->EpilogMap.rbegin()->second;
+
+ // Calculate the size of the epilogs. Note that we +1 to the size so that
+ // the terminator instruction is also included in the epilog (the Windows
+ // unwinder does a simple range check versus the current instruction pointer
+ // 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).
+ auto MaybeSize = GetOptionalAbsDifference(
+ OS->getAssembler(), LastEpilog.End, LastEpilog.UnwindV2Start);
+ if (!MaybeSize) {
+ context.reportError(LastEpilog.Loc,
+ "Failed to evaluate epilog size for Unwind v2");
+ return;
+ }
+ assert(*MaybeSize >= 0);
+ if (*MaybeSize >= (int64_t)UINT8_MAX) {
+ context.reportError(LastEpilog.Loc,
+ "Epilog size is too large for Unwind v2");
+ return;
+ }
+ EpilogSize = *MaybeSize + 1;
+
+ // 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);
+
+ // If we have an odd number of epilog codes, we need to add a padding code.
+ size_t numEpilogCodes =
+ info->EpilogMap.size() + (LastEpilogIsAtEnd ? 0 : 1);
+ if ((numEpilogCodes % 2) != 0) {
+ AddPaddingEpilogCode = true;
+ numEpilogCodes++;
+ }
+
+ // Too many epilogs to handle.
+ if ((size_t)numCodes + numEpilogCodes > UINT8_MAX) {
+ context.reportError(info->FunctionLoc,
+ "Too many unwind codes with Unwind v2 enabled");
+ return;
+ }
+
+ numCodes += numEpilogCodes;
+ }
+
+ // Upper 3 bits are the version number.
+ uint8_t flags = info->Version;
if (info->ChainedParent)
flags |= Win64EH::UNW_ChainInfo << 3;
else {
@@ -192,7 +310,6 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
else
streamer.emitInt8(0);
- uint8_t numCodes = CountOfUnwindCodes(info->Instructions);
streamer.emitInt8(numCodes);
uint8_t frame = 0;
@@ -203,6 +320,35 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
}
streamer.emitInt8(frame);
+ // Emit the epilog instructions.
+ if (EnableUnwindV2) {
+ MCDataFragment *DF = OS->getOrCreateDataFragment();
+
+ bool IsLast = true;
+ for (const auto &[_, Epilog] : llvm::reverse(info->EpilogMap)) {
+ if (IsLast) {
+ IsLast = false;
+ uint8_t Flags = LastEpilogIsAtEnd ? 0x01 : 0;
+ streamer.emitInt8(EpilogSize);
+ streamer.emitInt8((Flags << 4) | Win64EH::UOP_Epilog);
+
+ if (LastEpilogIsAtEnd)
+ continue;
+ }
+
+ // Each epilog is emitted as a fixup, since we can't measure the distance
+ // between the start of the epilog and the end of the function until
+ // layout has been completed.
+ auto *MCE = MCUnwindV2EpilogTargetExpr::create(*info, Epilog, EpilogSize,
+ context);
+ MCFixup Fixup = MCFixup::create(DF->getContents().size(), MCE, FK_Data_2);
+ DF->getFixups().push_back(Fixup);
+ DF->appendContents(2, 0);
+ }
+ }
+ if (AddPaddingEpilogCode)
+ streamer.emitInt16(Win64EH::UOP_Epilog << 8);
+
// Emit unwind instructions (in reverse order).
uint8_t numInst = info->Instructions.size();
for (uint8_t c = 0; c < numInst; ++c) {
@@ -234,6 +380,39 @@ static void EmitUnwindInfo(MCStr...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/129142
More information about the llvm-commits
mailing list