1use std::cmp::Ordering;
180use std::collections::HashMap;
181use std::sync::OnceLock;
182
183use rucc_base::float::Float;
184use rucc_ir::term::{PLAIN, Plan, Shown, Term, Terms};
185use rucc_ir::{
186 Block, Def, Extra, Flags, FloatPred, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value,
187};
188
189use crate::cfg::Cfg;
190use crate::rules::{Match, Piece, Subject, Table, canonical, compare, identities, strength, width};
191use crate::uses::{count, substitute};
192use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
193
194const FLIPPED: &str = "comparison negated by an exclusive or rewritten as the opposite comparison";
196
197const NO_FUEL: &str = "negated comparison left alone, the pass ran out of fuel";
199
200const COMPOSITE: &str = "two comparisons over the same operands combined into one";
202
203const NO_FUEL_COMPOSITE: &str = "pair of comparisons left alone, the pass ran out of fuel";
205
206const MAGNITUDE: &str = "comparison against a value whose sign bit is clear settled by the sign";
208
209const NO_FUEL_MAGNITUDE: &str =
211 "comparison against a magnitude left alone, the pass ran out of fuel";
212
213const BOUNDED: &str = "floating point comparison settled by a constant or by one operand twice";
215
216const NO_FUEL_BOUNDED: &str =
218 "floating point comparison against a bound left alone, the pass ran out of fuel";
219
220const NO_FUEL_RULE: &str = "rewrite left alone, the pass ran out of fuel";
222
223const PLANS: [Plan; 3] =
231 [[Shown::Reg, Shown::Const, Shown::Reg], [Shown::Const, Shown::Reg, Shown::Reg], PLAIN];
232
233const CANONICAL: [Plan; 1] = [[Shown::Const, Shown::Var, Shown::Reg]];
243
244const EXPAND: [Plan; 1] = [[Shown::Expand, Shown::Reg, Shown::Reg]];
254
255const COMPARE: [Plan; 2] =
275 [[Shown::Reg, Shown::Const, Shown::Reg], [Shown::Expand, Shown::Const, Shown::Reg]];
276
277const TABLES: [(&Table, &[Plan]); 5] = [
298 (&identities::TABLE, &PLANS),
299 (&strength::TABLE, &PLANS),
300 (&width::TABLE, &EXPAND),
301 (&compare::TABLE, &COMPARE),
302 (&canonical::TABLE, &CANONICAL),
303];
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307pub struct Simplify;
308
309impl Pass for Simplify {
310 fn name(&self) -> &'static str {
311 "simplify"
312 }
313
314 fn describe(&self) -> &'static str {
315 "the identities, the strength reductions, the canonicalisations, and the four comparison \
316 rewrites written by hand"
317 }
318
319 fn preserves(&self) -> Preserved {
320 Preserved::ALL.without(Analysis::Liveness)
334 }
335
336 fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
337 let mut stats = Stats::new();
338 let mut forward: HashMap<Value, Value> = HashMap::new();
343 let uses = count(func);
355 let cfg = Cfg::new(func);
358 let dead = |func: &Func, inst: Inst| match func[inst].first_result {
359 Some(result) => uses[result.index()] == 0,
360 None => false,
361 };
362 for block in func.blocks().collect::<Vec<Block>>() {
363 for inst in func.insts(block).collect::<Vec<Inst>>() {
364 if dead(func, inst) {
365 continue;
366 }
367 if let Some(flip) = negated_comparison(func, inst) {
368 if !fuel.take() {
369 stats.missed(NO_FUEL);
373 continue;
374 }
375 let args = func.push_values(&[flip.lhs, flip.rhs]);
376 let data = &mut func[inst];
377 data.opcode = flip.opcode;
378 data.flags = flip.flags;
379 data.args = args;
380 data.extra = flip.extra;
381 stats.optimized(FLIPPED);
382 continue;
383 }
384 if let Some(composite) = composite_comparison(func, inst) {
385 if !fuel.take() {
386 stats.missed(NO_FUEL_COMPOSITE);
387 continue;
388 }
389 fold_composite(func, inst, composite);
390 stats.optimized(COMPOSITE);
391 continue;
392 }
393 if let Some(settled) = magnitude_comparison(func, inst) {
394 if !fuel.take() {
395 stats.missed(NO_FUEL_MAGNITUDE);
396 continue;
397 }
398 fold_composite(func, inst, settled);
399 stats.optimized(MAGNITUDE);
400 continue;
401 }
402 if let Some(settled) = bounded_comparison(func, &cfg, inst) {
403 if !fuel.take() {
404 stats.missed(NO_FUEL_BOUNDED);
405 continue;
406 }
407 fold_composite(func, inst, settled);
408 stats.optimized(BOUNDED);
409 continue;
410 }
411 let Some((rewrite, pattern)) = identity(func, inst) else { continue };
412 if !fuel.take() {
413 stats.missed(NO_FUEL_RULE);
414 continue;
415 }
416 match rewrite {
417 Rewrite::Value(value) => {
418 let result = func[inst].first_result.expect("the rule matched a result");
419 forward.insert(result, value);
420 }
421 Rewrite::Constant(number) => become_constant(func, inst, number),
422 Rewrite::Built { opcode, pred, lhs, rhs } => {
423 become_instruction(func, inst, opcode, pred, lhs, rhs);
424 }
425 Rewrite::Converted { opcode, from } => {
426 become_conversion(func, inst, opcode, from);
427 }
428 }
429 stats.optimized(pattern);
430 }
431 }
432 if !forward.is_empty() {
433 substitute(func, &forward);
434 }
435 stats
436 }
437}
438
439#[derive(Clone, Copy, Debug, PartialEq, Eq)]
441enum Rewrite {
442 Value(Value),
444 Constant(i128),
446 Built {
448 opcode: Opcode,
450 pred: Option<IntPred>,
457 lhs: Operand,
459 rhs: Operand,
461 },
462 Converted {
470 opcode: Opcode,
472 from: Value,
474 },
475}
476
477#[derive(Clone, Copy, Debug, PartialEq, Eq)]
479enum Operand {
480 Value(Value),
482 Constant {
486 number: i128,
488 bits: u32,
496 },
497}
498
499fn identity(func: &Func, inst: Inst) -> Option<(Rewrite, &'static str)> {
505 let result = func[inst].first_result?;
506 for (table, plan) in
507 TABLES.into_iter().flat_map(|(table, plans)| plans.iter().map(move |&plan| (table, plan)))
508 {
509 let terms = Terms::new(func, inst, plan);
510 let Some(found) = table.find(&terms, Term::Root) else { continue };
511 let rule = table.rule(&found);
512 let rewrite = match rule.replacement {
513 [Piece::App { head, arity: 1 }, Piece::Var { index, .. }]
516 if head.starts_with("value.") =>
517 {
518 match found.bindings.get(*index) {
519 Some(&Term::Reg(value)) => Rewrite::Value(value),
520 _ => continue,
521 }
522 }
523 [Piece::App { head, arity: 1 }, Piece::Int(number)]
527 if head.starts_with("iconst.") && func[result].ty.is_int() =>
528 {
529 Rewrite::Constant(*number)
530 }
531 pieces => match built(pieces, &found, &matched(&terms, &found)) {
536 Some(rewrite) => rewrite,
537 None => continue,
541 },
542 };
543 return Some((rewrite, rule.pattern));
544 }
545 None
546}
547
548fn built(
555 pieces: &'static [Piece],
556 found: &Match<Term>,
557 matched: &[Option<i128>],
558) -> Option<Rewrite> {
559 if let Some(rewrite) = converted(pieces, found) {
560 return Some(rewrite);
561 }
562 let [Piece::App { head, arity: 2 }, rest @ ..] = pieces else { return None };
563 let opcode = opcode_of(head)?;
564 let pred = rucc_ir::term::int_pred(head);
567 if (opcode == Opcode::ICmp) != pred.is_some() {
568 return None;
572 }
573 let (lhs, rest) = operand(rest, found, matched)?;
574 let (rhs, rest) = operand(rest, found, matched)?;
575 rest.is_empty().then_some(Rewrite::Built { opcode, pred, lhs, rhs })
576}
577
578fn matched(terms: &Terms<'_>, found: &Match<Term>) -> Vec<Option<i128>> {
584 found.bindings.iter().map(|&node| terms.int(node)).collect()
585}
586
587fn converted(pieces: &'static [Piece], found: &Match<Term>) -> Option<Rewrite> {
599 let [Piece::App { head, arity: 1 }, rest @ ..] = pieces else { return None };
600 let opcode = match opcode_of(head)? {
601 opcode @ (Opcode::SExt | Opcode::ZExt | Opcode::Trunc) => opcode,
602 _ => return None,
603 };
604 let [Piece::App { head: inner, arity: 1 }, Piece::Var { index, .. }] = rest else {
605 return None;
606 };
607 if !inner.starts_with("value.") {
608 return None;
609 }
610 match found.bindings.get(*index) {
611 Some(&Term::Reg(from)) => Some(Rewrite::Converted { opcode, from }),
612 _ => None,
613 }
614}
615
616fn operand(
618 pieces: &'static [Piece],
619 found: &Match<Term>,
620 matched: &[Option<i128>],
621) -> Option<(Operand, &'static [Piece])> {
622 match pieces {
623 [Piece::App { head, arity: 1 }, Piece::Var { index, .. }, rest @ ..]
624 if head.starts_with("value.") =>
625 {
626 match found.bindings.get(*index) {
627 Some(&Term::Reg(value)) => Some((Operand::Value(value), rest)),
628 _ => None,
629 }
630 }
631 [Piece::App { head, arity: 1 }, Piece::Int(number), rest @ ..]
632 if head.starts_with("iconst.") =>
633 {
634 Some((Operand::Constant { number: *number, bits: bits_of(head)? }, rest))
635 }
636 [Piece::App { head, arity: 1 }, Piece::Computed { work, .. }, rest @ ..]
641 if head.starts_with("iconst.") =>
642 {
643 let number = work(matched)?;
644 Some((Operand::Constant { number, bits: bits_of(head)? }, rest))
645 }
646 [Piece::App { head, arity: 1 }, Piece::Var { index, .. }, rest @ ..]
650 if head.starts_with("iconst.") =>
651 {
652 match found.bindings.get(*index) {
653 Some(&Term::Num(number)) => {
654 Some((Operand::Constant { number, bits: bits_of(head)? }, rest))
655 }
656 _ => None,
657 }
658 }
659 _ => None,
660 }
661}
662
663fn bits_of(head: &str) -> Option<u32> {
670 head.rsplit_once('.')?.1.strip_prefix('i')?.parse().ok()
671}
672
673fn opcode_of(head: &str) -> Option<Opcode> {
684 static NAMES: OnceLock<HashMap<&'static str, Opcode>> = OnceLock::new();
685 let names = NAMES.get_or_init(|| {
686 let mut names = HashMap::new();
687 for (opcode, name) in rucc_ir::term::heads() {
688 names.entry(name).or_insert(opcode);
689 }
690 names
691 });
692 names.get(head).copied()
693}
694
695fn become_instruction(
700 func: &mut Func,
701 inst: Inst,
702 opcode: Opcode,
703 pred: Option<IntPred>,
704 lhs: Operand,
705 rhs: Operand,
706) {
707 let result = func[inst].first_result.expect("the rule matched a result");
708 let ty = func[result].ty;
709 let lhs = defined(func, inst, ty, lhs);
710 let rhs = defined(func, inst, ty, rhs);
711 let args = func.push_values(&[lhs, rhs]);
712 let data = &mut func[inst];
713 data.opcode = opcode;
714 data.args = args;
715 data.extra = match pred {
721 Some(pred) => Extra::IntPred(pred),
722 None => Extra::None,
723 };
724 data.flags = Flags::NONE;
730}
731
732fn become_conversion(func: &mut Func, inst: Inst, opcode: Opcode, from: Value) {
742 let args = func.push_values(&[from]);
743 let data = &mut func[inst];
744 data.opcode = opcode;
745 data.args = args;
746 data.extra = Extra::None;
749 data.flags = Flags::NONE;
750}
751
752fn defined(func: &mut Func, before: Inst, ty: Type, operand: Operand) -> Value {
760 match operand {
761 Operand::Value(value) => value,
762 Operand::Constant { number, bits } => {
763 let ty = if ty.lane() == Type::int(bits) { ty } else { Type::int(bits) };
764 let at = func.add_imm(Imm::int(number, ty.lane()));
765 let data = InstData { extra: Extra::Imm(at), ..InstData::new(Opcode::IConst) };
766 let span = func.span(before);
767 let iconst = func.create_inst(data, &[ty], span);
768 func.insert_before(iconst, before);
769 func[iconst].first_result.expect("one result was asked for")
770 }
771 }
772}
773
774fn become_constant(func: &mut Func, inst: Inst, number: i128) {
779 let result = func[inst].first_result.expect("the rule matched a result");
780 let ty = func[result].ty;
781 let imm = func.add_imm(Imm::int(number, ty.lane()));
782 let args = func.push_values(&[]);
783 let data = &mut func[inst];
784 data.opcode = Opcode::IConst;
785 data.args = args;
786 data.extra = Extra::Imm(imm);
787 data.flags = Flags::NONE;
790}
791
792pub(crate) struct Flip {
794 opcode: Opcode,
796 flags: Flags,
798 extra: Extra,
800 lhs: Value,
802 rhs: Value,
804}
805
806fn negated_comparison(func: &Func, inst: Inst) -> Option<Flip> {
813 let data = &func[inst];
814 if data.opcode != Opcode::Xor {
815 return None;
816 }
817 let args = &func[data.args];
818 let (&first, &second) = (args.first()?, args.get(1)?);
819 if func[first].ty != Type::int(1) {
820 return None;
821 }
822 let cmp = match (all_ones(func, first), all_ones(func, second)) {
823 (true, false) => second,
824 (false, true) => first,
825 _ => return None,
828 };
829 let Def::Result { inst: cmp, .. } = func[cmp].def else { return None };
830 let data = &func[cmp];
831 let extra = match (data.opcode, data.extra) {
832 (Opcode::ICmp, Extra::IntPred(pred)) => Extra::IntPred(pred.inverse()),
833 (Opcode::FCmp, Extra::FloatPred(pred)) => Extra::FloatPred(pred.inverse()),
834 _ => return None,
835 };
836 let args = &func[data.args];
837 Some(Flip {
838 opcode: data.opcode,
839 flags: data.flags,
840 extra,
841 lhs: *args.first()?,
842 rhs: *args.get(1)?,
843 })
844}
845
846mod bucket {
859 pub(super) const LT: u8 = 1;
861 pub(super) const EQ: u8 = 2;
863 pub(super) const GT: u8 = 4;
865 pub(super) const UN: u8 = 8;
867 pub(super) const ALL_INT: u8 = LT | EQ | GT;
869 pub(super) const ALL_FLOAT: u8 = LT | EQ | GT | UN;
871}
872
873#[derive(Clone, Copy, Debug, PartialEq, Eq)]
881enum Reading {
882 Signed,
884 Unsigned,
886 Neither,
888}
889
890impl Reading {
891 const fn shared(self, other: Self) -> Option<Self> {
893 match (self, other) {
894 (Self::Neither, same) | (same, Self::Neither) => Some(same),
895 (Self::Signed, Self::Signed) => Some(Self::Signed),
896 (Self::Unsigned, Self::Unsigned) => Some(Self::Unsigned),
897 (Self::Signed, Self::Unsigned) | (Self::Unsigned, Self::Signed) => None,
898 }
899 }
900}
901
902const fn int_buckets(pred: IntPred) -> (u8, Reading) {
904 use bucket::{EQ, GT, LT};
905 match pred {
906 IntPred::Eq => (EQ, Reading::Neither),
907 IntPred::Ne => (LT | GT, Reading::Neither),
908 IntPred::Slt => (LT, Reading::Signed),
909 IntPred::Sle => (LT | EQ, Reading::Signed),
910 IntPred::Sgt => (GT, Reading::Signed),
911 IntPred::Sge => (GT | EQ, Reading::Signed),
912 IntPred::Ult => (LT, Reading::Unsigned),
913 IntPred::Ule => (LT | EQ, Reading::Unsigned),
914 IntPred::Ugt => (GT, Reading::Unsigned),
915 IntPred::Uge => (GT | EQ, Reading::Unsigned),
916 }
917}
918
919const fn int_pred(buckets: u8, reading: Reading) -> Option<IntPred> {
927 use bucket::{EQ, GT, LT};
928 match (buckets, reading) {
929 (EQ, _) => Some(IntPred::Eq),
930 (b, _) if b == LT | GT => Some(IntPred::Ne),
931 (LT, Reading::Signed) => Some(IntPred::Slt),
932 (GT, Reading::Signed) => Some(IntPred::Sgt),
933 (b, Reading::Signed) if b == LT | EQ => Some(IntPred::Sle),
934 (b, Reading::Signed) if b == GT | EQ => Some(IntPred::Sge),
935 (LT, Reading::Unsigned) => Some(IntPred::Ult),
936 (GT, Reading::Unsigned) => Some(IntPred::Ugt),
937 (b, Reading::Unsigned) if b == LT | EQ => Some(IntPred::Ule),
938 (b, Reading::Unsigned) if b == GT | EQ => Some(IntPred::Uge),
939 _ => None,
940 }
941}
942
943const fn float_buckets(pred: FloatPred) -> u8 {
948 use bucket::{ALL_FLOAT, EQ, GT, LT, UN};
949 match pred {
950 FloatPred::False => 0,
951 FloatPred::Oeq => EQ,
952 FloatPred::Ogt => GT,
953 FloatPred::Oge => GT | EQ,
954 FloatPred::Olt => LT,
955 FloatPred::Ole => LT | EQ,
956 FloatPred::One => LT | GT,
957 FloatPred::Ord => LT | EQ | GT,
958 FloatPred::Uno => UN,
959 FloatPred::Ueq => EQ | UN,
960 FloatPred::Ugt => GT | UN,
961 FloatPred::Uge => GT | EQ | UN,
962 FloatPred::Ult => LT | UN,
963 FloatPred::Ule => LT | EQ | UN,
964 FloatPred::Une => LT | GT | UN,
965 FloatPred::True => ALL_FLOAT,
966 }
967}
968
969fn float_pred(buckets: u8) -> Option<FloatPred> {
971 FloatPred::all().find(|pred| float_buckets(*pred) == buckets)
972}
973
974struct Side {
976 opcode: Opcode,
978 flags: Flags,
982 buckets: u8,
984 reading: Reading,
987 lhs: Value,
989 rhs: Value,
991}
992
993fn side(func: &Func, value: Value) -> Option<Side> {
995 let Def::Result { inst, .. } = func[value].def else { return None };
996 let data = &func[inst];
997 let (buckets, reading) = match (data.opcode, data.extra) {
998 (Opcode::ICmp, Extra::IntPred(pred)) => int_buckets(pred),
999 (Opcode::FCmp, Extra::FloatPred(pred)) => (float_buckets(pred), Reading::Neither),
1000 _ => return None,
1001 };
1002 let args = &func[data.args];
1003 Some(Side {
1004 opcode: data.opcode,
1005 flags: data.flags,
1006 buckets,
1007 reading,
1008 lhs: *args.first()?,
1009 rhs: *args.get(1)?,
1010 })
1011}
1012
1013const fn turned(buckets: u8) -> u8 {
1018 use bucket::{GT, LT};
1019 let mut out = buckets & !(LT | GT);
1020 if buckets & LT != 0 {
1021 out |= GT;
1022 }
1023 if buckets & GT != 0 {
1024 out |= LT;
1025 }
1026 out
1027}
1028
1029fn aligned(first: &Side, second: Side) -> Option<Side> {
1035 if first.lhs == second.lhs && first.rhs == second.rhs {
1036 return Some(second);
1037 }
1038 if first.lhs != second.rhs || first.rhs != second.lhs {
1039 return None;
1040 }
1041 let buckets = turned(second.buckets);
1042 Some(Side { buckets, lhs: first.lhs, rhs: first.rhs, ..second })
1043}
1044
1045pub(crate) enum Composite {
1047 Always(bool),
1049 Pred(Flip),
1051}
1052
1053fn composite_comparison(func: &Func, inst: Inst) -> Option<Composite> {
1071 let data = &func[inst];
1072 if func[data.first_result?].ty != Type::int(1) {
1073 return None;
1074 }
1075 let args = &func[data.args];
1076 composite(func, data.opcode, *args.first()?, *args.get(1)?)
1077}
1078
1079pub(crate) fn composite(func: &Func, opcode: Opcode, lhs: Value, rhs: Value) -> Option<Composite> {
1086 let intersect = match opcode {
1087 Opcode::And => true,
1088 Opcode::Or => false,
1089 _ => return None,
1090 };
1091 let first = side(func, lhs)?;
1092 let second = aligned(&first, side(func, rhs)?)?;
1093 if first.opcode != second.opcode || first.flags != second.flags {
1094 return None;
1095 }
1096 let reading = first.reading.shared(second.reading)?;
1097 let buckets = match intersect {
1098 true => first.buckets & second.buckets,
1099 false => first.buckets | second.buckets,
1100 };
1101 let whole = match first.opcode {
1102 Opcode::ICmp => bucket::ALL_INT,
1103 _ => bucket::ALL_FLOAT,
1104 };
1105 if buckets == 0 {
1106 return Some(Composite::Always(false));
1107 }
1108 if buckets == whole {
1109 return Some(Composite::Always(true));
1110 }
1111 let extra = match first.opcode {
1112 Opcode::ICmp => Extra::IntPred(int_pred(buckets, reading)?),
1113 _ => Extra::FloatPred(float_pred(buckets)?),
1114 };
1115 Some(Composite::Pred(Flip {
1116 opcode: first.opcode,
1117 flags: first.flags,
1118 extra,
1119 lhs: first.lhs,
1120 rhs: first.rhs,
1121 }))
1122}
1123
1124fn magnitude(func: &Func, value: Value) -> bool {
1138 let Def::Result { inst, .. } = func[value].def else { return false };
1139 let data = &func[inst];
1140 if data.opcode != Opcode::Bitcast {
1141 return false;
1142 }
1143 let Some(&bits) = func[data.args].first() else { return false };
1144 let Def::Result { inst: masked, .. } = func[bits].def else { return false };
1145 let data = &func[masked];
1146 if data.opcode != Opcode::And {
1147 return false;
1148 }
1149 func[data.args].iter().any(|&arg| clears_the_sign(func, arg))
1150}
1151
1152fn clears_the_sign(func: &Func, value: Value) -> bool {
1154 let ty = func[value].ty;
1155 let Def::Result { inst, .. } = func[value].def else { return false };
1156 let data = &func[inst];
1157 let Extra::Imm(at) = data.extra else { return false };
1158 data.opcode == Opcode::IConst && ty.is_int() && func[at].signed(ty) >= 0
1159}
1160
1161fn against(func: &Func, value: Value) -> Option<u8> {
1171 use bucket::{EQ, GT, UN};
1172 let number = float_constant(func, value)?;
1173 match number.compare(Float::zero(number.format(), false))? {
1174 Ordering::Less => Some(GT | UN),
1175 Ordering::Equal => Some(GT | EQ | UN),
1176 Ordering::Greater => None,
1177 }
1178}
1179
1180fn magnitude_comparison(func: &Func, inst: Inst) -> Option<Composite> {
1195 let data = &func[inst];
1196 let Extra::FloatPred(pred) = data.extra else { return None };
1197 if data.opcode != Opcode::FCmp {
1198 return None;
1199 }
1200 let args = &func[data.args];
1201 let lhs = *args.first()?;
1202 let rhs = *args.get(1)?;
1203 let possible = if magnitude(func, lhs) {
1204 against(func, rhs)?
1205 } else if magnitude(func, rhs) {
1206 turned(against(func, lhs)?)
1207 } else {
1208 return None;
1209 };
1210 let asked = float_buckets(pred);
1211 let buckets = asked & possible;
1212 if buckets == asked {
1213 return None;
1214 }
1215 if buckets == 0 {
1216 return Some(Composite::Always(false));
1217 }
1218 Some(Composite::Pred(Flip {
1219 opcode: Opcode::FCmp,
1220 flags: data.flags,
1221 extra: Extra::FloatPred(float_pred(buckets)?),
1222 lhs,
1223 rhs,
1224 }))
1225}
1226
1227fn bounded_comparison(func: &Func, cfg: &Cfg, inst: Inst) -> Option<Composite> {
1245 use bucket::{ALL_FLOAT, EQ, GT, LT, UN};
1246 let data = &func[inst];
1247 let Extra::FloatPred(pred) = data.extra else { return None };
1248 if data.opcode != Opcode::FCmp {
1249 return None;
1250 }
1251 let args = &func[data.args];
1252 let lhs = *args.first()?;
1253 let rhs = *args.get(1)?;
1254 let left = float_constant(func, lhs);
1255 let right = float_constant(func, rhs);
1256 let possible = match (left, right) {
1257 _ if left.is_some_and(Float::is_nan) || right.is_some_and(Float::is_nan) => UN,
1258 (Some(left), Some(right)) => match left.compare(right)? {
1259 Ordering::Less => LT,
1260 Ordering::Equal => EQ,
1261 Ordering::Greater => GT,
1262 },
1263 (None, Some(bound)) => past(bound).unwrap_or(ALL_FLOAT),
1264 (Some(bound), None) => turned(past(bound).unwrap_or(ALL_FLOAT)),
1265 (None, None) if lhs == rhs => EQ | UN,
1266 (None, None) => ALL_FLOAT,
1267 };
1268 let possible = possible & guarded(func, cfg, func.block_of(inst)?, lhs, rhs);
1269 if possible == ALL_FLOAT {
1270 return None;
1271 }
1272 let asked = float_buckets(pred);
1273 let buckets = asked & possible;
1274 if buckets == 0 {
1275 return Some(Composite::Always(false));
1276 }
1277 if buckets == possible {
1278 return Some(Composite::Always(true));
1279 }
1280 if buckets == asked {
1281 return None;
1282 }
1283 Some(Composite::Pred(Flip {
1284 opcode: Opcode::FCmp,
1285 flags: data.flags,
1286 extra: Extra::FloatPred(float_pred(buckets)?),
1287 lhs,
1288 rhs,
1289 }))
1290}
1291
1292const GUARDS: u32 = 8;
1294
1295fn guarded(func: &Func, cfg: &Cfg, block: Block, lhs: Value, rhs: Value) -> u8 {
1302 let mut possible = bucket::ALL_FLOAT;
1303 let mut at = block;
1304 for _ in 0..GUARDS {
1305 let &[from] = cfg.predecessors(at) else { break };
1306 if let Some(buckets) = edge(func, from, at, lhs, rhs) {
1307 possible &= buckets;
1308 }
1309 at = from;
1310 }
1311 possible
1312}
1313
1314fn edge(func: &Func, from: Block, to: Block, lhs: Value, rhs: Value) -> Option<u8> {
1318 let term = func.terminator(from)?;
1319 if func[term].opcode != Opcode::BrIf {
1320 return None;
1321 }
1322 let calls: Vec<_> = func.successors(term).collect();
1323 let (then, other) = (calls.first()?, calls.get(1)?);
1324 if then.block == other.block {
1325 return None;
1326 }
1327 let cond = *func[func[term].args].first()?;
1328 let Def::Result { inst, .. } = func[cond].def else { return None };
1329 let data = &func[inst];
1330 let Extra::FloatPred(pred) = data.extra else { return None };
1331 if data.opcode != Opcode::FCmp {
1332 return None;
1333 }
1334 let args = &func[data.args];
1335 let (&left, &right) = (args.first()?, args.get(1)?);
1336 let accepted = if then.block == to {
1337 float_buckets(pred)
1338 } else {
1339 bucket::ALL_FLOAT & !float_buckets(pred)
1340 };
1341 if (left, right) == (lhs, rhs) {
1342 Some(accepted)
1343 } else if (left, right) == (rhs, lhs) {
1344 Some(turned(accepted))
1345 } else {
1346 None
1347 }
1348}
1349
1350fn past(bound: Float) -> Option<u8> {
1355 use bucket::{EQ, GT, LT, UN};
1356 if !bound.is_infinite() {
1357 return None;
1358 }
1359 Some(if bound.is_negative() { GT | EQ | UN } else { LT | EQ | UN })
1360}
1361
1362fn float_constant(func: &Func, value: Value) -> Option<Float> {
1364 let Def::Result { inst, .. } = func[value].def else { return None };
1365 let data = &func[inst];
1366 if data.opcode != Opcode::FConst {
1367 return None;
1368 }
1369 let Extra::Imm(at) = data.extra else { return None };
1370 let format = func[value].ty.format()?.encoding();
1371 Some(Float::from_bits(format, func[at].bits()))
1372}
1373
1374pub(crate) fn fold_composite(func: &mut Func, inst: Inst, composite: Composite) {
1379 match composite {
1380 Composite::Always(answer) => become_constant(func, inst, answer.into()),
1381 Composite::Pred(flip) => {
1382 let args = func.push_values(&[flip.lhs, flip.rhs]);
1383 let data = &mut func[inst];
1384 data.opcode = flip.opcode;
1385 data.flags = flip.flags;
1386 data.args = args;
1387 data.extra = flip.extra;
1388 }
1389 }
1390}
1391
1392fn all_ones(func: &Func, value: Value) -> bool {
1394 let ty = func[value].ty;
1395 let Def::Result { inst, .. } = func[value].def else { return false };
1396 let data = &func[inst];
1397 let Extra::Imm(at) = data.extra else { return false };
1398 if data.opcode != Opcode::IConst {
1399 return false;
1400 }
1401 func[at].signed(ty) == -1
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408 use rucc_base::Interner;
1409 use rucc_ir::{
1410 Block, Builder, Extra, Flags, Float, FloatPred, Func, IntPred, Module, Opcode, Signature,
1411 Type, Value,
1412 };
1413 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
1414
1415 use super::{
1416 CANONICAL, COMPARE, EXPAND, PLANS, Shown, TABLES, canonical, compare, identities, strength,
1417 width,
1418 };
1419 use crate::rules::Piece;
1420 use crate::stats::Kind;
1421 use crate::{Fuel, Pass, simplify::Simplify};
1422
1423 fn blank() -> (Interner, Func, Block) {
1425 let mut names = Interner::new();
1426 let name = names.intern("f");
1427 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(1)]));
1428 let block = func.create_block();
1429 (names, func, block)
1430 }
1431
1432 fn one_block(ty: Type) -> (Interner, Func, Block) {
1435 let mut names = Interner::new();
1436 let name = names.intern("f");
1437 let signature = Signature::new().with_params(&[ty]).with_returns(&[ty]);
1438 let mut func = Func::new(name, signature);
1439 let block = func.create_block();
1440 (names, func, block)
1441 }
1442
1443 fn simplify(func: &mut Func) -> bool {
1445 Simplify
1446 .run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1447 .changed()
1448 }
1449
1450 fn came_from(func: &Func, value: Value) -> (Opcode, Extra) {
1452 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
1453 (func[inst].opcode, func[inst].extra)
1454 }
1455
1456 fn returned(func: &Func, block: Block) -> Value {
1460 let inst = func.terminator(block).expect("the block has a terminator");
1461 func[func[inst].args][0]
1462 }
1463
1464 fn operands(func: &Func, value: Value) -> Vec<Value> {
1466 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
1467 func[func[inst].args].to_vec()
1468 }
1469
1470 fn number(func: &Func, value: Value) -> i128 {
1472 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
1473 let data = &func[inst];
1474 assert_eq!(data.opcode, Opcode::IConst, "not a constant");
1475 let Extra::Imm(at) = data.extra else { panic!("a constant with no number") };
1476 func[at].signed(func[value].ty)
1477 }
1478
1479 #[test]
1485 fn every_rule_leaves_a_shape_the_pass_knows_what_to_do_with() {
1486 for (table, _) in TABLES {
1487 for rule in table.rules {
1488 let known = matches!(
1489 rule.replacement,
1490 [Piece::App { head, arity: 1 }, Piece::Var { .. }]
1491 if head.starts_with("value.")
1492 ) || matches!(
1493 rule.replacement,
1494 [Piece::App { head, arity: 1 }, Piece::Int(_)]
1495 if head.starts_with("iconst.")
1496 ) || matches!(
1497 rule.replacement,
1498 [Piece::App { arity: 2, .. }, ..] if instruction(rule.replacement)
1499 ) || conversion(rule.replacement);
1500 assert!(known, "{} leaves a shape the pass would skip", rule.pattern);
1501 }
1502 }
1503 }
1504
1505 fn conversion(pieces: &'static [Piece]) -> bool {
1509 let [Piece::App { head, arity: 1 }, rest @ ..] = pieces else { return false };
1510 let converts =
1511 matches!(super::opcode_of(head), Some(Opcode::SExt | Opcode::ZExt | Opcode::Trunc));
1512 converts
1513 && matches!(
1514 rest,
1515 [Piece::App { head, arity: 1 }, Piece::Var { .. }] if head.starts_with("value.")
1516 )
1517 }
1518
1519 #[test]
1527 fn a_width_rule_writes_a_term_that_ends_where_the_one_it_matched_ended() {
1528 for rule in width::TABLE.rules {
1529 let [Piece::App { head, .. }, ..] = rule.replacement else {
1530 panic!("{} writes no head", rule.pattern)
1531 };
1532 let wrote = head.rsplit_once('.').expect("a replacement head names a width").1;
1533 let matched = rule
1534 .pattern
1535 .trim_start_matches('(')
1536 .split([' ', ')'])
1537 .next()
1538 .and_then(|head| head.rsplit_once('.'))
1539 .expect("a pattern head names a width")
1540 .1;
1541 assert_eq!(wrote, matched, "{} ends somewhere else", rule.pattern);
1542 }
1543 }
1544
1545 fn instruction(pieces: &'static [Piece]) -> bool {
1551 let [Piece::App { head, arity: 2 }, rest @ ..] = pieces else { return false };
1552 if super::opcode_of(head).is_none() {
1553 return false;
1554 }
1555 let operand = |pieces: &'static [Piece]| match pieces {
1556 [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
1557 if head.starts_with("value.") =>
1558 {
1559 Some(rest)
1560 }
1561 [Piece::App { head, arity: 1 }, Piece::Int(_), rest @ ..]
1562 if head.starts_with("iconst.") =>
1563 {
1564 Some(rest)
1565 }
1566 [Piece::App { head, arity: 1 }, Piece::Var { .. }, rest @ ..]
1567 if head.starts_with("iconst.") =>
1568 {
1569 Some(rest)
1570 }
1571 [Piece::App { head, arity: 1 }, Piece::Computed { .. }, rest @ ..]
1572 if head.starts_with("iconst.") =>
1573 {
1574 Some(rest)
1575 }
1576 _ => None,
1577 };
1578 operand(rest).and_then(operand).is_some_and(<[Piece]>::is_empty)
1579 }
1580
1581 #[test]
1585 fn each_table_holds_every_rule_its_file_writes() {
1586 let tier_one = include_str!("../rules/simplify.rules");
1587 let tier_two = include_str!("../rules/strength.rules");
1588 let tier_three = include_str!("../rules/canonical.rules");
1589 let tier_four = include_str!("../rules/width.rules");
1590 let tier_five = include_str!("../rules/compare.rules");
1591 let count = |text: &str| text.matches("(rule (simplify ").count();
1592 assert_eq!(identities::TABLE.rules.len(), count(tier_one));
1593 assert_eq!(strength::TABLE.rules.len(), count(tier_two));
1594 assert_eq!(canonical::TABLE.rules.len(), count(tier_three));
1595 assert_eq!(width::TABLE.rules.len(), count(tier_four));
1596 assert_eq!(compare::TABLE.rules.len(), count(tier_five));
1597 assert!(
1598 identities::TABLE.rules.len() > 100,
1599 "tier one is about a hundred rules and there are fewer"
1600 );
1601 assert!(
1602 strength::TABLE.rules.len() > 20,
1603 "tier two is the multiplications and the divisions and there are fewer"
1604 );
1605 assert_eq!(
1606 canonical::TABLE.rules.len(),
1607 20,
1608 "tier three is five commutative operators at four widths"
1609 );
1610 assert_eq!(
1611 width::TABLE.rules.len(),
1612 66,
1613 "tier four is the truncation and extension algebra over four widths, and the three \
1614 shapes of it that exist over the one bit a comparison answers in"
1615 );
1616 assert_eq!(
1617 compare::TABLE.rules.len(),
1618 72,
1619 "tier five is four predicates against each of four constants at four widths, and a \
1620 widened boolean against zero under two predicates at the same four"
1621 );
1622 }
1623
1624 #[test]
1627 fn a_pattern_is_reached_by_one_of_the_plans() {
1628 assert_eq!(PLANS.len(), 3);
1629 }
1630
1631 #[test]
1641 fn a_width_rule_is_only_matched_with_its_operand_expanded() {
1642 let (_, plans) = TABLES[2];
1643 assert_eq!(plans.len(), 1);
1644 assert_eq!(plans[0], EXPAND[0]);
1645 assert_eq!(plans[0][0], Shown::Expand);
1646 for plan in PLANS {
1647 assert_ne!(plan, plans[0], "no shared plan expands an operand");
1648 }
1649 assert_ne!(CANONICAL[0], plans[0]);
1650 assert_eq!(COMPARE[1][0], Shown::Expand);
1651 assert_eq!(COMPARE[1][1], Shown::Const);
1652 }
1653
1654 #[test]
1660 fn a_canonicalisation_is_only_matched_with_the_right_operand_refused() {
1661 let (_, plans) = TABLES[4];
1662 assert_eq!(plans.len(), 1);
1663 assert_eq!(plans[0], CANONICAL[0]);
1664 assert_eq!(plans[0][1], Shown::Var);
1665 for plan in PLANS {
1666 assert_ne!(plan, plans[0], "a shared plan would let a canonicalisation cycle");
1667 }
1668 }
1669
1670 #[test]
1677 fn a_comparison_rule_is_only_matched_with_the_constant_on_the_right() {
1678 let (_, plans) = TABLES[3];
1679 assert_eq!(plans.len(), 2);
1680 assert_eq!(plans, COMPARE);
1681 for plan in plans {
1682 assert_eq!(plan[1], Shown::Const);
1683 }
1684 assert_eq!(plans[0][0], Shown::Reg);
1685 assert_eq!(plans[1][0], Shown::Expand);
1686 }
1687
1688 fn edges(width: u32) -> [(i128, bool); 4] {
1693 let signed = 1i128 << (width - 1);
1694 [(0, false), (-1, false), (-signed, true), (signed - 1, true)]
1695 }
1696
1697 #[test]
1703 fn a_comparison_against_the_edge_of_its_type_folds_to_a_bit() {
1704 for width in [8u32, 16, 32, 64] {
1705 let ty = Type::int(width);
1706 for (edge, signed) in edges(width) {
1707 let below = edge == 0 || edge == -(1i128 << (width - 1));
1710 let (false_pred, true_pred) = match (signed, below) {
1711 (false, true) => (IntPred::Ult, IntPred::Uge),
1712 (false, false) => (IntPred::Ugt, IntPred::Ule),
1713 (true, true) => (IntPred::Slt, IntPred::Sge),
1714 (true, false) => (IntPred::Sgt, IntPred::Sle),
1715 };
1716 for (pred, answer) in [(false_pred, 0), (true_pred, -1)] {
1720 let (_, mut func, block) = blank();
1721 let x = func.append_param(block, ty);
1722 let mut build = Builder::new(&mut func, block);
1723 let bound = build.iconst(ty, edge);
1724 let cmp = build.icmp(pred, x, bound);
1725 build.ret(&[cmp]);
1726 assert!(simplify(&mut func), "i{width} {pred:?} {edge} was left alone");
1727 let got = returned(&func, block);
1728 assert_eq!(
1729 came_from(&func, got).0,
1730 Opcode::IConst,
1731 "i{width} {pred:?} {edge} did not fold"
1732 );
1733 assert_eq!(number(&func, got), answer, "i{width} {pred:?} {edge}");
1734 assert_eq!(func[got].ty, Type::int(1), "i{width} {pred:?} {edge} is a bit");
1735 }
1736 }
1737 }
1738 }
1739
1740 #[test]
1746 fn a_comparison_true_for_one_value_becomes_a_test_for_that_value() {
1747 for width in [8u32, 16, 32, 64] {
1748 let ty = Type::int(width);
1749 for (edge, signed) in edges(width) {
1750 let below = edge == 0 || edge == -(1i128 << (width - 1));
1751 let (eq_pred, ne_pred) = match (signed, below) {
1754 (false, true) => (IntPred::Ule, IntPred::Ugt),
1755 (false, false) => (IntPred::Uge, IntPred::Ult),
1756 (true, true) => (IntPred::Sle, IntPred::Sgt),
1757 (true, false) => (IntPred::Sge, IntPred::Slt),
1758 };
1759 for (pred, left) in [(eq_pred, IntPred::Eq), (ne_pred, IntPred::Ne)] {
1760 let (_, mut func, block) = blank();
1761 let x = func.append_param(block, ty);
1762 let mut build = Builder::new(&mut func, block);
1763 let bound = build.iconst(ty, edge);
1764 let cmp = build.icmp(pred, x, bound);
1765 build.ret(&[cmp]);
1766 assert!(simplify(&mut func), "i{width} {pred:?} {edge} was left alone");
1767 let got = returned(&func, block);
1768 assert_eq!(
1769 came_from(&func, got),
1770 (Opcode::ICmp, Extra::IntPred(left)),
1771 "i{width} {pred:?} {edge} kept the predicate it matched"
1772 );
1773 let args = operands(&func, got);
1774 assert_eq!(args[0], x, "i{width} {pred:?} {edge} lost its value");
1775 assert_eq!(number(&func, args[1]), edge, "i{width} {pred:?} {edge}");
1776 assert_eq!(func[args[1]].ty, ty, "i{width} {pred:?} {edge} narrowed its bound");
1780 }
1781 }
1782 }
1783 }
1784
1785 #[test]
1792 fn a_widened_boolean_compared_against_zero_is_the_boolean() {
1793 for width in [8u32, 16, 32, 64] {
1794 let ty = Type::int(width);
1795 let (_, mut func, block) = blank();
1796 let x = func.append_param(block, Type::int(32));
1797 let mut build = Builder::new(&mut func, block);
1798 let seven = build.iconst(Type::int(32), 7);
1799 let flag = build.icmp(IntPred::Eq, x, seven);
1800 let wide = build.unary(Opcode::ZExt, flag, ty);
1801 let zero = build.iconst(ty, 0);
1802 let test = build.icmp(IntPred::Ne, wide, zero);
1803 build.ret(&[test]);
1804 assert!(simplify(&mut func), "i{width} was left alone");
1805 let got = returned(&func, block);
1806 assert_eq!(got, flag, "i{width} did not end up on the comparison");
1807 assert_eq!(func[got].ty, Type::int(1), "i{width} is a bit");
1808 }
1809 }
1810
1811 #[test]
1823 fn a_widened_boolean_that_is_zero_is_the_boolean_negated() {
1824 for width in [8u32, 16, 32, 64] {
1825 let ty = Type::int(width);
1826 let (_, mut func, block) = blank();
1827 let x = func.append_param(block, Type::int(32));
1828 let mut build = Builder::new(&mut func, block);
1829 let seven = build.iconst(Type::int(32), 7);
1830 let flag = build.icmp(IntPred::Eq, x, seven);
1831 let wide = build.unary(Opcode::ZExt, flag, ty);
1832 let zero = build.iconst(ty, 0);
1833 let test = build.icmp(IntPred::Eq, wide, zero);
1834 build.ret(&[test]);
1835 assert!(simplify(&mut func), "i{width} was left alone");
1836 let got = returned(&func, block);
1837 assert_eq!(came_from(&func, got).0, Opcode::Xor, "i{width} is not a negation");
1838 assert!(simplify(&mut func), "i{width} kept the exclusive or");
1839 assert_eq!(
1840 came_from(&func, got),
1841 (Opcode::ICmp, Extra::IntPred(IntPred::Ne)),
1842 "i{width} did not come out as the opposite comparison"
1843 );
1844 let args = operands(&func, got);
1845 assert_eq!(args[0], x, "i{width} lost its value");
1846 assert_eq!(number(&func, args[1]), 7, "i{width} lost its bound");
1847 }
1848 }
1849
1850 #[test]
1855 fn a_constant_on_the_left_of_a_commutative_operation_moves_to_the_right() {
1856 for opcode in [Opcode::Add, Opcode::Mul, Opcode::And, Opcode::Or, Opcode::Xor] {
1857 for width in [8, 16, 32, 64] {
1858 let ty = Type::int(width);
1859 let (_, mut func, block) = one_block(ty);
1860 let x = func.append_param(block, ty);
1861 let mut build = Builder::new(&mut func, block);
1862 let three = build.iconst(ty, 3);
1866 let value = build.binary(opcode, three, x, Flags::NONE);
1867 build.ret(&[value]);
1868 assert!(simplify(&mut func), "{opcode:?} at i{width} was left alone");
1869 let args = operands(&func, returned(&func, block));
1870 assert_eq!(came_from(&func, returned(&func, block)).0, opcode);
1871 assert_eq!(args[0], x, "{opcode:?} at i{width} kept the value on the right");
1872 assert_eq!(number(&func, args[1]), 3, "{opcode:?} at i{width} lost its constant");
1873 }
1874 }
1875 }
1876
1877 #[test]
1884 fn an_operation_on_two_constants_is_not_swapped_back_and_forth() {
1885 let i32 = Type::int(32);
1886 let (_, mut func, block) = one_block(i32);
1887 let mut build = Builder::new(&mut func, block);
1888 let three = build.iconst(i32, 3);
1889 let five = build.iconst(i32, 5);
1890 let sum = build.binary(Opcode::Add, three, five, Flags::NONE);
1891 build.ret(&[sum]);
1892 assert!(!simplify(&mut func), "the constants were rearranged rather than left to folding");
1893 let args = operands(&func, returned(&func, block));
1894 assert_eq!(number(&func, args[0]), 3);
1895 assert_eq!(number(&func, args[1]), 5);
1896 }
1897
1898 #[test]
1903 fn a_constant_already_on_the_right_is_left_alone() {
1904 let i32 = Type::int(32);
1905 let (_, mut func, block) = one_block(i32);
1906 let x = func.append_param(block, i32);
1907 let mut build = Builder::new(&mut func, block);
1908 let three = build.iconst(i32, 3);
1909 let sum = build.binary(Opcode::Add, x, three, Flags::NONE);
1910 build.ret(&[sum]);
1911 assert!(!simplify(&mut func));
1912 let args = operands(&func, returned(&func, block));
1913 assert_eq!(args[0], x);
1914 assert_eq!(number(&func, args[1]), 3);
1915 }
1916
1917 #[test]
1923 fn a_subtraction_keeps_its_operands_where_they_are() {
1924 let i32 = Type::int(32);
1925 let (_, mut func, block) = one_block(i32);
1926 let x = func.append_param(block, i32);
1927 let mut build = Builder::new(&mut func, block);
1928 let three = build.iconst(i32, 3);
1929 let difference = build.binary(Opcode::Sub, three, x, Flags::NONE);
1930 build.ret(&[difference]);
1931 assert!(!simplify(&mut func));
1932 let args = operands(&func, returned(&func, block));
1933 assert_eq!(number(&func, args[0]), 3);
1934 assert_eq!(args[1], x);
1935 }
1936
1937 fn narrow_to_wide(takes: Type, gives: Type) -> (Interner, Func, Block) {
1940 let mut names = Interner::new();
1941 let name = names.intern("f");
1942 let signature = Signature::new().with_params(&[takes]).with_returns(&[gives]);
1943 let mut func = Func::new(name, signature);
1944 let block = func.create_block();
1945 (names, func, block)
1946 }
1947
1948 fn chain(
1953 inner: Opcode,
1954 outer: Opcode,
1955 from: Type,
1956 through: Type,
1957 to: Type,
1958 ) -> (Func, Block, Value) {
1959 let (_, mut func, block) = narrow_to_wide(from, to);
1960 let x = func.append_param(block, from);
1961 let mut build = Builder::new(&mut func, block);
1962 let middle = build.unary(inner, x, through);
1963 let outside = build.unary(outer, middle, to);
1964 build.ret(&[outside]);
1965 (func, block, x)
1966 }
1967
1968 #[test]
1973 fn truncating_an_extension_back_to_its_own_width_gives_the_value_back() {
1974 for extend in [Opcode::SExt, Opcode::ZExt] {
1975 for (narrow, wide) in [(8, 16), (8, 32), (8, 64), (16, 32), (16, 64), (32, 64)] {
1976 let (from, through) = (Type::int(narrow), Type::int(wide));
1977 let (mut func, block, x) = chain(extend, Opcode::Trunc, from, through, from);
1978 assert!(simplify(&mut func), "{extend:?} i{narrow} to i{wide} was left alone");
1979 assert_eq!(
1980 returned(&func, block),
1981 x,
1982 "{extend:?} i{narrow} to i{wide} and back did not give the value back"
1983 );
1984 }
1985 }
1986 }
1987
1988 #[test]
1991 fn truncating_an_extension_above_its_source_is_a_shorter_extension() {
1992 let (mut func, block, x) =
1993 chain(Opcode::SExt, Opcode::Trunc, Type::int(8), Type::int(64), Type::int(16));
1994 assert!(simplify(&mut func));
1995 let result = returned(&func, block);
1996 assert_eq!(came_from(&func, result).0, Opcode::SExt);
1997 assert_eq!(operands(&func, result), vec![x]);
1998 assert_eq!(func[result].ty, Type::int(16));
1999 }
2000
2001 #[test]
2004 fn truncating_an_extension_below_its_source_is_a_truncation_of_the_source() {
2005 let (mut func, block, x) =
2006 chain(Opcode::ZExt, Opcode::Trunc, Type::int(16), Type::int(32), Type::int(8));
2007 assert!(simplify(&mut func));
2008 let result = returned(&func, block);
2009 assert_eq!(came_from(&func, result).0, Opcode::Trunc);
2010 assert_eq!(operands(&func, result), vec![x]);
2011 assert_eq!(func[result].ty, Type::int(8));
2012 }
2013
2014 #[test]
2017 fn an_extension_of_an_extension_is_one_extension() {
2018 for (inner, outer, want) in [
2019 (Opcode::ZExt, Opcode::ZExt, Opcode::ZExt),
2020 (Opcode::SExt, Opcode::SExt, Opcode::SExt),
2021 (Opcode::ZExt, Opcode::SExt, Opcode::ZExt),
2022 ] {
2023 let (mut func, block, x) =
2024 chain(inner, outer, Type::int(8), Type::int(16), Type::int(64));
2025 assert!(simplify(&mut func), "{outer:?} of {inner:?} was left alone");
2026 let result = returned(&func, block);
2027 assert_eq!(came_from(&func, result).0, want, "{outer:?} of {inner:?}");
2028 assert_eq!(operands(&func, result), vec![x]);
2029 assert_eq!(func[result].ty, Type::int(64));
2030 }
2031 }
2032
2033 #[test]
2042 fn a_truncation_of_a_truncation_is_one_truncation() {
2043 for (from, through, to) in [(64u32, 32u32, 16u32), (64, 32, 8), (64, 16, 8), (32, 16, 8)] {
2044 let (mut func, block, x) = chain(
2045 Opcode::Trunc,
2046 Opcode::Trunc,
2047 Type::int(from),
2048 Type::int(through),
2049 Type::int(to),
2050 );
2051 assert!(simplify(&mut func), "i{from} to i{through} to i{to} was left alone");
2052 let result = returned(&func, block);
2053 assert_eq!(came_from(&func, result).0, Opcode::Trunc, "i{from} to i{through} to i{to}");
2054 assert_eq!(operands(&func, result), vec![x]);
2055 assert_eq!(func[result].ty, Type::int(to));
2056 }
2057 }
2058
2059 #[test]
2062 fn zero_extending_a_sign_extension_is_left_alone() {
2063 let (mut func, _, _) =
2064 chain(Opcode::SExt, Opcode::ZExt, Type::int(8), Type::int(16), Type::int(64));
2065 assert!(!simplify(&mut func), "a zero extension of a sign extension was rewritten");
2066 }
2067
2068 #[test]
2077 fn zero_extending_a_truncation_is_left_alone() {
2078 let (mut func, _, _) =
2079 chain(Opcode::Trunc, Opcode::ZExt, Type::int(64), Type::int(32), Type::int(64));
2080 assert!(!simplify(&mut func), "a zero extension of a truncation became a mask");
2081 }
2082
2083 #[test]
2090 fn a_width_rule_needs_an_operand_an_instruction_computed() {
2091 let (_, mut func, block) = narrow_to_wide(Type::int(64), Type::int(32));
2092 let x = func.append_param(block, Type::int(64));
2093 let mut build = Builder::new(&mut func, block);
2094 let narrowed = build.unary(Opcode::Trunc, x, Type::int(32));
2095 build.ret(&[narrowed]);
2096 assert!(!simplify(&mut func), "a truncation of a parameter was rewritten");
2097 }
2098
2099 #[test]
2100 fn adding_nothing_points_every_reader_at_the_operand() {
2101 let i32 = Type::int(32);
2102 let (_, mut func, block) = one_block(i32);
2103 let x = func.append_param(block, i32);
2104 let mut build = Builder::new(&mut func, block);
2105 let zero = build.iconst(i32, 0);
2106 let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
2107 build.ret(&[sum]);
2108 assert!(simplify(&mut func));
2109 assert_eq!(returned(&func, block), x);
2111 assert_eq!(came_from(&func, sum).0, Opcode::Add);
2112 }
2113
2114 #[test]
2117 fn the_constant_is_found_on_either_side_of_an_identity() {
2118 for swapped in [false, true] {
2119 let i32 = Type::int(32);
2120 let (_, mut func, block) = one_block(i32);
2121 let x = func.append_param(block, i32);
2122 let mut build = Builder::new(&mut func, block);
2123 let zero = build.iconst(i32, 0);
2124 let (lhs, rhs) = if swapped { (zero, x) } else { (x, zero) };
2125 let sum = build.binary(Opcode::Add, lhs, rhs, Flags::NONE);
2126 build.ret(&[sum]);
2127 assert!(simplify(&mut func), "swapped {swapped}");
2128 assert_eq!(returned(&func, block), x, "swapped {swapped}");
2129 }
2130 }
2131
2132 #[test]
2133 fn multiplying_by_nothing_becomes_the_constant_where_it_stands() {
2134 let i32 = Type::int(32);
2135 let (_, mut func, block) = one_block(i32);
2136 let x = func.append_param(block, i32);
2137 let mut build = Builder::new(&mut func, block);
2138 let zero = build.iconst(i32, 0);
2139 let product = build.binary(Opcode::Mul, x, zero, Flags::NONE);
2140 build.ret(&[product]);
2141 assert!(simplify(&mut func));
2142 assert_eq!(returned(&func, block), product);
2144 assert_eq!(came_from(&func, product).0, Opcode::IConst);
2145 assert_eq!(number(&func, product), 0);
2146 }
2147
2148 #[test]
2151 fn a_value_against_itself() {
2152 for bits in [8, 16, 32, 64] {
2153 let ty = Type::int(bits);
2154 let (_, mut func, block) = one_block(ty);
2155 let x = func.append_param(block, ty);
2156 let mut build = Builder::new(&mut func, block);
2157 let both = build.binary(Opcode::And, x, x, Flags::NONE);
2158 build.ret(&[both]);
2159 assert!(simplify(&mut func), "{bits} bits");
2160 assert_eq!(returned(&func, block), x, "{bits} bits");
2161
2162 let (_, mut func, block) = one_block(ty);
2163 let x = func.append_param(block, ty);
2164 let mut build = Builder::new(&mut func, block);
2165 let nothing = build.binary(Opcode::Sub, x, x, Flags::NONE);
2166 build.ret(&[nothing]);
2167 assert!(simplify(&mut func), "{bits} bits");
2168 assert_eq!(number(&func, nothing), 0, "{bits} bits");
2169 }
2170 }
2171
2172 #[test]
2176 fn every_comparison_of_a_value_with_itself_is_decided() {
2177 for bits in [8, 16, 32, 64] {
2178 for pred in IntPred::all() {
2179 let mut names = Interner::new();
2180 let name = names.intern("f");
2181 let int = Type::int(bits);
2182 let signature = Signature::new().with_params(&[int]).with_returns(&[Type::int(1)]);
2183 let mut func = Func::new(name, signature);
2184 let block = func.create_block();
2185 let x = func.append_param(block, int);
2186 let mut build = Builder::new(&mut func, block);
2187 let answer = build.icmp(pred, x, x);
2188 build.ret(&[answer]);
2189 assert!(simplify(&mut func), "{pred:?} at {bits} bits");
2190 let said = number(&func, answer);
2191 if matches!(
2192 pred,
2193 IntPred::Ne | IntPred::Slt | IntPred::Sgt | IntPred::Ult | IntPred::Ugt
2194 ) {
2195 assert_eq!(said, 0, "{pred:?} at {bits} bits");
2196 } else {
2197 assert_ne!(said, 0, "{pred:?} at {bits} bits");
2198 }
2199 }
2200 }
2201 }
2202
2203 #[test]
2207 fn dividing_by_one_and_the_remainder_that_goes_with_it() {
2208 let i32 = Type::int(32);
2209 let (_, mut func, block) = one_block(i32);
2210 let x = func.append_param(block, i32);
2211 let mut build = Builder::new(&mut func, block);
2212 let one = build.iconst(i32, 1);
2213 let quotient = build.binary(Opcode::SDiv, x, one, Flags::NONE);
2214 let rest = build.binary(Opcode::SRem, x, one, Flags::NONE);
2215 let sum = build.binary(Opcode::Add, quotient, rest, Flags::NONE);
2216 build.ret(&[sum]);
2217 assert!(simplify(&mut func));
2218 assert_eq!(number(&func, rest), 0);
2219 let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
2221 assert_eq!(func[func[inst].args][0], x);
2222 }
2223
2224 #[test]
2227 fn all_ones_at_one_bit_is_the_one_the_front_end_writes() {
2228 for written in [-1, 1] {
2229 let bit = Type::int(1);
2230 let (_, mut func, block) = one_block(bit);
2231 let x = func.append_param(block, bit);
2232 let mut build = Builder::new(&mut func, block);
2233 let ones = build.iconst(bit, written);
2234 let kept = build.binary(Opcode::And, x, ones, Flags::NONE);
2235 build.ret(&[kept]);
2236 assert!(simplify(&mut func), "written as {written}");
2237 assert_eq!(returned(&func, block), x, "written as {written}");
2238 }
2239 }
2240
2241 #[test]
2245 fn one_identity_feeding_another_is_followed_to_the_end() {
2246 let i32 = Type::int(32);
2247 let (_, mut func, block) = one_block(i32);
2248 let x = func.append_param(block, i32);
2249 let mut build = Builder::new(&mut func, block);
2250 let zero = build.iconst(i32, 0);
2251 let one = build.iconst(i32, 1);
2252 let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
2253 let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
2254 let shifted = build.binary(Opcode::Shl, product, zero, Flags::NONE);
2255 build.ret(&[shifted]);
2256 assert!(simplify(&mut func));
2257 assert_eq!(returned(&func, block), x);
2258 }
2259
2260 #[test]
2264 fn shifting_nothing_and_shifting_all_ones_with_the_sign() {
2265 for bits in [8, 16, 32, 64] {
2266 let ty = Type::int(bits);
2267 let cases = [
2268 (Opcode::Shl, 0_i128, 0_i128),
2269 (Opcode::LShr, 0, 0),
2270 (Opcode::AShr, 0, 0),
2271 (Opcode::AShr, -1, -1),
2272 ];
2273 for (opcode, from, expected) in cases {
2274 let (_, mut func, block) = one_block(ty);
2275 let count = func.append_param(block, ty);
2276 let mut build = Builder::new(&mut func, block);
2277 let value = build.iconst(ty, from);
2278 let shifted = build.binary(opcode, value, count, Flags::NONE);
2279 build.ret(&[shifted]);
2280 assert!(simplify(&mut func), "{opcode:?} of {from} at {bits} bits");
2281 let said = number(&func, shifted);
2282 assert_eq!(said, expected, "{opcode:?} of {from} at {bits} bits");
2283 }
2284 }
2285 }
2286
2287 #[test]
2290 fn all_ones_shifted_right_with_zeroes_coming_in_is_left_alone() {
2291 let i32 = Type::int(32);
2292 let (_, mut func, block) = one_block(i32);
2293 let count = func.append_param(block, i32);
2294 let mut build = Builder::new(&mut func, block);
2295 let ones = build.iconst(i32, -1);
2296 let shifted = build.binary(Opcode::LShr, ones, count, Flags::NONE);
2297 build.ret(&[shifted]);
2298 assert!(!simplify(&mut func));
2299 assert_eq!(came_from(&func, shifted).0, Opcode::LShr);
2300 }
2301
2302 #[test]
2303 fn an_instruction_no_rule_is_about_is_left_alone() {
2304 let i32 = Type::int(32);
2308 let (_, mut func, block) = one_block(i32);
2309 let x = func.append_param(block, i32);
2310 let mut build = Builder::new(&mut func, block);
2311 let three = build.iconst(i32, 3);
2312 let tripled = build.binary(Opcode::Mul, x, three, Flags::NONE);
2313 build.ret(&[tripled]);
2314 assert!(!simplify(&mut func), "no rule is about multiplying by three");
2315 assert_eq!(returned(&func, block), tripled);
2316 assert_eq!(came_from(&func, tripled).0, Opcode::Mul);
2317 }
2318
2319 #[test]
2320 fn multiplying_by_two_becomes_an_addition_of_the_value_with_itself() {
2321 let i32 = Type::int(32);
2322 let (_, mut func, block) = one_block(i32);
2323 let x = func.append_param(block, i32);
2324 let mut build = Builder::new(&mut func, block);
2325 let two = build.iconst(i32, 2);
2326 let doubled = build.binary(Opcode::Mul, x, two, Flags::NONE);
2327 build.ret(&[doubled]);
2328 assert!(simplify(&mut func));
2329 assert_eq!(returned(&func, block), doubled);
2331 assert_eq!(came_from(&func, doubled).0, Opcode::Add);
2332 assert_eq!(operands(&func, doubled), [x, x]);
2333 }
2337
2338 #[test]
2339 fn multiplying_by_a_power_of_two_becomes_a_shift_by_the_count_of_its_zeros() {
2340 let i32 = Type::int(32);
2341 let (_, mut func, block) = one_block(i32);
2342 let x = func.append_param(block, i32);
2343 let mut build = Builder::new(&mut func, block);
2344 let eight = build.iconst(i32, 8);
2345 let scaled = build.binary(Opcode::Mul, x, eight, Flags::NONE);
2346 build.ret(&[scaled]);
2347 assert!(simplify(&mut func));
2348 assert_eq!(returned(&func, block), scaled);
2349 assert_eq!(came_from(&func, scaled).0, Opcode::Shl);
2350 let args = operands(&func, scaled);
2351 assert_eq!(args[0], x);
2352 assert_eq!(number(&func, args[1]), 3);
2353 }
2354
2355 #[test]
2356 fn the_power_of_two_with_the_sign_bit_set_is_one_of_them() {
2357 let i32 = Type::int(32);
2362 let (_, mut func, block) = one_block(i32);
2363 let x = func.append_param(block, i32);
2364 let mut build = Builder::new(&mut func, block);
2365 let top = build.iconst(i32, 0x8000_0000);
2366 let scaled = build.binary(Opcode::Mul, x, top, Flags::NONE);
2367 build.ret(&[scaled]);
2368 assert!(simplify(&mut func));
2369 assert_eq!(came_from(&func, scaled).0, Opcode::Shl);
2370 assert_eq!(number(&func, operands(&func, scaled)[1]), 31);
2371 }
2372
2373 #[test]
2374 fn dividing_an_unsigned_value_by_a_power_of_two_becomes_a_shift() {
2375 let i32 = Type::int(32);
2376 let (_, mut func, block) = one_block(i32);
2377 let x = func.append_param(block, i32);
2378 let mut build = Builder::new(&mut func, block);
2379 let sixteen = build.iconst(i32, 16);
2380 let quotient = build.binary(Opcode::UDiv, x, sixteen, Flags::NONE);
2381 build.ret(&[quotient]);
2382 assert!(simplify(&mut func));
2383 assert_eq!(came_from(&func, quotient).0, Opcode::LShr);
2384 let args = operands(&func, quotient);
2385 assert_eq!(args[0], x);
2386 assert_eq!(number(&func, args[1]), 4);
2387 }
2388
2389 #[test]
2390 fn dividing_a_signed_value_by_a_power_of_two_is_left_alone() {
2391 let i32 = Type::int(32);
2396 let (_, mut func, block) = one_block(i32);
2397 let x = func.append_param(block, i32);
2398 let mut build = Builder::new(&mut func, block);
2399 let sixteen = build.iconst(i32, 16);
2400 let quotient = build.binary(Opcode::SDiv, x, sixteen, Flags::NONE);
2401 build.ret(&[quotient]);
2402 assert!(!simplify(&mut func), "no rule turns a signed division into a shift");
2403 assert_eq!(came_from(&func, quotient).0, Opcode::SDiv);
2404 }
2405
2406 #[test]
2407 fn the_unsigned_remainder_of_a_power_of_two_becomes_a_mask() {
2408 let i32 = Type::int(32);
2409 let (_, mut func, block) = one_block(i32);
2410 let x = func.append_param(block, i32);
2411 let mut build = Builder::new(&mut func, block);
2412 let thirty_two = build.iconst(i32, 32);
2413 let rest = build.binary(Opcode::URem, x, thirty_two, Flags::NONE);
2414 build.ret(&[rest]);
2415 assert!(simplify(&mut func));
2416 assert_eq!(came_from(&func, rest).0, Opcode::And);
2417 let args = operands(&func, rest);
2418 assert_eq!(args[0], x);
2419 assert_eq!(number(&func, args[1]), 31);
2420 }
2421
2422 #[test]
2423 fn a_division_by_a_constant_that_is_not_a_power_of_two_is_left_alone() {
2424 let i32 = Type::int(32);
2425 let (_, mut func, block) = one_block(i32);
2426 let x = func.append_param(block, i32);
2427 let mut build = Builder::new(&mut func, block);
2428 let ten = build.iconst(i32, 10);
2429 let quotient = build.binary(Opcode::UDiv, x, ten, Flags::NONE);
2430 build.ret(&[quotient]);
2431 assert!(!simplify(&mut func), "ten is no power of two");
2432 assert_eq!(came_from(&func, quotient).0, Opcode::UDiv);
2433 }
2434
2435 #[test]
2436 fn multiplying_by_minus_one_becomes_a_subtraction_from_a_zero_the_rewrite_defines() {
2437 let i32 = Type::int(32);
2440 let (_, mut func, block) = one_block(i32);
2441 let x = func.append_param(block, i32);
2442 let mut build = Builder::new(&mut func, block);
2443 let minus = build.iconst(i32, -1);
2444 let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
2445 build.ret(&[negated]);
2446 assert!(simplify(&mut func));
2447 assert_eq!(returned(&func, block), negated);
2448 assert_eq!(came_from(&func, negated).0, Opcode::Sub);
2449 let args = operands(&func, negated);
2450 assert_eq!(number(&func, args[0]), 0);
2451 assert_eq!(args[1], x);
2452 }
2453
2454 #[test]
2455 fn the_flags_of_the_instruction_a_strength_reduction_replaces_do_not_come_with_it() {
2456 let i32 = Type::int(32);
2460 let (_, mut func, block) = one_block(i32);
2461 let x = func.append_param(block, i32);
2462 let mut build = Builder::new(&mut func, block);
2463 let two = build.iconst(i32, 2);
2464 let doubled = build.binary(Opcode::Mul, x, two, Flags::NSW);
2465 build.ret(&[doubled]);
2466 assert!(simplify(&mut func));
2467 let rucc_ir::Def::Result { inst, .. } = func[doubled].def else { panic!("not a result") };
2468 assert_eq!(func[inst].flags, Flags::NONE);
2469 }
2470
2471 #[test]
2472 fn a_strength_reduction_leaves_the_verifier_nothing_to_complain_about() {
2473 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
2477 let i32 = Type::int(32);
2478 let (mut names, mut func, block) = one_block(i32);
2479 let mut module = Module::new(names.intern("test.c"), &target);
2480 let x = func.append_param(block, i32);
2481 let mut build = Builder::new(&mut func, block);
2482 let minus = build.iconst(i32, -1);
2483 let negated = build.binary(Opcode::Mul, x, minus, Flags::NONE);
2484 let two = build.iconst(i32, 2);
2485 let doubled = build.binary(Opcode::Mul, negated, two, Flags::NONE);
2486 build.ret(&[doubled]);
2487 assert!(simplify(&mut func));
2488 module.add_func(func);
2489 rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
2490 }
2491
2492 #[test]
2497 fn the_pass_leaves_the_verifier_nothing_to_complain_about() {
2498 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
2499 let i32 = Type::int(32);
2500 let (mut names, mut func, block) = one_block(i32);
2501 let mut module = Module::new(names.intern("test.c"), &target);
2502 let x = func.append_param(block, i32);
2503 let mut build = Builder::new(&mut func, block);
2504 let zero = build.iconst(i32, 0);
2505 let one = build.iconst(i32, 1);
2506 let sum = build.binary(Opcode::Add, x, zero, Flags::NONE);
2507 let product = build.binary(Opcode::Mul, sum, one, Flags::NONE);
2508 let gone = build.binary(Opcode::Sub, product, product, Flags::NONE);
2509 let total = build.binary(Opcode::Add, product, gone, Flags::NONE);
2510 build.ret(&[total]);
2511 assert!(simplify(&mut func));
2512 module.add_func(func);
2513 rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
2514 }
2515
2516 #[test]
2517 fn fuel_stops_an_identity_and_not_the_walk() {
2518 let i32 = Type::int(32);
2519 let (_, mut func, block) = one_block(i32);
2520 let x = func.append_param(block, i32);
2521 let mut build = Builder::new(&mut func, block);
2522 let zero = build.iconst(i32, 0);
2523 let first = build.binary(Opcode::Add, x, zero, Flags::NONE);
2524 let second = build.binary(Opcode::Sub, x, zero, Flags::NONE);
2525 let sum = build.binary(Opcode::Add, first, second, Flags::NONE);
2526 build.ret(&[sum]);
2527 let stats =
2528 Simplify.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
2529 assert!(stats.changed());
2530 assert_eq!(stats.total(Kind::Optimized), 1);
2531 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_RULE), 1);
2532 let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("not a result") };
2534 assert_eq!(func[func[inst].args], [x, second]);
2535 }
2536
2537 #[test]
2538 fn a_negated_float_comparison_becomes_the_opposite_predicate() {
2539 for pred in FloatPred::all() {
2542 let (_, mut func, block) = blank();
2543 let mut build = Builder::new(&mut func, block);
2544 let x = build.iconst(Type::int(64), 0);
2545 let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
2546 let y = build.iconst(Type::int(64), 1);
2548 let y = build.unary(Opcode::Bitcast, y, Type::float(Float::F64));
2549 let cmp = build.fcmp(pred, x, y, Flags::NONE);
2550 let ones = build.iconst(Type::int(1), -1);
2551 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
2552 build.ret(&[not]);
2553 assert!(simplify(&mut func), "{pred:?}");
2554 assert_eq!(
2555 came_from(&func, not),
2556 (Opcode::FCmp, Extra::FloatPred(pred.inverse())),
2557 "{pred:?}"
2558 );
2559 }
2560 }
2561
2562 #[test]
2563 fn a_negated_integer_comparison_becomes_the_opposite_predicate() {
2564 for pred in IntPred::all() {
2565 let (_, mut func, block) = blank();
2566 let mut build = Builder::new(&mut func, block);
2567 let x = build.iconst(Type::int(32), 3);
2568 let y = build.iconst(Type::int(32), 4);
2569 let cmp = build.icmp(pred, x, y);
2570 let ones = build.iconst(Type::int(1), -1);
2571 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
2572 build.ret(&[not]);
2573 assert!(simplify(&mut func), "{pred:?}");
2574 assert_eq!(
2575 came_from(&func, not),
2576 (Opcode::ICmp, Extra::IntPred(pred.inverse())),
2577 "{pred:?}"
2578 );
2579 }
2580 }
2581
2582 #[test]
2583 fn the_constant_is_found_on_either_side() {
2584 for swapped in [false, true] {
2585 let (_, mut func, block) = blank();
2586 let mut build = Builder::new(&mut func, block);
2587 let x = build.iconst(Type::int(32), 3);
2588 let y = build.iconst(Type::int(32), 4);
2589 let cmp = build.icmp(IntPred::Slt, x, y);
2590 let ones = build.iconst(Type::int(1), -1);
2591 let (lhs, rhs) = if swapped { (ones, cmp) } else { (cmp, ones) };
2592 let not = build.binary(Opcode::Xor, lhs, rhs, Flags::NONE);
2593 build.ret(&[not]);
2594 assert!(simplify(&mut func), "swapped {swapped}");
2595 assert_eq!(came_from(&func, not).1, Extra::IntPred(IntPred::Sge));
2596 }
2597 }
2598
2599 #[test]
2600 fn an_exclusive_or_of_two_comparisons_is_left_alone() {
2601 let (_, mut func, block) = blank();
2602 let mut build = Builder::new(&mut func, block);
2603 let x = build.iconst(Type::int(32), 3);
2604 let y = build.iconst(Type::int(32), 4);
2605 let a = build.icmp(IntPred::Slt, x, y);
2606 let b = build.icmp(IntPred::Sgt, x, y);
2607 let differ = build.binary(Opcode::Xor, a, b, Flags::NONE);
2608 build.ret(&[differ]);
2609 assert!(!simplify(&mut func));
2610 assert_eq!(came_from(&func, differ).0, Opcode::Xor);
2611 }
2612
2613 #[test]
2614 fn an_exclusive_or_of_something_that_is_not_a_comparison_is_left_alone() {
2615 let (_, mut func, block) = blank();
2616 let mut build = Builder::new(&mut func, block);
2617 let x = build.iconst(Type::int(32), 3);
2618 let narrow = build.unary(Opcode::Trunc, x, Type::int(1));
2619 let ones = build.iconst(Type::int(1), -1);
2620 let not = build.binary(Opcode::Xor, narrow, ones, Flags::NONE);
2621 build.ret(&[not]);
2622 assert!(!simplify(&mut func));
2623 assert_eq!(came_from(&func, not).0, Opcode::Xor);
2624 }
2625
2626 #[test]
2627 fn a_wider_exclusive_or_with_one_is_not_a_negation_and_is_left_alone() {
2628 let (_, mut func, block) = blank();
2629 let mut build = Builder::new(&mut func, block);
2630 let x = build.iconst(Type::int(32), 3);
2631 let y = build.iconst(Type::int(32), 4);
2632 let cmp = build.icmp(IntPred::Slt, x, y);
2633 let wide = build.unary(Opcode::ZExt, cmp, Type::int(32));
2634 let one = build.iconst(Type::int(32), 1);
2635 let flipped = build.binary(Opcode::Xor, wide, one, Flags::NONE);
2636 let narrow = build.unary(Opcode::Trunc, flipped, Type::int(1));
2637 build.ret(&[narrow]);
2638 assert!(!simplify(&mut func), "an i32 xor 1 flips one bit of thirty two");
2639 assert_eq!(came_from(&func, flipped).0, Opcode::Xor);
2640 }
2641
2642 #[test]
2643 fn the_comparisons_flags_travel_with_the_predicate() {
2644 let (_, mut func, block) = blank();
2645 let mut build = Builder::new(&mut func, block);
2646 let x = build.iconst(Type::int(64), 0);
2647 let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
2648 let y = build.iconst(Type::int(64), 1);
2650 let y = build.unary(Opcode::Bitcast, y, Type::float(Float::F64));
2651 let cmp = build.fcmp(FloatPred::Olt, x, y, Flags::FAST);
2652 let ones = build.iconst(Type::int(1), -1);
2653 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
2654 build.ret(&[not]);
2655 assert!(simplify(&mut func));
2656 let rucc_ir::Def::Result { inst, .. } = func[not].def else { panic!("not a result") };
2657 assert_eq!(func[inst].flags, Flags::FAST);
2660 }
2661
2662 #[test]
2663 fn fuel_stops_the_transformation_and_not_the_walk() {
2664 let (_, mut func, block) = blank();
2665 let mut build = Builder::new(&mut func, block);
2666 let x = build.iconst(Type::int(32), 3);
2667 let y = build.iconst(Type::int(32), 4);
2668 let a = build.icmp(IntPred::Slt, x, y);
2669 let b = build.icmp(IntPred::Sgt, x, y);
2670 let ones = build.iconst(Type::int(1), -1);
2671 let first = build.binary(Opcode::Xor, a, ones, Flags::NONE);
2672 let second = build.binary(Opcode::Xor, b, ones, Flags::NONE);
2673 let both = build.binary(Opcode::And, first, second, Flags::NONE);
2674 build.ret(&[both]);
2675 let stats =
2676 Simplify.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
2677 assert!(stats.changed());
2678 assert_eq!(stats.count(Kind::Optimized, super::FLIPPED), 1);
2679 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
2680 assert_eq!(came_from(&func, first).0, Opcode::ICmp);
2681 assert_eq!(came_from(&func, second).0, Opcode::Xor);
2682 }
2683
2684 fn a_pair() -> (Func, Block, Value, Value) {
2686 let mut names = Interner::new();
2687 let name = names.intern("f");
2688 let int = Type::int(32);
2689 let signature = Signature::new().with_params(&[int, int]).with_returns(&[Type::int(1)]);
2690 let mut func = Func::new(name, signature);
2691 let block = func.create_block();
2692 let x = func.append_param(block, int);
2693 let y = func.append_param(block, int);
2694 (func, block, x, y)
2695 }
2696
2697 fn a_float_pair() -> (Func, Block, Value, Value) {
2699 let mut names = Interner::new();
2700 let name = names.intern("f");
2701 let float = Type::float(Float::F64);
2702 let signature = Signature::new().with_params(&[float, float]).with_returns(&[Type::int(1)]);
2703 let mut func = Func::new(name, signature);
2704 let block = func.create_block();
2705 let x = func.append_param(block, float);
2706 let y = func.append_param(block, float);
2707 (func, block, x, y)
2708 }
2709
2710 #[test]
2718 fn the_opposite_of_a_float_predicate_is_the_buckets_it_leaves_out() {
2719 for pred in FloatPred::all() {
2720 assert_eq!(
2721 super::float_buckets(pred.inverse()),
2722 super::bucket::ALL_FLOAT ^ super::float_buckets(pred),
2723 "{pred:?}"
2724 );
2725 }
2726 }
2727
2728 #[test]
2733 fn swapping_a_float_predicates_operands_exchanges_below_and_above() {
2734 for pred in FloatPred::all() {
2735 let want = super::turned(super::float_buckets(pred));
2736 assert_eq!(super::float_buckets(pred.swapped()), want, "{pred:?}");
2737 }
2738 }
2739
2740 #[test]
2743 fn every_set_of_float_buckets_is_a_predicate() {
2744 for pred in FloatPred::all() {
2745 assert_eq!(super::float_pred(super::float_buckets(pred)), Some(pred), "{pred:?}");
2746 }
2747 for buckets in 0..=super::bucket::ALL_FLOAT {
2748 assert!(super::float_pred(buckets).is_some(), "{buckets} spells nothing");
2749 }
2750 }
2751
2752 #[test]
2754 fn an_integer_predicate_agrees_with_its_own_opposite_and_its_own_swap() {
2755 use super::bucket::ALL_INT;
2756 for pred in IntPred::all() {
2757 let (before, reading) = super::int_buckets(pred);
2758 let (opposite, other) = super::int_buckets(pred.inverse());
2759 assert_eq!(opposite, ALL_INT ^ before, "the opposite of {pred:?}");
2760 assert_eq!(other, reading, "the opposite of {pred:?} reads the operands differently");
2761 let (swapped, other) = super::int_buckets(pred.swapped());
2762 assert_eq!(swapped, super::turned(before), "the swap of {pred:?}");
2763 assert_eq!(other, reading, "the swap of {pred:?} reads the operands differently");
2764 }
2765 }
2766
2767 #[test]
2770 fn every_integer_predicate_is_read_back_as_itself() {
2771 for pred in IntPred::all() {
2772 let (buckets, reading) = super::int_buckets(pred);
2773 assert_eq!(super::int_pred(buckets, reading), Some(pred), "{pred:?}");
2774 }
2775 }
2776
2777 #[test]
2778 fn two_integer_comparisons_that_agree_about_nothing_are_false() {
2779 let (mut func, block, x, y) = a_pair();
2780 let mut build = Builder::new(&mut func, block);
2781 let same = build.icmp(IntPred::Eq, x, y);
2782 let differ = build.icmp(IntPred::Ne, x, y);
2783 let both = build.binary(Opcode::And, same, differ, Flags::NONE);
2784 build.ret(&[both]);
2785 assert!(simplify(&mut func));
2786 assert_eq!(number(&func, both), 0);
2787 }
2788
2789 #[test]
2790 fn two_integer_comparisons_that_cover_everything_are_true() {
2791 let (mut func, block, x, y) = a_pair();
2792 let mut build = Builder::new(&mut func, block);
2793 let above = build.icmp(IntPred::Sge, x, y);
2794 let below = build.icmp(IntPred::Slt, x, y);
2795 let either = build.binary(Opcode::Or, above, below, Flags::NONE);
2796 build.ret(&[either]);
2797 assert!(simplify(&mut func));
2798 assert_ne!(number(&func, either), 0);
2799 }
2800
2801 #[test]
2804 fn two_integer_comparisons_that_overlap_become_one() {
2805 let (mut func, block, x, y) = a_pair();
2806 let mut build = Builder::new(&mut func, block);
2807 let below = build.icmp(IntPred::Slt, x, y);
2808 let same = build.icmp(IntPred::Eq, x, y);
2809 let either = build.binary(Opcode::Or, below, same, Flags::NONE);
2810 build.ret(&[either]);
2811 assert!(simplify(&mut func));
2812 assert_eq!(came_from(&func, either), (Opcode::ICmp, Extra::IntPred(IntPred::Sle)));
2813 assert_eq!(operands(&func, either), [x, y]);
2814 }
2815
2816 #[test]
2819 fn the_second_comparison_is_read_in_the_first_ones_operand_order() {
2820 let (mut func, block, x, y) = a_pair();
2821 let mut build = Builder::new(&mut func, block);
2822 let below = build.icmp(IntPred::Slt, x, y);
2823 let above = build.icmp(IntPred::Slt, y, x);
2824 let both = build.binary(Opcode::And, below, above, Flags::NONE);
2825 build.ret(&[both]);
2826 assert!(simplify(&mut func));
2827 assert_eq!(number(&func, both), 0);
2828 }
2829
2830 #[test]
2833 fn an_equality_takes_the_ordering_of_the_comparison_beside_it() {
2834 for (ordered, want) in [(IntPred::Ult, IntPred::Ule), (IntPred::Slt, IntPred::Sle)] {
2835 let (mut func, block, x, y) = a_pair();
2836 let mut build = Builder::new(&mut func, block);
2837 let below = build.icmp(ordered, x, y);
2838 let same = build.icmp(IntPred::Eq, x, y);
2839 let either = build.binary(Opcode::Or, below, same, Flags::NONE);
2840 build.ret(&[either]);
2841 assert!(simplify(&mut func), "{ordered:?}");
2842 assert_eq!(came_from(&func, either).1, Extra::IntPred(want), "{ordered:?}");
2843 }
2844 }
2845
2846 #[test]
2849 fn a_signed_comparison_and_an_unsigned_one_are_left_alone() {
2850 let (mut func, block, x, y) = a_pair();
2851 let mut build = Builder::new(&mut func, block);
2852 let signed = build.icmp(IntPred::Slt, x, y);
2853 let unsigned = build.icmp(IntPred::Ugt, x, y);
2854 let both = build.binary(Opcode::And, signed, unsigned, Flags::NONE);
2855 build.ret(&[both]);
2856 assert!(!simplify(&mut func));
2857 assert_eq!(came_from(&func, both).0, Opcode::And);
2858 }
2859
2860 #[test]
2861 fn two_comparisons_about_different_operands_are_left_alone() {
2862 let (mut func, block, x, y) = a_pair();
2863 let mut build = Builder::new(&mut func, block);
2864 let other = build.iconst(Type::int(32), 7);
2865 let first = build.icmp(IntPred::Slt, x, y);
2866 let second = build.icmp(IntPred::Sgt, x, other);
2867 let both = build.binary(Opcode::And, first, second, Flags::NONE);
2868 build.ret(&[both]);
2869 assert!(!simplify(&mut func));
2870 assert_eq!(came_from(&func, both).0, Opcode::And);
2871 }
2872
2873 #[test]
2877 fn two_float_comparisons_that_agree_about_nothing_are_false() {
2878 let (mut func, block, x, y) = a_float_pair();
2879 let mut build = Builder::new(&mut func, block);
2880 let same = build.fcmp(FloatPred::Oeq, x, y, Flags::NONE);
2881 let differ = build.fcmp(FloatPred::Une, x, y, Flags::NONE);
2882 let both = build.binary(Opcode::And, same, differ, Flags::NONE);
2883 build.ret(&[both]);
2884 assert!(simplify(&mut func));
2885 assert_eq!(number(&func, both), 0);
2886 }
2887
2888 #[test]
2893 fn a_three_way_float_condition_folds_one_pair_at_a_time() {
2894 let (mut func, block, x, y) = a_float_pair();
2895 let mut build = Builder::new(&mut func, block);
2896 let neither = build.fcmp(FloatPred::Uno, x, y, Flags::NONE);
2897 let above = build.fcmp(FloatPred::Oge, x, y, Flags::NONE);
2898 let below = build.fcmp(FloatPred::Olt, x, y, Flags::NONE);
2899 let first = build.binary(Opcode::Or, neither, above, Flags::NONE);
2900 let whole = build.binary(Opcode::Or, first, below, Flags::NONE);
2901 build.ret(&[whole]);
2902 assert!(simplify(&mut func));
2903 assert_eq!(came_from(&func, first).1, Extra::FloatPred(FloatPred::Uge));
2904 assert_ne!(number(&func, whole), 0);
2905 }
2906
2907 fn a_float() -> (Func, Block, Value) {
2909 let mut names = Interner::new();
2910 let name = names.intern("f");
2911 let float = Type::float(Float::F64);
2912 let signature = Signature::new().with_params(&[float]).with_returns(&[Type::int(1)]);
2913 let mut func = Func::new(name, signature);
2914 let block = func.create_block();
2915 let x = func.append_param(block, float);
2916 (func, block, x)
2917 }
2918
2919 fn magnitude_of(build: &mut Builder<'_>, x: Value) -> Value {
2921 let bits = Type::int(64);
2922 let number = build.unary(Opcode::Bitcast, x, bits);
2923 let mask = build.iconst(bits, i128::from(i64::MAX));
2924 let cleared = build.binary(Opcode::And, number, mask, Flags::NONE);
2925 build.unary(Opcode::Bitcast, cleared, Type::float(Float::F64))
2926 }
2927
2928 #[test]
2931 fn a_magnitude_is_never_below_zero() {
2932 let (mut func, block, x) = a_float();
2933 let mut build = Builder::new(&mut func, block);
2934 let p = magnitude_of(&mut build, x);
2935 let zero = build.fconst(Type::float(Float::F64), 0);
2936 let below = build.fcmp(FloatPred::Olt, p, zero, Flags::NONE);
2937 build.ret(&[below]);
2938 assert!(simplify(&mut func));
2939 assert_eq!(number(&func, below), 0);
2940 }
2941
2942 #[test]
2945 fn zero_is_never_above_a_magnitude() {
2946 let (mut func, block, x) = a_float();
2947 let mut build = Builder::new(&mut func, block);
2948 let p = magnitude_of(&mut build, x);
2949 let zero = build.fconst(Type::float(Float::F64), 0);
2950 let above = build.fcmp(FloatPred::Ogt, zero, p, Flags::NONE);
2951 build.ret(&[above]);
2952 assert!(simplify(&mut func));
2953 assert_eq!(number(&func, above), 0);
2954 }
2955
2956 #[test]
2959 fn a_magnitude_at_or_below_zero_is_a_magnitude_equal_to_it() {
2960 let (mut func, block, x) = a_float();
2961 let mut build = Builder::new(&mut func, block);
2962 let p = magnitude_of(&mut build, x);
2963 let zero = build.fconst(Type::float(Float::F64), 0);
2964 let atmost = build.fcmp(FloatPred::Ole, p, zero, Flags::NONE);
2965 build.ret(&[atmost]);
2966 assert!(simplify(&mut func));
2967 assert_eq!(came_from(&func, atmost).1, Extra::FloatPred(FloatPred::Oeq));
2968 }
2969
2970 #[test]
2973 fn a_magnitude_is_never_at_or_below_a_negative_number() {
2974 let (mut func, block, x) = a_float();
2975 let mut build = Builder::new(&mut func, block);
2976 let p = magnitude_of(&mut build, x);
2977 let minus_one = build.fconst(Type::float(Float::F64), 0xbff0_0000_0000_0000);
2978 let atmost = build.fcmp(FloatPred::Ole, p, minus_one, Flags::NONE);
2979 build.ret(&[atmost]);
2980 assert!(simplify(&mut func));
2981 assert_eq!(number(&func, atmost), 0);
2982 }
2983
2984 #[test]
2988 fn a_magnitude_at_or_above_zero_is_still_a_question_about_a_nan() {
2989 let (mut func, block, x) = a_float();
2990 let mut build = Builder::new(&mut func, block);
2991 let p = magnitude_of(&mut build, x);
2992 let zero = build.fconst(Type::float(Float::F64), 0);
2993 let atleast = build.fcmp(FloatPred::Oge, p, zero, Flags::NONE);
2994 build.ret(&[atleast]);
2995 assert!(!simplify(&mut func));
2996 assert_eq!(came_from(&func, atleast).1, Extra::FloatPred(FloatPred::Oge));
2997 }
2998
2999 #[test]
3001 fn a_magnitude_against_a_positive_number_is_left_alone() {
3002 let (mut func, block, x) = a_float();
3003 let mut build = Builder::new(&mut func, block);
3004 let p = magnitude_of(&mut build, x);
3005 let one = build.fconst(Type::float(Float::F64), 0x3ff0_0000_0000_0000);
3006 let below = build.fcmp(FloatPred::Olt, p, one, Flags::NONE);
3007 build.ret(&[below]);
3008 assert!(!simplify(&mut func));
3009 assert_eq!(came_from(&func, below).1, Extra::FloatPred(FloatPred::Olt));
3010 }
3011
3012 #[test]
3015 fn a_mask_that_keeps_the_sign_bit_is_not_a_magnitude() {
3016 let (mut func, block, x) = a_float();
3017 let mut build = Builder::new(&mut func, block);
3018 let bits = Type::int(64);
3019 let number = build.unary(Opcode::Bitcast, x, bits);
3020 let mask = build.iconst(bits, -2);
3021 let cleared = build.binary(Opcode::And, number, mask, Flags::NONE);
3022 let p = build.unary(Opcode::Bitcast, cleared, Type::float(Float::F64));
3023 let zero = build.fconst(Type::float(Float::F64), 0);
3024 let below = build.fcmp(FloatPred::Olt, p, zero, Flags::NONE);
3025 build.ret(&[below]);
3026 assert!(!simplify(&mut func));
3027 assert_eq!(came_from(&func, below).1, Extra::FloatPred(FloatPred::Olt));
3028 }
3029
3030 #[test]
3033 fn a_magnitude_against_a_nan_is_settled_by_the_nan() {
3034 let (mut func, block, x) = a_float();
3035 let mut build = Builder::new(&mut func, block);
3036 let p = magnitude_of(&mut build, x);
3037 let nan = build.fconst(Type::float(Float::F64), NAN);
3038 let below = build.fcmp(FloatPred::Olt, p, nan, Flags::NONE);
3039 build.ret(&[below]);
3040 let stats = Simplify.run(
3041 &mut func,
3042 &mut crate::machine::fixtures::analyses(),
3043 &mut Fuel::unlimited(),
3044 );
3045 assert_eq!(stats.count(Kind::Optimized, super::MAGNITUDE), 0);
3046 assert_eq!(stats.count(Kind::Optimized, super::BOUNDED), 1);
3047 assert_eq!(number(&func, below), 0);
3048 }
3049
3050 const NAN: u128 = 0x7ff8_0000_0000_0000;
3052
3053 const INFINITY: u128 = 0x7ff0_0000_0000_0000;
3055
3056 #[test]
3060 fn a_nan_is_unordered_against_anything() {
3061 for (pred, answer) in [
3062 (FloatPred::Oeq, false),
3063 (FloatPred::Olt, false),
3064 (FloatPred::Ogt, false),
3065 (FloatPred::Ole, false),
3066 (FloatPred::Oge, false),
3067 (FloatPred::One, false),
3068 (FloatPred::Une, true),
3069 (FloatPred::Ult, true),
3070 (FloatPred::Uno, true),
3071 ] {
3072 let (mut func, block, x) = a_float();
3073 let mut build = Builder::new(&mut func, block);
3074 let nan = build.fconst(Type::float(Float::F64), NAN);
3075 let asked = build.fcmp(pred, nan, x, Flags::NONE);
3076 build.ret(&[asked]);
3077 assert!(simplify(&mut func), "{pred:?}");
3078 assert_eq!(number(&func, asked) != 0, answer, "{pred:?}");
3079 }
3080 }
3081
3082 #[test]
3086 fn nothing_is_above_a_positive_infinity() {
3087 let (mut func, block, x) = a_float();
3088 let mut build = Builder::new(&mut func, block);
3089 let infinity = build.fconst(Type::float(Float::F64), INFINITY);
3090 let above = build.fcmp(FloatPred::Ogt, x, infinity, Flags::NONE);
3091 let atmost = build.fcmp(FloatPred::Ole, x, infinity, Flags::NONE);
3092 let below = build.fcmp(FloatPred::Olt, x, infinity, Flags::NONE);
3093 build.ret(&[above, atmost, below]);
3094 assert!(simplify(&mut func));
3095 assert_eq!(number(&func, above), 0);
3096 assert_eq!(came_from(&func, atmost).1, Extra::FloatPred(FloatPred::Ole));
3097 assert_eq!(came_from(&func, below).1, Extra::FloatPred(FloatPred::Olt));
3098 }
3099
3100 #[test]
3102 fn a_negative_infinity_is_above_nothing() {
3103 let (mut func, block, x) = a_float();
3104 let mut build = Builder::new(&mut func, block);
3105 let infinity = build.fconst(Type::float(Float::F64), INFINITY | 1 << 63);
3106 let above = build.fcmp(FloatPred::Ogt, infinity, x, Flags::NONE);
3107 build.ret(&[above]);
3108 assert!(simplify(&mut func));
3109 assert_eq!(number(&func, above), 0);
3110 }
3111
3112 #[test]
3114 fn two_float_constants_are_an_answer() {
3115 let (mut func, block, _) = a_float();
3116 let mut build = Builder::new(&mut func, block);
3117 let one = build.fconst(Type::float(Float::F64), 0x3ff0_0000_0000_0000);
3118 let two = build.fconst(Type::float(Float::F64), 0x4000_0000_0000_0000);
3119 let below = build.fcmp(FloatPred::Olt, one, two, Flags::NONE);
3120 let equal = build.fcmp(FloatPred::Ueq, one, two, Flags::NONE);
3121 build.ret(&[below, equal]);
3122 assert!(simplify(&mut func));
3123 assert_ne!(number(&func, below), 0);
3124 assert_eq!(number(&func, equal), 0);
3125 }
3126
3127 #[test]
3130 fn a_value_against_itself_is_equal_or_a_nan() {
3131 let (mut func, block, x) = a_float();
3132 let mut build = Builder::new(&mut func, block);
3133 let below = build.fcmp(FloatPred::Olt, x, x, Flags::NONE);
3134 let differs = build.fcmp(FloatPred::Une, x, x, Flags::NONE);
3135 let same = build.fcmp(FloatPred::Oeq, x, x, Flags::NONE);
3136 build.ret(&[below, differs, same]);
3137 assert!(simplify(&mut func));
3138 assert_eq!(number(&func, below), 0);
3139 assert_eq!(came_from(&func, differs).1, Extra::FloatPred(FloatPred::Uno));
3140 assert_eq!(came_from(&func, same).1, Extra::FloatPred(FloatPred::Oeq));
3141 }
3142
3143 #[test]
3149 fn a_branch_in_front_settles_the_same_pair() {
3150 let (mut func, entry, x, y) = a_float_pair();
3151 let [then, other, join] = [(); 3].map(|()| func.create_block());
3152 let mut build = Builder::new(&mut func, entry);
3153 let neither = build.fcmp(FloatPred::Uno, x, y, Flags::NONE);
3154 build.br_if(neither, then, &[], other, &[]);
3155 let mut build = Builder::new(&mut func, other);
3156 let ordered = build.fcmp(FloatPred::Ord, x, y, Flags::NONE);
3157 let turned = build.fcmp(FloatPred::Ord, y, x, Flags::NONE);
3158 let above = build.fcmp(FloatPred::Ogt, x, y, Flags::NONE);
3159 build.jump(join, &[]);
3160 let mut build = Builder::new(&mut func, then);
3161 let there = build.fcmp(FloatPred::Ord, x, y, Flags::NONE);
3162 build.jump(join, &[]);
3163 let mut build = Builder::new(&mut func, join);
3164 let both = build.binary(Opcode::And, ordered, turned, Flags::NONE);
3165 let all = build.binary(Opcode::And, both, above, Flags::NONE);
3166 let all = build.binary(Opcode::And, all, there, Flags::NONE);
3167 build.ret(&[all]);
3168 assert!(simplify(&mut func));
3169 assert_ne!(number(&func, ordered), 0);
3170 assert_ne!(number(&func, turned), 0);
3171 assert_eq!(came_from(&func, above).1, Extra::FloatPred(FloatPred::Ogt));
3172 assert_eq!(number(&func, there), 0);
3173 }
3174
3175 #[test]
3178 fn a_join_settles_nothing() {
3179 let (mut func, entry, x, y) = a_float_pair();
3180 let [then, other, join] = [(); 3].map(|()| func.create_block());
3181 let mut build = Builder::new(&mut func, entry);
3182 let neither = build.fcmp(FloatPred::Uno, x, y, Flags::NONE);
3183 build.br_if(neither, then, &[], other, &[]);
3184 Builder::new(&mut func, then).jump(join, &[]);
3185 Builder::new(&mut func, other).jump(join, &[]);
3186 let mut build = Builder::new(&mut func, join);
3187 let ordered = build.fcmp(FloatPred::Ord, x, y, Flags::NONE);
3188 build.ret(&[ordered]);
3189 assert!(!simplify(&mut func));
3190 assert_eq!(came_from(&func, ordered).1, Extra::FloatPred(FloatPred::Ord));
3191 }
3192
3193 #[test]
3195 fn a_finite_bound_is_left_alone() {
3196 let (mut func, block, x) = a_float();
3197 let mut build = Builder::new(&mut func, block);
3198 let one = build.fconst(Type::float(Float::F64), 0x3ff0_0000_0000_0000);
3199 let below = build.fcmp(FloatPred::Olt, x, one, Flags::NONE);
3200 build.ret(&[below]);
3201 assert!(!simplify(&mut func));
3202 assert_eq!(came_from(&func, below).1, Extra::FloatPred(FloatPred::Olt));
3203 }
3204
3205 #[test]
3208 fn fuel_stops_the_magnitude_fold_and_not_the_walk() {
3209 let (mut func, block, x) = a_float();
3210 let mut build = Builder::new(&mut func, block);
3211 let p = magnitude_of(&mut build, x);
3212 let zero = build.fconst(Type::float(Float::F64), 0);
3213 let below = build.fcmp(FloatPred::Olt, p, zero, Flags::NONE);
3214 let also = build.fcmp(FloatPred::Olt, p, zero, Flags::NONE);
3215 let both = build.binary(Opcode::Or, below, also, Flags::NONE);
3216 build.ret(&[both]);
3217 let stats =
3218 Simplify.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
3219 assert_eq!(stats.count(Kind::Optimized, super::MAGNITUDE), 1);
3220 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_MAGNITUDE), 1);
3221 assert_eq!(number(&func, below), 0);
3222 assert_eq!(came_from(&func, also).0, Opcode::FCmp);
3223 }
3224
3225 #[test]
3228 fn two_comparisons_promised_different_things_are_left_alone() {
3229 let (mut func, block, x, y) = a_float_pair();
3230 let mut build = Builder::new(&mut func, block);
3231 let below = build.fcmp(FloatPred::Olt, x, y, Flags::FAST);
3232 let same = build.fcmp(FloatPred::Oeq, x, y, Flags::NONE);
3233 let either = build.binary(Opcode::Or, below, same, Flags::NONE);
3234 build.ret(&[either]);
3235 assert!(!simplify(&mut func));
3236 assert_eq!(came_from(&func, either).0, Opcode::Or);
3237 }
3238
3239 #[test]
3240 fn the_promise_both_comparisons_were_made_under_travels_to_the_one_that_replaces_them() {
3241 let (mut func, block, x, y) = a_float_pair();
3242 let mut build = Builder::new(&mut func, block);
3243 let below = build.fcmp(FloatPred::Olt, x, y, Flags::FAST);
3244 let same = build.fcmp(FloatPred::Oeq, x, y, Flags::FAST);
3245 let either = build.binary(Opcode::Or, below, same, Flags::NONE);
3246 build.ret(&[either]);
3247 assert!(simplify(&mut func));
3248 assert_eq!(came_from(&func, either).1, Extra::FloatPred(FloatPred::Ole));
3249 let rucc_ir::Def::Result { inst, .. } = func[either].def else { panic!("not a result") };
3250 assert_eq!(func[inst].flags, Flags::FAST);
3251 }
3252
3253 #[test]
3256 fn a_wider_and_of_two_comparisons_is_left_alone() {
3257 let (mut func, block, x, y) = a_pair();
3258 let mut build = Builder::new(&mut func, block);
3259 let same = build.icmp(IntPred::Eq, x, y);
3260 let differ = build.icmp(IntPred::Ne, x, y);
3261 let first = build.unary(Opcode::ZExt, same, Type::int(32));
3262 let second = build.unary(Opcode::ZExt, differ, Type::int(32));
3263 let both = build.binary(Opcode::And, first, second, Flags::NONE);
3264 let narrow = build.unary(Opcode::Trunc, both, Type::int(1));
3265 build.ret(&[narrow]);
3266 assert!(!simplify(&mut func));
3267 assert_eq!(came_from(&func, both).0, Opcode::And);
3268 }
3269
3270 #[test]
3271 fn fuel_stops_the_composite_fold_and_not_the_walk() {
3272 let (mut func, block, x, y) = a_pair();
3273 let mut build = Builder::new(&mut func, block);
3274 let same = build.icmp(IntPred::Eq, x, y);
3275 let differ = build.icmp(IntPred::Ne, x, y);
3276 let below = build.icmp(IntPred::Slt, x, y);
3277 let above = build.icmp(IntPred::Sgt, x, y);
3278 let first = build.binary(Opcode::And, same, differ, Flags::NONE);
3279 let second = build.binary(Opcode::And, below, above, Flags::NONE);
3280 let both = build.binary(Opcode::Or, first, second, Flags::NONE);
3281 build.ret(&[both]);
3282 let stats =
3283 Simplify.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
3284 assert!(stats.changed());
3285 assert_eq!(stats.count(Kind::Optimized, super::COMPOSITE), 1);
3286 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_COMPOSITE), 1);
3287 assert_eq!(came_from(&func, first).0, Opcode::IConst);
3288 assert_eq!(came_from(&func, second).0, Opcode::And);
3289 }
3290}