diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 452b1e325afb2..a31808da1525b 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -483,6 +483,9 @@ CODEGENOPT(StaticClosure, 1, 0) /// Assume that UAVs/SRVs may alias CODEGENOPT(ResMayAlias, 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 c8bd81bdf1e43..09773d7836ee7 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -2167,6 +2167,11 @@ defm assume_nothrow_exception_dtor: BoolFOption<"assume-nothrow-exception-dtor", LangOpts<"AssumeNothrowExceptionDtor">, DefaultFalse, PosFlag, NegFlag>; +defm winx64_eh_unwindv2 : BoolFOption<"winx64-eh-unwindv2", + CodeGenOpts<"WinX64EHUnwindV2">, DefaultFalse, + PosFlag, + NegFlag, + BothFlags<[], [ClangOption], " unwind v2 (epilog) information for x64 Windows">>; def fexcess_precision_EQ : Joined<["-"], "fexcess-precision=">, Group, Visibility<[ClangOption, CLOption]>, HelpText<"Allows control over excess precision on targets where native " @@ -8935,6 +8940,8 @@ def _SLASH_M_Group : OptionGroup<"">, Group; def _SLASH_volatile_Group : OptionGroup<"">, 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 c27817604f6ca..47ce2b012e8a6 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -1307,6 +1307,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 f87549baff5e1..c7829b73a89fa 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7503,6 +7503,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, } } + // Unwind v2 (epilog) information for x64 Windows. + Args.addOptInFlag(CmdArgs, options::OPT_fwinx64_eh_unwindv2, + options::OPT_fno_winx64_eh_unwindv2); + // C++ "sane" operator new. Args.addOptOutFlag(CmdArgs, options::OPT_fassume_sane_operator_new, options::OPT_fno_assume_sane_operator_new); @@ -8548,6 +8552,10 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType, if (Args.hasArg(options::OPT__SLASH_kernel)) CmdArgs.push_back("-fms-kernel"); + // Unwind v2 (epilog) information for x64 Windows. + if (Args.hasArg(options::OPT__SLASH_d2epilogunwind)) + CmdArgs.push_back("-fwinx64-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..991ff09fb37cf --- /dev/null +++ b/clang/test/CodeGen/epilog-unwind.c @@ -0,0 +1,9 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - | FileCheck %s -check-prefix=DISABLED +// RUN: %clang_cc1 -fwinx64-eh-unwindv2 -emit-llvm %s -o - | FileCheck %s -check-prefix=ENABLED +// RUN: %clang -fwinx64-eh-unwindv2 -S -emit-llvm %s -o - | FileCheck %s -check-prefix=ENABLED +// RUN: %clang -fno-winx64-eh-unwindv2 -S -emit-llvm %s -o - | FileCheck %s -check-prefix=DISABLED + +void f(void) {} + +// ENABLED: !"winx64-eh-unwindv2", i32 1} +// DISABLED-NOT: "winx64-eh-unwindv2" diff --git a/clang/test/Driver/cl-options.c b/clang/test/Driver/cl-options.c index beaef09c8a8d9..f8833e6995c39 100644 --- a/clang/test/Driver/cl-options.c +++ b/clang/test/Driver/cl-options.c @@ -820,4 +820,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: -fwinx64-eh-unwindv2 + void f(void) { } diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h index 78e18f97098be..beed370858d78 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..d889ec71e7d8f 100644 --- a/llvm/include/llvm/MC/MCWinEH.h +++ b/llvm/include/llvm/MC/MCWinEH.h @@ -10,6 +10,7 @@ #define LLVM_MC_MCWINEH_H #include "llvm/ADT/MapVector.h" +#include "llvm/Support/SMLoc.h" #include namespace llvm { @@ -42,6 +43,7 @@ struct FrameInfo { const MCSymbol *FuncletOrFuncEnd = nullptr; const MCSymbol *ExceptionHandler = nullptr; const MCSymbol *Function = nullptr; + SMLoc FunctionLoc; const MCSymbol *PrologEnd = nullptr; const MCSymbol *Symbol = nullptr; MCSection *TextSection = nullptr; @@ -52,6 +54,8 @@ struct FrameInfo { bool HandlesExceptions = false; bool EmitAttempted = false; bool Fragment = false; + constexpr static uint8_t DefaultVersion = 1; + uint8_t Version = DefaultVersion; int LastFrameInst = -1; const FrameInfo *ChainedParent = nullptr; @@ -59,7 +63,10 @@ struct FrameInfo { struct Epilog { std::vector Instructions; unsigned Condition; - MCSymbol *End; + const MCSymbol *Start = nullptr; + const MCSymbol *End = nullptr; + const MCSymbol *UnwindV2Start = nullptr; + SMLoc Loc; }; MapVector EpilogMap; diff --git a/llvm/lib/MC/MCAsmStreamer.cpp b/llvm/lib/MC/MCAsmStreamer.cpp index 6d39b92ab25cc..42e62459d9730 100644 --- a/llvm/lib/MC/MCAsmStreamer.cpp +++ b/llvm/lib/MC/MCAsmStreamer.cpp @@ -391,6 +391,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; @@ -2305,6 +2307,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 eb15d063be74b..f44f37d08a951 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); @@ -774,6 +780,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 b4c6428c49431..a1968423a1250 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(Symbol, StartProc)); CurrentWinFrameInfo = WinFrameInfos.back().get(); CurrentWinFrameInfo->TextSection = getCurrentSectionOnly(); + CurrentWinFrameInfo->FunctionLoc = Loc; } void MCStreamer::emitWinCFIEndProc(SMLoc Loc) { @@ -1000,8 +1001,12 @@ void MCStreamer::emitWinCFIBeginEpilogue(SMLoc Loc) { "(.seh_endprologue) in " + CurFrame->Function->getName()); - InEpilogCFI = true; - CurrentEpilog = emitCFILabel(); + MCSymbol *Label = emitCFILabel(); + CurrentWinEpilog = + &CurFrame->EpilogMap.insert_or_assign(Label, WinEH::FrameInfo::Epilog()) + .first->second; + CurrentWinEpilog->Start = Label; + CurrentWinEpilog->Loc = Loc; } void MCStreamer::emitWinCFIEndEpilogue(SMLoc Loc) { @@ -1009,14 +1014,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 e21f7756bda7a..7fc8919d10453 100644 --- a/llvm/lib/MC/MCWin64EH.cpp +++ b/llvm/lib/MC/MCWin64EH.cpp @@ -8,14 +8,57 @@ #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(); + } +}; } using namespace llvm; @@ -163,20 +206,91 @@ static void EmitRuntimeFunction(MCStreamer &streamer, context), 4); } +static std::optional +GetOptionalAbsDifference(const MCAssembler &Assembler, const MCSymbol *LHS, + const MCSymbol *RHS) { + MCContext &Context = Assembler.getContext(); + const MCExpr *Diff = + MCBinaryExpr::createSub(MCSymbolRefExpr::create(LHS, Context), + MCSymbolRefExpr::create(RHS, Context), Context); + // It should normally be possible to calculate the length of a function + // at this point, but it might not be possible in the presence of certain + // unusual constructs, like an inline asm with an alignment directive. + int64_t value; + if (!Diff->evaluateAsAbsolute(value, Assembler)) + return std::nullopt; + return value; +} + static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) { // If this UNWIND_INFO already has a symbol, it's already been emitted. if (info->Symbol) return; MCContext &context = streamer.getContext(); + MCObjectStreamer *OS = (MCObjectStreamer *)(&streamer); MCSymbol *Label = context.createTempSymbol(); streamer.emitValueToAlignment(Align(4)); streamer.emitLabel(Label); info->Symbol = Label; - // Upper 3 bits are the version number (currently 1). - uint8_t flags = 0x01; + uint8_t numCodes = CountOfUnwindCodes(info->Instructions); + bool LastEpilogIsAtEnd = false; + bool AddPaddingEpilogCode = false; + uint8_t EpilogSize = 0; + bool EnableUnwindV2 = (info->Version >= 2) && !info->EpilogMap.empty(); + if (EnableUnwindV2) { + auto &LastEpilog = info->EpilogMap.back().second; + + // Calculate the size of the epilogs. Note that we +1 to the size so that + // the terminator instruction is also included in the epilog (the Windows + // unwinder does a simple range check versus the current instruction pointer + // so, although there are terminators that are large than 1 byte, the + // starting address of the terminator instruction will always be considered + // inside the epilog). + auto MaybeSize = GetOptionalAbsDifference( + OS->getAssembler(), LastEpilog.End, LastEpilog.UnwindV2Start); + if (!MaybeSize) { + context.reportError(LastEpilog.Loc, + "Failed to evaluate epilog size for Unwind v2"); + return; + } + assert(*MaybeSize >= 0); + if (*MaybeSize >= (int64_t)UINT8_MAX) { + context.reportError(LastEpilog.Loc, + "Epilog size is too large for Unwind v2"); + return; + } + EpilogSize = *MaybeSize + 1; + + // If the last epilog is at the end of the function, we can use a special + // encoding for it. Because of our +1 trick for the size, this will only + // work where that final terminator instruction is 1 byte long. + auto LastEpilogToFuncEnd = GetOptionalAbsDifference( + OS->getAssembler(), info->FuncletOrFuncEnd, LastEpilog.UnwindV2Start); + LastEpilogIsAtEnd = (LastEpilogToFuncEnd == EpilogSize); + + // If we have an odd number of epilog codes, we need to add a padding code. + size_t numEpilogCodes = + info->EpilogMap.size() + (LastEpilogIsAtEnd ? 0 : 1); + if ((numEpilogCodes % 2) != 0) { + AddPaddingEpilogCode = true; + numEpilogCodes++; + } + + // Too many epilogs to handle. + if ((size_t)numCodes + numEpilogCodes > UINT8_MAX) { + context.reportError(info->FunctionLoc, + "Too many unwind codes with Unwind v2 enabled"); + return; + } + + numCodes += numEpilogCodes; + } + + // Upper 3 bits are the version number. + uint8_t flags = info->Version; if (info->ChainedParent) flags |= Win64EH::UNW_ChainInfo << 3; else { @@ -192,7 +306,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 +316,35 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) { } streamer.emitInt8(frame); + // Emit the epilog instructions. + if (EnableUnwindV2) { + MCDataFragment *DF = OS->getOrCreateDataFragment(); + + bool IsLast = true; + for (const auto &Epilog : llvm::reverse(info->EpilogMap)) { + if (IsLast) { + IsLast = false; + uint8_t Flags = LastEpilogIsAtEnd ? 0x01 : 0; + streamer.emitInt8(EpilogSize); + streamer.emitInt8((Flags << 4) | Win64EH::UOP_Epilog); + + if (LastEpilogIsAtEnd) + continue; + } + + // Each epilog is emitted as a fixup, since we can't measure the distance + // between the start of the epilog and the end of the function until + // layout has been completed. + auto *MCE = MCUnwindV2EpilogTargetExpr::create(*info, Epilog.second, + 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 +376,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 +451,8 @@ static const MCExpr *GetSubDivExpr(MCStreamer &Streamer, const MCSymbol *LHS, static std::optional 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, diff --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp index c03bc386ae349..64f96c57d2026 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 &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 701140fc4a9dc..6627e97dd0943 100644 --- a/llvm/lib/Target/X86/CMakeLists.txt +++ b/llvm/lib/Target/X86/CMakeLists.txt @@ -84,6 +84,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 ba53ffd857fb3..be2ddac35cab8 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 &); @@ -207,6 +210,7 @@ void initializeX86SpeculativeExecutionSideEffectSuppressionPass(PassRegistry &); void initializeX86SpeculativeLoadHardeningPassPass(PassRegistry &); void initializeX86TileConfigPass(PassRegistry &); void initializeX86SuppressAPXForRelocationPassPass(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 167e27eddd71e..efa1e8bd7f3e3 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 f50d3fa527742..750b4bbe04f3b 100644 --- a/llvm/lib/Target/X86/X86MCInstLower.cpp +++ b/llvm/lib/Target/X86/X86MCInstLower.cpp @@ -1786,6 +1786,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"); } @@ -2428,6 +2436,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 5fff9c30205dd..9d98b391c3916 100644 --- a/llvm/lib/Target/X86/X86TargetMachine.cpp +++ b/llvm/lib/Target/X86/X86TargetMachine.cpp @@ -107,6 +107,7 @@ extern "C" LLVM_C_ABI void LLVMInitializeX86Target() { initializeX86FixupVectorConstantsPassPass(PR); initializeX86DynAllocaExpanderPass(PR); initializeX86SuppressAPXForRelocationPassPass(PR); + initializeX86WinEHUnwindV2Pass(PR); } static std::unique_ptr createTLOF(const Triple &TT) { @@ -670,6 +671,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 PushedRegs; + bool HasStackAlloc = false; + + // Requested changes. + SmallVector 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: }