[llvm] [BranchFolder] Prevent nested salvage in empty BB chains with pseudo probes (PR #206092)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 29 06:05:29 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-x86
Author: Jinjie Huang (Jinjie-Huang)
<details>
<summary>Changes</summary>
Before removing empty blocks, BranchFolder attempts to salvage DBG_VALUEs. When copying this debug info to a predecessor, it needs to insert the DBG just before the predecessor's first terminator. However, in long chains of empty blocks, this process becomes prohibitively expensive: repeated salvaging appends an ever-growing number of DBG_VALUEs before the same terminator, forcing each call to getFirstTerminator() to rescan the massively accumulated debug tail. This results in severe compile-time degradation like this:
```
bb.0.Pred:
NORMAL_INST
DBG_1
DBG_2
...
DBG_x <- insertion point (has to iterate backwards until it hits the NORMAL_INST, then skip forward over the DBGs to locate it)
JMP %bb.1 <- first terminator
```
Since the time complexity of [`getFirstTerminator()`](https://github.com/llvm/llvm-project/blob/llvmorg-23-init/llvm/lib/CodeGen/MachineBasicBlock.cpp#L242) is O(N), attempting a best-effort salvage of debug info here seems unnecessary (especially PSEUDO_PROBEs themselves are not salvaged anyway). Therefore, this change identifies empty BBs containing pseudo probes and abandons copying debug info to predecessor blocks(inserting into the successor BB is relatively cheap because it is always placed at the very beginning). BTW, caching FirstTerminator for the MBB to solve this seems to have too high of an overhead.
---
Full diff: https://github.com/llvm/llvm-project/pull/206092.diff
2 Files Affected:
- (modified) llvm/lib/CodeGen/BranchFolding.cpp (+12)
- (added) llvm/test/CodeGen/X86/branch-folder-pseudoprobe-debug-salvage.mir (+108)
``````````diff
diff --git a/llvm/lib/CodeGen/BranchFolding.cpp b/llvm/lib/CodeGen/BranchFolding.cpp
index 55f2dd430d6cb..22806763e941d 100644
--- a/llvm/lib/CodeGen/BranchFolding.cpp
+++ b/llvm/lib/CodeGen/BranchFolding.cpp
@@ -1283,6 +1283,11 @@ 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 +1352,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/X86/branch-folder-pseudoprobe-debug-salvage.mir b/llvm/test/CodeGen/X86/branch-folder-pseudoprobe-debug-salvage.mir
new file mode 100644
index 0000000000000..ab6163e41f26a
--- /dev/null
+++ b/llvm/test/CodeGen/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
+
+...
``````````
</details>
https://github.com/llvm/llvm-project/pull/206092
More information about the llvm-commits
mailing list