1use std::collections::HashMap;
42
43use rucc_cost::heuristics::PREDICT_EXPECT;
44use rucc_ir::{Block, Def, Extra, Func, Hint, Imm, Inst, IntPred, Opcode, Type, Value};
45
46use crate::fold::{constant, convert};
47use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats, uses};
48
49const PLACED: &str = "branch weight written from a __builtin_expect on its condition";
51
52const NO_BRANCH: &str = "__builtin_expect dropped, no branch in this function is on its value";
54
55const NO_FUEL: &str = "__builtin_expect kept, the pass ran out of fuel";
57
58#[derive(Debug)]
60pub struct Expect;
61
62impl Pass for Expect {
63 fn name(&self) -> &'static str {
64 "expect"
65 }
66
67 fn describe(&self) -> &'static str {
68 "what __builtin_expect said moves onto the arms of the branch it was said about"
69 }
70
71 fn preserves(&self) -> Preserved {
72 Preserved::ALL.without(Analysis::Liveness).without(Analysis::Frequencies)
76 }
77
78 fn required(&self) -> bool {
79 true
83 }
84
85 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
86 let mut stats = Stats::new();
87 let mut hints: Vec<Inst> = Vec::new();
88 for block in func.blocks().collect::<Vec<Block>>() {
89 for inst in func.insts(block) {
90 if func[inst].opcode == Opcode::Expect {
91 hints.push(inst);
92 }
93 }
94 }
95 if hints.is_empty() {
98 return stats;
99 }
100
101 let mut placed = 0;
102 for block in func.blocks().collect::<Vec<Block>>() {
103 let Some(term) = func.terminator(block) else { continue };
104 let Some(hints) = claims(func, term) else { continue };
105 if !fuel.take() {
106 stats.missed(NO_FUEL);
107 break;
108 }
109 write(func, term, &hints);
110 stats.optimized(PLACED);
111 placed += 1;
112 }
113
114 let mut forward: HashMap<Value, Value> = HashMap::new();
118 for &inst in &hints {
119 let args = &func[func[inst].args];
120 let (Some(&result), Some(&value)) = (func[inst].first_result.as_ref(), args.first())
121 else {
122 continue;
123 };
124 forward.insert(result, value);
125 }
126 uses::substitute(func, &forward);
127 for &inst in &hints {
128 func.remove_inst(inst);
129 }
130 for _ in placed..hints.len() {
131 stats.note(NO_BRANCH);
132 }
133 stats
134 }
135}
136
137fn claims(func: &Func, term: Inst) -> Option<Vec<Hint>> {
139 let &value = func[func[term].args].first()?;
140 match func[term].opcode {
141 Opcode::BrIf => {
142 let (inst, sense) = through(func, value)?;
143 let hint = Hint::parts(claim(func, inst, sense)?);
144 Some(vec![hint, hint.complement()])
145 }
146 Opcode::Switch => switched(func, term, value),
147 _ => None,
148 }
149}
150
151fn switched(func: &Func, term: Inst, value: Value) -> Option<Vec<Hint>> {
159 let Extra::Switch(info) = func[term].extra else { return None };
160 let info = func[info];
161 let cases = &func[info.cases];
162 let arms = u32::try_from(cases.len()).ok()?;
163 if arms == 0 {
164 return None;
165 }
166 let (inst, steps) = expected(func, value)?;
167 let args = &func[func[inst].args];
168 let ty = func[func[inst].first_result?].ty;
169 let mut wanted = Imm::int(literal(func, *args.get(1)?)?, ty);
170 for &(opcode, from, to) in steps.iter().rev() {
171 wanted = convert(opcode, wanted, from, to);
172 }
173 let ty = func[value].ty;
174 let hot =
175 cases.iter().position(|case| case.signed(ty) == wanted.signed(ty)).map_or(0, |at| at + 1);
176 let parts = probability(func, args.get(2))?;
177 let rest = (Hint::SCALE - parts) / arms;
178 let most = Hint::SCALE - rest * arms;
179 let hints = (0..=cases.len()).map(|at| Hint::parts(if at == hot { most } else { rest }));
180 Some(hints.collect())
181}
182
183type Step = (Opcode, Type, Type);
186
187fn expected(func: &Func, value: Value) -> Option<(Inst, Vec<Step>)> {
196 let mut value = value;
197 let mut steps = Vec::new();
198 loop {
199 let Def::Result { inst, .. } = func[value].def else { return None };
200 let data = &func[inst];
201 match data.opcode {
202 Opcode::Expect => return Some((inst, steps)),
203 Opcode::Trunc | Opcode::ZExt | Opcode::SExt => {
204 let from = *func[data.args].first()?;
205 steps.push((data.opcode, func[from].ty, func[value].ty));
206 value = from;
207 }
208 _ => return None,
209 }
210 }
211}
212
213fn through(func: &Func, cond: Value) -> Option<(Inst, bool)> {
222 let mut value = cond;
223 let mut sense = true;
224 loop {
227 let Def::Result { inst, .. } = func[value].def else { return None };
228 let data = &func[inst];
229 match data.opcode {
230 Opcode::Expect => return Some((inst, sense)),
231 Opcode::ZExt | Opcode::SExt => value = *func[data.args].first()?,
233 Opcode::ICmp => {
234 let Extra::IntPred(pred) = data.extra else { return None };
235 let args = &func[data.args];
236 let lhs = *args.first()?;
237 let rhs = *args.get(1)?;
238 if literal(func, rhs)? != 0 {
239 return None;
240 }
241 match pred {
242 IntPred::Ne => {}
243 IntPred::Eq => sense = !sense,
244 _ => return None,
245 }
246 value = lhs;
247 }
248 _ => return None,
249 }
250 }
251}
252
253fn claim(func: &Func, inst: Inst, sense: bool) -> Option<u32> {
261 let args = &func[func[inst].args];
262 let value = literal(func, *args.get(1)?)?;
263 let parts = probability(func, args.get(2))?;
264 let met = (value != 0) == sense;
265 Some(if met { parts } else { Hint::SCALE - parts })
266}
267
268fn probability(func: &Func, given: Option<&Value>) -> Option<u32> {
271 Some(match given {
272 Some(&given) => u32::try_from(literal(func, given)?).ok()?.min(Hint::SCALE),
273 None => PREDICT_EXPECT * Hint::SCALE / 100,
274 })
275}
276
277fn literal(func: &Func, value: Value) -> Option<i128> {
288 let mut value = value;
289 loop {
290 if let Some((bits, ty)) = constant(func, value) {
291 return Some(bits.signed(ty));
292 }
293 let Def::Result { inst, .. } = func[value].def else { return None };
294 let data = &func[inst];
295 match data.opcode {
296 Opcode::ZExt | Opcode::SExt => value = *func[data.args].first()?,
297 _ => return None,
298 }
299 }
300}
301
302fn write(func: &mut Func, term: Inst, hints: &[Hint]) {
304 for (at, &hint) in func.target_list(term).iter().zip(hints) {
305 let call = func[at];
306 func.set_block_call(at, rucc_ir::BlockCall { hint, ..call });
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use rucc_base::Interner;
313 use rucc_ir::{Builder, InstData, Signature, Type};
314
315 use super::*;
316
317 fn blank(blocks: usize) -> (Interner, Func, Vec<Block>) {
319 let mut names = Interner::new();
320 let mut func = Func::new(names.intern("f"), Signature::new());
321 let list = (0..blocks).map(|_| func.create_block()).collect();
322 (names, func, list)
323 }
324
325 fn shaped(hint: i128, parts: Option<i128>) -> (Interner, Func, Vec<Block>) {
330 let (names, mut func, at) = blank(3);
331 let i64_ = Type::int(64);
332 let value = func.append_param(at[0], i64_);
333 let mut build = Builder::new(&mut func, at[0]);
334 let hint = build.iconst(i64_, hint);
335 let mut operands = vec![value, hint];
336 if let Some(parts) = parts {
337 let parts = build.iconst(i64_, parts);
338 operands.push(parts);
339 }
340 let args = build.func().push_values(&operands);
341 let wrapped = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, i64_);
342 let zero = build.iconst(i64_, 0);
343 let cond = build.icmp(IntPred::Ne, wrapped, zero);
344 build.br_if(cond, at[1], &[], at[2], &[]);
345 for block in [at[1], at[2]] {
346 let mut build = Builder::new(&mut func, block);
347 let answer = build.iconst(Type::int(32), 0);
348 build.ret(&[answer]);
349 }
350 (names, func, at)
351 }
352
353 fn arms(func: &Func, block: Block) -> Vec<Option<u32>> {
355 let term = func.terminator(block).expect("a branch");
356 func.target_list(term).iter().map(|at| func[at].hint.taken()).collect()
357 }
358
359 fn run(func: &mut Func) -> Stats {
361 Expect.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
362 }
363
364 fn switched_on(hint: i128) -> (Interner, Func, Vec<Block>) {
368 let (names, mut func, at) = blank(5);
369 let (int, long) = (Type::int(32), Type::int(64));
370 let x = func.append_param(at[0], int);
371 let mut build = Builder::new(&mut func, at[0]);
372 let widened = build.unary(Opcode::SExt, x, long);
373 let hint = build.iconst(long, hint);
374 let args = build.func().push_values(&[widened, hint]);
375 let wrapped = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, long);
376 let narrowed = build.unary(Opcode::Trunc, wrapped, int);
377 build.switch(narrowed, at[1], &[(1, at[2]), (3, at[3]), (5, at[4])]);
378 for &block in &at[1..] {
379 let mut build = Builder::new(&mut func, block);
380 let answer = build.iconst(int, 0);
381 build.ret(&[answer]);
382 }
383 (names, func, at)
384 }
385
386 #[test]
387 fn the_case_a_switch_expects_gets_the_hint_and_the_other_arms_share_the_rest() {
388 let (_, mut func, at) = switched_on(3);
389 let stats = run(&mut func);
390 assert_eq!(stats.count(crate::stats::Kind::Optimized, PLACED), 1);
391 assert_eq!(arms(&func, at[0]), [Some(333), Some(333), Some(9_001), Some(333)]);
392 assert!(!func.blocks().any(|block| func.insts(block).any(|inst| is_expect(&func)(&inst))));
393 }
394
395 #[test]
396 fn an_expected_value_no_case_has_is_the_default() {
397 let (_, mut func, at) = switched_on(4);
398 run(&mut func);
399 assert_eq!(arms(&func, at[0]), [Some(9_001), Some(333), Some(333), Some(333)]);
400 }
401
402 #[test]
405 fn an_expected_value_is_narrowed_the_way_the_operand_is() {
406 let (_, mut func, at) = switched_on((1 << 32) + 5);
407 run(&mut func);
408 assert_eq!(arms(&func, at[0]), [Some(333), Some(333), Some(333), Some(9_001)]);
409 }
410
411 #[test]
412 fn a_hint_of_one_names_the_arm_taken_when_the_condition_holds() {
413 let (_, mut func, at) = shaped(1, None);
414 run(&mut func);
415 assert_eq!(arms(&func, at[0]), [Some(9_000), Some(1_000)]);
416 }
417
418 #[test]
419 fn a_hint_of_zero_names_the_other_arm() {
420 let (_, mut func, at) = shaped(0, None);
421 run(&mut func);
422 assert_eq!(arms(&func, at[0]), [Some(1_000), Some(9_000)]);
423 }
424
425 #[test]
428 fn a_hint_behind_the_conversion_the_prototype_asked_for_is_still_a_hint() {
429 let (_, mut func, at) = blank(3);
430 let i64_ = Type::int(64);
431 let value = func.append_param(at[0], i64_);
432 let mut build = Builder::new(&mut func, at[0]);
433 let narrow = build.iconst(Type::int(32), 0);
434 let hint = build.unary(Opcode::SExt, narrow, i64_);
435 let args = build.func().push_values(&[value, hint]);
436 let wrapped = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, i64_);
437 let zero = build.iconst(i64_, 0);
438 let cond = build.icmp(IntPred::Ne, wrapped, zero);
439 build.br_if(cond, at[1], &[], at[2], &[]);
440 for block in [at[1], at[2]] {
441 let mut build = Builder::new(&mut func, block);
442 let answer = build.iconst(Type::int(32), 0);
443 build.ret(&[answer]);
444 }
445 run(&mut func);
446 assert_eq!(arms(&func, at[0]), [Some(1_000), Some(9_000)]);
447 }
448
449 #[test]
450 fn a_probability_the_program_wrote_is_the_one_the_branch_gets() {
451 let (_, mut func, at) = shaped(1, Some(7_500));
452 run(&mut func);
453 assert_eq!(arms(&func, at[0]), [Some(7_500), Some(2_500)]);
454 }
455
456 #[test]
459 fn a_probability_with_a_hint_of_zero_is_about_the_other_arm() {
460 let (_, mut func, at) = shaped(0, Some(7_500));
461 run(&mut func);
462 assert_eq!(arms(&func, at[0]), [Some(2_500), Some(7_500)]);
463 }
464
465 #[test]
466 fn the_instruction_goes_and_its_readers_read_what_it_was_given() {
467 let (_, mut func, at) = shaped(1, None);
468 run(&mut func);
469 let left: Vec<Inst> =
470 func.blocks().flat_map(|block| func.insts(block)).filter(is_expect(&func)).collect();
471 assert!(left.is_empty(), "the wrapper is gone");
472 let term = func.terminator(at[0]).expect("a branch");
474 let cond = *func[func[term].args].first().expect("a condition");
475 let Def::Result { inst, .. } = func[cond].def else { panic!("a comparison") };
476 let read = *func[func[inst].args].first().expect("a left hand side");
477 assert!(matches!(func[read].def, Def::Param { .. }), "it reads the parameter");
478 }
479
480 fn is_expect(func: &Func) -> impl Fn(&Inst) -> bool + use<'_> {
482 move |&inst| func[inst].opcode == Opcode::Expect
483 }
484
485 #[test]
486 fn a_function_with_no_hint_in_it_is_left_alone() {
487 let (_, mut func, at) = blank(3);
488 let i64_ = Type::int(64);
489 let value = func.append_param(at[0], i64_);
490 let mut build = Builder::new(&mut func, at[0]);
491 let zero = build.iconst(i64_, 0);
492 let cond = build.icmp(IntPred::Ne, value, zero);
493 build.br_if(cond, at[1], &[], at[2], &[]);
494 for block in [at[1], at[2]] {
495 let mut build = Builder::new(&mut func, block);
496 let answer = build.iconst(Type::int(32), 0);
497 build.ret(&[answer]);
498 }
499
500 let stats = run(&mut func);
501 assert!(!stats.changed(), "nothing to do");
502 assert_eq!(arms(&func, at[0]), [None, None]);
503 }
504
505 #[test]
508 fn a_condition_that_is_a_comparison_against_zero_the_other_way_flips_the_arms() {
509 let (_, mut func, at) = blank(3);
510 let i64_ = Type::int(64);
511 let value = func.append_param(at[0], i64_);
512 let mut build = Builder::new(&mut func, at[0]);
513 let hint = build.iconst(i64_, 1);
514 let args = build.func().push_values(&[value, hint]);
515 let wrapped = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, i64_);
516 let zero = build.iconst(i64_, 0);
517 let cond = build.icmp(IntPred::Eq, wrapped, zero);
518 build.br_if(cond, at[1], &[], at[2], &[]);
519 for block in [at[1], at[2]] {
520 let mut build = Builder::new(&mut func, block);
521 let answer = build.iconst(Type::int(32), 0);
522 build.ret(&[answer]);
523 }
524
525 run(&mut func);
526 assert_eq!(arms(&func, at[0]), [Some(1_000), Some(9_000)]);
527 }
528
529 #[test]
531 fn a_hint_on_a_value_no_branch_reads_leaves_nothing_behind() {
532 let (_, mut func, at) = blank(1);
533 let i64_ = Type::int(64);
534 let value = func.append_param(at[0], i64_);
535 let mut build = Builder::new(&mut func, at[0]);
536 let hint = build.iconst(i64_, 1);
537 let args = build.func().push_values(&[value, hint]);
538 let wrapped = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, i64_);
539 build.ret(&[wrapped]);
540
541 run(&mut func);
542 let left: Vec<Inst> =
543 func.blocks().flat_map(|block| func.insts(block)).filter(is_expect(&func)).collect();
544 assert!(left.is_empty(), "the wrapper is gone");
545 let term = func.terminator(at[0]).expect("a return");
546 let answer = *func[func[term].args].first().expect("a returned value");
547 assert!(matches!(func[answer].def, Def::Param { .. }), "it returns the parameter");
548 }
549}