[llvm] [Analysis] Use CycleInfo for BranchProbabilityInfo (PR #210301)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 19 19:55:31 PDT 2026


MaskRay wrote:

Replacing two analyses with one is clearly the right direction. CycleInfo also strictly improves a case LoopInfo cannot see at all: when an irreducible cycle is nested inside a natural loop, LoopInfo claims every block, so `sccNum` is -1 everywhere and SccInfo never fires. Pre-patch BPI is blind there and every branch falls back to uniform; with CycleInfo it produces real estimates.

To get a feel for the size of the change I built a small standalone model of the estimated heuristic (`getInitialEstimatedBlockWeight` -> `propagateEstimatedBlockWeight` -> `calcEstimatedHeuristics`), one copy per loop model, each validated edge-for-edge against its own `opt` binary, and ran ~7000 generated CFGs through both. 1855 of 2919 irreducible CFGs differ (~64%), which matches the description.

One *reducible* CFG differs too, and I think that one deserves a look, since the description implies reducible CFGs are unaffected.

```llvm
declare void @cold_fn() cold
declare i1 @opaque()

define void @f() {
bb0:
  call void @cold_fn()
  %c0 = call i1 @opaque()
  br i1 %c0, label %bb1, label %bb6
bb1:
  call void @cold_fn()
  br label %bb2
bb2:
  %c2 = call i1 @opaque()
  br i1 %c2, label %bb3, label %bb5
bb3:
  call void @cold_fn()
  %c3 = call i1 @opaque()
  br i1 %c3, label %bb4, label %bb7
bb4:
  br label %bb1
bb5:
  br label %bb4
bb6:
  ret void
bb7:
  %c7 = call i1 @opaque()
  br i1 %c7, label %bb3, label %bb4
}
```

```
before:  edge %bb3 -> %bb4 probability is 0x03fffc20 / 0x80000000 = 3.12%
         edge %bb3 -> %bb7 probability is 0x7c0003e0 / 0x80000000 = 96.88%
after:   edge %bb3 -> %bb4 probability is 0x0041edfd / 0x80000000 = 0.20%
         edge %bb3 -> %bb7 probability is 0x7fbe1203 / 0x80000000 = 99.80%
```

Every cycle here is single-entry -- outer loop `{bb1,bb2,bb3,bb4,bb5,bb7}` headed at `bb1`,
inner loop `{bb3,bb7}` headed at `bb3` -- and both analyses agree on the forest. The
difference is the enter-block set:

```cpp
// before, natural-loop path: every predecessor of the header, in-loop latch included
Enters.append(pred_begin(Header), pred_end(Header));

// after: only predecessors from outside the cycle
for (BasicBlock *Entry : CI->getEntries(C))
  for (const auto *Pred : predecessors(Entry))
    if (!CI->contains(C, Pred))
      Enters.push_back(const_cast<BasicBlock *>(Pred));
```

so the latch `bb7` is no longer queued, never gets an estimated weight, and the edge falls
back to `DEFAULT_WEIGHT` instead of the COLD-derived one -- a 15x move on the exit edge.

For what it is worth, the old code was already inconsistent with itself here:
`getSccEnterBlocks` filtered to `getSCCNum(Pred) != SccNum`, i.e. outside predecessors only,
while the natural-loop path did not. So the new form makes the two halves agree, which is an
improvement. Two things I would still like to pin down:

1. Is narrowing the enter-block set intended? It is independent of CycleInfo and could land
   separately against the current code with its own test, which would keep this patch's delta
   purely about the loop forest. Note the SCC half moved in the other direction at the same
   time -- it used to push the entered block and now pushes the predecessor.

2. Could the description mention that reducible CFGs can change as well? Right now it reads as
   irreducible-only.

https://github.com/llvm/llvm-project/pull/210301


More information about the llvm-commits mailing list