Skip to main content

rucc_opt/
predict.rs

1//! Static branch prediction: which way a branch goes, when there is no profile that says.
2//!
3//! Design: section 11.2 of `spec/optimizer/11-profile-and-frequency.md`.
4//!
5//! # Ten predictors and not fifty five
6//!
7//! GCC has fifty five, in `gcc/predict.def`, each naming a syntactic situation and the rate at
8//! which the guess turned out right when somebody measured it. Ten of those are for Fortran, and
9//! a long tail of the rest sit below sixty five percent. Section 11.2 keeps ten: the ones that
10//! survive both cuts. A predictor at fifty nine percent moves a probability nine points off even,
11//! and nothing downstream of a frequency decides differently over nine points, so it costs a
12//! branch of code here and buys nothing.
13//!
14//! The numbers themselves are Ball and Larus's and Wu and Larus's, from the middle of the 1990s,
15//! and they have held up because they are facts about how people write programs rather than about
16//! any machine. They live in [`rucc_cost::heuristics`] with the document that argued for them, the
17//! way section 40.12 says every threshold has to.
18//!
19//! # First match
20//!
21//! The predictors are ordered and the first one that applies decides. GCC computes both this and a
22//! Dempster-Shafer combination of every predictor that applies, and uses first match by default;
23//! this does the part GCC uses. The order is the order section 11.2 lists them in and it is the
24//! part of this file most worth getting right, because it is where the predictors disagree that
25//! the order is doing anything at all. `__builtin_expect` is first because a user who wrote it
26//! meant it, and the `cold` attribute is near the top for the same reason.
27//!
28//! # What a prediction is worth
29//!
30//! Every probability out of here is [`Quality::Guessed`], with one exception: a block with one
31//! way out takes it, and that is [`Quality::Precise`] because it is not a guess. So a function
32//! with no branches in it gets precise frequencies, which is the right answer and comes out of the
33//! arithmetic rather than out of a special case.
34//!
35//! # Where the noreturn predictor gets its answer
36//!
37//! Two places, and only the first needs a call graph. The IR says it directly: the front end emits
38//! [`Opcode::Unreachable`] after a call to a `noreturn` function, so a block from which no `return`
39//! is reachable is a block control does not come back from, and that is a walk backwards from the
40//! returns. The other place is the callee's own attributes, which are per function and not at the
41//! call site, so a caller that has the module hands them over in [`Callees`]. A function pass that
42//! has only its function passes [`Callees::nothing`] and keeps the first answer, which is most of
43//! what the predictor was for: C error handling is `if (x) { report(); abort(); }` and it is the
44//! `abort` that shows up as unreachable.
45
46use std::collections::HashMap;
47
48use rucc_base::Symbol;
49use rucc_cost::heuristics::{
50    PREDICT_CALL_NOT_TAKEN, PREDICT_COLD_CALL, PREDICT_CONTINUE_TAKEN, PREDICT_EXPECT,
51    PREDICT_LOOP_EXIT_NOT_TAKEN, PREDICT_LOOP_GUARD_TAKEN, PREDICT_NEGATIVE_RETURN,
52    PREDICT_NEVER_RETURNS, PREDICT_NULL_RETURN, PREDICT_POINTER_NOT_NULL, PREDICT_RETURN_BLOCKS,
53};
54use rucc_ir::{AttrSet, Attrs, Block, Def, Extra, Func, Inst, IntPred, Module, Opcode, Value};
55
56use crate::cfg::Cfg;
57use crate::fold::constant;
58use crate::loops::Loops;
59use crate::profile::{Probability, Quality};
60
61/// Which predictor decided a branch.
62///
63/// In the order they are asked, which is section 11.2's order, so a comparison between two of
64/// these says which one wins where both apply.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
66pub enum Predictor {
67    /// `__builtin_expect` named an arm.
68    Expect,
69    /// One arm does not come back.
70    NeverReturns,
71    /// One arm calls a function the user marked `cold`.
72    ColdCall,
73    /// One arm leaves the loop and the other stays in it.
74    LoopExit,
75    /// The branch decides whether to run a loop at all.
76    LoopGuard,
77    /// The condition compares a pointer against null.
78    PointerNotNull,
79    /// One arm returns a negative constant.
80    NegativeReturn,
81    /// One arm returns a null pointer.
82    NullReturn,
83    /// One arm contains a call and the other does not.
84    CallNotTaken,
85    /// One arm goes back to the top of the loop.
86    Continue,
87    /// Nothing applied, so the arms are even.
88    Nothing,
89}
90
91impl Predictor {
92    /// How it reads in a dump.
93    #[must_use]
94    pub const fn as_str(self) -> &'static str {
95        match self {
96            Self::Expect => "__builtin_expect",
97            Self::NeverReturns => "the arm that does not come back",
98            Self::ColdCall => "the arm that calls a cold function",
99            Self::LoopExit => "the loop exit",
100            Self::LoopGuard => "the loop guard",
101            Self::PointerNotNull => "the pointer is not null",
102            Self::NegativeReturn => "the arm that returns a negative number",
103            Self::NullReturn => "the arm that returns null",
104            Self::CallNotTaken => "the arm that calls something",
105            Self::Continue => "the continue",
106            Self::Nothing => "nothing, so even",
107        }
108    }
109
110    /// The rate at which it was measured right, in percent, and fifty for no prediction at all.
111    #[must_use]
112    pub const fn hit_rate(self) -> u32 {
113        match self {
114            Self::Expect => PREDICT_EXPECT,
115            Self::NeverReturns => PREDICT_NEVER_RETURNS,
116            Self::ColdCall => PREDICT_COLD_CALL,
117            Self::LoopExit => PREDICT_LOOP_EXIT_NOT_TAKEN,
118            Self::LoopGuard => PREDICT_LOOP_GUARD_TAKEN,
119            Self::PointerNotNull => PREDICT_POINTER_NOT_NULL,
120            Self::NegativeReturn => PREDICT_NEGATIVE_RETURN,
121            Self::NullReturn => PREDICT_NULL_RETURN,
122            Self::CallNotTaken => PREDICT_CALL_NOT_TAKEN,
123            Self::Continue => PREDICT_CONTINUE_TAKEN,
124            // Even, which is the absence of a prediction rather than one, and not a number
125            // anybody would tune.
126            Self::Nothing => 50,
127        }
128    }
129
130    /// The ten, in the order they are asked.
131    pub const ORDER: [Self; 10] = [
132        Self::Expect,
133        Self::NeverReturns,
134        Self::ColdCall,
135        Self::LoopExit,
136        Self::LoopGuard,
137        Self::PointerNotNull,
138        Self::NegativeReturn,
139        Self::NullReturn,
140        Self::CallNotTaken,
141        Self::Continue,
142    ];
143}
144
145impl std::fmt::Display for Predictor {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        f.write_str(self.as_str())
148    }
149}
150
151/// What the predictors know about the functions this one calls.
152///
153/// A call site carries the callee's name and the signature it is called with, and not the callee's
154/// attributes, because the attributes belong to the callee and there is one of it and many call
155/// sites. So whoever has the module builds this once and hands it over. A caller that does not
156/// have one passes [`Callees::nothing`], which answers no to everything and costs the two
157/// predictors that read it.
158#[derive(Debug, Clone, Default)]
159pub struct Callees {
160    known: HashMap<Symbol, AttrSet>,
161}
162
163impl Callees {
164    /// Nothing known about anything.
165    #[must_use]
166    pub fn nothing() -> Self {
167        Self::default()
168    }
169
170    /// Every function in the module, by name, with what it promises.
171    ///
172    /// Declarations count and are most of the value: `abort` is declared and not defined, and it
173    /// is the one the predictor most wants to know about.
174    #[must_use]
175    pub fn of_module(module: &Module) -> Self {
176        let mut known = HashMap::new();
177        for id in module.funcs() {
178            let func = &module[id];
179            known.insert(func.name, func.attrs.set);
180        }
181        Self { known }
182    }
183
184    /// Records what one function promises, for a caller assembling this by hand.
185    pub fn record(&mut self, name: Symbol, attrs: Attrs) {
186        self.known.insert(name, attrs.set);
187    }
188
189    /// Whether control does not come back from a call to it.
190    #[must_use]
191    pub fn never_returns(&self, name: Symbol) -> bool {
192        self.known.get(&name).is_some_and(|set| set.contains(AttrSet::NORETURN))
193    }
194
195    /// Whether the user said it is rarely called.
196    #[must_use]
197    pub fn is_cold(&self, name: Symbol) -> bool {
198        self.known.get(&name).is_some_and(|set| set.contains(AttrSet::COLD))
199    }
200}
201
202/// How likely each edge out of each block is.
203///
204/// Indexed the way the graph is: [`Predictions::edges`] gives one probability for each block in
205/// [`Cfg::successors`], in that order. They sum to exactly [`Probability::SCALE`] for every block
206/// that has any, which is what the frequency computation in section 11.3 needs and what the test
207/// at the bottom of this file checks on every shape it builds.
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct Predictions {
210    edges: Vec<Vec<Probability>>,
211    by: Vec<Predictor>,
212}
213
214impl Predictions {
215    /// Predicts every branch in the function.
216    ///
217    /// Linear in the blocks and the edges, except for the two return value predictors, which walk
218    /// forward from an arm over blocks with one way out and stop after
219    /// [`PREDICT_RETURN_BLOCKS`] of them.
220    #[must_use]
221    pub fn of(func: &Func, cfg: &Cfg, loops: &Loops, callees: &Callees) -> Self {
222        let width = cfg.capacity();
223        let mut edges: Vec<Vec<Probability>> = vec![Vec::new(); width];
224        let mut by = vec![Predictor::Nothing; width];
225        let returns = returning(func, cfg);
226
227        for block in func.blocks() {
228            let Some(term) = func.terminator(block) else { continue };
229            let succs = cfg.successors(block);
230            if succs.len() == 2 && func[term].opcode == Opcode::BrIf {
231                let (taken, who) = branch(func, cfg, loops, callees, &returns, block);
232                edges[block.index()] = vec![taken, taken.complement()];
233                by[block.index()] = who;
234                continue;
235            }
236            let (parts, who) = share(func, cfg, callees, &returns, block, term);
237            edges[block.index()] = parts;
238            by[block.index()] = who;
239        }
240
241        Self { edges, by }
242    }
243
244    /// The probability of each edge out of this block, in [`Cfg::successors`] order.
245    #[must_use]
246    pub fn edges(&self, block: Block) -> &[Probability] {
247        self.edges.get(block.index()).map_or(&[], Vec::as_slice)
248    }
249
250    /// The probability of the edge at that position among this block's successors.
251    ///
252    /// Zero for an edge that is not there, because the chance of taking an edge that does not
253    /// exist is not a guess.
254    #[must_use]
255    pub fn taken(&self, block: Block, index: usize) -> Probability {
256        self.edges(block).get(index).copied().unwrap_or_else(Probability::never)
257    }
258
259    /// Which predictor decided this block's branch.
260    #[must_use]
261    pub fn by(&self, block: Block) -> Predictor {
262        self.by.get(block.index()).copied().unwrap_or(Predictor::Nothing)
263    }
264}
265
266/// The probability of the first arm, given which arm the predictor thinks is taken.
267fn toward(first: bool, percent: u32) -> Probability {
268    let likely = Probability::percent(percent, Quality::Guessed);
269    if first { likely } else { likely.complement() }
270}
271
272/// Predicts a two armed branch, first match, in section 11.2's order.
273///
274/// The answer is the probability of the first successor, which for a `br_if` is the arm taken when
275/// the condition is one. The second gets the complement, so the two sum to certainty exactly.
276fn branch(
277    func: &Func,
278    cfg: &Cfg,
279    loops: &Loops,
280    callees: &Callees,
281    returns: &[bool],
282    block: Block,
283) -> (Probability, Predictor) {
284    let succs = cfg.successors(block);
285    let (first, second) = (succs[0], succs[1]);
286    let term = func.terminator(block).expect("a block with successors has a terminator");
287    let cond = *func[func[term].args].first().expect("a br_if has a condition");
288
289    if let Some(taken) = claimed(func, term) {
290        return (taken, Predictor::Expect);
291    }
292
293    let gone = |at: Block| never_comes_back(func, callees, returns, at);
294    if gone(first) != gone(second) {
295        return (toward(!gone(first), PREDICT_NEVER_RETURNS), Predictor::NeverReturns);
296    }
297
298    let cold = |at: Block| calls_named(func, at, |name| callees.is_cold(name));
299    if cold(first) != cold(second) {
300        return (toward(!cold(first), PREDICT_COLD_CALL), Predictor::ColdCall);
301    }
302
303    let leaves = |at: Block| match loops.innermost(block) {
304        Some(id) => !loops.contains(id, at),
305        None => false,
306    };
307    if leaves(first) != leaves(second) {
308        return (toward(!leaves(first), PREDICT_LOOP_EXIT_NOT_TAKEN), Predictor::LoopExit);
309    }
310
311    let enters = |at: Block| enters_loop(cfg, loops, block, at);
312    if enters(first) != enters(second) {
313        return (toward(enters(first), PREDICT_LOOP_GUARD_TAKEN), Predictor::LoopGuard);
314    }
315
316    if let Some(taken) = pointer_null(func, cond) {
317        return (taken, Predictor::PointerNotNull);
318    }
319
320    let gives = |at: Block| returns_constant(func, cfg, at);
321    let negative = |at: Block| matches!(gives(at), Some(Returned::Negative));
322    if negative(first) != negative(second) {
323        return (toward(!negative(first), PREDICT_NEGATIVE_RETURN), Predictor::NegativeReturn);
324    }
325    let null = |at: Block| matches!(gives(at), Some(Returned::Null));
326    if null(first) != null(second) {
327        return (toward(!null(first), PREDICT_NULL_RETURN), Predictor::NullReturn);
328    }
329
330    let calls = |at: Block| has_call(func, at);
331    if calls(first) != calls(second) {
332        return (toward(!calls(first), PREDICT_CALL_NOT_TAKEN), Predictor::CallNotTaken);
333    }
334
335    let again = |at: Block| goes_round_again(loops, block, at);
336    if again(first) != again(second) {
337        return (toward(again(first), PREDICT_CONTINUE_TAKEN), Predictor::Continue);
338    }
339
340    (Probability::even(), Predictor::Nothing)
341}
342
343/// Splits a block's outgoing probability when it is not a two armed branch.
344///
345/// A jump takes its one edge, and that is a certainty rather than a guess. A `switch` and an
346/// `indirect_br` split evenly, weighted by how many edges name each successor, because a block two
347/// labels lead to is reached two ways. The one prediction that still applies is the noreturn one:
348/// a `switch` arm that aborts is as unlikely here as it is on a branch, and the arms that come back
349/// share what is left.
350fn share(
351    func: &Func,
352    cfg: &Cfg,
353    callees: &Callees,
354    returns: &[bool],
355    block: Block,
356    term: Inst,
357) -> (Vec<Probability>, Predictor) {
358    let succs = cfg.successors(block);
359    if succs.is_empty() {
360        return (Vec::new(), Predictor::Nothing);
361    }
362    if succs.len() == 1 {
363        return (vec![Probability::always()], Predictor::Nothing);
364    }
365
366    let mut weight = vec![0u64; succs.len()];
367    for call in func.successors(term) {
368        if let Some(at) = succs.iter().position(|&block| block == call.block) {
369            weight[at] += 1;
370        }
371    }
372    let gone: Vec<bool> =
373        succs.iter().map(|&at| never_comes_back(func, callees, returns, at)).collect();
374
375    let total = |side: bool| -> u64 {
376        weight.iter().zip(&gone).filter(|&(_, &away)| away == side).map(|(w, _)| *w).sum()
377    };
378    let whole = u64::from(Probability::SCALE);
379    let mut parts = vec![0u32; succs.len()];
380    let who = if total(true) == 0 || total(false) == 0 {
381        // Every arm comes back or none of them does, and either way there is nothing true of one
382        // of them that is not true of all of them. The side with the weight takes everything.
383        hand_out(whole, &weight, &gone, total(false) == 0, &mut parts);
384        Predictor::Nothing
385    } else {
386        let budget = u64::from(
387            Probability::percent(PREDICT_NEVER_RETURNS, Quality::Guessed).complement().parts(),
388        );
389        hand_out(budget, &weight, &gone, true, &mut parts);
390        hand_out(whole - budget, &weight, &gone, false, &mut parts);
391        Predictor::NeverReturns
392    };
393
394    let split = parts.into_iter().map(|parts| Probability::new(parts, Quality::Guessed)).collect();
395    (split, who)
396}
397
398/// Divides a budget between the successors on one side of a question, in proportion to how many
399/// edges lead to each.
400///
401/// What the division leaves over goes to the first of them, so the parts add up to the budget
402/// exactly. A budget of nothing is a group that gets nothing and is not an error: a switch whose
403/// every arm aborts has no arm to give the other side's share to.
404fn hand_out(budget: u64, weight: &[u64], gone: &[bool], side: bool, parts: &mut [u32]) {
405    let total: u64 =
406        weight.iter().zip(gone).filter(|&(_, &away)| away == side).map(|(w, _)| *w).sum();
407    if total == 0 || budget == 0 {
408        return;
409    }
410    let mut spent = 0;
411    let mut first = None;
412    for (at, &w) in weight.iter().enumerate() {
413        if gone[at] != side {
414            continue;
415        }
416        let share = budget * w / total;
417        parts[at] = u32::try_from(share).unwrap_or(Probability::SCALE);
418        spent += share;
419        if first.is_none() {
420            first = Some(at);
421        }
422    }
423    if let Some(at) = first {
424        parts[at] += u32::try_from(budget - spent).unwrap_or(0);
425    }
426}
427
428/// What the branch itself says about how often its first arm is taken, if anything does.
429///
430/// This is where `__builtin_expect` arrives. The front end writes an [`Opcode::Expect`] holding the
431/// value and what the program says it will be, and `crate::expect` moves the claim onto the arms of
432/// the branch and takes the instruction away, so by the time anything predicts anything the hint is
433/// a number on the edge rather than a node to chase the condition back to. A profile will write the
434/// same number in the same place, which is the point of it being there.
435///
436/// The quality is [`Quality::Guessed`] because a hint is what somebody expected and not what
437/// anybody measured. Section 11.2 says the same thing about the number itself, which is ninety
438/// percent rather than certainty: the other arm still has to run correctly.
439fn claimed(func: &Func, term: Inst) -> Option<Probability> {
440    let at = func.target_list(term).iter().next()?;
441    let parts = func[at].hint.taken()?;
442    Some(Probability::new(parts, Quality::Guessed))
443}
444
445/// The prediction a comparison of a pointer against null makes, if that is what the condition is.
446fn pointer_null(func: &Func, cond: Value) -> Option<Probability> {
447    let Def::Result { inst, .. } = func[cond].def else { return None };
448    let data = &func[inst];
449    if data.opcode != Opcode::ICmp {
450        return None;
451    }
452    let Extra::IntPred(pred) = data.extra else { return None };
453    let args = &func[data.args];
454    let lhs = *args.first()?;
455    let rhs = *args.get(1)?;
456    // Exactly one side null. Both sides null is a comparison of two constants, which simplify-cfg
457    // answers properly rather than guessing at.
458    if is_null(func, lhs) == is_null(func, rhs) {
459        return None;
460    }
461    match pred {
462        IntPred::Eq => Some(toward(false, PREDICT_POINTER_NOT_NULL)),
463        IntPred::Ne => Some(toward(true, PREDICT_POINTER_NOT_NULL)),
464        _ => None,
465    }
466}
467
468/// Whether this value is a null pointer.
469///
470/// Which is `int_to_ptr` of a zero, because that is what `crates/rucc-lower/src/body.rs` writes for
471/// one: `iconst` produces an integer and never a pointer, so a pointer constant is always a
472/// conversion of an integer one.
473fn is_null(func: &Func, value: Value) -> bool {
474    if !func[value].ty.is_ptr() {
475        return false;
476    }
477    let Def::Result { inst, .. } = func[value].def else { return false };
478    if func[inst].opcode != Opcode::IntToPtr {
479        return false;
480    }
481    let Some(&arg) = func[func[inst].args].first() else { return false };
482    match constant(func, arg) {
483        Some((value, ty)) => value.signed(ty) == 0,
484        None => false,
485    }
486}
487
488/// What the two return value predictors found at the end of an arm.
489#[derive(Debug, Clone, Copy, PartialEq, Eq)]
490enum Returned {
491    /// A negative constant, which in C means the call failed.
492    Negative,
493    /// A null pointer.
494    Null,
495    /// A constant that is neither.
496    Other,
497}
498
499/// What this arm returns, if it goes straight to a `return` of a constant.
500///
501/// Forward over blocks with one way out, stopping after [`PREDICT_RETURN_BLOCKS`] of them. GCC
502/// propagates the prediction backwards from the return over every path that reaches it, which
503/// needs the paths. This finds `if (bad) return -1;` and the two or three statements somebody put
504/// in front of the return, which is the shape the predictor was measured on.
505fn returns_constant(func: &Func, cfg: &Cfg, start: Block) -> Option<Returned> {
506    let mut at = start;
507    for _ in 0..PREDICT_RETURN_BLOCKS {
508        let term = func.terminator(at)?;
509        if func[term].opcode == Opcode::Return {
510            let &value = func[func[term].args].first()?;
511            if is_null(func, value) {
512                return Some(Returned::Null);
513            }
514            let (value, ty) = constant(func, value)?;
515            return Some(if value.signed(ty) < 0 { Returned::Negative } else { Returned::Other });
516        }
517        match cfg.successors(at) {
518            [only] => at = *only,
519            _ => return None,
520        }
521    }
522    None
523}
524
525/// Whether control comes back from this block at all.
526///
527/// Two questions in one, because they have the same answer and the same consequence: whether a
528/// `return` is reachable from here, and whether the block calls something the callee's own
529/// attributes say does not come back.
530fn never_comes_back(func: &Func, callees: &Callees, returns: &[bool], block: Block) -> bool {
531    !returns[block.index()] || calls_named(func, block, |name| callees.never_returns(name))
532}
533
534/// Whether this block holds a direct call to a function the predicate accepts.
535///
536/// A call through a pointer is never one, because there is no name to ask about.
537fn calls_named(func: &Func, block: Block, mut ok: impl FnMut(Symbol) -> bool) -> bool {
538    func.insts(block).any(|inst| {
539        let data = &func[inst];
540        if !matches!(data.opcode, Opcode::Call | Opcode::TailCall) {
541            return false;
542        }
543        let Extra::Call(at) = data.extra else { return false };
544        match func[at].callee {
545            Some(name) => ok(name),
546            None => false,
547        }
548    })
549}
550
551/// Whether this block calls anything at all, by name or through a pointer.
552fn has_call(func: &Func, block: Block) -> bool {
553    func.insts(block).any(|inst| {
554        matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall | Opcode::CallIndirect)
555    })
556}
557
558/// Whether taking this edge runs a loop the branch is outside of.
559///
560/// The header itself, or the one block in front of it, because a guard the front end wrote usually
561/// branches to a preheader rather than to the header.
562fn enters_loop(cfg: &Cfg, loops: &Loops, from: Block, at: Block) -> bool {
563    if heads_a_loop(loops, from, at) {
564        return true;
565    }
566    match cfg.successors(at) {
567        [only] => heads_a_loop(loops, from, *only),
568        _ => false,
569    }
570}
571
572/// Whether this block is the header of a loop the other block is not in.
573fn heads_a_loop(loops: &Loops, from: Block, at: Block) -> bool {
574    let Some(id) = loops.innermost(at) else { return false };
575    loops.header(id) == at && !loops.contains(id, from)
576}
577
578/// Whether this edge is a `continue`, which is a jump back to the top from inside the body.
579fn goes_round_again(loops: &Loops, from: Block, at: Block) -> bool {
580    match loops.innermost(from) {
581        Some(id) => loops.header(id) == at,
582        None => false,
583    }
584}
585
586/// Which blocks a `return` is reachable from.
587///
588/// Backwards from every block that ends in one. What this answers is the noreturn question without
589/// a call graph: a block from which no return is reachable either aborts or spins forever, and in
590/// C the first is nearly always what it is. A block with no terminator is a function under
591/// construction and counts as not returning, which costs nothing because a pass asking this has a
592/// function the verifier has already accepted.
593fn returning(func: &Func, cfg: &Cfg) -> Vec<bool> {
594    let mut yes = vec![false; cfg.capacity()];
595    let mut stack = Vec::new();
596    for block in func.blocks() {
597        let Some(term) = func.terminator(block) else { continue };
598        if matches!(func[term].opcode, Opcode::Return | Opcode::TailCall) {
599            yes[block.index()] = true;
600            stack.push(block);
601        }
602    }
603    while let Some(block) = stack.pop() {
604        for &pred in cfg.predecessors(block) {
605            if !yes[pred.index()] {
606                yes[pred.index()] = true;
607                stack.push(pred);
608            }
609        }
610    }
611    yes
612}
613
614#[cfg(test)]
615mod tests {
616    use rucc_base::Interner;
617    use rucc_ir::{
618        AttrSet, Attrs, Block, BlockCall, Builder, Func, Hint, IntPred, Opcode, Signature, Type,
619    };
620
621    use super::{Callees, Predictions, Predictor};
622    use crate::cfg::Cfg;
623    use crate::dom::Dominators;
624    use crate::loops::Loops;
625    use crate::profile::{Probability, Quality};
626
627    /// The three analyses a prediction is read against.
628    fn shape(func: &Func) -> (Cfg, Loops) {
629        let cfg = Cfg::new(func);
630        let doms = Dominators::new(&cfg);
631        let loops = Loops::new(&cfg, &doms);
632        (cfg, loops)
633    }
634
635    /// Predicts with nothing known about any callee, which is what a function pass has.
636    fn predict(func: &Func) -> (Predictions, Cfg) {
637        let (cfg, loops) = shape(func);
638        let seen = Predictions::of(func, &cfg, &loops, &Callees::nothing());
639        (seen, cfg)
640    }
641
642    /// A function with `n` blocks and a name to call things by.
643    fn blank(blocks: usize) -> (Interner, Func, Vec<Block>) {
644        let mut names = Interner::new();
645        let mut func = Func::new(names.intern("f"), Signature::new());
646        let list = (0..blocks).map(|_| func.create_block()).collect();
647        (names, func, list)
648    }
649
650    #[test]
651    fn a_block_with_one_way_out_takes_it_and_that_is_not_a_guess() {
652        let (_, mut func, at) = blank(2);
653        Builder::new(&mut func, at[0]).jump(at[1], &[]);
654        let mut build = Builder::new(&mut func, at[1]);
655        let zero = build.iconst(Type::int(32), 0);
656        build.ret(&[zero]);
657
658        let (seen, _) = predict(&func);
659        assert_eq!(seen.edges(at[0]).len(), 1);
660        assert_eq!(seen.taken(at[0], 0), Probability::always());
661        assert_eq!(seen.taken(at[0], 0).quality(), Quality::Precise);
662        // The block that returns has no edges at all, and an edge that is not there is not taken.
663        assert!(seen.edges(at[1]).is_empty());
664        assert_eq!(seen.taken(at[1], 0), Probability::never());
665    }
666
667    #[test]
668    fn the_arm_that_does_not_come_back_is_the_one_not_taken() {
669        // `if (x) abort();` as the front end leaves it, which is a branch to a block ending in
670        // `unreachable`. No call graph is needed to see it.
671        let (_, mut func, at) = blank(3);
672        let mut build = Builder::new(&mut func, at[0]);
673        let cond = build.iconst(Type::int(1), 1);
674        build.br_if(cond, at[1], &[], at[2], &[]);
675        Builder::new(&mut func, at[1]).unreachable();
676        let mut build = Builder::new(&mut func, at[2]);
677        let zero = build.iconst(Type::int(32), 0);
678        build.ret(&[zero]);
679
680        let (seen, _) = predict(&func);
681        assert_eq!(seen.by(at[0]), Predictor::NeverReturns);
682        assert_eq!(seen.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
683        assert_eq!(seen.taken(at[0], 1), Probability::percent(99, Quality::Guessed));
684    }
685
686    #[test]
687    fn the_arm_that_calls_a_noreturn_function_is_the_one_not_taken() {
688        // The same prediction from the other direction: control does come back from the block as
689        // far as the graph is concerned, and it is the callee's attributes that say otherwise.
690        let (mut names, mut func, at) = blank(4);
691        let abort = names.intern("abort");
692        let sig = func.add_signature(Signature::new());
693        let mut build = Builder::new(&mut func, at[0]);
694        let cond = build.iconst(Type::int(1), 1);
695        build.br_if(cond, at[1], &[], at[2], &[]);
696        let mut build = Builder::new(&mut func, at[1]);
697        build.call(abort, sig, &[]);
698        build.jump(at[3], &[]);
699        Builder::new(&mut func, at[2]).jump(at[3], &[]);
700        let mut build = Builder::new(&mut func, at[3]);
701        let zero = build.iconst(Type::int(32), 0);
702        build.ret(&[zero]);
703
704        let mut callees = Callees::nothing();
705        callees.record(abort, Attrs { set: AttrSet::NORETURN, ..Attrs::NONE });
706        let (cfg, loops) = shape(&func);
707
708        let told = Predictions::of(&func, &cfg, &loops, &callees);
709        assert_eq!(told.by(at[0]), Predictor::NeverReturns);
710        assert_eq!(told.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
711
712        // And with nothing known about the callee the two arms are the same shape, so the call
713        // predictor is what is left to say anything about them.
714        let (guessed, _) = predict(&func);
715        assert_eq!(guessed.by(at[0]), Predictor::CallNotTaken);
716    }
717
718    #[test]
719    fn the_arm_that_calls_a_cold_function_is_the_one_not_taken() {
720        let (mut names, mut func, at) = blank(4);
721        let report = names.intern("report");
722        let sig = func.add_signature(Signature::new());
723        let mut build = Builder::new(&mut func, at[0]);
724        let cond = build.iconst(Type::int(1), 1);
725        build.br_if(cond, at[1], &[], at[2], &[]);
726        let mut build = Builder::new(&mut func, at[1]);
727        build.call(report, sig, &[]);
728        build.jump(at[3], &[]);
729        Builder::new(&mut func, at[2]).jump(at[3], &[]);
730        let mut build = Builder::new(&mut func, at[3]);
731        let zero = build.iconst(Type::int(32), 0);
732        build.ret(&[zero]);
733
734        let mut callees = Callees::nothing();
735        callees.record(report, Attrs { set: AttrSet::COLD, ..Attrs::NONE });
736        let (cfg, loops) = shape(&func);
737        let told = Predictions::of(&func, &cfg, &loops, &callees);
738
739        // The call predictor would also have fired here, at sixty seven percent. First match is
740        // what makes the user's own statement win over the guess, which is what section 11.2
741        // asks for when it says the attribute is honoured rather than blended.
742        assert_eq!(told.by(at[0]), Predictor::ColdCall);
743        assert_eq!(told.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
744    }
745
746    /// A loop: entry, header, body, exit, with the header testing and the body going round.
747    fn loop_shape() -> (Func, Vec<Block>) {
748        let (_, mut func, at) = blank(4);
749        Builder::new(&mut func, at[0]).jump(at[1], &[]);
750        let mut build = Builder::new(&mut func, at[1]);
751        let cond = build.iconst(Type::int(1), 1);
752        build.br_if(cond, at[2], &[], at[3], &[]);
753        Builder::new(&mut func, at[2]).jump(at[1], &[]);
754        let mut build = Builder::new(&mut func, at[3]);
755        let zero = build.iconst(Type::int(32), 0);
756        build.ret(&[zero]);
757        (func, at)
758    }
759
760    #[test]
761    fn a_loop_exit_is_the_edge_not_taken() {
762        let (func, at) = loop_shape();
763        let (seen, _) = predict(&func);
764        assert_eq!(seen.by(at[1]), Predictor::LoopExit);
765        // Staying in the loop, which is the first arm here.
766        assert_eq!(seen.taken(at[1], 0), Probability::percent(89, Quality::Guessed));
767        assert_eq!(seen.taken(at[1], 1), Probability::percent(89, Quality::Guessed).complement());
768    }
769
770    #[test]
771    fn a_loop_guard_is_taken_more_often_than_not() {
772        // `if (n) { while (...) ... }`, where the guard branches to the preheader rather than to
773        // the header, which is the shape the front end produces.
774        let (_, mut func, at) = blank(6);
775        let mut build = Builder::new(&mut func, at[0]);
776        let cond = build.iconst(Type::int(1), 1);
777        build.br_if(cond, at[1], &[], at[2], &[]);
778        Builder::new(&mut func, at[1]).jump(at[3], &[]);
779        Builder::new(&mut func, at[2]).jump(at[5], &[]);
780        let mut build = Builder::new(&mut func, at[3]);
781        let test = build.iconst(Type::int(1), 1);
782        build.br_if(test, at[4], &[], at[5], &[]);
783        Builder::new(&mut func, at[4]).jump(at[3], &[]);
784        let mut build = Builder::new(&mut func, at[5]);
785        let zero = build.iconst(Type::int(32), 0);
786        build.ret(&[zero]);
787
788        let (seen, _) = predict(&func);
789        assert_eq!(seen.by(at[0]), Predictor::LoopGuard);
790        assert_eq!(seen.taken(at[0], 0), Probability::percent(73, Quality::Guessed));
791    }
792
793    #[test]
794    fn a_continue_goes_round_again_more_often_than_it_falls_through() {
795        let (_, mut func, at) = blank(5);
796        Builder::new(&mut func, at[0]).jump(at[1], &[]);
797        let mut build = Builder::new(&mut func, at[1]);
798        let cond = build.iconst(Type::int(1), 1);
799        build.br_if(cond, at[2], &[], at[3], &[]);
800        let mut build = Builder::new(&mut func, at[2]);
801        let again = build.iconst(Type::int(1), 1);
802        build.br_if(again, at[1], &[], at[4], &[]);
803        Builder::new(&mut func, at[4]).jump(at[1], &[]);
804        let mut build = Builder::new(&mut func, at[3]);
805        let zero = build.iconst(Type::int(32), 0);
806        build.ret(&[zero]);
807
808        let (seen, _) = predict(&func);
809        assert_eq!(seen.by(at[2]), Predictor::Continue);
810        assert_eq!(seen.taken(at[2], 0), Probability::percent(67, Quality::Guessed));
811    }
812
813    #[test]
814    fn a_pointer_tested_against_null_is_predicted_not_null() {
815        let (_, mut func, at) = blank(3);
816        let mut build = Builder::new(&mut func, at[0]);
817        let seven = build.iconst(Type::int(64), 7);
818        let some = build.unary(Opcode::IntToPtr, seven, Type::PTR);
819        let zero = build.iconst(Type::int(64), 0);
820        let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
821        let cond = build.icmp(IntPred::Eq, some, null);
822        build.br_if(cond, at[1], &[], at[2], &[]);
823        for block in [at[1], at[2]] {
824            let mut build = Builder::new(&mut func, block);
825            let zero = build.iconst(Type::int(32), 0);
826            build.ret(&[zero]);
827        }
828
829        let (seen, _) = predict(&func);
830        assert_eq!(seen.by(at[0]), Predictor::PointerNotNull);
831        // The arm taken when the pointer is null, which is the thirty percent of the time.
832        assert_eq!(seen.taken(at[0], 0), Probability::percent(70, Quality::Guessed).complement());
833    }
834
835    #[test]
836    fn an_arm_that_returns_a_negative_number_is_the_one_not_taken() {
837        let (_, mut func, at) = blank(3);
838        let mut build = Builder::new(&mut func, at[0]);
839        let cond = build.iconst(Type::int(1), 1);
840        build.br_if(cond, at[1], &[], at[2], &[]);
841        let mut build = Builder::new(&mut func, at[1]);
842        let bad = build.iconst(Type::int(32), -1);
843        build.ret(&[bad]);
844        let mut build = Builder::new(&mut func, at[2]);
845        let good = build.iconst(Type::int(32), 0);
846        build.ret(&[good]);
847
848        let (seen, _) = predict(&func);
849        assert_eq!(seen.by(at[0]), Predictor::NegativeReturn);
850        assert_eq!(seen.taken(at[0], 0), Probability::percent(98, Quality::Guessed).complement());
851    }
852
853    #[test]
854    fn an_arm_that_returns_null_is_the_one_not_taken_and_by_a_smaller_margin() {
855        let (_, mut func, at) = blank(3);
856        let mut build = Builder::new(&mut func, at[0]);
857        let cond = build.iconst(Type::int(1), 1);
858        build.br_if(cond, at[1], &[], at[2], &[]);
859        let mut build = Builder::new(&mut func, at[1]);
860        let zero = build.iconst(Type::int(64), 0);
861        let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
862        build.ret(&[null]);
863        let mut build = Builder::new(&mut func, at[2]);
864        let seven = build.iconst(Type::int(64), 7);
865        let some = build.unary(Opcode::IntToPtr, seven, Type::PTR);
866        build.ret(&[some]);
867
868        let (seen, _) = predict(&func);
869        assert_eq!(seen.by(at[0]), Predictor::NullReturn);
870        assert_eq!(seen.taken(at[0], 0), Probability::percent(71, Quality::Guessed).complement());
871        // The end of a list is an ordinary answer and a negative return is a failure, which is
872        // why one of these predictors is at seventy one and the other at ninety eight.
873        assert!(Predictor::NullReturn.hit_rate() < Predictor::NegativeReturn.hit_rate());
874    }
875
876    #[test]
877    fn nothing_to_go_on_is_an_even_split_that_says_it_is_a_guess() {
878        let (_, mut func, at) = blank(3);
879        let mut build = Builder::new(&mut func, at[0]);
880        let cond = build.iconst(Type::int(1), 1);
881        build.br_if(cond, at[1], &[], at[2], &[]);
882        for block in [at[1], at[2]] {
883            let mut build = Builder::new(&mut func, block);
884            let zero = build.iconst(Type::int(32), 0);
885            build.ret(&[zero]);
886        }
887
888        let (seen, _) = predict(&func);
889        assert_eq!(seen.by(at[0]), Predictor::Nothing);
890        assert_eq!(seen.taken(at[0], 0), Probability::even());
891        assert_eq!(seen.taken(at[0], 0).quality(), Quality::Guessed);
892        assert!(!seen.taken(at[0], 0).is_predictable());
893    }
894
895    /// Writes a hint onto the arms of a branch, which is what `crate::expect` does to a
896    /// `__builtin_expect` before anything gets here.
897    fn hinted(func: &mut Func, block: Block, parts: u32) {
898        let term = func.terminator(block).expect("a branch");
899        let hint = Hint::parts(parts);
900        for (at, hint) in func.target_list(term).iter().zip([hint, hint.complement()]) {
901            let call = func[at];
902            func.set_block_call(at, BlockCall { hint, ..call });
903        }
904    }
905
906    #[test]
907    fn a_builtin_expect_wins_over_every_predictor_after_it() {
908        // The arm the user named is also the arm that aborts, and the user wins. This is the one
909        // test that says what first match is for: without it the noreturn predictor would answer,
910        // and it would answer the other way round.
911        let (_, mut func, at) = blank(3);
912        let mut build = Builder::new(&mut func, at[0]);
913        let cond = build.iconst(Type::int(1), 1);
914        build.br_if(cond, at[1], &[], at[2], &[]);
915        Builder::new(&mut func, at[1]).unreachable();
916        let mut build = Builder::new(&mut func, at[2]);
917        let zero = build.iconst(Type::int(32), 0);
918        build.ret(&[zero]);
919        hinted(&mut func, at[0], 9_000);
920
921        let (seen, _) = predict(&func);
922        assert_eq!(seen.by(at[0]), Predictor::Expect);
923        assert_eq!(seen.taken(at[0], 0), Probability::percent(90, Quality::Guessed));
924    }
925
926    #[test]
927    fn a_builtin_expect_of_zero_names_the_other_arm() {
928        let (_, mut func, at) = blank(3);
929        let mut build = Builder::new(&mut func, at[0]);
930        let cond = build.iconst(Type::int(1), 1);
931        build.br_if(cond, at[1], &[], at[2], &[]);
932        for block in [at[1], at[2]] {
933            let mut build = Builder::new(&mut func, block);
934            let zero = build.iconst(Type::int(32), 0);
935            build.ret(&[zero]);
936        }
937        hinted(&mut func, at[0], 1_000);
938
939        let (seen, _) = predict(&func);
940        assert_eq!(seen.by(at[0]), Predictor::Expect);
941        assert_eq!(seen.taken(at[0], 0), Probability::percent(90, Quality::Guessed).complement());
942    }
943
944    /// A switch on four values, where the first case aborts and the last two share a block.
945    fn switch_shape() -> (Func, Vec<Block>) {
946        let (_, mut func, at) = blank(5);
947        let mut build = Builder::new(&mut func, at[0]);
948        let value = build.iconst(Type::int(32), 0);
949        build.switch(value, at[1], &[(0, at[2]), (1, at[3]), (2, at[4]), (3, at[4])]);
950        Builder::new(&mut func, at[2]).unreachable();
951        for block in [at[1], at[3], at[4]] {
952            let mut build = Builder::new(&mut func, block);
953            let zero = build.iconst(Type::int(32), 0);
954            build.ret(&[zero]);
955        }
956        (func, at)
957    }
958
959    #[test]
960    fn a_switch_arm_that_aborts_leaves_the_rest_to_share_what_is_left() {
961        let (func, at) = switch_shape();
962        let (seen, cfg) = predict(&func);
963        let succs = cfg.successors(at[0]);
964        let aborts = succs.iter().position(|&block| block == at[2]).expect("the arm is an edge");
965        let shared = succs.iter().position(|&block| block == at[4]).expect("the arm is an edge");
966        let alone = succs.iter().position(|&block| block == at[3]).expect("the arm is an edge");
967
968        assert_eq!(seen.by(at[0]), Predictor::NeverReturns);
969        // One percent between the arms that do not come back, of which there is one.
970        assert_eq!(
971            seen.taken(at[0], aborts),
972            Probability::percent(99, Quality::Guessed).complement()
973        );
974        // Two cases lead to the same block, so it is reached two ways and gets twice the share.
975        assert_eq!(seen.taken(at[0], shared).parts(), 2 * seen.taken(at[0], alone).parts());
976    }
977
978    #[test]
979    fn the_edges_out_of_every_block_add_up_to_certainty() {
980        // What the frequency computation in section 11.3 needs, and the one property of this file
981        // that a caller is entitled to assume without reading it.
982        let (guarded, _) = {
983            let (_, mut func, at) = blank(3);
984            let mut build = Builder::new(&mut func, at[0]);
985            let cond = build.iconst(Type::int(1), 1);
986            build.br_if(cond, at[1], &[], at[2], &[]);
987            for block in [at[1], at[2]] {
988                let mut build = Builder::new(&mut func, block);
989                let zero = build.iconst(Type::int(32), 0);
990                build.ret(&[zero]);
991            }
992            (func, at)
993        };
994        let (looped, _) = loop_shape();
995        let (switched, _) = switch_shape();
996
997        for func in [guarded, looped, switched] {
998            let (seen, cfg) = predict(&func);
999            for block in func.blocks() {
1000                let edges = seen.edges(block);
1001                if edges.is_empty() {
1002                    continue;
1003                }
1004                assert_eq!(edges.len(), cfg.successors(block).len());
1005                let total: u32 = edges.iter().map(|edge| edge.parts()).sum();
1006                assert_eq!(total, Probability::SCALE, "block {block:?} does not add up");
1007            }
1008        }
1009    }
1010
1011    #[test]
1012    fn the_ten_are_the_ten_the_document_named_and_they_are_asked_in_its_order() {
1013        assert_eq!(Predictor::ORDER.len(), 10);
1014        assert!(!Predictor::ORDER.contains(&Predictor::Nothing));
1015        let mut sorted = Predictor::ORDER;
1016        sorted.sort_unstable();
1017        assert_eq!(sorted, Predictor::ORDER, "the enum order is the order they are asked in");
1018        for one in Predictor::ORDER {
1019            assert!(one.hit_rate() > Predictor::Nothing.hit_rate(), "{one} predicts nothing");
1020            assert!(!one.as_str().is_empty());
1021        }
1022    }
1023}