blob: bd98b932459a67f27d33ae86f49bbbb6abbf6fcf [file]
//===-- X86WinEHUnwindV3.cpp - Win x64 Unwind v3 ----------------*- 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 capacity-checking and sub-fragment splitting pass for
/// Unwind v3 information. V3 can encode any prolog/epilog pattern, so this
/// pass does not validate epilog structure; it only needs to:
/// 1. Count prolog/epilog operations and epilogs.
/// 2. Check V3 capacity limits (<=31 prolog/epilog ops, <=7 epilogs).
/// 3. Insert sub-fragment split points if limits are exceeded.
///
/// The unwind version is set module-wide, not per-function.
///
/// See https://learn.microsoft.com/en-us/cpp/build/x64-unwind-information-v3
///
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/X86BaseInfo.h"
#include "X86.h"
#include "X86Subtarget.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/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
using namespace llvm;
#define DEBUG_TYPE "x86-wineh-unwindv3"
STATISTIC(FunctionsProcessed,
"Number of functions processed by Unwind v3 pass");
STATISTIC(SubFragmentSplits,
"Number of sub-fragment splits inserted for Unwind v3");
/// V3 limits from the format specification.
static constexpr unsigned MaxV3PrologOps = 31;
static constexpr unsigned MaxV3Epilogs = 7;
static constexpr unsigned MaxV3EpilogOps = 31;
/// Maximum approximate instruction distance allowed between two adjacent
/// epilogs, and between the last epilog and the funclet end, before the
/// funclet is split into a new chained sub-fragment. V3 encodes each epilog's
/// position as a signed 16-bit EpilogOffset: a delta from the previous epilog,
/// with the tail-closest epilog encoded relative to the fragment end. The exact
/// byte offsets aren't known until MC layout, so the approximate instruction
/// count is used as a proxy, with margin for the average emitted instruction
/// size.
static cl::opt<unsigned> EpilogDistanceThreshold(
"x86-wineh-unwindv3-epilog-distance-threshold", cl::Hidden,
cl::desc(
"Maximum approximate instruction distance between adjacent epilogs "
"(or between the last epilog and the funclet end) before "
"splitting into a new chained unwind info for Unwind v3."),
cl::init(3000));
/// After reporting a recoverable error for `MF`, erase all SEH pseudo-
/// instructions and clear the WinCFI flag so the AsmPrinter doesn't try to
/// emit (potentially malformed) unwind information. The LLVMContext
/// diagnostic recorded by the caller will prevent the object file from
/// actually being written.
static void suppressWinCFI(MachineFunction &MF) {
for (MachineBasicBlock &MBB : MF) {
for (MachineInstr &MI : llvm::make_early_inc_range(MBB)) {
switch (MI.getOpcode()) {
case X86::SEH_PushReg:
case X86::SEH_Push2Regs:
case X86::SEH_SaveReg:
case X86::SEH_SaveXMM:
case X86::SEH_StackAlloc:
case X86::SEH_StackAlign:
case X86::SEH_SetFrame:
case X86::SEH_PushFrame:
case X86::SEH_EndPrologue:
case X86::SEH_BeginEpilogue:
case X86::SEH_EndEpilogue:
case X86::SEH_SplitChained:
case X86::SEH_SplitChainedAtEndOfBlock:
MI.eraseFromParent();
break;
default:
break;
}
}
}
MF.setHasWinCFI(false);
}
namespace {
/// A V3 epilog and the approximate instruction position where it begins, used
/// as a candidate sub-fragment split point.
struct EpilogSplitPoint {
MachineInstr *BeginEpilog;
unsigned ApproxInstrPos;
};
/// Per-funclet analysis results.
struct FuncletInfo {
unsigned PrologOpCount = 0;
unsigned MaxEpilogOpCount = 0;
/// Approximate instruction position at the end of the funclet, used as the
/// initial fragment tail reference for size-based splitting.
unsigned EndInstrPos = 0;
/// SEH_BeginEpilogue instructions (with approximate positions), used as
/// candidate insertion points for sub-fragment splitting.
SmallVector<EpilogSplitPoint, 8> Epilogs;
};
class X86WinEHUnwindV3 : public MachineFunctionPass {
public:
static char ID;
X86WinEHUnwindV3() : MachineFunctionPass(ID) {
initializeX86WinEHUnwindV3Pass(*PassRegistry::getPassRegistry());
}
StringRef getPassName() const override { return "WinEH Unwind V3"; }
bool runOnMachineFunction(MachineFunction &MF) override;
private:
/// Analyze one funclet (or the main function body) starting at Iter.
/// Advances Iter past the analyzed region, stopping at the next funclet
/// entry or the end of the function. ApproxInstrPos is a running count of
/// emitted instructions across the whole function, used to estimate the
/// byte distance between epilogs and their fragment tail.
static FuncletInfo analyzeFunclet(MachineFunction &MF,
MachineFunction::iterator &Iter,
unsigned &ApproxInstrPos);
};
} // end anonymous namespace
char X86WinEHUnwindV3::ID = 0;
INITIALIZE_PASS(X86WinEHUnwindV3, "x86-wineh-unwindv3",
"Capacity check and sub-fragment splitting for Win64 Unwind v3",
false, false)
FunctionPass *llvm::createX86WinEHUnwindV3Pass() {
return new X86WinEHUnwindV3();
}
FuncletInfo X86WinEHUnwindV3::analyzeFunclet(MachineFunction &MF,
MachineFunction::iterator &Iter,
unsigned &ApproxInstrPos) {
FuncletInfo Info;
bool InEpilog = false;
bool SeenProlog = false;
unsigned CurrentEpilogOpCount = 0;
for (; Iter != MF.end(); ++Iter) {
MachineBasicBlock &MBB = *Iter;
// If we've already been processing a funclet's prolog/body and encounter
// another funclet entry, stop - that funclet gets its own analysis.
if (MBB.isEHFuncletEntry() && SeenProlog)
break;
for (MachineInstr &MI : MBB) {
// Approximate the number of emitted instructions. This estimates how
// far each epilog sits from its fragment tail; the exact byte offsets
// aren't available until MC layout.
if (!MI.isPseudo() && !MI.isMetaInstruction())
ApproxInstrPos++;
switch (MI.getOpcode()) {
case X86::SEH_PushReg:
case X86::SEH_Push2Regs:
case X86::SEH_StackAlloc:
case X86::SEH_SetFrame:
case X86::SEH_SaveReg:
case X86::SEH_SaveXMM:
case X86::SEH_PushFrame:
if (InEpilog)
CurrentEpilogOpCount++;
else
Info.PrologOpCount++;
break;
case X86::SEH_EndPrologue:
SeenProlog = true;
break;
case X86::SEH_BeginEpilogue:
InEpilog = true;
CurrentEpilogOpCount = 0;
LLVM_DEBUG(dbgs() << " epilog " << Info.Epilogs.size()
<< " begins at approx instruction position "
<< ApproxInstrPos << "\n");
Info.Epilogs.push_back({&MI, ApproxInstrPos});
break;
case X86::SEH_EndEpilogue:
InEpilog = false;
Info.MaxEpilogOpCount =
std::max(Info.MaxEpilogOpCount, CurrentEpilogOpCount);
break;
default:
break;
}
}
}
Info.EndInstrPos = ApproxInstrPos;
LLVM_DEBUG(dbgs() << " funclet has " << Info.Epilogs.size()
<< " epilog(s); ends at approx instruction position "
<< ApproxInstrPos << "\n");
return Info;
}
bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) {
WinX64EHUnwindMode Mode =
MF.getFunction().getParent()->getWinX64EHUnwindMode();
Function &F = MF.getFunction();
LLVMContext &Ctx = F.getContext();
// EGPR (R16-R31) requires V3 unwind info because V1/V2 cannot encode
// registers beyond R15. Only enforce this for functions that actually
// emit SEH unwind info — `nounwind` functions and targets that don't
// require unwind tables (e.g. cross-compilation host defaults) can use
// EGPR with any unwind mode since no SEH metadata is generated.
if (Mode != WinX64EHUnwindMode::V3) {
if (!F.needsUnwindTableEntry())
return false;
const auto &STI = MF.getSubtarget<X86Subtarget>();
if (STI.hasEGPR()) {
Ctx.diagnose(DiagnosticInfoUnsupported(
F, "EGPR (R16-R31) requires V3 unwind info on Windows x64"));
// Stripping the SEH pseudos modifies the function, so report a change.
suppressWinCFI(MF);
return true;
}
return false;
}
bool Changed = false;
unsigned ApproxInstrPos = 0;
MachineFunction::iterator Iter = MF.begin();
LLVM_DEBUG(dbgs() << "X86WinEHUnwindV3: processing " << MF.getName() << "\n");
// Process each funclet (and the main function body) independently.
// Each funclet gets its own UNWIND_INFO, so V3 limits apply per funclet.
while (Iter != MF.end()) {
FuncletInfo Info = analyzeFunclet(MF, Iter, ApproxInstrPos);
if (Info.PrologOpCount > MaxV3PrologOps) {
Ctx.diagnose(DiagnosticInfoResourceLimit(
F, "number of unwind v3 prolog operations required",
Info.PrologOpCount, MaxV3PrologOps, DS_Error, DK_ResourceLimit));
Ctx.diagnose(DiagnosticInfoGenericWithLoc(
"sub-fragment splitting for prolog overflow is not yet implemented",
F, F.getSubprogram(), DS_Note));
// Stripping the SEH pseudos modifies the function, so report a change.
suppressWinCFI(MF);
return true;
}
if (Info.MaxEpilogOpCount > MaxV3EpilogOps) {
Ctx.diagnose(DiagnosticInfoResourceLimit(
F, "number of unwind v3 epilog operations required",
Info.MaxEpilogOpCount, MaxV3EpilogOps, DS_Error, DK_ResourceLimit));
Ctx.diagnose(DiagnosticInfoGenericWithLoc(
"sub-fragment splitting for epilog overflow is not yet implemented",
F, F.getSubprogram(), DS_Note));
// Stripping the SEH pseudos modifies the function, so report a change.
suppressWinCFI(MF);
return true;
}
// Split the funclet into chained sub-fragments so that each fragment's
// UNWIND_INFO stays within the V3 capacity limits: at most 7 epilogs per
// fragment, and each adjacent-epilog gap (plus the gap from the last epilog
// to the fragment tail) small enough that the corresponding signed-16-bit
// EpilogOffset delta fits.
//
// A SEH_SplitChainedAtEndOfBlock inserted at the start of an epilog's
// block makes the AsmPrinter emit the actual .seh_splitchained at the
// *end* of that block, so the epilog becomes the last epilog of the
// earlier fragment, immediately followed by the new chained fragment. A
// long tail after the last epilog is pushed into its own epilog-free
// chained fragment.
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
auto SplitAfter = [&](const EpilogSplitPoint &Epilog) {
MachineBasicBlock *MBB = Epilog.BeginEpilog->getParent();
BuildMI(*MBB, MBB->begin(), Epilog.BeginEpilog->getDebugLoc(),
TII->get(X86::SEH_SplitChainedAtEndOfBlock));
SubFragmentSplits++;
Changed = true;
};
unsigned EpilogsInFragment = 0;
const EpilogSplitPoint *LastEpilog = nullptr;
unsigned LastEpilogIdx = 0;
for (unsigned Idx = 0; Idx < Info.Epilogs.size(); ++Idx) {
const EpilogSplitPoint &Epilog = Info.Epilogs[Idx];
// If adding this epilog would exceed a fragment limit or is too far, end
// the current fragment after the previous epilog and start a new one.
if (EpilogsInFragment > 0) {
bool ExceedsEpilogCount = EpilogsInFragment >= MaxV3Epilogs;
bool ExceedsDistance =
Epilog.ApproxInstrPos - LastEpilog->ApproxInstrPos >=
EpilogDistanceThreshold;
if (ExceedsEpilogCount || ExceedsDistance) {
LLVM_DEBUG({
dbgs() << " splitting after epilog " << LastEpilogIdx
<< " because adding epilog " << Idx << " would exceed the ";
if (ExceedsEpilogCount)
dbgs() << "7-epilog-per-fragment limit\n";
else
dbgs() << "epilog distance threshold (gap from previous epilog "
"at "
<< LastEpilog->ApproxInstrPos << " to epilog at "
<< Epilog.ApproxInstrPos << ")\n";
});
SplitAfter(*LastEpilog);
EpilogsInFragment = 0;
}
}
EpilogsInFragment++;
LastEpilog = &Epilog;
LastEpilogIdx = Idx;
}
// If the last epilog is too far from the funclet end, split after it so the
// trailing code becomes its own epilog-free chained fragment.
if (LastEpilog && Info.EndInstrPos - LastEpilog->ApproxInstrPos >=
EpilogDistanceThreshold) {
LLVM_DEBUG(dbgs() << " splitting after last epilog " << LastEpilogIdx
<< " to isolate the trailing tail (gap from epilog at "
<< LastEpilog->ApproxInstrPos << " to funclet end "
<< Info.EndInstrPos << ")\n");
SplitAfter(*LastEpilog);
}
}
if (Changed)
FunctionsProcessed++;
return Changed;
}