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
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 claimed(func: &Func, term: Inst) -> Option<Probability> {
440 let at = func.target_list(term).iter().next()?;
441 let parts = func[at].hint.taken()?;
442 Some(Probability::new(parts, Quality::Guessed))
443}
444
445fn pointer_null(func: &Func, cond: Value) -> Option<Probability> {
447 let Def::Result { inst, .. } = func[cond].def else { return None };
448 let data = &func[inst];
449 if data.opcode != Opcode::ICmp {
450 return None;
451 }
452 let Extra::IntPred(pred) = data.extra else { return None };
453 let args = &func[data.args];
454 let lhs = *args.first()?;
455 let rhs = *args.get(1)?;
456 if is_null(func, lhs) == is_null(func, rhs) {
459 return None;
460 }
461 match pred {
462 IntPred::Eq => Some(toward(false, PREDICT_POINTER_NOT_NULL)),
463 IntPred::Ne => Some(toward(true, PREDICT_POINTER_NOT_NULL)),
464 _ => None,
465 }
466}
467
468fn is_null(func: &Func, value: Value) -> bool {
474 if !func[value].ty.is_ptr() {
475 return false;
476 }
477 let Def::Result { inst, .. } = func[value].def else { return false };
478 if func[inst].opcode != Opcode::IntToPtr {
479 return false;
480 }
481 let Some(&arg) = func[func[inst].args].first() else { return false };
482 match constant(func, arg) {
483 Some((value, ty)) => value.signed(ty) == 0,
484 None => false,
485 }
486}
487
488#[derive(Debug, Clone, Copy, PartialEq, Eq)]
490enum Returned {
491 Negative,
493 Null,
495 Other,
497}
498
499fn returns_constant(func: &Func, cfg: &Cfg, start: Block) -> Option<Returned> {
506 let mut at = start;
507 for _ in 0..PREDICT_RETURN_BLOCKS {
508 let term = func.terminator(at)?;
509 if func[term].opcode == Opcode::Return {
510 let &value = func[func[term].args].first()?;
511 if is_null(func, value) {
512 return Some(Returned::Null);
513 }
514 let (value, ty) = constant(func, value)?;
515 return Some(if value.signed(ty) < 0 { Returned::Negative } else { Returned::Other });
516 }
517 match cfg.successors(at) {
518 [only] => at = *only,
519 _ => return None,
520 }
521 }
522 None
523}
524
525fn never_comes_back(func: &Func, callees: &Callees, returns: &[bool], block: Block) -> bool {
531 !returns[block.index()] || calls_named(func, block, |name| callees.never_returns(name))
532}
533
534fn calls_named(func: &Func, block: Block, mut ok: impl FnMut(Symbol) -> bool) -> bool {
538 func.insts(block).any(|inst| {
539 let data = &func[inst];
540 if !matches!(data.opcode, Opcode::Call | Opcode::TailCall) {
541 return false;
542 }
543 let Extra::Call(at) = data.extra else { return false };
544 match func[at].callee {
545 Some(name) => ok(name),
546 None => false,
547 }
548 })
549}
550
551fn has_call(func: &Func, block: Block) -> bool {
553 func.insts(block).any(|inst| {
554 matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall | Opcode::CallIndirect)
555 })
556}
557
558fn enters_loop(cfg: &Cfg, loops: &Loops, from: Block, at: Block) -> bool {
563 if heads_a_loop(loops, from, at) {
564 return true;
565 }
566 match cfg.successors(at) {
567 [only] => heads_a_loop(loops, from, *only),
568 _ => false,
569 }
570}
571
572fn heads_a_loop(loops: &Loops, from: Block, at: Block) -> bool {
574 let Some(id) = loops.innermost(at) else { return false };
575 loops.header(id) == at && !loops.contains(id, from)
576}
577
578fn goes_round_again(loops: &Loops, from: Block, at: Block) -> bool {
580 match loops.innermost(from) {
581 Some(id) => loops.header(id) == at,
582 None => false,
583 }
584}
585
586fn returning(func: &Func, cfg: &Cfg) -> Vec<bool> {
594 let mut yes = vec![false; cfg.capacity()];
595 let mut stack = Vec::new();
596 for block in func.blocks() {
597 let Some(term) = func.terminator(block) else { continue };
598 if matches!(func[term].opcode, Opcode::Return | Opcode::TailCall) {
599 yes[block.index()] = true;
600 stack.push(block);
601 }
602 }
603 while let Some(block) = stack.pop() {
604 for &pred in cfg.predecessors(block) {
605 if !yes[pred.index()] {
606 yes[pred.index()] = true;
607 stack.push(pred);
608 }
609 }
610 }
611 yes
612}
613
614#[cfg(test)]
615mod tests {
616 use rucc_base::Interner;
617 use rucc_ir::{
618 AttrSet, Attrs, Block, BlockCall, Builder, Func, Hint, IntPred, Opcode, Signature, Type,
619 };
620
621 use super::{Callees, Predictions, Predictor};
622 use crate::cfg::Cfg;
623 use crate::dom::Dominators;
624 use crate::loops::Loops;
625 use crate::profile::{Probability, Quality};
626
627 fn shape(func: &Func) -> (Cfg, Loops) {
629 let cfg = Cfg::new(func);
630 let doms = Dominators::new(&cfg);
631 let loops = Loops::new(&cfg, &doms);
632 (cfg, loops)
633 }
634
635 fn predict(func: &Func) -> (Predictions, Cfg) {
637 let (cfg, loops) = shape(func);
638 let seen = Predictions::of(func, &cfg, &loops, &Callees::nothing());
639 (seen, cfg)
640 }
641
642 fn blank(blocks: usize) -> (Interner, Func, Vec<Block>) {
644 let mut names = Interner::new();
645 let mut func = Func::new(names.intern("f"), Signature::new());
646 let list = (0..blocks).map(|_| func.create_block()).collect();
647 (names, func, list)
648 }
649
650 #[test]
651 fn a_block_with_one_way_out_takes_it_and_that_is_not_a_guess() {
652 let (_, mut func, at) = blank(2);
653 Builder::new(&mut func, at[0]).jump(at[1], &[]);
654 let mut build = Builder::new(&mut func, at[1]);
655 let zero = build.iconst(Type::int(32), 0);
656 build.ret(&[zero]);
657
658 let (seen, _) = predict(&func);
659 assert_eq!(seen.edges(at[0]).len(), 1);
660 assert_eq!(seen.taken(at[0], 0), Probability::always());
661 assert_eq!(seen.taken(at[0], 0).quality(), Quality::Precise);
662 assert!(seen.edges(at[1]).is_empty());
664 assert_eq!(seen.taken(at[1], 0), Probability::never());
665 }
666
667 #[test]
668 fn the_arm_that_does_not_come_back_is_the_one_not_taken() {
669 let (_, mut func, at) = blank(3);
672 let mut build = Builder::new(&mut func, at[0]);
673 let cond = build.iconst(Type::int(1), 1);
674 build.br_if(cond, at[1], &[], at[2], &[]);
675 Builder::new(&mut func, at[1]).unreachable();
676 let mut build = Builder::new(&mut func, at[2]);
677 let zero = build.iconst(Type::int(32), 0);
678 build.ret(&[zero]);
679
680 let (seen, _) = predict(&func);
681 assert_eq!(seen.by(at[0]), Predictor::NeverReturns);
682 assert_eq!(seen.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
683 assert_eq!(seen.taken(at[0], 1), Probability::percent(99, Quality::Guessed));
684 }
685
686 #[test]
687 fn the_arm_that_calls_a_noreturn_function_is_the_one_not_taken() {
688 let (mut names, mut func, at) = blank(4);
691 let abort = names.intern("abort");
692 let sig = func.add_signature(Signature::new());
693 let mut build = Builder::new(&mut func, at[0]);
694 let cond = build.iconst(Type::int(1), 1);
695 build.br_if(cond, at[1], &[], at[2], &[]);
696 let mut build = Builder::new(&mut func, at[1]);
697 build.call(abort, sig, &[]);
698 build.jump(at[3], &[]);
699 Builder::new(&mut func, at[2]).jump(at[3], &[]);
700 let mut build = Builder::new(&mut func, at[3]);
701 let zero = build.iconst(Type::int(32), 0);
702 build.ret(&[zero]);
703
704 let mut callees = Callees::nothing();
705 callees.record(abort, Attrs { set: AttrSet::NORETURN, ..Attrs::NONE });
706 let (cfg, loops) = shape(&func);
707
708 let told = Predictions::of(&func, &cfg, &loops, &callees);
709 assert_eq!(told.by(at[0]), Predictor::NeverReturns);
710 assert_eq!(told.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
711
712 let (guessed, _) = predict(&func);
715 assert_eq!(guessed.by(at[0]), Predictor::CallNotTaken);
716 }
717
718 #[test]
719 fn the_arm_that_calls_a_cold_function_is_the_one_not_taken() {
720 let (mut names, mut func, at) = blank(4);
721 let report = names.intern("report");
722 let sig = func.add_signature(Signature::new());
723 let mut build = Builder::new(&mut func, at[0]);
724 let cond = build.iconst(Type::int(1), 1);
725 build.br_if(cond, at[1], &[], at[2], &[]);
726 let mut build = Builder::new(&mut func, at[1]);
727 build.call(report, sig, &[]);
728 build.jump(at[3], &[]);
729 Builder::new(&mut func, at[2]).jump(at[3], &[]);
730 let mut build = Builder::new(&mut func, at[3]);
731 let zero = build.iconst(Type::int(32), 0);
732 build.ret(&[zero]);
733
734 let mut callees = Callees::nothing();
735 callees.record(report, Attrs { set: AttrSet::COLD, ..Attrs::NONE });
736 let (cfg, loops) = shape(&func);
737 let told = Predictions::of(&func, &cfg, &loops, &callees);
738
739 assert_eq!(told.by(at[0]), Predictor::ColdCall);
743 assert_eq!(told.taken(at[0], 0), Probability::percent(99, Quality::Guessed).complement());
744 }
745
746 fn loop_shape() -> (Func, Vec<Block>) {
748 let (_, mut func, at) = blank(4);
749 Builder::new(&mut func, at[0]).jump(at[1], &[]);
750 let mut build = Builder::new(&mut func, at[1]);
751 let cond = build.iconst(Type::int(1), 1);
752 build.br_if(cond, at[2], &[], at[3], &[]);
753 Builder::new(&mut func, at[2]).jump(at[1], &[]);
754 let mut build = Builder::new(&mut func, at[3]);
755 let zero = build.iconst(Type::int(32), 0);
756 build.ret(&[zero]);
757 (func, at)
758 }
759
760 #[test]
761 fn a_loop_exit_is_the_edge_not_taken() {
762 let (func, at) = loop_shape();
763 let (seen, _) = predict(&func);
764 assert_eq!(seen.by(at[1]), Predictor::LoopExit);
765 assert_eq!(seen.taken(at[1], 0), Probability::percent(89, Quality::Guessed));
767 assert_eq!(seen.taken(at[1], 1), Probability::percent(89, Quality::Guessed).complement());
768 }
769
770 #[test]
771 fn a_loop_guard_is_taken_more_often_than_not() {
772 let (_, mut func, at) = blank(6);
775 let mut build = Builder::new(&mut func, at[0]);
776 let cond = build.iconst(Type::int(1), 1);
777 build.br_if(cond, at[1], &[], at[2], &[]);
778 Builder::new(&mut func, at[1]).jump(at[3], &[]);
779 Builder::new(&mut func, at[2]).jump(at[5], &[]);
780 let mut build = Builder::new(&mut func, at[3]);
781 let test = build.iconst(Type::int(1), 1);
782 build.br_if(test, at[4], &[], at[5], &[]);
783 Builder::new(&mut func, at[4]).jump(at[3], &[]);
784 let mut build = Builder::new(&mut func, at[5]);
785 let zero = build.iconst(Type::int(32), 0);
786 build.ret(&[zero]);
787
788 let (seen, _) = predict(&func);
789 assert_eq!(seen.by(at[0]), Predictor::LoopGuard);
790 assert_eq!(seen.taken(at[0], 0), Probability::percent(73, Quality::Guessed));
791 }
792
793 #[test]
794 fn a_continue_goes_round_again_more_often_than_it_falls_through() {
795 let (_, mut func, at) = blank(5);
796 Builder::new(&mut func, at[0]).jump(at[1], &[]);
797 let mut build = Builder::new(&mut func, at[1]);
798 let cond = build.iconst(Type::int(1), 1);
799 build.br_if(cond, at[2], &[], at[3], &[]);
800 let mut build = Builder::new(&mut func, at[2]);
801 let again = build.iconst(Type::int(1), 1);
802 build.br_if(again, at[1], &[], at[4], &[]);
803 Builder::new(&mut func, at[4]).jump(at[1], &[]);
804 let mut build = Builder::new(&mut func, at[3]);
805 let zero = build.iconst(Type::int(32), 0);
806 build.ret(&[zero]);
807
808 let (seen, _) = predict(&func);
809 assert_eq!(seen.by(at[2]), Predictor::Continue);
810 assert_eq!(seen.taken(at[2], 0), Probability::percent(67, Quality::Guessed));
811 }
812
813 #[test]
814 fn a_pointer_tested_against_null_is_predicted_not_null() {
815 let (_, mut func, at) = blank(3);
816 let mut build = Builder::new(&mut func, at[0]);
817 let seven = build.iconst(Type::int(64), 7);
818 let some = build.unary(Opcode::IntToPtr, seven, Type::PTR);
819 let zero = build.iconst(Type::int(64), 0);
820 let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
821 let cond = build.icmp(IntPred::Eq, some, null);
822 build.br_if(cond, at[1], &[], at[2], &[]);
823 for block in [at[1], at[2]] {
824 let mut build = Builder::new(&mut func, block);
825 let zero = build.iconst(Type::int(32), 0);
826 build.ret(&[zero]);
827 }
828
829 let (seen, _) = predict(&func);
830 assert_eq!(seen.by(at[0]), Predictor::PointerNotNull);
831 assert_eq!(seen.taken(at[0], 0), Probability::percent(70, Quality::Guessed).complement());
833 }
834
835 #[test]
836 fn an_arm_that_returns_a_negative_number_is_the_one_not_taken() {
837 let (_, mut func, at) = blank(3);
838 let mut build = Builder::new(&mut func, at[0]);
839 let cond = build.iconst(Type::int(1), 1);
840 build.br_if(cond, at[1], &[], at[2], &[]);
841 let mut build = Builder::new(&mut func, at[1]);
842 let bad = build.iconst(Type::int(32), -1);
843 build.ret(&[bad]);
844 let mut build = Builder::new(&mut func, at[2]);
845 let good = build.iconst(Type::int(32), 0);
846 build.ret(&[good]);
847
848 let (seen, _) = predict(&func);
849 assert_eq!(seen.by(at[0]), Predictor::NegativeReturn);
850 assert_eq!(seen.taken(at[0], 0), Probability::percent(98, Quality::Guessed).complement());
851 }
852
853 #[test]
854 fn an_arm_that_returns_null_is_the_one_not_taken_and_by_a_smaller_margin() {
855 let (_, mut func, at) = blank(3);
856 let mut build = Builder::new(&mut func, at[0]);
857 let cond = build.iconst(Type::int(1), 1);
858 build.br_if(cond, at[1], &[], at[2], &[]);
859 let mut build = Builder::new(&mut func, at[1]);
860 let zero = build.iconst(Type::int(64), 0);
861 let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
862 build.ret(&[null]);
863 let mut build = Builder::new(&mut func, at[2]);
864 let seven = build.iconst(Type::int(64), 7);
865 let some = build.unary(Opcode::IntToPtr, seven, Type::PTR);
866 build.ret(&[some]);
867
868 let (seen, _) = predict(&func);
869 assert_eq!(seen.by(at[0]), Predictor::NullReturn);
870 assert_eq!(seen.taken(at[0], 0), Probability::percent(71, Quality::Guessed).complement());
871 assert!(Predictor::NullReturn.hit_rate() < Predictor::NegativeReturn.hit_rate());
874 }
875
876 #[test]
877 fn nothing_to_go_on_is_an_even_split_that_says_it_is_a_guess() {
878 let (_, mut func, at) = blank(3);
879 let mut build = Builder::new(&mut func, at[0]);
880 let cond = build.iconst(Type::int(1), 1);
881 build.br_if(cond, at[1], &[], at[2], &[]);
882 for block in [at[1], at[2]] {
883 let mut build = Builder::new(&mut func, block);
884 let zero = build.iconst(Type::int(32), 0);
885 build.ret(&[zero]);
886 }
887
888 let (seen, _) = predict(&func);
889 assert_eq!(seen.by(at[0]), Predictor::Nothing);
890 assert_eq!(seen.taken(at[0], 0), Probability::even());
891 assert_eq!(seen.taken(at[0], 0).quality(), Quality::Guessed);
892 assert!(!seen.taken(at[0], 0).is_predictable());
893 }
894
895 fn hinted(func: &mut Func, block: Block, parts: u32) {
898 let term = func.terminator(block).expect("a branch");
899 let hint = Hint::parts(parts);
900 for (at, hint) in func.target_list(term).iter().zip([hint, hint.complement()]) {
901 let call = func[at];
902 func.set_block_call(at, BlockCall { hint, ..call });
903 }
904 }
905
906 #[test]
907 fn a_builtin_expect_wins_over_every_predictor_after_it() {
908 let (_, mut func, at) = blank(3);
912 let mut build = Builder::new(&mut func, at[0]);
913 let cond = build.iconst(Type::int(1), 1);
914 build.br_if(cond, at[1], &[], at[2], &[]);
915 Builder::new(&mut func, at[1]).unreachable();
916 let mut build = Builder::new(&mut func, at[2]);
917 let zero = build.iconst(Type::int(32), 0);
918 build.ret(&[zero]);
919 hinted(&mut func, at[0], 9_000);
920
921 let (seen, _) = predict(&func);
922 assert_eq!(seen.by(at[0]), Predictor::Expect);
923 assert_eq!(seen.taken(at[0], 0), Probability::percent(90, Quality::Guessed));
924 }
925
926 #[test]
927 fn a_builtin_expect_of_zero_names_the_other_arm() {
928 let (_, mut func, at) = blank(3);
929 let mut build = Builder::new(&mut func, at[0]);
930 let cond = build.iconst(Type::int(1), 1);
931 build.br_if(cond, at[1], &[], at[2], &[]);
932 for block in [at[1], at[2]] {
933 let mut build = Builder::new(&mut func, block);
934 let zero = build.iconst(Type::int(32), 0);
935 build.ret(&[zero]);
936 }
937 hinted(&mut func, at[0], 1_000);
938
939 let (seen, _) = predict(&func);
940 assert_eq!(seen.by(at[0]), Predictor::Expect);
941 assert_eq!(seen.taken(at[0], 0), Probability::percent(90, Quality::Guessed).complement());
942 }
943
944 fn switch_shape() -> (Func, Vec<Block>) {
946 let (_, mut func, at) = blank(5);
947 let mut build = Builder::new(&mut func, at[0]);
948 let value = build.iconst(Type::int(32), 0);
949 build.switch(value, at[1], &[(0, at[2]), (1, at[3]), (2, at[4]), (3, at[4])]);
950 Builder::new(&mut func, at[2]).unreachable();
951 for block in [at[1], at[3], at[4]] {
952 let mut build = Builder::new(&mut func, block);
953 let zero = build.iconst(Type::int(32), 0);
954 build.ret(&[zero]);
955 }
956 (func, at)
957 }
958
959 #[test]
960 fn a_switch_arm_that_aborts_leaves_the_rest_to_share_what_is_left() {
961 let (func, at) = switch_shape();
962 let (seen, cfg) = predict(&func);
963 let succs = cfg.successors(at[0]);
964 let aborts = succs.iter().position(|&block| block == at[2]).expect("the arm is an edge");
965 let shared = succs.iter().position(|&block| block == at[4]).expect("the arm is an edge");
966 let alone = succs.iter().position(|&block| block == at[3]).expect("the arm is an edge");
967
968 assert_eq!(seen.by(at[0]), Predictor::NeverReturns);
969 assert_eq!(
971 seen.taken(at[0], aborts),
972 Probability::percent(99, Quality::Guessed).complement()
973 );
974 assert_eq!(seen.taken(at[0], shared).parts(), 2 * seen.taken(at[0], alone).parts());
976 }
977
978 #[test]
979 fn the_edges_out_of_every_block_add_up_to_certainty() {
980 let (guarded, _) = {
983 let (_, mut func, at) = blank(3);
984 let mut build = Builder::new(&mut func, at[0]);
985 let cond = build.iconst(Type::int(1), 1);
986 build.br_if(cond, at[1], &[], at[2], &[]);
987 for block in [at[1], at[2]] {
988 let mut build = Builder::new(&mut func, block);
989 let zero = build.iconst(Type::int(32), 0);
990 build.ret(&[zero]);
991 }
992 (func, at)
993 };
994 let (looped, _) = loop_shape();
995 let (switched, _) = switch_shape();
996
997 for func in [guarded, looped, switched] {
998 let (seen, cfg) = predict(&func);
999 for block in func.blocks() {
1000 let edges = seen.edges(block);
1001 if edges.is_empty() {
1002 continue;
1003 }
1004 assert_eq!(edges.len(), cfg.successors(block).len());
1005 let total: u32 = edges.iter().map(|edge| edge.parts()).sum();
1006 assert_eq!(total, Probability::SCALE, "block {block:?} does not add up");
1007 }
1008 }
1009 }
1010
1011 #[test]
1012 fn the_ten_are_the_ten_the_document_named_and_they_are_asked_in_its_order() {
1013 assert_eq!(Predictor::ORDER.len(), 10);
1014 assert!(!Predictor::ORDER.contains(&Predictor::Nothing));
1015 let mut sorted = Predictor::ORDER;
1016 sorted.sort_unstable();
1017 assert_eq!(sorted, Predictor::ORDER, "the enum order is the order they are asked in");
1018 for one in Predictor::ORDER {
1019 assert!(one.hit_rate() > Predictor::Nothing.hit_rate(), "{one} predicts nothing");
1020 assert!(!one.as_str().is_empty());
1021 }
1022 }
1023}