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) = 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
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
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 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
398fn 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
428fn 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
444fn 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 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
467fn 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
489enum Returned {
490 Negative,
492 Null,
494 Other,
496}
497
498fn 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
524fn 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
533fn 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
550fn 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
557fn 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
571fn 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
577fn 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
585fn 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 assert_eq!(
963 seen.taken(at[0], aborts),
964 Probability::percent(99, Quality::Guessed).complement()
965 );
966 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 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}