Hi, this patch is a new optimization with using `CCMP`.
Previous, ARM64 and x64 only fuse logical-expression compare chains in the data flow, in this patch, a CFG branch cascade is added, which recognize `if (x==C1) goto T1; else if (x==C2) goto T2; else goto F;`, where the two true edges converge (identical target, or a merge block whose phi inputs match), and fuse it into a single ccmp chain, saving a branch.
PTAL, thanks!
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
📍 Job mac-m4-pro-perf/jetstream3.crossbench complete.
See results at: https://pinpoint-dot-chromeperf.appspot.com/job/11a81f91a90000
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
📍 Job win-11-perf/jetstream3.crossbench complete.
See results at: https://pinpoint-dot-chromeperf.appspot.com/job/11e40f02a90000
Looks good, just some nits.
Please add some tests that specifically create the pattern in question, to make sure we have sufficient coverage for exercising this logic. Include some cases that are "almost but not quite" eligible, to make sure we get the decision-making right and don't accidentally change behavior.
Recognize the control-flow patternnit: no reason to have indentation here (and in lines 15-17).
// * Case 1 (visiting Block B): validate, record Block A as a head, turn BlockWhat are blocks A and B?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Also, a high-level question: can you explain why a fused ccmp sequence is expected to be faster? I would have guessed that the traditional sequence makes life easier for the branch predictor.
Also, a high-level question: can you explain why a fused ccmp sequence is expected to be faster? I would have guessed that the traditional sequence makes life easier for the branch predictor.
Your intuition isn't wrong — it's just conditional. The honest answer is that the fused form isn't provably faster; it's a favorable expected-value bet. Two points:
1. You're right that in some regimes the traditional sequence is friendlier to the predictor.
First, note what the transform actually does — it's not "add a ccmp on top of a good first branch." It's all-or-nothing:
traditional: cmp x,C1 ; je T ; cmp x,C2 ; je T → two branches
fused: cmp x,C1 ; ccmp x,C2,ne ; je T → one branch on (x==C1)||(x==C2)
So there's no "first branch" left to help the predictor — both branches are merged into a single one on the OR. When both conditions are highly predictable, the misprediction cost is ≈ 0 for both forms, so the win collapses to
second-order effects — and there the traditional form has real advantages: the early-out je T lets you skip the second compare on the common path, and it keeps the branch hints (the fused path discards them). In that regime two
branches can genuinely tie or win.
2. How to actually account for the ccmp bet.
The net effect is: spend one always-executed extra compare (ccmp, ~1 cycle) to remove one branch. Removing that branch saves two things:
- its misprediction risk (a mispredict is ~15–20 cycles), and
- its front-end footprint — one fewer BTB entry, one fewer taken-branch fetch bubble, tighter code (better uop/I-cache).
So the trade is:
(expected mispredict cost + front-end cost of the removed branch) vs. one ~1-cycle compare
Because a mispredict is ~15–20× costlier than the extra compare, the bet pays off as long as the removed branch is mispredicted even occasionally (a few percent), or front-end pressure matters (e.g., a hot loop header). It only loses
when that branch is almost never mispredicted and the extra compare sits on the critical path and the front end isn't the bottleneck.
Bottom line: ccmp isn't faster because branches are bad for the predictor; it's faster on average because it converts a possibly-mispredicted, front-end-costly control decision into deterministic data flow that never enters the
predictor, at the price of one cheap compare. That's positive expected value in typical code but not a guarantee.
nit: no reason to have indentation here (and in lines 15-17).
Marked as resolved.
// * Case 1 (visiting Block B): validate, record Block A as a head, turn BlockWhat are blocks A and B?
Updated the notes.
Block A is the head block. Its branch tests `x==C1` with if_true=T1 and if_false=Block B.
Block B is Block A's if_false successor. Its branch tests `x==C2` with if_true=T2 and if_false=F.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |