[clang] [llvm] [win][x64] Unwind v2 3/n: Add support for emitting unwind v2 information (equivalent to MSVC /d2epilogunwind) (PR #129142)
Daniel Paoliello via llvm-commits
llvm-commits at lists.llvm.org
Fri Mar 21 10:54:46 PDT 2025
https://github.com/dpaoliello updated https://github.com/llvm/llvm-project/pull/129142
>From ac5609c09e6423ab30b77cc9a18d24a33ba16048 Mon Sep 17 00:00:00 2001
From: Daniel Paoliello <danpao at microsoft.com>
Date: Thu, 27 Feb 2025 15:08:36 -0800
Subject: [PATCH] [win][x64] Unwind v2 3/n: Add support for emitting unwind v2
information (equivalent to MSVC /d2epilogunwind)
---
clang/include/clang/Basic/CodeGenOptions.def | 3 +
clang/include/clang/Driver/Options.td | 6 +
clang/lib/CodeGen/CodeGenModule.cpp | 4 +
clang/lib/Driver/ToolChains/Clang.cpp | 3 +
clang/test/CodeGen/epilog-unwind.c | 5 +
clang/test/Driver/cl-options.c | 3 +
llvm/include/llvm/MC/MCStreamer.h | 13 +-
llvm/include/llvm/MC/MCWinEH.h | 32 +-
llvm/lib/MC/MCAsmStreamer.cpp | 16 +
llvm/lib/MC/MCParser/COFFAsmParser.cpp | 28 ++
llvm/lib/MC/MCStreamer.cpp | 51 ++-
llvm/lib/MC/MCWin64EH.cpp | 382 +++++++++++++-----
.../MCTargetDesc/AArch64WinCOFFStreamer.cpp | 4 +-
.../ARM/MCTargetDesc/ARMWinCOFFStreamer.cpp | 8 +-
llvm/lib/Target/X86/CMakeLists.txt | 1 +
llvm/lib/Target/X86/X86.h | 4 +
llvm/lib/Target/X86/X86InstrCompiler.td | 4 +
llvm/lib/Target/X86/X86MCInstLower.cpp | 10 +
llvm/lib/Target/X86/X86TargetMachine.cpp | 6 +
llvm/lib/Target/X86/X86WinEHUnwindV2.cpp | 221 ++++++++++
llvm/test/CodeGen/X86/win64-eh-unwindv2.ll | 160 ++++++++
llvm/test/MC/AsmParser/seh-directive-errors.s | 27 ++
llvm/test/MC/COFF/bad-parse.s | 11 +
llvm/test/MC/COFF/seh-unwindv2.s | 154 +++++++
24 files changed, 1034 insertions(+), 122 deletions(-)
create mode 100644 clang/test/CodeGen/epilog-unwind.c
create mode 100644 llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
create mode 100644 llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
create mode 100644 llvm/test/MC/COFF/seh-unwindv2.s
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 66ae8f1c7f064..f5bfa526fa41a 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -7659,6 +7659,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]
//===----------------------------------------------------------------------===//
@@ -8771,6 +8775,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 f88d6202ad381..c7b2978362a9e 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1303,6 +1303,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 fe172d923ac07..43a924739b95a 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8529,6 +8529,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 9d63c1e66bdae..a86be076dfad6 100644
--- a/llvm/include/llvm/MC/MCStreamer.h
+++ b/llvm/include/llvm/MC/MCStreamer.h
@@ -255,11 +255,8 @@ class MCStreamer {
bool AllowAutoPadding = false;
protected:
- // True if we are processing SEH directives in an epilogue.
- bool InEpilogCFI = false;
-
// Symbol of the current epilog for which we are processing SEH directives.
- MCSymbol *CurrentEpilog = nullptr;
+ WinEH::FrameInfo::Epilog *CurrentWinEpilog = nullptr;
MCFragment *CurFrag = nullptr;
@@ -342,9 +339,11 @@ class MCStreamer {
return WinFrameInfos;
}
- MCSymbol *getCurrentEpilog() const { return CurrentEpilog; }
+ WinEH::FrameInfo::Epilog *getCurrentWinEpilog() const {
+ return CurrentWinEpilog;
+ }
- bool isInEpilogCFI() const { return InEpilogCFI; }
+ bool isInEpilogCFI() const { return CurrentWinEpilog; }
void generateCompactUnwindEncodings(MCAsmBackend *MAB);
@@ -1026,6 +1025,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..0addca3aa026d 100644
--- a/llvm/include/llvm/MC/MCWinEH.h
+++ b/llvm/include/llvm/MC/MCWinEH.h
@@ -10,6 +10,8 @@
#define LLVM_MC_MCWINEH_H
#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/SMLoc.h"
#include <vector>
namespace llvm {
@@ -42,6 +44,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 +55,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,9 +64,12 @@ struct FrameInfo {
struct Epilog {
std::vector<Instruction> Instructions;
unsigned Condition;
- MCSymbol *End;
+ const MCSymbol *Start = nullptr;
+ const MCSymbol *End = nullptr;
+ const MCSymbol *UnwindV2Start = nullptr;
+ SMLoc Loc;
};
- MapVector<MCSymbol *, Epilog> EpilogMap;
+ std::vector<Epilog> Epilogs;
// For splitting unwind info of large functions
struct Segment {
@@ -70,7 +78,11 @@ struct FrameInfo {
bool HasProlog;
MCSymbol *Symbol = nullptr;
// Map an Epilog's symbol to its offset within the function.
- MapVector<MCSymbol *, int64_t> Epilogs;
+ struct Epilog {
+ const MCSymbol *Symbol;
+ int64_t Offset;
+ };
+ std::vector<Epilog> Epilogs;
Segment(int64_t Offset, int64_t Length, bool HasProlog = false)
: Offset(Offset), Length(Length), HasProlog(HasProlog) {}
@@ -89,11 +101,21 @@ struct FrameInfo {
bool empty() const {
if (!Instructions.empty())
return false;
- for (const auto &E : EpilogMap)
- if (!E.second.Instructions.empty())
+ for (const auto &E : Epilogs)
+ if (!E.Instructions.empty())
return false;
return true;
}
+
+ auto findEpilog(const MCSymbol *Start) const {
+ return llvm::find_if(Epilogs,
+ [Start](const Epilog &E) { return E.Start == Start; });
+ }
+
+ auto findEpilog(const MCSymbol *Start) {
+ return llvm::find_if(Epilogs,
+ [Start](Epilog &E) { return E.Start == Start; });
+ }
};
class UnwindEmitter {
diff --git a/llvm/lib/MC/MCAsmStreamer.cpp b/llvm/lib/MC/MCAsmStreamer.cpp
index fe6bb8c965147..7dd9a203325e2 100644
--- a/llvm/lib/MC/MCAsmStreamer.cpp
+++ b/llvm/lib/MC/MCAsmStreamer.cpp
@@ -390,6 +390,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;
@@ -2304,6 +2306,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 f040954efb6b5..7b78ccc22c7df 100644
--- a/llvm/lib/MC/MCStreamer.cpp
+++ b/llvm/lib/MC/MCStreamer.cpp
@@ -743,6 +743,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) {
@@ -1000,8 +1001,10 @@ void MCStreamer::emitWinCFIBeginEpilogue(SMLoc Loc) {
"(.seh_endprologue) in " +
CurFrame->Function->getName());
- InEpilogCFI = true;
- CurrentEpilog = emitCFILabel();
+ CurFrame->Epilogs.push_back(WinEH::FrameInfo::Epilog());
+ CurrentWinEpilog = &CurFrame->Epilogs.back();
+ CurrentWinEpilog->Start = emitCFILabel();
+ CurrentWinEpilog->Loc = Loc;
}
void MCStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
@@ -1009,14 +1012,50 @@ void MCStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
if (!CurFrame)
return;
- if (!InEpilogCFI)
+ if (!CurrentWinEpilog)
return getContext().reportError(Loc, "Stray .seh_endepilogue in " +
CurFrame->Function->getName());
- InEpilogCFI = false;
+ if ((CurFrame->Version >= 2) && !CurrentWinEpilog->UnwindV2Start)
+ return getContext().reportError(Loc, "Missing .seh_unwindv2start in " +
+ CurFrame->Function->getName());
+
+ CurrentWinEpilog->End = emitCFILabel();
+ CurrentWinEpilog = nullptr;
+}
+
+void MCStreamer::emitWinCFIUnwindV2Start(SMLoc Loc) {
+ WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
+ if (!CurFrame)
+ return;
+
+ if (!CurrentWinEpilog)
+ return getContext().reportError(Loc, "Stray .seh_unwindv2start in " +
+ CurFrame->Function->getName());
+
+ if (CurrentWinEpilog->UnwindV2Start)
+ return getContext().reportError(Loc, "Duplicate .seh_unwindv2start in " +
+ CurFrame->Function->getName());
+
MCSymbol *Label = emitCFILabel();
- CurFrame->EpilogMap[CurrentEpilog].End = Label;
- CurrentEpilog = nullptr;
+ CurrentWinEpilog->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) {}
diff --git a/llvm/lib/MC/MCWin64EH.cpp b/llvm/lib/MC/MCWin64EH.cpp
index bd5cf354659b6..5805daf2dca5d 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 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,90 @@ 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->Epilogs.empty();
+ if (EnableUnwindV2) {
+ auto &LastEpilog = info->Epilogs.back();
+
+ // 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->Epilogs.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 +309,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 +319,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->Epilogs)) {
+ 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 +379,39 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
}
}
+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);
+ if (!Offset) {
+ Asm->getContext().reportError(
+ Loc, "Failed to evaluate epilog offset for Unwind v2");
+ 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");
+ return false;
+ }
+
+ // Sanity check 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");
+ return false;
+ }
+
+ auto HighBits = *Offset >> 8;
+ Res = MCValue::get((HighBits << 12) | (Win64EH::UOP_Epilog << 8) |
+ (*Offset & 0xFF));
+ return true;
+}
+
void llvm::Win64EH::UnwindEmitter::Emit(MCStreamer &Streamer) const {
// Emit the unwind info structs first.
for (const auto &CFI : Streamer.getWinFrameInfos()) {
@@ -276,18 +454,8 @@ static const MCExpr *GetSubDivExpr(MCStreamer &Streamer, const MCSymbol *LHS,
static std::optional<int64_t> GetOptionalAbsDifference(MCStreamer &Streamer,
const MCSymbol *LHS,
const MCSymbol *RHS) {
- MCContext &Context = Streamer.getContext();
- const MCExpr *Diff =
- MCBinaryExpr::createSub(MCSymbolRefExpr::create(LHS, Context),
- MCSymbolRefExpr::create(RHS, Context), Context);
MCObjectStreamer *OS = (MCObjectStreamer *)(&Streamer);
- // 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, OS->getAssembler()))
- return std::nullopt;
- return value;
+ return GetOptionalAbsDifference(OS->getAssembler(), LHS, RHS);
}
static int64_t GetAbsDifference(MCStreamer &Streamer, const MCSymbol *LHS,
@@ -647,15 +815,14 @@ static void ARM64EmitUnwindCode(MCStreamer &streamer,
// sequence, if it exists. Otherwise, returns nullptr.
// EpilogInstrs - Unwind codes for the current epilog.
// Epilogs - Epilogs that potentialy match the current epilog.
-static MCSymbol*
-FindMatchingEpilog(const std::vector<WinEH::Instruction>& EpilogInstrs,
- const std::vector<MCSymbol *>& Epilogs,
+static const MCSymbol *
+FindMatchingEpilog(const std::vector<WinEH::Instruction> &EpilogInstrs,
+ const std::vector<const MCSymbol *> &Epilogs,
const WinEH::FrameInfo *info) {
for (auto *EpilogStart : Epilogs) {
- auto InstrsIter = info->EpilogMap.find(EpilogStart);
- assert(InstrsIter != info->EpilogMap.end() &&
- "Epilog not found in EpilogMap");
- const auto &Instrs = InstrsIter->second.Instructions;
+ auto Epilog = info->findEpilog(EpilogStart);
+ assert(Epilog != info->Epilogs.end() && "Epilog not found in Epilogs");
+ const auto &Instrs = Epilog->Instructions;
if (Instrs.size() != EpilogInstrs.size())
continue;
@@ -765,15 +932,16 @@ static int checkARM64PackedEpilog(MCStreamer &streamer, WinEH::FrameInfo *info,
if (Seg->Epilogs.size() != 1)
return -1;
- MCSymbol *Sym = Seg->Epilogs.begin()->first;
- const std::vector<WinEH::Instruction> &Epilog =
- info->EpilogMap[Sym].Instructions;
+ const MCSymbol *Sym = Seg->Epilogs.begin()->Symbol;
+ auto Epilog = info->findEpilog(Sym);
+ assert(Epilog != info->Epilogs.end());
+ const std::vector<WinEH::Instruction> &EpilogInstr = Epilog->Instructions;
// Check that the epilog actually is at the very end of the function,
// otherwise it can't be packed.
uint32_t DistanceFromEnd =
- (uint32_t)(Seg->Offset + Seg->Length - Seg->Epilogs.begin()->second);
- if (DistanceFromEnd / 4 != Epilog.size())
+ (uint32_t)(Seg->Offset + Seg->Length - Seg->Epilogs.begin()->Offset);
+ if (DistanceFromEnd / 4 != EpilogInstr.size())
return -1;
int RetVal = -1;
@@ -782,10 +950,10 @@ static int checkARM64PackedEpilog(MCStreamer &streamer, WinEH::FrameInfo *info,
// the end of the function and the offset (pointing after the prolog) fits
// as a packed offset.
if (PrologCodeBytes <= 31 &&
- PrologCodeBytes + ARM64CountOfUnwindCodes(Epilog) <= 124)
+ PrologCodeBytes + ARM64CountOfUnwindCodes(EpilogInstr) <= 124)
RetVal = PrologCodeBytes;
- int Offset = getARM64OffsetInProlog(info->Instructions, Epilog);
+ int Offset = getARM64OffsetInProlog(info->Instructions, EpilogInstr);
if (Offset < 0)
return RetVal;
@@ -797,7 +965,7 @@ static int checkARM64PackedEpilog(MCStreamer &streamer, WinEH::FrameInfo *info,
// As we choose to express the epilog as part of the prolog, remove the
// epilog from the map, so we don't try to emit its opcodes.
- info->EpilogMap.erase(Sym);
+ info->Epilogs.erase(Epilog);
return Offset;
}
@@ -1078,44 +1246,53 @@ static bool tryARM64PackedUnwind(WinEH::FrameInfo *info, uint32_t FuncLength,
return true;
}
+struct ARM64EpilogInfo {
+ const MCSymbol *Start;
+ int64_t Offset;
+ uint32_t Info;
+};
+
static void ARM64ProcessEpilogs(WinEH::FrameInfo *info,
- WinEH::FrameInfo::Segment *Seg,
+ const WinEH::FrameInfo::Segment *Seg,
uint32_t &TotalCodeBytes,
- MapVector<MCSymbol *, uint32_t> &EpilogInfo) {
-
- std::vector<MCSymbol *> EpilogStarts;
- for (auto &I : Seg->Epilogs)
- EpilogStarts.push_back(I.first);
-
+ std::vector<ARM64EpilogInfo> &EpilogInfo) {
// Epilogs processed so far.
- std::vector<MCSymbol *> AddedEpilogs;
- for (auto *S : EpilogStarts) {
- MCSymbol *EpilogStart = S;
- auto &EpilogInstrs = info->EpilogMap[S].Instructions;
+ std::vector<const MCSymbol *> AddedEpilogs;
+ for (auto &[EpilogStart, Offset] : Seg->Epilogs) {
+ auto Epilog = info->findEpilog(EpilogStart);
+ // If we were able to use a packed epilog, then it will have been removed
+ // from the epilogs list.
+ if (Epilog == info->Epilogs.end())
+ continue;
+ auto &EpilogInstrs = Epilog->Instructions;
uint32_t CodeBytes = ARM64CountOfUnwindCodes(EpilogInstrs);
- MCSymbol* MatchingEpilog =
- FindMatchingEpilog(EpilogInstrs, AddedEpilogs, info);
+ const MCSymbol *MatchingEpilog =
+ FindMatchingEpilog(EpilogInstrs, AddedEpilogs, info);
int PrologOffset;
if (MatchingEpilog) {
- assert(EpilogInfo.contains(MatchingEpilog) &&
+ auto MatchingEpilogInfo = llvm::find_if(
+ EpilogInfo, [MatchingEpilog](const ARM64EpilogInfo &EI) {
+ return EI.Start == MatchingEpilog;
+ });
+ assert(MatchingEpilogInfo != EpilogInfo.end() &&
"Duplicate epilog not found");
- EpilogInfo[EpilogStart] = EpilogInfo.lookup(MatchingEpilog);
- // Clear the unwind codes in the EpilogMap, so that they don't get output
+ EpilogInfo.push_back({EpilogStart, Offset, MatchingEpilogInfo->Info});
+ // Clear the unwind codes in the Epilogs, so that they don't get output
// in ARM64EmitUnwindInfoForSegment().
EpilogInstrs.clear();
} else if ((PrologOffset = getARM64OffsetInProlog(info->Instructions,
EpilogInstrs)) >= 0) {
- EpilogInfo[EpilogStart] = PrologOffset;
// If the segment doesn't have a prolog, an end_c will be emitted before
// prolog opcodes. So epilog start index in opcodes array is moved by 1.
if (!Seg->HasProlog)
- EpilogInfo[EpilogStart] += 1;
- // Clear the unwind codes in the EpilogMap, so that they don't get output
+ PrologOffset += 1;
+ EpilogInfo.push_back({EpilogStart, Offset, uint32_t(PrologOffset)});
+ // Clear the unwind codes in the Epilogs, so that they don't get output
// in ARM64EmitUnwindInfoForSegment().
EpilogInstrs.clear();
} else {
- EpilogInfo[EpilogStart] = TotalCodeBytes;
+ EpilogInfo.push_back({EpilogStart, Offset, TotalCodeBytes});
TotalCodeBytes += CodeBytes;
AddedEpilogs.push_back(EpilogStart);
}
@@ -1130,17 +1307,17 @@ static void ARM64FindSegmentsInFunction(MCStreamer &streamer,
info->PrologEnd, info->Function->getName(),
"prologue");
struct EpilogStartEnd {
- MCSymbol *Start;
+ const MCSymbol *Start;
int64_t Offset;
int64_t End;
};
// Record Start and End of each epilog.
SmallVector<struct EpilogStartEnd, 4> Epilogs;
- for (auto &I : info->EpilogMap) {
- MCSymbol *Start = I.first;
- auto &Instrs = I.second.Instructions;
+ for (auto &I : info->Epilogs) {
+ const MCSymbol *Start = I.Start;
+ auto &Instrs = I.Instructions;
int64_t Offset = GetAbsDifference(streamer, Start, info->Begin);
- checkARM64Instructions(streamer, Instrs, Start, I.second.End,
+ checkARM64Instructions(streamer, Instrs, Start, I.End,
info->Function->getName(), "epilogue");
assert((Epilogs.size() == 0 || Offset >= Epilogs.back().End) &&
"Epilogs should be monotonically ordered");
@@ -1164,11 +1341,11 @@ static void ARM64FindSegmentsInFunction(MCStreamer &streamer,
int64_t SegLength = SegLimit;
int64_t SegEnd = SegOffset + SegLength;
// Keep record on symbols and offsets of epilogs in this segment.
- MapVector<MCSymbol *, int64_t> EpilogsInSegment;
+ std::vector<WinEH::FrameInfo::Segment::Epilog> EpilogsInSegment;
while (E < Epilogs.size() && Epilogs[E].End < SegEnd) {
// Epilogs within current segment.
- EpilogsInSegment[Epilogs[E].Start] = Epilogs[E].Offset;
+ EpilogsInSegment.push_back({Epilogs[E].Start, Epilogs[E].Offset});
++E;
}
@@ -1199,7 +1376,7 @@ static void ARM64FindSegmentsInFunction(MCStreamer &streamer,
WinEH::FrameInfo::Segment(SegOffset, RawFuncLength - SegOffset,
/* HasProlog */!SegOffset);
for (; E < Epilogs.size(); ++E)
- LastSeg.Epilogs[Epilogs[E].Start] = Epilogs[E].Offset;
+ LastSeg.Epilogs.push_back({Epilogs[E].Start, Epilogs[E].Offset});
info->Segments.push_back(LastSeg);
}
@@ -1263,7 +1440,7 @@ static void ARM64EmitUnwindInfoForSegment(MCStreamer &streamer,
uint32_t TotalCodeBytes = PrologCodeBytes;
// Process epilogs.
- MapVector<MCSymbol *, uint32_t> EpilogInfo;
+ std::vector<ARM64EpilogInfo> EpilogInfo;
ARM64ProcessEpilogs(info, &Seg, TotalCodeBytes, EpilogInfo);
// Code Words, Epilog count, E, X, Vers, Function Length
@@ -1302,11 +1479,9 @@ static void ARM64EmitUnwindInfoForSegment(MCStreamer &streamer,
if (PackedEpilogOffset < 0) {
// Epilog Start Index, Epilog Start Offset
- for (auto &I : EpilogInfo) {
- MCSymbol *EpilogStart = I.first;
- uint32_t EpilogIndex = I.second;
+ for (auto &[EpilogStart, EpilogOffsetInFunc, EpilogIndex] : EpilogInfo) {
// Epilog offset within the Segment.
- uint32_t EpilogOffset = (uint32_t)(Seg.Epilogs[EpilogStart] - Seg.Offset);
+ uint32_t EpilogOffset = (uint32_t)(EpilogOffsetInFunc - Seg.Offset);
if (EpilogOffset)
EpilogOffset /= 4;
uint32_t row3 = EpilogOffset;
@@ -1330,8 +1505,13 @@ static void ARM64EmitUnwindInfoForSegment(MCStreamer &streamer,
ARM64EmitUnwindCode(streamer, Inst);
// Emit epilog unwind instructions
- for (auto &I : Seg.Epilogs) {
- auto &EpilogInstrs = info->EpilogMap[I.first].Instructions;
+ for (auto &[Start, _] : Seg.Epilogs) {
+ auto Epilog = info->findEpilog(Start);
+ // If we were able to use a packed epilog, then it will have been removed
+ // from the epilogs list.
+ if (Epilog == info->Epilogs.end())
+ continue;
+ auto &EpilogInstrs = Epilog->Instructions;
for (const WinEH::Instruction &inst : EpilogInstrs)
ARM64EmitUnwindCode(streamer, inst);
}
@@ -1378,8 +1558,8 @@ static void ARM64EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
}
simplifyARM64Opcodes(info->Instructions, false);
- for (auto &I : info->EpilogMap)
- simplifyARM64Opcodes(I.second.Instructions, true);
+ for (auto &I : info->Epilogs)
+ simplifyARM64Opcodes(I.Instructions, true);
int64_t RawFuncLength;
if (!info->FuncletOrFuncEnd) {
@@ -1771,10 +1951,10 @@ static int getARMOffsetInProlog(const std::vector<WinEH::Instruction> &Prolog,
static int checkARMPackedEpilog(MCStreamer &streamer, WinEH::FrameInfo *info,
int PrologCodeBytes) {
// Can only pack if there's one single epilog
- if (info->EpilogMap.size() != 1)
+ if (info->Epilogs.size() != 1)
return -1;
- const WinEH::FrameInfo::Epilog &EpilogInfo = info->EpilogMap.begin()->second;
+ const WinEH::FrameInfo::Epilog &EpilogInfo = *info->Epilogs.begin();
// Can only pack if the epilog is unconditional
if (EpilogInfo.Condition != 0xe) // ARMCC::AL
return -1;
@@ -1787,7 +1967,7 @@ static int checkARMPackedEpilog(MCStreamer &streamer, WinEH::FrameInfo *info,
// Check that the epilog actually is at the very end of the function,
// otherwise it can't be packed.
std::optional<int64_t> MaybeDistance = GetOptionalAbsDifference(
- streamer, info->FuncletOrFuncEnd, info->EpilogMap.begin()->first);
+ streamer, info->FuncletOrFuncEnd, info->Epilogs.begin()->Start);
if (!MaybeDistance)
return -1;
uint32_t DistanceFromEnd = (uint32_t)*MaybeDistance;
@@ -1821,7 +2001,7 @@ static int checkARMPackedEpilog(MCStreamer &streamer, WinEH::FrameInfo *info,
// As we choose to express the epilog as part of the prolog, remove the
// epilog from the map, so we don't try to emit its opcodes.
- info->EpilogMap.clear();
+ info->Epilogs.clear();
return Offset;
}
@@ -1996,24 +2176,23 @@ static bool tryARMPackedUnwind(MCStreamer &streamer, WinEH::FrameInfo *info,
return false;
// Packed uneind info can't express multiple epilogues.
- if (info->EpilogMap.size() > 1)
+ if (info->Epilogs.size() > 1)
return false;
unsigned EF = 0;
int Ret = 0;
- if (info->EpilogMap.size() == 0) {
+ if (info->Epilogs.size() == 0) {
Ret = 3; // No epilogue
} else {
// As the prologue and epilogue aren't exact mirrors of each other,
// we have to check the epilogue too and see if it matches what we've
// concluded from the prologue.
- const WinEH::FrameInfo::Epilog &EpilogInfo =
- info->EpilogMap.begin()->second;
+ const WinEH::FrameInfo::Epilog &EpilogInfo = *info->Epilogs.begin();
if (EpilogInfo.Condition != 0xe) // ARMCC::AL
return false;
const std::vector<WinEH::Instruction> &Epilog = EpilogInfo.Instructions;
std::optional<int64_t> MaybeDistance = GetOptionalAbsDifference(
- streamer, info->FuncletOrFuncEnd, info->EpilogMap.begin()->first);
+ streamer, info->FuncletOrFuncEnd, info->Epilogs.begin()->Start);
if (!MaybeDistance)
return false;
uint32_t DistanceFromEnd = (uint32_t)*MaybeDistance;
@@ -2310,9 +2489,8 @@ static void ARMEmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
checkARMInstructions(streamer, info->Instructions, info->Begin,
info->PrologEnd, info->Function->getName(),
"prologue");
- for (auto &I : info->EpilogMap) {
- MCSymbol *EpilogStart = I.first;
- auto &Epilog = I.second;
+ for (auto &Epilog : info->Epilogs) {
+ const MCSymbol *EpilogStart = Epilog.Start;
checkARMInstructions(streamer, Epilog.Instructions, EpilogStart, Epilog.End,
info->Function->getName(), "epilogue");
if (Epilog.Instructions.empty() ||
@@ -2367,24 +2545,32 @@ static void ARMEmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
checkARMPackedEpilog(streamer, info, PrologCodeBytes);
// Process epilogs.
- MapVector<MCSymbol *, uint32_t> EpilogInfo;
+ struct ARMEpilogInfo {
+ const MCSymbol *Start;
+ uint32_t Info;
+ };
+ std::vector<ARMEpilogInfo> EpilogInfo;
// Epilogs processed so far.
- std::vector<MCSymbol *> AddedEpilogs;
+ std::vector<const MCSymbol *> AddedEpilogs;
bool CanTweakProlog = true;
- for (auto &I : info->EpilogMap) {
- MCSymbol *EpilogStart = I.first;
- auto &EpilogInstrs = I.second.Instructions;
+ for (auto &I : info->Epilogs) {
+ const MCSymbol *EpilogStart = I.Start;
+ auto &EpilogInstrs = I.Instructions;
uint32_t CodeBytes = ARMCountOfUnwindCodes(EpilogInstrs);
- MCSymbol *MatchingEpilog =
+ const MCSymbol *MatchingEpilog =
FindMatchingEpilog(EpilogInstrs, AddedEpilogs, info);
int PrologOffset;
if (MatchingEpilog) {
- assert(EpilogInfo.contains(MatchingEpilog) &&
+ auto MatchingEpilogInfo =
+ llvm::find_if(EpilogInfo, [MatchingEpilog](const ARMEpilogInfo &EI) {
+ return EI.Start == MatchingEpilog;
+ });
+ assert(MatchingEpilogInfo != EpilogInfo.end() &&
"Duplicate epilog not found");
- EpilogInfo[EpilogStart] = EpilogInfo.lookup(MatchingEpilog);
- // Clear the unwind codes in the EpilogMap, so that they don't get output
+ EpilogInfo.push_back({EpilogStart, MatchingEpilogInfo->Info});
+ // Clear the unwind codes in the Epilogs, so that they don't get output
// in the logic below.
EpilogInstrs.clear();
} else if ((PrologOffset = getARMOffsetInProlog(
@@ -2396,12 +2582,12 @@ static void ARMEmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
// Later epilogs need a strict match for the end opcode.
CanTweakProlog = false;
}
- EpilogInfo[EpilogStart] = PrologOffset;
- // Clear the unwind codes in the EpilogMap, so that they don't get output
+ EpilogInfo.push_back({EpilogStart, uint32_t(PrologOffset)});
+ // Clear the unwind codes in the Epilogs, so that they don't get output
// in the logic below.
EpilogInstrs.clear();
} else {
- EpilogInfo[EpilogStart] = TotalCodeBytes;
+ EpilogInfo.push_back({EpilogStart, TotalCodeBytes});
TotalCodeBytes += CodeBytes;
AddedEpilogs.push_back(EpilogStart);
}
@@ -2414,7 +2600,7 @@ static void ARMEmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
if (CodeWordsMod)
CodeWords++;
uint32_t EpilogCount =
- PackedEpilogOffset >= 0 ? PackedEpilogOffset : info->EpilogMap.size();
+ PackedEpilogOffset >= 0 ? PackedEpilogOffset : info->Epilogs.size();
bool ExtensionWord = EpilogCount > 31 || CodeWords > 15;
if (!ExtensionWord) {
row1 |= (EpilogCount & 0x1F) << 23;
@@ -2448,10 +2634,7 @@ static void ARMEmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
if (PackedEpilogOffset < 0) {
// Epilog Start Index, Epilog Start Offset
- for (auto &I : EpilogInfo) {
- MCSymbol *EpilogStart = I.first;
- uint32_t EpilogIndex = I.second;
-
+ for (auto &[EpilogStart, EpilogIndex] : EpilogInfo) {
std::optional<int64_t> MaybeEpilogOffset =
GetOptionalAbsDifference(streamer, EpilogStart, info->Begin);
const MCExpr *OffsetExpr = nullptr;
@@ -2461,8 +2644,9 @@ static void ARMEmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
else
OffsetExpr = GetSubDivExpr(streamer, EpilogStart, info->Begin, 2);
- assert(info->EpilogMap.contains(EpilogStart));
- unsigned Condition = info->EpilogMap[EpilogStart].Condition;
+ auto Epilog = info->findEpilog(EpilogStart);
+ assert(Epilog != info->Epilogs.end());
+ unsigned Condition = Epilog->Condition;
assert(Condition <= 0xf);
uint32_t row3 = EpilogOffset;
@@ -2487,8 +2671,8 @@ static void ARMEmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
}
// Emit epilog unwind instructions
- for (auto &I : info->EpilogMap) {
- auto &EpilogInstrs = I.second.Instructions;
+ for (auto &I : info->Epilogs) {
+ auto &EpilogInstrs = I.Instructions;
for (const WinEH::Instruction &inst : EpilogInstrs)
ARMEmitUnwindCode(streamer, inst);
}
diff --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp
index fb8a3e1215d90..adac8b4f1c9fa 100644
--- a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp
+++ b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp
@@ -74,7 +74,7 @@ void AArch64TargetWinCOFFStreamer::emitARM64WinUnwindCode(unsigned UnwindCode,
return;
auto Inst = WinEH::Instruction(UnwindCode, /*Label=*/nullptr, Reg, Offset);
if (S.isInEpilogCFI())
- CurFrame->EpilogMap[S.getCurrentEpilog()].Instructions.push_back(Inst);
+ S.getCurrentWinEpilog()->Instructions.push_back(Inst);
else
CurFrame->Instructions.push_back(Inst);
}
@@ -195,7 +195,7 @@ void AArch64TargetWinCOFFStreamer::emitARM64WinCFIEpilogEnd() {
if (S.isInEpilogCFI()) {
WinEH::Instruction Inst =
WinEH::Instruction(Win64EH::UOP_End, /*Label=*/nullptr, -1, 0);
- CurFrame->EpilogMap[S.getCurrentEpilog()].Instructions.push_back(Inst);
+ S.getCurrentWinEpilog()->Instructions.push_back(Inst);
}
S.emitWinCFIEndEpilogue();
}
diff --git a/llvm/lib/Target/ARM/MCTargetDesc/ARMWinCOFFStreamer.cpp b/llvm/lib/Target/ARM/MCTargetDesc/ARMWinCOFFStreamer.cpp
index 6566aa4c243be..ca366edad89ee 100644
--- a/llvm/lib/Target/ARM/MCTargetDesc/ARMWinCOFFStreamer.cpp
+++ b/llvm/lib/Target/ARM/MCTargetDesc/ARMWinCOFFStreamer.cpp
@@ -112,7 +112,7 @@ void ARMTargetWinCOFFStreamer::emitARMWinUnwindCode(unsigned UnwindCode,
MCSymbol *Label = S.emitCFILabel();
auto Inst = WinEH::Instruction(UnwindCode, Label, Reg, Offset);
if (S.isInEpilogCFI())
- CurFrame->EpilogMap[S.getCurrentEpilog()].Instructions.push_back(Inst);
+ S.getCurrentWinEpilog()->Instructions.push_back(Inst);
else
CurFrame->Instructions.push_back(Inst);
}
@@ -223,7 +223,7 @@ void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogStart(unsigned Condition) {
S.emitWinCFIBeginEpilogue();
if (S.isInEpilogCFI()) {
- CurFrame->EpilogMap[S.getCurrentEpilog()].Condition = Condition;
+ S.getCurrentWinEpilog()->Condition = Condition;
}
}
@@ -235,7 +235,7 @@ void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogEnd() {
if (S.isInEpilogCFI()) {
std::vector<WinEH::Instruction> &Epilog =
- CurFrame->EpilogMap[S.getCurrentEpilog()].Instructions;
+ S.getCurrentWinEpilog()->Instructions;
unsigned UnwindCode = Win64EH::UOP_End;
if (!Epilog.empty()) {
@@ -250,7 +250,7 @@ void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogEnd() {
}
WinEH::Instruction Inst = WinEH::Instruction(UnwindCode, nullptr, -1, 0);
- CurFrame->EpilogMap[S.getCurrentEpilog()].Instructions.push_back(Inst);
+ S.getCurrentWinEpilog()->Instructions.push_back(Inst);
}
S.emitWinCFIEndEpilogue();
}
diff --git a/llvm/lib/Target/X86/CMakeLists.txt b/llvm/lib/Target/X86/CMakeLists.txt
index 9553a8619feb5..db7a0c7d15536 100644
--- a/llvm/lib/Target/X86/CMakeLists.txt
+++ b/llvm/lib/Target/X86/CMakeLists.txt
@@ -83,6 +83,7 @@ set(sources
X86TargetTransformInfo.cpp
X86VZeroUpper.cpp
X86WinEHState.cpp
+ X86WinEHUnwindV2.cpp
X86WinFixupBufferSecurityCheck.cpp
X86InsertWait.cpp
GISel/X86CallLowering.cpp
diff --git a/llvm/lib/Target/X86/X86.h b/llvm/lib/Target/X86/X86.h
index 48a3fe1934a96..a36c1fd568a48 100644
--- a/llvm/lib/Target/X86/X86.h
+++ b/llvm/lib/Target/X86/X86.h
@@ -160,6 +160,9 @@ FunctionPass *createX86InsertX87waitPass();
/// ways.
FunctionPass *createX86PartialReductionPass();
+/// // Analyzes and emits pseudos to support Win x64 Unwind V2.
+FunctionPass *createX86WinEHUnwindV2Pass();
+
InstructionSelector *createX86InstructionSelector(const X86TargetMachine &TM,
const X86Subtarget &,
const X86RegisterBankInfo &);
@@ -204,6 +207,7 @@ void initializeX86ReturnThunksPass(PassRegistry &);
void initializeX86SpeculativeExecutionSideEffectSuppressionPass(PassRegistry &);
void initializeX86SpeculativeLoadHardeningPassPass(PassRegistry &);
void initializeX86TileConfigPass(PassRegistry &);
+void initializeX86WinEHUnwindV2Pass(PassRegistry &);
namespace X86AS {
enum : unsigned {
diff --git a/llvm/lib/Target/X86/X86InstrCompiler.td b/llvm/lib/Target/X86/X86InstrCompiler.td
index 9687ae29f1c78..300b3c545dcd3 100644
--- a/llvm/lib/Target/X86/X86InstrCompiler.td
+++ b/llvm/lib/Target/X86/X86InstrCompiler.td
@@ -258,6 +258,8 @@ let isPseudo = 1, isMeta = 1, isNotDuplicable = 1, SchedRW = [WriteSystem] in {
"#SEH_PushFrame $mode", []>;
def SEH_EndPrologue : I<0, Pseudo, (outs), (ins),
"#SEH_EndPrologue", []>;
+ def SEH_UnwindVersion : I<0, Pseudo, (outs), (ins i1imm:$version),
+ "#SEH_UnwindVersion $version", []>;
}
// Epilog instructions:
@@ -266,6 +268,8 @@ let isPseudo = 1, isMeta = 1, SchedRW = [WriteSystem] in {
"#SEH_BeginEpilogue", []>;
def SEH_EndEpilogue : I<0, Pseudo, (outs), (ins),
"#SEH_EndEpilogue", []>;
+ def SEH_UnwindV2Start : I<0, Pseudo, (outs), (ins),
+ "#SEH_UnwindV2Start", []>;
}
//===----------------------------------------------------------------------===//
diff --git a/llvm/lib/Target/X86/X86MCInstLower.cpp b/llvm/lib/Target/X86/X86MCInstLower.cpp
index c172c9e60795f..1689ff6cffa1f 100644
--- a/llvm/lib/Target/X86/X86MCInstLower.cpp
+++ b/llvm/lib/Target/X86/X86MCInstLower.cpp
@@ -1791,6 +1791,14 @@ void X86AsmPrinter::EmitSEHInstruction(const MachineInstr *MI) {
OutStreamer->emitWinCFIEndEpilogue();
break;
+ case X86::SEH_UnwindV2Start:
+ OutStreamer->emitWinCFIUnwindV2Start();
+ break;
+
+ case X86::SEH_UnwindVersion:
+ OutStreamer->emitWinCFIUnwindVersion(MI->getOperand(0).getImm());
+ break;
+
default:
llvm_unreachable("expected SEH_ instruction");
}
@@ -2433,6 +2441,8 @@ void X86AsmPrinter::emitInstruction(const MachineInstr *MI) {
case X86::SEH_PushFrame:
case X86::SEH_EndPrologue:
case X86::SEH_EndEpilogue:
+ case X86::SEH_UnwindV2Start:
+ case X86::SEH_UnwindVersion:
EmitSEHInstruction(MI);
return;
diff --git a/llvm/lib/Target/X86/X86TargetMachine.cpp b/llvm/lib/Target/X86/X86TargetMachine.cpp
index 4cecbbf27aa30..68572d9b411c2 100644
--- a/llvm/lib/Target/X86/X86TargetMachine.cpp
+++ b/llvm/lib/Target/X86/X86TargetMachine.cpp
@@ -105,6 +105,7 @@ extern "C" LLVM_C_ABI void LLVMInitializeX86Target() {
initializeX86FixupInstTuningPassPass(PR);
initializeX86FixupVectorConstantsPassPass(PR);
initializeX86DynAllocaExpanderPass(PR);
+ initializeX86WinEHUnwindV2Pass(PR);
}
static std::unique_ptr<TargetLoweringObjectFile> createTLOF(const Triple &TT) {
@@ -666,6 +667,11 @@ void X86PassConfig::addPreEmitPass2() {
(M->getFunction("objc_retainAutoreleasedReturnValue") ||
M->getFunction("objc_unsafeClaimAutoreleasedReturnValue")));
}));
+
+ // Analyzes and emits pseudos to support Win x64 Unwind V2. This pass must run
+ // after all real instructions have been added to the epilog.
+ if (TT.isOSWindows() && (TT.getArch() == Triple::x86_64))
+ addPass(createX86WinEHUnwindV2Pass());
}
bool X86PassConfig::addPostFastRegAllocRewrite() {
diff --git a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
new file mode 100644
index 0000000000000..2c1f9a5746e38
--- /dev/null
+++ b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
@@ -0,0 +1,221 @@
+//===-- X86WinEHUnwindV2.cpp - Win x64 Unwind v2 ----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// Implements the analysis required to detect if a function can use Unwind v2
+/// information, and emits the neccesary pseudo instructions used by MC to
+/// generate the unwind info.
+///
+//===----------------------------------------------------------------------===//
+
+#include "MCTargetDesc/X86BaseInfo.h"
+#include "X86.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/CodeGen/MachineBasicBlock.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/CodeGen/TargetInstrInfo.h"
+#include "llvm/CodeGen/TargetSubtargetInfo.h"
+#include "llvm/IR/Module.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "x86-wineh-unwindv2"
+
+STATISTIC(MeetsUnwindV2Criteria,
+ "Number of functions that meet Unwind v2 criteria");
+STATISTIC(FailsUnwindV2Criteria,
+ "Number of functions that fail Unwind v2 criteria");
+
+namespace {
+
+class X86WinEHUnwindV2 : public MachineFunctionPass {
+public:
+ static char ID;
+
+ X86WinEHUnwindV2() : MachineFunctionPass(ID) {
+ initializeX86WinEHUnwindV2Pass(*PassRegistry::getPassRegistry());
+ }
+
+ StringRef getPassName() const override { return "WinEH Unwind V2"; }
+
+ bool runOnMachineFunction(MachineFunction &MF) override;
+ bool rejectCurrentFunction() const {
+ FailsUnwindV2Criteria++;
+ return false;
+ }
+};
+
+enum class FunctionState {
+ InProlog,
+ HasProlog,
+ InEpilog,
+ FinishedEpilog,
+};
+
+} // end anonymous namespace
+
+char X86WinEHUnwindV2::ID = 0;
+
+INITIALIZE_PASS(X86WinEHUnwindV2, "x86-wineh-unwindv2",
+ "Analyze and emit instructions for Win64 Unwind v2", false,
+ false)
+
+FunctionPass *llvm::createX86WinEHUnwindV2Pass() {
+ return new X86WinEHUnwindV2();
+}
+
+bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
+ if (!MF.getFunction().getParent()->getModuleFlag("winx64-eh-unwindv2"))
+ return false;
+
+ // Current state of processing the function. We'll assume that all functions
+ // start with a prolog.
+ FunctionState State = FunctionState::InProlog;
+
+ // Prolog information.
+ SmallVector<int64_t> PushedRegs;
+ bool HasStackAlloc = false;
+
+ // Requested changes.
+ SmallVector<MachineInstr *> UnwindV2StartLocations;
+
+ for (MachineBasicBlock &MBB : MF) {
+ // Current epilog information. We assume that epilogs cannot cross basic
+ // block boundaries.
+ unsigned PoppedRegCount = 0;
+ bool HasStackDealloc = false;
+ MachineInstr *UnwindV2StartLocation = nullptr;
+
+ for (MachineInstr &MI : MBB) {
+ switch (MI.getOpcode()) {
+ //
+ // Prolog handling.
+ //
+ case X86::SEH_PushReg:
+ if (State != FunctionState::InProlog)
+ llvm_unreachable("SEH_PushReg outside of prolog");
+ PushedRegs.push_back(MI.getOperand(0).getImm());
+ break;
+
+ case X86::SEH_StackAlloc:
+ case X86::SEH_SetFrame:
+ if (State != FunctionState::InProlog)
+ llvm_unreachable("SEH_StackAlloc or SEH_SetFrame outside of prolog");
+ HasStackAlloc = true;
+ break;
+
+ case X86::SEH_EndPrologue:
+ if (State != FunctionState::InProlog)
+ llvm_unreachable("SEH_EndPrologue outside of prolog");
+ State = FunctionState::HasProlog;
+ break;
+
+ //
+ // Epilog handling.
+ //
+ case X86::SEH_BeginEpilogue:
+ if (State != FunctionState::HasProlog)
+ llvm_unreachable("SEH_BeginEpilogue in prolog or another epilog");
+ State = FunctionState::InEpilog;
+ break;
+
+ case X86::SEH_EndEpilogue:
+ if (State != FunctionState::InEpilog)
+ llvm_unreachable("SEH_EndEpilogue outside of epilog");
+ if ((HasStackAlloc != HasStackDealloc) ||
+ (PoppedRegCount != PushedRegs.size()))
+ // Non-canonical epilog, reject the function.
+ return rejectCurrentFunction();
+
+ // If we didn't find the start location, then use the end of the
+ // epilog.
+ if (!UnwindV2StartLocation)
+ UnwindV2StartLocation = &MI;
+ UnwindV2StartLocations.push_back(UnwindV2StartLocation);
+ State = FunctionState::FinishedEpilog;
+ break;
+
+ case X86::MOV64rr:
+ case X86::ADD64ri32:
+ if (State == FunctionState::InEpilog) {
+ // If the prolog contains a stack allocation, then the first
+ // instruction in the epilog must be to adjust the stack pointer.
+ if (!HasStackAlloc || HasStackDealloc || (PoppedRegCount > 0)) {
+ return rejectCurrentFunction();
+ }
+ HasStackDealloc = true;
+ } else if (State == FunctionState::FinishedEpilog)
+ // Unexpected instruction after the epilog.
+ return rejectCurrentFunction();
+ break;
+
+ case X86::POP64r:
+ if (State == FunctionState::InEpilog) {
+ // After the stack pointer has been adjusted, the epilog must
+ // POP each register in reverse order of the PUSHes in the prolog.
+ PoppedRegCount++;
+ if ((HasStackAlloc != HasStackDealloc) ||
+ (PoppedRegCount > PushedRegs.size()) ||
+ (PushedRegs[PushedRegs.size() - PoppedRegCount] !=
+ MI.getOperand(0).getReg())) {
+ return rejectCurrentFunction();
+ }
+
+ // Unwind v2 records the size of the epilog not from where we place
+ // SEH_BeginEpilogue (as that contains the instruction to adjust the
+ // stack pointer) but from the first POP instruction (if there is
+ // one).
+ if (!UnwindV2StartLocation) {
+ assert(PoppedRegCount == 1);
+ UnwindV2StartLocation = &MI;
+ }
+ } else if (State == FunctionState::FinishedEpilog)
+ // Unexpected instruction after the epilog.
+ return rejectCurrentFunction();
+ break;
+
+ default:
+ if (MI.isTerminator()) {
+ if (State == FunctionState::FinishedEpilog)
+ // Found the terminator after the epilog, we're now ready for
+ // another epilog.
+ State = FunctionState::HasProlog;
+ else if (State == FunctionState::InEpilog)
+ llvm_unreachable("Terminator in the middle of the epilog");
+ } else if (!MI.isDebugOrPseudoInstr()) {
+ if ((State == FunctionState::FinishedEpilog) ||
+ (State == FunctionState::InEpilog))
+ // Unknown instruction in or after the epilog.
+ return rejectCurrentFunction();
+ }
+ }
+ }
+ }
+
+ if (UnwindV2StartLocations.empty()) {
+ assert(State == FunctionState::InProlog &&
+ "If there are no epilogs, then there should be no prolog");
+ 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));
+ }
+ // Note that the function is using Unwind v2.
+ MachineBasicBlock &FirstMBB = MF.front();
+ BuildMI(FirstMBB, FirstMBB.front(), FirstMBB.front().getDebugLoc(),
+ TII->get(X86::SEH_UnwindVersion))
+ .addImm(2);
+
+ return true;
+}
diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
new file mode 100644
index 0000000000000..a9fd1b9ac2acd
--- /dev/null
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
@@ -0,0 +1,160 @@
+; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s
+
+define dso_local void @no_epilog() local_unnamed_addr {
+entry:
+ ret void
+}
+; CHECK-LABEL: no_epilog:
+; CHECK-NOT: .seh_
+; CHECK: retq
+
+define dso_local void @stack_alloc_no_pushes() local_unnamed_addr {
+entry:
+ call void @a()
+ ret void
+}
+; CHECK-LABEL: stack_alloc_no_pushes:
+; CHECK: .seh_unwindversion 2
+; CHECK-NOT: .seh_pushreg
+; CHECK: .seh_stackalloc
+; CHECK: .seh_endprologue
+; CHECK-NOT: .seh_endproc
+; CHECK: .seh_startepilogue
+; CHECK-NEXT: addq
+; CHECK-NEXT: .seh_unwindv2start
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: retq
+
+define dso_local i32 @stack_alloc_and_pushes(i32 %x) local_unnamed_addr {
+entry:
+ %call = tail call i32 @c(i32 %x)
+ %call1 = tail call i32 @c(i32 %x)
+ %add = add nsw i32 %call1, %call
+ %call2 = tail call i32 @c(i32 %x)
+ %call3 = tail call i32 @c(i32 %call2)
+ %add4 = add nsw i32 %add, %call3
+ ret i32 %add4
+}
+; CHECK-LABEL: stack_alloc_and_pushes:
+; CHECK: .seh_unwindversion 2
+; CHECK: .seh_pushreg %rsi
+; CHECK: .seh_pushreg %rdi
+; CHECK: .seh_pushreg %rbx
+; CHECK: .seh_stackalloc
+; CHECK: .seh_endprologue
+; CHECK-NOT: .seh_endproc
+; CHECK: .seh_startepilogue
+; CHECK-NEXT: addq
+; CHECK-NEXT: .seh_unwindv2start
+; CHECK-NEXT: popq %rbx
+; CHECK-NEXT: popq %rdi
+; CHECK-NEXT: popq %rsi
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: retq
+
+define dso_local i32 @tail_call(i32 %x) local_unnamed_addr {
+entry:
+ %call = tail call i32 @c(i32 %x)
+ %call1 = tail call i32 @c(i32 %call)
+ ret i32 %call1
+}
+; CHECK-LABEL: tail_call:
+; CHECK: .seh_unwindversion 2
+; CHECK-NOT: .seh_pushreg
+; CHECK: .seh_stackalloc
+; CHECK: .seh_endprologue
+; CHECK-NOT: .seh_endproc
+; CHECK: .seh_startepilogue
+; CHECK-NEXT: addq
+; CHECK-NEXT: .seh_unwindv2start
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: jmp
+
+define dso_local i32 @multiple_epilogs(i32 %x) local_unnamed_addr {
+entry:
+ %call = tail call i32 @c(i32 noundef %x)
+ %cmp = icmp sgt i32 %call, 0
+ br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+ %call1 = tail call i32 @c(i32 noundef %call)
+ ret i32 %call1
+
+if.else:
+ %call2 = tail call i32 @b()
+ ret i32 %call2
+}
+; CHECK-LABEL: multiple_epilogs:
+; CHECK: .seh_unwindversion 2
+; CHECK-NOT: .seh_pushreg
+; CHECK: .seh_stackalloc
+; CHECK: .seh_endprologue
+; CHECK-NOT: .seh_endproc
+; CHECK: .seh_startepilogue
+; CHECK-NEXT: addq
+; CHECK-NEXT: .seh_unwindv2start
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: jmp
+; CHECK-NOT: .seh_endproc
+; CHECK: .seh_startepilogue
+; CHECK-NEXT: addq
+; CHECK-NEXT: .seh_unwindv2start
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: jmp
+
+define dso_local i32 @mismatched_terminators() local_unnamed_addr {
+entry:
+ %call = tail call i32 @b()
+ %cmp = icmp sgt i32 %call, 0
+ br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+ %call1 = tail call i32 @b()
+ ret i32 %call1
+
+if.else:
+ ret i32 %call
+}
+; CHECK-LABEL: mismatched_terminators:
+; CHECK: .seh_unwindversion 2
+; CHECK-NOT: .seh_pushreg
+; CHECK: .seh_stackalloc
+; CHECK: .seh_endprologue
+; CHECK-NOT: .seh_endproc
+; CHECK: .seh_startepilogue
+; CHECK-NEXT: addq
+; CHECK-NEXT: .seh_unwindv2start
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: jmp
+; CHECK-NOT: .seh_endproc
+; CHECK: .seh_startepilogue
+; CHECK-NEXT: addq
+; CHECK-NEXT: .seh_unwindv2start
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: ret
+
+define dso_local void @dynamic_stack_alloc(i32 %x) local_unnamed_addr {
+entry:
+ %y = alloca i32, i32 %x
+ ret void
+}
+; CHECK-LABEL: dynamic_stack_alloc:
+; CHECK: .seh_unwindversion 2
+; CHECK: .seh_pushreg %rbp
+; CHECK: .seh_setframe %rbp, 0
+; CHECK: .seh_endprologue
+; CHECK-NOT: .seh_endproc
+; CHECK: .seh_startepilogue
+; CHECK-NEXT: movq %rbp, %rsp
+; CHECK-NEXT: .seh_unwindv2start
+; CHECK-NEXT: popq %rbp
+; CHECK-NEXT: .seh_endepilogue
+; CHECK-NEXT: retq
+; CHECK-NEXT: .seh_endproc
+
+declare void @a() local_unnamed_addr
+declare i32 @b() local_unnamed_addr
+declare i32 @c(i32) local_unnamed_addr
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"winx64-eh-unwindv2", i32 1}
diff --git a/llvm/test/MC/AsmParser/seh-directive-errors.s b/llvm/test/MC/AsmParser/seh-directive-errors.s
index c8a9d5d12c31d..d9dfe4b4182b9 100644
--- a/llvm/test/MC/AsmParser/seh-directive-errors.s
+++ b/llvm/test/MC/AsmParser/seh-directive-errors.s
@@ -17,6 +17,9 @@
.seh_endepilogue
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
+ .seh_unwindv2start
+ # CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
+
.def f;
.scl 2;
.type 32;
@@ -132,3 +135,27 @@ i:
.seh_endprologue
ret
.seh_endproc
+
+j:
+ .seh_proc j
+ .seh_unwindversion 1
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Unsupported version specified in .seh_unwindversion in j
+ .seh_unwindversion 2
+ .seh_unwindversion 2
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Duplicate .seh_unwindversion in j
+ .seh_endprologue
+ .seh_unwindv2start
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Stray .seh_unwindv2start in j
+
+ .seh_startepilogue
+ .seh_endepilogue
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Missing .seh_unwindv2start in j
+ ret
+
+ .seh_startepilogue
+ .seh_unwindv2start
+ .seh_unwindv2start
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Duplicate .seh_unwindv2start in j
+ .seh_endepilogue
+ ret
+ .seh_endproc
diff --git a/llvm/test/MC/COFF/bad-parse.s b/llvm/test/MC/COFF/bad-parse.s
index 2491f41abeb4e..bd728876dbca4 100644
--- a/llvm/test/MC/COFF/bad-parse.s
+++ b/llvm/test/MC/COFF/bad-parse.s
@@ -11,3 +11,14 @@
.secoffset
// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: unexpected token in directive
.secoffset section extra
+
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: expected unwind version number
+ .seh_unwindversion
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: expected unwind version number
+ .seh_unwindversion hello
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: invalid unwind version
+ .seh_unwindversion 0
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: invalid unwind version
+ .seh_unwindversion 9000
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: unexpected token in directive
+ .seh_unwindversion 2 hello
diff --git a/llvm/test/MC/COFF/seh-unwindv2.s b/llvm/test/MC/COFF/seh-unwindv2.s
new file mode 100644
index 0000000000000..a746a5ffa5e5b
--- /dev/null
+++ b/llvm/test/MC/COFF/seh-unwindv2.s
@@ -0,0 +1,154 @@
+// RUN: llvm-mc -triple x86_64-pc-win32 -filetype=obj %s | llvm-readobj -u - | FileCheck %s
+
+// CHECK: UnwindInformation [
+
+.text
+
+single_epilog_atend:
+ .seh_proc stack_alloc_no_pushes
+ .seh_unwindversion 2
+ subq $40, %rsp
+ .seh_stackalloc 40
+ .seh_endprologue
+ callq a
+ nop
+ .seh_startepilogue
+ addq $40, %rsp
+ .seh_unwindv2start
+ .seh_endepilogue
+ retq
+ .seh_endproc
+// CHECK-LABEL: StartAddress: single_epilog_atend
+// CHECK-NEXT: EndAddress: single_epilog_atend +0xF
+// CHECK-NEXT: UnwindInfoAddress: .xdata
+// 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=yes, length=0x1
+// CHECK-NEXT: 0x00: EPILOG padding
+// CHECK-NEXT: 0x04: ALLOC_SMALL size=40
+// CHECK-NEXT: ]
+// CHECK-NEXT: }
+
+single_epilog_notatend:
+ .seh_proc stack_alloc_no_pushes
+ .seh_unwindversion 2
+ subq $40, %rsp
+ .seh_stackalloc 40
+ .seh_endprologue
+ callq a
+ nop
+ .seh_startepilogue
+ addq $40, %rsp
+ .seh_unwindv2start
+ .seh_endepilogue
+ retq
+ nop
+ .seh_endproc
+// CHECK-LABEL: StartAddress: single_epilog_notatend
+// CHECK-NEXT: EndAddress: single_epilog_notatend +0x10
+// CHECK-NEXT: UnwindInfoAddress: .xdata +0xC
+// 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: 0x02: EPILOG offset=0x2
+// CHECK-NEXT: 0x04: ALLOC_SMALL size=40
+// CHECK-NEXT: ]
+// CHECK-NEXT: }
+
+multiple_epilogs:
+ .seh_proc multiple_epilogs
+ .seh_unwindversion 2
+ subq $40, %rsp
+ .seh_stackalloc 40
+ .seh_endprologue
+ callq c
+ testl %eax, %eax
+ jle .L_ELSE_1
+ movl %eax, %ecx
+ .seh_startepilogue
+ addq $40, %rsp
+ .seh_unwindv2start
+ .seh_endepilogue
+ jmp c
+.L_ELSE_1:
+ nop
+ .seh_startepilogue
+ addq $40, %rsp
+ .seh_unwindv2start
+ .seh_endepilogue
+ jmp b
+ .seh_endproc
+// CHECK-LABEL: StartAddress: multiple_epilogs
+// CHECK-NEXT: EndAddress: multiple_epilogs +0x22
+// CHECK-NEXT: UnwindInfoAddress: .xdata +0x18
+// 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: 5
+// CHECK-NEXT: UnwindCodes [
+// CHECK-NEXT: 0x01: EPILOG atend=no, length=0x1
+// CHECK-NEXT: 0x05: EPILOG offset=0x5
+// CHECK-NEXT: 0x0F: EPILOG offset=0xF
+// CHECK-NEXT: 0x00: EPILOG padding
+// CHECK-NEXT: 0x04: ALLOC_SMALL size=40
+// CHECK-NEXT: ]
+// CHECK-NEXT: }
+
+mismatched_terminators:
+ .seh_proc mismatched_terminators
+ .seh_unwindversion 2
+ subq $40, %rsp
+ .seh_stackalloc 40
+ .seh_endprologue
+ callq b
+ testl %eax, %eax
+ jle .L_ELSE_1
+# %bb.2:
+ .seh_startepilogue
+ addq $40, %rsp
+ .seh_unwindv2start
+ .seh_endepilogue
+ jmp b
+.L_ELSE_2:
+ nop
+ .seh_startepilogue
+ addq $40, %rsp
+ .seh_unwindv2start
+ .seh_endepilogue
+ retq
+ .seh_endproc
+// CHECK-LABEL: StartAddress: mismatched_terminators
+// CHECK-NEXT: EndAddress: mismatched_terminators +0x1C
+// CHECK-NEXT: UnwindInfoAddress: .xdata +0x28
+// 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=yes, length=0x1
+// CHECK-NEXT: 0x0B: EPILOG offset=0xB
+// CHECK-NEXT: 0x04: ALLOC_SMALL size=40
+// CHECK-NEXT: ]
+// CHECK-NEXT: }
More information about the llvm-commits
mailing list