1//===-- X86WinEHUnwindV3.cpp - Win x64 Unwind v3 ----------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// Implements the capacity-checking and sub-fragment splitting pass for
10/// Unwind v3 information. V3 can encode any prolog/epilog pattern, so this
11/// pass does not validate epilog structure; it only needs to:
12/// 1. Count prolog/epilog operations and epilogs.
13/// 2. Check V3 capacity limits (<=31 prolog/epilog ops, <=7 epilogs).
14/// 3. Insert sub-fragment split points if limits are exceeded.
15///
16/// The unwind version is set module-wide, not per-function.
17///
18/// See https://learn.microsoft.com/en-us/cpp/build/x64-unwind-information-v3
19///
20//===----------------------------------------------------------------------===//
21
22#include "MCTargetDesc/X86BaseInfo.h"
23#include "X86.h"
24#include "X86Subtarget.h"
25#include "llvm/ADT/Statistic.h"
26#include "llvm/CodeGen/MachineBasicBlock.h"
27#include "llvm/CodeGen/MachineFunctionPass.h"
28#include "llvm/CodeGen/MachineInstrBuilder.h"
29#include "llvm/CodeGen/TargetInstrInfo.h"
30#include "llvm/CodeGen/TargetSubtargetInfo.h"
31#include "llvm/IR/DiagnosticInfo.h"
32#include "llvm/IR/LLVMContext.h"
33#include "llvm/IR/Module.h"
34#include "llvm/Support/CommandLine.h"
35#include "llvm/Support/Debug.h"
36
37using namespace llvm;
38
39#define DEBUG_TYPE "x86-wineh-unwindv3"
40
41STATISTIC(FunctionsProcessed,
42 "Number of functions processed by Unwind v3 pass");
43STATISTIC(SubFragmentSplits,
44 "Number of sub-fragment splits inserted for Unwind v3");
45
46/// V3 limits from the format specification.
47static constexpr unsigned MaxV3PrologOps = 31;
48static constexpr unsigned MaxV3Epilogs = 7;
49static constexpr unsigned MaxV3EpilogOps = 31;
50
51/// Maximum approximate instruction distance allowed between two adjacent
52/// epilogs, and between the last epilog and the funclet end, before the
53/// funclet is split into a new chained sub-fragment. V3 encodes each epilog's
54/// position as a signed 16-bit EpilogOffset: a delta from the previous epilog,
55/// with the tail-closest epilog encoded relative to the fragment end. The exact
56/// byte offsets aren't known until MC layout, so the approximate instruction
57/// count is used as a proxy, with margin for the average emitted instruction
58/// size.
59static cl::opt<unsigned> EpilogDistanceThreshold(
60 "x86-wineh-unwindv3-epilog-distance-threshold", cl::Hidden,
61 cl::desc(
62 "Maximum approximate instruction distance between adjacent epilogs "
63 "(or between the last epilog and the funclet end) before "
64 "splitting into a new chained unwind info for Unwind v3."),
65 cl::init(Val: 3000));
66
67/// After reporting a recoverable error for `MF`, erase all SEH pseudo-
68/// instructions and clear the WinCFI flag so the AsmPrinter doesn't try to
69/// emit (potentially malformed) unwind information. The LLVMContext
70/// diagnostic recorded by the caller will prevent the object file from
71/// actually being written.
72static void suppressWinCFI(MachineFunction &MF) {
73 for (MachineBasicBlock &MBB : MF) {
74 for (MachineInstr &MI : llvm::make_early_inc_range(Range&: MBB)) {
75 switch (MI.getOpcode()) {
76 case X86::SEH_PushReg:
77 case X86::SEH_Push2Regs:
78 case X86::SEH_SaveReg:
79 case X86::SEH_SaveXMM:
80 case X86::SEH_StackAlloc:
81 case X86::SEH_StackAlign:
82 case X86::SEH_SetFrame:
83 case X86::SEH_PushFrame:
84 case X86::SEH_EndPrologue:
85 case X86::SEH_BeginEpilogue:
86 case X86::SEH_EndEpilogue:
87 case X86::SEH_SplitChained:
88 case X86::SEH_SplitChainedAtEndOfBlock:
89 MI.eraseFromParent();
90 break;
91 default:
92 break;
93 }
94 }
95 }
96 MF.setHasWinCFI(false);
97}
98
99namespace {
100
101/// A V3 epilog and the approximate instruction position where it begins, used
102/// as a candidate sub-fragment split point.
103struct EpilogSplitPoint {
104 MachineInstr *BeginEpilog;
105 unsigned ApproxInstrPos;
106};
107
108/// Per-funclet analysis results.
109struct FuncletInfo {
110 unsigned PrologOpCount = 0;
111 unsigned MaxEpilogOpCount = 0;
112 /// Approximate instruction position at the end of the funclet, used as the
113 /// initial fragment tail reference for size-based splitting.
114 unsigned EndInstrPos = 0;
115 /// SEH_BeginEpilogue instructions (with approximate positions), used as
116 /// candidate insertion points for sub-fragment splitting.
117 SmallVector<EpilogSplitPoint, 8> Epilogs;
118};
119
120class X86WinEHUnwindV3 : public MachineFunctionPass {
121public:
122 static char ID;
123
124 X86WinEHUnwindV3() : MachineFunctionPass(ID) {
125 initializeX86WinEHUnwindV3Pass(*PassRegistry::getPassRegistry());
126 }
127
128 StringRef getPassName() const override { return "WinEH Unwind V3"; }
129
130 bool runOnMachineFunction(MachineFunction &MF) override;
131
132private:
133 /// Analyze one funclet (or the main function body) starting at Iter.
134 /// Advances Iter past the analyzed region, stopping at the next funclet
135 /// entry or the end of the function. ApproxInstrPos is a running count of
136 /// emitted instructions across the whole function, used to estimate the
137 /// byte distance between epilogs and their fragment tail.
138 static FuncletInfo analyzeFunclet(MachineFunction &MF,
139 MachineFunction::iterator &Iter,
140 unsigned &ApproxInstrPos);
141};
142
143} // end anonymous namespace
144
145char X86WinEHUnwindV3::ID = 0;
146
147INITIALIZE_PASS(X86WinEHUnwindV3, "x86-wineh-unwindv3",
148 "Capacity check and sub-fragment splitting for Win64 Unwind v3",
149 false, false)
150
151FunctionPass *llvm::createX86WinEHUnwindV3Pass() {
152 return new X86WinEHUnwindV3();
153}
154
155FuncletInfo X86WinEHUnwindV3::analyzeFunclet(MachineFunction &MF,
156 MachineFunction::iterator &Iter,
157 unsigned &ApproxInstrPos) {
158 FuncletInfo Info;
159 bool InEpilog = false;
160 bool SeenProlog = false;
161 unsigned CurrentEpilogOpCount = 0;
162
163 for (; Iter != MF.end(); ++Iter) {
164 MachineBasicBlock &MBB = *Iter;
165
166 // If we've already been processing a funclet's prolog/body and encounter
167 // another funclet entry, stop - that funclet gets its own analysis.
168 if (MBB.isEHFuncletEntry() && SeenProlog)
169 break;
170
171 for (MachineInstr &MI : MBB) {
172 // Approximate the number of emitted instructions. This estimates how
173 // far each epilog sits from its fragment tail; the exact byte offsets
174 // aren't available until MC layout.
175 if (!MI.isPseudo() && !MI.isMetaInstruction())
176 ApproxInstrPos++;
177
178 switch (MI.getOpcode()) {
179 case X86::SEH_PushReg:
180 case X86::SEH_Push2Regs:
181 case X86::SEH_StackAlloc:
182 case X86::SEH_SetFrame:
183 case X86::SEH_SaveReg:
184 case X86::SEH_SaveXMM:
185 case X86::SEH_PushFrame:
186 if (InEpilog)
187 CurrentEpilogOpCount++;
188 else
189 Info.PrologOpCount++;
190 break;
191 case X86::SEH_EndPrologue:
192 SeenProlog = true;
193 break;
194 case X86::SEH_BeginEpilogue:
195 InEpilog = true;
196 CurrentEpilogOpCount = 0;
197 LLVM_DEBUG(dbgs() << " epilog " << Info.Epilogs.size()
198 << " begins at approx instruction position "
199 << ApproxInstrPos << "\n");
200 Info.Epilogs.push_back(Elt: {.BeginEpilog: &MI, .ApproxInstrPos: ApproxInstrPos});
201 break;
202 case X86::SEH_EndEpilogue:
203 InEpilog = false;
204 Info.MaxEpilogOpCount =
205 std::max(a: Info.MaxEpilogOpCount, b: CurrentEpilogOpCount);
206 break;
207 default:
208 break;
209 }
210 }
211 }
212
213 Info.EndInstrPos = ApproxInstrPos;
214 LLVM_DEBUG(dbgs() << " funclet has " << Info.Epilogs.size()
215 << " epilog(s); ends at approx instruction position "
216 << ApproxInstrPos << "\n");
217 return Info;
218}
219
220bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) {
221 WinX64EHUnwindMode Mode =
222 MF.getFunction().getParent()->getWinX64EHUnwindMode();
223
224 Function &F = MF.getFunction();
225 LLVMContext &Ctx = F.getContext();
226
227 // EGPR (R16-R31) requires V3 unwind info because V1/V2 cannot encode
228 // registers beyond R15. Only enforce this for functions that actually
229 // emit SEH unwind info — `nounwind` functions and targets that don't
230 // require unwind tables (e.g. cross-compilation host defaults) can use
231 // EGPR with any unwind mode since no SEH metadata is generated.
232 if (Mode != WinX64EHUnwindMode::V3) {
233 if (!F.needsUnwindTableEntry())
234 return false;
235 const auto &STI = MF.getSubtarget<X86Subtarget>();
236 if (STI.hasEGPR()) {
237 Ctx.diagnose(DI: DiagnosticInfoUnsupported(
238 F, "EGPR (R16-R31) requires V3 unwind info on Windows x64"));
239 // Stripping the SEH pseudos modifies the function, so report a change.
240 suppressWinCFI(MF);
241 return true;
242 }
243 return false;
244 }
245
246 bool Changed = false;
247 unsigned ApproxInstrPos = 0;
248 MachineFunction::iterator Iter = MF.begin();
249
250 LLVM_DEBUG(dbgs() << "X86WinEHUnwindV3: processing " << MF.getName() << "\n");
251
252 // Process each funclet (and the main function body) independently.
253 // Each funclet gets its own UNWIND_INFO, so V3 limits apply per funclet.
254 while (Iter != MF.end()) {
255 FuncletInfo Info = analyzeFunclet(MF, Iter, ApproxInstrPos);
256
257 if (Info.PrologOpCount > MaxV3PrologOps) {
258 Ctx.diagnose(DI: DiagnosticInfoResourceLimit(
259 F, "number of unwind v3 prolog operations required",
260 Info.PrologOpCount, MaxV3PrologOps, DS_Error, DK_ResourceLimit));
261 Ctx.diagnose(DI: DiagnosticInfoGenericWithLoc(
262 "sub-fragment splitting for prolog overflow is not yet implemented",
263 F, F.getSubprogram(), DS_Note));
264 // Stripping the SEH pseudos modifies the function, so report a change.
265 suppressWinCFI(MF);
266 return true;
267 }
268
269 if (Info.MaxEpilogOpCount > MaxV3EpilogOps) {
270 Ctx.diagnose(DI: DiagnosticInfoResourceLimit(
271 F, "number of unwind v3 epilog operations required",
272 Info.MaxEpilogOpCount, MaxV3EpilogOps, DS_Error, DK_ResourceLimit));
273 Ctx.diagnose(DI: DiagnosticInfoGenericWithLoc(
274 "sub-fragment splitting for epilog overflow is not yet implemented",
275 F, F.getSubprogram(), DS_Note));
276 // Stripping the SEH pseudos modifies the function, so report a change.
277 suppressWinCFI(MF);
278 return true;
279 }
280
281 // Split the funclet into chained sub-fragments so that each fragment's
282 // UNWIND_INFO stays within the V3 capacity limits: at most 7 epilogs per
283 // fragment, and each adjacent-epilog gap (plus the gap from the last epilog
284 // to the fragment tail) small enough that the corresponding signed-16-bit
285 // EpilogOffset delta fits.
286 //
287 // A SEH_SplitChainedAtEndOfBlock inserted at the start of an epilog's
288 // block makes the AsmPrinter emit the actual .seh_splitchained at the
289 // *end* of that block, so the epilog becomes the last epilog of the
290 // earlier fragment, immediately followed by the new chained fragment. A
291 // long tail after the last epilog is pushed into its own epilog-free
292 // chained fragment.
293 const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
294 auto SplitAfter = [&](const EpilogSplitPoint &Epilog) {
295 MachineBasicBlock *MBB = Epilog.BeginEpilog->getParent();
296 BuildMI(BB&: *MBB, I: MBB->begin(), MIMD: Epilog.BeginEpilog->getDebugLoc(),
297 MCID: TII->get(Opcode: X86::SEH_SplitChainedAtEndOfBlock));
298 SubFragmentSplits++;
299 Changed = true;
300 };
301
302 unsigned EpilogsInFragment = 0;
303 const EpilogSplitPoint *LastEpilog = nullptr;
304 [[maybe_unused]] unsigned LastEpilogIdx = 0;
305 for (unsigned Idx = 0; Idx < Info.Epilogs.size(); ++Idx) {
306 const EpilogSplitPoint &Epilog = Info.Epilogs[Idx];
307 // If adding this epilog would exceed a fragment limit or is too far, end
308 // the current fragment after the previous epilog and start a new one.
309 if (EpilogsInFragment > 0) {
310 bool ExceedsEpilogCount = EpilogsInFragment >= MaxV3Epilogs;
311 bool ExceedsDistance =
312 Epilog.ApproxInstrPos - LastEpilog->ApproxInstrPos >=
313 EpilogDistanceThreshold;
314 if (ExceedsEpilogCount || ExceedsDistance) {
315 LLVM_DEBUG({
316 dbgs() << " splitting after epilog " << LastEpilogIdx
317 << " because adding epilog " << Idx << " would exceed the ";
318 if (ExceedsEpilogCount)
319 dbgs() << "7-epilog-per-fragment limit\n";
320 else
321 dbgs() << "epilog distance threshold (gap from previous epilog "
322 "at "
323 << LastEpilog->ApproxInstrPos << " to epilog at "
324 << Epilog.ApproxInstrPos << ")\n";
325 });
326 SplitAfter(*LastEpilog);
327 EpilogsInFragment = 0;
328 }
329 }
330 EpilogsInFragment++;
331 LastEpilog = &Epilog;
332 LastEpilogIdx = Idx;
333 }
334
335 // If the last epilog is too far from the funclet end, split after it so the
336 // trailing code becomes its own epilog-free chained fragment.
337 if (LastEpilog && Info.EndInstrPos - LastEpilog->ApproxInstrPos >=
338 EpilogDistanceThreshold) {
339 LLVM_DEBUG(dbgs() << " splitting after last epilog " << LastEpilogIdx
340 << " to isolate the trailing tail (gap from epilog at "
341 << LastEpilog->ApproxInstrPos << " to funclet end "
342 << Info.EndInstrPos << ")\n");
343 SplitAfter(*LastEpilog);
344 }
345 }
346
347 if (Changed)
348 FunctionsProcessed++;
349
350 return Changed;
351}
352