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) = expect(func, cond) {
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/// The prediction a `__builtin_expect` on the condition makes, if there is one.
429///
430/// Nothing emits [`Opcode::Expect`] today: `crates/rucc-sema/src/check/builtin/expect.rs` replaces
431/// the call with its first argument and drops the hint, and it says why, which is that a node
432/// every pass has to step over is a cost with no consumer. This is the consumer, so the hint has
433/// somewhere to arrive.
434fn expect(func: &Func, cond: Value) -> Option<Probability> {
435    let Def::Result { inst, .. } = func[cond].def else { return None };
436    if func[inst].opcode != Opcode::Expect {
437        return None;
438    }
439    let hint = *func[func[inst].args].get(1)?;
440    let (value, ty) = constant(func, hint)?;
441    Some(toward(value.signed(ty) != 0, PREDICT_EXPECT))
442}
443
444/// The prediction a comparison of a pointer against null makes, if that is what the condition is.
445fn pointer_null(func: &Func, cond: Value) -> Option<Probability> {
446    let Def::Result { inst, .. } = func[cond].def else { return None };
447    let data = &func[inst];
448    if data.opcode != Opcode::ICmp {
449        return None;
450    }
451    let Extra::IntPred(pred) = data.extra else { return None };
452    let args = &func[data.args];
453    let lhs = *args.first()?;
454    let rhs = *args.get(1)?;
455    // Exactly one side null. Both sides null is a comparison of two constants, which simplify-cfg
456    // answers properly rather than guessing at.
457    if is_null(func, lhs) == is_null(func, rhs) {
458        return None;
459    }
460    match pred {
461        IntPred::Eq => Some(toward(false, PREDICT_POINTER_NOT_NULL)),
462        IntPred::Ne => Some(toward(true, PREDICT_POINTER_NOT_NULL)),
463        _ => None,
464    }
465}
466
467/// Whether this value is a null pointer.
468///
469/// Which is `int_to_ptr` of a zero, because that is what `crates/rucc-lower/src/body.rs` writes for
470/// one: `iconst` produces an integer and never a pointer, so a pointer constant is always a
471/// conversion of an integer one.
472fn is_null(func: &Func, value: Value) -> bool {
473    if !func[value].ty.is_ptr() {
474        return false;
475    }
476    let Def::Result { inst, .. } = func[value].def else { return false };
477    if func[inst].opcode != Opcode::IntToPtr {
478        return false;
479    }
480    let Some(&arg) = func[func[inst].args].first() else { return false };
481    match constant(func, arg) {
482        Some((value, ty)) => value.signed(ty) == 0,
483        None => false,
484    }
485}
486
487/// What the two return value predictors found at the end of an arm.
488#[derive(Debug, Clone, Copy, PartialEq, Eq)]
489enum Returned {
490    /// A negative constant, which in C means the call failed.
491    Negative,
492    /// A null pointer.
493    Null,
494    /// A constant that is neither.
495    Other,
496}
497
498/// What this arm returns, if it goes straight to a `return` of a constant.
499///
500/// Forward over blocks with one way out, stopping after [`PREDICT_RETURN_BLOCKS`] of them. GCC
501/// propagates the prediction backwards from the return over every path that reaches it, which
502/// needs the paths. This finds `if (bad) return -1;` and the two or three statements somebody put
503/// in front of the return, which is the shape the predictor was measured on.
504fn returns_constant(func: &Func, cfg: &Cfg, start: Block) -> Option<Returned> {
505    let mut at = start;
506    for _ in 0..PREDICT_RETURN_BLOCKS {
507        let term = func.terminator(at)?;
508        if func[term].opcode == Opcode::Return {
509            let &value = func[func[term].args].first()?;
510            if is_null(func, value) {
511                return Some(Returned::Null);
512            }
513            let (value, ty) = constant(func, value)?;
514            return Some(if value.signed(ty) < 0 { Returned::Negative } else { Returned::Other });
515        }
516        match cfg.successors(at) {
517            [only] => at = *only,
518            _ => return None,
519        }
520    }
521    None
522}
523
524/// Whether control comes back from this block at all.
525///
526/// Two questions in one, because they have the same answer and the same consequence: whether a
527/// `return` is reachable from here, and whether the block calls something the callee's own
528/// attributes say does not come back.
529fn never_comes_back(func: &Func, callees: &Callees, returns: &[bool], block: Block) -> bool {
530    !returns[block.index()] || calls_named(func, block, |name| callees.never_returns(name))
531}
532
533/// Whether this block holds a direct call to a function the predicate accepts.
534///
535/// A call through a pointer is never one, because there is no name to ask about.
536fn calls_named(func: &Func, block: Block, mut ok: impl FnMut(Symbol) -> bool) -> bool {
537    func.insts(block).any(|inst| {
538        let data = &func[inst];
539        if !matches!(data.opcode, Opcode::Call | Opcode::TailCall) {
540            return false;
541        }
542        let Extra::Call(at) = data.extra else { return false };
543        match func[at].callee {
544            Some(name) => ok(name),
545            None => false,
546        }
547    })
548}
549
550/// Whether this block calls anything at all, by name or through a pointer.
551fn has_call(func: &Func, block: Block) -> bool {
552    func.insts(block).any(|inst| {
553        matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall | Opcode::CallIndirect)
554    })
555}
556
557/// Whether taking this edge runs a loop the branch is outside of.
558///
559/// The header itself, or the one block in front of it, because a guard the front end wrote usually
560/// branches to a preheader rather than to the header.
561fn enters_loop(cfg: &Cfg, loops: &Loops, from: Block, at: Block) -> bool {
562    if heads_a_loop(loops, from, at) {
563        return true;
564    }
565    match cfg.successors(at) {
566        [only] => heads_a_loop(loops, from, *only),
567        _ => false,
568    }
569}
570
571/// Whether this block is the header of a loop the other block is not in.
572fn heads_a_loop(loops: &Loops, from: Block, at: Block) -> bool {
573    let Some(id) = loops.innermost(at) else { return false };
574    loops.header(id) == at && !loops.contains(id, from)
575}
576
577/// Whether this edge is a `continue`, which is a jump back to the top from inside the body.
578fn goes_round_again(loops: &Loops, from: Block, at: Block) -> bool {
579    match loops.innermost(from) {
580        Some(id) => loops.header(id) == at,
581        None => false,
582    }
583}
584
585/// Which blocks a `return` is reachable from.
586///
587/// Backwards from every block that ends in one. What this answers is the noreturn question without
588/// a call graph: a block from which no return is reachable either aborts or spins forever, and in
589/// C the first is nearly always what it is. A block with no terminator is a function under
590/// construction and counts as not returning, which costs nothing because a pass asking this has a
591/// function the verifier has already accepted.
592fn returning(func: &Func, cfg: &Cfg) -> Vec<bool> {
593    let mut yes = vec![false; cfg.capacity()];
594    let mut stack = Vec::new();
595    for block in func.blocks() {
596        let Some(term) = func.terminator(block) else { continue };
597        if matches!(func[term].opcode, Opcode::Return | Opcode::TailCall) {
598            yes[block.index()] = true;
599            stack.push(block);
600        }
601    }
602    while let Some(block) = stack.pop() {
603        for &pred in cfg.predecessors(block) {
604            if !yes[pred.index()] {
605                yes[pred.index()] = true;
606                stack.push(pred);
607            }
608        }
609    }
610    yes
611}
612
613#[cfg(test)]
614mod tests {
615    use rucc_base::Interner;
616    use rucc_ir::{
617        AttrSet, Attrs, Block, Builder, Func, InstData, IntPred, Opcode, Signature, Type,
618    };
619
620    use super::{Callees, Predictions, Predictor};
621    use crate::cfg::Cfg;
622    use crate::dom::Dominators;
623    use crate::loops::Loops;
624    use crate::profile::{Probability, Quality};
625
626    /// The three analyses a prediction is read against.
627    fn shape(func: &Func) -> (Cfg, Loops) {
628        let cfg = Cfg::new(func);
629        let doms = Dominators::new(&cfg);
630        let loops = Loops::new(&cfg, &doms);
631        (cfg, loops)
632    }
633
634    /// Predicts with nothing known about any callee, which is what a function pass has.
635    fn predict(func: &Func) -> (Predictions, Cfg) {
636        let (cfg, loops) = shape(func);
637        let seen = Predictions::of(func, &cfg, &loops, &Callees::nothing());
638        (seen, cfg)
639    }
640
641    /// A function with `n` blocks and a name to call things by.
642    fn blank(blocks: usize) -> (Interner, Func, Vec<Block>) {
643        let mut names = Interner::new();
644        let mut func = Func::new(names.intern("f"), Signature::new());
645        let list = (0..blocks).map(|_| func.create_block()).collect();
646        (names, func, list)
647    }
648
649    #[test]
650    fn a_block_with_one_way_out_takes_it_and_that_is_not_a_guess() {
651        let (_, mut func, at) = blank(2);
652        Builder::new(&mut func, at[0]).jump(at[1], &[]);
653        let mut build = Builder::new(&mut func, at[1]);
654        let zero = build.iconst(Type::int(32), 0);
655        build.ret(&[zero]);
656
657        let (seen, _) = predict(&func);
658        assert_eq!(seen.edges(at[0]).len(), 1);
659        assert_eq!(seen.taken(at[0], 0), Probability::always());
660        assert_eq!(seen.taken(at[0], 0).quality(), Quality::Precise);
661        // The block that returns has no edges at all, and an edge that is not there is not taken.
662        assert!(seen.edges(at[1]).is_empty());
663        assert_eq!(seen.taken(at[1], 0), Probability::never());
664    }
665
666    #[test]
667    fn the_arm_that_does_not_come_back_is_the_one_not_taken() {
668        // `if (x) abort();` as the front end leaves it, which is a branch to a block ending in
669        // `unreachable`. No call graph is needed to see it.
670        let (_, mut func, at) = blank(3);
671        let mut build = Builder::new(&mut func, at[0]);
672        let cond = build.iconst(Type::int(1), 1);
673        build.br_if(cond, at[1], &[], at[2], &[]);
674        Builder::new(&mut func, at[1]).unreachable();
675        let mut build = Builder::new(&mut func, at[2]);
676        let zero = build.iconst(Type::int(32), 0);
677        build.ret(&[zero]);
678
679        let (seen, _) = predict(&func);
680        assert_eq!(seen.by(at[0]), Predictor::NeverReturns);
681        assert_eq!(seen.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
682        assert_eq!(seen.taken(at[0], 1), Probability::percent(99, Quality::Guessed));
683    }
684
685    #[test]
686    fn the_arm_that_calls_a_noreturn_function_is_the_one_not_taken() {
687        // The same prediction from the other direction: control does come back from the block as
688        // far as the graph is concerned, and it is the callee's attributes that say otherwise.
689        let (mut names, mut func, at) = blank(4);
690        let abort = names.intern("abort");
691        let sig = func.add_signature(Signature::new());
692        let mut build = Builder::new(&mut func, at[0]);
693        let cond = build.iconst(Type::int(1), 1);
694        build.br_if(cond, at[1], &[], at[2], &[]);
695        let mut build = Builder::new(&mut func, at[1]);
696        build.call(abort, sig, &[]);
697        build.jump(at[3], &[]);
698        Builder::new(&mut func, at[2]).jump(at[3], &[]);
699        let mut build = Builder::new(&mut func, at[3]);
700        let zero = build.iconst(Type::int(32), 0);
701        build.ret(&[zero]);
702
703        let mut callees = Callees::nothing();
704        callees.record(abort, Attrs { set: AttrSet::NORETURN, ..Attrs::NONE });
705        let (cfg, loops) = shape(&func);
706
707        let told = Predictions::of(&func, &cfg, &loops, &callees);
708        assert_eq!(told.by(at[0]), Predictor::NeverReturns);
709        assert_eq!(told.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
710
711        // And with nothing known about the callee the two arms are the same shape, so the call
712        // predictor is what is left to say anything about them.
713        let (guessed, _) = predict(&func);
714        assert_eq!(guessed.by(at[0]), Predictor::CallNotTaken);
715    }
716
717    #[test]
718    fn the_arm_that_calls_a_cold_function_is_the_one_not_taken() {
719        let (mut names, mut func, at) = blank(4);
720        let report = names.intern("report");
721        let sig = func.add_signature(Signature::new());
722        let mut build = Builder::new(&mut func, at[0]);
723        let cond = build.iconst(Type::int(1), 1);
724        build.br_if(cond, at[1], &[], at[2], &[]);
725        let mut build = Builder::new(&mut func, at[1]);
726        build.call(report, sig, &[]);
727        build.jump(at[3], &[]);
728        Builder::new(&mut func, at[2]).jump(at[3], &[]);
729        let mut build = Builder::new(&mut func, at[3]);
730        let zero = build.iconst(Type::int(32), 0);
731        build.ret(&[zero]);
732
733        let mut callees = Callees::nothing();
734        callees.record(report, Attrs { set: AttrSet::COLD, ..Attrs::NONE });
735        let (cfg, loops) = shape(&func);
736        let told = Predictions::of(&func, &cfg, &loops, &callees);
737
738        // The call predictor would also have fired here, at sixty seven percent. First match is
739        // what makes the user's own statement win over the guess, which is what section 11.2
740        // asks for when it says the attribute is honoured rather than blended.
741        assert_eq!(told.by(at[0]), Predictor::ColdCall);
742        assert_eq!(told.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
743    }
744
745    /// A loop: entry, header, body, exit, with the header testing and the body going round.
746    fn loop_shape() -> (Func, Vec<Block>) {
747        let (_, mut func, at) = blank(4);
748        Builder::new(&mut func, at[0]).jump(at[1], &[]);
749        let mut build = Builder::new(&mut func, at[1]);
750        let cond = build.iconst(Type::int(1), 1);
751        build.br_if(cond, at[2], &[], at[3], &[]);
752        Builder::new(&mut func, at[2]).jump(at[1], &[]);
753        let mut build = Builder::new(&mut func, at[3]);
754        let zero = build.iconst(Type::int(32), 0);
755        build.ret(&[zero]);
756        (func, at)
757    }
758
759    #[test]
760    fn a_loop_exit_is_the_edge_not_taken() {
761        let (func, at) = loop_shape();
762        let (seen, _) = predict(&func);
763        assert_eq!(seen.by(at[1]), Predictor::LoopExit);
764        // Staying in the loop, which is the first arm here.
765        assert_eq!(seen.taken(at[1], 0), Probability::percent(89, Quality::Guessed));
766        assert_eq!(seen.taken(at[1], 1), Probability::percent(89, Quality::Guessed).complement());
767    }
768
769    #[test]
770    fn a_loop_guard_is_taken_more_often_than_not() {
771        // `if (n) { while (...) ... }`, where the guard branches to the preheader rather than to
772        // the header, which is the shape the front end produces.
773        let (_, mut func, at) = blank(6);
774        let mut build = Builder::new(&mut func, at[0]);
775        let cond = build.iconst(Type::int(1), 1);
776        build.br_if(cond, at[1], &[], at[2], &[]);
777        Builder::new(&mut func, at[1]).jump(at[3], &[]);
778        Builder::new(&mut func, at[2]).jump(at[5], &[]);
779        let mut build = Builder::new(&mut func, at[3]);
780        let test = build.iconst(Type::int(1), 1);
781        build.br_if(test, at[4], &[], at[5], &[]);
782        Builder::new(&mut func, at[4]).jump(at[3], &[]);
783        let mut build = Builder::new(&mut func, at[5]);
784        let zero = build.iconst(Type::int(32), 0);
785        build.ret(&[zero]);
786
787        let (seen, _) = predict(&func);
788        assert_eq!(seen.by(at[0]), Predictor::LoopGuard);
789        assert_eq!(seen.taken(at[0], 0), Probability::percent(73, Quality::Guessed));
790    }
791
792    #[test]
793    fn a_continue_goes_round_again_more_often_than_it_falls_through() {
794        let (_, mut func, at) = blank(5);
795        Builder::new(&mut func, at[0]).jump(at[1], &[]);
796        let mut build = Builder::new(&mut func, at[1]);
797        let cond = build.iconst(Type::int(1), 1);
798        build.br_if(cond, at[2], &[], at[3], &[]);
799        let mut build = Builder::new(&mut func, at[2]);
800        let again = build.iconst(Type::int(1), 1);
801        build.br_if(again, at[1], &[], at[4], &[]);
802        Builder::new(&mut func, at[4]).jump(at[1], &[]);
803        let mut build = Builder::new(&mut func, at[3]);
804        let zero = build.iconst(Type::int(32), 0);
805        build.ret(&[zero]);
806
807        let (seen, _) = predict(&func);
808        assert_eq!(seen.by(at[2]), Predictor::Continue);
809        assert_eq!(seen.taken(at[2], 0), Probability::percent(67, Quality::Guessed));
810    }
811
812    #[test]
813    fn a_pointer_tested_against_null_is_predicted_not_null() {
814        let (_, mut func, at) = blank(3);
815        let mut build = Builder::new(&mut func, at[0]);
816        let seven = build.iconst(Type::int(64), 7);
817        let some = build.unary(Opcode::IntToPtr, seven, Type::PTR);
818        let zero = build.iconst(Type::int(64), 0);
819        let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
820        let cond = build.icmp(IntPred::Eq, some, null);
821        build.br_if(cond, at[1], &[], at[2], &[]);
822        for block in [at[1], at[2]] {
823            let mut build = Builder::new(&mut func, block);
824            let zero = build.iconst(Type::int(32), 0);
825            build.ret(&[zero]);
826        }
827
828        let (seen, _) = predict(&func);
829        assert_eq!(seen.by(at[0]), Predictor::PointerNotNull);
830        // The arm taken when the pointer is null, which is the thirty percent of the time.
831        assert_eq!(seen.taken(at[0], 0), Probability::percent(70, Quality::Guessed).complement());
832    }
833
834    #[test]
835    fn an_arm_that_returns_a_negative_number_is_the_one_not_taken() {
836        let (_, mut func, at) = blank(3);
837        let mut build = Builder::new(&mut func, at[0]);
838        let cond = build.iconst(Type::int(1), 1);
839        build.br_if(cond, at[1], &[], at[2], &[]);
840        let mut build = Builder::new(&mut func, at[1]);
841        let bad = build.iconst(Type::int(32), -1);
842        build.ret(&[bad]);
843        let mut build = Builder::new(&mut func, at[2]);
844        let good = build.iconst(Type::int(32), 0);
845        build.ret(&[good]);
846
847        let (seen, _) = predict(&func);
848        assert_eq!(seen.by(at[0]), Predictor::NegativeReturn);
849        assert_eq!(seen.taken(at[0], 0), Probability::percent(98, Quality::Guessed).complement());
850    }
851
852    #[test]
853    fn an_arm_that_returns_null_is_the_one_not_taken_and_by_a_smaller_margin() {
854        let (_, mut func, at) = blank(3);
855        let mut build = Builder::new(&mut func, at[0]);
856        let cond = build.iconst(Type::int(1), 1);
857        build.br_if(cond, at[1], &[], at[2], &[]);
858        let mut build = Builder::new(&mut func, at[1]);
859        let zero = build.iconst(Type::int(64), 0);
860        let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
861        build.ret(&[null]);
862        let mut build = Builder::new(&mut func, at[2]);
863        let seven = build.iconst(Type::int(64), 7);
864        let some = build.unary(Opcode::IntToPtr, seven, Type::PTR);
865        build.ret(&[some]);
866
867        let (seen, _) = predict(&func);
868        assert_eq!(seen.by(at[0]), Predictor::NullReturn);
869        assert_eq!(seen.taken(at[0], 0), Probability::percent(71, Quality::Guessed).complement());
870        // The end of a list is an ordinary answer and a negative return is a failure, which is
871        // why one of these predictors is at seventy one and the other at ninety eight.
872        assert!(Predictor::NullReturn.hit_rate() < Predictor::NegativeReturn.hit_rate());
873    }
874
875    #[test]
876    fn nothing_to_go_on_is_an_even_split_that_says_it_is_a_guess() {
877        let (_, mut func, at) = blank(3);
878        let mut build = Builder::new(&mut func, at[0]);
879        let cond = build.iconst(Type::int(1), 1);
880        build.br_if(cond, at[1], &[], at[2], &[]);
881        for block in [at[1], at[2]] {
882            let mut build = Builder::new(&mut func, block);
883            let zero = build.iconst(Type::int(32), 0);
884            build.ret(&[zero]);
885        }
886
887        let (seen, _) = predict(&func);
888        assert_eq!(seen.by(at[0]), Predictor::Nothing);
889        assert_eq!(seen.taken(at[0], 0), Probability::even());
890        assert_eq!(seen.taken(at[0], 0).quality(), Quality::Guessed);
891        assert!(!seen.taken(at[0], 0).is_predictable());
892    }
893
894    #[test]
895    fn a_builtin_expect_wins_over_every_predictor_after_it() {
896        // The arm the user named is also the arm that aborts, and the user wins. This is the one
897        // test that says what first match is for: without it the noreturn predictor would answer,
898        // and it would answer the other way round.
899        let (_, mut func, at) = blank(3);
900        let mut build = Builder::new(&mut func, at[0]);
901        let value = build.iconst(Type::int(1), 1);
902        let hint = build.iconst(Type::int(1), 1);
903        let args = build.func().push_values(&[value, hint]);
904        let cond = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, Type::int(1));
905        build.br_if(cond, at[1], &[], at[2], &[]);
906        Builder::new(&mut func, at[1]).unreachable();
907        let mut build = Builder::new(&mut func, at[2]);
908        let zero = build.iconst(Type::int(32), 0);
909        build.ret(&[zero]);
910
911        let (seen, _) = predict(&func);
912        assert_eq!(seen.by(at[0]), Predictor::Expect);
913        assert_eq!(seen.taken(at[0], 0), Probability::percent(90, Quality::Guessed));
914    }
915
916    #[test]
917    fn a_builtin_expect_of_zero_names_the_other_arm() {
918        let (_, mut func, at) = blank(3);
919        let mut build = Builder::new(&mut func, at[0]);
920        let value = build.iconst(Type::int(1), 1);
921        let hint = build.iconst(Type::int(1), 0);
922        let args = build.func().push_values(&[value, hint]);
923        let cond = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, Type::int(1));
924        build.br_if(cond, at[1], &[], at[2], &[]);
925        for block in [at[1], at[2]] {
926            let mut build = Builder::new(&mut func, block);
927            let zero = build.iconst(Type::int(32), 0);
928            build.ret(&[zero]);
929        }
930
931        let (seen, _) = predict(&func);
932        assert_eq!(seen.by(at[0]), Predictor::Expect);
933        assert_eq!(seen.taken(at[0], 0), Probability::percent(90, Quality::Guessed).complement());
934    }
935
936    /// A switch on four values, where the first case aborts and the last two share a block.
937    fn switch_shape() -> (Func, Vec<Block>) {
938        let (_, mut func, at) = blank(5);
939        let mut build = Builder::new(&mut func, at[0]);
940        let value = build.iconst(Type::int(32), 0);
941        build.switch(value, at[1], &[(0, at[2]), (1, at[3]), (2, at[4]), (3, at[4])]);
942        Builder::new(&mut func, at[2]).unreachable();
943        for block in [at[1], at[3], at[4]] {
944            let mut build = Builder::new(&mut func, block);
945            let zero = build.iconst(Type::int(32), 0);
946            build.ret(&[zero]);
947        }
948        (func, at)
949    }
950
951    #[test]
952    fn a_switch_arm_that_aborts_leaves_the_rest_to_share_what_is_left() {
953        let (func, at) = switch_shape();
954        let (seen, cfg) = predict(&func);
955        let succs = cfg.successors(at[0]);
956        let aborts = succs.iter().position(|&block| block == at[2]).expect("the arm is an edge");
957        let shared = succs.iter().position(|&block| block == at[4]).expect("the arm is an edge");
958        let alone = succs.iter().position(|&block| block == at[3]).expect("the arm is an edge");
959
960        assert_eq!(seen.by(at[0]), Predictor::NeverReturns);
961        // One percent between the arms that do not come back, of which there is one.
962        assert_eq!(
963            seen.taken(at[0], aborts),
964            Probability::percent(99, Quality::Guessed).complement()
965        );
966        // Two cases lead to the same block, so it is reached two ways and gets twice the share.
967        assert_eq!(seen.taken(at[0], shared).parts(), 2 * seen.taken(at[0], alone).parts());
968    }
969
970    #[test]
971    fn the_edges_out_of_every_block_add_up_to_certainty() {
972        // What the frequency computation in section 11.3 needs, and the one property of this file
973        // that a caller is entitled to assume without reading it.
974        let (guarded, _) = {
975            let (_, mut func, at) = blank(3);
976            let mut build = Builder::new(&mut func, at[0]);
977            let cond = build.iconst(Type::int(1), 1);
978            build.br_if(cond, at[1], &[], at[2], &[]);
979            for block in [at[1], at[2]] {
980                let mut build = Builder::new(&mut func, block);
981                let zero = build.iconst(Type::int(32), 0);
982                build.ret(&[zero]);
983            }
984            (func, at)
985        };
986        let (looped, _) = loop_shape();
987        let (switched, _) = switch_shape();
988
989        for func in [guarded, looped, switched] {
990            let (seen, cfg) = predict(&func);
991            for block in func.blocks() {
992                let edges = seen.edges(block);
993                if edges.is_empty() {
994                    continue;
995                }
996                assert_eq!(edges.len(), cfg.successors(block).len());
997                let total: u32 = edges.iter().map(|edge| edge.parts()).sum();
998                assert_eq!(total, Probability::SCALE, "block {block:?} does not add up");
999            }
1000        }
1001    }
1002
1003    #[test]
1004    fn the_ten_are_the_ten_the_document_named_and_they_are_asked_in_its_order() {
1005        assert_eq!(Predictor::ORDER.len(), 10);
1006        assert!(!Predictor::ORDER.contains(&Predictor::Nothing));
1007        let mut sorted = Predictor::ORDER;
1008        sorted.sort_unstable();
1009        assert_eq!(sorted, Predictor::ORDER, "the enum order is the order they are asked in");
1010        for one in Predictor::ORDER {
1011            assert!(one.hit_rate() > Predictor::Nothing.hit_rate(), "{one} predicts nothing");
1012            assert!(!one.as_str().is_empty());
1013        }
1014    }
1015}