1use std::collections::HashMap;
86use std::sync::OnceLock;
87
88use rucc_ir::term::{PLAIN, Plan, Shown, Term, Terms};
89use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, InstData, Opcode, Type, Value};
90
91use crate::rules::{Match, Piece, Table, canonical, identities, strength};
92use crate::uses::count;
93use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
94
95const FLIPPED: &str = "comparison negated by an exclusive or rewritten as the opposite comparison";
97
98const NO_FUEL: &str = "negated comparison left alone, the pass ran out of fuel";
100
101const NO_FUEL_RULE: &str = "rewrite left alone, the pass ran out of fuel";
103
104const PLANS: [Plan; 3] =
112 [[Shown::Reg, Shown::Const, Shown::Reg], [Shown::Const, Shown::Reg, Shown::Reg], PLAIN];
113
114const CANONICAL: [Plan; 1] = [[Shown::Const, Shown::Var, Shown::Reg]];
124
125const TABLES: [(&Table, &[Plan]); 3] =
137 [(&identities::TABLE, &PLANS), (&strength::TABLE, &PLANS), (&canonical::TABLE, &CANONICAL)];
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct Simplify;
142
143impl Pass for Simplify {
144 fn name(&self) -> &'static str {
145 "simplify"
146 }
147
148 fn describe(&self) -> &'static str {
149 "the identities, the strength reductions, the canonicalisations, and a negated comparison \
150 as the opposite one"
151 }
152
153 fn preserves(&self) -> Preserved {
154 Preserved::ALL.without(Analysis::Liveness)
168 }
169
170 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
171 let mut stats = Stats::new();
172 let mut forward: HashMap<Value, Value> = HashMap::new();
177 let uses = count(func);
189 let dead = |func: &Func, inst: Inst| match func[inst].first_result {
190 Some(result) => uses[result.index()] == 0,
191 None => false,
192 };
193 for block in func.blocks().collect::<Vec<Block>>() {
194 for inst in func.insts(block).collect::<Vec<Inst>>() {
195 if dead(func, inst) {
196 continue;
197 }
198 if let Some(flip) = negated_comparison(func, inst) {
199 if !fuel.take() {
200 stats.missed(NO_FUEL);
204 continue;
205 }
206 let args = func.push_values(&[flip.lhs, flip.rhs]);
207 let data = &mut func[inst];
208 data.opcode = flip.opcode;
209 data.flags = flip.flags;
210 data.args = args;
211 data.extra = flip.extra;
212 stats.optimized(FLIPPED);
213 continue;
214 }
215 let Some((rewrite, pattern)) = identity(func, inst) else { continue };
216 if !fuel.take() {
217 stats.missed(NO_FUEL_RULE);
218 continue;
219 }
220 match rewrite {
221 Rewrite::Value(value) => {
222 let result = func[inst].first_result.expect("the rule matched a result");
223 forward.insert(result, value);
224 }
225 Rewrite::Constant(number) => become_constant(func, inst, number),
226 Rewrite::Built { opcode, lhs, rhs } => {
227 become_instruction(func, inst, opcode, lhs, rhs);
228 }
229 }
230 stats.optimized(pattern);
231 }
232 }
233 if !forward.is_empty() {
234 substitute(func, &forward);
235 }
236 stats
237 }
238}
239
240#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242enum Rewrite {
243 Value(Value),
245 Constant(i128),
247 Built {
249 opcode: Opcode,
251 lhs: Operand,
253 rhs: Operand,
255 },
256}
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq)]
260enum Operand {
261 Value(Value),
263 Constant(i128),
267}
268
269fn identity(func: &Func, inst: Inst) -> Option<(Rewrite, &'static str)> {
275 let result = func[inst].first_result?;
276 for (table, plan) in
277 TABLES.into_iter().flat_map(|(table, plans)| plans.iter().map(move |&plan| (table, plan)))
278 {
279 let terms = Terms::new(func, inst, plan);
280 let Some(found) = table.find(&terms, Term::Root) else { continue };
281 let rule = table.rule(&found);
282 let rewrite = match rule.replacement {
283 [Piece::App { head, arity: 1 }, Piece::Var { index, .. }]
286 if head.starts_with("value.") =>
287 {
288 match found.bindings.get(*index) {
289 Some(&Term::Reg(value)) => Rewrite::Value(value),
290 _ => continue,
291 }
292 }
293 [Piece::App { head, arity: 1 }, Piece::Int(number)]
297 if head.starts_with("iconst.") && func[result].ty.is_int() =>
298 {
299 Rewrite::Constant(*number)
300 }
301 pieces => match built(pieces, &found) {
306 Some(rewrite) => rewrite,
307 None => continue,
311 },
312 };
313 return Some((rewrite, rule.pattern));
314 }
315 None
316}
317
318fn built(pieces: &'static [Piece], found: &Match<Term>) -> Option<Rewrite> {
325 let [Piece::App { head, arity: 2 }, rest @ ..] = pieces else { return None };
326 let opcode = opcode_of(head)?;
327 let (lhs, rest) = operand(rest, found)?;
328 let (rhs, rest) = operand(rest, found)?;
329 rest.is_empty().then_some(Rewrite::Built { opcode, lhs, rhs })
330}
331
332fn operand(pieces: &'static [Piece], found: &Match<Term>) -> Option<(Operand, &'static [Piece])> {
334 match pieces {
335 [Piece::App { head, arity: 1 }, Piece::Var { index, .. }, rest @ ..]
336 if head.starts_with("value.") =>
337 {
338 match found.bindings.get(*index) {
339 Some(&Term::Reg(value)) => Some((Operand::Value(value), rest)),
340 _ => None,
341 }
342 }
343 [Piece::App { head, arity: 1 }, Piece::Int(number), rest @ ..]
344 if head.starts_with("iconst.") =>
345 {
346 Some((Operand::Constant(*number), rest))
347 }
348 [Piece::App { head, arity: 1 }, Piece::Var { index, .. }, rest @ ..]
352 if head.starts_with("iconst.") =>
353 {
354 match found.bindings.get(*index) {
355 Some(&Term::Num(number)) => Some((Operand::Constant(number), rest)),
356 _ => None,
357 }
358 }
359 _ => None,
360 }
361}
362
363fn opcode_of(head: &str) -> Option<Opcode> {
374 static NAMES: OnceLock<HashMap<&'static str, Opcode>> = OnceLock::new();
375 let names = NAMES.get_or_init(|| {
376 let mut names = HashMap::new();
377 for (opcode, name) in rucc_ir::term::heads() {
378 names.entry(name).or_insert(opcode);
379 }
380 names
381 });
382 names.get(head).copied()
383}
384
385fn become_instruction(func: &mut Func, inst: Inst, opcode: Opcode, lhs: Operand, rhs: Operand) {
390 let result = func[inst].first_result.expect("the rule matched a result");
391 let ty = func[result].ty;
392 let lhs = defined(func, inst, ty, lhs);
393 let rhs = defined(func, inst, ty, rhs);
394 let args = func.push_values(&[lhs, rhs]);
395 let data = &mut func[inst];
396 data.opcode = opcode;
397 data.args = args;
398 data.extra = Extra::None;
402 data.flags = Flags::NONE;
408}
409
410fn defined(func: &mut Func, before: Inst, ty: Type, operand: Operand) -> Value {
412 match operand {
413 Operand::Value(value) => value,
414 Operand::Constant(number) => {
415 let at = func.add_imm(Imm::int(number, ty.lane()));
416 let data = InstData { extra: Extra::Imm(at), ..InstData::new(Opcode::IConst) };
417 let span = func.span(before);
418 let iconst = func.create_inst(data, &[ty], span);
419 func.insert_before(iconst, before);
420 func[iconst].first_result.expect("one result was asked for")
421 }
422 }
423}
424
425fn become_constant(func: &mut Func, inst: Inst, number: i128) {
430 let result = func[inst].first_result.expect("the rule matched a result");
431 let ty = func[result].ty;
432 let imm = func.add_imm(Imm::int(number, ty.lane()));
433 let args = func.push_values(&[]);
434 let data = &mut func[inst];
435 data.opcode = Opcode::IConst;
436 data.args = args;
437 data.extra = Extra::Imm(imm);
438 data.flags = Flags::NONE;
441}
442
443fn chase(forward: &HashMap<Value, Value>, value: Value) -> Value {
450 let mut value = value;
451 while let Some(&next) = forward.get(&value) {
452 value = next;
453 }
454 value
455}
456
457fn substitute(func: &mut Func, forward: &HashMap<Value, Value>) {
462 let with = |value: Value| chase(forward, value);
463 for block in func.blocks().collect::<Vec<Block>>() {
464 for inst in func.insts(block).collect::<Vec<Inst>>() {
465 let args = func[inst].args;
466 func.rewrite(args, with);
467 for call in func.successors(inst).collect::<Vec<_>>() {
468 func.rewrite(call.args, with);
469 }
470 }
471 }
472}
473
474struct Flip {
476 opcode: Opcode,
478 flags: Flags,
480 extra: Extra,
482 lhs: Value,
484 rhs: Value,
486}
487
488fn negated_comparison(func: &Func, inst: Inst) -> Option<Flip> {
495 let data = &func[inst];
496 if data.opcode != Opcode::Xor {
497 return None;
498 }
499 let args = &func[data.args];
500 let (&first, &second) = (args.first()?, args.get(1)?);
501 if func[first].ty != Type::int(1) {
502 return None;
503 }
504 let cmp = match (all_ones(func, first), all_ones(func, second)) {
505 (true, false) => second,
506 (false, true) => first,
507 _ => return None,
510 };
511 let Def::Result { inst: cmp, .. } = func[cmp].def else { return None };
512 let data = &func[cmp];
513 let extra = match (data.opcode, data.extra) {
514 (Opcode::ICmp, Extra::IntPred(pred)) => Extra::IntPred(pred.inverse()),
515 (Opcode::FCmp, Extra::FloatPred(pred)) => Extra::FloatPred(pred.inverse()),
516 _ => return None,
517 };
518 let args = &func[data.args];
519 Some(Flip {
520 opcode: data.opcode,
521 flags: data.flags,
522 extra,
523 lhs: *args.first()?,
524 rhs: *args.get(1)?,
525 })
526}
527
528fn all_ones(func: &Func, value: Value) -> bool {
530 let ty = func[value].ty;
531 let Def::Result { inst, .. } = func[value].def else { return false };
532 let data = &func[inst];
533 let Extra::Imm(at) = data.extra else { return false };
534 if data.opcode != Opcode::IConst {
535 return false;
536 }
537 func[at].signed(ty) == -1
540}
541
542#[cfg(test)]
543mod tests {
544 use rucc_base::Interner;
545 use rucc_ir::{
546 Block, Builder, Extra, Flags, Float, FloatPred, Func, IntPred, Module, Opcode, Signature,
547 Type, Value,
548 };
549 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
550
551 use super::{CANONICAL, PLANS, Shown, TABLES, canonical, identities, strength};
552 use crate::rules::Piece;
553 use crate::stats::Kind;
554 use crate::{Analyses, Fuel, Pass, simplify::Simplify};
555
556 fn blank() -> (Interner, Func, Block) {
558 let mut names = Interner::new();
559 let name = names.intern("f");
560 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(1)]));
561 let block = func.create_block();
562 (names, func, block)
563 }
564
565 fn one_block(ty: Type) -> (Interner, Func, Block) {
568 let mut names = Interner::new();
569 let name = names.intern("f");
570 let signature = Signature::new().with_params(&[ty]).with_returns(&[ty]);
571 let mut func = Func::new(name, signature);
572 let block = func.create_block();
573 (names, func, block)
574 }
575
576 fn simplify(func: &mut Func) -> bool {
578 Simplify.run(func, &mut Analyses::new(), &mut Fuel::unlimited()).changed()
579 }
580
581 fn came_from(func: &Func, value: Value) -> (Opcode, Extra) {
583 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
584 (func[inst].opcode, func[inst].extra)
585 }
586
587 fn returned(func: &Func, block: Block) -> Value {
591 let inst = func.terminator(block).expect("the block has a terminator");
592 func[func[inst].args][0]
593 }
594
595 fn operands(func: &Func, value: Value) -> Vec<Value> {
597 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
598 func[func[inst].args].to_vec()
599 }
600
601 fn number(func: &Func, value: Value) -> i128 {
603 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
604 let data = &func[inst];
605 assert_eq!(data.opcode, Opcode::IConst, "not a constant");
606 let Extra::Imm(at) = data.extra else { panic!("a constant with no number") };
607 func[at].signed(func[value].ty)
608 }
609
610 #[test]
616 fn every_rule_leaves_a_shape_the_pass_knows_what_to_do_with() {
617 for (table, _) in TABLES {
618 for rule in table.rules {
619 let known = matches!(
620 rule.replacement,
621 [Piece::App { head, arity: 1 }, Piece::Var { .. }]
622 if head.starts_with("value.")
623 ) || matches!(
624 rule.replacement,
625 [Piece::App { head, arity: 1 }, Piece::Int(_)]
626 if head.starts_with("iconst.")
627 ) || matches!(
628 rule.replacement,
629 [Piece::App { arity: 2, .. }, ..] if instruction(rule.replacement)
630 );
631 assert!(known, "{} leaves a shape the pass would skip", rule.pattern);
632 }
633 }
634 }
635
636 fn instruction(pieces: &'static [Piece]) -> bool {
642 let [Piece::App { head, arity: 2 }, rest @ ..] = pieces else { return false };
643 if super::opcode_of(head).is_none() {
644 return false;
645 }
646 let operand = |pieces: &'static [Piece]| match pieces {
647 [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
648 if head.starts_with("value.") =>
649 {
650 Some(rest)
651 }
652 [Piece::App { head, arity: 1 }, Piece::Int(_), rest @ ..]
653 if head.starts_with("iconst.") =>
654 {
655 Some(rest)
656 }
657 [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
658 if head.starts_with("iconst.") =>
659 {
660 Some(rest)
661 }
662 _ => None,
663 };
664 operand(rest).and_then(operand).is_some_and(<[Piece]>::is_empty)
665 }
666
667 #[test]
671 fn each_table_holds_every_rule_its_file_writes() {
672 let tier_one = include_str!("../rules/simplify.rules");
673 let tier_two = include_str!("../rules/strength.rules");
674 let tier_three = include_str!("../rules/canonical.rules");
675 let count = |text: &str| text.matches("(rule (simplify ").count();
676 assert_eq!(identities::TABLE.rules.len(), count(tier_one));
677 assert_eq!(strength::TABLE.rules.len(), count(tier_two));
678 assert_eq!(canonical::TABLE.rules.len(), count(tier_three));
679 assert!(
680 identities::TABLE.rules.len() > 100,
681 "tier one is about a hundred rules and there are fewer"
682 );
683 assert!(
684 strength::TABLE.rules.len() > 20,
685 "tier two is the multiplications and the divisions and there are fewer"
686 );
687 assert_eq!(
688 canonical::TABLE.rules.len(),
689 20,
690 "tier three is five commutative operators at four widths"
691 );
692 }
693
694 #[test]
697 fn a_pattern_is_reached_by_one_of_the_plans() {
698 assert_eq!(PLANS.len(), 3);
699 }
700
701 #[test]
707 fn a_canonicalisation_is_only_matched_with_the_right_operand_refused() {
708 let (_, plans) = TABLES[2];
709 assert_eq!(plans.len(), 1);
710 assert_eq!(plans[0], CANONICAL[0]);
711 assert_eq!(plans[0][1], Shown::Var);
712 for plan in PLANS {
713 assert_ne!(plan, plans[0], "a shared plan would let a canonicalisation cycle");
714 }
715 }
716
717 #[test]
722 fn a_constant_on_the_left_of_a_commutative_operation_moves_to_the_right() {
723 for opcode in [Opcode::Add, Opcode::Mul, Opcode::And, Opcode::Or, Opcode::Xor] {
724 for width in [8, 16, 32, 64] {
725 let ty = Type::int(width);
726 let (_, mut func, block) = one_block(ty);
727 let x = func.append_param(block, ty);
728 let mut build = Builder::new(&mut func, block);
729 let three = build.iconst(ty, 3);
733 let value = build.binary(opcode, three, x, Flags::NONE);
734 build.ret(&[value]);
735 assert!(simplify(&mut func), "{opcode:?} at i{width} was left alone");
736 let args = operands(&func, returned(&func, block));
737 assert_eq!(came_from(&func, returned(&func, block)).0, opcode);
738 assert_eq!(args[0], x, "{opcode:?} at i{width} kept the value on the right");
739 assert_eq!(number(&func, args[1]), 3, "{opcode:?} at i{width} lost its constant");
740 }
741 }
742 }
743
744 #[test]
751 fn an_operation_on_two_constants_is_not_swapped_back_and_forth() {
752 let i32 = Type::int(32);
753 let (_, mut func, block) = one_block(i32);
754 let mut build = Builder::new(&mut func, block);
755 let three = build.iconst(i32, 3);
756 let five = build.iconst(i32, 5);
757 let sum = build.binary(Opcode::Add, three, five, Flags::NONE);
758 build.ret(&[sum]);
759 assert!(!simplify(&mut func), "the constants were rearranged rather than left to folding");
760 let args = operands(&func, returned(&func, block));
761 assert_eq!(number(&func, args[0]), 3);
762 assert_eq!(number(&func, args[1]), 5);
763 }
764
765 #[test]
770 fn a_constant_already_on_the_right_is_left_alone() {
771 let i32 = Type::int(32);
772 let (_, mut func, block) = one_block(i32);
773 let x = func.append_param(block, i32);
774 let mut build = Builder::new(&mut func, block);
775 let three = build.iconst(i32, 3);
776 let sum = build.binary(Opcode::Add, x, three, Flags::NONE);
777 build.ret(&[sum]);
778 assert!(!simplify(&mut func));
779 let args = operands(&func, returned(&func, block));
780 assert_eq!(args[0], x);
781 assert_eq!(number(&func, args[1]), 3);
782 }
783
784 #[test]
790 fn a_subtraction_keeps_its_operands_where_they_are() {
791 let i32 = Type::int(32);
792 let (_, mut func, block) = one_block(i32);
793 let x = func.append_param(block, i32);
794 let mut build = Builder::new(&mut func, block);
795 let three = build.iconst(i32, 3);
796 let difference = build.binary(Opcode::Sub, three, x, Flags::NONE);
797 build.ret(&[difference]);
798 assert!(!simplify(&mut func));
799 let args = operands(&func, returned(&func, block));
800 assert_eq!(number(&func, args[0]), 3);
801 assert_eq!(args[1], x);
802 }
803
804 #[test]
805 fn adding_nothing_points_every_reader_at_the_operand() {
806 let i32 = Type::int(32);
807 let (_, mut func, block) = one_block(i32);
808 let x = func.append_param(block, i32);
809 let mut build = Builder::new(&mut func, block);
810 let zero = build.iconst(i32, 0);
811 let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
812 build.ret(&[sum]);
813 assert!(simplify(&mut func));
814 assert_eq!(returned(&func, block), x);
816 assert_eq!(came_from(&func, sum).0, Opcode::Add);
817 }
818
819 #[test]
822 fn the_constant_is_found_on_either_side_of_an_identity() {
823 for swapped in [false, true] {
824 let i32 = Type::int(32);
825 let (_, mut func, block) = one_block(i32);
826 let x = func.append_param(block, i32);
827 let mut build = Builder::new(&mut func, block);
828 let zero = build.iconst(i32, 0);
829 let (lhs, rhs) = if swapped { (zero, x) } else { (x, zero) };
830 let sum = build.binary(Opcode::Add, lhs, rhs, Flags::NONE);
831 build.ret(&[sum]);
832 assert!(simplify(&mut func), "swapped {swapped}");
833 assert_eq!(returned(&func, block), x, "swapped {swapped}");
834 }
835 }
836
837 #[test]
838 fn multiplying_by_nothing_becomes_the_constant_where_it_stands() {
839 let i32 = Type::int(32);
840 let (_, mut func, block) = one_block(i32);
841 let x = func.append_param(block, i32);
842 let mut build = Builder::new(&mut func, block);
843 let zero = build.iconst(i32, 0);
844 let product = build.binary(Opcode::Mul, x, zero, Flags::NONE);
845 build.ret(&[product]);
846 assert!(simplify(&mut func));
847 assert_eq!(returned(&func, block), product);
849 assert_eq!(came_from(&func, product).0, Opcode::IConst);
850 assert_eq!(number(&func, product), 0);
851 }
852
853 #[test]
856 fn a_value_against_itself() {
857 for bits in [8, 16, 32, 64] {
858 let ty = Type::int(bits);
859 let (_, mut func, block) = one_block(ty);
860 let x = func.append_param(block, ty);
861 let mut build = Builder::new(&mut func, block);
862 let both = build.binary(Opcode::And, x, x, Flags::NONE);
863 build.ret(&[both]);
864 assert!(simplify(&mut func), "{bits} bits");
865 assert_eq!(returned(&func, block), x, "{bits} bits");
866
867 let (_, mut func, block) = one_block(ty);
868 let x = func.append_param(block, ty);
869 let mut build = Builder::new(&mut func, block);
870 let nothing = build.binary(Opcode::Sub, x, x, Flags::NONE);
871 build.ret(&[nothing]);
872 assert!(simplify(&mut func), "{bits} bits");
873 assert_eq!(number(&func, nothing), 0, "{bits} bits");
874 }
875 }
876
877 #[test]
881 fn dividing_by_one_and_the_remainder_that_goes_with_it() {
882 let i32 = Type::int(32);
883 let (_, mut func, block) = one_block(i32);
884 let x = func.append_param(block, i32);
885 let mut build = Builder::new(&mut func, block);
886 let one = build.iconst(i32, 1);
887 let quotient = build.binary(Opcode::SDiv, x, one, Flags::NONE);
888 let rest = build.binary(Opcode::SRem, x, one, Flags::NONE);
889 let sum = build.binary(Opcode::Add, quotient, rest, Flags::NONE);
890 build.ret(&[sum]);
891 assert!(simplify(&mut func));
892 assert_eq!(number(&func, rest), 0);
893 let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
895 assert_eq!(func[func[inst].args][0], x);
896 }
897
898 #[test]
901 fn all_ones_at_one_bit_is_the_one_the_front_end_writes() {
902 for written in [-1, 1] {
903 let bit = Type::int(1);
904 let (_, mut func, block) = one_block(bit);
905 let x = func.append_param(block, bit);
906 let mut build = Builder::new(&mut func, block);
907 let ones = build.iconst(bit, written);
908 let kept = build.binary(Opcode::And, x, ones, Flags::NONE);
909 build.ret(&[kept]);
910 assert!(simplify(&mut func), "written as {written}");
911 assert_eq!(returned(&func, block), x, "written as {written}");
912 }
913 }
914
915 #[test]
919 fn one_identity_feeding_another_is_followed_to_the_end() {
920 let i32 = Type::int(32);
921 let (_, mut func, block) = one_block(i32);
922 let x = func.append_param(block, i32);
923 let mut build = Builder::new(&mut func, block);
924 let zero = build.iconst(i32, 0);
925 let one = build.iconst(i32, 1);
926 let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
927 let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
928 let shifted = build.binary(Opcode::Shl, product, zero, Flags::NONE);
929 build.ret(&[shifted]);
930 assert!(simplify(&mut func));
931 assert_eq!(returned(&func, block), x);
932 }
933
934 #[test]
935 fn an_instruction_no_rule_is_about_is_left_alone() {
936 let i32 = Type::int(32);
940 let (_, mut func, block) = one_block(i32);
941 let x = func.append_param(block, i32);
942 let mut build = Builder::new(&mut func, block);
943 let three = build.iconst(i32, 3);
944 let tripled = build.binary(Opcode::Mul, x, three, Flags::NONE);
945 build.ret(&[tripled]);
946 assert!(!simplify(&mut func), "no rule is about multiplying by three");
947 assert_eq!(returned(&func, block), tripled);
948 assert_eq!(came_from(&func, tripled).0, Opcode::Mul);
949 }
950
951 #[test]
952 fn multiplying_by_two_becomes_an_addition_of_the_value_with_itself() {
953 let i32 = Type::int(32);
954 let (_, mut func, block) = one_block(i32);
955 let x = func.append_param(block, i32);
956 let mut build = Builder::new(&mut func, block);
957 let two = build.iconst(i32, 2);
958 let doubled = build.binary(Opcode::Mul, x, two, Flags::NONE);
959 build.ret(&[doubled]);
960 assert!(simplify(&mut func));
961 assert_eq!(returned(&func, block), doubled);
963 assert_eq!(came_from(&func, doubled).0, Opcode::Add);
964 assert_eq!(operands(&func, doubled), [x, x]);
965 }
966
967 #[test]
968 fn multiplying_by_minus_one_becomes_a_subtraction_from_a_zero_the_rewrite_defines() {
969 let i32 = Type::int(32);
972 let (_, mut func, block) = one_block(i32);
973 let x = func.append_param(block, i32);
974 let mut build = Builder::new(&mut func, block);
975 let minus = build.iconst(i32, -1);
976 let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
977 build.ret(&[negated]);
978 assert!(simplify(&mut func));
979 assert_eq!(returned(&func, block), negated);
980 assert_eq!(came_from(&func, negated).0, Opcode::Sub);
981 let args = operands(&func, negated);
982 assert_eq!(number(&func, args[0]), 0);
983 assert_eq!(args[1], x);
984 }
985
986 #[test]
987 fn the_flags_of_the_instruction_a_strength_reduction_replaces_do_not_come_with_it() {
988 let i32 = Type::int(32);
992 let (_, mut func, block) = one_block(i32);
993 let x = func.append_param(block, i32);
994 let mut build = Builder::new(&mut func, block);
995 let two = build.iconst(i32, 2);
996 let doubled = build.binary(Opcode::Mul, x, two, Flags::NSW);
997 build.ret(&[doubled]);
998 assert!(simplify(&mut func));
999 let rucc_ir::Def::Result { inst, .. } = func[doubled].def else { panic!("not a result") };
1000 assert_eq!(func[inst].flags, Flags::NONE);
1001 }
1002
1003 #[test]
1004 fn a_strength_reduction_leaves_the_verifier_nothing_to_complain_about() {
1005 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1009 let i32 = Type::int(32);
1010 let (mut names, mut func, block) = one_block(i32);
1011 let mut module = Module::new(names.intern("test.c"), &target);
1012 let x = func.append_param(block, i32);
1013 let mut build = Builder::new(&mut func, block);
1014 let minus = build.iconst(i32, -1);
1015 let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
1016 let two = build.iconst(i32, 2);
1017 let doubled = build.binary(Opcode::Mul, negated, two, Flags::NONE);
1018 build.ret(&[doubled]);
1019 assert!(simplify(&mut func));
1020 module.add_func(func);
1021 rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
1022 }
1023
1024 #[test]
1029 fn the_pass_leaves_the_verifier_nothing_to_complain_about() {
1030 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1031 let i32 = Type::int(32);
1032 let (mut names, mut func, block) = one_block(i32);
1033 let mut module = Module::new(names.intern("test.c"), &target);
1034 let x = func.append_param(block, i32);
1035 let mut build = Builder::new(&mut func, block);
1036 let zero = build.iconst(i32, 0);
1037 let one = build.iconst(i32, 1);
1038 let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
1039 let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
1040 let gone = build.binary(Opcode::Sub, product, product, Flags::NONE);
1041 let total = build.binary(Opcode::Add, product, gone, Flags::NONE);
1042 build.ret(&[total]);
1043 assert!(simplify(&mut func));
1044 module.add_func(func);
1045 rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
1046 }
1047
1048 #[test]
1049 fn fuel_stops_an_identity_and_not_the_walk() {
1050 let i32 = Type::int(32);
1051 let (_, mut func, block) = one_block(i32);
1052 let x = func.append_param(block, i32);
1053 let mut build = Builder::new(&mut func, block);
1054 let zero = build.iconst(i32, 0);
1055 let first = build.binary(Opcode::Add, x, zero, Flags::NONE);
1056 let second = build.binary(Opcode::Sub, x, zero, Flags::NONE);
1057 let sum = build.binary(Opcode::Add, first, second, Flags::NONE);
1058 build.ret(&[sum]);
1059 let stats = Simplify.run(&mut func, &mut Analyses::new(), &mut Fuel::of(1));
1060 assert!(stats.changed());
1061 assert_eq!(stats.total(Kind::Optimized), 1);
1062 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_RULE), 1);
1063 let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
1065 assert_eq!(func[func[inst].args], [x, second]);
1066 }
1067
1068 #[test]
1069 fn a_negated_float_comparison_becomes_the_opposite_predicate() {
1070 for pred in FloatPred::all() {
1073 let (_, mut func, block) = blank();
1074 let mut build = Builder::new(&mut func, block);
1075 let x = build.iconst(Type::int(64), 0);
1076 let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
1077 let cmp = build.fcmp(pred, x, x, Flags::NONE);
1078 let ones = build.iconst(Type::int(1), -1);
1079 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1080 build.ret(&[not]);
1081 assert!(simplify(&mut func), "{pred:?}");
1082 assert_eq!(
1083 came_from(&func, not),
1084 (Opcode::FCmp, Extra::FloatPred(pred.inverse())),
1085 "{pred:?}"
1086 );
1087 }
1088 }
1089
1090 #[test]
1091 fn a_negated_integer_comparison_becomes_the_opposite_predicate() {
1092 for pred in IntPred::all() {
1093 let (_, mut func, block) = blank();
1094 let mut build = Builder::new(&mut func, block);
1095 let x = build.iconst(Type::int(32), 3);
1096 let cmp = build.icmp(pred, x, x);
1097 let ones = build.iconst(Type::int(1), -1);
1098 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1099 build.ret(&[not]);
1100 assert!(simplify(&mut func), "{pred:?}");
1101 assert_eq!(
1102 came_from(&func, not),
1103 (Opcode::ICmp, Extra::IntPred(pred.inverse())),
1104 "{pred:?}"
1105 );
1106 }
1107 }
1108
1109 #[test]
1110 fn the_constant_is_found_on_either_side() {
1111 for swapped in [false, true] {
1112 let (_, mut func, block) = blank();
1113 let mut build = Builder::new(&mut func, block);
1114 let x = build.iconst(Type::int(32), 3);
1115 let cmp = build.icmp(IntPred::Slt, x, x);
1116 let ones = build.iconst(Type::int(1), -1);
1117 let (lhs, rhs) = if swapped { (ones, cmp) } else { (cmp, ones) };
1118 let not = build.binary(Opcode::Xor, lhs, rhs, Flags::NONE);
1119 build.ret(&[not]);
1120 assert!(simplify(&mut func), "swapped {swapped}");
1121 assert_eq!(came_from(&func, not).1, Extra::IntPred(IntPred::Sge));
1122 }
1123 }
1124
1125 #[test]
1126 fn an_exclusive_or_of_two_comparisons_is_left_alone() {
1127 let (_, mut func, block) = blank();
1128 let mut build = Builder::new(&mut func, block);
1129 let x = build.iconst(Type::int(32), 3);
1130 let a = build.icmp(IntPred::Slt, x, x);
1131 let b = build.icmp(IntPred::Sgt, x, x);
1132 let differ = build.binary(Opcode::Xor, a, b, Flags::NONE);
1133 build.ret(&[differ]);
1134 assert!(!simplify(&mut func));
1135 assert_eq!(came_from(&func, differ).0, Opcode::Xor);
1136 }
1137
1138 #[test]
1139 fn an_exclusive_or_of_something_that_is_not_a_comparison_is_left_alone() {
1140 let (_, mut func, block) = blank();
1141 let mut build = Builder::new(&mut func, block);
1142 let x = build.iconst(Type::int(32), 3);
1143 let narrow = build.unary(Opcode::Trunc, x, Type::int(1));
1144 let ones = build.iconst(Type::int(1), -1);
1145 let not = build.binary(Opcode::Xor, narrow, ones, Flags::NONE);
1146 build.ret(&[not]);
1147 assert!(!simplify(&mut func));
1148 assert_eq!(came_from(&func, not).0, Opcode::Xor);
1149 }
1150
1151 #[test]
1152 fn a_wider_exclusive_or_with_one_is_not_a_negation_and_is_left_alone() {
1153 let (_, mut func, block) = blank();
1154 let mut build = Builder::new(&mut func, block);
1155 let x = build.iconst(Type::int(32), 3);
1156 let cmp = build.icmp(IntPred::Slt, x, x);
1157 let wide = build.unary(Opcode::ZExt, cmp, Type::int(32));
1158 let one = build.iconst(Type::int(32), 1);
1159 let flipped = build.binary(Opcode::Xor, wide, one, Flags::NONE);
1160 let narrow = build.unary(Opcode::Trunc, flipped, Type::int(1));
1161 build.ret(&[narrow]);
1162 assert!(!simplify(&mut func), "an i32 xor 1 flips one bit of thirty two");
1163 assert_eq!(came_from(&func, flipped).0, Opcode::Xor);
1164 }
1165
1166 #[test]
1167 fn the_comparisons_flags_travel_with_the_predicate() {
1168 let (_, mut func, block) = blank();
1169 let mut build = Builder::new(&mut func, block);
1170 let x = build.iconst(Type::int(64), 0);
1171 let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
1172 let cmp = build.fcmp(FloatPred::Olt, x, x, Flags::FAST);
1173 let ones = build.iconst(Type::int(1), -1);
1174 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
1175 build.ret(&[not]);
1176 assert!(simplify(&mut func));
1177 let rucc_ir::Def::Result { inst, .. } = func[not].def else { panic!("not a result") };
1178 assert_eq!(func[inst].flags, Flags::FAST);
1181 }
1182
1183 #[test]
1184 fn fuel_stops_the_transformation_and_not_the_walk() {
1185 let (_, mut func, block) = blank();
1186 let mut build = Builder::new(&mut func, block);
1187 let x = build.iconst(Type::int(32), 3);
1188 let a = build.icmp(IntPred::Slt, x, x);
1189 let b = build.icmp(IntPred::Sgt, x, x);
1190 let ones = build.iconst(Type::int(1), -1);
1191 let first = build.binary(Opcode::Xor, a, ones, Flags::NONE);
1192 let second = build.binary(Opcode::Xor, b, ones, Flags::NONE);
1193 let both = build.binary(Opcode::And, first, second, Flags::NONE);
1194 build.ret(&[both]);
1195 let stats = Simplify.run(&mut func, &mut Analyses::new(), &mut Fuel::of(1));
1196 assert!(stats.changed());
1197 assert_eq!(stats.count(Kind::Optimized, super::FLIPPED), 1);
1198 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1199 assert_eq!(came_from(&func, first).0, Opcode::ICmp);
1200 assert_eq!(came_from(&func, second).0, Opcode::Xor);
1201 }
1202}