[clang] [llvm] [win][x64] Add support for Windows x64 unwind v3 (PR #200249)
Mészáros Gergely via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 2 04:33:06 PDT 2026
================
@@ -0,0 +1,208 @@
+//===-- 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. Unlike the V2 pass, V3 does not need to validate
+/// epilog structure (V3 can encode any prolog/epilog pattern). This pass
+/// 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/Module.h"
+#include "llvm/Support/ErrorHandling.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;
+
+namespace {
+
+/// Per-funclet analysis results.
+struct FuncletInfo {
+ unsigned PrologOpCount = 0;
+ unsigned EpilogCount = 0;
+ unsigned MaxEpilogOpCount = 0;
+ /// SEH_BeginEpilogue instructions, used as insertion points for splitting.
+ SmallVector<MachineInstr *, 8> EpilogBegins;
+};
+
+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.
+ static FuncletInfo analyzeFunclet(MachineFunction &MF,
+ MachineFunction::iterator &Iter);
+};
+
+} // 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) {
+ 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) {
+ 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;
+ Info.EpilogCount++;
+ Info.EpilogBegins.push_back(&MI);
+ break;
+ case X86::SEH_EndEpilogue:
+ InEpilog = false;
+ Info.MaxEpilogOpCount =
+ std::max(Info.MaxEpilogOpCount, CurrentEpilogOpCount);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return Info;
+}
+
+bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) {
+ WinX64EHUnwindMode Mode =
+ MF.getFunction().getParent()->getWinX64EHUnwindMode();
+
+ // 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 (!MF.getFunction().needsUnwindTableEntry())
+ return false;
+ const auto &STI = MF.getSubtarget<X86Subtarget>();
+ if (STI.hasEGPR())
+ reportFatalUsageError(
+ "EGPR (R16-R31) requires V3 unwind info on Windows x64.");
+ return false;
+ }
+
+ bool Changed = false;
+ MachineFunction::iterator Iter = MF.begin();
+
+ // 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);
+
+ if (Info.PrologOpCount > MaxV3PrologOps) {
+ reportFatalUsageError("function '" + MF.getName() +
+ "' requires more than 31 V3 prolog operations; "
+ "sub-fragment splitting for prolog overflow is "
+ "not yet implemented");
+ }
+
+ if (Info.MaxEpilogOpCount > MaxV3EpilogOps) {
+ reportFatalUsageError("function '" + MF.getName() +
+ "' has an epilog with more than 31 V3 operations; "
+ "sub-fragment splitting for epilog op overflow is "
+ "not yet implemented");
----------------
Maetveis wrote:
And this could become
```suggestion
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));
return Changed;
```
https://github.com/llvm/llvm-project/pull/200249
More information about the cfe-commits
mailing list