[llvm] [UnwinderV3] Include padding in spliting calculations (PR #208458)

Evgenii Kudriashov via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 14 03:12:21 PDT 2026


https://github.com/e-kud updated https://github.com/llvm/llvm-project/pull/208458

>From 30db25272624b3fd3cdea5e2ca5700f23623e1a2 Mon Sep 17 00:00:00 2001
From: "Pirog, Mikolaj Maciej" <mikolaj.maciej.pirog at intel.com>
Date: Mon, 6 Jul 2026 11:50:32 +0200
Subject: [PATCH 1/3] Include padding in epliog spliting calculations

---
 llvm/lib/Target/X86/X86WinEHUnwindV3.cpp      | 92 ++++++++++---------
 .../X86/win64-eh-unwindv3-split-large.ll      |  4 +-
 2 files changed, 51 insertions(+), 45 deletions(-)

diff --git a/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp b/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp
index 901510763936e..7e5bc46ece006 100644
--- a/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp
+++ b/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp
@@ -47,22 +47,20 @@ STATISTIC(SubFragmentSplits,
 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));
+static constexpr unsigned EpilogDistanceThreshold = 32767;
+
+/// Approximate byte distance between an epilog and its fragment tail beyond
+/// which the funclet is split into a new chained sub-fragment. The V3
+/// EpilogOffset field is a signed 16-bit byte offset measured from the
+/// fragment tail, so each fragment must span less than 32 KiB of code. The
+/// exact byte offsets aren't known until MC layout, so (like the V2 pass) an
+/// approximate byte count is used as a proxy — instructions are charged
+/// ApproxBytesPerInstr each and alignment padding is added.
+static cl::opt<unsigned> ApproxBytesPerInstr(
+    "x86-wineh-unwindv3-instr-avg-size", cl::Hidden,
+    cl::desc("Average size of an instruction. This value is used in determining "
+             "split points for chained unwinder info"),
+    cl::init(6));
 
 /// After reporting a recoverable error for `MF`, erase all SEH pseudo-
 /// instructions and clear the WinCFI flag so the AsmPrinter doesn't try to
@@ -98,20 +96,20 @@ static void suppressWinCFI(MachineFunction &MF) {
 
 namespace {
 
-/// A V3 epilog and the approximate instruction position where it begins, used
+/// A V3 epilog and the approximate byte position where it begins, used
 /// as a candidate sub-fragment split point.
 struct EpilogSplitPoint {
   MachineInstr *BeginEpilog;
-  unsigned ApproxInstrPos;
+  unsigned ApproxBytePos;
 };
 
 /// Per-funclet analysis results.
 struct FuncletInfo {
   unsigned PrologOpCount = 0;
   unsigned MaxEpilogOpCount = 0;
-  /// Approximate instruction position at the end of the funclet, used as the
+  /// Approximate byte position at the end of the funclet, used as the
   /// initial fragment tail reference for size-based splitting.
-  unsigned EndInstrPos = 0;
+  unsigned EndBytePos = 0;
   /// SEH_BeginEpilogue instructions (with approximate positions), used as
   /// candidate insertion points for sub-fragment splitting.
   SmallVector<EpilogSplitPoint, 8> Epilogs;
@@ -132,12 +130,12 @@ class X86WinEHUnwindV3 : public MachineFunctionPass {
 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.
+  /// entry or the end of the function. ApproxBytePos is a running estimate of
+  /// the byte position 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);
+                                    unsigned &ApproxBytePos);
 };
 
 } // end anonymous namespace
@@ -154,7 +152,7 @@ FunctionPass *llvm::createX86WinEHUnwindV3Pass() {
 
 FuncletInfo X86WinEHUnwindV3::analyzeFunclet(MachineFunction &MF,
                                              MachineFunction::iterator &Iter,
-                                             unsigned &ApproxInstrPos) {
+                                             unsigned &ApproxBytePos) {
   FuncletInfo Info;
   bool InEpilog = false;
   bool SeenProlog = false;
@@ -168,12 +166,20 @@ FuncletInfo X86WinEHUnwindV3::analyzeFunclet(MachineFunction &MF,
     if (MBB.isEHFuncletEntry() && SeenProlog)
       break;
 
+    // Account for worst-case scenario of padding inserted to align this block.
+    Align A = MBB.getAlignment();
+    unsigned MaxPadding = A.value() - 1;
+    if (unsigned MaxBytes = MBB.getMaxBytesForAlignment())
+      MaxPadding = std::min(MaxPadding, MaxBytes);
+    ApproxBytePos += MaxPadding;
+
     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.
+      // Approximate the emitted byte size, mirroring the V2 pass. This
+      // estimates how far each epilog sits from its fragment tail; the exact
+      // byte offsets aren't available until MC layout, so each real
+      // instruction is charged ApproxBytesPerInstr bytes.
       if (!MI.isPseudo() && !MI.isMetaInstruction())
-        ApproxInstrPos++;
+        ApproxBytePos += ApproxBytesPerInstr;
 
       switch (MI.getOpcode()) {
       case X86::SEH_PushReg:
@@ -195,9 +201,9 @@ FuncletInfo X86WinEHUnwindV3::analyzeFunclet(MachineFunction &MF,
         InEpilog = true;
         CurrentEpilogOpCount = 0;
         LLVM_DEBUG(dbgs() << "  epilog " << Info.Epilogs.size()
-                          << " begins at approx instruction position "
-                          << ApproxInstrPos << "\n");
-        Info.Epilogs.push_back({&MI, ApproxInstrPos});
+                          << " begins at approx byte position "
+                          << ApproxBytePos << "\n");
+        Info.Epilogs.push_back({&MI, ApproxBytePos});
         break;
       case X86::SEH_EndEpilogue:
         InEpilog = false;
@@ -210,10 +216,10 @@ FuncletInfo X86WinEHUnwindV3::analyzeFunclet(MachineFunction &MF,
     }
   }
 
-  Info.EndInstrPos = ApproxInstrPos;
+  Info.EndBytePos = ApproxBytePos;
   LLVM_DEBUG(dbgs() << "  funclet has " << Info.Epilogs.size()
-                    << " epilog(s); ends at approx instruction position "
-                    << ApproxInstrPos << "\n");
+                    << " epilog(s); ends at approx byte position "
+                    << ApproxBytePos << "\n");
   return Info;
 }
 
@@ -244,7 +250,7 @@ bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) {
   }
 
   bool Changed = false;
-  unsigned ApproxInstrPos = 0;
+  unsigned ApproxBytePos = 0;
   MachineFunction::iterator Iter = MF.begin();
 
   LLVM_DEBUG(dbgs() << "X86WinEHUnwindV3: processing " << MF.getName() << "\n");
@@ -252,7 +258,7 @@ bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) {
   // 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);
+    FuncletInfo Info = analyzeFunclet(MF, Iter, ApproxBytePos);
 
     if (Info.PrologOpCount > MaxV3PrologOps) {
       Ctx.diagnose(DiagnosticInfoResourceLimit(
@@ -309,7 +315,7 @@ bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) {
       if (EpilogsInFragment > 0) {
         bool ExceedsEpilogCount = EpilogsInFragment >= MaxV3Epilogs;
         bool ExceedsDistance =
-            Epilog.ApproxInstrPos - LastEpilog->ApproxInstrPos >=
+            Epilog.ApproxBytePos - LastEpilog->ApproxBytePos >=
             EpilogDistanceThreshold;
         if (ExceedsEpilogCount || ExceedsDistance) {
           LLVM_DEBUG({
@@ -320,8 +326,8 @@ bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) {
             else
               dbgs() << "epilog distance threshold (gap from previous epilog "
                         "at "
-                     << LastEpilog->ApproxInstrPos << " to epilog at "
-                     << Epilog.ApproxInstrPos << ")\n";
+                     << LastEpilog->ApproxBytePos << " to epilog at "
+                     << Epilog.ApproxBytePos << ")\n";
           });
           SplitAfter(*LastEpilog);
           EpilogsInFragment = 0;
@@ -334,12 +340,12 @@ bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) {
 
     // 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 >=
+    if (LastEpilog && Info.EndBytePos - LastEpilog->ApproxBytePos >=
                           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");
+                        << LastEpilog->ApproxBytePos << " to funclet end "
+                        << Info.EndBytePos << ")\n");
       SplitAfter(*LastEpilog);
     }
   }
diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv3-split-large.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv3-split-large.ll
index ab8762fc72192..50b36fb9edc1d 100644
--- a/llvm/test/CodeGen/X86/win64-eh-unwindv3-split-large.ll
+++ b/llvm/test/CodeGen/X86/win64-eh-unwindv3-split-large.ll
@@ -1,7 +1,7 @@
 ; RUN: llc -mtriple=x86_64-unknown-windows-msvc \
-; RUN:   -x86-wineh-unwindv3-epilog-distance-threshold=1 -o - %s | FileCheck %s
+; RUN:   -x86-wineh-unwindv3-instr-avg-size=100000 -o - %s | FileCheck %s
 ; RUN: llc -mtriple=x86_64-unknown-windows-msvc \
-; RUN:   -x86-wineh-unwindv3-epilog-distance-threshold=1 -filetype=obj %s -o - \
+; RUN:   -x86-wineh-unwindv3-instr-avg-size=100000 -filetype=obj %s -o - \
 ; RUN:   | llvm-readobj --unwind - | FileCheck %s --check-prefix=OBJ
 
 ; Test V3 *size-based* sub-fragment splitting (the "Unwind v2 style" heuristic).

>From 9e9c123b178b7513d9ba0118fe2d972668e8d5a3 Mon Sep 17 00:00:00 2001
From: "Pirog, Mikolaj Maciej" <mikolaj.maciej.pirog at intel.com>
Date: Thu, 9 Jul 2026 15:22:23 +0200
Subject: [PATCH 2/3] Bump instr size

---
 llvm/lib/Target/X86/X86WinEHUnwindV3.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp b/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp
index 7e5bc46ece006..1baa88d56d8a5 100644
--- a/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp
+++ b/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp
@@ -60,7 +60,7 @@ static cl::opt<unsigned> ApproxBytesPerInstr(
     "x86-wineh-unwindv3-instr-avg-size", cl::Hidden,
     cl::desc("Average size of an instruction. This value is used in determining "
              "split points for chained unwinder info"),
-    cl::init(6));
+    cl::init(7));
 
 /// After reporting a recoverable error for `MF`, erase all SEH pseudo-
 /// instructions and clear the WinCFI flag so the AsmPrinter doesn't try to

>From 904166db1d1e32c91a7ce290f39faa7cd5c63cf4 Mon Sep 17 00:00:00 2001
From: "Pirog, Mikolaj Maciej" <mikolaj.maciej.pirog at intel.com>
Date: Thu, 9 Jul 2026 15:36:07 +0200
Subject: [PATCH 3/3] Formatting

---
 llvm/lib/Target/X86/X86WinEHUnwindV3.cpp | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp b/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp
index 1baa88d56d8a5..162633e2fddf3 100644
--- a/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp
+++ b/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp
@@ -58,8 +58,9 @@ static constexpr unsigned EpilogDistanceThreshold = 32767;
 /// ApproxBytesPerInstr each and alignment padding is added.
 static cl::opt<unsigned> ApproxBytesPerInstr(
     "x86-wineh-unwindv3-instr-avg-size", cl::Hidden,
-    cl::desc("Average size of an instruction. This value is used in determining "
-             "split points for chained unwinder info"),
+    cl::desc(
+        "Average size of an instruction. This value is used in determining "
+        "split points for chained unwinder info"),
     cl::init(7));
 
 /// After reporting a recoverable error for `MF`, erase all SEH pseudo-
@@ -201,8 +202,8 @@ FuncletInfo X86WinEHUnwindV3::analyzeFunclet(MachineFunction &MF,
         InEpilog = true;
         CurrentEpilogOpCount = 0;
         LLVM_DEBUG(dbgs() << "  epilog " << Info.Epilogs.size()
-                          << " begins at approx byte position "
-                          << ApproxBytePos << "\n");
+                          << " begins at approx byte position " << ApproxBytePos
+                          << "\n");
         Info.Epilogs.push_back({&MI, ApproxBytePos});
         break;
       case X86::SEH_EndEpilogue:



More information about the llvm-commits mailing list