A function that saves a callee-saved EGPR (R16-R31) cannot be encoded with V1/V2 unwind info, so it must use V3 even when the module default stays on V1/V2 (e.g. an APX clone created by auto-dispatch alongside a baseline generic clone). The previous code rejected such functions with a recoverable backend diagnostic ("EGPR (R16-R31) requires V3 unwind info on Windows x64") instead of emitting valid V3 unwind info.
Introduce a single shared predicate, requiresWinX64UnwindV3(MF), that returns true when the whole module is in V3 mode, or when the function needs an unwind table and may use EGPR. It is consumed by X86FrameLowering (SEH prolog/epilog layout), the X86WinEHUnwindV2 pass (which skips such functions), and the X86WinEHUnwindV3 pass (which stamps a per-function .seh_unwindversion 3 on every WinEH frame -- the entry block and each funclet). Also widen the SEH_UnwindVersion pseudo operand from i1imm to i8imm since it holds 1, 2 or 3.
457 lines
17 KiB
C++
457 lines
17 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/TargetFrameLowering.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>
|
|
UnwindCodeThreshold("x86-wineh-unwindv2-unwind-codes-threshold", cl::Hidden,
|
|
cl::desc("Maximum number of unwind codes before "
|
|
"splitting into a new 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."));
|
|
|
|
// This threshold is for the *approximate* number of instructions, see the
|
|
// comment in runAnalysisOnFuncOrFunclet for more details.
|
|
static cl::opt<unsigned> InstructionCountThreshold(
|
|
"x86-wineh-unwindv2-instruction-count-threshold", cl::Hidden,
|
|
cl::desc("Maximum number of (approximate) instructions before splitting "
|
|
"into a new unwind info."),
|
|
cl::init(600));
|
|
|
|
namespace {
|
|
|
|
struct EpilogInfo {
|
|
MachineInstr *UnwindV2StartLocation;
|
|
unsigned ApproximateInstructionPosition;
|
|
};
|
|
|
|
struct FrameInfo {
|
|
unsigned ApproximatePrologCodeCount;
|
|
unsigned ApproximateInstructionCount;
|
|
SmallVector<EpilogInfo> EpilogInfos;
|
|
};
|
|
|
|
class X86WinEHUnwindV2Legacy : public MachineFunctionPass {
|
|
public:
|
|
static char ID;
|
|
|
|
X86WinEHUnwindV2Legacy() : MachineFunctionPass(ID) {
|
|
initializeX86WinEHUnwindV2LegacyPass(*PassRegistry::getPassRegistry());
|
|
}
|
|
|
|
StringRef getPassName() const override { return "WinEH Unwind V2"; }
|
|
|
|
bool runOnMachineFunction(MachineFunction &MF) override;
|
|
};
|
|
|
|
/// Rejects the current function due to an internal error within LLVM.
|
|
std::nullopt_t rejectCurrentFunctionInternalError(const MachineFunction &MF,
|
|
WinX64EHUnwindMode Mode,
|
|
StringRef Reason) {
|
|
if (Mode == WinX64EHUnwindMode::V2Required)
|
|
reportFatalInternalError("Windows x64 Unwind v2 is required, but LLVM has "
|
|
"generated incompatible code in function '" +
|
|
MF.getName() + "': " + Reason);
|
|
|
|
FailsUnwindV2Criteria++;
|
|
return std::nullopt;
|
|
}
|
|
|
|
enum class FunctionState {
|
|
InProlog,
|
|
HasProlog,
|
|
InEpilog,
|
|
FinishedEpilog,
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
char X86WinEHUnwindV2Legacy::ID = 0;
|
|
|
|
INITIALIZE_PASS(X86WinEHUnwindV2Legacy, "x86-wineh-unwindv2",
|
|
"Analyze and emit instructions for Win64 Unwind v2", false,
|
|
false)
|
|
|
|
FunctionPass *llvm::createX86WinEHUnwindV2LegacyPass() {
|
|
return new X86WinEHUnwindV2Legacy();
|
|
}
|
|
|
|
DebugLoc findDebugLoc(const MachineBasicBlock &MBB) {
|
|
for (const MachineInstr &MI : MBB)
|
|
if (MI.getDebugLoc())
|
|
return MI.getDebugLoc();
|
|
|
|
return DebugLoc::getUnknown();
|
|
}
|
|
|
|
// Continues running the analysis on the given function or funclet.
|
|
std::optional<FrameInfo>
|
|
runAnalysisOnFuncOrFunclet(MachineFunction &MF, MachineFunction::iterator &Iter,
|
|
WinX64EHUnwindMode Mode) {
|
|
const TargetFrameLowering &TFL = *MF.getSubtarget().getFrameLowering();
|
|
|
|
// 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;
|
|
bool HasSetFrame = false;
|
|
unsigned ApproximatePrologCodeCount = 0;
|
|
|
|
SmallVector<EpilogInfo> EpilogInfos;
|
|
|
|
// Unwind v2 requires that the epilog is no more than 4Kb away from the last
|
|
// instruction that the current unwind info covers. If we believe that we are
|
|
// going over that limit then we need to split the unwind info. Ideally we'd
|
|
// do this at the point where we actually know how far away we are from the
|
|
// last instruction, but that's not possible here and splitting unwind infos
|
|
// in MC would be difficult. However, the cost of splitting an unwind info is
|
|
// fairly cheap (in the other of bytes in the xdata section), so we can
|
|
// instead use a heuristic based on the number of MachineInstrs to decide when
|
|
// to split unwind infos, and allow users to tune the threshold if needed.
|
|
// This is not a perfect solution, but 1) it is cheap to calculate, 2) allows
|
|
// the common case for small functions or large functions with multiple
|
|
// returns at the end to have a single unwind info, and 3) allows unwind v2 to
|
|
// be used in large functions (that would otherwise be rejected) for a small
|
|
// binary size cost.
|
|
unsigned ApproximateInstructionCount = 0;
|
|
|
|
for (; Iter != MF.end(); ++Iter) {
|
|
MachineBasicBlock &MBB = *Iter;
|
|
|
|
// If we're already been processing a function, then come across a funclet
|
|
// then break since the funclet will get a fresh frame info.
|
|
if (MBB.isEHFuncletEntry() && State != FunctionState::InProlog)
|
|
break;
|
|
|
|
// Current epilog information. We assume that epilogs cannot cross basic
|
|
// block boundaries.
|
|
unsigned PoppedRegCount = 0;
|
|
bool HasStackDealloc = false;
|
|
bool HasSetFrameBack = false;
|
|
MachineInstr *UnwindV2StartLocation = nullptr;
|
|
|
|
for (MachineInstr &MI : MBB) {
|
|
// This is an *approximation* of the number of instructions that will be
|
|
// emitted. It is not the actual number of instructions, but that doesn't
|
|
// matter: see the comment at the declaration of
|
|
// ApproximateInstructionCount.
|
|
if (!MI.isPseudo() && !MI.isMetaInstruction())
|
|
ApproximateInstructionCount++;
|
|
|
|
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:
|
|
if (State != FunctionState::InProlog)
|
|
llvm_unreachable("SEH_StackAlloc outside of prolog");
|
|
// Assume a large alloc...
|
|
ApproximatePrologCodeCount += 3;
|
|
HasStackAlloc = true;
|
|
break;
|
|
|
|
case X86::SEH_SetFrame:
|
|
if (State != FunctionState::InProlog)
|
|
llvm_unreachable("SEH_SetFrame outside of prolog");
|
|
ApproximatePrologCodeCount++;
|
|
HasSetFrame = 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;
|
|
EpilogInfos.push_back(
|
|
{UnwindV2StartLocation, ApproximateInstructionCount});
|
|
State = FunctionState::FinishedEpilog;
|
|
break;
|
|
|
|
case X86::MOV64rr:
|
|
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 (!HasSetFrame)
|
|
return rejectCurrentFunctionInternalError(
|
|
MF, Mode,
|
|
"The epilog is setting frame back, but prolog did not set it");
|
|
if (PoppedRegCount > 0)
|
|
return rejectCurrentFunctionInternalError(
|
|
MF, Mode,
|
|
"The epilog is setting the frame back after popping "
|
|
"registers");
|
|
if (HasStackDealloc)
|
|
return rejectCurrentFunctionInternalError(
|
|
MF, Mode,
|
|
"Cannot set the frame back after the stack "
|
|
"allocation has been deallocated");
|
|
HasSetFrameBack = true;
|
|
} else if (State == FunctionState::FinishedEpilog)
|
|
return rejectCurrentFunctionInternalError(
|
|
MF, Mode, "Unexpected mov instruction after the epilog");
|
|
break;
|
|
|
|
case X86::LEA64r:
|
|
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 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 {
|
|
// Special case: no explicit stack dealloc is required if SetFrame
|
|
// was used and the function has a frame pointer.
|
|
if (PoppedRegCount == 0 && HasStackAlloc && !HasStackDealloc &&
|
|
HasSetFrameBack && TFL.hasFP(MF))
|
|
HasStackDealloc = true;
|
|
|
|
// 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");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FrameInfo{ApproximatePrologCodeCount, ApproximateInstructionCount,
|
|
EpilogInfos};
|
|
}
|
|
|
|
bool runX86WinEHUnwindV2(MachineFunction &MF) {
|
|
WinX64EHUnwindMode Mode =
|
|
ForceMode.getNumOccurrences()
|
|
? static_cast<WinX64EHUnwindMode>(ForceMode.getValue())
|
|
: MF.getFunction().getParent()->getWinX64EHUnwindMode();
|
|
|
|
// Only act on V2 modes; V1 = disabled, V3 handled by the V3 pass.
|
|
if (Mode != WinX64EHUnwindMode::V2BestEffort &&
|
|
Mode != WinX64EHUnwindMode::V2Required)
|
|
return false;
|
|
|
|
// A function that requires V3 (see requireWinX64UnwindV3()) is emitted as V3
|
|
// instead; skip it here so this pass does not stamp V2 pseudos that conflict
|
|
// with the V3 layout.
|
|
if (requireWinX64UnwindV3(MF))
|
|
return false;
|
|
|
|
// Requested changes.
|
|
SmallVector<FrameInfo> FrameInfos;
|
|
MachineFunction::iterator Iter = MF.begin();
|
|
while (Iter != MF.end()) {
|
|
auto FI = runAnalysisOnFuncOrFunclet(MF, Iter, Mode);
|
|
if (!FI)
|
|
return false;
|
|
if (!FI->EpilogInfos.empty())
|
|
FrameInfos.push_back(std::move(*FI));
|
|
}
|
|
|
|
if (FrameInfos.empty())
|
|
return false;
|
|
|
|
MeetsUnwindV2Criteria++;
|
|
|
|
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
|
|
for (auto &FI : FrameInfos) {
|
|
// Walk the list of epilogs backwards and add new SEH pseudo instructions:
|
|
// * SEH_UnwindV2Start at the start of each epilog.
|
|
// * If the current instruction is too far away from where the last unwind
|
|
// info ended OR there are too many unwind codes in the info, then add
|
|
// SEH_SplitChainedAtEndOfBlock to finish the current info.
|
|
unsigned LastUnwindInfoEndPosition = FI.ApproximateInstructionCount;
|
|
unsigned UnwindCodeCount = FI.ApproximatePrologCodeCount + 1;
|
|
for (auto &Info : llvm::reverse(FI.EpilogInfos)) {
|
|
MachineBasicBlock &MBB = *Info.UnwindV2StartLocation->getParent();
|
|
const DebugLoc &DL = Info.UnwindV2StartLocation->getDebugLoc();
|
|
BuildMI(MBB, Info.UnwindV2StartLocation, DL,
|
|
TII->get(X86::SEH_UnwindV2Start));
|
|
|
|
if ((LastUnwindInfoEndPosition - Info.ApproximateInstructionPosition >=
|
|
InstructionCountThreshold) ||
|
|
(UnwindCodeCount >= UnwindCodeThreshold)) {
|
|
BuildMI(MBB, MBB.begin(), DL,
|
|
TII->get(X86::SEH_SplitChainedAtEndOfBlock));
|
|
LastUnwindInfoEndPosition = Info.ApproximateInstructionPosition;
|
|
// Doesn't reset to 0, as the prolog unwind codes are now in this info.
|
|
UnwindCodeCount = FI.ApproximatePrologCodeCount + 1;
|
|
}
|
|
|
|
UnwindCodeCount++;
|
|
}
|
|
}
|
|
|
|
// Note that the function is using Unwind v2.
|
|
MachineBasicBlock &FirstMBB = MF.front();
|
|
BuildMI(FirstMBB, FirstMBB.front(), findDebugLoc(FirstMBB),
|
|
TII->get(X86::SEH_UnwindVersion))
|
|
.addImm(2);
|
|
|
|
return true;
|
|
}
|
|
|
|
PreservedAnalyses
|
|
X86WinEHUnwindV2Pass::run(MachineFunction &MF,
|
|
MachineFunctionAnalysisManager &MFAM) {
|
|
const bool Modified = runX86WinEHUnwindV2(MF);
|
|
return Modified ? getMachineFunctionPassPreservedAnalyses()
|
|
.preserveSet<CFGAnalyses>()
|
|
: PreservedAnalyses::all();
|
|
}
|
|
|
|
bool X86WinEHUnwindV2Legacy::runOnMachineFunction(MachineFunction &MF) {
|
|
return runX86WinEHUnwindV2(MF);
|
|
}
|