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 <dan...@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: } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits