1use std::collections::HashMap;
51
52use rucc_ir::{Block, Def, Extra, Flags, Func, Imm, Inst, IntPred, Opcode, Type, Value};
53
54use crate::cfg::Cfg;
55use crate::loops::{LoopId, Loops};
56
57const STEP_LIMIT: u32 = 16;
64
65const FORWARD_LIMIT: u32 = 8;
71
72const ASSUMED_ITERATIONS: u64 = 10;
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub struct Invariant {
92 pub value: Option<Value>,
94 pub scale: i128,
96 pub offset: i128,
98}
99
100impl Invariant {
101 #[must_use]
103 pub fn number(offset: i128) -> Self {
104 Self { value: None, scale: 0, offset }
105 }
106
107 #[must_use]
109 pub fn of(value: Value) -> Self {
110 Self { value: Some(value), scale: 1, offset: 0 }
111 }
112
113 #[must_use]
115 pub fn as_number(self) -> Option<i128> {
116 (self.value.is_none() || self.scale == 0).then_some(self.offset)
117 }
118
119 #[must_use]
121 pub fn is_zero(self) -> bool {
122 self.as_number() == Some(0)
123 }
124
125 fn shared(self, other: Self) -> Option<Option<Value>> {
127 match (self.as_number().is_some(), other.as_number().is_some()) {
128 (true, _) => Some(other.value),
129 (_, true) => Some(self.value),
130 _ => (self.value == other.value).then_some(self.value),
131 }
132 }
133
134 #[must_use]
136 pub fn plus(self, other: Self) -> Option<Self> {
137 let value = self.shared(other)?;
138 Some(Self {
139 value,
140 scale: self.scale.checked_add(other.scale)?,
141 offset: self.offset.checked_add(other.offset)?,
142 })
143 }
144
145 #[must_use]
147 pub fn minus(self, other: Self) -> Option<Self> {
148 self.plus(other.negated()?)
149 }
150
151 #[must_use]
153 pub fn negated(self) -> Option<Self> {
154 Some(Self {
155 value: self.value,
156 scale: self.scale.checked_neg()?,
157 offset: self.offset.checked_neg()?,
158 })
159 }
160
161 #[must_use]
163 pub fn times(self, other: Self) -> Option<Self> {
164 let (symbol, by) = match (self.as_number(), other.as_number()) {
165 (Some(by), _) => (other, by),
166 (_, Some(by)) => (self, by),
167 _ => return None,
168 };
169 Some(Self {
170 value: symbol.value,
171 scale: symbol.scale.checked_mul(by)?,
172 offset: symbol.offset.checked_mul(by)?,
173 })
174 }
175}
176
177#[derive(Clone, Copy, Debug, PartialEq, Eq)]
179pub enum Evolution {
180 Invariant(Invariant),
182 Affine(Chrec),
184 Unknown,
186}
187
188impl Evolution {
189 #[must_use]
191 pub fn chrec(self) -> Option<Chrec> {
192 match self {
193 Self::Affine(chrec) => Some(chrec),
194 _ => None,
195 }
196 }
197
198 #[must_use]
200 pub fn invariant(self) -> Option<Invariant> {
201 match self {
202 Self::Invariant(inv) => Some(inv),
203 _ => None,
204 }
205 }
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
216pub struct Chrec {
217 pub base: Invariant,
219 pub step: Invariant,
221 pub ty: Type,
223 pub flags: Flags,
227}
228
229impl Chrec {
230 #[must_use]
232 pub fn does_not_wrap(self, signed: bool) -> bool {
233 self.flags.contains(if signed { Flags::NSW } else { Flags::NUW })
234 }
235}
236
237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
243pub enum Assumption {
244 Approaching,
257 NoWrap(Chrec),
262 StrictOverflow,
270}
271
272impl Assumption {
273 #[must_use]
279 pub fn describe(&self) -> String {
280 match self {
281 Self::Approaching => "the counter starts on the near side of its limit".to_string(),
282 Self::NoWrap(chrec) => {
283 format!("the induction variable does not wrap in i{}", chrec.ty.bits())
284 }
285 Self::StrictOverflow => {
286 "signed overflow is undefined, so -fwrapv withdraws this count".to_string()
287 }
288 }
289 }
290}
291
292#[derive(Clone, Copy, Debug, PartialEq, Eq)]
294pub enum Count {
295 Exact(u128),
297 Symbolic(Invariant),
299}
300
301#[derive(Clone, Debug, PartialEq, Eq)]
307pub struct Bound {
308 count: Count,
309 assumptions: Vec<Assumption>,
310}
311
312impl Bound {
313 #[must_use]
315 pub fn parts(&self) -> (Count, &[Assumption]) {
316 (self.count, &self.assumptions)
317 }
318
319 #[must_use]
321 pub fn assumptions(&self) -> &[Assumption] {
322 &self.assumptions
323 }
324
325 #[must_use]
330 pub fn proven(&self) -> Option<Count> {
331 self.assumptions.is_empty().then_some(self.count)
332 }
333}
334
335#[derive(Clone, Copy, Debug, PartialEq, Eq)]
341pub struct Estimate {
342 iterations: u64,
343 guessed: bool,
344}
345
346impl Estimate {
347 #[must_use]
349 pub fn iterations(self) -> u64 {
350 self.iterations
351 }
352
353 #[must_use]
355 pub fn is_guess(self) -> bool {
356 self.guessed
357 }
358}
359
360#[derive(Debug)]
367pub struct Scev<'a> {
368 func: &'a Func,
369 cfg: &'a Cfg,
370 loops: &'a Loops,
371 known: HashMap<(LoopId, Value), Evolution>,
372}
373
374impl<'a> Scev<'a> {
375 #[must_use]
377 pub fn new(func: &'a Func, cfg: &'a Cfg, loops: &'a Loops) -> Self {
378 Self { func, cfg, loops, known: HashMap::new() }
379 }
380
381 pub fn evolution(&mut self, id: LoopId, value: Value) -> Evolution {
383 if let Some(&known) = self.known.get(&(id, value)) {
384 return known;
385 }
386 self.known.insert((id, value), Evolution::Unknown);
392 let found = self.compute(id, value);
393 self.known.insert((id, value), found);
394 found
395 }
396
397 pub fn bound(&mut self, id: LoopId) -> Option<Bound> {
404 let exits: Vec<Block> = self.loops.exits(id).iter().map(|exit| exit.from).collect();
405 exits.into_iter().find_map(|from| self.bound_at(id, from))
406 }
407
408 pub fn estimate(&mut self, id: LoopId) -> Estimate {
410 match self.bound(id).map(|bound| bound.count) {
411 Some(Count::Exact(exact)) => {
412 Estimate { iterations: u64::try_from(exact).unwrap_or(u64::MAX), guessed: false }
413 }
414 _ => Estimate { iterations: ASSUMED_ITERATIONS, guessed: true },
415 }
416 }
417
418 fn compute(&mut self, id: LoopId, value: Value) -> Evolution {
420 if let Some(invariant) = self.invariant(id, value) {
421 return Evolution::Invariant(invariant);
422 }
423 match self.func[value].def {
424 Def::Param { block, index } if block == self.loops.header(id) => {
425 self.at_header(id, value, index as usize)
426 }
427 Def::Param { .. } => match self.forwarded(value) {
432 same if same == value => Evolution::Unknown,
433 through => self.evolution(id, through),
434 },
435 Def::Result { inst, .. } => self.at_inst(id, inst, value),
436 }
437 }
438
439 fn invariant(&self, id: LoopId, value: Value) -> Option<Invariant> {
441 if let Some((imm, ty)) = constant(self.func, value) {
442 return Some(Invariant::number(imm.signed(ty)));
443 }
444 self.loops.is_invariant(self.func, id, value).then(|| Invariant::of(value))
447 }
448
449 fn at_header(&mut self, id: LoopId, value: Value, index: usize) -> Evolution {
455 let (func, cfg, loops) = (self.func, self.cfg, self.loops);
456 let header = loops.header(id);
457 let [latch] = loops.latches(id) else { return Evolution::Unknown };
460 let mut entering = None;
461 let mut around = None;
462 for &pred in cfg.predecessors(header) {
463 let Some(arg) = argument(func, pred, header, index) else { return Evolution::Unknown };
464 let arg = self.forwarded(arg);
465 let slot = if pred == *latch { &mut around } else { &mut entering };
466 if slot.replace(arg).is_some_and(|old| old != arg) {
467 return Evolution::Unknown;
468 }
469 }
470 let (Some(entering), Some(around)) = (entering, around) else { return Evolution::Unknown };
471 let Some(base) = self.invariant(id, entering) else { return Evolution::Unknown };
472 let Some((step, flags)) = self.step(id, around, value, 0) else {
473 return Evolution::Unknown;
474 };
475 affine(base, step, func[value].ty, flags)
476 }
477
478 fn forwarded(&self, value: Value) -> Value {
491 let mut value = value;
492 for _ in 0..FORWARD_LIMIT {
493 let Def::Param { block, index } = self.func[value].def else { return value };
494 let [pred] = self.cfg.predecessors(block) else { return value };
495 let Some(arg) = argument(self.func, *pred, block, index as usize) else { return value };
496 if arg == value {
497 return value;
498 }
499 value = arg;
500 }
501 value
502 }
503
504 fn step(&self, id: LoopId, value: Value, of: Value, depth: u32) -> Option<(Invariant, Flags)> {
510 let value = self.forwarded(value);
511 if value == of {
512 return Some((Invariant::number(0), Flags::NSW.union(Flags::NUW)));
514 }
515 if depth >= STEP_LIMIT {
516 return None;
517 }
518 let Def::Result { inst, .. } = self.func[value].def else { return None };
519 let data = &self.func[inst];
520 let args = &self.func[data.args];
521 let (&lhs, &rhs) = (args.first()?, args.get(1)?);
522 let combine = |carried: (Invariant, Flags), other: Invariant, subtract: bool| {
523 let (delta, flags) = carried;
524 let moved = if subtract { delta.minus(other)? } else { delta.plus(other)? };
525 Some((moved, flags.intersection(data.flags)))
526 };
527 match data.opcode {
528 Opcode::Add => {
529 if let Some(carried) = self.step(id, lhs, of, depth + 1) {
530 return combine(carried, self.invariant(id, rhs)?, false);
531 }
532 combine(self.step(id, rhs, of, depth + 1)?, self.invariant(id, lhs)?, false)
533 }
534 Opcode::Sub => {
535 combine(self.step(id, lhs, of, depth + 1)?, self.invariant(id, rhs)?, true)
536 }
537 Opcode::PtrAdd => {
541 combine(self.step(id, lhs, of, depth + 1)?, self.invariant(id, rhs)?, false)
542 }
543 _ => None,
544 }
545 }
546
547 fn at_inst(&mut self, id: LoopId, inst: Inst, value: Value) -> Evolution {
549 let func = self.func;
550 let data = &func[inst];
551 let (opcode, flags) = (data.opcode, data.flags);
552 let args = &func[data.args];
553 let ty = func[value].ty;
554 let Some(&lhs) = args.first() else { return Evolution::Unknown };
555 match opcode {
556 Opcode::Add | Opcode::PtrAdd => {
557 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
558 let (left, right) = (self.evolution(id, lhs), self.evolution(id, rhs));
559 combine(left, right, ty, flags, false)
560 }
561 Opcode::Sub => {
562 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
563 let (left, right) = (self.evolution(id, lhs), self.evolution(id, rhs));
564 combine(left, right, ty, flags, true)
565 }
566 Opcode::Mul => {
567 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
568 let (left, right) = (self.evolution(id, lhs), self.evolution(id, rhs));
569 scale(left, right, ty, flags)
570 }
571 Opcode::Shl => {
576 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
577 let Some((count, count_ty)) = constant(func, rhs) else {
578 return Evolution::Unknown;
579 };
580 let count = count.unsigned();
581 if count >= u128::from(ty.bits()) || !count_ty.is_int() {
582 return Evolution::Unknown;
583 }
584 let by = Evolution::Invariant(Invariant::number(1i128 << count));
585 scale(self.evolution(id, lhs), by, ty, flags)
586 }
587 Opcode::SExt | Opcode::ZExt => self.extend(id, opcode, lhs, ty),
588 _ => Evolution::Unknown,
591 }
592 }
593
594 fn extend(&mut self, id: LoopId, opcode: Opcode, from: Value, to: Type) -> Evolution {
607 let narrow = self.func[from].ty;
608 let signed = opcode == Opcode::SExt;
609 match self.evolution(id, from) {
610 Evolution::Invariant(inv) => match inv.as_number() {
611 Some(number) if signed || number >= 0 => Evolution::Invariant(inv),
614 _ => Evolution::Unknown,
615 },
616 Evolution::Affine(chrec) if chrec.ty == narrow && chrec.does_not_wrap(signed) => {
617 let (Some(base), Some(step)) = (chrec.base.as_number(), chrec.step.as_number())
618 else {
619 return Evolution::Unknown;
620 };
621 Evolution::Affine(Chrec {
622 base: Invariant::number(base),
623 step: Invariant::number(step),
624 ty: to,
625 flags: chrec.flags,
626 })
627 }
628 _ => Evolution::Unknown,
629 }
630 }
631
632 fn bound_at(&mut self, id: LoopId, from: Block) -> Option<Bound> {
634 let func = self.func;
635 let term = func.terminator(from)?;
636 if func[term].opcode != Opcode::BrIf {
637 return None;
638 }
639 let args = &func[func[term].args];
640 let &cond = args.first()?;
641 let calls = &func[func.target_list(term)];
642 let (&taken, ¬_taken) = (calls.first()?, calls.get(1)?);
643 let stays = match (
646 self.loops.contains(id, taken.block),
647 self.loops.contains(id, not_taken.block),
648 ) {
649 (true, false) => true,
650 (false, true) => false,
651 _ => return None,
652 };
653
654 let Def::Result { inst, .. } = func[cond].def else { return None };
655 if func[inst].opcode != Opcode::ICmp {
656 return None;
657 }
658 let Extra::IntPred(pred) = func[inst].extra else { return None };
659 let pred = if stays { pred } else { invert(pred) };
662 let operands = &func[func[inst].args];
663 let (&lhs, &rhs) = (operands.first()?, operands.get(1)?);
664
665 let (chrec, limit, pred) = match (self.evolution(id, lhs), self.evolution(id, rhs)) {
668 (Evolution::Affine(chrec), other) => (chrec, other.invariant()?, pred),
669 (other, Evolution::Affine(chrec)) => (chrec, other.invariant()?, swap(pred)),
670 _ => return None,
671 };
672 solve(chrec, limit, pred)
673 }
674}
675
676fn combine(left: Evolution, right: Evolution, ty: Type, flags: Flags, subtract: bool) -> Evolution {
678 let apply = |a: Invariant, b: Invariant| if subtract { a.minus(b) } else { a.plus(b) };
679 match (left, right) {
680 (Evolution::Invariant(a), Evolution::Invariant(b)) => {
681 apply(a, b).map_or(Evolution::Unknown, Evolution::Invariant)
682 }
683 (Evolution::Affine(chrec), Evolution::Invariant(b)) => {
684 let Some(base) = apply(chrec.base, b) else { return Evolution::Unknown };
686 affine(base, chrec.step, ty, flags.intersection(chrec.flags))
687 }
688 (Evolution::Invariant(a), Evolution::Affine(chrec)) => {
689 let (Some(base), Some(step)) = (
690 apply(a, chrec.base),
691 if subtract { chrec.step.negated() } else { Some(chrec.step) },
692 ) else {
693 return Evolution::Unknown;
694 };
695 affine(base, step, ty, flags.intersection(chrec.flags))
696 }
697 (Evolution::Affine(a), Evolution::Affine(b)) => {
698 if a.ty != b.ty {
702 return Evolution::Unknown;
703 }
704 let (Some(base), Some(step)) = (apply(a.base, b.base), apply(a.step, b.step)) else {
705 return Evolution::Unknown;
706 };
707 affine(base, step, ty, flags.intersection(a.flags).intersection(b.flags))
708 }
709 _ => Evolution::Unknown,
710 }
711}
712
713fn scale(left: Evolution, right: Evolution, ty: Type, flags: Flags) -> Evolution {
715 let (chrec, by) = match (left, right) {
716 (Evolution::Invariant(a), Evolution::Invariant(b)) => {
717 return a.times(b).map_or(Evolution::Unknown, Evolution::Invariant);
718 }
719 (Evolution::Affine(chrec), Evolution::Invariant(by))
720 | (Evolution::Invariant(by), Evolution::Affine(chrec)) => (chrec, by),
721 _ => return Evolution::Unknown,
724 };
725 let (Some(base), Some(step)) = (chrec.base.times(by), chrec.step.times(by)) else {
726 return Evolution::Unknown;
727 };
728 affine(base, step, ty, flags.intersection(chrec.flags))
729}
730
731fn affine(base: Invariant, step: Invariant, ty: Type, flags: Flags) -> Evolution {
739 if step.is_zero() {
740 return Evolution::Invariant(base);
741 }
742 Evolution::Affine(Chrec { base, step, ty, flags })
743}
744
745fn solve(chrec: Chrec, limit: Invariant, pred: IntPred) -> Option<Bound> {
747 let step = chrec.step.as_number()?;
750 if step == 0 {
751 return None;
752 }
753 let signed = matches!(pred, IntPred::Slt | IntPred::Sle | IntPred::Sgt | IntPred::Sge);
754
755 let mut assumptions = Vec::new();
756 if !chrec.does_not_wrap(signed) {
757 assumptions.push(Assumption::NoWrap(chrec));
758 }
759 if signed {
760 assumptions.push(Assumption::StrictOverflow);
761 }
762
763 let (base, limit) = if signed {
766 (chrec.base, limit)
767 } else {
768 (as_unsigned(chrec.base, chrec.ty)?, as_unsigned(limit, chrec.ty)?)
769 };
770
771 let apart = step.unsigned_abs();
775 match (pred, step > 0) {
776 (IntPred::Slt | IntPred::Ult, true) => {
777 ordered(limit.minus(base)?, apart, false, assumptions)
778 }
779 (IntPred::Sle | IntPred::Ule, true) => {
780 ordered(limit.minus(base)?, apart, true, assumptions)
781 }
782 (IntPred::Sgt | IntPred::Ugt, false) => {
783 ordered(base.minus(limit)?, apart, false, assumptions)
784 }
785 (IntPred::Sge | IntPred::Uge, false) => {
786 ordered(base.minus(limit)?, apart, true, assumptions)
787 }
788 (IntPred::Ne, _) => {
789 let distance = if step > 0 { limit.minus(base)? } else { base.minus(limit)? };
790 landing(distance, apart, assumptions)
791 }
792 _ => None,
795 }
796}
797
798fn as_unsigned(inv: Invariant, ty: Type) -> Option<Invariant> {
809 match inv.as_number() {
810 Some(number) if number >= 0 => Some(inv),
811 Some(number) => {
812 let bits = ty.is_int().then(|| ty.bits()).filter(|&bits| bits < 127)?;
815 Some(Invariant::number(number & ((1i128 << bits) - 1)))
816 }
817 None => (inv.scale == 1 && inv.offset == 0).then_some(inv),
820 }
821}
822
823fn ordered(
825 distance: Invariant,
826 step: u128,
827 inclusive: bool,
828 mut assumptions: Vec<Assumption>,
829) -> Option<Bound> {
830 match distance.as_number() {
831 Some(exact) => {
832 if exact < 0 {
833 return Some(Bound { count: Count::Exact(0), assumptions: Vec::new() });
837 }
838 let count = (exact.unsigned_abs() + u128::from(inclusive)).div_ceil(step);
840 Some(Bound { count: Count::Exact(count), assumptions })
841 }
842 None if step == 1 => {
845 assumptions.push(Assumption::Approaching);
846 let count = distance.plus(Invariant::number(i128::from(inclusive)))?;
847 Some(Bound { count: Count::Symbolic(count), assumptions })
848 }
849 None => None,
850 }
851}
852
853fn landing(distance: Invariant, step: u128, mut assumptions: Vec<Assumption>) -> Option<Bound> {
862 match distance.as_number() {
863 Some(exact) => {
864 let travel = u128::try_from(exact).ok()?;
865 (travel % step == 0).then(|| Bound { count: Count::Exact(travel / step), assumptions })
868 }
869 None if step == 1 => {
873 assumptions.push(Assumption::Approaching);
874 Some(Bound { count: Count::Symbolic(distance), assumptions })
875 }
876 None => None,
877 }
878}
879
880fn invert(pred: IntPred) -> IntPred {
882 match pred {
883 IntPred::Eq => IntPred::Ne,
884 IntPred::Ne => IntPred::Eq,
885 IntPred::Slt => IntPred::Sge,
886 IntPred::Sle => IntPred::Sgt,
887 IntPred::Sgt => IntPred::Sle,
888 IntPred::Sge => IntPred::Slt,
889 IntPred::Ult => IntPred::Uge,
890 IntPred::Ule => IntPred::Ugt,
891 IntPred::Ugt => IntPred::Ule,
892 IntPred::Uge => IntPred::Ult,
893 }
894}
895
896fn swap(pred: IntPred) -> IntPred {
898 match pred {
899 IntPred::Eq => IntPred::Eq,
900 IntPred::Ne => IntPred::Ne,
901 IntPred::Slt => IntPred::Sgt,
902 IntPred::Sle => IntPred::Sge,
903 IntPred::Sgt => IntPred::Slt,
904 IntPred::Sge => IntPred::Sle,
905 IntPred::Ult => IntPred::Ugt,
906 IntPred::Ule => IntPred::Uge,
907 IntPred::Ugt => IntPred::Ult,
908 IntPred::Uge => IntPred::Ule,
909 }
910}
911
912fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
914 let Def::Result { inst, .. } = func[value].def else { return None };
915 if func[inst].opcode != Opcode::IConst {
916 return None;
917 }
918 let Extra::Imm(at) = func[inst].extra else { return None };
919 let ty = func[value].ty;
920 ty.is_int().then(|| (func[at], ty))
921}
922
923fn argument(func: &Func, pred: Block, block: Block, index: usize) -> Option<Value> {
929 let term = func.terminator(pred)?;
930 let mut found = None;
931 for call in func.successors(term) {
932 if call.block != block {
933 continue;
934 }
935 let arg = *func[call.args].get(index)?;
936 if found.replace(arg).is_some_and(|old| old != arg) {
937 return None;
938 }
939 }
940 found
941}
942
943#[cfg(test)]
944mod tests {
945 use rucc_base::Interner;
946 use rucc_ir::{Builder, Flags, Func, IntPred, Opcode, Signature, Type, Value};
947
948 use crate::cfg::Cfg;
949 use crate::dom::Dominators;
950 use crate::loops::{LoopId, Loops};
951 use crate::scev::{Assumption, Bound, Count, Evolution, Invariant, Scev};
952
953 struct Counted {
964 func: Func,
965 counter: Value,
966 next: Value,
967 }
968
969 fn counted(ty: Type, from: i128, to: i128, step: i128, pred: IntPred, flags: Flags) -> Counted {
970 let (it, ()) = counted_with(ty, from, to, step, pred, flags, |_, _| ());
971 it
972 }
973
974 fn counted_with<T>(
980 ty: Type,
981 from: i128,
982 to: i128,
983 step: i128,
984 pred: IntPred,
985 flags: Flags,
986 extra: impl FnOnce(&mut Builder<'_>, Value) -> T,
987 ) -> (Counted, T) {
988 let mut names = Interner::new();
989 let mut func = Func::new(names.intern("f"), Signature::new());
990 let entry = func.create_block();
991 let header = func.create_block();
992 let body = func.create_block();
993 let exit = func.create_block();
994 let counter = func.append_param(header, ty);
995
996 let mut build = Builder::new(&mut func, entry);
997 let start = build.iconst(ty, from);
998 build.jump(header, &[start]);
999
1000 let mut build = Builder::new(&mut func, header);
1001 let limit = build.iconst(ty, to);
1002 let test = build.icmp(pred, counter, limit);
1003 build.br_if(test, body, &[], exit, &[]);
1004
1005 let mut build = Builder::new(&mut func, body);
1006 let derived = extra(&mut build, counter);
1007 let by = build.iconst(ty, step);
1008 let next = build.binary(Opcode::Add, counter, by, flags);
1009 build.jump(header, &[next]);
1010
1011 let mut build = Builder::new(&mut func, exit);
1012 build.ret(&[]);
1013
1014 (Counted { func, counter, next }, derived)
1015 }
1016
1017 fn analyse(func: &Func) -> (Cfg, Loops) {
1019 let cfg = Cfg::new(func);
1020 let doms = Dominators::new(&cfg);
1021 let loops = Loops::new(&cfg, &doms);
1022 (cfg, loops)
1023 }
1024
1025 fn evolution(func: &Func, value: Value) -> Evolution {
1027 let (cfg, loops) = analyse(func);
1028 let id = loops.roots()[0];
1029 Scev::new(func, &cfg, &loops).evolution(id, value)
1030 }
1031
1032 fn bound(func: &Func) -> Option<Bound> {
1034 let (cfg, loops) = analyse(func);
1035 let id: LoopId = loops.roots()[0];
1036 Scev::new(func, &cfg, &loops).bound(id)
1037 }
1038
1039 #[test]
1040 fn a_counter_from_zero_by_one_is_the_chrec_everyone_expects() {
1041 let it = counted(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW);
1042 let chrec = evolution(&it.func, it.counter).chrec().expect("the counter evolves");
1043 assert_eq!(chrec.base, Invariant::number(0));
1044 assert_eq!(chrec.step, Invariant::number(1));
1045 assert_eq!(chrec.ty, Type::int(32));
1046 assert!(chrec.does_not_wrap(true));
1047 }
1048
1049 #[test]
1050 fn the_value_fed_back_is_the_chrec_one_step_along() {
1051 let it = counted(Type::int(32), 5, 100, 3, IntPred::Slt, Flags::NSW);
1052 let chrec = evolution(&it.func, it.next).chrec().expect("the increment evolves");
1053 assert_eq!(chrec.base, Invariant::number(8));
1054 assert_eq!(chrec.step, Invariant::number(3));
1055 }
1056
1057 #[test]
1058 fn a_multiple_of_the_counter_plus_a_number_is_a_chrec_of_its_own() {
1059 let (it, shifted) =
1062 counted_with(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW, |build, counter| {
1063 let two = build.iconst(Type::int(32), 2);
1064 let three = build.iconst(Type::int(32), 3);
1065 let doubled = build.binary(Opcode::Mul, counter, two, Flags::NSW);
1066 build.binary(Opcode::Add, doubled, three, Flags::NSW)
1067 });
1068
1069 let chrec = evolution(&it.func, shifted).chrec().expect("it evolves");
1070 assert_eq!(chrec.base, Invariant::number(3));
1071 assert_eq!(chrec.step, Invariant::number(2));
1072 }
1073
1074 #[test]
1075 fn a_shift_by_a_constant_scales_the_chrec_and_a_shift_past_the_width_does_not() {
1076 let (it, (scaled, poison)) =
1077 counted_with(Type::int(32), 1, 100, 1, IntPred::Slt, Flags::NSW, |build, counter| {
1078 let three = build.iconst(Type::int(32), 3);
1079 let wide = build.iconst(Type::int(32), 32);
1080 (
1081 build.binary(Opcode::Shl, counter, three, Flags::NSW),
1082 build.binary(Opcode::Shl, counter, wide, Flags::NSW),
1083 )
1084 });
1085
1086 let chrec = evolution(&it.func, scaled).chrec().expect("it evolves");
1087 assert_eq!(chrec.base, Invariant::number(8));
1088 assert_eq!(chrec.step, Invariant::number(8));
1089 assert_eq!(evolution(&it.func, poison), Evolution::Unknown);
1092 }
1093
1094 #[test]
1095 fn a_pointer_walked_by_the_element_size_is_a_chrec_in_bytes() {
1096 let mut names = Interner::new();
1100 let mut func = Func::new(names.intern("f"), Signature::new());
1101 let entry = func.create_block();
1102 let header = func.create_block();
1103 let body = func.create_block();
1104 let exit = func.create_block();
1105 let start = func.append_param(entry, Type::PTR);
1106 let cursor = func.append_param(header, Type::PTR);
1107
1108 let mut build = Builder::new(&mut func, entry);
1109 build.jump(header, &[start]);
1110 let mut build = Builder::new(&mut func, header);
1111 let done = build.icmp(IntPred::Eq, cursor, start);
1112 build.br_if(done, exit, &[], body, &[]);
1113 let mut build = Builder::new(&mut func, body);
1114 let four = build.iconst(Type::int(64), 4);
1115 let next = build.binary(Opcode::PtrAdd, cursor, four, Flags::NONE);
1116 build.jump(header, &[next]);
1117 let mut build = Builder::new(&mut func, exit);
1118 build.ret(&[]);
1119
1120 let chrec = evolution(&func, cursor).chrec().expect("the cursor evolves");
1121 assert_eq!(chrec.base, Invariant::of(start));
1122 assert_eq!(chrec.step, Invariant::number(4));
1123 assert_eq!(chrec.ty, Type::PTR);
1124 }
1125
1126 #[test]
1127 fn a_counter_in_unsigned_char_wraps_and_does_not_widen_without_a_promise() {
1128 let (it, wide) =
1132 counted_with(Type::int(8), 0, 100, 1, IntPred::Ult, Flags::NONE, |build, counter| {
1133 build.unary(Opcode::ZExt, counter, Type::int(32))
1134 });
1135 let chrec = evolution(&it.func, it.counter).chrec().expect("the counter evolves");
1136 assert_eq!(chrec.ty, Type::int(8));
1137 assert!(!chrec.does_not_wrap(false));
1138 assert_eq!(evolution(&it.func, wide), Evolution::Unknown);
1139 }
1140
1141 #[test]
1142 fn a_counter_in_short_widens_when_the_increment_promised_it_would_not_wrap() {
1143 let (it, (wide, zero_extended)) =
1144 counted_with(Type::int(16), 0, 100, 1, IntPred::Slt, Flags::NSW, |build, counter| {
1145 (
1146 build.unary(Opcode::SExt, counter, Type::int(32)),
1147 build.unary(Opcode::ZExt, counter, Type::int(32)),
1148 )
1149 });
1150
1151 let chrec = evolution(&it.func, wide).chrec().expect("it widens");
1152 assert_eq!(chrec.ty, Type::int(32));
1153 assert_eq!(chrec.base, Invariant::number(0));
1154 assert_eq!(chrec.step, Invariant::number(1));
1155 assert_eq!(evolution(&it.func, zero_extended), Evolution::Unknown);
1157 }
1158
1159 #[test]
1160 fn a_step_of_zero_is_invariant_and_has_no_trip_count() {
1161 let it = counted(Type::int(32), 0, 100, 0, IntPred::Slt, Flags::NSW);
1165 assert!(matches!(evolution(&it.func, it.counter), Evolution::Invariant(_)));
1166 assert_eq!(bound(&it.func), None);
1167 }
1168
1169 #[test]
1170 fn a_counted_loop_has_the_count_anyone_would_work_out_by_hand() {
1171 let it = counted(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW);
1172 let found = bound(&it.func).expect("it is counted");
1173 let (count, assumptions) = found.parts();
1174 assert_eq!(count, Count::Exact(100));
1175 assert_eq!(assumptions, [Assumption::StrictOverflow]);
1178 assert_eq!(found.proven(), None);
1179 }
1180
1181 #[test]
1182 fn a_step_that_overshoots_still_takes_the_iteration_that_overshot() {
1183 let it = counted(Type::int(32), 0, 10, 3, IntPred::Slt, Flags::NSW);
1186 let (count, _) = bound(&it.func).expect("it is counted").parts();
1187 assert_eq!(count, Count::Exact(4));
1188 }
1189
1190 #[test]
1191 fn an_inclusive_test_runs_one_more_time() {
1192 let it = counted(Type::int(32), 0, 10, 1, IntPred::Sle, Flags::NSW);
1193 let (count, _) = bound(&it.func).expect("it is counted").parts();
1194 assert_eq!(count, Count::Exact(11));
1195 }
1196
1197 #[test]
1198 fn a_loop_whose_test_fails_first_time_runs_no_times_and_rests_on_nothing() {
1199 let it = counted(Type::int(32), 10, 0, 1, IntPred::Slt, Flags::NSW);
1200 let found = bound(&it.func).expect("it is counted");
1201 assert_eq!(found.proven(), Some(Count::Exact(0)));
1202 assert!(found.assumptions().is_empty());
1203 }
1204
1205 #[test]
1206 fn counting_down_is_the_same_problem_with_the_ends_swapped() {
1207 let it = counted(Type::int(32), 10, 0, -1, IntPred::Sgt, Flags::NSW);
1208 let (count, _) = bound(&it.func).expect("it is counted").parts();
1209 assert_eq!(count, Count::Exact(10));
1210 }
1211
1212 #[test]
1213 fn an_unsigned_test_does_not_drag_in_the_signed_overflow_assumption() {
1214 let it = counted(Type::int(32), 0, 100, 1, IntPred::Ult, Flags::NUW);
1215 let found = bound(&it.func).expect("it is counted");
1216 assert_eq!(found.proven(), Some(Count::Exact(100)));
1217 }
1218
1219 #[test]
1220 fn a_test_against_something_the_loop_does_not_change_gives_a_symbolic_count() {
1221 let mut names = Interner::new();
1224 let mut func = Func::new(names.intern("f"), Signature::new());
1225 let entry = func.create_block();
1226 let header = func.create_block();
1227 let body = func.create_block();
1228 let exit = func.create_block();
1229 let limit = func.append_param(entry, Type::int(32));
1230 let counter = func.append_param(header, Type::int(32));
1231
1232 let mut build = Builder::new(&mut func, entry);
1233 let zero = build.iconst(Type::int(32), 0);
1234 build.jump(header, &[zero]);
1235 let mut build = Builder::new(&mut func, header);
1236 let test = build.icmp(IntPred::Slt, counter, limit);
1237 build.br_if(test, body, &[], exit, &[]);
1238 let mut build = Builder::new(&mut func, body);
1239 let one = build.iconst(Type::int(32), 1);
1240 let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
1241 build.jump(header, &[next]);
1242 let mut build = Builder::new(&mut func, exit);
1243 build.ret(&[]);
1244
1245 let found = bound(&func).expect("it is counted");
1246 let (count, assumptions) = found.parts();
1247 assert_eq!(count, Count::Symbolic(Invariant::of(limit)));
1248 assert!(assumptions.contains(&Assumption::Approaching), "{assumptions:?}");
1249 assert!(assumptions.contains(&Assumption::StrictOverflow), "{assumptions:?}");
1250 assert_eq!(found.proven(), None);
1251 }
1252
1253 #[test]
1254 fn a_counter_without_a_no_wrap_promise_carries_the_assumption_instead() {
1255 let it = counted(Type::int(32), 0, 100, 1, IntPred::Ult, Flags::NONE);
1256 let found = bound(&it.func).expect("it is counted");
1257 let (_, assumptions) = found.parts();
1258 assert!(assumptions.iter().any(|a| matches!(a, Assumption::NoWrap(_))), "{assumptions:?}");
1259 }
1260
1261 #[test]
1262 fn a_test_that_ends_the_loop_when_it_succeeds_is_read_the_other_way_round() {
1263 let mut names = Interner::new();
1266 let mut func = Func::new(names.intern("f"), Signature::new());
1267 let entry = func.create_block();
1268 let header = func.create_block();
1269 let body = func.create_block();
1270 let exit = func.create_block();
1271 let counter = func.append_param(header, Type::int(32));
1272
1273 let mut build = Builder::new(&mut func, entry);
1274 let zero = build.iconst(Type::int(32), 0);
1275 build.jump(header, &[zero]);
1276 let mut build = Builder::new(&mut func, header);
1277 let limit = build.iconst(Type::int(32), 100);
1278 let done = build.icmp(IntPred::Sge, counter, limit);
1279 build.br_if(done, exit, &[], body, &[]);
1280 let mut build = Builder::new(&mut func, body);
1281 let one = build.iconst(Type::int(32), 1);
1282 let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
1283 build.jump(header, &[next]);
1284 let mut build = Builder::new(&mut func, exit);
1285 build.ret(&[]);
1286
1287 let (count, _) = bound(&func).expect("it is counted").parts();
1288 assert_eq!(count, Count::Exact(100));
1289 }
1290
1291 #[test]
1292 fn an_unsigned_limit_past_the_middle_of_its_type_is_not_a_negative_one() {
1293 let it = counted(Type::int(8), 0, 200, 1, IntPred::Ult, Flags::NUW);
1297 let found = bound(&it.func).expect("it is counted");
1298 assert_eq!(found.proven(), Some(Count::Exact(200)));
1299 }
1300
1301 #[test]
1302 fn a_walk_that_lands_on_a_not_equal_limit_exactly_is_counted() {
1303 let it = counted(Type::int(32), 0, 10, 1, IntPred::Ne, Flags::NSW.union(Flags::NUW));
1307 let found = bound(&it.func).expect("it lands on its limit");
1308 assert_eq!(found.proven(), Some(Count::Exact(10)));
1311 }
1312
1313 #[test]
1314 fn a_counter_stepping_away_from_a_not_equal_limit_is_not_a_loop_that_runs_no_times() {
1315 let it = counted(Type::int(32), 48, 15, 1, IntPred::Ne, Flags::NSW);
1319 assert_eq!(bound(&it.func), None);
1320 }
1321
1322 #[test]
1323 fn a_counter_stepping_over_a_not_equal_limit_never_arrives_either() {
1324 let it = counted(Type::int(32), 0, 10, 3, IntPred::Ne, Flags::NSW);
1327 assert_eq!(bound(&it.func), None);
1328 }
1329
1330 #[test]
1331 fn an_estimate_is_the_count_when_there_is_one_and_a_guess_when_there_is_not() {
1332 let counted_loop = counted(Type::int(32), 0, 7, 1, IntPred::Slt, Flags::NSW);
1333 let (cfg, loops) = analyse(&counted_loop.func);
1334 let id = loops.roots()[0];
1335 let estimate = Scev::new(&counted_loop.func, &cfg, &loops).estimate(id);
1336 assert_eq!(estimate.iterations(), 7);
1337 assert!(!estimate.is_guess());
1338
1339 let uncounted = counted(Type::int(32), 0, 100, 0, IntPred::Slt, Flags::NSW);
1342 let (cfg, loops) = analyse(&uncounted.func);
1343 let id = loops.roots()[0];
1344 let estimate = Scev::new(&uncounted.func, &cfg, &loops).estimate(id);
1345 assert!(estimate.is_guess());
1346 assert_eq!(estimate.iterations(), super::ASSUMED_ITERATIONS);
1347 }
1348
1349 #[test]
1350 fn a_value_the_loop_does_not_touch_is_invariant_rather_than_unknown() {
1351 let it = counted(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW);
1352 let (cfg, loops) = analyse(&it.func);
1353 let id = loops.roots()[0];
1354 let mut scev = Scev::new(&it.func, &cfg, &loops);
1355 assert_eq!(
1357 scev.evolution(id, it.counter).chrec().expect("it evolves").base,
1358 Invariant::number(0)
1359 );
1360 }
1361
1362 #[test]
1363 fn a_back_edge_of_its_own_does_not_hide_the_counter() {
1364 let mut names = Interner::new();
1369 let mut func = Func::new(names.intern("f"), Signature::new());
1370 let entry = func.create_block();
1371 let header = func.create_block();
1372 let body = func.create_block();
1373 let latch = func.create_block();
1374 let exit = func.create_block();
1375 let counter = func.append_param(header, Type::int(32));
1376 let carried = func.append_param(latch, Type::int(32));
1377
1378 let start = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1379 Builder::new(&mut func, entry).jump(header, &[start]);
1380
1381 let mut build = Builder::new(&mut func, header);
1382 let limit = build.iconst(Type::int(32), 100);
1383 let test = build.icmp(IntPred::Slt, counter, limit);
1384 build.br_if(test, body, &[], exit, &[]);
1385
1386 let mut build = Builder::new(&mut func, body);
1387 let by = build.iconst(Type::int(32), 1);
1388 let next = build.binary(Opcode::Add, counter, by, Flags::NSW);
1389 build.jump(latch, &[next]);
1390
1391 Builder::new(&mut func, latch).jump(header, &[carried]);
1392 Builder::new(&mut func, exit).ret(&[]);
1393
1394 let chrec = evolution(&func, counter).chrec().expect("the counter still evolves");
1395 assert_eq!(chrec.base, Invariant::number(0));
1396 assert_eq!(chrec.step, Invariant::number(1));
1397 let (count, _) = bound(&func).expect("it is still counted").parts();
1398 assert_eq!(count, Count::Exact(100));
1399 }
1400
1401 #[test]
1402 fn every_assumption_says_what_it_is_in_a_line() {
1403 let it = counted(Type::int(8), 0, 100, 1, IntPred::Ult, Flags::NONE);
1404 let found = bound(&it.func).expect("it is counted");
1405 for assumption in found.assumptions() {
1406 let line = assumption.describe();
1407 assert!(!line.is_empty());
1408 assert!(!line.contains('\n'), "an assumption is one line: {line}");
1409 }
1410 }
1411}