1use std::collections::HashMap;
41
42use rucc_cost::heuristics::PREDICT_EXPECT;
43use rucc_ir::{Block, Def, Extra, Func, Hint, Inst, IntPred, Opcode, Value};
44
45use crate::fold::constant;
46use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats, uses};
47
48const PLACED: &str = "branch weight written from a __builtin_expect on its condition";
50
51const NO_BRANCH: &str = "__builtin_expect dropped, no branch in this function is on its value";
53
54const NO_FUEL: &str = "__builtin_expect kept, the pass ran out of fuel";
56
57#[derive(Debug)]
59pub struct Expect;
60
61impl Pass for Expect {
62 fn name(&self) -> &'static str {
63 "expect"
64 }
65
66 fn describe(&self) -> &'static str {
67 "what __builtin_expect said moves onto the arms of the branch it was said about"
68 }
69
70 fn preserves(&self) -> Preserved {
71 Preserved::ALL.without(Analysis::Liveness).without(Analysis::Frequencies)
75 }
76
77 fn required(&self) -> bool {
78 true
82 }
83
84 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
85 let mut stats = Stats::new();
86 let mut hints: Vec<Inst> = Vec::new();
87 for block in func.blocks().collect::<Vec<Block>>() {
88 for inst in func.insts(block) {
89 if func[inst].opcode == Opcode::Expect {
90 hints.push(inst);
91 }
92 }
93 }
94 if hints.is_empty() {
97 return stats;
98 }
99
100 let mut placed = 0;
101 for block in func.blocks().collect::<Vec<Block>>() {
102 let Some(term) = func.terminator(block) else { continue };
103 if func[term].opcode != Opcode::BrIf {
104 continue;
105 }
106 let Some(&cond) = func[func[term].args].first() else { continue };
107 let Some((inst, sense)) = through(func, cond) else { continue };
108 let Some(parts) = claim(func, inst, sense) else { continue };
109 if !fuel.take() {
110 stats.missed(NO_FUEL);
111 break;
112 }
113 write(func, term, parts);
114 stats.optimized(PLACED);
115 placed += 1;
116 }
117
118 let mut forward: HashMap<Value, Value> = HashMap::new();
122 for &inst in &hints {
123 let args = &func[func[inst].args];
124 let (Some(&result), Some(&value)) = (func[inst].first_result.as_ref(), args.first())
125 else {
126 continue;
127 };
128 forward.insert(result, value);
129 }
130 uses::substitute(func, &forward);
131 for &inst in &hints {
132 func.remove_inst(inst);
133 }
134 for _ in placed..hints.len() {
135 stats.note(NO_BRANCH);
136 }
137 stats
138 }
139}
140
141fn through(func: &Func, cond: Value) -> Option<(Inst, bool)> {
150 let mut value = cond;
151 let mut sense = true;
152 loop {
155 let Def::Result { inst, .. } = func[value].def else { return None };
156 let data = &func[inst];
157 match data.opcode {
158 Opcode::Expect => return Some((inst, sense)),
159 Opcode::ZExt | Opcode::SExt => value = *func[data.args].first()?,
161 Opcode::ICmp => {
162 let Extra::IntPred(pred) = data.extra else { return None };
163 let args = &func[data.args];
164 let lhs = *args.first()?;
165 let rhs = *args.get(1)?;
166 if literal(func, rhs)? != 0 {
167 return None;
168 }
169 match pred {
170 IntPred::Ne => {}
171 IntPred::Eq => sense = !sense,
172 _ => return None,
173 }
174 value = lhs;
175 }
176 _ => return None,
177 }
178 }
179}
180
181fn claim(func: &Func, inst: Inst, sense: bool) -> Option<u32> {
189 let args = &func[func[inst].args];
190 let value = literal(func, *args.get(1)?)?;
191 let parts = match args.get(2) {
192 Some(&given) => u32::try_from(literal(func, given)?).ok()?.min(Hint::SCALE),
193 None => PREDICT_EXPECT * Hint::SCALE / 100,
194 };
195 let met = (value != 0) == sense;
196 Some(if met { parts } else { Hint::SCALE - parts })
197}
198
199fn literal(func: &Func, value: Value) -> Option<i128> {
210 let mut value = value;
211 loop {
212 if let Some((bits, ty)) = constant(func, value) {
213 return Some(bits.signed(ty));
214 }
215 let Def::Result { inst, .. } = func[value].def else { return None };
216 let data = &func[inst];
217 match data.opcode {
218 Opcode::ZExt | Opcode::SExt => value = *func[data.args].first()?,
219 _ => return None,
220 }
221 }
222}
223
224fn write(func: &mut Func, term: Inst, parts: u32) {
226 let hint = Hint::parts(parts);
227 for (at, hint) in func.target_list(term).iter().zip([hint, hint.complement()]) {
228 let call = func[at];
229 func.set_block_call(at, rucc_ir::BlockCall { hint, ..call });
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use rucc_base::Interner;
236 use rucc_ir::{Builder, InstData, Signature, Type};
237
238 use super::*;
239
240 fn blank(blocks: usize) -> (Interner, Func, Vec<Block>) {
242 let mut names = Interner::new();
243 let mut func = Func::new(names.intern("f"), Signature::new());
244 let list = (0..blocks).map(|_| func.create_block()).collect();
245 (names, func, list)
246 }
247
248 fn shaped(hint: i128, parts: Option<i128>) -> (Interner, Func, Vec<Block>) {
253 let (names, mut func, at) = blank(3);
254 let i64_ = Type::int(64);
255 let value = func.append_param(at[0], i64_);
256 let mut build = Builder::new(&mut func, at[0]);
257 let hint = build.iconst(i64_, hint);
258 let mut operands = vec![value, hint];
259 if let Some(parts) = parts {
260 let parts = build.iconst(i64_, parts);
261 operands.push(parts);
262 }
263 let args = build.func().push_values(&operands);
264 let wrapped = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, i64_);
265 let zero = build.iconst(i64_, 0);
266 let cond = build.icmp(IntPred::Ne, wrapped, zero);
267 build.br_if(cond, at[1], &[], at[2], &[]);
268 for block in [at[1], at[2]] {
269 let mut build = Builder::new(&mut func, block);
270 let answer = build.iconst(Type::int(32), 0);
271 build.ret(&[answer]);
272 }
273 (names, func, at)
274 }
275
276 fn arms(func: &Func, block: Block) -> Vec<Option<u32>> {
278 let term = func.terminator(block).expect("a branch");
279 func.target_list(term).iter().map(|at| func[at].hint.taken()).collect()
280 }
281
282 fn run(func: &mut Func) -> Stats {
284 Expect.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
285 }
286
287 #[test]
288 fn a_hint_of_one_names_the_arm_taken_when_the_condition_holds() {
289 let (_, mut func, at) = shaped(1, None);
290 run(&mut func);
291 assert_eq!(arms(&func, at[0]), [Some(9_000), Some(1_000)]);
292 }
293
294 #[test]
295 fn a_hint_of_zero_names_the_other_arm() {
296 let (_, mut func, at) = shaped(0, None);
297 run(&mut func);
298 assert_eq!(arms(&func, at[0]), [Some(1_000), Some(9_000)]);
299 }
300
301 #[test]
304 fn a_hint_behind_the_conversion_the_prototype_asked_for_is_still_a_hint() {
305 let (_, mut func, at) = blank(3);
306 let i64_ = Type::int(64);
307 let value = func.append_param(at[0], i64_);
308 let mut build = Builder::new(&mut func, at[0]);
309 let narrow = build.iconst(Type::int(32), 0);
310 let hint = build.unary(Opcode::SExt, narrow, i64_);
311 let args = build.func().push_values(&[value, hint]);
312 let wrapped = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, i64_);
313 let zero = build.iconst(i64_, 0);
314 let cond = build.icmp(IntPred::Ne, wrapped, zero);
315 build.br_if(cond, at[1], &[], at[2], &[]);
316 for block in [at[1], at[2]] {
317 let mut build = Builder::new(&mut func, block);
318 let answer = build.iconst(Type::int(32), 0);
319 build.ret(&[answer]);
320 }
321 run(&mut func);
322 assert_eq!(arms(&func, at[0]), [Some(1_000), Some(9_000)]);
323 }
324
325 #[test]
326 fn a_probability_the_program_wrote_is_the_one_the_branch_gets() {
327 let (_, mut func, at) = shaped(1, Some(7_500));
328 run(&mut func);
329 assert_eq!(arms(&func, at[0]), [Some(7_500), Some(2_500)]);
330 }
331
332 #[test]
335 fn a_probability_with_a_hint_of_zero_is_about_the_other_arm() {
336 let (_, mut func, at) = shaped(0, Some(7_500));
337 run(&mut func);
338 assert_eq!(arms(&func, at[0]), [Some(2_500), Some(7_500)]);
339 }
340
341 #[test]
342 fn the_instruction_goes_and_its_readers_read_what_it_was_given() {
343 let (_, mut func, at) = shaped(1, None);
344 run(&mut func);
345 let left: Vec<Inst> =
346 func.blocks().flat_map(|block| func.insts(block)).filter(is_expect(&func)).collect();
347 assert!(left.is_empty(), "the wrapper is gone");
348 let term = func.terminator(at[0]).expect("a branch");
350 let cond = *func[func[term].args].first().expect("a condition");
351 let Def::Result { inst, .. } = func[cond].def else { panic!("a comparison") };
352 let read = *func[func[inst].args].first().expect("a left hand side");
353 assert!(matches!(func[read].def, Def::Param { .. }), "it reads the parameter");
354 }
355
356 fn is_expect(func: &Func) -> impl Fn(&Inst) -> bool + use<'_> {
358 move |&inst| func[inst].opcode == Opcode::Expect
359 }
360
361 #[test]
362 fn a_function_with_no_hint_in_it_is_left_alone() {
363 let (_, mut func, at) = blank(3);
364 let i64_ = Type::int(64);
365 let value = func.append_param(at[0], i64_);
366 let mut build = Builder::new(&mut func, at[0]);
367 let zero = build.iconst(i64_, 0);
368 let cond = build.icmp(IntPred::Ne, value, zero);
369 build.br_if(cond, at[1], &[], at[2], &[]);
370 for block in [at[1], at[2]] {
371 let mut build = Builder::new(&mut func, block);
372 let answer = build.iconst(Type::int(32), 0);
373 build.ret(&[answer]);
374 }
375
376 let stats = run(&mut func);
377 assert!(!stats.changed(), "nothing to do");
378 assert_eq!(arms(&func, at[0]), [None, None]);
379 }
380
381 #[test]
384 fn a_condition_that_is_a_comparison_against_zero_the_other_way_flips_the_arms() {
385 let (_, mut func, at) = blank(3);
386 let i64_ = Type::int(64);
387 let value = func.append_param(at[0], i64_);
388 let mut build = Builder::new(&mut func, at[0]);
389 let hint = build.iconst(i64_, 1);
390 let args = build.func().push_values(&[value, hint]);
391 let wrapped = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, i64_);
392 let zero = build.iconst(i64_, 0);
393 let cond = build.icmp(IntPred::Eq, wrapped, zero);
394 build.br_if(cond, at[1], &[], at[2], &[]);
395 for block in [at[1], at[2]] {
396 let mut build = Builder::new(&mut func, block);
397 let answer = build.iconst(Type::int(32), 0);
398 build.ret(&[answer]);
399 }
400
401 run(&mut func);
402 assert_eq!(arms(&func, at[0]), [Some(1_000), Some(9_000)]);
403 }
404
405 #[test]
407 fn a_hint_on_a_value_no_branch_reads_leaves_nothing_behind() {
408 let (_, mut func, at) = blank(1);
409 let i64_ = Type::int(64);
410 let value = func.append_param(at[0], i64_);
411 let mut build = Builder::new(&mut func, at[0]);
412 let hint = build.iconst(i64_, 1);
413 let args = build.func().push_values(&[value, hint]);
414 let wrapped = build.value(InstData { args, ..InstData::new(Opcode::Expect) }, i64_);
415 build.ret(&[wrapped]);
416
417 run(&mut func);
418 let left: Vec<Inst> =
419 func.blocks().flat_map(|block| func.insts(block)).filter(is_expect(&func)).collect();
420 assert!(left.is_empty(), "the wrapper is gone");
421 let term = func.terminator(at[0]).expect("a return");
422 let answer = *func[func[term].args].first().expect("a returned value");
423 assert!(matches!(func[answer].def, Def::Param { .. }), "it returns the parameter");
424 }
425}