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 #[must_use]
349 pub fn under_undefined_overflow(&self) -> Option<Count> {
350 self.assumptions
351 .iter()
352 .all(|rests_on| matches!(rests_on, Assumption::StrictOverflow))
353 .then_some(self.count)
354 }
355}
356
357#[derive(Clone, Copy, Debug, PartialEq, Eq)]
363pub struct Estimate {
364 iterations: u64,
365 guessed: bool,
366}
367
368impl Estimate {
369 #[must_use]
371 pub fn iterations(self) -> u64 {
372 self.iterations
373 }
374
375 #[must_use]
377 pub fn is_guess(self) -> bool {
378 self.guessed
379 }
380}
381
382#[derive(Debug)]
389pub struct Scev<'a> {
390 func: &'a Func,
391 cfg: &'a Cfg,
392 loops: &'a Loops,
393 known: HashMap<(LoopId, Value), Evolution>,
394}
395
396impl<'a> Scev<'a> {
397 #[must_use]
399 pub fn new(func: &'a Func, cfg: &'a Cfg, loops: &'a Loops) -> Self {
400 Self { func, cfg, loops, known: HashMap::new() }
401 }
402
403 pub fn evolution(&mut self, id: LoopId, value: Value) -> Evolution {
405 if let Some(&known) = self.known.get(&(id, value)) {
406 return known;
407 }
408 self.known.insert((id, value), Evolution::Unknown);
414 let found = self.compute(id, value);
415 self.known.insert((id, value), found);
416 found
417 }
418
419 pub fn bound(&mut self, id: LoopId) -> Option<Bound> {
426 let exits: Vec<Block> = self.loops.exits(id).iter().map(|exit| exit.from).collect();
427 exits.into_iter().find_map(|from| self.bound_at(id, from))
428 }
429
430 pub fn estimate(&mut self, id: LoopId) -> Estimate {
432 match self.bound(id).map(|bound| bound.count) {
433 Some(Count::Exact(exact)) => {
434 Estimate { iterations: u64::try_from(exact).unwrap_or(u64::MAX), guessed: false }
435 }
436 _ => Estimate { iterations: ASSUMED_ITERATIONS, guessed: true },
437 }
438 }
439
440 fn compute(&mut self, id: LoopId, value: Value) -> Evolution {
442 if let Some(invariant) = self.invariant(id, value) {
443 return Evolution::Invariant(invariant);
444 }
445 match self.func[value].def {
446 Def::Param { block, index } if block == self.loops.header(id) => {
447 self.at_header(id, value, index as usize)
448 }
449 Def::Param { .. } => match self.forwarded(value) {
454 same if same == value => Evolution::Unknown,
455 through => self.evolution(id, through),
456 },
457 Def::Result { inst, .. } => self.at_inst(id, inst, value),
458 }
459 }
460
461 fn invariant(&self, id: LoopId, value: Value) -> Option<Invariant> {
463 if let Some((imm, ty)) = constant(self.func, value) {
464 return Some(Invariant::number(imm.signed(ty)));
465 }
466 self.loops.is_invariant(self.func, id, value).then(|| Invariant::of(value))
469 }
470
471 fn at_header(&mut self, id: LoopId, value: Value, index: usize) -> Evolution {
477 let (func, cfg, loops) = (self.func, self.cfg, self.loops);
478 let header = loops.header(id);
479 let [latch] = loops.latches(id) else { return Evolution::Unknown };
482 let mut entering = None;
483 let mut around = None;
484 for &pred in cfg.predecessors(header) {
485 let Some(arg) = argument(func, pred, header, index) else { return Evolution::Unknown };
486 let arg = self.forwarded(arg);
487 let slot = if pred == *latch { &mut around } else { &mut entering };
488 if slot.replace(arg).is_some_and(|old| old != arg) {
489 return Evolution::Unknown;
490 }
491 }
492 let (Some(entering), Some(around)) = (entering, around) else { return Evolution::Unknown };
493 let Some(base) = self.invariant(id, entering) else { return Evolution::Unknown };
494 let Some((step, flags)) = self.step(id, around, value, 0) else {
495 return Evolution::Unknown;
496 };
497 affine(base, step, func[value].ty, flags)
498 }
499
500 fn forwarded(&self, value: Value) -> Value {
513 let mut value = value;
514 for _ in 0..FORWARD_LIMIT {
515 let Def::Param { block, index } = self.func[value].def else { return value };
516 let [pred] = self.cfg.predecessors(block) else { return value };
517 let Some(arg) = argument(self.func, *pred, block, index as usize) else { return value };
518 if arg == value {
519 return value;
520 }
521 value = arg;
522 }
523 value
524 }
525
526 fn step(&self, id: LoopId, value: Value, of: Value, depth: u32) -> Option<(Invariant, Flags)> {
532 let value = self.forwarded(value);
533 if value == of {
534 return Some((Invariant::number(0), Flags::NSW.union(Flags::NUW)));
536 }
537 if depth >= STEP_LIMIT {
538 return None;
539 }
540 let Def::Result { inst, .. } = self.func[value].def else { return None };
541 let data = &self.func[inst];
542 let args = &self.func[data.args];
543 let (&lhs, &rhs) = (args.first()?, args.get(1)?);
544 let combine = |carried: (Invariant, Flags), other: Invariant, subtract: bool| {
545 let (delta, flags) = carried;
546 let moved = if subtract { delta.minus(other)? } else { delta.plus(other)? };
547 Some((moved, flags.intersection(data.flags)))
548 };
549 match data.opcode {
550 Opcode::Add => {
551 if let Some(carried) = self.step(id, lhs, of, depth + 1) {
552 return combine(carried, self.invariant(id, rhs)?, false);
553 }
554 combine(self.step(id, rhs, of, depth + 1)?, self.invariant(id, lhs)?, false)
555 }
556 Opcode::Sub => {
557 combine(self.step(id, lhs, of, depth + 1)?, self.invariant(id, rhs)?, true)
558 }
559 Opcode::PtrAdd => {
563 combine(self.step(id, lhs, of, depth + 1)?, self.invariant(id, rhs)?, false)
564 }
565 _ => None,
566 }
567 }
568
569 fn at_inst(&mut self, id: LoopId, inst: Inst, value: Value) -> Evolution {
571 let func = self.func;
572 let data = &func[inst];
573 let (opcode, flags) = (data.opcode, data.flags);
574 let args = &func[data.args];
575 let ty = func[value].ty;
576 let Some(&lhs) = args.first() else { return Evolution::Unknown };
577 match opcode {
578 Opcode::Add | Opcode::PtrAdd => {
579 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
580 let (left, right) = (self.evolution(id, lhs), self.evolution(id, rhs));
581 combine(left, right, ty, flags, false)
582 }
583 Opcode::Sub => {
584 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
585 let (left, right) = (self.evolution(id, lhs), self.evolution(id, rhs));
586 combine(left, right, ty, flags, true)
587 }
588 Opcode::Mul => {
589 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
590 let (left, right) = (self.evolution(id, lhs), self.evolution(id, rhs));
591 scale(left, right, ty, flags)
592 }
593 Opcode::Shl => {
598 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
599 let Some((count, count_ty)) = constant(func, rhs) else {
600 return Evolution::Unknown;
601 };
602 let count = count.unsigned();
603 if count >= u128::from(ty.bits()) || !count_ty.is_int() {
604 return Evolution::Unknown;
605 }
606 let by = Evolution::Invariant(Invariant::number(1i128 << count));
607 scale(self.evolution(id, lhs), by, ty, flags)
608 }
609 Opcode::SExt | Opcode::ZExt => self.extend(id, opcode, lhs, ty),
610 _ => Evolution::Unknown,
613 }
614 }
615
616 fn extend(&mut self, id: LoopId, opcode: Opcode, from: Value, to: Type) -> Evolution {
629 let narrow = self.func[from].ty;
630 let signed = opcode == Opcode::SExt;
631 match self.evolution(id, from) {
632 Evolution::Invariant(inv) => match inv.as_number() {
633 Some(number) if signed || number >= 0 => Evolution::Invariant(inv),
636 _ => Evolution::Unknown,
637 },
638 Evolution::Affine(chrec) if chrec.ty == narrow && chrec.does_not_wrap(signed) => {
639 let (Some(base), Some(step)) = (chrec.base.as_number(), chrec.step.as_number())
640 else {
641 return Evolution::Unknown;
642 };
643 Evolution::Affine(Chrec {
644 base: Invariant::number(base),
645 step: Invariant::number(step),
646 ty: to,
647 flags: chrec.flags,
648 })
649 }
650 _ => Evolution::Unknown,
651 }
652 }
653
654 fn bound_at(&mut self, id: LoopId, from: Block) -> Option<Bound> {
656 let func = self.func;
657 let term = func.terminator(from)?;
658 if func[term].opcode != Opcode::BrIf {
659 return None;
660 }
661 let args = &func[func[term].args];
662 let &cond = args.first()?;
663 let calls = &func[func.target_list(term)];
664 let (&taken, ¬_taken) = (calls.first()?, calls.get(1)?);
665 let stays = match (
668 self.loops.contains(id, taken.block),
669 self.loops.contains(id, not_taken.block),
670 ) {
671 (true, false) => true,
672 (false, true) => false,
673 _ => return None,
674 };
675
676 let Def::Result { inst, .. } = func[cond].def else { return None };
677 if func[inst].opcode != Opcode::ICmp {
678 return None;
679 }
680 let Extra::IntPred(pred) = func[inst].extra else { return None };
681 let pred = if stays { pred } else { invert(pred) };
684 let operands = &func[func[inst].args];
685 let (&lhs, &rhs) = (operands.first()?, operands.get(1)?);
686
687 let (chrec, limit, pred) = match (self.evolution(id, lhs), self.evolution(id, rhs)) {
690 (Evolution::Affine(chrec), other) => (chrec, other.invariant()?, pred),
691 (other, Evolution::Affine(chrec)) => (chrec, other.invariant()?, swap(pred)),
692 _ => return None,
693 };
694 solve(chrec, limit, pred)
695 }
696}
697
698fn combine(left: Evolution, right: Evolution, ty: Type, flags: Flags, subtract: bool) -> Evolution {
700 let apply = |a: Invariant, b: Invariant| if subtract { a.minus(b) } else { a.plus(b) };
701 match (left, right) {
702 (Evolution::Invariant(a), Evolution::Invariant(b)) => {
703 apply(a, b).map_or(Evolution::Unknown, Evolution::Invariant)
704 }
705 (Evolution::Affine(chrec), Evolution::Invariant(b)) => {
706 let Some(base) = apply(chrec.base, b) else { return Evolution::Unknown };
708 affine(base, chrec.step, ty, flags.intersection(chrec.flags))
709 }
710 (Evolution::Invariant(a), Evolution::Affine(chrec)) => {
711 let (Some(base), Some(step)) = (
712 apply(a, chrec.base),
713 if subtract { chrec.step.negated() } else { Some(chrec.step) },
714 ) else {
715 return Evolution::Unknown;
716 };
717 affine(base, step, ty, flags.intersection(chrec.flags))
718 }
719 (Evolution::Affine(a), Evolution::Affine(b)) => {
720 if a.ty != b.ty {
724 return Evolution::Unknown;
725 }
726 let (Some(base), Some(step)) = (apply(a.base, b.base), apply(a.step, b.step)) else {
727 return Evolution::Unknown;
728 };
729 affine(base, step, ty, flags.intersection(a.flags).intersection(b.flags))
730 }
731 _ => Evolution::Unknown,
732 }
733}
734
735fn scale(left: Evolution, right: Evolution, ty: Type, flags: Flags) -> Evolution {
737 let (chrec, by) = match (left, right) {
738 (Evolution::Invariant(a), Evolution::Invariant(b)) => {
739 return a.times(b).map_or(Evolution::Unknown, Evolution::Invariant);
740 }
741 (Evolution::Affine(chrec), Evolution::Invariant(by))
742 | (Evolution::Invariant(by), Evolution::Affine(chrec)) => (chrec, by),
743 _ => return Evolution::Unknown,
746 };
747 let (Some(base), Some(step)) = (chrec.base.times(by), chrec.step.times(by)) else {
748 return Evolution::Unknown;
749 };
750 affine(base, step, ty, flags.intersection(chrec.flags))
751}
752
753fn affine(base: Invariant, step: Invariant, ty: Type, flags: Flags) -> Evolution {
761 if step.is_zero() {
762 return Evolution::Invariant(base);
763 }
764 Evolution::Affine(Chrec { base, step, ty, flags })
765}
766
767fn solve(chrec: Chrec, limit: Invariant, pred: IntPred) -> Option<Bound> {
769 let step = chrec.step.as_number()?;
772 if step == 0 {
773 return None;
774 }
775 let signed = matches!(pred, IntPred::Slt | IntPred::Sle | IntPred::Sgt | IntPred::Sge);
776
777 let mut assumptions = Vec::new();
778 if !chrec.does_not_wrap(signed) {
779 assumptions.push(Assumption::NoWrap(chrec));
780 }
781 if signed {
782 assumptions.push(Assumption::StrictOverflow);
783 }
784
785 let (base, limit) = if signed {
788 (chrec.base, limit)
789 } else {
790 (as_unsigned(chrec.base, chrec.ty)?, as_unsigned(limit, chrec.ty)?)
791 };
792
793 let apart = step.unsigned_abs();
797 match (pred, step > 0) {
798 (IntPred::Slt | IntPred::Ult, true) => {
799 ordered(limit.minus(base)?, apart, false, assumptions)
800 }
801 (IntPred::Sle | IntPred::Ule, true) => {
802 ordered(limit.minus(base)?, apart, true, assumptions)
803 }
804 (IntPred::Sgt | IntPred::Ugt, false) => {
805 ordered(base.minus(limit)?, apart, false, assumptions)
806 }
807 (IntPred::Sge | IntPred::Uge, false) => {
808 ordered(base.minus(limit)?, apart, true, assumptions)
809 }
810 (IntPred::Ne, _) => {
811 let distance = if step > 0 { limit.minus(base)? } else { base.minus(limit)? };
812 landing(distance, apart, assumptions)
813 }
814 _ => None,
817 }
818}
819
820fn as_unsigned(inv: Invariant, ty: Type) -> Option<Invariant> {
831 match inv.as_number() {
832 Some(number) if number >= 0 => Some(inv),
833 Some(number) => {
834 let bits = ty.is_int().then(|| ty.bits()).filter(|&bits| bits < 127)?;
837 Some(Invariant::number(number & ((1i128 << bits) - 1)))
838 }
839 None => (inv.scale == 1 && inv.offset == 0).then_some(inv),
842 }
843}
844
845fn ordered(
847 distance: Invariant,
848 step: u128,
849 inclusive: bool,
850 mut assumptions: Vec<Assumption>,
851) -> Option<Bound> {
852 match distance.as_number() {
853 Some(exact) => {
854 if exact < 0 {
855 return Some(Bound { count: Count::Exact(0), assumptions: Vec::new() });
859 }
860 let count = (exact.unsigned_abs() + u128::from(inclusive)).div_ceil(step);
862 Some(Bound { count: Count::Exact(count), assumptions })
863 }
864 None if step == 1 => {
867 assumptions.push(Assumption::Approaching);
868 let count = distance.plus(Invariant::number(i128::from(inclusive)))?;
869 Some(Bound { count: Count::Symbolic(count), assumptions })
870 }
871 None => None,
872 }
873}
874
875fn landing(distance: Invariant, step: u128, mut assumptions: Vec<Assumption>) -> Option<Bound> {
884 match distance.as_number() {
885 Some(exact) => {
886 let travel = u128::try_from(exact).ok()?;
887 (travel % step == 0).then(|| Bound { count: Count::Exact(travel / step), assumptions })
890 }
891 None if step == 1 => {
895 assumptions.push(Assumption::Approaching);
896 Some(Bound { count: Count::Symbolic(distance), assumptions })
897 }
898 None => None,
899 }
900}
901
902fn invert(pred: IntPred) -> IntPred {
904 match pred {
905 IntPred::Eq => IntPred::Ne,
906 IntPred::Ne => IntPred::Eq,
907 IntPred::Slt => IntPred::Sge,
908 IntPred::Sle => IntPred::Sgt,
909 IntPred::Sgt => IntPred::Sle,
910 IntPred::Sge => IntPred::Slt,
911 IntPred::Ult => IntPred::Uge,
912 IntPred::Ule => IntPred::Ugt,
913 IntPred::Ugt => IntPred::Ule,
914 IntPred::Uge => IntPred::Ult,
915 }
916}
917
918fn swap(pred: IntPred) -> IntPred {
920 match pred {
921 IntPred::Eq => IntPred::Eq,
922 IntPred::Ne => IntPred::Ne,
923 IntPred::Slt => IntPred::Sgt,
924 IntPred::Sle => IntPred::Sge,
925 IntPred::Sgt => IntPred::Slt,
926 IntPred::Sge => IntPred::Sle,
927 IntPred::Ult => IntPred::Ugt,
928 IntPred::Ule => IntPred::Uge,
929 IntPred::Ugt => IntPred::Ult,
930 IntPred::Uge => IntPred::Ule,
931 }
932}
933
934fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
936 let Def::Result { inst, .. } = func[value].def else { return None };
937 if func[inst].opcode != Opcode::IConst {
938 return None;
939 }
940 let Extra::Imm(at) = func[inst].extra else { return None };
941 let ty = func[value].ty;
942 ty.is_int().then(|| (func[at], ty))
943}
944
945fn argument(func: &Func, pred: Block, block: Block, index: usize) -> Option<Value> {
951 let term = func.terminator(pred)?;
952 let mut found = None;
953 for call in func.successors(term) {
954 if call.block != block {
955 continue;
956 }
957 let arg = *func[call.args].get(index)?;
958 if found.replace(arg).is_some_and(|old| old != arg) {
959 return None;
960 }
961 }
962 found
963}
964
965#[cfg(test)]
966mod tests {
967 use rucc_base::Interner;
968 use rucc_ir::{Builder, Flags, Func, IntPred, Opcode, Signature, Type, Value};
969
970 use crate::cfg::Cfg;
971 use crate::dom::Dominators;
972 use crate::loops::{LoopId, Loops};
973 use crate::scev::{Assumption, Bound, Count, Evolution, Invariant, Scev};
974
975 struct Counted {
986 func: Func,
987 counter: Value,
988 next: Value,
989 }
990
991 fn counted(ty: Type, from: i128, to: i128, step: i128, pred: IntPred, flags: Flags) -> Counted {
992 let (it, ()) = counted_with(ty, from, to, step, pred, flags, |_, _| ());
993 it
994 }
995
996 fn counted_with<T>(
1002 ty: Type,
1003 from: i128,
1004 to: i128,
1005 step: i128,
1006 pred: IntPred,
1007 flags: Flags,
1008 extra: impl FnOnce(&mut Builder<'_>, Value) -> T,
1009 ) -> (Counted, T) {
1010 let mut names = Interner::new();
1011 let mut func = Func::new(names.intern("f"), Signature::new());
1012 let entry = func.create_block();
1013 let header = func.create_block();
1014 let body = func.create_block();
1015 let exit = func.create_block();
1016 let counter = func.append_param(header, ty);
1017
1018 let mut build = Builder::new(&mut func, entry);
1019 let start = build.iconst(ty, from);
1020 build.jump(header, &[start]);
1021
1022 let mut build = Builder::new(&mut func, header);
1023 let limit = build.iconst(ty, to);
1024 let test = build.icmp(pred, counter, limit);
1025 build.br_if(test, body, &[], exit, &[]);
1026
1027 let mut build = Builder::new(&mut func, body);
1028 let derived = extra(&mut build, counter);
1029 let by = build.iconst(ty, step);
1030 let next = build.binary(Opcode::Add, counter, by, flags);
1031 build.jump(header, &[next]);
1032
1033 let mut build = Builder::new(&mut func, exit);
1034 build.ret(&[]);
1035
1036 (Counted { func, counter, next }, derived)
1037 }
1038
1039 fn analyse(func: &Func) -> (Cfg, Loops) {
1041 let cfg = Cfg::new(func);
1042 let doms = Dominators::new(&cfg);
1043 let loops = Loops::new(&cfg, &doms);
1044 (cfg, loops)
1045 }
1046
1047 fn evolution(func: &Func, value: Value) -> Evolution {
1049 let (cfg, loops) = analyse(func);
1050 let id = loops.roots()[0];
1051 Scev::new(func, &cfg, &loops).evolution(id, value)
1052 }
1053
1054 fn bound(func: &Func) -> Option<Bound> {
1056 let (cfg, loops) = analyse(func);
1057 let id: LoopId = loops.roots()[0];
1058 Scev::new(func, &cfg, &loops).bound(id)
1059 }
1060
1061 #[test]
1062 fn a_counter_from_zero_by_one_is_the_chrec_everyone_expects() {
1063 let it = counted(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW);
1064 let chrec = evolution(&it.func, it.counter).chrec().expect("the counter evolves");
1065 assert_eq!(chrec.base, Invariant::number(0));
1066 assert_eq!(chrec.step, Invariant::number(1));
1067 assert_eq!(chrec.ty, Type::int(32));
1068 assert!(chrec.does_not_wrap(true));
1069 }
1070
1071 #[test]
1072 fn the_value_fed_back_is_the_chrec_one_step_along() {
1073 let it = counted(Type::int(32), 5, 100, 3, IntPred::Slt, Flags::NSW);
1074 let chrec = evolution(&it.func, it.next).chrec().expect("the increment evolves");
1075 assert_eq!(chrec.base, Invariant::number(8));
1076 assert_eq!(chrec.step, Invariant::number(3));
1077 }
1078
1079 #[test]
1080 fn a_multiple_of_the_counter_plus_a_number_is_a_chrec_of_its_own() {
1081 let (it, shifted) =
1084 counted_with(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW, |build, counter| {
1085 let two = build.iconst(Type::int(32), 2);
1086 let three = build.iconst(Type::int(32), 3);
1087 let doubled = build.binary(Opcode::Mul, counter, two, Flags::NSW);
1088 build.binary(Opcode::Add, doubled, three, Flags::NSW)
1089 });
1090
1091 let chrec = evolution(&it.func, shifted).chrec().expect("it evolves");
1092 assert_eq!(chrec.base, Invariant::number(3));
1093 assert_eq!(chrec.step, Invariant::number(2));
1094 }
1095
1096 #[test]
1097 fn a_shift_by_a_constant_scales_the_chrec_and_a_shift_past_the_width_does_not() {
1098 let (it, (scaled, poison)) =
1099 counted_with(Type::int(32), 1, 100, 1, IntPred::Slt, Flags::NSW, |build, counter| {
1100 let three = build.iconst(Type::int(32), 3);
1101 let wide = build.iconst(Type::int(32), 32);
1102 (
1103 build.binary(Opcode::Shl, counter, three, Flags::NSW),
1104 build.binary(Opcode::Shl, counter, wide, Flags::NSW),
1105 )
1106 });
1107
1108 let chrec = evolution(&it.func, scaled).chrec().expect("it evolves");
1109 assert_eq!(chrec.base, Invariant::number(8));
1110 assert_eq!(chrec.step, Invariant::number(8));
1111 assert_eq!(evolution(&it.func, poison), Evolution::Unknown);
1114 }
1115
1116 #[test]
1117 fn a_pointer_walked_by_the_element_size_is_a_chrec_in_bytes() {
1118 let mut names = Interner::new();
1122 let mut func = Func::new(names.intern("f"), Signature::new());
1123 let entry = func.create_block();
1124 let header = func.create_block();
1125 let body = func.create_block();
1126 let exit = func.create_block();
1127 let start = func.append_param(entry, Type::PTR);
1128 let cursor = func.append_param(header, Type::PTR);
1129
1130 let mut build = Builder::new(&mut func, entry);
1131 build.jump(header, &[start]);
1132 let mut build = Builder::new(&mut func, header);
1133 let done = build.icmp(IntPred::Eq, cursor, start);
1134 build.br_if(done, exit, &[], body, &[]);
1135 let mut build = Builder::new(&mut func, body);
1136 let four = build.iconst(Type::int(64), 4);
1137 let next = build.binary(Opcode::PtrAdd, cursor, four, Flags::NONE);
1138 build.jump(header, &[next]);
1139 let mut build = Builder::new(&mut func, exit);
1140 build.ret(&[]);
1141
1142 let chrec = evolution(&func, cursor).chrec().expect("the cursor evolves");
1143 assert_eq!(chrec.base, Invariant::of(start));
1144 assert_eq!(chrec.step, Invariant::number(4));
1145 assert_eq!(chrec.ty, Type::PTR);
1146 }
1147
1148 #[test]
1149 fn a_counter_in_unsigned_char_wraps_and_does_not_widen_without_a_promise() {
1150 let (it, wide) =
1154 counted_with(Type::int(8), 0, 100, 1, IntPred::Ult, Flags::NONE, |build, counter| {
1155 build.unary(Opcode::ZExt, counter, Type::int(32))
1156 });
1157 let chrec = evolution(&it.func, it.counter).chrec().expect("the counter evolves");
1158 assert_eq!(chrec.ty, Type::int(8));
1159 assert!(!chrec.does_not_wrap(false));
1160 assert_eq!(evolution(&it.func, wide), Evolution::Unknown);
1161 }
1162
1163 #[test]
1164 fn a_counter_in_short_widens_when_the_increment_promised_it_would_not_wrap() {
1165 let (it, (wide, zero_extended)) =
1166 counted_with(Type::int(16), 0, 100, 1, IntPred::Slt, Flags::NSW, |build, counter| {
1167 (
1168 build.unary(Opcode::SExt, counter, Type::int(32)),
1169 build.unary(Opcode::ZExt, counter, Type::int(32)),
1170 )
1171 });
1172
1173 let chrec = evolution(&it.func, wide).chrec().expect("it widens");
1174 assert_eq!(chrec.ty, Type::int(32));
1175 assert_eq!(chrec.base, Invariant::number(0));
1176 assert_eq!(chrec.step, Invariant::number(1));
1177 assert_eq!(evolution(&it.func, zero_extended), Evolution::Unknown);
1179 }
1180
1181 #[test]
1182 fn a_step_of_zero_is_invariant_and_has_no_trip_count() {
1183 let it = counted(Type::int(32), 0, 100, 0, IntPred::Slt, Flags::NSW);
1187 assert!(matches!(evolution(&it.func, it.counter), Evolution::Invariant(_)));
1188 assert_eq!(bound(&it.func), None);
1189 }
1190
1191 #[test]
1192 fn a_counted_loop_has_the_count_anyone_would_work_out_by_hand() {
1193 let it = counted(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW);
1194 let found = bound(&it.func).expect("it is counted");
1195 let (count, assumptions) = found.parts();
1196 assert_eq!(count, Count::Exact(100));
1197 assert_eq!(assumptions, [Assumption::StrictOverflow]);
1200 assert_eq!(found.proven(), None);
1201 }
1202
1203 #[test]
1204 fn a_step_that_overshoots_still_takes_the_iteration_that_overshot() {
1205 let it = counted(Type::int(32), 0, 10, 3, IntPred::Slt, Flags::NSW);
1208 let (count, _) = bound(&it.func).expect("it is counted").parts();
1209 assert_eq!(count, Count::Exact(4));
1210 }
1211
1212 #[test]
1213 fn an_inclusive_test_runs_one_more_time() {
1214 let it = counted(Type::int(32), 0, 10, 1, IntPred::Sle, Flags::NSW);
1215 let (count, _) = bound(&it.func).expect("it is counted").parts();
1216 assert_eq!(count, Count::Exact(11));
1217 }
1218
1219 #[test]
1220 fn a_loop_whose_test_fails_first_time_runs_no_times_and_rests_on_nothing() {
1221 let it = counted(Type::int(32), 10, 0, 1, IntPred::Slt, Flags::NSW);
1222 let found = bound(&it.func).expect("it is counted");
1223 assert_eq!(found.proven(), Some(Count::Exact(0)));
1224 assert!(found.assumptions().is_empty());
1225 }
1226
1227 #[test]
1228 fn counting_down_is_the_same_problem_with_the_ends_swapped() {
1229 let it = counted(Type::int(32), 10, 0, -1, IntPred::Sgt, Flags::NSW);
1230 let (count, _) = bound(&it.func).expect("it is counted").parts();
1231 assert_eq!(count, Count::Exact(10));
1232 }
1233
1234 #[test]
1235 fn an_unsigned_test_does_not_drag_in_the_signed_overflow_assumption() {
1236 let it = counted(Type::int(32), 0, 100, 1, IntPred::Ult, Flags::NUW);
1237 let found = bound(&it.func).expect("it is counted");
1238 assert_eq!(found.proven(), Some(Count::Exact(100)));
1239 }
1240
1241 #[test]
1242 fn a_test_against_something_the_loop_does_not_change_gives_a_symbolic_count() {
1243 let mut names = Interner::new();
1246 let mut func = Func::new(names.intern("f"), Signature::new());
1247 let entry = func.create_block();
1248 let header = func.create_block();
1249 let body = func.create_block();
1250 let exit = func.create_block();
1251 let limit = func.append_param(entry, Type::int(32));
1252 let counter = func.append_param(header, Type::int(32));
1253
1254 let mut build = Builder::new(&mut func, entry);
1255 let zero = build.iconst(Type::int(32), 0);
1256 build.jump(header, &[zero]);
1257 let mut build = Builder::new(&mut func, header);
1258 let test = build.icmp(IntPred::Slt, counter, limit);
1259 build.br_if(test, body, &[], exit, &[]);
1260 let mut build = Builder::new(&mut func, body);
1261 let one = build.iconst(Type::int(32), 1);
1262 let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
1263 build.jump(header, &[next]);
1264 let mut build = Builder::new(&mut func, exit);
1265 build.ret(&[]);
1266
1267 let found = bound(&func).expect("it is counted");
1268 let (count, assumptions) = found.parts();
1269 assert_eq!(count, Count::Symbolic(Invariant::of(limit)));
1270 assert!(assumptions.contains(&Assumption::Approaching), "{assumptions:?}");
1271 assert!(assumptions.contains(&Assumption::StrictOverflow), "{assumptions:?}");
1272 assert_eq!(found.proven(), None);
1273 }
1274
1275 #[test]
1276 fn a_counter_without_a_no_wrap_promise_carries_the_assumption_instead() {
1277 let it = counted(Type::int(32), 0, 100, 1, IntPred::Ult, Flags::NONE);
1278 let found = bound(&it.func).expect("it is counted");
1279 let (_, assumptions) = found.parts();
1280 assert!(assumptions.iter().any(|a| matches!(a, Assumption::NoWrap(_))), "{assumptions:?}");
1281 }
1282
1283 #[test]
1284 fn a_test_that_ends_the_loop_when_it_succeeds_is_read_the_other_way_round() {
1285 let mut names = Interner::new();
1288 let mut func = Func::new(names.intern("f"), Signature::new());
1289 let entry = func.create_block();
1290 let header = func.create_block();
1291 let body = func.create_block();
1292 let exit = func.create_block();
1293 let counter = func.append_param(header, Type::int(32));
1294
1295 let mut build = Builder::new(&mut func, entry);
1296 let zero = build.iconst(Type::int(32), 0);
1297 build.jump(header, &[zero]);
1298 let mut build = Builder::new(&mut func, header);
1299 let limit = build.iconst(Type::int(32), 100);
1300 let done = build.icmp(IntPred::Sge, counter, limit);
1301 build.br_if(done, exit, &[], body, &[]);
1302 let mut build = Builder::new(&mut func, body);
1303 let one = build.iconst(Type::int(32), 1);
1304 let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
1305 build.jump(header, &[next]);
1306 let mut build = Builder::new(&mut func, exit);
1307 build.ret(&[]);
1308
1309 let (count, _) = bound(&func).expect("it is counted").parts();
1310 assert_eq!(count, Count::Exact(100));
1311 }
1312
1313 #[test]
1314 fn an_unsigned_limit_past_the_middle_of_its_type_is_not_a_negative_one() {
1315 let it = counted(Type::int(8), 0, 200, 1, IntPred::Ult, Flags::NUW);
1319 let found = bound(&it.func).expect("it is counted");
1320 assert_eq!(found.proven(), Some(Count::Exact(200)));
1321 }
1322
1323 #[test]
1324 fn a_walk_that_lands_on_a_not_equal_limit_exactly_is_counted() {
1325 let it = counted(Type::int(32), 0, 10, 1, IntPred::Ne, Flags::NSW.union(Flags::NUW));
1329 let found = bound(&it.func).expect("it lands on its limit");
1330 assert_eq!(found.proven(), Some(Count::Exact(10)));
1333 }
1334
1335 #[test]
1336 fn a_counter_stepping_away_from_a_not_equal_limit_is_not_a_loop_that_runs_no_times() {
1337 let it = counted(Type::int(32), 48, 15, 1, IntPred::Ne, Flags::NSW);
1341 assert_eq!(bound(&it.func), None);
1342 }
1343
1344 #[test]
1345 fn a_counter_stepping_over_a_not_equal_limit_never_arrives_either() {
1346 let it = counted(Type::int(32), 0, 10, 3, IntPred::Ne, Flags::NSW);
1349 assert_eq!(bound(&it.func), None);
1350 }
1351
1352 #[test]
1353 fn an_estimate_is_the_count_when_there_is_one_and_a_guess_when_there_is_not() {
1354 let counted_loop = counted(Type::int(32), 0, 7, 1, IntPred::Slt, Flags::NSW);
1355 let (cfg, loops) = analyse(&counted_loop.func);
1356 let id = loops.roots()[0];
1357 let estimate = Scev::new(&counted_loop.func, &cfg, &loops).estimate(id);
1358 assert_eq!(estimate.iterations(), 7);
1359 assert!(!estimate.is_guess());
1360
1361 let uncounted = counted(Type::int(32), 0, 100, 0, IntPred::Slt, Flags::NSW);
1364 let (cfg, loops) = analyse(&uncounted.func);
1365 let id = loops.roots()[0];
1366 let estimate = Scev::new(&uncounted.func, &cfg, &loops).estimate(id);
1367 assert!(estimate.is_guess());
1368 assert_eq!(estimate.iterations(), super::ASSUMED_ITERATIONS);
1369 }
1370
1371 #[test]
1372 fn a_value_the_loop_does_not_touch_is_invariant_rather_than_unknown() {
1373 let it = counted(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW);
1374 let (cfg, loops) = analyse(&it.func);
1375 let id = loops.roots()[0];
1376 let mut scev = Scev::new(&it.func, &cfg, &loops);
1377 assert_eq!(
1379 scev.evolution(id, it.counter).chrec().expect("it evolves").base,
1380 Invariant::number(0)
1381 );
1382 }
1383
1384 #[test]
1385 fn a_back_edge_of_its_own_does_not_hide_the_counter() {
1386 let mut names = Interner::new();
1391 let mut func = Func::new(names.intern("f"), Signature::new());
1392 let entry = func.create_block();
1393 let header = func.create_block();
1394 let body = func.create_block();
1395 let latch = func.create_block();
1396 let exit = func.create_block();
1397 let counter = func.append_param(header, Type::int(32));
1398 let carried = func.append_param(latch, Type::int(32));
1399
1400 let start = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1401 Builder::new(&mut func, entry).jump(header, &[start]);
1402
1403 let mut build = Builder::new(&mut func, header);
1404 let limit = build.iconst(Type::int(32), 100);
1405 let test = build.icmp(IntPred::Slt, counter, limit);
1406 build.br_if(test, body, &[], exit, &[]);
1407
1408 let mut build = Builder::new(&mut func, body);
1409 let by = build.iconst(Type::int(32), 1);
1410 let next = build.binary(Opcode::Add, counter, by, Flags::NSW);
1411 build.jump(latch, &[next]);
1412
1413 Builder::new(&mut func, latch).jump(header, &[carried]);
1414 Builder::new(&mut func, exit).ret(&[]);
1415
1416 let chrec = evolution(&func, counter).chrec().expect("the counter still evolves");
1417 assert_eq!(chrec.base, Invariant::number(0));
1418 assert_eq!(chrec.step, Invariant::number(1));
1419 let (count, _) = bound(&func).expect("it is still counted").parts();
1420 assert_eq!(count, Count::Exact(100));
1421 }
1422
1423 #[test]
1424 fn every_assumption_says_what_it_is_in_a_line() {
1425 let it = counted(Type::int(8), 0, 100, 1, IntPred::Ult, Flags::NONE);
1426 let found = bound(&it.func).expect("it is counted");
1427 for assumption in found.assumptions() {
1428 let line = assumption.describe();
1429 assert!(!line.is_empty());
1430 assert!(!line.contains('\n'), "an assumption is one line: {line}");
1431 }
1432 }
1433}