[win][x64] Unwind v2 3/n: Add support for requiring unwind v2 to be used (equivalent to MSVC's /d2epilogunwindrequirev2) (#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.
This commit is contained in:
Daniel Paoliello
2025-06-16 15:06:41 -07:00
committed by GitHub
parent 4bcf9732c7
commit 2488f26d15
12 changed files with 595 additions and 43 deletions

View File

@@ -483,8 +483,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"

View File

@@ -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 "
@@ -8972,7 +8975,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">;

View File

@@ -1319,8 +1319,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)

View File

@@ -7360,8 +7360,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,
@@ -8418,8 +8417,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();

View File

@@ -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"

View File

@@ -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

View File

@@ -1041,6 +1041,10 @@ public:
/// 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

View File

@@ -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

View File

@@ -917,3 +917,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;
}

View File

@@ -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 @@ public:
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;
}

View File

@@ -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 --crash llc -mtriple=x86_64-pc-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-pc-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 --crash llc -mtriple=x86_64-pc-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-pc-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 --crash llc -mtriple=x86_64-pc-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-pc-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 --crash llc -mtriple=x86_64-pc-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-pc-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 --crash llc -mtriple=x86_64-pc-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-pc-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 --crash llc -mtriple=x86_64-pc-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-pc-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 --crash llc -mtriple=x86_64-pc-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-pc-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 --crash llc -mtriple=x86_64-pc-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-pc-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 --crash llc -mtriple=x86_64-pc-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-pc-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 --crash llc -mtriple=x86_64-pc-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-pc-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
...

View File

@@ -0,0 +1,94 @@
# Require V2 and restrict the number of unwind codes to 8
# RUN: not llc -mtriple=x86_64-pc-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-pc-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: llc -mtriple=x86_64-pc-windows-msvc -o - %s \
# RUN: -run-pass=x86-wineh-unwindv2 | FileCheck %s -check-prefix=ALLOWMORE
# Usually 255 unwind codes are permitted, but we passed an arg to llc to limit
# it to 8.
# REQUIREV2: error: example.c:2:1: Windows x64 Unwind v2 is required, but the function 'too_many_epilogs' has too many unwind codes.
# REQUIREV2-SAME: Try splitting the function or reducing the number of places where it exits early with a tail call.
# 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
# If we allow more epilogs then too_many_epilogs will compile with v2.
# ALLOWMORE-LABEL: too_many_epilogs
# ALLOWMORE: SEH_UnwindVersion 2
# ALLOWMORE: SEH_UnwindV2Start
--- |
define dso_local void @too_many_epilogs() local_unnamed_addr !dbg !9 {
entry:
ret void, !dbg !10
}
!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: "too_many_epilogs", scope: !6, file: !6, line: 1, type: !7, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
!10 = !DILocation(line: 2, column: 1, scope: !9)
!11 = !DILocation(line: 3, column: 1, scope: !9)
...
---
name: too_many_epilogs
body: |
bb.0.entry:
frame-setup SEH_EndPrologue
SEH_BeginEpilogue
SEH_EndEpilogue
RET64 debug-location !10
bb.1:
SEH_BeginEpilogue
SEH_EndEpilogue
RET64 debug-location !11
bb.2:
SEH_BeginEpilogue
SEH_EndEpilogue
RET64 debug-location !11
bb.3:
SEH_BeginEpilogue
SEH_EndEpilogue
RET64 debug-location !11
bb.4:
SEH_BeginEpilogue
SEH_EndEpilogue
RET64 debug-location !11
bb.5:
SEH_BeginEpilogue
SEH_EndEpilogue
RET64 debug-location !11
bb.6:
SEH_BeginEpilogue
SEH_EndEpilogue
RET64 debug-location !11
bb.7:
SEH_BeginEpilogue
SEH_EndEpilogue
RET64 debug-location !11
bb.8:
SEH_BeginEpilogue
SEH_EndEpilogue
RET64 debug-location !11
...