llvm-project/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
Daniel Paoliello a4cff34f3f
[win][x64] Permit lea to adjust the stack when using unwind v2 (#154171)
In some cases `leaq` may be used to adjust the stack in an epilog, this
is permitted by unwind v2 and shouldn't raise an error.
2025-08-18 15:00:59 -07:00

332 lines
12 KiB
C++

//===-- X86WinEHUnwindV2.cpp - Win x64 Unwind v2 ----------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// Implements the analysis required to detect if a function can use Unwind v2
/// information, and emits the neccesary pseudo instructions used by MC to
/// generate the unwind info.
///
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/X86BaseInfo.h"
#include "X86.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Module.h"
using namespace llvm;
#define DEBUG_TYPE "x86-wineh-unwindv2"
STATISTIC(MeetsUnwindV2Criteria,
"Number of functions that meet Unwind v2 criteria");
STATISTIC(FailsUnwindV2Criteria,
"Number of functions that fail Unwind v2 criteria");
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 {
public:
static char ID;
X86WinEHUnwindV2() : MachineFunctionPass(ID) {
initializeX86WinEHUnwindV2Pass(*PassRegistry::getPassRegistry());
}
StringRef getPassName() const override { return "WinEH Unwind V2"; }
bool runOnMachineFunction(MachineFunction &MF) override;
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 {
InProlog,
HasProlog,
InEpilog,
FinishedEpilog,
};
} // end anonymous namespace
char X86WinEHUnwindV2::ID = 0;
INITIALIZE_PASS(X86WinEHUnwindV2, "x86-wineh-unwindv2",
"Analyze and emit instructions for Win64 Unwind v2", false,
false)
FunctionPass *llvm::createX86WinEHUnwindV2Pass() {
return new X86WinEHUnwindV2();
}
DebugLoc findDebugLoc(const MachineBasicBlock &MBB) {
for (const MachineInstr &MI : MBB)
if (MI.getDebugLoc())
return MI.getDebugLoc();
return DebugLoc::getUnknown();
}
bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
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
// start with a prolog.
FunctionState State = FunctionState::InProlog;
// Prolog information.
SmallVector<int64_t> PushedRegs;
bool HasStackAlloc = false;
unsigned ApproximatePrologCodeCount = 0;
// Requested changes.
SmallVector<MachineInstr *> UnwindV2StartLocations;
for (MachineBasicBlock &MBB : MF) {
// Current epilog information. We assume that epilogs cannot cross basic
// block boundaries.
unsigned PoppedRegCount = 0;
bool HasStackDealloc = false;
MachineInstr *UnwindV2StartLocation = nullptr;
for (MachineInstr &MI : MBB) {
switch (MI.getOpcode()) {
//
// Prolog handling.
//
case X86::SEH_PushReg:
if (State != FunctionState::InProlog)
llvm_unreachable("SEH_PushReg outside of prolog");
ApproximatePrologCodeCount++;
PushedRegs.push_back(MI.getOperand(0).getImm());
break;
case X86::SEH_StackAlloc:
case X86::SEH_SetFrame:
if (State != FunctionState::InProlog)
llvm_unreachable("SEH_StackAlloc or SEH_SetFrame outside of prolog");
// 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");
State = FunctionState::HasProlog;
break;
//
// Epilog handling.
//
case X86::SEH_BeginEpilogue:
if (State != FunctionState::HasProlog)
llvm_unreachable("SEH_BeginEpilogue in prolog or another epilog");
State = FunctionState::InEpilog;
break;
case X86::SEH_EndEpilogue:
if (State != FunctionState::InEpilog)
llvm_unreachable("SEH_EndEpilogue outside of epilog");
if (HasStackAlloc != HasStackDealloc)
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.
if (!UnwindV2StartLocation)
UnwindV2StartLocation = &MI;
UnwindV2StartLocations.push_back(UnwindV2StartLocation);
State = FunctionState::FinishedEpilog;
break;
case X86::LEA64r:
case X86::MOV64rr:
case X86::ADD64ri32:
if (State == FunctionState::InEpilog) {
// If the prolog contains a stack allocation, then the first
// instruction in the epilog must be to adjust the stack pointer.
if (!HasStackAlloc)
return rejectCurrentFunctionInternalError(
MF, Mode,
"The epilog is deallocating a stack "
"allocation, but the prolog did "
"not allocate one");
if (PoppedRegCount > 0)
return rejectCurrentFunctionInternalError(
MF, Mode,
"The epilog is deallocating a stack allocation after popping "
"registers");
HasStackDealloc = true;
} else if (State == FunctionState::FinishedEpilog)
return rejectCurrentFunctionInternalError(
MF, Mode,
"Unexpected lea, mov or add instruction after the epilog");
break;
case X86::POP64r:
if (State == FunctionState::InEpilog) {
Register Reg = MI.getOperand(0).getReg();
if (HasStackAlloc && (PoppedRegCount == 0) &&
!llvm::is_contained(PushedRegs, Reg)) {
// If this is a pop that doesn't correspond to the set of pushed
// registers, then assume it was used to adjust the stack pointer.
HasStackDealloc = true;
} else {
// 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)
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] != Reg.id())
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
// stack pointer) but from the first POP instruction (if there is
// one).
if (!UnwindV2StartLocation) {
assert(PoppedRegCount == 1);
UnwindV2StartLocation = &MI;
}
}
} else if (State == FunctionState::FinishedEpilog)
// Unexpected instruction after the epilog.
return rejectCurrentFunctionInternalError(
MF, Mode, "Registers are being popped after the epilog");
break;
default:
if (MI.isTerminator()) {
if (State == FunctionState::FinishedEpilog)
// Found the terminator after the epilog, we're now ready for
// another epilog.
State = FunctionState::HasProlog;
else if (State == FunctionState::InEpilog)
llvm_unreachable("Terminator in the middle of the epilog");
} else if (!MI.isDebugOrPseudoInstr()) {
if ((State == FunctionState::FinishedEpilog) ||
(State == FunctionState::InEpilog))
// Unknown instruction in or after the epilog.
return rejectCurrentFunctionInternalError(
MF, Mode, "Unexpected instruction in or after the epilog");
}
}
}
}
if (UnwindV2StartLocations.empty()) {
assert(State == FunctionState::InProlog &&
"If there are no epilogs, then there should be no prolog");
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.
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
for (MachineInstr *MI : UnwindV2StartLocations) {
BuildMI(*MI->getParent(), MI, MI->getDebugLoc(),
TII->get(X86::SEH_UnwindV2Start));
}
// Note that the function is using Unwind v2.
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;
}