[clang] [llvm] [win][x64] Unwind v2 3/n: Add support for requiring unwind v2 to be used (equivalent to MSVC's /d2epilogunwindrequirev2) (PR #143577)
Daniel Paoliello via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 11 15:33:36 PDT 2025
https://github.com/dpaoliello updated https://github.com/llvm/llvm-project/pull/143577
>From 07390317820324535fc75f7bd9dd53c2e65bda41 Mon Sep 17 00:00:00 2001
From: Daniel Paoliello <danpao at 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 | 152 +++++++--
.../CodeGen/X86/win64-eh-unwindv2-errors.mir | 318 ++++++++++++++++++
11 files changed, 501 insertions(+), 43 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..e9081a4ae4e72 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,10 +54,12 @@ class X86WinEHUnwindV2 : public MachineFunctionPass {
StringRef getPassName() const override { return "WinEH Unwind V2"; }
bool runOnMachineFunction(MachineFunction &MF) override;
- bool rejectCurrentFunction() const {
- FailsUnwindV2Criteria++;
- return false;
- }
+
+private:
+ /// Rejects the current function due to an internal error within LLVM.
+ static bool rejectCurrentFunctionInternalError(const MachineFunction &MF,
+ WinX64EHUnwindV2Mode Mode,
+ StringRef Reason);
};
enum class FunctionState {
@@ -69,8 +81,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 +105,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 +125,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 +133,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 +171,16 @@ 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 rejectCurrentFunctionInternalError(
+ MF, Mode,
+ "The prolog made a stack allocation, "
+ "but the epilog did not deallocate it");
+ if (PoppedRegCount != PushedRegs.size())
+ return rejectCurrentFunctionInternalError(
+ MF, 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 +195,26 @@ 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 rejectCurrentFunctionInternalError(
+ MF, Mode,
+ "The epilog is deallocating a stack "
+ "allocation, but the prolog did "
+ "not allocate one");
+ if (HasStackDealloc)
+ return rejectCurrentFunctionInternalError(
+ MF, 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 rejectCurrentFunctionInternalError(
+ MF, Mode, "Unexpected mov or add instruction after the epilog");
break;
case X86::POP64r:
@@ -159,12 +222,22 @@ 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 rejectCurrentFunctionInternalError(
+ MF, Mode,
+ "Cannot pop registers before the stack "
+ "allocation has been deallocated");
+ if (PoppedRegCount > PushedRegs.size())
+ return rejectCurrentFunctionInternalError(
+ MF, Mode,
+ "The epilog is popping more registers than the prolog pushed");
+ if (PushedRegs[PushedRegs.size() - PoppedRegCount] !=
+ MI.getOperand(0).getReg())
+ return rejectCurrentFunctionInternalError(
+ MF, 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 +249,8 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
}
} else if (State == FunctionState::FinishedEpilog)
// Unexpected instruction after the epilog.
- return rejectCurrentFunction();
+ return rejectCurrentFunctionInternalError(
+ MF, Mode, "Registers are being popped after the epilog");
break;
default:
@@ -191,7 +265,8 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
if ((State == FunctionState::FinishedEpilog) ||
(State == FunctionState::InEpilog))
// Unknown instruction in or after the epilog.
- return rejectCurrentFunction();
+ return rejectCurrentFunctionInternalError(
+ MF, Mode, "Unexpected instruction in or after the epilog");
}
}
}
@@ -203,6 +278,25 @@ 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)) {
+ if (Mode == WinX64EHUnwindV2Mode::Required)
+ MF.getFunction().getContext().diagnose(DiagnosticInfoGenericWithLoc(
+ "Windows x64 Unwind v2 is required, but the function '" +
+ MF.getName() +
+ "' has too many unwind codes. Try splitting the function or "
+ "reducing the number of places where it exits early with a tail "
+ "call.",
+ MF.getFunction(), findDebugLoc(FirstMBB)));
+
+ FailsUnwindV2Criteria++;
+ return false;
+ }
+
MeetsUnwindV2Criteria++;
// Emit the pseudo instruction that marks the start of each epilog.
@@ -212,10 +306,20 @@ 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::rejectCurrentFunctionInternalError(
+ const MachineFunction &MF, WinX64EHUnwindV2Mode Mode, StringRef Reason) {
+ if (Mode == WinX64EHUnwindV2Mode::Required)
+ reportFatalInternalError("Windows x64 Unwind v2 is required, but LLVM has "
+ "generated incompatible code in function '" +
+ MF.getName() + "': " + Reason);
+
+ 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..dd886f8ee6172
--- /dev/null
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv2-errors.mir
@@ -0,0 +1,318 @@
+# RUN: split-file %s %t
+
+# If we force "best effort" mode, then we won't see any errors, but we won't use
+# v2.
+# BESTEFFORT-NOT: SEH_UnwindVersion
+# BESTEFFORT-NOT: SEH_UnwindV2Start
+
+;--- alloc_no_dealloc.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - \
+# RUN: %t/alloc_no_dealloc.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
+# RUN: FileCheck %s --check-prefix=ALLOC-NO-DEALLOC
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %t/alloc_no_dealloc.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
+# RUN: FileCheck %s --check-prefix=BESTEFFORT
+# ALLOC-NO-DEALLOC: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'alloc_no_dealloc':
+# ALLOC-NO-DEALLOC-SAME: The prolog made a stack allocation, but the epilog did not deallocate it
+
+--- |
+ define dso_local void @alloc_no_dealloc() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+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
+...
+
+;--- missed_push.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - %t/missed_push.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 2>&1 | FileCheck %s \
+# RUN: --check-prefix=MISSED-PUSH
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %t/missed_push.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
+# RUN: FileCheck %s --check-prefix=BESTEFFORT
+# MISSED-PUSH: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'missed_push':
+# MISSED-PUSH-SAME: The prolog pushed more registers than the epilog popped
+
+--- |
+ define dso_local void @missed_push() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+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
+...
+
+;--- dealloc_no_alloc.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - \
+# RUN: %t/dealloc_no_alloc.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
+# RUN: FileCheck %s --check-prefix=DEALLOC-NO-ALLOC
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %t/dealloc_no_alloc.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
+# RUN: FileCheck %s --check-prefix=BESTEFFORT
+# DEALLOC-NO-ALLOC: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'dealloc_no_alloc':
+# DEALLOC-NO-ALLOC-SAME: The epilog is deallocating a stack allocation, but the prolog did not allocate one
+
+--- |
+ define dso_local void @dealloc_no_alloc() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+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
+...
+
+;--- double_dealloc.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - %t/double_dealloc.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 2>&1 | FileCheck %s \
+# RUN: --check-prefix=DOUBLE-DEALLOC
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %t/double_dealloc.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
+# RUN: FileCheck %s --check-prefix=BESTEFFORT
+# DOUBLE-DEALLOC: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'double_dealloc':
+# DOUBLE-DEALLOC-SAME: The epilog is deallocating the stack allocation more than once
+
+--- |
+ define dso_local void @double_dealloc() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+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
+...
+
+;--- dealloc_after_epilog.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - \
+# RUN: %t/dealloc_after_epilog.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
+# RUN: FileCheck %s --check-prefix=DEALLOC-AFTER-EPILOG
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - \
+# RUN: %t/dealloc_after_epilog.mir -run-pass=x86-wineh-unwindv2 \
+# RUN: -x86-wineh-unwindv2-force-mode=1 | FileCheck %s \
+# RUN: --check-prefix=BESTEFFORT
+# DEALLOC-AFTER-EPILOG: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'dealloc_after_epilog':
+# DEALLOC-AFTER-EPILOG-SAME: Unexpected mov or add instruction after the epilog
+
+--- |
+ define dso_local void @dealloc_after_epilog() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+name: dealloc_after_epilog
+body: |
+ bb.0.entry:
+ frame-setup SEH_EndPrologue
+ SEH_BeginEpilogue
+ SEH_EndEpilogue
+ $rsp = frame-destroy ADD64ri32 $rsp, 40, implicit-def dead $eflags
+ RET64
+...
+
+;--- pop_before_dealloc.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - \
+# RUN: %t/pop_before_dealloc.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
+# RUN: FileCheck %s --check-prefix=POP-BEFORE-DEALLOC
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %t/pop_before_dealloc.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
+# RUN: FileCheck %s --check-prefix=BESTEFFORT
+# POP-BEFORE-DEALLOC: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'pop_before_dealloc':
+# POP-BEFORE-DEALLOC-SAME: Cannot pop registers before the stack allocation has been deallocated
+
+--- |
+ define dso_local void @pop_before_dealloc() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+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
+...
+
+;--- too_many_pops.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - %t/too_many_pops.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 2>&1 | FileCheck %s \
+# RUN: --check-prefix=TOO-MANY-POPS
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %t/too_many_pops.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
+# RUN: FileCheck %s --check-prefix=BESTEFFORT
+# TOO-MANY-POPS: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'too_many_pops':
+# TOO-MANY-POPS-SAME: The epilog is popping more registers than the prolog pushed
+
+--- |
+ define dso_local void @too_many_pops() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+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
+...
+
+;--- pop_in_wrong_order.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - \
+# RUN: %t/pop_in_wrong_order.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
+# RUN: FileCheck %s --check-prefix=POP-WRONG-ORDER
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %t/pop_in_wrong_order.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
+# RUN: FileCheck %s --check-prefix=BESTEFFORT
+# POP-WRONG-ORDER: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'pop_in_wrong_order':
+# POP-WRONG-ORDER-SAME: The epilog is popping a registers in a different order than the prolog pushed them
+
+--- |
+ define dso_local void @pop_in_wrong_order() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+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
+...
+
+;--- pop_after_epilog.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - \
+# RUN: %t/pop_after_epilog.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
+# RUN: FileCheck %s --check-prefix=POP-AFTER-EPILOG
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %t/pop_after_epilog.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
+# RUN: FileCheck %s --check-prefix=BESTEFFORT
+# POP-AFTER-EPILOG: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'pop_after_epilog':
+# POP-AFTER-EPILOG-SAME: Registers are being popped after the epilog
+
+--- |
+ define dso_local void @pop_after_epilog() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+name: pop_after_epilog
+body: |
+ bb.0.entry:
+ frame-setup SEH_EndPrologue
+ SEH_BeginEpilogue
+ SEH_EndEpilogue
+ $rdi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+ RET64
+...
+
+;--- instr_after_epilog.mir
+# RUN: not llc -mtriple=x86_64-unknown-windows-msvc -o - \
+# RUN: %t/instr_after_epilog.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
+# RUN: FileCheck %s --check-prefix=INSTR-AFTER-END
+# RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %t/instr_after_epilog.mir \
+# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
+# RUN: FileCheck %s --check-prefix=BESTEFFORT
+# INSTR-AFTER-END: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'instr_after_epilog':
+# INSTR-AFTER-END-SAME: Unexpected instruction in or after the epilog
+
+--- |
+ define dso_local void @instr_after_epilog() local_unnamed_addr {
+ entry:
+ ret void
+ }
+ !llvm.module.flags = !{!0}
+ !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
+...
+---
+name: instr_after_epilog
+body: |
+ bb.0.entry:
+ frame-setup SEH_EndPrologue
+ SEH_BeginEpilogue
+ SEH_EndEpilogue
+ $ecx = MOV32rr killed $eax
+ RET64
+...
More information about the llvm-commits
mailing list