1use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, InstData, Opcode, Type, Value};
69
70use crate::uses::count;
71use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
72
73const NARROWED: &str = "arithmetic redone at the width the program truncates it to";
75
76const NO_FUEL: &str = "arithmetic left wide, the pass ran out of fuel";
78
79const DEPTH: u32 = 6;
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub struct Narrow;
90
91impl Pass for Narrow {
92 fn name(&self) -> &'static str {
93 "narrow"
94 }
95
96 fn describe(&self) -> &'static str {
97 "arithmetic the program truncates is redone at the width it truncates to"
98 }
99
100 fn preserves(&self) -> Preserved {
101 Preserved::ALL.without(Analysis::Liveness)
106 }
107
108 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
109 let mut stats = Stats::new();
110 let mut uses = count(func);
111 for block in func.blocks().collect::<Vec<Block>>() {
112 for inst in func.insts(block).collect::<Vec<Inst>>() {
113 let Some(redo) = truncated_arithmetic(func, inst, &uses)
114 .or_else(|| extended_comparison(func, inst))
115 else {
116 continue;
117 };
118 if !fuel.take() {
119 stats.missed(NO_FUEL);
123 continue;
124 }
125 apply(func, inst, &redo, &mut uses);
126 stats.optimized(NARROWED);
127 }
128 }
129 stats
130 }
131}
132
133struct Redo {
135 opcode: Opcode,
137 extra: Extra,
139 ty: Type,
141 lhs: Plan,
143 rhs: Plan,
145}
146
147enum Plan {
149 Already(Value),
151 Constant(i128),
153 Nested(Box<Redo>),
155}
156
157fn truncated_arithmetic(func: &Func, inst: Inst, uses: &[u32]) -> Option<Redo> {
163 let data = &func[inst];
164 if data.opcode != Opcode::Trunc {
165 return None;
166 }
167 let ty = func[data.results().next()?].ty;
168 if !narrowable(ty) {
169 return None;
170 }
171 redo(func, *func[data.args].first()?, ty, uses, DEPTH)
172}
173
174const fn narrowable(ty: Type) -> bool {
184 ty.is_int() && ty.is_scalar() && ty.bits() >= 8
185}
186
187fn redo(func: &Func, value: Value, ty: Type, uses: &[u32], depth: u32) -> Option<Redo> {
189 if depth == 0 || uses[value.index()] != 1 {
190 return None;
191 }
192 let Def::Result { inst, .. } = func[value].def else { return None };
193 let data = &func[inst];
194 if !low_bits_only(data.opcode) {
195 return None;
196 }
197 let args = &func[data.args];
198 let (&left, &right) = (args.first()?, args.get(1)?);
199 let lhs = plan(func, left, ty, uses, depth)?;
200 let rhs = match data.opcode {
203 Opcode::Shl => Plan::Constant(count_below(func, right, ty)?),
204 _ => plan(func, right, ty, uses, depth)?,
205 };
206 Some(Redo { opcode: data.opcode, extra: Extra::None, ty, lhs, rhs })
207}
208
209fn plan(func: &Func, value: Value, ty: Type, uses: &[u32], depth: u32) -> Option<Plan> {
211 if let Some(narrow) = extended(func, value, ty) {
212 return Some(Plan::Already(narrow));
213 }
214 if let Some((imm, wide)) = constant(func, value) {
215 return Some(Plan::Constant(imm.signed(wide)));
216 }
217 redo(func, value, ty, uses, depth - 1).map(|redo| Plan::Nested(Box::new(redo)))
218}
219
220const fn low_bits_only(opcode: Opcode) -> bool {
225 matches!(
226 opcode,
227 Opcode::Add
228 | Opcode::Sub
229 | Opcode::Mul
230 | Opcode::And
231 | Opcode::Or
232 | Opcode::Xor
233 | Opcode::Shl
234 )
235}
236
237fn extended_comparison(func: &Func, inst: Inst) -> Option<Redo> {
250 let data = &func[inst];
251 if data.opcode != Opcode::ICmp {
252 return None;
253 }
254 let Extra::IntPred(pred) = data.extra else { return None };
255 let args = &func[data.args];
256 let (&left, &right) = (args.first()?, args.get(1)?);
257 let (kind, ty, narrow) = widening(func, left)?;
258 if !narrowable(ty) {
259 return None;
260 }
261 if kind == Opcode::ZExt && pred.is_signed() {
262 return None;
263 }
264 let rhs = match widening(func, right) {
265 Some((same, from, other)) if same == kind && from == ty => Plan::Already(other),
266 _ => Plan::Constant(survives(func, right, kind, ty)?),
267 };
268 Some(Redo { opcode: Opcode::ICmp, extra: data.extra, ty, lhs: Plan::Already(narrow), rhs })
269}
270
271fn widening(func: &Func, value: Value) -> Option<(Opcode, Type, Value)> {
273 let Def::Result { inst, .. } = func[value].def else { return None };
274 let data = &func[inst];
275 if data.opcode != Opcode::SExt && data.opcode != Opcode::ZExt {
276 return None;
277 }
278 let narrow = *func[data.args].first()?;
279 Some((data.opcode, func[narrow].ty, narrow))
280}
281
282fn extended(func: &Func, value: Value, ty: Type) -> Option<Value> {
287 let (_, from, narrow) = widening(func, value)?;
288 (from == ty).then_some(narrow)
289}
290
291fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
293 let Def::Result { inst, .. } = func[value].def else { return None };
294 let data = &func[inst];
295 let Extra::Imm(at) = data.extra else { return None };
296 if data.opcode != Opcode::IConst {
297 return None;
298 }
299 let ty = func[value].ty;
300 ty.is_int().then(|| (func[at], ty))
301}
302
303fn count_below(func: &Func, value: Value, ty: Type) -> Option<i128> {
309 let (imm, wide) = constant(func, value)?;
310 let by = imm.signed(wide);
311 (by >= 0 && by < i128::from(ty.bits())).then_some(by)
312}
313
314fn survives(func: &Func, value: Value, kind: Opcode, ty: Type) -> Option<i128> {
320 let (imm, wide) = constant(func, value)?;
321 let k = imm.signed(wide);
322 let back = Imm::int(k, ty).signed(ty);
323 let same = if kind == Opcode::SExt { back } else { Imm::int(k, ty).unsigned() as i128 };
324 (same == k).then_some(k)
325}
326
327fn apply(func: &mut Func, inst: Inst, redo: &Redo, uses: &mut Vec<u32>) {
333 let lhs = build(func, inst, redo.ty, &redo.lhs, uses);
334 let rhs = build(func, inst, redo.ty, &redo.rhs, uses);
335 for value in func[func[inst].args].iter().copied() {
336 uses[value.index()] -= 1;
337 }
338 let args = func.push_values(&[lhs, rhs]);
339 uses[lhs.index()] += 1;
340 uses[rhs.index()] += 1;
341 let data = &mut func[inst];
342 data.opcode = redo.opcode;
343 data.flags = Flags::NONE;
347 data.args = args;
348 data.extra = redo.extra;
349}
350
351fn build(func: &mut Func, before: Inst, ty: Type, plan: &Plan, uses: &mut Vec<u32>) -> Value {
353 match plan {
354 Plan::Already(value) => *value,
355 Plan::Constant(value) => {
356 let at = func.add_imm(Imm::int(*value, ty.lane()));
357 let data = InstData { extra: Extra::Imm(at), ..InstData::new(Opcode::IConst) };
358 written(func, before, data, ty, uses)
359 }
360 Plan::Nested(redo) => {
361 let lhs = build(func, before, redo.ty, &redo.lhs, uses);
362 let rhs = build(func, before, redo.ty, &redo.rhs, uses);
363 let args = func.push_values(&[lhs, rhs]);
364 uses[lhs.index()] += 1;
365 uses[rhs.index()] += 1;
366 let data = InstData { args, extra: redo.extra, ..InstData::new(redo.opcode) };
367 written(func, before, data, redo.ty, uses)
368 }
369 }
370}
371
372fn written(func: &mut Func, before: Inst, data: InstData, ty: Type, uses: &mut Vec<u32>) -> Value {
374 let span = func.span(before);
375 let inst = func.create_inst(data, &[ty], span);
376 func.insert_before(inst, before);
377 uses.resize(func.counts().values, 0);
378 func[inst].first_result.expect("one result was asked for")
379}
380
381#[cfg(test)]
382mod tests {
383 use rucc_base::Interner;
384 use rucc_ir::{Block, Builder, Flags, Func, Inst, IntPred, Opcode, Signature, Type, Value};
385
386 use crate::narrow::Narrow;
387 use crate::{Fuel, Pass};
388
389 fn blank() -> (Func, Block) {
391 let mut names = Interner::new();
392 let name = names.intern("f");
393 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(32)]));
394 let block = func.create_block();
395 (func, block)
396 }
397
398 fn shape(func: &Func, value: Value) -> (Opcode, Vec<Type>) {
400 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
401 let data = &func[inst];
402 (data.opcode, func[data.args].iter().map(|&arg| func[arg].ty).collect())
403 }
404
405 fn left(func: &Func, block: Block) -> usize {
407 func.insts(block).count()
408 }
409
410 fn last(func: &Func, block: Block) -> Inst {
412 func.insts(block).last().expect("a block with something in it")
413 }
414
415 #[test]
416 fn a_truncated_sum_of_two_extensions_is_the_sum_at_the_narrow_width() {
417 let (mut func, block) = blank();
418 let a = func.append_param(block, Type::int(8));
419 let b = func.append_param(block, Type::int(8));
420 let mut build = Builder::new(&mut func, block);
421 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
422 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
423 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
424 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
425 build.ret(&[narrow]);
426 assert!(
427 Narrow
428 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
429 .changed()
430 );
431 assert_eq!(shape(&func, narrow), (Opcode::Add, vec![Type::int(8), Type::int(8)]));
432 assert_eq!(left(&func, block), 5);
435 }
436
437 #[test]
438 fn a_constant_operand_is_written_down_again_at_the_narrow_width() {
439 let (mut func, block) = blank();
440 let a = func.append_param(block, Type::int(8));
441 let mut build = Builder::new(&mut func, block);
442 let wide = build.unary(Opcode::SExt, a, Type::int(32));
443 let one = build.iconst(Type::int(32), 1);
444 let sum = build.binary(Opcode::Add, wide, one, Flags::NONE);
445 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
446 build.ret(&[narrow]);
447 assert!(
448 Narrow
449 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
450 .changed()
451 );
452 assert_eq!(shape(&func, narrow), (Opcode::Add, vec![Type::int(8), Type::int(8)]));
453 }
454
455 #[test]
456 fn a_chain_of_arithmetic_narrows_the_whole_way_down() {
457 let (mut func, block) = blank();
458 let a = func.append_param(block, Type::int(8));
459 let b = func.append_param(block, Type::int(8));
460 let c = func.append_param(block, Type::int(8));
461 let mut build = Builder::new(&mut func, block);
462 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
463 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
464 let wide_c = build.unary(Opcode::SExt, c, Type::int(32));
465 let inner = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
466 let outer = build.binary(Opcode::Mul, inner, wide_c, Flags::NONE);
467 let narrow = build.unary(Opcode::Trunc, outer, Type::int(8));
468 build.ret(&[narrow]);
469 assert!(
470 Narrow
471 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
472 .changed()
473 );
474 assert_eq!(shape(&func, narrow), (Opcode::Mul, vec![Type::int(8), Type::int(8)]));
477 assert_eq!(left(&func, block), 8);
478 }
479
480 #[test]
481 fn an_operation_something_else_reads_stays_wide() {
482 let (mut func, block) = blank();
483 let a = func.append_param(block, Type::int(8));
484 let b = func.append_param(block, Type::int(8));
485 let mut build = Builder::new(&mut func, block);
486 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
487 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
488 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
489 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
490 let kept = build.unary(Opcode::SExt, narrow, Type::int(32));
491 build.ret(&[sum, kept]);
492 assert!(
493 !Narrow
494 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
495 .changed()
496 );
497 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
500 }
501
502 #[test]
503 fn a_divide_stays_wide_because_the_narrow_one_can_raise() {
504 let (mut func, block) = blank();
505 let a = func.append_param(block, Type::int(8));
506 let b = func.append_param(block, Type::int(8));
507 let mut build = Builder::new(&mut func, block);
508 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
509 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
510 let quotient = build.binary(Opcode::SDiv, wide_a, wide_b, Flags::NONE);
511 let narrow = build.unary(Opcode::Trunc, quotient, Type::int(8));
512 build.ret(&[narrow]);
513 assert!(
514 !Narrow
515 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
516 .changed()
517 );
518 assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
522 }
523
524 #[test]
525 fn a_shift_by_a_constant_below_the_width_narrows_and_one_at_it_does_not() {
526 for (by, narrows) in [(3, true), (20, false)] {
527 let (mut func, block) = blank();
528 let a = func.append_param(block, Type::int(8));
529 let mut build = Builder::new(&mut func, block);
530 let wide = build.unary(Opcode::SExt, a, Type::int(32));
531 let count = build.iconst(Type::int(32), by);
532 let shifted = build.binary(Opcode::Shl, wide, count, Flags::NONE);
533 let narrow = build.unary(Opcode::Trunc, shifted, Type::int(8));
534 build.ret(&[narrow]);
535 assert_eq!(
536 Narrow
537 .run(
538 &mut func,
539 &mut crate::machine::fixtures::analyses(),
540 &mut Fuel::unlimited()
541 )
542 .changed(),
543 narrows,
544 "shift by {by}"
545 );
546 let want = if narrows { Opcode::Shl } else { Opcode::Trunc };
549 assert_eq!(shape(&func, narrow).0, want, "shift by {by}");
550 }
551 }
552
553 #[test]
554 fn a_shift_by_a_value_stays_wide() {
555 let (mut func, block) = blank();
556 let a = func.append_param(block, Type::int(8));
557 let n = func.append_param(block, Type::int(8));
558 let mut build = Builder::new(&mut func, block);
559 let wide = build.unary(Opcode::SExt, a, Type::int(32));
560 let by = build.unary(Opcode::SExt, n, Type::int(32));
561 let shifted = build.binary(Opcode::Shl, wide, by, Flags::NONE);
562 let narrow = build.unary(Opcode::Trunc, shifted, Type::int(8));
563 build.ret(&[narrow]);
564 assert!(
565 !Narrow
566 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
567 .changed()
568 );
569 assert_eq!(shape(&func, narrow).0, Opcode::Trunc);
570 }
571
572 #[test]
573 fn a_comparison_of_two_sign_extensions_is_the_comparison_of_what_they_extended() {
574 for pred in IntPred::all() {
575 let (mut func, block) = blank();
576 let a = func.append_param(block, Type::int(8));
577 let b = func.append_param(block, Type::int(8));
578 let mut build = Builder::new(&mut func, block);
579 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
580 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
581 let answer = build.icmp(pred, wide_a, wide_b);
582 build.ret(&[answer]);
583 assert!(
584 Narrow
585 .run(
586 &mut func,
587 &mut crate::machine::fixtures::analyses(),
588 &mut Fuel::unlimited()
589 )
590 .changed(),
591 "{pred}"
592 );
593 assert_eq!(shape(&func, answer).1, vec![Type::int(8), Type::int(8)], "{pred}");
596 }
597 }
598
599 #[test]
600 fn a_comparison_of_two_zero_extensions_narrows_at_every_predicate_but_the_signed_ones() {
601 for pred in IntPred::all() {
602 let (mut func, block) = blank();
603 let a = func.append_param(block, Type::int(8));
604 let b = func.append_param(block, Type::int(8));
605 let mut build = Builder::new(&mut func, block);
606 let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
607 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
608 let answer = build.icmp(pred, wide_a, wide_b);
609 build.ret(&[answer]);
610 assert_eq!(
613 Narrow
614 .run(
615 &mut func,
616 &mut crate::machine::fixtures::analyses(),
617 &mut Fuel::unlimited()
618 )
619 .changed(),
620 !pred.is_signed(),
621 "{pred}"
622 );
623 }
624 }
625
626 #[test]
627 fn a_comparison_against_a_constant_narrows_when_the_constant_is_one_of_the_narrow_ones() {
628 for (k, narrows) in [(120, true), (-1, true), (200, false)] {
629 let (mut func, block) = blank();
630 let a = func.append_param(block, Type::int(8));
631 let mut build = Builder::new(&mut func, block);
632 let wide = build.unary(Opcode::SExt, a, Type::int(32));
633 let k = build.iconst(Type::int(32), k);
634 let answer = build.icmp(IntPred::Eq, wide, k);
635 build.ret(&[answer]);
636 assert_eq!(
639 Narrow
640 .run(
641 &mut func,
642 &mut crate::machine::fixtures::analyses(),
643 &mut Fuel::unlimited()
644 )
645 .changed(),
646 narrows
647 );
648 }
649 }
650
651 #[test]
652 fn one_extension_against_the_other_kind_is_not_a_comparison_at_the_narrow_width() {
653 for pred in IntPred::all() {
658 let (mut func, block) = blank();
659 let a = func.append_param(block, Type::int(8));
660 let b = func.append_param(block, Type::int(8));
661 let mut build = Builder::new(&mut func, block);
662 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
663 let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
664 let answer = build.icmp(pred, wide_a, wide_b);
665 build.ret(&[answer]);
666 assert!(
667 !Narrow
668 .run(
669 &mut func,
670 &mut crate::machine::fixtures::analyses(),
671 &mut Fuel::unlimited()
672 )
673 .changed(),
674 "{pred}"
675 );
676 }
677 }
678
679 #[test]
680 fn a_truth_is_not_a_width_to_narrow_to() {
681 let (mut func, block) = blank();
685 let a = func.append_param(block, Type::int(1));
686 let mut build = Builder::new(&mut func, block);
687 let wide = build.unary(Opcode::ZExt, a, Type::int(32));
688 let zero = build.iconst(Type::int(32), 0);
689 let answer = build.icmp(IntPred::Ne, wide, zero);
690 build.ret(&[answer]);
691 assert!(
692 !Narrow
693 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
694 .changed()
695 );
696 assert_eq!(shape(&func, answer).1, vec![Type::int(32), Type::int(32)]);
697 }
698
699 #[test]
700 fn extensions_from_different_widths_are_not_a_comparison_at_either_of_them() {
701 let (mut func, block) = blank();
702 let a = func.append_param(block, Type::int(8));
703 let b = func.append_param(block, Type::int(16));
704 let mut build = Builder::new(&mut func, block);
705 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
706 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
707 let answer = build.icmp(IntPred::Slt, wide_a, wide_b);
708 build.ret(&[answer]);
709 assert!(
710 !Narrow
711 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
712 .changed()
713 );
714 }
715
716 #[test]
717 fn the_overflow_flags_do_not_come_along() {
718 let (mut func, block) = blank();
719 let a = func.append_param(block, Type::int(8));
720 let b = func.append_param(block, Type::int(8));
721 let mut build = Builder::new(&mut func, block);
722 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
723 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
724 let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NSW);
725 let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
726 build.ret(&[narrow]);
727 assert!(
728 Narrow
729 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
730 .changed()
731 );
732 let rucc_ir::Def::Result { inst, .. } = func[narrow].def else { panic!("a result") };
735 assert_eq!(func[inst].flags, Flags::NONE);
736 }
737
738 #[test]
739 fn fuel_stops_the_narrowing_and_not_the_looking() {
740 let (mut func, block) = blank();
741 let a = func.append_param(block, Type::int(8));
742 let b = func.append_param(block, Type::int(8));
743 let mut build = Builder::new(&mut func, block);
744 let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
745 let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
746 let first = build.icmp(IntPred::Slt, wide_a, wide_b);
747 let second = build.icmp(IntPred::Sgt, wide_a, wide_b);
748 build.ret(&[first, second]);
749 let mut fuel = Fuel::of(1);
750 assert!(
751 Narrow.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel).changed()
752 );
753 assert_eq!(shape(&func, first).1, vec![Type::int(8), Type::int(8)]);
754 assert_eq!(shape(&func, second).1, vec![Type::int(32), Type::int(32)]);
755 }
756
757 #[test]
758 fn a_block_that_narrows_nothing_is_left_exactly_as_it_was() {
759 let (mut func, block) = blank();
760 let a = func.append_param(block, Type::int(32));
761 let mut build = Builder::new(&mut func, block);
762 let sum = build.binary(Opcode::Add, a, a, Flags::NONE);
763 build.ret(&[sum]);
764 assert!(
765 !Narrow
766 .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
767 .changed()
768 );
769 assert_eq!(left(&func, block), 2);
770 assert_eq!(func[last(&func, block)].opcode, Opcode::Return);
771 }
772}