1use std::collections::HashMap;
80
81use rucc_base::Symbol;
82use rucc_ir::{Block, Extra, Flags, FloatPred, Func, Inst, IntPred, Opcode, Type, Value};
83
84use crate::uses::substitute;
85use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
86
87const ADDRESS: &str = "address removed, an earlier one in the block computes the same address";
89
90const MERGED: &str = "instruction removed, an earlier one in the block computes the same thing";
92
93const NO_FUEL: &str = "duplicate instruction kept, the pass ran out of fuel";
95
96const OPERANDS: usize = 3;
98
99#[derive(Debug)]
101pub struct Number;
102
103impl Pass for Number {
104 fn name(&self) -> &'static str {
105 "number"
106 }
107
108 fn describe(&self) -> &'static str {
109 "two instructions in a block computing the same thing from the same things are one value"
110 }
111
112 fn preserves(&self) -> Preserved {
113 Preserved::ALL.without(Analysis::Liveness)
120 }
121
122 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
123 let mut stats = Stats::new();
124 let mut same: HashMap<Value, Value> = HashMap::new();
128 let mut gone: Vec<Inst> = Vec::new();
129
130 for block in func.blocks().collect::<Vec<Block>>() {
131 let mut seen: HashMap<Key, Value> = HashMap::new();
132 for inst in func.insts(block).collect::<Vec<Inst>>() {
133 let Some((key, result)) = key(func, &same, inst) else { continue };
134 let Some(&first) = seen.get(&key) else {
135 seen.insert(key, result);
136 continue;
137 };
138 if !fuel.take() {
139 stats.missed(NO_FUEL);
145 continue;
146 }
147 same.insert(result, first);
148 gone.push(inst);
149 stats.optimized(if is_address(func[inst].opcode) { ADDRESS } else { MERGED });
150 }
151 }
152
153 for inst in gone {
154 func.remove_inst(inst);
155 }
156 if !same.is_empty() {
157 substitute(func, &same);
158 }
159 stats
160 }
161}
162
163fn is_address(opcode: Opcode) -> bool {
169 matches!(opcode, Opcode::PtrAdd | Opcode::GlobalAddr)
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
177struct Key {
178 opcode: Opcode,
180 flags: Flags,
183 ty: Type,
185 tag: Tag,
187 args: [Option<Value>; OPERANDS],
189}
190
191#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
197enum Tag {
198 None,
200 Bits(u128),
203 Symbol(Symbol),
205 IntPred(IntPred),
207 FloatPred(FloatPred),
209}
210
211fn key(func: &Func, same: &HashMap<Value, Value>, inst: Inst) -> Option<(Key, Value)> {
220 let data = &func[inst];
221 if data.opcode.has_effects() || data.opcode == Opcode::MemEntry {
222 return None;
223 }
224 let mut results = data.results();
225 let (Some(result), None) = (results.next(), results.next()) else { return None };
226 let tag = match data.extra {
227 Extra::None => Tag::None,
228 Extra::Imm(at) => Tag::Bits(func[at].bits()),
229 Extra::Symbol(name) => Tag::Symbol(name),
230 Extra::IntPred(pred) => Tag::IntPred(pred),
231 Extra::FloatPred(pred) => Tag::FloatPred(pred),
232 _ => return None,
233 };
234 let operands = &func[data.args];
235 if operands.len() > OPERANDS {
236 return None;
237 }
238 let mut args = [None; OPERANDS];
239 for (slot, &arg) in args.iter_mut().zip(operands) {
240 *slot = Some(same.get(&arg).copied().unwrap_or(arg));
241 }
242 if data.opcode.is_commutative() && operands.len() == 2 {
247 args[..2].sort_unstable();
248 }
249 Some((Key { opcode: data.opcode, flags: data.flags, ty: func[result].ty, tag, args }, result))
250}
251
252#[cfg(test)]
253mod tests {
254 use rucc_ir::{
255 Block, Builder, Def, Extra, InstData, MemInfo, MemOrder, Restrict, Signature, Type,
256 };
257
258 use super::*;
259 use crate::stats::Kind;
260
261 fn blank() -> (Func, Block) {
263 let mut names = rucc_base::Interner::new();
264 let name = names.intern("f");
265 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
266 let block = func.create_block();
267 (func, block)
268 }
269
270 fn plain(align: u32) -> MemInfo {
272 MemInfo { size: 0, align, order: MemOrder::NotAtomic, tbaa: None, restrict: Restrict::NONE }
273 }
274
275 fn local(build: &mut Builder<'_>) -> Value {
277 let mem = build.func().add_mem(MemInfo { size: 32, ..plain(8) });
278 build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
279 }
280
281 fn run(func: &mut Func) -> Stats {
283 Number.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
284 }
285
286 fn count(func: &Func, opcode: Opcode) -> usize {
288 func.blocks()
289 .flat_map(|block| func.insts(block).collect::<Vec<Inst>>())
290 .filter(|&inst| func[inst].opcode == opcode)
291 .count()
292 }
293
294 fn returned(func: &Func) -> Vec<Value> {
296 let block = func.blocks().last().expect("the function has a block");
297 let inst = func.terminator(block).expect("the block has a terminator");
298 func[func[inst].args].to_vec()
299 }
300
301 fn operands(func: &Func, value: Value) -> Vec<Value> {
303 let Def::Result { inst, .. } = func[value].def else { panic!("not an instruction result") };
304 func[func[inst].args].to_vec()
305 }
306
307 #[test]
308 fn the_same_arithmetic_on_the_same_operands_twice_is_one_instruction() {
309 let (mut func, block) = blank();
310 let mut build = Builder::new(&mut func, block);
311 let left = build.iconst(Type::int(64), 3);
312 let right = build.iconst(Type::int(64), 5);
313 let first = build.binary(Opcode::Add, left, right, Flags::NONE);
314 let second = build.binary(Opcode::Add, left, right, Flags::NONE);
315 build.ret(&[first, second]);
316
317 let stats = run(&mut func);
318 assert_eq!(stats.count(Kind::Optimized, MERGED), 1);
319 assert_eq!(count(&func, Opcode::Add), 1);
320 assert_eq!(returned(&func), vec![first, first]);
321 }
322
323 #[test]
324 fn a_commutative_pair_matches_with_its_operands_the_other_way_round() {
325 let (mut func, block) = blank();
326 let mut build = Builder::new(&mut func, block);
327 let left = build.iconst(Type::int(64), 3);
328 let right = build.iconst(Type::int(64), 5);
329 let first = build.binary(Opcode::Add, left, right, Flags::NONE);
330 let second = build.binary(Opcode::Add, right, left, Flags::NONE);
331 build.ret(&[first, second]);
332
333 let stats = run(&mut func);
334 assert_eq!(stats.count(Kind::Optimized, MERGED), 1);
335 assert_eq!(returned(&func), vec![first, first]);
336 }
337
338 #[test]
339 fn a_subtraction_the_other_way_round_is_a_different_answer() {
340 let (mut func, block) = blank();
341 let mut build = Builder::new(&mut func, block);
342 let left = build.iconst(Type::int(64), 3);
343 let right = build.iconst(Type::int(64), 5);
344 let first = build.binary(Opcode::Sub, left, right, Flags::NONE);
345 let second = build.binary(Opcode::Sub, right, left, Flags::NONE);
346 build.ret(&[first, second]);
347
348 let stats = run(&mut func);
349 assert!(!stats.changed(), "three minus five is not five minus three");
350 assert_eq!(count(&func, Opcode::Sub), 2);
351 }
352
353 #[test]
354 fn two_adds_that_promise_different_things_stay_two_adds() {
355 let (mut func, block) = blank();
356 let mut build = Builder::new(&mut func, block);
357 let left = build.iconst(Type::int(64), 3);
358 let right = build.iconst(Type::int(64), 5);
359 let first = build.binary(Opcode::Add, left, right, Flags::NSW);
360 let second = build.binary(Opcode::Add, left, right, Flags::NONE);
361 build.ret(&[first, second]);
362
363 let stats = run(&mut func);
366 assert!(!stats.changed());
367 assert_eq!(count(&func, Opcode::Add), 2);
368 }
369
370 #[test]
371 fn a_chain_collapses_all_the_way_up_and_not_just_at_the_bottom() {
372 let (mut func, block) = blank();
373 let mut build = Builder::new(&mut func, block);
374 let index = build.iconst(Type::int(64), 2);
375 let scale = build.iconst(Type::int(64), 8);
376 let first = build.binary(Opcode::Mul, index, scale, Flags::NONE);
377 let second = build.binary(Opcode::Mul, index, scale, Flags::NONE);
378 let up = build.binary(Opcode::Add, first, scale, Flags::NONE);
379 let down = build.binary(Opcode::Add, second, scale, Flags::NONE);
380 build.ret(&[up, down]);
381
382 let stats = run(&mut func);
386 assert_eq!(stats.count(Kind::Optimized, MERGED), 2);
387 assert_eq!(count(&func, Opcode::Mul), 1);
388 assert_eq!(count(&func, Opcode::Add), 1);
389 assert_eq!(returned(&func), vec![up, up]);
390 }
391
392 #[test]
393 fn the_same_constant_written_twice_is_one_constant() {
394 let (mut func, block) = blank();
395 let mut build = Builder::new(&mut func, block);
396 let first = build.iconst(Type::int(64), 7);
397 let second = build.iconst(Type::int(64), 7);
398 let narrow = build.iconst(Type::int(32), 7);
399 build.ret(&[first, second, narrow]);
400
401 let stats = run(&mut func);
405 assert_eq!(stats.count(Kind::Optimized, MERGED), 1);
406 assert_eq!(count(&func, Opcode::IConst), 2);
407 assert_eq!(returned(&func), vec![first, first, narrow]);
408 }
409
410 #[test]
411 fn two_allocas_are_two_addresses_however_alike_they_look() {
412 let (mut func, block) = blank();
413 let mut build = Builder::new(&mut func, block);
414 let one = local(&mut build);
415 let two = local(&mut build);
416 build.ret(&[one, two]);
417
418 let stats = run(&mut func);
421 assert!(!stats.changed());
422 assert_eq!(count(&func, Opcode::Alloca), 2);
423 }
424
425 #[test]
426 fn two_loads_of_one_address_are_left_to_the_pass_that_knows_about_memory() {
427 let (mut func, block) = blank();
428 let mut build = Builder::new(&mut func, block);
429 let slot = local(&mut build);
430 let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
431 let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
432 build.ret(&[first, second]);
433
434 let stats = run(&mut func);
437 assert!(!stats.changed());
438 assert_eq!(count(&func, Opcode::Load), 2);
439 }
440
441 #[test]
442 fn what_one_block_computes_does_not_reach_the_next_one() {
443 let (mut func, entry) = blank();
444 let next = func.create_block();
445 let mut build = Builder::new(&mut func, entry);
446 let left = build.iconst(Type::int(64), 3);
447 let right = build.iconst(Type::int(64), 5);
448 let first = build.binary(Opcode::Add, left, right, Flags::NONE);
449 build.jump(next, &[]);
450 let mut build = Builder::new(&mut func, next);
451 let second = build.binary(Opcode::Add, left, right, Flags::NONE);
452 build.ret(&[first, second]);
453
454 let stats = run(&mut func);
457 assert!(!stats.changed());
458 assert_eq!(count(&func, Opcode::Add), 2);
459 }
460
461 #[test]
462 fn one_name_for_the_address_is_what_lets_the_load_be_forwarded() {
463 let (mut func, block) = blank();
464 let mut build = Builder::new(&mut func, block);
465 let base = local(&mut build);
466 let index = build.iconst(Type::int(64), 2);
467 let scale = build.iconst(Type::int(64), 8);
468 let wrote = build.iconst(Type::int(64), 7);
469 let to = build.binary(Opcode::Mul, index, scale, Flags::NONE);
470 let to = build.binary(Opcode::PtrAdd, base, to, Flags::NONE);
471 build.store(wrote, to, plain(8), Flags::NONE);
472 let from = build.binary(Opcode::Mul, index, scale, Flags::NONE);
473 let from = build.binary(Opcode::PtrAdd, base, from, Flags::NONE);
474 let read = build.load(Type::int(64), from, plain(8), Flags::NONE);
475 build.ret(&[read]);
476
477 let stats = run(&mut func);
482 assert_eq!(stats.count(Kind::Optimized, ADDRESS), 1);
483 assert_eq!(stats.count(Kind::Optimized, MERGED), 1);
484 assert_eq!(count(&func, Opcode::PtrAdd), 1);
485
486 let mut analyses = crate::machine::fixtures::analyses();
487 let stats = crate::load::LoadForward.run(&mut func, &mut analyses, &mut Fuel::unlimited());
488 assert!(stats.changed(), "the two addresses are one value now");
489 assert_eq!(count(&func, Opcode::Load), 0);
490 assert_eq!(returned(&func), vec![wrote]);
491 }
492
493 #[test]
494 fn an_instruction_with_three_operands_is_matched_on_all_three() {
495 let (mut func, block) = blank();
496 let mut build = Builder::new(&mut func, block);
497 let left = build.iconst(Type::int(64), 3);
498 let right = build.iconst(Type::int(64), 5);
499 let which = build.icmp(IntPred::Slt, left, right);
500 let args = build.func().push_values(&[which, left, right]);
501 let pick = InstData { args, ..InstData::new(Opcode::Select) };
502 let first = build.value(pick, Type::int(64));
503 let second = build.value(pick, Type::int(64));
504 let args = build.func().push_values(&[which, right, left]);
505 let other = InstData { args, ..InstData::new(Opcode::Select) };
506 let other = build.value(other, Type::int(64));
507 build.ret(&[first, second, other]);
508
509 let stats = run(&mut func);
510 assert_eq!(stats.count(Kind::Optimized, MERGED), 1, "the arms the other way round differ");
511 assert_eq!(count(&func, Opcode::Select), 2);
512 assert_eq!(returned(&func), vec![first, first, other]);
513 }
514
515 #[test]
516 fn a_repeated_global_address_is_counted_as_an_address() {
517 let (mut func, block) = blank();
518 let mut names = rucc_base::Interner::new();
519 let global = names.intern("g");
520 let mut build = Builder::new(&mut func, block);
521 let named = InstData { extra: Extra::Symbol(global), ..InstData::new(Opcode::GlobalAddr) };
522 let first = build.value(named, Type::PTR);
523 let second = build.value(named, Type::PTR);
524 let offset = build.iconst(Type::int(64), 8);
525 let one = build.binary(Opcode::PtrAdd, first, offset, Flags::NONE);
526 let two = build.binary(Opcode::PtrAdd, second, offset, Flags::NONE);
527 build.ret(&[one, two]);
528
529 let stats = run(&mut func);
530 assert_eq!(stats.count(Kind::Optimized, ADDRESS), 2);
531 assert_eq!(count(&func, Opcode::GlobalAddr), 1);
532 assert_eq!(operands(&func, one), vec![first, offset]);
533 }
534
535 #[test]
536 fn without_fuel_the_duplicate_stays_and_the_chance_is_still_counted() {
537 let (mut func, block) = blank();
538 let mut build = Builder::new(&mut func, block);
539 let left = build.iconst(Type::int(64), 3);
540 let right = build.iconst(Type::int(64), 5);
541 let first = build.binary(Opcode::Add, left, right, Flags::NONE);
542 let second = build.binary(Opcode::Add, left, right, Flags::NONE);
543 build.ret(&[first, second]);
544
545 let stats =
546 Number.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
547 assert!(!stats.changed());
548 assert_eq!(stats.count(Kind::Missed, NO_FUEL), 1);
549 assert_eq!(count(&func, Opcode::Add), 2);
550 }
551}