[llvm] [BranchFolder] Prevent nested salvage in empty BB chains with pseudo probes (PR #206092)

Jinjie Huang via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 29 06:04:47 PDT 2026


https://github.com/Jinjie-Huang updated https://github.com/llvm/llvm-project/pull/206092

>From 5daf830970757c9b802edaa86e1bfb5ae4282275 Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Fri, 26 Jun 2026 22:10:21 +0800
Subject: [PATCH 1/3]  Prevent nested salvage in empty BB chains with pseudo
 probes

---
 llvm/lib/CodeGen/BranchFolding.cpp            |  13 +++
 ...ranch-folder-pseudoprobe-debug-salvage.mir | 108 ++++++++++++++++++
 2 files changed, 121 insertions(+)
 create mode 100644 llvm/test/CodeGen/MIR/X86/branch-folder-pseudoprobe-debug-salvage.mir

diff --git a/llvm/lib/CodeGen/BranchFolding.cpp b/llvm/lib/CodeGen/BranchFolding.cpp
index 55f2dd430d6cb..275e600a24d03 100644
--- a/llvm/lib/CodeGen/BranchFolding.cpp
+++ b/llvm/lib/CodeGen/BranchFolding.cpp
@@ -1283,6 +1283,12 @@ static bool IsBranchOnlyBlock(MachineBasicBlock *MBB) {
   return I->isBranch();
 }
 
+static bool HasPseudoProbe(const MachineBasicBlock &MBB) {
+  return llvm::any_of(MBB, [](const MachineInstr &MI) {
+    return MI.isPseudoProbe();
+  });
+}
+
 /// IsBetterFallthrough - Return true if it would be clearly better to
 /// fall-through to MBB1 than to fall through into MBB2.  This has to return
 /// a strict ordering, returning true for both (MBB1,MBB2) and (MBB2,MBB1) will
@@ -1347,6 +1353,13 @@ static void salvageDebugInfoFromEmptyBlock(const TargetInstrInfo *TII,
   for (MachineBasicBlock *SuccBB : MBB.successors())
     if (SuccBB->pred_size() == 1)
       copyDebugInfoToSuccessor(TII, MBB, *SuccBB);
+
+  // Prevent O(N^2) getFirstTerminator() rescans in long chains of empty blocks
+  // (a pattern common with llvm.pseudoprobe) by not salvaging them into
+  // predecessor debug tails.
+  if (HasPseudoProbe(MBB))
+    return;
+
   // If this MBB is the only successor of a predecessor it is legal to copy the
   // DBG_VALUE instructions to the end of the predecessor (just before the
   // terminators, assuming that the terminator isn't affecting the DBG_VALUE).
diff --git a/llvm/test/CodeGen/MIR/X86/branch-folder-pseudoprobe-debug-salvage.mir b/llvm/test/CodeGen/MIR/X86/branch-folder-pseudoprobe-debug-salvage.mir
new file mode 100644
index 0000000000000..ab6163e41f26a
--- /dev/null
+++ b/llvm/test/CodeGen/MIR/X86/branch-folder-pseudoprobe-debug-salvage.mir
@@ -0,0 +1,108 @@
+# RUN: llc -o - %s -mtriple=x86_64-- -run-pass=branch-folder | FileCheck %s
+
+# BranchFolder removes empty blocks and salvages their DBG_VALUEs.  Empty
+# pseudo-probe blocks can appear in very long chains.  Salvaging their debug
+# info into predecessors may repeatedly append DBG_VALUEs before the same
+# predecessor terminator and cause quadratic getFirstTerminator() rescans.
+# Check that pseudo-probe empty blocks are still salvaged to successors, but not
+# to predecessors, while ordinary debug-only empty blocks keep the predecessor
+# salvage behavior.
+
+--- |
+  define void @probe_pred_skip() !dbg !4 {
+    ret void
+  }
+
+  define void @debug_pred_keep() !dbg !15 {
+    ret void
+  }
+
+  !llvm.dbg.cu = !{!0}
+  !llvm.module.flags = !{!3}
+
+  !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: true, emissionKind: FullDebug)
+  !1 = !DIFile(filename: "branch-folder-pseudoprobe-debug-salvage.c", directory: "/tmp")
+  !2 = !{}
+  !3 = !{i32 2, !"Debug Info Version", i32 3}
+  !4 = distinct !DISubprogram(name: "probe_pred_skip", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !8)
+  !5 = !DISubroutineType(types: !6)
+  !6 = !{null}
+  !7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+  !8 = !{!9}
+  !9 = !DILocalVariable(name: "probe_var", scope: !4, file: !1, line: 2, type: !7)
+  !10 = !DILocation(line: 2, column: 3, scope: !4)
+  !15 = distinct !DISubprogram(name: "debug_pred_keep", scope: !1, file: !1, line: 10, type: !5, scopeLine: 10, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !16)
+  !16 = !{!17}
+  !17 = !DILocalVariable(name: "debug_var", scope: !15, file: !1, line: 11, type: !7)
+  !18 = !DILocation(line: 11, column: 3, scope: !15)
+
+...
+---
+name:            probe_pred_skip
+tracksRegLiveness: false
+body:             |
+  ; The empty pseudo-probe block bb.2 is removed.  Its successor has another
+  ; predecessor, so successor salvage is not legal.  Check that the DBG_VALUE is
+  ; not copied into bb.1 either.
+  ; CHECK-LABEL: name: probe_pred_skip
+  ; CHECK: bb.1:
+  ; CHECK: $eax = MOV32ri 42
+  ; CHECK-NOT: DBG_VALUE 1, $noreg
+  ; CHECK: RET 0
+  bb.0:
+    successors: %bb.1, %bb.3
+    TEST32rr $eax, $eax, implicit-def $eflags
+    JCC_1 %bb.3, 4, implicit $eflags
+    JMP_1 %bb.1
+
+  bb.1:
+    successors: %bb.2
+    $eax = MOV32ri 42
+    JMP_1 %bb.2
+
+  bb.2:
+    successors: %bb.4
+    PSEUDO_PROBE 1, 1, 0, 0, debug-location !10
+    DBG_VALUE 1, $noreg, !9, !DIExpression(), debug-location !10
+
+  bb.4:
+    RET 0
+
+  bb.3:
+    successors: %bb.4
+    JMP_1 %bb.4
+
+...
+---
+name:            debug_pred_keep
+tracksRegLiveness: false
+body:             |
+  ; Ordinary debug-only empty blocks retain predecessor salvage.
+  ; CHECK-LABEL: name: debug_pred_keep
+  ; CHECK: bb.1:
+  ; CHECK: $eax = MOV32ri 42
+  ; CHECK-NEXT: DBG_VALUE 2, $noreg
+  ; CHECK: RET 0
+  bb.0:
+    successors: %bb.1, %bb.3
+    TEST32rr $eax, $eax, implicit-def $eflags
+    JCC_1 %bb.3, 4, implicit $eflags
+    JMP_1 %bb.1
+
+  bb.1:
+    successors: %bb.2
+    $eax = MOV32ri 42
+    JMP_1 %bb.2
+
+  bb.2:
+    successors: %bb.4
+    DBG_VALUE 2, $noreg, !17, !DIExpression(), debug-location !18
+
+  bb.4:
+    RET 0
+
+  bb.3:
+    successors: %bb.4
+    JMP_1 %bb.4
+
+...

>From f79cc5513c1ddb38e6ebcd92e52a160176536873 Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Fri, 26 Jun 2026 23:59:06 +0800
Subject: [PATCH 2/3] clang fromat

---
 llvm/lib/CodeGen/BranchFolding.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/CodeGen/BranchFolding.cpp b/llvm/lib/CodeGen/BranchFolding.cpp
index 275e600a24d03..22806763e941d 100644
--- a/llvm/lib/CodeGen/BranchFolding.cpp
+++ b/llvm/lib/CodeGen/BranchFolding.cpp
@@ -1284,9 +1284,8 @@ static bool IsBranchOnlyBlock(MachineBasicBlock *MBB) {
 }
 
 static bool HasPseudoProbe(const MachineBasicBlock &MBB) {
-  return llvm::any_of(MBB, [](const MachineInstr &MI) {
-    return MI.isPseudoProbe();
-  });
+  return llvm::any_of(
+      MBB, [](const MachineInstr &MI) { return MI.isPseudoProbe(); });
 }
 
 /// IsBetterFallthrough - Return true if it would be clearly better to

>From 02ab10bd5226700538d8d380d9fff52218e5a0a8 Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Mon, 29 Jun 2026 21:04:14 +0800
Subject: [PATCH 3/3] move test to CodeGen/X86

---
 .../{MIR => }/X86/branch-folder-pseudoprobe-debug-salvage.mir     | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename llvm/test/CodeGen/{MIR => }/X86/branch-folder-pseudoprobe-debug-salvage.mir (100%)

diff --git a/llvm/test/CodeGen/MIR/X86/branch-folder-pseudoprobe-debug-salvage.mir b/llvm/test/CodeGen/X86/branch-folder-pseudoprobe-debug-salvage.mir
similarity index 100%
rename from llvm/test/CodeGen/MIR/X86/branch-folder-pseudoprobe-debug-salvage.mir
rename to llvm/test/CodeGen/X86/branch-folder-pseudoprobe-debug-salvage.mir



More information about the llvm-commits mailing list