[clang] [llvm] [win][x64] Unwind v2 3/n: Add support for emitting unwind v2 information (equivalent to MSVC /d2epilogunwind) (PR #129142)

Daniel Paoliello via cfe-commits cfe-commits at lists.llvm.org
Thu Feb 27 15:23:50 PST 2025


https://github.com/dpaoliello created https://github.com/llvm/llvm-project/pull/129142

Adds support for emitting Windows x64 Unwind V2 information, includes support `/d2epilogunwind` in clang-cl.

Unwind v2 adds information about the epilogs in functions such that the unwinder can unwind even in the middle of an epilog, without having to disassembly the function to see what has or has not been cleaned up.

Unwind v2 requires that all epilogs are in "canonical" form:
* If there was a stack allocation (fixed or dynamic) in the prolog, then the first instruction in the epilog must be a stack deallocation.
* Next, for each `PUSH` in the prolog there must be a corresponding `POP` instruction in exact reverse order.
* Finally, the epilog must end with the terminator.

This change adds a pass to validate epilogs in modules that have Unwind v2 enabled and, if they pass, emits new pseudo instructions to MC that 1) note that the function is using unwind v2 and 2) mark the start of the epilog (this is either the first `POP` if there is one, otherwise the terminator instruction). If a function does not meet these requirements, it is downgraded to Unwind v1 (i.e., these new pseudo instructions are not emitted).

Note that the unwind v2 table only marks the size of the epilog in the "header" unwind code, but it's possible for epilogs to use different terminator instructions thus they are not all the same size. As a work around for this, MC will assume that all terminator instructions are 1-byte long - this still works correctly with the Windows unwinder as it is only using the size to do a range check to see if a thread is in an epilog or not, and since the instruction pointer will never be in the middle of an instruction and the terminator is always at the end of an epilog the range check will function correctly. This does mean, however, that the "at end" optimization (where an epilog unwind code can be elided if the last epilog is at the end of the function) can only be used if the terminator is 1-byte long.

One other complication with the implementation is that the unwind table for a function is emitted during streaming, however we can't calculate the distance between an epilog and the end of the function at that time as layout hasn't been completed yet (thus some instructions may be relaxed). To work around this, epilog unwind codes are emitted via a fixup. This also means that we can't pre-emptively downgrade a function to Unwind v1 if one of these offsets is too large, so instead we raise an error (but I've passed through the location information, so the user will know which of their functions is problematic).

>From 39beb38b24894f3db9faf28c72944d282e947f22 Mon Sep 17 00:00:00 2001
From: Daniel Paoliello <danpao at microsoft.com>
Date: Thu, 27 Feb 2025 15:08:36 -0800
Subject: [PATCH] [win][x64] Unwind v2 3/n: Add support for emitting unwind v2
 information (equivalent to MSVC /d2epilogunwind)

---
 clang/include/clang/Basic/CodeGenOptions.def  |   3 +
 clang/include/clang/Driver/Options.td         |   6 +
 clang/lib/CodeGen/CodeGenModule.cpp           |   4 +
 clang/lib/Driver/ToolChains/Clang.cpp         |   3 +
 clang/test/CodeGen/epilog-unwind.c            |   5 +
 clang/test/Driver/cl-options.c                |   3 +
 llvm/include/llvm/MC/MCStreamer.h             |   2 +
 llvm/include/llvm/MC/MCWinEH.h                |   8 +-
 llvm/lib/MC/MCAsmStreamer.cpp                 |  16 ++
 llvm/lib/MC/MCParser/COFFAsmParser.cpp        |  28 +++
 llvm/lib/MC/MCStreamer.cpp                    |  46 +++-
 llvm/lib/MC/MCWin64EH.cpp                     | 197 ++++++++++++++--
 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 ++++++++++++
 22 files changed, 903 insertions(+), 16 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 1cf62ab466134..4061d313970d3 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -7613,6 +7613,10 @@ def import_call_optimization : Flag<["-"], "import-call-optimization">,
              "by the Windows kernel to enable import call optimization">,
     MarshallingInfoFlag<CodeGenOpts<"ImportCallOptimization">>;
 
+def epilog_unwind : Flag<["-"], "winx64-eh-unwindv2">,
+  HelpText<"Enable unwind v2 (epilog) information for x64 Windows">,
+  MarshallingInfoFlag<CodeGenOpts<"WinX64EHUnwindV2">>;
+
 } // let Visibility = [CC1Option]
 
 //===----------------------------------------------------------------------===//
@@ -8725,6 +8729,8 @@ def _SLASH_M_Group : OptionGroup<"</M group>">, Group<cl_compile_Group>;
 def _SLASH_volatile_Group : OptionGroup<"</volatile group>">,
   Group<cl_compile_Group>;
 
+def _SLASH_d2epilogunwind : CLFlag<"d2epilogunwind">,
+  HelpText<"Enable unwind v2 (epilog) information for x64 Windows">;
 def _SLASH_EH : CLJoined<"EH">, HelpText<"Set exception handling model">;
 def _SLASH_EP : CLFlag<"EP">,
   HelpText<"Disable linemarker output and preprocess to stdout">;
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 7924c32fcf633..1f53a2d055516 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1302,6 +1302,10 @@ void CodeGenModule::Release() {
     getModule().addModuleFlag(llvm::Module::Warning, "import-call-optimization",
                               1);
 
+  // Enable unwind v2 (epilog).
+  if (CodeGenOpts.WinX64EHUnwindV2)
+    getModule().addModuleFlag(llvm::Module::Warning, "winx64-eh-unwindv2", 1);
+
   // Indicate whether this Module was compiled with -fopenmp
   if (getLangOpts().OpenMP && !getLangOpts().OpenMPSimd)
     getModule().addModuleFlag(llvm::Module::Max, "openmp", LangOpts.OpenMP);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 5deafa2ad0f4a..e96a271cb47d9 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8532,6 +8532,9 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType,
   if (Args.hasArg(options::OPT__SLASH_kernel))
     CmdArgs.push_back("-fms-kernel");
 
+  if (Args.hasArg(options::OPT__SLASH_d2epilogunwind))
+    CmdArgs.push_back("-winx64-eh-unwindv2");
+
   for (const Arg *A : Args.filtered(options::OPT__SLASH_guard)) {
     StringRef GuardArgs = A->getValue();
     // The only valid options are "cf", "cf,nochecks", "cf-", "ehcont" and
diff --git a/clang/test/CodeGen/epilog-unwind.c b/clang/test/CodeGen/epilog-unwind.c
new file mode 100644
index 0000000000000..73bba28f94b29
--- /dev/null
+++ b/clang/test/CodeGen/epilog-unwind.c
@@ -0,0 +1,5 @@
+// RUN: %clang_cc1 -winx64-eh-unwindv2 -emit-llvm %s -o - | FileCheck %s
+
+void f(void) {}
+
+// CHECK: !"winx64-eh-unwindv2", i32 1}
diff --git a/clang/test/Driver/cl-options.c b/clang/test/Driver/cl-options.c
index 9f9ca1bf1a8fd..c0031e9d96d09 100644
--- a/clang/test/Driver/cl-options.c
+++ b/clang/test/Driver/cl-options.c
@@ -817,4 +817,7 @@
 // RUN: %clang_cl -vctoolsdir "" /arm64EC /c -target x86_64-pc-windows-msvc  -### -- %s 2>&1 | FileCheck --check-prefix=ARM64EC_OVERRIDE %s
 // ARM64EC_OVERRIDE: warning: /arm64EC has been overridden by specified target: x86_64-pc-windows-msvc; option ignored
 
+// RUN: %clang_cl /d2epilogunwind /c -### -- %s 2>&1 | FileCheck %s --check-prefix=EPILOGUNWIND
+// EPILOGUNWIND: -winx64-eh-unwindv2
+
 void f(void) { }
diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h
index 4fb2158f26e1e..1d6ed8e159e13 100644
--- a/llvm/include/llvm/MC/MCStreamer.h
+++ b/llvm/include/llvm/MC/MCStreamer.h
@@ -1066,6 +1066,8 @@ class MCStreamer {
   virtual void emitWinCFIEndProlog(SMLoc Loc = SMLoc());
   virtual void emitWinCFIBeginEpilogue(SMLoc Loc = SMLoc());
   virtual void emitWinCFIEndEpilogue(SMLoc Loc = SMLoc());
+  virtual void emitWinCFIUnwindV2Start(SMLoc Loc = SMLoc());
+  virtual void emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc = SMLoc());
   virtual void emitWinEHHandler(const MCSymbol *Sym, bool Unwind, bool Except,
                                 SMLoc Loc = SMLoc());
   virtual void emitWinEHHandlerData(SMLoc Loc = SMLoc());
diff --git a/llvm/include/llvm/MC/MCWinEH.h b/llvm/include/llvm/MC/MCWinEH.h
index fcce2dcd54837..c603aa896883d 100644
--- a/llvm/include/llvm/MC/MCWinEH.h
+++ b/llvm/include/llvm/MC/MCWinEH.h
@@ -10,6 +10,7 @@
 #define LLVM_MC_MCWINEH_H
 
 #include "llvm/ADT/MapVector.h"
+#include "llvm/Support/SMLoc.h"
 #include <vector>
 
 namespace llvm {
@@ -42,6 +43,7 @@ struct FrameInfo {
   const MCSymbol *FuncletOrFuncEnd = nullptr;
   const MCSymbol *ExceptionHandler = nullptr;
   const MCSymbol *Function = nullptr;
+  SMLoc FunctionLoc;
   const MCSymbol *PrologEnd = nullptr;
   const MCSymbol *Symbol = nullptr;
   MCSection *TextSection = nullptr;
@@ -52,6 +54,8 @@ struct FrameInfo {
   bool HandlesExceptions = false;
   bool EmitAttempted = false;
   bool Fragment = false;
+  constexpr static uint8_t DefaultVersion = 1;
+  uint8_t Version = DefaultVersion;
 
   int LastFrameInst = -1;
   const FrameInfo *ChainedParent = nullptr;
@@ -59,7 +63,9 @@ struct FrameInfo {
   struct Epilog {
     std::vector<Instruction> Instructions;
     unsigned Condition;
-    MCSymbol *End;
+    const MCSymbol *End = nullptr;
+    const MCSymbol *UnwindV2Start = nullptr;
+    SMLoc Loc;
   };
   MapVector<MCSymbol *, Epilog> EpilogMap;
 
diff --git a/llvm/lib/MC/MCAsmStreamer.cpp b/llvm/lib/MC/MCAsmStreamer.cpp
index 4db405051f7f3..1b74b8d73165b 100644
--- a/llvm/lib/MC/MCAsmStreamer.cpp
+++ b/llvm/lib/MC/MCAsmStreamer.cpp
@@ -399,6 +399,8 @@ class MCAsmStreamer final : public MCStreamer {
   void emitWinCFIEndProlog(SMLoc Loc) override;
   void emitWinCFIBeginEpilogue(SMLoc Loc) override;
   void emitWinCFIEndEpilogue(SMLoc Loc) override;
+  void emitWinCFIUnwindV2Start(SMLoc Loc) override;
+  void emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc) override;
 
   void emitWinEHHandler(const MCSymbol *Sym, bool Unwind, bool Except,
                         SMLoc Loc) override;
@@ -2351,6 +2353,20 @@ void MCAsmStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
   EmitEOL();
 }
 
+void MCAsmStreamer::emitWinCFIUnwindV2Start(SMLoc Loc) {
+  MCStreamer::emitWinCFIUnwindV2Start(Loc);
+
+  OS << "\t.seh_unwindv2start";
+  EmitEOL();
+}
+
+void MCAsmStreamer::emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc) {
+  MCStreamer::emitWinCFIUnwindVersion(Version, Loc);
+
+  OS << "\t.seh_unwindversion " << (unsigned)Version;
+  EmitEOL();
+}
+
 void MCAsmStreamer::emitCGProfileEntry(const MCSymbolRefExpr *From,
                                        const MCSymbolRefExpr *To,
                                        uint64_t Count) {
diff --git a/llvm/lib/MC/MCParser/COFFAsmParser.cpp b/llvm/lib/MC/MCParser/COFFAsmParser.cpp
index 4618e5675e47b..4632df9f32540 100644
--- a/llvm/lib/MC/MCParser/COFFAsmParser.cpp
+++ b/llvm/lib/MC/MCParser/COFFAsmParser.cpp
@@ -96,6 +96,10 @@ class COFFAsmParser : public MCAsmParserExtension {
         ".seh_startepilogue");
     addDirectiveHandler<&COFFAsmParser::ParseSEHDirectiveEndEpilog>(
         ".seh_endepilogue");
+    addDirectiveHandler<&COFFAsmParser::ParseSEHDirectiveUnwindV2Start>(
+        ".seh_unwindv2start");
+    addDirectiveHandler<&COFFAsmParser::ParseSEHDirectiveUnwindVersion>(
+        ".seh_unwindversion");
   }
 
   bool parseSectionDirectiveText(StringRef, SMLoc) {
@@ -147,6 +151,8 @@ class COFFAsmParser : public MCAsmParserExtension {
   bool parseSEHDirectiveEndProlog(StringRef, SMLoc);
   bool ParseSEHDirectiveBeginEpilog(StringRef, SMLoc);
   bool ParseSEHDirectiveEndEpilog(StringRef, SMLoc);
+  bool ParseSEHDirectiveUnwindV2Start(StringRef, SMLoc);
+  bool ParseSEHDirectiveUnwindVersion(StringRef, SMLoc);
 
   bool parseAtUnwindOrAtExcept(bool &unwind, bool &except);
   bool parseDirectiveSymbolAttribute(StringRef Directive, SMLoc);
@@ -767,6 +773,28 @@ bool COFFAsmParser::ParseSEHDirectiveEndEpilog(StringRef, SMLoc Loc) {
   return false;
 }
 
+bool COFFAsmParser::ParseSEHDirectiveUnwindV2Start(StringRef, SMLoc Loc) {
+  Lex();
+  getStreamer().emitWinCFIUnwindV2Start(Loc);
+  return false;
+}
+
+bool COFFAsmParser::ParseSEHDirectiveUnwindVersion(StringRef, SMLoc Loc) {
+  int64_t Version;
+  if (getParser().parseIntToken(Version, "expected unwind version number"))
+    return true;
+
+  if ((Version < 1) || (Version > UINT8_MAX))
+    return Error(Loc, "invalid unwind version");
+
+  if (getLexer().isNot(AsmToken::EndOfStatement))
+    return TokError("unexpected token in directive");
+
+  Lex();
+  getStreamer().emitWinCFIUnwindVersion(Version, Loc);
+  return false;
+}
+
 bool COFFAsmParser::parseAtUnwindOrAtExcept(bool &unwind, bool &except) {
   StringRef identifier;
   if (getLexer().isNot(AsmToken::At) && getLexer().isNot(AsmToken::Percent))
diff --git a/llvm/lib/MC/MCStreamer.cpp b/llvm/lib/MC/MCStreamer.cpp
index dd04e1e30720a..dab98feac1cb1 100644
--- a/llvm/lib/MC/MCStreamer.cpp
+++ b/llvm/lib/MC/MCStreamer.cpp
@@ -500,7 +500,8 @@ MCSymbol *MCStreamer::emitLineTableLabel() {
 MCSymbol *MCStreamer::emitCFILabel() {
   // Return a dummy non-null value so that label fields appear filled in when
   // generating textual assembly.
-  return (MCSymbol *)1;
+  static size_t DummyLabelValue = 0;
+  return (MCSymbol *)(++DummyLabelValue);
 }
 
 void MCStreamer::emitCFIDefCfa(int64_t Register, int64_t Offset, SMLoc Loc) {
@@ -767,6 +768,7 @@ void MCStreamer::emitWinCFIStartProc(const MCSymbol *Symbol, SMLoc Loc) {
       std::make_unique<WinEH::FrameInfo>(Symbol, StartProc));
   CurrentWinFrameInfo = WinFrameInfos.back().get();
   CurrentWinFrameInfo->TextSection = getCurrentSectionOnly();
+  CurrentWinFrameInfo->FunctionLoc = Loc;
 }
 
 void MCStreamer::emitWinCFIEndProc(SMLoc Loc) {
@@ -1026,6 +1028,7 @@ void MCStreamer::emitWinCFIBeginEpilogue(SMLoc Loc) {
 
   InEpilogCFI = true;
   CurrentEpilog = emitCFILabel();
+  CurFrame->EpilogMap[CurrentEpilog].Loc = Loc;
 }
 
 void MCStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
@@ -1037,12 +1040,53 @@ void MCStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
     return getContext().reportError(Loc, "Stray .seh_endepilogue in " +
                                              CurFrame->Function->getName());
 
+  assert(CurrentEpilog);
+  if ((CurFrame->Version >= 2) &&
+      !CurFrame->EpilogMap[CurrentEpilog].UnwindV2Start)
+    return getContext().reportError(Loc, "Missing .seh_unwindv2start in " +
+                                             CurFrame->Function->getName());
+
   InEpilogCFI = false;
   MCSymbol *Label = emitCFILabel();
   CurFrame->EpilogMap[CurrentEpilog].End = Label;
   CurrentEpilog = nullptr;
 }
 
+void MCStreamer::emitWinCFIUnwindV2Start(SMLoc Loc) {
+  WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
+  if (!CurFrame)
+    return;
+
+  if (!InEpilogCFI)
+    return getContext().reportError(Loc, "Stray .seh_unwindv2start in " +
+                                             CurFrame->Function->getName());
+
+  assert(CurrentEpilog);
+  if (CurFrame->EpilogMap[CurrentEpilog].UnwindV2Start)
+    return getContext().reportError(Loc, "Duplicate .seh_unwindv2start in " +
+                                             CurFrame->Function->getName());
+
+  MCSymbol *Label = emitCFILabel();
+  CurFrame->EpilogMap[CurrentEpilog].UnwindV2Start = Label;
+}
+
+void MCStreamer::emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc) {
+  WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
+  if (!CurFrame)
+    return;
+
+  if (CurFrame->Version != WinEH::FrameInfo::DefaultVersion)
+    return getContext().reportError(Loc, "Duplicate .seh_unwindversion in " +
+                                             CurFrame->Function->getName());
+
+  if (Version != 2)
+    return getContext().reportError(
+        Loc, "Unsupported version specified in .seh_unwindversion in " +
+                 CurFrame->Function->getName());
+
+  CurFrame->Version = Version;
+}
+
 void MCStreamer::emitCOFFSafeSEH(MCSymbol const *Symbol) {}
 
 void MCStreamer::emitCOFFSymbolIndex(MCSymbol const *Symbol) {}
diff --git a/llvm/lib/MC/MCWin64EH.cpp b/llvm/lib/MC/MCWin64EH.cpp
index bd5cf354659b6..253af0c048e5a 100644
--- a/llvm/lib/MC/MCWin64EH.cpp
+++ b/llvm/lib/MC/MCWin64EH.cpp
@@ -8,14 +8,61 @@
 
 #include "llvm/MC/MCWin64EH.h"
 #include "llvm/ADT/Twine.h"
+#include "llvm/MC/MCAssembler.h"
 #include "llvm/MC/MCContext.h"
 #include "llvm/MC/MCExpr.h"
 #include "llvm/MC/MCObjectStreamer.h"
 #include "llvm/MC/MCStreamer.h"
 #include "llvm/MC/MCSymbol.h"
+#include "llvm/MC/MCValue.h"
 #include "llvm/Support/Win64EH.h"
+
 namespace llvm {
 class MCSection;
+
+/// MCExpr that represents the epilog unwind code in an unwind table.
+class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {
+  const MCSymbol *FunctionEnd;
+  const MCSymbol *UnwindV2Start;
+  const MCSymbol *EpilogEnd;
+  uint8_t EpilogSize;
+  SMLoc Loc;
+
+  MCUnwindV2EpilogTargetExpr(const WinEH::FrameInfo &FrameInfo,
+                             const WinEH::FrameInfo::Epilog &Epilog,
+                             uint8_t EpilogSize_)
+      : FunctionEnd(FrameInfo.FuncletOrFuncEnd),
+        UnwindV2Start(Epilog.UnwindV2Start), EpilogEnd(Epilog.End),
+        EpilogSize(EpilogSize_), Loc(Epilog.Loc) {}
+
+public:
+  static MCUnwindV2EpilogTargetExpr *
+  create(const WinEH::FrameInfo &FrameInfo,
+         const WinEH::FrameInfo::Epilog &Epilog, uint8_t EpilogSize_,
+         MCContext &Ctx) {
+    return new (Ctx) MCUnwindV2EpilogTargetExpr(FrameInfo, Epilog, EpilogSize_);
+  }
+
+  void printImpl(raw_ostream &OS, const MCAsmInfo *MAI) const override {
+    OS << ":epilog:";
+    UnwindV2Start->print(OS, MAI);
+  }
+
+  bool evaluateAsRelocatableImpl(MCValue &Res, const MCAssembler *Asm,
+                                 const MCFixup *Fixup) const override;
+
+  void visitUsedExpr(MCStreamer &Streamer) const override {
+    // Contains no sub-expressions.
+  }
+
+  MCFragment *findAssociatedFragment() const override {
+    return UnwindV2Start->getFragment();
+  }
+
+  void fixELFSymbolsInTLSFixups(MCAssembler &) const override {
+    llvm_unreachable("Not supported for ELF");
+  }
+};
 }
 
 using namespace llvm;
@@ -163,20 +210,91 @@ static void EmitRuntimeFunction(MCStreamer &streamer,
                                              context), 4);
 }
 
+static std::optional<int64_t>
+GetOptionalAbsDifference(const MCAssembler &Assembler, const MCSymbol *LHS,
+                         const MCSymbol *RHS) {
+  MCContext &Context = Assembler.getContext();
+  const MCExpr *Diff =
+      MCBinaryExpr::createSub(MCSymbolRefExpr::create(LHS, Context),
+                              MCSymbolRefExpr::create(RHS, Context), Context);
+  // It should normally be possible to calculate the length of a function
+  // at this point, but it might not be possible in the presence of certain
+  // unusual constructs, like an inline asm with an alignment directive.
+  int64_t value;
+  if (!Diff->evaluateAsAbsolute(value, Assembler))
+    return std::nullopt;
+  return value;
+}
+
 static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
   // If this UNWIND_INFO already has a symbol, it's already been emitted.
   if (info->Symbol)
     return;
 
   MCContext &context = streamer.getContext();
+  MCObjectStreamer *OS = (MCObjectStreamer *)(&streamer);
   MCSymbol *Label = context.createTempSymbol();
 
   streamer.emitValueToAlignment(Align(4));
   streamer.emitLabel(Label);
   info->Symbol = Label;
 
-  // Upper 3 bits are the version number (currently 1).
-  uint8_t flags = 0x01;
+  uint8_t numCodes = CountOfUnwindCodes(info->Instructions);
+  bool LastEpilogIsAtEnd = false;
+  bool AddPaddingEpilogCode = false;
+  uint8_t EpilogSize = 0;
+  bool EnableUnwindV2 = (info->Version >= 2) && !info->EpilogMap.empty();
+  if (EnableUnwindV2) {
+    auto &LastEpilog = info->EpilogMap.rbegin()->second;
+
+    // Calculate the size of the epilogs. Note that we +1 to the size so that
+    // the terminator instruction is also included in the epilog (the Windows
+    // unwinder does a simple range check versus the current instruction pointer
+    // so, although there are terminators that are large than 1 byte, the
+    // starting address of the terminator instruction will always be considered
+    // inside the epilog).
+    auto MaybeSize = GetOptionalAbsDifference(
+        OS->getAssembler(), LastEpilog.End, LastEpilog.UnwindV2Start);
+    if (!MaybeSize) {
+      context.reportError(LastEpilog.Loc,
+                          "Failed to evaluate epilog size for Unwind v2");
+      return;
+    }
+    assert(*MaybeSize >= 0);
+    if (*MaybeSize >= (int64_t)UINT8_MAX) {
+      context.reportError(LastEpilog.Loc,
+                          "Epilog size is too large for Unwind v2");
+      return;
+    }
+    EpilogSize = *MaybeSize + 1;
+
+    // If the last epilog is at the end of the function, we can use a special
+    // encoding for it. Because of our +1 trick for the size, this will only
+    // work where that final terminator instruction is 1 byte long.
+    auto LastEpilogToFuncEnd = GetOptionalAbsDifference(
+        OS->getAssembler(), info->FuncletOrFuncEnd, LastEpilog.UnwindV2Start);
+    LastEpilogIsAtEnd = (LastEpilogToFuncEnd == EpilogSize);
+
+    // If we have an odd number of epilog codes, we need to add a padding code.
+    size_t numEpilogCodes =
+        info->EpilogMap.size() + (LastEpilogIsAtEnd ? 0 : 1);
+    if ((numEpilogCodes % 2) != 0) {
+      AddPaddingEpilogCode = true;
+      numEpilogCodes++;
+    }
+
+    // Too many epilogs to handle.
+    if ((size_t)numCodes + numEpilogCodes > UINT8_MAX) {
+      context.reportError(info->FunctionLoc,
+                          "Too many unwind codes with Unwind v2 enabled");
+      return;
+    }
+
+    numCodes += numEpilogCodes;
+  }
+
+  // Upper 3 bits are the version number.
+  uint8_t flags = info->Version;
   if (info->ChainedParent)
     flags |= Win64EH::UNW_ChainInfo << 3;
   else {
@@ -192,7 +310,6 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
   else
     streamer.emitInt8(0);
 
-  uint8_t numCodes = CountOfUnwindCodes(info->Instructions);
   streamer.emitInt8(numCodes);
 
   uint8_t frame = 0;
@@ -203,6 +320,35 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
   }
   streamer.emitInt8(frame);
 
+  // Emit the epilog instructions.
+  if (EnableUnwindV2) {
+    MCDataFragment *DF = OS->getOrCreateDataFragment();
+
+    bool IsLast = true;
+    for (const auto &[_, Epilog] : llvm::reverse(info->EpilogMap)) {
+      if (IsLast) {
+        IsLast = false;
+        uint8_t Flags = LastEpilogIsAtEnd ? 0x01 : 0;
+        streamer.emitInt8(EpilogSize);
+        streamer.emitInt8((Flags << 4) | Win64EH::UOP_Epilog);
+
+        if (LastEpilogIsAtEnd)
+          continue;
+      }
+
+      // Each epilog is emitted as a fixup, since we can't measure the distance
+      // between the start of the epilog and the end of the function until
+      // layout has been completed.
+      auto *MCE = MCUnwindV2EpilogTargetExpr::create(*info, Epilog, EpilogSize,
+                                                     context);
+      MCFixup Fixup = MCFixup::create(DF->getContents().size(), MCE, FK_Data_2);
+      DF->getFixups().push_back(Fixup);
+      DF->appendContents(2, 0);
+    }
+  }
+  if (AddPaddingEpilogCode)
+    streamer.emitInt16(Win64EH::UOP_Epilog << 8);
+
   // Emit unwind instructions (in reverse order).
   uint8_t numInst = info->Instructions.size();
   for (uint8_t c = 0; c < numInst; ++c) {
@@ -234,6 +380,39 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
   }
 }
 
+bool MCUnwindV2EpilogTargetExpr::evaluateAsRelocatableImpl(
+    MCValue &Res, const MCAssembler *Asm, const MCFixup *Fixup) 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 +455,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,
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 0f8fbf5be1c95..7c36ada16db40 100644
--- a/llvm/lib/Target/X86/X86MCInstLower.cpp
+++ b/llvm/lib/Target/X86/X86MCInstLower.cpp
@@ -1787,6 +1787,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");
   }
@@ -2429,6 +2437,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 0430279b88984..980caf6f9d619 100644
--- a/llvm/lib/Target/X86/X86TargetMachine.cpp
+++ b/llvm/lib/Target/X86/X86TargetMachine.cpp
@@ -105,6 +105,7 @@ extern "C" LLVM_C_ABI void LLVMInitializeX86Target() {
   initializeX86FixupInstTuningPassPass(PR);
   initializeX86FixupVectorConstantsPassPass(PR);
   initializeX86DynAllocaExpanderPass(PR);
+  initializeX86WinEHUnwindV2Pass(PR);
 }
 
 static std::unique_ptr<TargetLoweringObjectFile> createTLOF(const Triple &TT) {
@@ -666,6 +667,11 @@ void X86PassConfig::addPreEmitPass2() {
             (M->getFunction("objc_retainAutoreleasedReturnValue") ||
              M->getFunction("objc_unsafeClaimAutoreleasedReturnValue")));
   }));
+
+  // Analyzes and emits pseudos to support Win x64 Unwind V2. This pass must run
+  // after all real instructions have been added to the epilog.
+  if (TT.isOSWindows() && (TT.getArch() == Triple::x86_64))
+    addPass(createX86WinEHUnwindV2Pass());
 }
 
 bool X86PassConfig::addPostFastRegAllocRewrite() {
diff --git a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
new file mode 100644
index 0000000000000..2c1f9a5746e38
--- /dev/null
+++ b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
@@ -0,0 +1,221 @@
+//===-- X86WinEHUnwindV2.cpp - Win x64 Unwind v2 ----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// Implements the analysis required to detect if a function can use Unwind v2
+/// information, and emits the neccesary pseudo instructions used by MC to
+/// generate the unwind info.
+///
+//===----------------------------------------------------------------------===//
+
+#include "MCTargetDesc/X86BaseInfo.h"
+#include "X86.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/CodeGen/MachineBasicBlock.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/CodeGen/TargetInstrInfo.h"
+#include "llvm/CodeGen/TargetSubtargetInfo.h"
+#include "llvm/IR/Module.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "x86-wineh-unwindv2"
+
+STATISTIC(MeetsUnwindV2Criteria,
+          "Number of functions that meet Unwind v2 criteria");
+STATISTIC(FailsUnwindV2Criteria,
+          "Number of functions that fail Unwind v2 criteria");
+
+namespace {
+
+class X86WinEHUnwindV2 : public MachineFunctionPass {
+public:
+  static char ID;
+
+  X86WinEHUnwindV2() : MachineFunctionPass(ID) {
+    initializeX86WinEHUnwindV2Pass(*PassRegistry::getPassRegistry());
+  }
+
+  StringRef getPassName() const override { return "WinEH Unwind V2"; }
+
+  bool runOnMachineFunction(MachineFunction &MF) override;
+  bool rejectCurrentFunction() const {
+    FailsUnwindV2Criteria++;
+    return false;
+  }
+};
+
+enum class FunctionState {
+  InProlog,
+  HasProlog,
+  InEpilog,
+  FinishedEpilog,
+};
+
+} // end anonymous namespace
+
+char X86WinEHUnwindV2::ID = 0;
+
+INITIALIZE_PASS(X86WinEHUnwindV2, "x86-wineh-unwindv2",
+                "Analyze and emit instructions for Win64 Unwind v2", false,
+                false)
+
+FunctionPass *llvm::createX86WinEHUnwindV2Pass() {
+  return new X86WinEHUnwindV2();
+}
+
+bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
+  if (!MF.getFunction().getParent()->getModuleFlag("winx64-eh-unwindv2"))
+    return false;
+
+  // Current state of processing the function. We'll assume that all functions
+  // start with a prolog.
+  FunctionState State = FunctionState::InProlog;
+
+  // Prolog information.
+  SmallVector<int64_t> PushedRegs;
+  bool HasStackAlloc = false;
+
+  // Requested changes.
+  SmallVector<MachineInstr *> UnwindV2StartLocations;
+
+  for (MachineBasicBlock &MBB : MF) {
+    // Current epilog information. We assume that epilogs cannot cross basic
+    // block boundaries.
+    unsigned PoppedRegCount = 0;
+    bool HasStackDealloc = false;
+    MachineInstr *UnwindV2StartLocation = nullptr;
+
+    for (MachineInstr &MI : MBB) {
+      switch (MI.getOpcode()) {
+      //
+      // Prolog handling.
+      //
+      case X86::SEH_PushReg:
+        if (State != FunctionState::InProlog)
+          llvm_unreachable("SEH_PushReg outside of prolog");
+        PushedRegs.push_back(MI.getOperand(0).getImm());
+        break;
+
+      case X86::SEH_StackAlloc:
+      case X86::SEH_SetFrame:
+        if (State != FunctionState::InProlog)
+          llvm_unreachable("SEH_StackAlloc or SEH_SetFrame outside of prolog");
+        HasStackAlloc = true;
+        break;
+
+      case X86::SEH_EndPrologue:
+        if (State != FunctionState::InProlog)
+          llvm_unreachable("SEH_EndPrologue outside of prolog");
+        State = FunctionState::HasProlog;
+        break;
+
+      //
+      // Epilog handling.
+      //
+      case X86::SEH_BeginEpilogue:
+        if (State != FunctionState::HasProlog)
+          llvm_unreachable("SEH_BeginEpilogue in prolog or another epilog");
+        State = FunctionState::InEpilog;
+        break;
+
+      case X86::SEH_EndEpilogue:
+        if (State != FunctionState::InEpilog)
+          llvm_unreachable("SEH_EndEpilogue outside of epilog");
+        if ((HasStackAlloc != HasStackDealloc) ||
+            (PoppedRegCount != PushedRegs.size()))
+          // Non-canonical epilog, reject the function.
+          return rejectCurrentFunction();
+
+        // If we didn't find the start location, then use the end of the
+        // epilog.
+        if (!UnwindV2StartLocation)
+          UnwindV2StartLocation = &MI;
+        UnwindV2StartLocations.push_back(UnwindV2StartLocation);
+        State = FunctionState::FinishedEpilog;
+        break;
+
+      case X86::MOV64rr:
+      case X86::ADD64ri32:
+        if (State == FunctionState::InEpilog) {
+          // If the prolog contains a stack allocation, then the first
+          // instruction in the epilog must be to adjust the stack pointer.
+          if (!HasStackAlloc || HasStackDealloc || (PoppedRegCount > 0)) {
+            return rejectCurrentFunction();
+          }
+          HasStackDealloc = true;
+        } else if (State == FunctionState::FinishedEpilog)
+          // Unexpected instruction after the epilog.
+          return rejectCurrentFunction();
+        break;
+
+      case X86::POP64r:
+        if (State == FunctionState::InEpilog) {
+          // After the stack pointer has been adjusted, the epilog must
+          // POP each register in reverse order of the PUSHes in the prolog.
+          PoppedRegCount++;
+          if ((HasStackAlloc != HasStackDealloc) ||
+              (PoppedRegCount > PushedRegs.size()) ||
+              (PushedRegs[PushedRegs.size() - PoppedRegCount] !=
+               MI.getOperand(0).getReg())) {
+            return rejectCurrentFunction();
+          }
+
+          // Unwind v2 records the size of the epilog not from where we place
+          // SEH_BeginEpilogue (as that contains the instruction to adjust the
+          // stack pointer) but from the first POP instruction (if there is
+          // one).
+          if (!UnwindV2StartLocation) {
+            assert(PoppedRegCount == 1);
+            UnwindV2StartLocation = &MI;
+          }
+        } else if (State == FunctionState::FinishedEpilog)
+          // Unexpected instruction after the epilog.
+          return rejectCurrentFunction();
+        break;
+
+      default:
+        if (MI.isTerminator()) {
+          if (State == FunctionState::FinishedEpilog)
+            // Found the terminator after the epilog, we're now ready for
+            // another epilog.
+            State = FunctionState::HasProlog;
+          else if (State == FunctionState::InEpilog)
+            llvm_unreachable("Terminator in the middle of the epilog");
+        } else if (!MI.isDebugOrPseudoInstr()) {
+          if ((State == FunctionState::FinishedEpilog) ||
+              (State == FunctionState::InEpilog))
+            // Unknown instruction in or after the epilog.
+            return rejectCurrentFunction();
+        }
+      }
+    }
+  }
+
+  if (UnwindV2StartLocations.empty()) {
+    assert(State == FunctionState::InProlog &&
+           "If there are no epilogs, then there should be no prolog");
+    return false;
+  }
+
+  MeetsUnwindV2Criteria++;
+
+  // Emit the pseudo instruction that marks the start of each epilog.
+  const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
+  for (MachineInstr *MI : UnwindV2StartLocations) {
+    BuildMI(*MI->getParent(), MI, MI->getDebugLoc(),
+            TII->get(X86::SEH_UnwindV2Start));
+  }
+  // Note that the function is using Unwind v2.
+  MachineBasicBlock &FirstMBB = MF.front();
+  BuildMI(FirstMBB, FirstMBB.front(), FirstMBB.front().getDebugLoc(),
+          TII->get(X86::SEH_UnwindVersion))
+      .addImm(2);
+
+  return true;
+}
diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
new file mode 100644
index 0000000000000..a9fd1b9ac2acd
--- /dev/null
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
@@ -0,0 +1,160 @@
+; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s
+
+define dso_local void @no_epilog() local_unnamed_addr {
+entry:
+  ret void
+}
+; CHECK-LABEL:  no_epilog:
+; CHECK-NOT:    .seh_
+; CHECK:        retq
+
+define dso_local void @stack_alloc_no_pushes() local_unnamed_addr {
+entry:
+  call void @a()
+  ret void
+}
+; CHECK-LABEL:  stack_alloc_no_pushes:
+; CHECK:        .seh_unwindversion 2
+; CHECK-NOT:    .seh_pushreg
+; CHECK:        .seh_stackalloc
+; CHECK:        .seh_endprologue
+; CHECK-NOT:    .seh_endproc
+; CHECK:        .seh_startepilogue
+; CHECK-NEXT:   addq
+; CHECK-NEXT:   .seh_unwindv2start
+; CHECK-NEXT:   .seh_endepilogue
+; CHECK-NEXT:   retq
+
+define dso_local i32 @stack_alloc_and_pushes(i32 %x) local_unnamed_addr {
+entry:
+  %call = tail call i32 @c(i32 %x)
+  %call1 = tail call i32 @c(i32 %x)
+  %add = add nsw i32 %call1, %call
+  %call2 = tail call i32 @c(i32 %x)
+  %call3 = tail call i32 @c(i32 %call2)
+  %add4 = add nsw i32 %add, %call3
+  ret i32 %add4
+}
+; CHECK-LABEL:  stack_alloc_and_pushes:
+; CHECK:        .seh_unwindversion 2
+; CHECK:        .seh_pushreg %rsi
+; CHECK:        .seh_pushreg %rdi
+; CHECK:        .seh_pushreg %rbx
+; CHECK:        .seh_stackalloc
+; CHECK:        .seh_endprologue
+; CHECK-NOT:    .seh_endproc
+; CHECK:        .seh_startepilogue
+; CHECK-NEXT:   addq
+; CHECK-NEXT:   .seh_unwindv2start
+; CHECK-NEXT:   popq    %rbx
+; CHECK-NEXT:   popq    %rdi
+; CHECK-NEXT:   popq    %rsi
+; CHECK-NEXT:   .seh_endepilogue
+; CHECK-NEXT:   retq
+
+define dso_local i32 @tail_call(i32 %x) local_unnamed_addr {
+entry:
+  %call = tail call i32 @c(i32 %x)
+  %call1 = tail call i32 @c(i32 %call)
+  ret i32 %call1
+}
+; CHECK-LABEL:  tail_call:
+; CHECK:        .seh_unwindversion 2
+; CHECK-NOT:    .seh_pushreg
+; CHECK:        .seh_stackalloc
+; CHECK:        .seh_endprologue
+; CHECK-NOT:    .seh_endproc
+; CHECK:        .seh_startepilogue
+; CHECK-NEXT:   addq
+; CHECK-NEXT:   .seh_unwindv2start
+; CHECK-NEXT:   .seh_endepilogue
+; CHECK-NEXT:   jmp
+
+define dso_local i32 @multiple_epilogs(i32 %x) local_unnamed_addr {
+entry:
+  %call = tail call i32 @c(i32 noundef %x)
+  %cmp = icmp sgt i32 %call, 0
+  br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+  %call1 = tail call i32 @c(i32 noundef %call)
+  ret i32 %call1
+
+if.else:
+  %call2 = tail call i32 @b()
+  ret i32 %call2
+}
+; CHECK-LABEL:  multiple_epilogs:
+; CHECK:        .seh_unwindversion 2
+; CHECK-NOT:    .seh_pushreg
+; CHECK:        .seh_stackalloc
+; CHECK:        .seh_endprologue
+; CHECK-NOT:    .seh_endproc
+; CHECK:        .seh_startepilogue
+; CHECK-NEXT:   addq
+; CHECK-NEXT:   .seh_unwindv2start
+; CHECK-NEXT:   .seh_endepilogue
+; CHECK-NEXT:   jmp
+; CHECK-NOT:    .seh_endproc
+; CHECK:        .seh_startepilogue
+; CHECK-NEXT:   addq
+; CHECK-NEXT:   .seh_unwindv2start
+; CHECK-NEXT:   .seh_endepilogue
+; CHECK-NEXT:   jmp
+
+define dso_local i32 @mismatched_terminators() local_unnamed_addr {
+entry:
+  %call = tail call i32 @b()
+  %cmp = icmp sgt i32 %call, 0
+  br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+  %call1 = tail call i32 @b()
+  ret i32 %call1
+
+if.else:
+  ret i32 %call
+}
+; CHECK-LABEL:  mismatched_terminators:
+; CHECK:        .seh_unwindversion 2
+; CHECK-NOT:    .seh_pushreg
+; CHECK:        .seh_stackalloc
+; CHECK:        .seh_endprologue
+; CHECK-NOT:    .seh_endproc
+; CHECK:        .seh_startepilogue
+; CHECK-NEXT:   addq
+; CHECK-NEXT:   .seh_unwindv2start
+; CHECK-NEXT:   .seh_endepilogue
+; CHECK-NEXT:   jmp
+; CHECK-NOT:    .seh_endproc
+; CHECK:        .seh_startepilogue
+; CHECK-NEXT:   addq
+; CHECK-NEXT:   .seh_unwindv2start
+; CHECK-NEXT:   .seh_endepilogue
+; CHECK-NEXT:   ret
+
+define dso_local void @dynamic_stack_alloc(i32 %x) local_unnamed_addr {
+entry:
+  %y = alloca i32, i32 %x
+  ret void
+}
+; CHECK-LABEL:  dynamic_stack_alloc:
+; CHECK:        .seh_unwindversion 2
+; CHECK:        .seh_pushreg %rbp
+; CHECK:        .seh_setframe %rbp, 0
+; CHECK:        .seh_endprologue
+; CHECK-NOT:    .seh_endproc
+; CHECK:        .seh_startepilogue
+; CHECK-NEXT:   movq    %rbp, %rsp
+; CHECK-NEXT:   .seh_unwindv2start
+; CHECK-NEXT:   popq    %rbp
+; CHECK-NEXT:   .seh_endepilogue
+; CHECK-NEXT:   retq
+; CHECK-NEXT:   .seh_endproc
+
+declare void @a() local_unnamed_addr
+declare i32 @b() local_unnamed_addr
+declare i32 @c(i32) local_unnamed_addr
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"winx64-eh-unwindv2", i32 1}
diff --git a/llvm/test/MC/AsmParser/seh-directive-errors.s b/llvm/test/MC/AsmParser/seh-directive-errors.s
index c8a9d5d12c31d..d9dfe4b4182b9 100644
--- a/llvm/test/MC/AsmParser/seh-directive-errors.s
+++ b/llvm/test/MC/AsmParser/seh-directive-errors.s
@@ -17,6 +17,9 @@
 	.seh_endepilogue
 	# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
 
+	.seh_unwindv2start
+	# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
+
 	.def	 f;
 	.scl	2;
 	.type	32;
@@ -132,3 +135,27 @@ i:
 	.seh_endprologue
 	ret
 	.seh_endproc
+
+j:
+	.seh_proc j
+	.seh_unwindversion 1
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Unsupported version specified in .seh_unwindversion in j
+	.seh_unwindversion 2
+	.seh_unwindversion 2
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Duplicate .seh_unwindversion in j
+	.seh_endprologue
+	.seh_unwindv2start
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Stray .seh_unwindv2start in j
+
+	.seh_startepilogue
+	.seh_endepilogue
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Missing .seh_unwindv2start in j
+	ret
+
+	.seh_startepilogue
+	.seh_unwindv2start
+	.seh_unwindv2start
+# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Duplicate .seh_unwindv2start in j
+	.seh_endepilogue
+	ret
+	.seh_endproc
diff --git a/llvm/test/MC/COFF/bad-parse.s b/llvm/test/MC/COFF/bad-parse.s
index 2491f41abeb4e..bd728876dbca4 100644
--- a/llvm/test/MC/COFF/bad-parse.s
+++ b/llvm/test/MC/COFF/bad-parse.s
@@ -11,3 +11,14 @@
         .secoffset
 // CHECK: [[@LINE+1]]:{{[0-9]+}}: error: unexpected token in directive
         .secoffset section extra
+
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: expected unwind version number
+        .seh_unwindversion
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: expected unwind version number
+        .seh_unwindversion hello
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: invalid unwind version
+        .seh_unwindversion 0
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: invalid unwind version
+        .seh_unwindversion 9000
+// CHECK: [[@LINE+1]]:{{[0-9]+}}: error: unexpected token in directive
+        .seh_unwindversion 2 hello
diff --git a/llvm/test/MC/COFF/seh-unwindv2.s b/llvm/test/MC/COFF/seh-unwindv2.s
new file mode 100644
index 0000000000000..a746a5ffa5e5b
--- /dev/null
+++ b/llvm/test/MC/COFF/seh-unwindv2.s
@@ -0,0 +1,154 @@
+// RUN: llvm-mc -triple x86_64-pc-win32 -filetype=obj %s | llvm-readobj -u - | FileCheck %s
+
+// CHECK:       UnwindInformation [
+
+.text
+
+single_epilog_atend:
+    .seh_proc stack_alloc_no_pushes
+    .seh_unwindversion 2
+    subq    $40, %rsp
+    .seh_stackalloc 40
+    .seh_endprologue
+    callq   a
+    nop
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    retq
+    .seh_endproc
+// CHECK-LABEL:  StartAddress: single_epilog_atend
+// CHECK-NEXT:   EndAddress: single_epilog_atend +0xF
+// CHECK-NEXT:   UnwindInfoAddress: .xdata
+// CHECK-NEXT:   UnwindInfo {
+// CHECK-NEXT:     Version: 2
+// CHECK-NEXT:     Flags [ (0x0)
+// CHECK-NEXT:     ]
+// CHECK-NEXT:     PrologSize: 4
+// CHECK-NEXT:     FrameRegister: -
+// CHECK-NEXT:     FrameOffset: -
+// CHECK-NEXT:     UnwindCodeCount: 3
+// CHECK-NEXT:     UnwindCodes [
+// CHECK-NEXT:       0x01: EPILOG atend=yes, length=0x1
+// CHECK-NEXT:       0x00: EPILOG padding
+// CHECK-NEXT:       0x04: ALLOC_SMALL size=40
+// CHECK-NEXT:     ]
+// CHECK-NEXT:   }
+
+single_epilog_notatend:
+    .seh_proc stack_alloc_no_pushes
+    .seh_unwindversion 2
+    subq    $40, %rsp
+    .seh_stackalloc 40
+    .seh_endprologue
+    callq   a
+    nop
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    retq
+    nop
+    .seh_endproc
+// CHECK-LABEL:  StartAddress: single_epilog_notatend
+// CHECK-NEXT:   EndAddress: single_epilog_notatend +0x10
+// CHECK-NEXT:   UnwindInfoAddress: .xdata +0xC
+// CHECK-NEXT:   UnwindInfo {
+// CHECK-NEXT:     Version: 2
+// CHECK-NEXT:     Flags [ (0x0)
+// CHECK-NEXT:     ]
+// CHECK-NEXT:     PrologSize: 4
+// CHECK-NEXT:     FrameRegister: -
+// CHECK-NEXT:     FrameOffset: -
+// CHECK-NEXT:     UnwindCodeCount: 3
+// CHECK-NEXT:     UnwindCodes [
+// CHECK-NEXT:       0x01: EPILOG atend=no, length=0x1
+// CHECK-NEXT:       0x02: EPILOG offset=0x2
+// CHECK-NEXT:       0x04: ALLOC_SMALL size=40
+// CHECK-NEXT:     ]
+// CHECK-NEXT:   }
+
+multiple_epilogs:
+    .seh_proc multiple_epilogs
+    .seh_unwindversion 2
+    subq    $40, %rsp
+    .seh_stackalloc 40
+    .seh_endprologue
+    callq   c
+    testl   %eax, %eax
+    jle     .L_ELSE_1
+    movl    %eax, %ecx
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    jmp     c
+.L_ELSE_1:
+    nop
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    jmp     b
+    .seh_endproc
+// CHECK-LABEL:  StartAddress: multiple_epilogs
+// CHECK-NEXT:   EndAddress: multiple_epilogs +0x22
+// CHECK-NEXT:   UnwindInfoAddress: .xdata +0x18
+// CHECK-NEXT:   UnwindInfo {
+// CHECK-NEXT:     Version: 2
+// CHECK-NEXT:     Flags [ (0x0)
+// CHECK-NEXT:     ]
+// CHECK-NEXT:     PrologSize: 4
+// CHECK-NEXT:     FrameRegister: -
+// CHECK-NEXT:     FrameOffset: -
+// CHECK-NEXT:     UnwindCodeCount: 5
+// CHECK-NEXT:     UnwindCodes [
+// CHECK-NEXT:       0x01: EPILOG atend=no, length=0x1
+// CHECK-NEXT:       0x05: EPILOG offset=0x5
+// CHECK-NEXT:       0x0F: EPILOG offset=0xF
+// CHECK-NEXT:       0x00: EPILOG padding
+// CHECK-NEXT:       0x04: ALLOC_SMALL size=40
+// CHECK-NEXT:     ]
+// CHECK-NEXT:   }
+
+mismatched_terminators:
+    .seh_proc mismatched_terminators
+    .seh_unwindversion 2
+    subq    $40, %rsp
+    .seh_stackalloc 40
+    .seh_endprologue
+    callq   b
+    testl   %eax, %eax
+    jle     .L_ELSE_1
+# %bb.2:
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    jmp     b
+.L_ELSE_2:
+    nop
+    .seh_startepilogue
+    addq    $40, %rsp
+    .seh_unwindv2start
+    .seh_endepilogue
+    retq
+    .seh_endproc
+// CHECK-LABEL:  StartAddress: mismatched_terminators
+// CHECK-NEXT:   EndAddress: mismatched_terminators +0x1C
+// CHECK-NEXT:   UnwindInfoAddress: .xdata +0x28
+// CHECK-NEXT:   UnwindInfo {
+// CHECK-NEXT:     Version: 2
+// CHECK-NEXT:     Flags [ (0x0)
+// CHECK-NEXT:     ]
+// CHECK-NEXT:     PrologSize: 4
+// CHECK-NEXT:     FrameRegister: -
+// CHECK-NEXT:     FrameOffset: -
+// CHECK-NEXT:     UnwindCodeCount: 3
+// CHECK-NEXT:     UnwindCodes [
+// CHECK-NEXT:       0x01: EPILOG atend=yes, length=0x1
+// CHECK-NEXT:       0x0B: EPILOG offset=0xB
+// CHECK-NEXT:       0x04: ALLOC_SMALL size=40
+// CHECK-NEXT:     ]
+// CHECK-NEXT:   }



More information about the cfe-commits mailing list