1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
66pub enum Predictor {
67 Expect,
69 NeverReturns,
71 ColdCall,
73 LoopExit,
75 LoopGuard,
77 PointerNotNull,
79 NegativeReturn,
81 NullReturn,
83 CallNotTaken,
85 Continue,
87 Nothing,
89}
90
91impl Predictor {
92 #[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 #[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 Self::Nothing => 50,
127 }
128 }
129
130 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#[derive(Debug, Clone, Default)]
159pub struct Callees {
160 known: HashMap<Symbol, AttrSet>,
161}
162
163impl Callees {
164 #[must_use]
166 pub fn nothing() -> Self {
167 Self::default()
168 }
169
170 #[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 pub fn record(&mut self, name: Symbol, attrs: Attrs) {
186 self.known.insert(name, attrs.set);
187 }
188
189 #[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 #[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#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct Predictions {
210 edges: Vec<Vec<Probability>>,
211 by: Vec<Predictor>,
212}
213
214impl Predictions {
215 #[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 #[must_use]
246 pub fn edges(&self, block: Block) -> &[Probability] {
247 self.edges.get(block.index()).map_or(&[], Vec::as_slice)
248 }
249
250 #[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 #[must_use]
261 pub fn by(&self, block: Block) -> Predictor {
262 self.by.get(block.index()).copied().unwrap_or(Predictor::Nothing)
263 }
264}
265
266fn toward(first: bool, percent: u32) -> Probability {
268 let likely = Probability::percent(percent, Quality::Guessed);
269 if first { likely } else { likely.complement() }
270}
271
272fn 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
343fn 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 if let Some(split) = hinted(func, succs, term) {
366 return (split, Predictor::Expect);
367 }
368
369 let mut weight = vec![0u64; succs.len()];
370 for call in func.successors(term) {
371 if let Some(at) = succs.iter().position(|&block| block == call.block) {
372 weight[at] += 1;
373 }
374 }
375 let gone: Vec<bool> =
376 succs.iter().map(|&at| never_comes_back(func, callees, returns, at)).collect();
377
378 let total = |side: bool| -> u64 {
379 weight.iter().zip(&gone).filter(|&(_, &away)| away == side).map(|(w, _)| *w).sum()
380 };
381 let whole = u64::from(Probability::SCALE);
382 let mut parts = vec![0u32; succs.len()];
383 let who = if total(true) == 0 || total(false) == 0 {
384 hand_out(whole, &weight, &gone, total(false) == 0, &mut parts);
387 Predictor::Nothing
388 } else {
389 let budget = u64::from(
390 Probability::percent(PREDICT_NEVER_RETURNS, Quality::Guessed).complement().parts(),
391 );
392 hand_out(budget, &weight, &gone, true, &mut parts);
393 hand_out(whole - budget, &weight, &gone, false, &mut parts);
394 Predictor::NeverReturns
395 };
396
397 let split = parts.into_iter().map(|parts| Probability::new(parts, Quality::Guessed)).collect();
398 (split, who)
399}
400
401fn hand_out(budget: u64, weight: &[u64], gone: &[bool], side: bool, parts: &mut [u32]) {
408 let total: u64 =
409 weight.iter().zip(gone).filter(|&(_, &away)| away == side).map(|(w, _)| *w).sum();
410 if total == 0 || budget == 0 {
411 return;
412 }
413 let mut spent = 0;
414 let mut first = None;
415 for (at, &w) in weight.iter().enumerate() {
416 if gone[at] != side {
417 continue;
418 }
419 let share = budget * w / total;
420 parts[at] = u32::try_from(share).unwrap_or(Probability::SCALE);
421 spent += share;
422 if first.is_none() {
423 first = Some(at);
424 }
425 }
426 if let Some(at) = first {
427 parts[at] += u32::try_from(budget - spent).unwrap_or(0);
428 }
429}
430
431fn claimed(func: &Func, term: Inst) -> Option<Probability> {
443 let at = func.target_list(term).iter().next()?;
444 let parts = func[at].hint.taken()?;
445 Some(Probability::new(parts, Quality::Guessed))
446}
447
448fn hinted(func: &Func, succs: &[Block], term: Inst) -> Option<Vec<Probability>> {
455 let mut parts = vec![0u32; succs.len()];
456 for call in func.successors(term) {
457 let taken = call.hint.taken()?;
458 let at = succs.iter().position(|&block| block == call.block)?;
459 parts[at] += taken;
460 }
461 if parts.iter().sum::<u32>() != Probability::SCALE {
462 return None;
463 }
464 Some(parts.into_iter().map(|parts| Probability::new(parts, Quality::Guessed)).collect())
465}
466
467fn pointer_null(func: &Func, cond: Value) -> Option<Probability> {
469 let Def::Result { inst, .. } = func[cond].def else { return None };
470 let data = &func[inst];
471 if data.opcode != Opcode::ICmp {
472 return None;
473 }
474 let Extra::IntPred(pred) = data.extra else { return None };
475 let args = &func[data.args];
476 let lhs = *args.first()?;
477 let rhs = *args.get(1)?;
478 if is_null(func, lhs) == is_null(func, rhs) {
481 return None;
482 }
483 match pred {
484 IntPred::Eq => Some(toward(false, PREDICT_POINTER_NOT_NULL)),
485 IntPred::Ne => Some(toward(true, PREDICT_POINTER_NOT_NULL)),
486 _ => None,
487 }
488}
489
490fn is_null(func: &Func, value: Value) -> bool {
496 if !func[value].ty.is_ptr() {
497 return false;
498 }
499 let Def::Result { inst, .. } = func[value].def else { return false };
500 if func[inst].opcode != Opcode::IntToPtr {
501 return false;
502 }
503 let Some(&arg) = func[func[inst].args].first() else { return false };
504 match constant(func, arg) {
505 Some((value, ty)) => value.signed(ty) == 0,
506 None => false,
507 }
508}
509
510#[derive(Debug, Clone, Copy, PartialEq, Eq)]
512enum Returned {
513 Negative,
515 Null,
517 Other,
519}
520
521fn returns_constant(func: &Func, cfg: &Cfg, start: Block) -> Option<Returned> {
528 let mut at = start;
529 for _ in 0..PREDICT_RETURN_BLOCKS {
530 let term = func.terminator(at)?;
531 if func[term].opcode == Opcode::Return {
532 let &value = func[func[term].args].first()?;
533 if is_null(func, value) {
534 return Some(Returned::Null);
535 }
536 let (value, ty) = constant(func, value)?;
537 return Some(if value.signed(ty) < 0 { Returned::Negative } else { Returned::Other });
538 }
539 match cfg.successors(at) {
540 [only] => at = *only,
541 _ => return None,
542 }
543 }
544 None
545}
546
547fn never_comes_back(func: &Func, callees: &Callees, returns: &[bool], block: Block) -> bool {
553 !returns[block.index()] || calls_named(func, block, |name| callees.never_returns(name))
554}
555
556fn calls_named(func: &Func, block: Block, mut ok: impl FnMut(Symbol) -> bool) -> bool {
560 func.insts(block).any(|inst| {
561 let data = &func[inst];
562 if !matches!(data.opcode, Opcode::Call | Opcode::TailCall) {
563 return false;
564 }
565 let Extra::Call(at) = data.extra else { return false };
566 match func[at].callee {
567 Some(name) => ok(name),
568 None => false,
569 }
570 })
571}
572
573fn has_call(func: &Func, block: Block) -> bool {
575 func.insts(block).any(|inst| {
576 matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall | Opcode::CallIndirect)
577 })
578}
579
580fn enters_loop(cfg: &Cfg, loops: &Loops, from: Block, at: Block) -> bool {
585 if heads_a_loop(loops, from, at) {
586 return true;
587 }
588 match cfg.successors(at) {
589 [only] => heads_a_loop(loops, from, *only),
590 _ => false,
591 }
592}
593
594fn heads_a_loop(loops: &Loops, from: Block, at: Block) -> bool {
596 let Some(id) = loops.innermost(at) else { return false };
597 loops.header(id) == at && !loops.contains(id, from)
598}
599
600fn goes_round_again(loops: &Loops, from: Block, at: Block) -> bool {
602 match loops.innermost(from) {
603 Some(id) => loops.header(id) == at,
604 None => false,
605 }
606}
607
608fn returning(func: &Func, cfg: &Cfg) -> Vec<bool> {
616 let mut yes = vec![false; cfg.capacity()];
617 let mut stack = Vec::new();
618 for block in func.blocks() {
619 let Some(term) = func.terminator(block) else { continue };
620 if matches!(func[term].opcode, Opcode::Return | Opcode::TailCall) {
621 yes[block.index()] = true;
622 stack.push(block);
623 }
624 }
625 while let Some(block) = stack.pop() {
626 for &pred in cfg.predecessors(block) {
627 if !yes[pred.index()] {
628 yes[pred.index()] = true;
629 stack.push(pred);
630 }
631 }
632 }
633 yes
634}
635
636#[cfg(test)]
637mod tests {
638 use rucc_base::Interner;
639 use rucc_ir::{
640 AttrSet, Attrs, Block, BlockCall, Builder, Func, Hint, IntPred, Opcode, Signature, Type,
641 };
642
643 use super::{Callees, Predictions, Predictor};
644 use crate::cfg::Cfg;
645 use crate::dom::Dominators;
646 use crate::loops::Loops;
647 use crate::profile::{Probability, Quality};
648
649 fn shape(func: &Func) -> (Cfg, Loops) {
651 let cfg = Cfg::new(func);
652 let doms = Dominators::new(&cfg);
653 let loops = Loops::new(&cfg, &doms);
654 (cfg, loops)
655 }
656
657 fn predict(func: &Func) -> (Predictions, Cfg) {
659 let (cfg, loops) = shape(func);
660 let seen = Predictions::of(func, &cfg, &loops, &Callees::nothing());
661 (seen, cfg)
662 }
663
664 fn blank(blocks: usize) -> (Interner, Func, Vec<Block>) {
666 let mut names = Interner::new();
667 let mut func = Func::new(names.intern("f"), Signature::new());
668 let list = (0..blocks).map(|_| func.create_block()).collect();
669 (names, func, list)
670 }
671
672 #[test]
673 fn a_block_with_one_way_out_takes_it_and_that_is_not_a_guess() {
674 let (_, mut func, at) = blank(2);
675 Builder::new(&mut func, at[0]).jump(at[1], &[]);
676 let mut build = Builder::new(&mut func, at[1]);
677 let zero = build.iconst(Type::int(32), 0);
678 build.ret(&[zero]);
679
680 let (seen, _) = predict(&func);
681 assert_eq!(seen.edges(at[0]).len(), 1);
682 assert_eq!(seen.taken(at[0], 0), Probability::always());
683 assert_eq!(seen.taken(at[0], 0).quality(), Quality::Precise);
684 assert!(seen.edges(at[1]).is_empty());
686 assert_eq!(seen.taken(at[1], 0), Probability::never());
687 }
688
689 #[test]
690 fn the_arm_that_does_not_come_back_is_the_one_not_taken() {
691 let (_, mut func, at) = blank(3);
694 let mut build = Builder::new(&mut func, at[0]);
695 let cond = build.iconst(Type::int(1), 1);
696 build.br_if(cond, at[1], &[], at[2], &[]);
697 Builder::new(&mut func, at[1]).unreachable();
698 let mut build = Builder::new(&mut func, at[2]);
699 let zero = build.iconst(Type::int(32), 0);
700 build.ret(&[zero]);
701
702 let (seen, _) = predict(&func);
703 assert_eq!(seen.by(at[0]), Predictor::NeverReturns);
704 assert_eq!(seen.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
705 assert_eq!(seen.taken(at[0], 1), Probability::percent(99, Quality::Guessed));
706 }
707
708 #[test]
709 fn the_arm_that_calls_a_noreturn_function_is_the_one_not_taken() {
710 let (mut names, mut func, at) = blank(4);
713 let abort = names.intern("abort");
714 let sig = func.add_signature(Signature::new());
715 let mut build = Builder::new(&mut func, at[0]);
716 let cond = build.iconst(Type::int(1), 1);
717 build.br_if(cond, at[1], &[], at[2], &[]);
718 let mut build = Builder::new(&mut func, at[1]);
719 build.call(abort, sig, &[]);
720 build.jump(at[3], &[]);
721 Builder::new(&mut func, at[2]).jump(at[3], &[]);
722 let mut build = Builder::new(&mut func, at[3]);
723 let zero = build.iconst(Type::int(32), 0);
724 build.ret(&[zero]);
725
726 let mut callees = Callees::nothing();
727 callees.record(abort, Attrs { set: AttrSet::NORETURN, ..Attrs::NONE });
728 let (cfg, loops) = shape(&func);
729
730 let told = Predictions::of(&func, &cfg, &loops, &callees);
731 assert_eq!(told.by(at[0]), Predictor::NeverReturns);
732 assert_eq!(told.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
733
734 let (guessed, _) = predict(&func);
737 assert_eq!(guessed.by(at[0]), Predictor::CallNotTaken);
738 }
739
740 #[test]
741 fn the_arm_that_calls_a_cold_function_is_the_one_not_taken() {
742 let (mut names, mut func, at) = blank(4);
743 let report = names.intern("report");
744 let sig = func.add_signature(Signature::new());
745 let mut build = Builder::new(&mut func, at[0]);
746 let cond = build.iconst(Type::int(1), 1);
747 build.br_if(cond, at[1], &[], at[2], &[]);
748 let mut build = Builder::new(&mut func, at[1]);
749 build.call(report, sig, &[]);
750 build.jump(at[3], &[]);
751 Builder::new(&mut func, at[2]).jump(at[3], &[]);
752 let mut build = Builder::new(&mut func, at[3]);
753 let zero = build.iconst(Type::int(32), 0);
754 build.ret(&[zero]);
755
756 let mut callees = Callees::nothing();
757 callees.record(report, Attrs { set: AttrSet::COLD, ..Attrs::NONE });
758 let (cfg, loops) = shape(&func);
759 let told = Predictions::of(&func, &cfg, &loops, &callees);
760
761 assert_eq!(told.by(at[0]), Predictor::ColdCall);
765 assert_eq!(told.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
766 }
767
768 fn loop_shape() -> (Func, Vec<Block>) {
770 let (_, mut func, at) = blank(4);
771 Builder::new(&mut func, at[0]).jump(at[1], &[]);
772 let mut build = Builder::new(&mut func, at[1]);
773 let cond = build.iconst(Type::int(1), 1);
774 build.br_if(cond, at[2], &[], at[3], &[]);
775 Builder::new(&mut func, at[2]).jump(at[1], &[]);
776 let mut build = Builder::new(&mut func, at[3]);
777 let zero = build.iconst(Type::int(32), 0);
778 build.ret(&[zero]);
779 (func, at)
780 }
781
782 #[test]
783 fn a_loop_exit_is_the_edge_not_taken() {
784 let (func, at) = loop_shape();
785 let (seen, _) = predict(&func);
786 assert_eq!(seen.by(at[1]), Predictor::LoopExit);
787 assert_eq!(seen.taken(at[1], 0), Probability::percent(89, Quality::Guessed));
789 assert_eq!(seen.taken(at[1], 1), Probability::percent(89, Quality::Guessed).complement());
790 }
791
792 #[test]
793 fn a_loop_guard_is_taken_more_often_than_not() {
794 let (_, mut func, at) = blank(6);
797 let mut build = Builder::new(&mut func, at[0]);
798 let cond = build.iconst(Type::int(1), 1);
799 build.br_if(cond, at[1], &[], at[2], &[]);
800 Builder::new(&mut func, at[1]).jump(at[3], &[]);
801 Builder::new(&mut func, at[2]).jump(at[5], &[]);
802 let mut build = Builder::new(&mut func, at[3]);
803 let test = build.iconst(Type::int(1), 1);
804 build.br_if(test, at[4], &[], at[5], &[]);
805 Builder::new(&mut func, at[4]).jump(at[3], &[]);
806 let mut build = Builder::new(&mut func, at[5]);
807 let zero = build.iconst(Type::int(32), 0);
808 build.ret(&[zero]);
809
810 let (seen, _) = predict(&func);
811 assert_eq!(seen.by(at[0]), Predictor::LoopGuard);
812 assert_eq!(seen.taken(at[0], 0), Probability::percent(73, Quality::Guessed));
813 }
814
815 #[test]
816 fn a_continue_goes_round_again_more_often_than_it_falls_through() {
817 let (_, mut func, at) = blank(5);
818 Builder::new(&mut func, at[0]).jump(at[1], &[]);
819 let mut build = Builder::new(&mut func, at[1]);
820 let cond = build.iconst(Type::int(1), 1);
821 build.br_if(cond, at[2], &[], at[3], &[]);
822 let mut build = Builder::new(&mut func, at[2]);
823 let again = build.iconst(Type::int(1), 1);
824 build.br_if(again, at[1], &[], at[4], &[]);
825 Builder::new(&mut func, at[4]).jump(at[1], &[]);
826 let mut build = Builder::new(&mut func, at[3]);
827 let zero = build.iconst(Type::int(32), 0);
828 build.ret(&[zero]);
829
830 let (seen, _) = predict(&func);
831 assert_eq!(seen.by(at[2]), Predictor::Continue);
832 assert_eq!(seen.taken(at[2], 0), Probability::percent(67, Quality::Guessed));
833 }
834
835 #[test]
836 fn a_pointer_tested_against_null_is_predicted_not_null() {
837 let (_, mut func, at) = blank(3);
838 let mut build = Builder::new(&mut func, at[0]);
839 let seven = build.iconst(Type::int(64), 7);
840 let some = build.unary(Opcode::IntToPtr, seven, Type::PTR);
841 let zero = build.iconst(Type::int(64), 0);
842 let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
843 let cond = build.icmp(IntPred::Eq, some, null);
844 build.br_if(cond, at[1], &[], at[2], &[]);
845 for block in [at[1], at[2]] {
846 let mut build = Builder::new(&mut func, block);
847 let zero = build.iconst(Type::int(32), 0);
848 build.ret(&[zero]);
849 }
850
851 let (seen, _) = predict(&func);
852 assert_eq!(seen.by(at[0]), Predictor::PointerNotNull);
853 assert_eq!(seen.taken(at[0], 0), Probability::percent(70, Quality::Guessed).complement());
855 }
856
857 #[test]
858 fn an_arm_that_returns_a_negative_number_is_the_one_not_taken() {
859 let (_, mut func, at) = blank(3);
860 let mut build = Builder::new(&mut func, at[0]);
861 let cond = build.iconst(Type::int(1), 1);
862 build.br_if(cond, at[1], &[], at[2], &[]);
863 let mut build = Builder::new(&mut func, at[1]);
864 let bad = build.iconst(Type::int(32), -1);
865 build.ret(&[bad]);
866 let mut build = Builder::new(&mut func, at[2]);
867 let good = build.iconst(Type::int(32), 0);
868 build.ret(&[good]);
869
870 let (seen, _) = predict(&func);
871 assert_eq!(seen.by(at[0]), Predictor::NegativeReturn);
872 assert_eq!(seen.taken(at[0], 0), Probability::percent(98, Quality::Guessed).complement());
873 }
874
875 #[test]
876 fn an_arm_that_returns_null_is_the_one_not_taken_and_by_a_smaller_margin() {
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 let mut build = Builder::new(&mut func, at[1]);
882 let zero = build.iconst(Type::int(64), 0);
883 let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
884 build.ret(&[null]);
885 let mut build = Builder::new(&mut func, at[2]);
886 let seven = build.iconst(Type::int(64), 7);
887 let some = build.unary(Opcode::IntToPtr, seven, Type::PTR);
888 build.ret(&[some]);
889
890 let (seen, _) = predict(&func);
891 assert_eq!(seen.by(at[0]), Predictor::NullReturn);
892 assert_eq!(seen.taken(at[0], 0), Probability::percent(71, Quality::Guessed).complement());
893 assert!(Predictor::NullReturn.hit_rate() < Predictor::NegativeReturn.hit_rate());
896 }
897
898 #[test]
899 fn nothing_to_go_on_is_an_even_split_that_says_it_is_a_guess() {
900 let (_, mut func, at) = blank(3);
901 let mut build = Builder::new(&mut func, at[0]);
902 let cond = build.iconst(Type::int(1), 1);
903 build.br_if(cond, at[1], &[], at[2], &[]);
904 for block in [at[1], at[2]] {
905 let mut build = Builder::new(&mut func, block);
906 let zero = build.iconst(Type::int(32), 0);
907 build.ret(&[zero]);
908 }
909
910 let (seen, _) = predict(&func);
911 assert_eq!(seen.by(at[0]), Predictor::Nothing);
912 assert_eq!(seen.taken(at[0], 0), Probability::even());
913 assert_eq!(seen.taken(at[0], 0).quality(), Quality::Guessed);
914 assert!(!seen.taken(at[0], 0).is_predictable());
915 }
916
917 fn hinted(func: &mut Func, block: Block, parts: u32) {
920 let term = func.terminator(block).expect("a branch");
921 let hint = Hint::parts(parts);
922 for (at, hint) in func.target_list(term).iter().zip([hint, hint.complement()]) {
923 let call = func[at];
924 func.set_block_call(at, BlockCall { hint, ..call });
925 }
926 }
927
928 #[test]
929 fn a_builtin_expect_wins_over_every_predictor_after_it() {
930 let (_, mut func, at) = blank(3);
934 let mut build = Builder::new(&mut func, at[0]);
935 let cond = build.iconst(Type::int(1), 1);
936 build.br_if(cond, at[1], &[], at[2], &[]);
937 Builder::new(&mut func, at[1]).unreachable();
938 let mut build = Builder::new(&mut func, at[2]);
939 let zero = build.iconst(Type::int(32), 0);
940 build.ret(&[zero]);
941 hinted(&mut func, at[0], 9_000);
942
943 let (seen, _) = predict(&func);
944 assert_eq!(seen.by(at[0]), Predictor::Expect);
945 assert_eq!(seen.taken(at[0], 0), Probability::percent(90, Quality::Guessed));
946 }
947
948 #[test]
949 fn a_builtin_expect_of_zero_names_the_other_arm() {
950 let (_, mut func, at) = blank(3);
951 let mut build = Builder::new(&mut func, at[0]);
952 let cond = build.iconst(Type::int(1), 1);
953 build.br_if(cond, at[1], &[], at[2], &[]);
954 for block in [at[1], at[2]] {
955 let mut build = Builder::new(&mut func, block);
956 let zero = build.iconst(Type::int(32), 0);
957 build.ret(&[zero]);
958 }
959 hinted(&mut func, at[0], 1_000);
960
961 let (seen, _) = predict(&func);
962 assert_eq!(seen.by(at[0]), Predictor::Expect);
963 assert_eq!(seen.taken(at[0], 0), Probability::percent(90, Quality::Guessed).complement());
964 }
965
966 fn switch_shape() -> (Func, Vec<Block>) {
968 let (_, mut func, at) = blank(5);
969 let mut build = Builder::new(&mut func, at[0]);
970 let value = build.iconst(Type::int(32), 0);
971 build.switch(value, at[1], &[(0, at[2]), (1, at[3]), (2, at[4]), (3, at[4])]);
972 Builder::new(&mut func, at[2]).unreachable();
973 for block in [at[1], at[3], at[4]] {
974 let mut build = Builder::new(&mut func, block);
975 let zero = build.iconst(Type::int(32), 0);
976 build.ret(&[zero]);
977 }
978 (func, at)
979 }
980
981 fn hint_switch(func: &mut Func, block: Block, parts: &[Option<u32>]) {
983 let term = func.terminator(block).expect("the switch");
984 for (at, &parts) in func.target_list(term).iter().zip(parts) {
985 let call = func[at];
986 let hint = parts.map_or(Hint::NONE, Hint::parts);
987 func.set_block_call(at, BlockCall { hint, ..call });
988 }
989 }
990
991 #[test]
992 fn a_switch_whose_arms_all_carry_a_hint_is_split_the_way_they_say() {
993 let (mut func, at) = switch_shape();
994 let parts = [Some(1_000), Some(1_000), Some(6_000), Some(1_000), Some(1_000)];
995 hint_switch(&mut func, at[0], &parts);
996 let (seen, cfg) = predict(&func);
997 let succs = cfg.successors(at[0]);
998 let edge = |block: Block| succs.iter().position(|&one| one == block).expect("an edge");
999
1000 assert_eq!(seen.by(at[0]), Predictor::Expect);
1001 assert_eq!(seen.taken(at[0], edge(at[3])).parts(), 6_000);
1002 assert_eq!(seen.taken(at[0], edge(at[4])).parts(), 2_000);
1004 assert_eq!(seen.taken(at[0], edge(at[2])).parts(), 1_000);
1006 }
1007
1008 #[test]
1009 fn a_switch_with_a_hint_on_only_some_arms_is_split_as_if_it_had_none() {
1010 let (mut func, at) = switch_shape();
1011 hint_switch(&mut func, at[0], &[None, None, Some(9_000), None, None]);
1012 let (seen, _) = predict(&func);
1013 assert_eq!(seen.by(at[0]), Predictor::NeverReturns);
1014 }
1015
1016 #[test]
1017 fn a_switch_arm_that_aborts_leaves_the_rest_to_share_what_is_left() {
1018 let (func, at) = switch_shape();
1019 let (seen, cfg) = predict(&func);
1020 let succs = cfg.successors(at[0]);
1021 let aborts = succs.iter().position(|&block| block == at[2]).expect("the arm is an edge");
1022 let shared = succs.iter().position(|&block| block == at[4]).expect("the arm is an edge");
1023 let alone = succs.iter().position(|&block| block == at[3]).expect("the arm is an edge");
1024
1025 assert_eq!(seen.by(at[0]), Predictor::NeverReturns);
1026 assert_eq!(
1028 seen.taken(at[0], aborts),
1029 Probability::percent(99, Quality::Guessed).complement()
1030 );
1031 assert_eq!(seen.taken(at[0], shared).parts(), 2 * seen.taken(at[0], alone).parts());
1033 }
1034
1035 #[test]
1036 fn the_edges_out_of_every_block_add_up_to_certainty() {
1037 let (guarded, _) = {
1040 let (_, mut func, at) = blank(3);
1041 let mut build = Builder::new(&mut func, at[0]);
1042 let cond = build.iconst(Type::int(1), 1);
1043 build.br_if(cond, at[1], &[], at[2], &[]);
1044 for block in [at[1], at[2]] {
1045 let mut build = Builder::new(&mut func, block);
1046 let zero = build.iconst(Type::int(32), 0);
1047 build.ret(&[zero]);
1048 }
1049 (func, at)
1050 };
1051 let (looped, _) = loop_shape();
1052 let (switched, _) = switch_shape();
1053
1054 for func in [guarded, looped, switched] {
1055 let (seen, cfg) = predict(&func);
1056 for block in func.blocks() {
1057 let edges = seen.edges(block);
1058 if edges.is_empty() {
1059 continue;
1060 }
1061 assert_eq!(edges.len(), cfg.successors(block).len());
1062 let total: u32 = edges.iter().map(|edge| edge.parts()).sum();
1063 assert_eq!(total, Probability::SCALE, "block {block:?} does not add up");
1064 }
1065 }
1066 }
1067
1068 #[test]
1069 fn the_ten_are_the_ten_the_document_named_and_they_are_asked_in_its_order() {
1070 assert_eq!(Predictor::ORDER.len(), 10);
1071 assert!(!Predictor::ORDER.contains(&Predictor::Nothing));
1072 let mut sorted = Predictor::ORDER;
1073 sorted.sort_unstable();
1074 assert_eq!(sorted, Predictor::ORDER, "the enum order is the order they are asked in");
1075 for one in Predictor::ORDER {
1076 assert!(one.hit_rate() > Predictor::Nothing.hit_rate(), "{one} predicts nothing");
1077 assert!(!one.as_str().is_empty());
1078 }
1079 }
1080}