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

#129142 added support for emitting Windows x64 unwind v2 information, but it 
was "best effort". If any function didn't follow the requirements for v2 it was 
silently downgraded to v1.

There are some parts of Windows (specifically kernel-mode code running on Xbox) 
that require v2, hence we need the ability to fail the compilation if v2 can't 
be used.

This change also adds a heuristic to check if there might be too many unwind 
codes, it's currently conservative (i.e., assumes that certain prolog 
instructions will use the maximum number of unwind codes).

Future work: attempting to chain unwind info across multiple tables if there 
are too many unwind codes due to epilogs and adding a heuristic to detect if an 
epilog will be too far from the end of the function.

>From 8244cf55c8e432f637863ddf9acbac56792750d5 Mon Sep 17 00:00:00 2001
From: Daniel Paoliello <dan...@microsoft.com>
Date: Fri, 6 Jun 2025 16:39:07 -0700
Subject: [PATCH] Add support for requiring Win x64 Unwind V2

---
 clang/include/clang/Basic/CodeGenOptions.def  |   6 +-
 clang/include/clang/Driver/Options.td         |  17 +-
 clang/lib/CodeGen/CodeGenModule.cpp           |   6 +-
 clang/lib/Driver/ToolChains/Clang.cpp         |   9 +-
 clang/test/CodeGen/epilog-unwind.c            |  10 +-
 clang/test/Driver/cl-options.c                |   6 +-
 llvm/include/llvm/IR/Module.h                 |   4 +
 llvm/include/llvm/Support/CodeGen.h           |   9 +
 llvm/lib/IR/Module.cpp                        |   7 +
 llvm/lib/Target/X86/X86WinEHUnwindV2.cpp      | 151 ++++++--
 .../CodeGen/X86/win64-eh-unwindv2-errors.mir  | 327 ++++++++++++++++++
 11 files changed, 510 insertions(+), 42 deletions(-)
 create mode 100644 llvm/test/CodeGen/X86/win64-eh-unwindv2-errors.mir

diff --git a/clang/include/clang/Basic/CodeGenOptions.def 
b/clang/include/clang/Basic/CodeGenOptions.def
index fa9474d63ae42..32a2ee0e23200 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -481,8 +481,10 @@ 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)
+/// Controls how unwind v2 (epilog) information should be generated for x64
+/// Windows.
+ENUM_CODEGENOPT(WinX64EHUnwindV2, llvm::WinX64EHUnwindV2Mode,
+                2, llvm::WinX64EHUnwindV2Mode::Disabled)
 
 /// 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 89c63fb3397d3..d13ce5ce16467 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2167,11 +2167,14 @@ defm assume_nothrow_exception_dtor: 
BoolFOption<"assume-nothrow-exception-dtor",
   LangOpts<"AssumeNothrowExceptionDtor">, DefaultFalse,
   PosFlag<SetTrue, [], [ClangOption, CC1Option], "Assume that exception 
objects' destructors are non-throwing">,
   NegFlag<SetFalse>>;
-defm winx64_eh_unwindv2 : BoolFOption<"winx64-eh-unwindv2",
-  CodeGenOpts<"WinX64EHUnwindV2">, DefaultFalse,
-  PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,
-  NegFlag<SetFalse, [], [ClangOption], "Disable">,
-  BothFlags<[], [ClangOption], " unwind v2 (epilog) information for x64 
Windows">>;
+def winx64_eh_unwindv2
+    : Joined<["-"], "fwinx64-eh-unwindv2=">, Group<f_Group>,
+    Visibility<[ClangOption, CC1Option]>,
+      HelpText<"Generate unwind v2 (epilog) information for x64 Windows">,
+      Values<"disabled,best-effort,required">,
+      NormalizedValues<["Disabled", "BestEffort", "Required"]>,
+      NormalizedValuesScope<"llvm::WinX64EHUnwindV2Mode">,
+      MarshallingInfoEnum<CodeGenOpts<"WinX64EHUnwindV2">, "Disabled">;
 def fexcess_precision_EQ : Joined<["-"], "fexcess-precision=">, Group<f_Group>,
   Visibility<[ClangOption, CLOption]>,
   HelpText<"Allows control over excess precision on targets where native "
@@ -8968,7 +8971,9 @@ 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">;
+  HelpText<"Best effort generate unwind v2 (epilog) information for x64 
Windows">;
+def _SLASH_d2epilogunwindrequirev2 : CLFlag<"d2epilogunwindrequirev2">,
+  HelpText<"Require generation of 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 16e49aab4fe61..148c31912acbd 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1314,8 +1314,10 @@ void CodeGenModule::Release() {
                               1);
 
   // Enable unwind v2 (epilog).
-  if (CodeGenOpts.WinX64EHUnwindV2)
-    getModule().addModuleFlag(llvm::Module::Warning, "winx64-eh-unwindv2", 1);
+  if (CodeGenOpts.getWinX64EHUnwindV2() != 
llvm::WinX64EHUnwindV2Mode::Disabled)
+    getModule().addModuleFlag(
+        llvm::Module::Warning, "winx64-eh-unwindv2",
+        static_cast<unsigned>(CodeGenOpts.getWinX64EHUnwindV2()));
 
   // Indicate whether this Module was compiled with -fopenmp
   if (getLangOpts().OpenMP && !getLangOpts().OpenMPSimd)
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 65f101ddf1d0a..dce5d132b10a7 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7471,8 +7471,7 @@ 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);
+  Args.AddLastArg(CmdArgs, options::OPT_winx64_eh_unwindv2);
 
   // C++ "sane" operator new.
   Args.addOptOutFlag(CmdArgs, options::OPT_fassume_sane_operator_new,
@@ -8529,8 +8528,10 @@ void Clang::AddClangCLArgs(const ArgList &Args, 
types::ID InputType,
     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");
+  if (Args.hasArg(options::OPT__SLASH_d2epilogunwindrequirev2))
+    CmdArgs.push_back("-fwinx64-eh-unwindv2=required");
+  else if (Args.hasArg(options::OPT__SLASH_d2epilogunwind))
+    CmdArgs.push_back("-fwinx64-eh-unwindv2=best-effort");
 
   for (const Arg *A : Args.filtered(options::OPT__SLASH_guard)) {
     StringRef GuardArgs = A->getValue();
diff --git a/clang/test/CodeGen/epilog-unwind.c 
b/clang/test/CodeGen/epilog-unwind.c
index 991ff09fb37cf..b2f7497b455b6 100644
--- a/clang/test/CodeGen/epilog-unwind.c
+++ b/clang/test/CodeGen/epilog-unwind.c
@@ -1,9 +1,11 @@
 // 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
+// RUN: %clang_cc1 -fwinx64-eh-unwindv2=disabled -emit-llvm %s -o - | 
FileCheck %s -check-prefix=DISABLED
+// RUN: %clang_cc1 -fwinx64-eh-unwindv2=best-effort -emit-llvm %s -o - | 
FileCheck %s -check-prefix=BESTEFFORT
+// RUN: %clang_cc1 -fwinx64-eh-unwindv2=required -emit-llvm %s -o - | 
FileCheck %s -check-prefix=REQUIRED
+// RUN: %clang -fwinx64-eh-unwindv2=best-effort -S -emit-llvm %s -o - | 
FileCheck %s -check-prefix=BESTEFFORT
 
 void f(void) {}
 
-// ENABLED: !"winx64-eh-unwindv2", i32 1}
+// BESTEFFORT: !"winx64-eh-unwindv2", i32 1}
+// REQUIRED: !"winx64-eh-unwindv2", i32 2}
 // DISABLED-NOT: "winx64-eh-unwindv2"
diff --git a/clang/test/Driver/cl-options.c b/clang/test/Driver/cl-options.c
index 0535285862b9f..eb079895a0a88 100644
--- a/clang/test/Driver/cl-options.c
+++ b/clang/test/Driver/cl-options.c
@@ -821,7 +821,11 @@
 // 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
+// EPILOGUNWIND: -fwinx64-eh-unwindv2=best-effort
+
+// RUN: %clang_cl /d2epilogunwindrequirev2 /c -### -- %s 2>&1 | FileCheck %s 
--check-prefix=EPILOGUNWINDREQUIREV2
+// RUN: %clang_cl /d2epilogunwindrequirev2 /d2epilogunwind /c -### -- %s 2>&1 
| FileCheck %s --check-prefix=EPILOGUNWINDREQUIREV2
+// EPILOGUNWINDREQUIREV2: -fwinx64-eh-unwindv2=require
 
 // RUN: %clang_cl /funcoverride:override_me1 /funcoverride:override_me2 /c 
-### -- %s 2>&1 | FileCheck %s --check-prefix=FUNCOVERRIDE
 // FUNCOVERRIDE: -loader-replaceable-function=override_me1
diff --git a/llvm/include/llvm/IR/Module.h b/llvm/include/llvm/IR/Module.h
index 7a26efb74b324..9d90a5755f7ed 100644
--- a/llvm/include/llvm/IR/Module.h
+++ b/llvm/include/llvm/IR/Module.h
@@ -1061,6 +1061,10 @@ class LLVM_ABI Module {
 
   /// Returns target-abi from MDString, null if target-abi is absent.
   StringRef getTargetABIFromMD();
+
+  /// Get how unwind v2 (epilog) information should be generated for x64
+  /// Windows.
+  WinX64EHUnwindV2Mode getWinX64EHUnwindV2Mode() const;
 };
 
 /// Given "llvm.used" or "llvm.compiler.used" as a global name, collect the
diff --git a/llvm/include/llvm/Support/CodeGen.h 
b/llvm/include/llvm/Support/CodeGen.h
index 0e42789ba932e..48745f7f4d2a6 100644
--- a/llvm/include/llvm/Support/CodeGen.h
+++ b/llvm/include/llvm/Support/CodeGen.h
@@ -130,6 +130,15 @@ namespace llvm {
     Invalid = 2, ///< Not used.
   };
 
+  enum class WinX64EHUnwindV2Mode {
+    // Don't use unwind v2 (i.e., use v1).
+    Disabled = 0,
+    // Use unwind v2 here possible, otherwise fallback to v1.
+    BestEffort = 1,
+    // Use unwind v2 everywhere, otherwise raise an error.
+    Required = 2,
+  };
+
   } // namespace llvm
 
 #endif
diff --git a/llvm/lib/IR/Module.cpp b/llvm/lib/IR/Module.cpp
index 0a47f98619691..7b3a0bb333c05 100644
--- a/llvm/lib/IR/Module.cpp
+++ b/llvm/lib/IR/Module.cpp
@@ -919,3 +919,10 @@ StringRef Module::getTargetABIFromMD() {
     TargetABI = TargetABIMD->getString();
   return TargetABI;
 }
+
+WinX64EHUnwindV2Mode Module::getWinX64EHUnwindV2Mode() const {
+  Metadata *MD = getModuleFlag("winx64-eh-unwindv2");
+  if (auto *CI = mdconst::dyn_extract_or_null<ConstantInt>(MD))
+    return static_cast<WinX64EHUnwindV2Mode>(CI->getZExtValue());
+  return WinX64EHUnwindV2Mode::Disabled;
+}
diff --git a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp 
b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
index 2c1f9a5746e38..01256ec8995a8 100644
--- a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
+++ b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
@@ -20,6 +20,7 @@
 #include "llvm/CodeGen/MachineInstrBuilder.h"
 #include "llvm/CodeGen/TargetInstrInfo.h"
 #include "llvm/CodeGen/TargetSubtargetInfo.h"
+#include "llvm/IR/DiagnosticInfo.h"
 #include "llvm/IR/Module.h"
 
 using namespace llvm;
@@ -31,6 +32,15 @@ STATISTIC(MeetsUnwindV2Criteria,
 STATISTIC(FailsUnwindV2Criteria,
           "Number of functions that fail Unwind v2 criteria");
 
+static cl::opt<unsigned> MaximumUnwindCodes(
+    "x86-wineh-unwindv2-max-unwind-codes", cl::Hidden,
+    cl::desc("Maximum number of unwind codes permitted in each unwind info."),
+    cl::init(UINT8_MAX));
+
+static cl::opt<unsigned> ForceMode(
+    "x86-wineh-unwindv2-force-mode", cl::Hidden,
+    cl::desc("Overwrites the Unwind v2 mode for testing purposes."));
+
 namespace {
 
 class X86WinEHUnwindV2 : public MachineFunctionPass {
@@ -44,9 +54,21 @@ class X86WinEHUnwindV2 : public MachineFunctionPass {
   StringRef getPassName() const override { return "WinEH Unwind V2"; }
 
   bool runOnMachineFunction(MachineFunction &MF) override;
-  bool rejectCurrentFunction() const {
-    FailsUnwindV2Criteria++;
-    return false;
+
+private:
+  bool rejectCurrentFunction(const MachineFunction &MF, const DebugLoc &Loc,
+                             WinX64EHUnwindV2Mode Mode,
+                             StringRef Reason) const;
+
+  bool rejectCurrentFunction(const MachineFunction &MF,
+                             const MachineBasicBlock &EpilogBlock,
+                             WinX64EHUnwindV2Mode Mode,
+                             StringRef Reason) const {
+    // If we're in the Epilog, then the generated instructions might not have a
+    // location, so use the terminator's location.
+    return rejectCurrentFunction(
+        MF, EpilogBlock.getFirstTerminator()->getDebugLoc(), Mode,
+        Reason);
   }
 };
 
@@ -69,8 +91,21 @@ FunctionPass *llvm::createX86WinEHUnwindV2Pass() {
   return new X86WinEHUnwindV2();
 }
 
+DebugLoc findDebugLoc(const MachineBasicBlock &MBB) {
+  for (const MachineInstr &MI : MBB)
+    if (MI.getDebugLoc())
+      return MI.getDebugLoc();
+
+  return DebugLoc::getUnknown();
+}
+
 bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
-  if (!MF.getFunction().getParent()->getModuleFlag("winx64-eh-unwindv2"))
+  WinX64EHUnwindV2Mode Mode =
+      ForceMode.getNumOccurrences()
+          ? static_cast<WinX64EHUnwindV2Mode>(ForceMode.getValue())
+          : MF.getFunction().getParent()->getWinX64EHUnwindV2Mode();
+
+  if (Mode == WinX64EHUnwindV2Mode::Disabled)
     return false;
 
   // Current state of processing the function. We'll assume that all functions
@@ -80,6 +115,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction 
&MF) {
   // Prolog information.
   SmallVector<int64_t> PushedRegs;
   bool HasStackAlloc = false;
+  unsigned ApproximatePrologCodeCount = 0;
 
   // Requested changes.
   SmallVector<MachineInstr *> UnwindV2StartLocations;
@@ -99,6 +135,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction 
&MF) {
       case X86::SEH_PushReg:
         if (State != FunctionState::InProlog)
           llvm_unreachable("SEH_PushReg outside of prolog");
+        ApproximatePrologCodeCount++;
         PushedRegs.push_back(MI.getOperand(0).getImm());
         break;
 
@@ -106,9 +143,26 @@ bool 
X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
       case X86::SEH_SetFrame:
         if (State != FunctionState::InProlog)
           llvm_unreachable("SEH_StackAlloc or SEH_SetFrame outside of prolog");
+        // Assume a large alloc...
+        ApproximatePrologCodeCount +=
+            (MI.getOpcode() == X86::SEH_StackAlloc) ? 3 : 1;
         HasStackAlloc = true;
         break;
 
+      case X86::SEH_SaveReg:
+      case X86::SEH_SaveXMM:
+        if (State != FunctionState::InProlog)
+          llvm_unreachable("SEH_SaveXMM or SEH_SaveReg outside of prolog");
+        // Assume a big reg...
+        ApproximatePrologCodeCount += 3;
+        break;
+
+      case X86::SEH_PushFrame:
+        if (State != FunctionState::InProlog)
+          llvm_unreachable("SEH_PushFrame outside of prolog");
+        ApproximatePrologCodeCount++;
+        break;
+
       case X86::SEH_EndPrologue:
         if (State != FunctionState::InProlog)
           llvm_unreachable("SEH_EndPrologue outside of prolog");
@@ -127,10 +181,14 @@ bool 
X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
       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 (HasStackAlloc != HasStackDealloc)
+          return rejectCurrentFunction(MF, MBB, Mode,
+                                       "The prolog made a stack allocation, "
+                                       "but the epilog did not deallocate it");
+        if (PoppedRegCount != PushedRegs.size())
+          return rejectCurrentFunction(MF, MBB, Mode,
+                                       "The prolog pushed more registers than "
+                                       "the epilog popped");
 
         // If we didn't find the start location, then use the end of the
         // epilog.
@@ -145,13 +203,25 @@ bool 
X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
         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();
-          }
+          if (!HasStackAlloc)
+            return rejectCurrentFunction(
+                MF, MBB, Mode,
+                "The epilog is deallocating a stack allocation, but the prolog 
did "
+                "not allocate one");
+          if (HasStackDealloc)
+            return rejectCurrentFunction(
+                MF, MBB, Mode,
+                "The epilog is deallocating the stack allocation more than 
once");
+          if (PoppedRegCount > 0)
+            llvm_unreachable(
+                "Should have raised an error: either popping before "
+                "deallocating or deallocating without an allocation");
+
           HasStackDealloc = true;
         } else if (State == FunctionState::FinishedEpilog)
-          // Unexpected instruction after the epilog.
-          return rejectCurrentFunction();
+          return rejectCurrentFunction(
+              MF, MBB, Mode,
+              "Unexpected mov or add instruction after the epilog");
         break;
 
       case X86::POP64r:
@@ -159,12 +229,20 @@ bool 
X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
           // 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();
-          }
+          if (HasStackAlloc != HasStackDealloc)
+            return rejectCurrentFunction(MF, MBB, Mode,
+                                         "Cannot pop registers before the 
stack "
+                                         "allocation has been deallocated");
+          if (PoppedRegCount > PushedRegs.size())
+            return rejectCurrentFunction(
+                MF, MBB, Mode,
+                "The epilog is popping more registers than the prolog pushed");
+          if (PushedRegs[PushedRegs.size() - PoppedRegCount] !=
+              MI.getOperand(0).getReg())
+            return rejectCurrentFunction(
+                MF, MBB, Mode,
+                "The epilog is popping a registers in a different order than 
the "
+                "prolog pushed them");
 
           // Unwind v2 records the size of the epilog not from where we place
           // SEH_BeginEpilogue (as that contains the instruction to adjust the
@@ -176,7 +254,9 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction 
&MF) {
           }
         } else if (State == FunctionState::FinishedEpilog)
           // Unexpected instruction after the epilog.
-          return rejectCurrentFunction();
+          return rejectCurrentFunction(
+              MF, MBB, Mode,
+              "Registers are being popped after the epilog");
         break;
 
       default:
@@ -191,7 +271,9 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction 
&MF) {
           if ((State == FunctionState::FinishedEpilog) ||
               (State == FunctionState::InEpilog))
             // Unknown instruction in or after the epilog.
-            return rejectCurrentFunction();
+            return rejectCurrentFunction(
+                MF, MI.getDebugLoc(), Mode,
+                "Unexpected instruction in or after the epilog");
         }
       }
     }
@@ -203,6 +285,16 @@ bool 
X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
     return false;
   }
 
+  MachineBasicBlock &FirstMBB = MF.front();
+  // Assume +1 for the "header" UOP_Epilog that contains the epilog size, and
+  // that we won't be able to use the "last epilog at the end of function"
+  // optimization.
+  if (ApproximatePrologCodeCount + UnwindV2StartLocations.size() + 1 >
+      static_cast<unsigned>(MaximumUnwindCodes))
+    return rejectCurrentFunction(
+        MF, findDebugLoc(FirstMBB), Mode,
+        "The function has too many epilogs");
+
   MeetsUnwindV2Criteria++;
 
   // Emit the pseudo instruction that marks the start of each epilog.
@@ -212,10 +304,23 @@ bool 
X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
             TII->get(X86::SEH_UnwindV2Start));
   }
   // Note that the function is using Unwind v2.
-  MachineBasicBlock &FirstMBB = MF.front();
-  BuildMI(FirstMBB, FirstMBB.front(), FirstMBB.front().getDebugLoc(),
+  BuildMI(FirstMBB, FirstMBB.front(), findDebugLoc(FirstMBB),
           TII->get(X86::SEH_UnwindVersion))
       .addImm(2);
 
   return true;
 }
+
+bool X86WinEHUnwindV2::rejectCurrentFunction(const MachineFunction &MF,
+                                             const DebugLoc &Loc,
+                                             WinX64EHUnwindV2Mode Mode,
+                                             StringRef Reason) const {
+  if (Mode == WinX64EHUnwindV2Mode::Required)
+    MF.getFunction().getContext().diagnose(DiagnosticInfoGenericWithLoc(
+        "Windows x64 Unwind v2 is required, but the function '" + MF.getName() 
+
+            "' does not meet the criteria: " + Reason,
+        MF.getFunction(), Loc));
+
+  FailsUnwindV2Criteria++;
+  return false;
+}
diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv2-errors.mir 
b/llvm/test/CodeGen/X86/win64-eh-unwindv2-errors.mir
new file mode 100644
index 0000000000000..08c0c0fa6c1db
--- /dev/null
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv2-errors.mir
@@ -0,0 +1,327 @@
+# Require V2 and restrict the number of unwind codes to 8
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - %s \
+# RUN:    -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-max-unwind-codes=8 \
+# RUN:    2>&1 | FileCheck %s -check-prefix=REQUIREV2
+
+# Force best-effort and restrict the number of unwind codes to 8
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s \
+# RUN:    -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-max-unwind-codes=8 \
+# RUN:    -x86-wineh-unwindv2-force-mode=1 | \
+# RUN:    FileCheck %s -check-prefix=BESTEFFORT
+
+# Require V2, but allow the default number of unwind codes (255)
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - %s \
+# RUN:    -run-pass=x86-wineh-unwindv2 | FileCheck %s -check-prefix=ALLOWMORE
+
+# If we force "best effort" mode, then we won't see any errors, but we won't 
use
+# v2 for any of these functions.
+# BESTEFFORT-NOT: SEH_UnwindVersion
+# BESTEFFORT-NOT: SEH_UnwindV2Start
+
+# If we allow more epilogs, then the too_many_epilogs test should pass, but 
only
+# that one test.
+# ALLOWMORE-LABEL: too_many_epilogs
+# ALLOWMORE: SEH_UnwindVersion 2
+# ALLOWMORE: SEH_UnwindV2Start
+# ALLOWMORE-NOT: SEH_UnwindVersion
+
+--- |
+  define dso_local void @alloc_no_dealloc() local_unnamed_addr !dbg !9 {
+  entry:
+    ret void, !dbg !10
+  }
+
+  define dso_local void @missed_push() local_unnamed_addr !dbg !11 {
+  entry:
+    ret void, !dbg !12
+  }
+
+  define dso_local void @dealloc_no_alloc() local_unnamed_addr !dbg !13 {
+  entry:
+    ret void, !dbg !14
+  }
+
+  define dso_local void @double_dealloc() local_unnamed_addr !dbg !15 {
+  entry:
+    ret void, !dbg !16
+  }
+
+  define dso_local void @dealloc_after_endepilog() local_unnamed_addr !dbg !17 
{
+  entry:
+    ret void, !dbg !18
+  }
+
+  define dso_local void @pop_before_dealloc() local_unnamed_addr !dbg !19 {
+  entry:
+    ret void, !dbg !20
+  }
+
+  define dso_local void @too_many_pops() local_unnamed_addr !dbg !21 {
+  entry:
+    ret void, !dbg !22
+  }
+
+  define dso_local void @pop_in_wrong_order() local_unnamed_addr !dbg !23 {
+  entry:
+    ret void, !dbg !24
+  }
+
+  define dso_local void @pop_after_endepilog() local_unnamed_addr !dbg !25 {
+  entry:
+    ret void, !dbg !26
+  }
+
+  define dso_local void @instr_after_epilog() local_unnamed_addr !dbg !27 {
+  entry:
+    ret void, !dbg !28
+  }
+
+  define dso_local void @too_many_epilogs() local_unnamed_addr !dbg !30 {
+  entry:
+    ret void, !dbg !31
+  }
+
+  !llvm.dbg.cu = !{!0}
+  !llvm.module.flags = !{!2, !3, !4, !5}
+
+  !0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: 
"clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, 
splitDebugInlining: false, nameTableKind: None)
+  !1 = !DIFile(filename: "/app/example.c", directory: "/app")
+  !2 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+  !3 = !{i32 7, !"Dwarf Version", i32 4}
+  !4 = !{i32 2, !"CodeView", i32 1}
+  !5 = !{i32 2, !"Debug Info Version", i32 3}
+  !6 = !DIFile(filename: "example.c", directory: "/app")
+  !7 = !DISubroutineType(types: !8)
+  !8 = !{null}
+  !9 = distinct !DISubprogram(name: "alloc_no_dealloc", scope: !6, file: !6, 
line: 2, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !10 = !DILocation(line: 3, column: 1, scope: !9)
+  !11 = distinct !DISubprogram(name: "missed_push", scope: !6, file: !6, line: 
4, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !12 = !DILocation(line: 5, column: 1, scope: !11)
+  !13 = distinct !DISubprogram(name: "dealloc_no_alloc", scope: !6, file: !6, 
line: 6, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !14 = !DILocation(line: 7, column: 1, scope: !13)
+  !15 = distinct !DISubprogram(name: "double_dealloc", scope: !6, file: !6, 
line: 8, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !16 = !DILocation(line: 9, column: 1, scope: !15)
+  !17 = distinct !DISubprogram(name: "dealloc_after_endepilog", scope: !6, 
file: !6, line: 10, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: 
!0)
+  !18 = !DILocation(line: 11, column: 1, scope: !17)
+  !19 = distinct !DISubprogram(name: "pop_before_dealloc", scope: !6, file: 
!6, line: 12, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !20 = !DILocation(line: 13, column: 1, scope: !19)
+  !21 = distinct !DISubprogram(name: "too_many_pops", scope: !6, file: !6, 
line: 14, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !22 = !DILocation(line: 15, column: 1, scope: !21)
+  !23 = distinct !DISubprogram(name: "pop_in_wrong_order", scope: !6, file: 
!6, line: 16, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !24 = !DILocation(line: 17, column: 1, scope: !23)
+  !25 = distinct !DISubprogram(name: "pop_after_endepilog", scope: !6, file: 
!6, line: 18, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !26 = !DILocation(line: 19, column: 1, scope: !25)
+  !27 = distinct !DISubprogram(name: "instr_after_epilog", scope: !6, file: 
!6, line: 20, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !28 = !DILocation(line: 21, column: 1, scope: !27)
+  !29 = !DILocation(line: 22, column: 1, scope: !27)
+  !30 = distinct !DISubprogram(name: "too_many_epilogs", scope: !6, file: !6, 
line: 23, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+  !31 = !DILocation(line: 24, column: 1, scope: !30)
+  !32 = !DILocation(line: 25, column: 1, scope: !30)
+...
+
+# REQUIREV2: error: example.c:3:1: Windows x64 Unwind v2 is required, but the 
function 'alloc_no_dealloc' does not meet the criteria:
+# REQUIREV2-SAME: The prolog made a stack allocation, but the epilog did not 
deallocate it
+---
+name:            alloc_no_dealloc
+body:             |
+  bb.0.entry:
+    $rsp = frame-setup SUB64ri32 $rsp, 40, implicit-def dead $eflags
+    frame-setup SEH_StackAlloc 40
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !10
+
+...
+
+# REQUIREV2: error: example.c:5:1: Windows x64 Unwind v2 is required, but the 
function 'missed_push' does not meet the criteria:
+# REQUIREV2-SAME: The prolog pushed more registers than the epilog popped
+---
+name:            missed_push
+body:             |
+  bb.0.entry:
+    frame-setup PUSH64r killed $rsi, implicit-def $rsp, implicit $rsp
+    frame-setup SEH_PushReg 60
+    frame-setup PUSH64r killed $rdi, implicit-def $rsp, implicit $rsp
+    frame-setup SEH_PushReg 55
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    $rdi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+    SEH_EndEpilogue
+    RET64 debug-location !12
+
+...
+
+# REQUIREV2: error: example.c:7:1: Windows x64 Unwind v2 is required, but the 
function 'dealloc_no_alloc' does not meet the criteria:
+# REQUIREV2-SAME: The epilog is deallocating a stack allocation, but the 
prolog did not allocate one
+---
+name:            dealloc_no_alloc
+body:             |
+  bb.0.entry:
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    $rsp = frame-destroy ADD64ri32 $rsp, 40, implicit-def dead $eflags
+    SEH_EndEpilogue
+    RET64 debug-location !14
+
+...
+
+# REQUIREV2: error: example.c:9:1: Windows x64 Unwind v2 is required, but the 
function 'double_dealloc' does not meet the criteria:
+# REQUIREV2-SAME: The epilog is deallocating the stack allocation more than 
once
+---
+name:            double_dealloc
+body:             |
+  bb.0.entry:
+    $rsp = frame-setup SUB64ri32 $rsp, 40, implicit-def dead $eflags
+    frame-setup SEH_StackAlloc 40
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    $rsp = frame-destroy ADD64ri32 $rsp, 40, implicit-def dead $eflags
+    $rsp = frame-destroy ADD64ri32 $rsp, 40, implicit-def dead $eflags
+    SEH_EndEpilogue
+    RET64 debug-location !16
+
+...
+
+# REQUIREV2: error: example.c:11:1: Windows x64 Unwind v2 is required, but the 
function 'dealloc_after_endepilog' does not meet the criteria:
+# REQUIREV2-SAME: Unexpected mov or add instruction after the epilog
+---
+name:            dealloc_after_endepilog
+body:             |
+  bb.0.entry:
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    $rsp = frame-destroy ADD64ri32 $rsp, 40, implicit-def dead $eflags
+    RET64 debug-location !18
+
+...
+
+# REQUIREV2: error: example.c:13:1: Windows x64 Unwind v2 is required, but the 
function 'pop_before_dealloc' does not meet the criteria:
+# REQUIREV2-SAME: Cannot pop registers before the stack allocation has been 
deallocated
+---
+name:            pop_before_dealloc
+body:             |
+  bb.0.entry:
+    frame-setup PUSH64r killed $rdi, implicit-def $rsp, implicit $rsp
+    frame-setup SEH_PushReg 55
+    $rsp = frame-setup SUB64ri32 $rsp, 40, implicit-def dead $eflags
+    frame-setup SEH_StackAlloc 40
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    $rdi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+    $rsp = frame-destroy ADD64ri32 $rsp, 40, implicit-def dead $eflags
+    SEH_EndEpilogue
+    RET64 debug-location !20
+
+...
+
+# REQUIREV2: error: example.c:15:1: Windows x64 Unwind v2 is required, but the 
function 'too_many_pops' does not meet the criteria:
+# REQUIREV2-SAME: The epilog is popping more registers than the prolog pushed
+---
+name:            too_many_pops
+body:             |
+  bb.0.entry:
+    frame-setup PUSH64r killed $rdi, implicit-def $rsp, implicit $rsp
+    frame-setup SEH_PushReg 55
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    $rdi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+    $rsi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+    SEH_EndEpilogue
+    RET64 debug-location !22
+
+...
+
+# REQUIREV2: error: example.c:17:1: Windows x64 Unwind v2 is required, but the 
function 'pop_in_wrong_order' does not meet the criteria:
+# REQUIREV2-SAME: The epilog is popping a registers in a different order than 
the prolog pushed them
+---
+name:            pop_in_wrong_order
+body:             |
+  bb.0.entry:
+    frame-setup PUSH64r killed $rdi, implicit-def $rsp, implicit $rsp
+    frame-setup SEH_PushReg 55
+    frame-setup PUSH64r killed $rsi, implicit-def $rsp, implicit $rsp
+    frame-setup SEH_PushReg 60
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    $rdi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+    $rsi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+    SEH_EndEpilogue
+    RET64 debug-location !24
+
+...
+
+# REQUIREV2: error: example.c:19:1: Windows x64 Unwind v2 is required, but the 
function 'pop_after_endepilog' does not meet the criteria:
+# REQUIREV2-SAME: Registers are being popped after the epilog
+---
+name:            pop_after_endepilog
+body:             |
+  bb.0.entry:
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    $rdi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+    RET64 debug-location !26
+
+...
+
+# REQUIREV2: error: example.c:21:1: Windows x64 Unwind v2 is required, but the 
function 'instr_after_epilog' does not meet the criteria:
+# REQUIREV2-SAME: Unexpected instruction in or after the epilog
+---
+name:            instr_after_epilog
+body:             |
+  bb.0.entry:
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    $ecx = MOV32rr killed $eax, debug-location !28
+    RET64 debug-location !29
+
+...
+
+# Usually 255 unwind codes are permitted, but we passed an arg to llc to limit 
it to 8.
+# REQUIREV2: error: example.c:24:1: Windows x64 Unwind v2 is required, but the 
function 'too_many_epilogs' does not meet the criteria:
+# REQUIREV2-SAME: The function has too many epilogs
+---
+name:            too_many_epilogs
+body:             |
+  bb.0.entry:
+    frame-setup SEH_EndPrologue
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !31
+  bb.1:
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !32
+  bb.2:
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !32
+  bb.3:
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !32
+  bb.4:
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !32
+  bb.5:
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !32
+  bb.6:
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !32
+  bb.7:
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !32
+  bb.8:
+    SEH_BeginEpilogue
+    SEH_EndEpilogue
+    RET64 debug-location !32
+
+...

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to