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 ASSUMED_ITERATIONS: u64 = 10;
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct Invariant {
85 pub value: Option<Value>,
87 pub scale: i128,
89 pub offset: i128,
91}
92
93impl Invariant {
94 #[must_use]
96 pub fn number(offset: i128) -> Self {
97 Self { value: None, scale: 0, offset }
98 }
99
100 #[must_use]
102 pub fn of(value: Value) -> Self {
103 Self { value: Some(value), scale: 1, offset: 0 }
104 }
105
106 #[must_use]
108 pub fn as_number(self) -> Option<i128> {
109 (self.value.is_none() || self.scale == 0).then_some(self.offset)
110 }
111
112 #[must_use]
114 pub fn is_zero(self) -> bool {
115 self.as_number() == Some(0)
116 }
117
118 fn shared(self, other: Self) -> Option<Option<Value>> {
120 match (self.as_number().is_some(), other.as_number().is_some()) {
121 (true, _) => Some(other.value),
122 (_, true) => Some(self.value),
123 _ => (self.value == other.value).then_some(self.value),
124 }
125 }
126
127 #[must_use]
129 pub fn plus(self, other: Self) -> Option<Self> {
130 let value = self.shared(other)?;
131 Some(Self {
132 value,
133 scale: self.scale.checked_add(other.scale)?,
134 offset: self.offset.checked_add(other.offset)?,
135 })
136 }
137
138 #[must_use]
140 pub fn minus(self, other: Self) -> Option<Self> {
141 self.plus(other.negated()?)
142 }
143
144 #[must_use]
146 pub fn negated(self) -> Option<Self> {
147 Some(Self {
148 value: self.value,
149 scale: self.scale.checked_neg()?,
150 offset: self.offset.checked_neg()?,
151 })
152 }
153
154 #[must_use]
156 pub fn times(self, other: Self) -> Option<Self> {
157 let (symbol, by) = match (self.as_number(), other.as_number()) {
158 (Some(by), _) => (other, by),
159 (_, Some(by)) => (self, by),
160 _ => return None,
161 };
162 Some(Self {
163 value: symbol.value,
164 scale: symbol.scale.checked_mul(by)?,
165 offset: symbol.offset.checked_mul(by)?,
166 })
167 }
168}
169
170#[derive(Clone, Copy, Debug, PartialEq, Eq)]
172pub enum Evolution {
173 Invariant(Invariant),
175 Affine(Chrec),
177 Unknown,
179}
180
181impl Evolution {
182 #[must_use]
184 pub fn chrec(self) -> Option<Chrec> {
185 match self {
186 Self::Affine(chrec) => Some(chrec),
187 _ => None,
188 }
189 }
190
191 #[must_use]
193 pub fn invariant(self) -> Option<Invariant> {
194 match self {
195 Self::Invariant(inv) => Some(inv),
196 _ => None,
197 }
198 }
199}
200
201#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub struct Chrec {
210 pub base: Invariant,
212 pub step: Invariant,
214 pub ty: Type,
216 pub flags: Flags,
220}
221
222impl Chrec {
223 #[must_use]
225 pub fn does_not_wrap(self, signed: bool) -> bool {
226 self.flags.contains(if signed { Flags::NSW } else { Flags::NUW })
227 }
228}
229
230#[derive(Clone, Copy, Debug, PartialEq, Eq)]
236pub enum Assumption {
237 Approaching,
250 NoWrap(Chrec),
255 StrictOverflow,
263}
264
265impl Assumption {
266 #[must_use]
272 pub fn describe(&self) -> String {
273 match self {
274 Self::Approaching => "the counter starts on the near side of its limit".to_string(),
275 Self::NoWrap(chrec) => {
276 format!("the induction variable does not wrap in i{}", chrec.ty.bits())
277 }
278 Self::StrictOverflow => {
279 "signed overflow is undefined, so -fwrapv withdraws this count".to_string()
280 }
281 }
282 }
283}
284
285#[derive(Clone, Copy, Debug, PartialEq, Eq)]
287pub enum Count {
288 Exact(u128),
290 Symbolic(Invariant),
292}
293
294#[derive(Clone, Debug, PartialEq, Eq)]
300pub struct Bound {
301 count: Count,
302 assumptions: Vec<Assumption>,
303}
304
305impl Bound {
306 #[must_use]
308 pub fn parts(&self) -> (Count, &[Assumption]) {
309 (self.count, &self.assumptions)
310 }
311
312 #[must_use]
314 pub fn assumptions(&self) -> &[Assumption] {
315 &self.assumptions
316 }
317
318 #[must_use]
323 pub fn proven(&self) -> Option<Count> {
324 self.assumptions.is_empty().then_some(self.count)
325 }
326}
327
328#[derive(Clone, Copy, Debug, PartialEq, Eq)]
334pub struct Estimate {
335 iterations: u64,
336 guessed: bool,
337}
338
339impl Estimate {
340 #[must_use]
342 pub fn iterations(self) -> u64 {
343 self.iterations
344 }
345
346 #[must_use]
348 pub fn is_guess(self) -> bool {
349 self.guessed
350 }
351}
352
353#[derive(Debug)]
360pub struct Scev<'a> {
361 func: &'a Func,
362 cfg: &'a Cfg,
363 loops: &'a Loops,
364 known: HashMap<(LoopId, Value), Evolution>,
365}
366
367impl<'a> Scev<'a> {
368 #[must_use]
370 pub fn new(func: &'a Func, cfg: &'a Cfg, loops: &'a Loops) -> Self {
371 Self { func, cfg, loops, known: HashMap::new() }
372 }
373
374 pub fn evolution(&mut self, id: LoopId, value: Value) -> Evolution {
376 if let Some(&known) = self.known.get(&(id, value)) {
377 return known;
378 }
379 self.known.insert((id, value), Evolution::Unknown);
385 let found = self.compute(id, value);
386 self.known.insert((id, value), found);
387 found
388 }
389
390 pub fn bound(&mut self, id: LoopId) -> Option<Bound> {
397 let exits: Vec<Block> = self.loops.exits(id).iter().map(|exit| exit.from).collect();
398 exits.into_iter().find_map(|from| self.bound_at(id, from))
399 }
400
401 pub fn estimate(&mut self, id: LoopId) -> Estimate {
403 match self.bound(id).map(|bound| bound.count) {
404 Some(Count::Exact(exact)) => {
405 Estimate { iterations: u64::try_from(exact).unwrap_or(u64::MAX), guessed: false }
406 }
407 _ => Estimate { iterations: ASSUMED_ITERATIONS, guessed: true },
408 }
409 }
410
411 fn compute(&mut self, id: LoopId, value: Value) -> Evolution {
413 if let Some(invariant) = self.invariant(id, value) {
414 return Evolution::Invariant(invariant);
415 }
416 match self.func[value].def {
417 Def::Param { block, index } if block == self.loops.header(id) => {
418 self.at_header(id, value, index as usize)
419 }
420 Def::Param { .. } => Evolution::Unknown,
424 Def::Result { inst, .. } => self.at_inst(id, inst, value),
425 }
426 }
427
428 fn invariant(&self, id: LoopId, value: Value) -> Option<Invariant> {
430 if let Some((imm, ty)) = constant(self.func, value) {
431 return Some(Invariant::number(imm.signed(ty)));
432 }
433 self.loops.is_invariant(self.func, id, value).then(|| Invariant::of(value))
436 }
437
438 fn at_header(&mut self, id: LoopId, value: Value, index: usize) -> Evolution {
444 let (func, cfg, loops) = (self.func, self.cfg, self.loops);
445 let header = loops.header(id);
446 let [latch] = loops.latches(id) else { return Evolution::Unknown };
449 let mut entering = None;
450 let mut around = None;
451 for &pred in cfg.predecessors(header) {
452 let Some(arg) = argument(func, pred, header, index) else { return Evolution::Unknown };
453 let slot = if pred == *latch { &mut around } else { &mut entering };
454 if slot.replace(arg).is_some_and(|old| old != arg) {
455 return Evolution::Unknown;
456 }
457 }
458 let (Some(entering), Some(around)) = (entering, around) else { return Evolution::Unknown };
459 let Some(base) = self.invariant(id, entering) else { return Evolution::Unknown };
460 let Some((step, flags)) = self.step(id, around, value, 0) else {
461 return Evolution::Unknown;
462 };
463 affine(base, step, func[value].ty, flags)
464 }
465
466 fn step(&self, id: LoopId, value: Value, of: Value, depth: u32) -> Option<(Invariant, Flags)> {
472 if value == of {
473 return Some((Invariant::number(0), Flags::NSW.union(Flags::NUW)));
475 }
476 if depth >= STEP_LIMIT {
477 return None;
478 }
479 let Def::Result { inst, .. } = self.func[value].def else { return None };
480 let data = &self.func[inst];
481 let args = &self.func[data.args];
482 let (&lhs, &rhs) = (args.first()?, args.get(1)?);
483 let combine = |carried: (Invariant, Flags), other: Invariant, subtract: bool| {
484 let (delta, flags) = carried;
485 let moved = if subtract { delta.minus(other)? } else { delta.plus(other)? };
486 Some((moved, flags.intersection(data.flags)))
487 };
488 match data.opcode {
489 Opcode::Add => {
490 if let Some(carried) = self.step(id, lhs, of, depth + 1) {
491 return combine(carried, self.invariant(id, rhs)?, false);
492 }
493 combine(self.step(id, rhs, of, depth + 1)?, self.invariant(id, lhs)?, false)
494 }
495 Opcode::Sub => {
496 combine(self.step(id, lhs, of, depth + 1)?, self.invariant(id, rhs)?, true)
497 }
498 Opcode::PtrAdd => {
502 combine(self.step(id, lhs, of, depth + 1)?, self.invariant(id, rhs)?, false)
503 }
504 _ => None,
505 }
506 }
507
508 fn at_inst(&mut self, id: LoopId, inst: Inst, value: Value) -> Evolution {
510 let func = self.func;
511 let data = &func[inst];
512 let (opcode, flags) = (data.opcode, data.flags);
513 let args = &func[data.args];
514 let ty = func[value].ty;
515 let Some(&lhs) = args.first() else { return Evolution::Unknown };
516 match opcode {
517 Opcode::Add | Opcode::PtrAdd => {
518 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
519 let (left, right) = (self.evolution(id, lhs), self.evolution(id, rhs));
520 combine(left, right, ty, flags, false)
521 }
522 Opcode::Sub => {
523 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
524 let (left, right) = (self.evolution(id, lhs), self.evolution(id, rhs));
525 combine(left, right, ty, flags, true)
526 }
527 Opcode::Mul => {
528 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
529 let (left, right) = (self.evolution(id, lhs), self.evolution(id, rhs));
530 scale(left, right, ty, flags)
531 }
532 Opcode::Shl => {
537 let Some(&rhs) = args.get(1) else { return Evolution::Unknown };
538 let Some((count, count_ty)) = constant(func, rhs) else {
539 return Evolution::Unknown;
540 };
541 let count = count.unsigned();
542 if count >= u128::from(ty.bits()) || !count_ty.is_int() {
543 return Evolution::Unknown;
544 }
545 let by = Evolution::Invariant(Invariant::number(1i128 << count));
546 scale(self.evolution(id, lhs), by, ty, flags)
547 }
548 Opcode::SExt | Opcode::ZExt => self.extend(id, opcode, lhs, ty),
549 _ => Evolution::Unknown,
552 }
553 }
554
555 fn extend(&mut self, id: LoopId, opcode: Opcode, from: Value, to: Type) -> Evolution {
568 let narrow = self.func[from].ty;
569 let signed = opcode == Opcode::SExt;
570 match self.evolution(id, from) {
571 Evolution::Invariant(inv) => match inv.as_number() {
572 Some(number) if signed || number >= 0 => Evolution::Invariant(inv),
575 _ => Evolution::Unknown,
576 },
577 Evolution::Affine(chrec) if chrec.ty == narrow && chrec.does_not_wrap(signed) => {
578 let (Some(base), Some(step)) = (chrec.base.as_number(), chrec.step.as_number())
579 else {
580 return Evolution::Unknown;
581 };
582 Evolution::Affine(Chrec {
583 base: Invariant::number(base),
584 step: Invariant::number(step),
585 ty: to,
586 flags: chrec.flags,
587 })
588 }
589 _ => Evolution::Unknown,
590 }
591 }
592
593 fn bound_at(&mut self, id: LoopId, from: Block) -> Option<Bound> {
595 let func = self.func;
596 let term = func.terminator(from)?;
597 if func[term].opcode != Opcode::BrIf {
598 return None;
599 }
600 let args = &func[func[term].args];
601 let &cond = args.first()?;
602 let calls = &func[func.target_list(term)];
603 let (&taken, ¬_taken) = (calls.first()?, calls.get(1)?);
604 let stays = match (
607 self.loops.contains(id, taken.block),
608 self.loops.contains(id, not_taken.block),
609 ) {
610 (true, false) => true,
611 (false, true) => false,
612 _ => return None,
613 };
614
615 let Def::Result { inst, .. } = func[cond].def else { return None };
616 if func[inst].opcode != Opcode::ICmp {
617 return None;
618 }
619 let Extra::IntPred(pred) = func[inst].extra else { return None };
620 let pred = if stays { pred } else { invert(pred) };
623 let operands = &func[func[inst].args];
624 let (&lhs, &rhs) = (operands.first()?, operands.get(1)?);
625
626 let (chrec, limit, pred) = match (self.evolution(id, lhs), self.evolution(id, rhs)) {
629 (Evolution::Affine(chrec), other) => (chrec, other.invariant()?, pred),
630 (other, Evolution::Affine(chrec)) => (chrec, other.invariant()?, swap(pred)),
631 _ => return None,
632 };
633 solve(chrec, limit, pred)
634 }
635}
636
637fn combine(left: Evolution, right: Evolution, ty: Type, flags: Flags, subtract: bool) -> Evolution {
639 let apply = |a: Invariant, b: Invariant| if subtract { a.minus(b) } else { a.plus(b) };
640 match (left, right) {
641 (Evolution::Invariant(a), Evolution::Invariant(b)) => {
642 apply(a, b).map_or(Evolution::Unknown, Evolution::Invariant)
643 }
644 (Evolution::Affine(chrec), Evolution::Invariant(b)) => {
645 let Some(base) = apply(chrec.base, b) else { return Evolution::Unknown };
647 affine(base, chrec.step, ty, flags.intersection(chrec.flags))
648 }
649 (Evolution::Invariant(a), Evolution::Affine(chrec)) => {
650 let (Some(base), Some(step)) = (
651 apply(a, chrec.base),
652 if subtract { chrec.step.negated() } else { Some(chrec.step) },
653 ) else {
654 return Evolution::Unknown;
655 };
656 affine(base, step, ty, flags.intersection(chrec.flags))
657 }
658 (Evolution::Affine(a), Evolution::Affine(b)) => {
659 if a.ty != b.ty {
663 return Evolution::Unknown;
664 }
665 let (Some(base), Some(step)) = (apply(a.base, b.base), apply(a.step, b.step)) else {
666 return Evolution::Unknown;
667 };
668 affine(base, step, ty, flags.intersection(a.flags).intersection(b.flags))
669 }
670 _ => Evolution::Unknown,
671 }
672}
673
674fn scale(left: Evolution, right: Evolution, ty: Type, flags: Flags) -> Evolution {
676 let (chrec, by) = match (left, right) {
677 (Evolution::Invariant(a), Evolution::Invariant(b)) => {
678 return a.times(b).map_or(Evolution::Unknown, Evolution::Invariant);
679 }
680 (Evolution::Affine(chrec), Evolution::Invariant(by))
681 | (Evolution::Invariant(by), Evolution::Affine(chrec)) => (chrec, by),
682 _ => return Evolution::Unknown,
685 };
686 let (Some(base), Some(step)) = (chrec.base.times(by), chrec.step.times(by)) else {
687 return Evolution::Unknown;
688 };
689 affine(base, step, ty, flags.intersection(chrec.flags))
690}
691
692fn affine(base: Invariant, step: Invariant, ty: Type, flags: Flags) -> Evolution {
700 if step.is_zero() {
701 return Evolution::Invariant(base);
702 }
703 Evolution::Affine(Chrec { base, step, ty, flags })
704}
705
706fn solve(chrec: Chrec, limit: Invariant, pred: IntPred) -> Option<Bound> {
708 let step = chrec.step.as_number()?;
711 if step == 0 {
712 return None;
713 }
714 let signed = matches!(pred, IntPred::Slt | IntPred::Sle | IntPred::Sgt | IntPred::Sge);
715
716 let mut assumptions = Vec::new();
717 if !chrec.does_not_wrap(signed) {
718 assumptions.push(Assumption::NoWrap(chrec));
719 }
720 if signed {
721 assumptions.push(Assumption::StrictOverflow);
722 }
723
724 let (base, limit) = if signed {
727 (chrec.base, limit)
728 } else {
729 (as_unsigned(chrec.base, chrec.ty)?, as_unsigned(limit, chrec.ty)?)
730 };
731
732 let apart = step.unsigned_abs();
736 match (pred, step > 0) {
737 (IntPred::Slt | IntPred::Ult, true) => {
738 ordered(limit.minus(base)?, apart, false, assumptions)
739 }
740 (IntPred::Sle | IntPred::Ule, true) => {
741 ordered(limit.minus(base)?, apart, true, assumptions)
742 }
743 (IntPred::Sgt | IntPred::Ugt, false) => {
744 ordered(base.minus(limit)?, apart, false, assumptions)
745 }
746 (IntPred::Sge | IntPred::Uge, false) => {
747 ordered(base.minus(limit)?, apart, true, assumptions)
748 }
749 (IntPred::Ne, _) => {
750 let distance = if step > 0 { limit.minus(base)? } else { base.minus(limit)? };
751 landing(distance, apart, assumptions)
752 }
753 _ => None,
756 }
757}
758
759fn as_unsigned(inv: Invariant, ty: Type) -> Option<Invariant> {
770 match inv.as_number() {
771 Some(number) if number >= 0 => Some(inv),
772 Some(number) => {
773 let bits = ty.is_int().then(|| ty.bits()).filter(|&bits| bits < 127)?;
776 Some(Invariant::number(number & ((1i128 << bits) - 1)))
777 }
778 None => (inv.scale == 1 && inv.offset == 0).then_some(inv),
781 }
782}
783
784fn ordered(
786 distance: Invariant,
787 step: u128,
788 inclusive: bool,
789 mut assumptions: Vec<Assumption>,
790) -> Option<Bound> {
791 match distance.as_number() {
792 Some(exact) => {
793 if exact < 0 {
794 return Some(Bound { count: Count::Exact(0), assumptions: Vec::new() });
798 }
799 let count = (exact.unsigned_abs() + u128::from(inclusive)).div_ceil(step);
801 Some(Bound { count: Count::Exact(count), assumptions })
802 }
803 None if step == 1 => {
806 assumptions.push(Assumption::Approaching);
807 let count = distance.plus(Invariant::number(i128::from(inclusive)))?;
808 Some(Bound { count: Count::Symbolic(count), assumptions })
809 }
810 None => None,
811 }
812}
813
814fn landing(distance: Invariant, step: u128, mut assumptions: Vec<Assumption>) -> Option<Bound> {
823 match distance.as_number() {
824 Some(exact) => {
825 let travel = u128::try_from(exact).ok()?;
826 (travel % step == 0).then(|| Bound { count: Count::Exact(travel / step), assumptions })
829 }
830 None if step == 1 => {
834 assumptions.push(Assumption::Approaching);
835 Some(Bound { count: Count::Symbolic(distance), assumptions })
836 }
837 None => None,
838 }
839}
840
841fn invert(pred: IntPred) -> IntPred {
843 match pred {
844 IntPred::Eq => IntPred::Ne,
845 IntPred::Ne => IntPred::Eq,
846 IntPred::Slt => IntPred::Sge,
847 IntPred::Sle => IntPred::Sgt,
848 IntPred::Sgt => IntPred::Sle,
849 IntPred::Sge => IntPred::Slt,
850 IntPred::Ult => IntPred::Uge,
851 IntPred::Ule => IntPred::Ugt,
852 IntPred::Ugt => IntPred::Ule,
853 IntPred::Uge => IntPred::Ult,
854 }
855}
856
857fn swap(pred: IntPred) -> IntPred {
859 match pred {
860 IntPred::Eq => IntPred::Eq,
861 IntPred::Ne => IntPred::Ne,
862 IntPred::Slt => IntPred::Sgt,
863 IntPred::Sle => IntPred::Sge,
864 IntPred::Sgt => IntPred::Slt,
865 IntPred::Sge => IntPred::Sle,
866 IntPred::Ult => IntPred::Ugt,
867 IntPred::Ule => IntPred::Uge,
868 IntPred::Ugt => IntPred::Ult,
869 IntPred::Uge => IntPred::Ule,
870 }
871}
872
873fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
875 let Def::Result { inst, .. } = func[value].def else { return None };
876 if func[inst].opcode != Opcode::IConst {
877 return None;
878 }
879 let Extra::Imm(at) = func[inst].extra else { return None };
880 let ty = func[value].ty;
881 ty.is_int().then(|| (func[at], ty))
882}
883
884fn argument(func: &Func, pred: Block, block: Block, index: usize) -> Option<Value> {
890 let term = func.terminator(pred)?;
891 let mut found = None;
892 for call in func.successors(term) {
893 if call.block != block {
894 continue;
895 }
896 let arg = *func[call.args].get(index)?;
897 if found.replace(arg).is_some_and(|old| old != arg) {
898 return None;
899 }
900 }
901 found
902}
903
904#[cfg(test)]
905mod tests {
906 use rucc_base::Interner;
907 use rucc_ir::{Builder, Flags, Func, IntPred, Opcode, Signature, Type, Value};
908
909 use crate::cfg::Cfg;
910 use crate::dom::Dominators;
911 use crate::loops::{LoopId, Loops};
912 use crate::scev::{Assumption, Bound, Count, Evolution, Invariant, Scev};
913
914 struct Counted {
925 func: Func,
926 counter: Value,
927 next: Value,
928 }
929
930 fn counted(ty: Type, from: i128, to: i128, step: i128, pred: IntPred, flags: Flags) -> Counted {
931 let (it, ()) = counted_with(ty, from, to, step, pred, flags, |_, _| ());
932 it
933 }
934
935 fn counted_with<T>(
941 ty: Type,
942 from: i128,
943 to: i128,
944 step: i128,
945 pred: IntPred,
946 flags: Flags,
947 extra: impl FnOnce(&mut Builder<'_>, Value) -> T,
948 ) -> (Counted, T) {
949 let mut names = Interner::new();
950 let mut func = Func::new(names.intern("f"), Signature::new());
951 let entry = func.create_block();
952 let header = func.create_block();
953 let body = func.create_block();
954 let exit = func.create_block();
955 let counter = func.append_param(header, ty);
956
957 let mut build = Builder::new(&mut func, entry);
958 let start = build.iconst(ty, from);
959 build.jump(header, &[start]);
960
961 let mut build = Builder::new(&mut func, header);
962 let limit = build.iconst(ty, to);
963 let test = build.icmp(pred, counter, limit);
964 build.br_if(test, body, &[], exit, &[]);
965
966 let mut build = Builder::new(&mut func, body);
967 let derived = extra(&mut build, counter);
968 let by = build.iconst(ty, step);
969 let next = build.binary(Opcode::Add, counter, by, flags);
970 build.jump(header, &[next]);
971
972 let mut build = Builder::new(&mut func, exit);
973 build.ret(&[]);
974
975 (Counted { func, counter, next }, derived)
976 }
977
978 fn analyse(func: &Func) -> (Cfg, Loops) {
980 let cfg = Cfg::new(func);
981 let doms = Dominators::new(&cfg);
982 let loops = Loops::new(&cfg, &doms);
983 (cfg, loops)
984 }
985
986 fn evolution(func: &Func, value: Value) -> Evolution {
988 let (cfg, loops) = analyse(func);
989 let id = loops.roots()[0];
990 Scev::new(func, &cfg, &loops).evolution(id, value)
991 }
992
993 fn bound(func: &Func) -> Option<Bound> {
995 let (cfg, loops) = analyse(func);
996 let id: LoopId = loops.roots()[0];
997 Scev::new(func, &cfg, &loops).bound(id)
998 }
999
1000 #[test]
1001 fn a_counter_from_zero_by_one_is_the_chrec_everyone_expects() {
1002 let it = counted(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW);
1003 let chrec = evolution(&it.func, it.counter).chrec().expect("the counter evolves");
1004 assert_eq!(chrec.base, Invariant::number(0));
1005 assert_eq!(chrec.step, Invariant::number(1));
1006 assert_eq!(chrec.ty, Type::int(32));
1007 assert!(chrec.does_not_wrap(true));
1008 }
1009
1010 #[test]
1011 fn the_value_fed_back_is_the_chrec_one_step_along() {
1012 let it = counted(Type::int(32), 5, 100, 3, IntPred::Slt, Flags::NSW);
1013 let chrec = evolution(&it.func, it.next).chrec().expect("the increment evolves");
1014 assert_eq!(chrec.base, Invariant::number(8));
1015 assert_eq!(chrec.step, Invariant::number(3));
1016 }
1017
1018 #[test]
1019 fn a_multiple_of_the_counter_plus_a_number_is_a_chrec_of_its_own() {
1020 let (it, shifted) =
1023 counted_with(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW, |build, counter| {
1024 let two = build.iconst(Type::int(32), 2);
1025 let three = build.iconst(Type::int(32), 3);
1026 let doubled = build.binary(Opcode::Mul, counter, two, Flags::NSW);
1027 build.binary(Opcode::Add, doubled, three, Flags::NSW)
1028 });
1029
1030 let chrec = evolution(&it.func, shifted).chrec().expect("it evolves");
1031 assert_eq!(chrec.base, Invariant::number(3));
1032 assert_eq!(chrec.step, Invariant::number(2));
1033 }
1034
1035 #[test]
1036 fn a_shift_by_a_constant_scales_the_chrec_and_a_shift_past_the_width_does_not() {
1037 let (it, (scaled, poison)) =
1038 counted_with(Type::int(32), 1, 100, 1, IntPred::Slt, Flags::NSW, |build, counter| {
1039 let three = build.iconst(Type::int(32), 3);
1040 let wide = build.iconst(Type::int(32), 32);
1041 (
1042 build.binary(Opcode::Shl, counter, three, Flags::NSW),
1043 build.binary(Opcode::Shl, counter, wide, Flags::NSW),
1044 )
1045 });
1046
1047 let chrec = evolution(&it.func, scaled).chrec().expect("it evolves");
1048 assert_eq!(chrec.base, Invariant::number(8));
1049 assert_eq!(chrec.step, Invariant::number(8));
1050 assert_eq!(evolution(&it.func, poison), Evolution::Unknown);
1053 }
1054
1055 #[test]
1056 fn a_pointer_walked_by_the_element_size_is_a_chrec_in_bytes() {
1057 let mut names = Interner::new();
1061 let mut func = Func::new(names.intern("f"), Signature::new());
1062 let entry = func.create_block();
1063 let header = func.create_block();
1064 let body = func.create_block();
1065 let exit = func.create_block();
1066 let start = func.append_param(entry, Type::PTR);
1067 let cursor = func.append_param(header, Type::PTR);
1068
1069 let mut build = Builder::new(&mut func, entry);
1070 build.jump(header, &[start]);
1071 let mut build = Builder::new(&mut func, header);
1072 let done = build.icmp(IntPred::Eq, cursor, start);
1073 build.br_if(done, exit, &[], body, &[]);
1074 let mut build = Builder::new(&mut func, body);
1075 let four = build.iconst(Type::int(64), 4);
1076 let next = build.binary(Opcode::PtrAdd, cursor, four, Flags::NONE);
1077 build.jump(header, &[next]);
1078 let mut build = Builder::new(&mut func, exit);
1079 build.ret(&[]);
1080
1081 let chrec = evolution(&func, cursor).chrec().expect("the cursor evolves");
1082 assert_eq!(chrec.base, Invariant::of(start));
1083 assert_eq!(chrec.step, Invariant::number(4));
1084 assert_eq!(chrec.ty, Type::PTR);
1085 }
1086
1087 #[test]
1088 fn a_counter_in_unsigned_char_wraps_and_does_not_widen_without_a_promise() {
1089 let (it, wide) =
1093 counted_with(Type::int(8), 0, 100, 1, IntPred::Ult, Flags::NONE, |build, counter| {
1094 build.unary(Opcode::ZExt, counter, Type::int(32))
1095 });
1096 let chrec = evolution(&it.func, it.counter).chrec().expect("the counter evolves");
1097 assert_eq!(chrec.ty, Type::int(8));
1098 assert!(!chrec.does_not_wrap(false));
1099 assert_eq!(evolution(&it.func, wide), Evolution::Unknown);
1100 }
1101
1102 #[test]
1103 fn a_counter_in_short_widens_when_the_increment_promised_it_would_not_wrap() {
1104 let (it, (wide, zero_extended)) =
1105 counted_with(Type::int(16), 0, 100, 1, IntPred::Slt, Flags::NSW, |build, counter| {
1106 (
1107 build.unary(Opcode::SExt, counter, Type::int(32)),
1108 build.unary(Opcode::ZExt, counter, Type::int(32)),
1109 )
1110 });
1111
1112 let chrec = evolution(&it.func, wide).chrec().expect("it widens");
1113 assert_eq!(chrec.ty, Type::int(32));
1114 assert_eq!(chrec.base, Invariant::number(0));
1115 assert_eq!(chrec.step, Invariant::number(1));
1116 assert_eq!(evolution(&it.func, zero_extended), Evolution::Unknown);
1118 }
1119
1120 #[test]
1121 fn a_step_of_zero_is_invariant_and_has_no_trip_count() {
1122 let it = counted(Type::int(32), 0, 100, 0, IntPred::Slt, Flags::NSW);
1126 assert!(matches!(evolution(&it.func, it.counter), Evolution::Invariant(_)));
1127 assert_eq!(bound(&it.func), None);
1128 }
1129
1130 #[test]
1131 fn a_counted_loop_has_the_count_anyone_would_work_out_by_hand() {
1132 let it = counted(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW);
1133 let found = bound(&it.func).expect("it is counted");
1134 let (count, assumptions) = found.parts();
1135 assert_eq!(count, Count::Exact(100));
1136 assert_eq!(assumptions, [Assumption::StrictOverflow]);
1139 assert_eq!(found.proven(), None);
1140 }
1141
1142 #[test]
1143 fn a_step_that_overshoots_still_takes_the_iteration_that_overshot() {
1144 let it = counted(Type::int(32), 0, 10, 3, IntPred::Slt, Flags::NSW);
1147 let (count, _) = bound(&it.func).expect("it is counted").parts();
1148 assert_eq!(count, Count::Exact(4));
1149 }
1150
1151 #[test]
1152 fn an_inclusive_test_runs_one_more_time() {
1153 let it = counted(Type::int(32), 0, 10, 1, IntPred::Sle, Flags::NSW);
1154 let (count, _) = bound(&it.func).expect("it is counted").parts();
1155 assert_eq!(count, Count::Exact(11));
1156 }
1157
1158 #[test]
1159 fn a_loop_whose_test_fails_first_time_runs_no_times_and_rests_on_nothing() {
1160 let it = counted(Type::int(32), 10, 0, 1, IntPred::Slt, Flags::NSW);
1161 let found = bound(&it.func).expect("it is counted");
1162 assert_eq!(found.proven(), Some(Count::Exact(0)));
1163 assert!(found.assumptions().is_empty());
1164 }
1165
1166 #[test]
1167 fn counting_down_is_the_same_problem_with_the_ends_swapped() {
1168 let it = counted(Type::int(32), 10, 0, -1, IntPred::Sgt, Flags::NSW);
1169 let (count, _) = bound(&it.func).expect("it is counted").parts();
1170 assert_eq!(count, Count::Exact(10));
1171 }
1172
1173 #[test]
1174 fn an_unsigned_test_does_not_drag_in_the_signed_overflow_assumption() {
1175 let it = counted(Type::int(32), 0, 100, 1, IntPred::Ult, Flags::NUW);
1176 let found = bound(&it.func).expect("it is counted");
1177 assert_eq!(found.proven(), Some(Count::Exact(100)));
1178 }
1179
1180 #[test]
1181 fn a_test_against_something_the_loop_does_not_change_gives_a_symbolic_count() {
1182 let mut names = Interner::new();
1185 let mut func = Func::new(names.intern("f"), Signature::new());
1186 let entry = func.create_block();
1187 let header = func.create_block();
1188 let body = func.create_block();
1189 let exit = func.create_block();
1190 let limit = func.append_param(entry, Type::int(32));
1191 let counter = func.append_param(header, Type::int(32));
1192
1193 let mut build = Builder::new(&mut func, entry);
1194 let zero = build.iconst(Type::int(32), 0);
1195 build.jump(header, &[zero]);
1196 let mut build = Builder::new(&mut func, header);
1197 let test = build.icmp(IntPred::Slt, counter, limit);
1198 build.br_if(test, body, &[], exit, &[]);
1199 let mut build = Builder::new(&mut func, body);
1200 let one = build.iconst(Type::int(32), 1);
1201 let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
1202 build.jump(header, &[next]);
1203 let mut build = Builder::new(&mut func, exit);
1204 build.ret(&[]);
1205
1206 let found = bound(&func).expect("it is counted");
1207 let (count, assumptions) = found.parts();
1208 assert_eq!(count, Count::Symbolic(Invariant::of(limit)));
1209 assert!(assumptions.contains(&Assumption::Approaching), "{assumptions:?}");
1210 assert!(assumptions.contains(&Assumption::StrictOverflow), "{assumptions:?}");
1211 assert_eq!(found.proven(), None);
1212 }
1213
1214 #[test]
1215 fn a_counter_without_a_no_wrap_promise_carries_the_assumption_instead() {
1216 let it = counted(Type::int(32), 0, 100, 1, IntPred::Ult, Flags::NONE);
1217 let found = bound(&it.func).expect("it is counted");
1218 let (_, assumptions) = found.parts();
1219 assert!(assumptions.iter().any(|a| matches!(a, Assumption::NoWrap(_))), "{assumptions:?}");
1220 }
1221
1222 #[test]
1223 fn a_test_that_ends_the_loop_when_it_succeeds_is_read_the_other_way_round() {
1224 let mut names = Interner::new();
1227 let mut func = Func::new(names.intern("f"), Signature::new());
1228 let entry = func.create_block();
1229 let header = func.create_block();
1230 let body = func.create_block();
1231 let exit = func.create_block();
1232 let counter = func.append_param(header, Type::int(32));
1233
1234 let mut build = Builder::new(&mut func, entry);
1235 let zero = build.iconst(Type::int(32), 0);
1236 build.jump(header, &[zero]);
1237 let mut build = Builder::new(&mut func, header);
1238 let limit = build.iconst(Type::int(32), 100);
1239 let done = build.icmp(IntPred::Sge, counter, limit);
1240 build.br_if(done, exit, &[], body, &[]);
1241 let mut build = Builder::new(&mut func, body);
1242 let one = build.iconst(Type::int(32), 1);
1243 let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
1244 build.jump(header, &[next]);
1245 let mut build = Builder::new(&mut func, exit);
1246 build.ret(&[]);
1247
1248 let (count, _) = bound(&func).expect("it is counted").parts();
1249 assert_eq!(count, Count::Exact(100));
1250 }
1251
1252 #[test]
1253 fn an_unsigned_limit_past_the_middle_of_its_type_is_not_a_negative_one() {
1254 let it = counted(Type::int(8), 0, 200, 1, IntPred::Ult, Flags::NUW);
1258 let found = bound(&it.func).expect("it is counted");
1259 assert_eq!(found.proven(), Some(Count::Exact(200)));
1260 }
1261
1262 #[test]
1263 fn a_walk_that_lands_on_a_not_equal_limit_exactly_is_counted() {
1264 let it = counted(Type::int(32), 0, 10, 1, IntPred::Ne, Flags::NSW.union(Flags::NUW));
1268 let found = bound(&it.func).expect("it lands on its limit");
1269 assert_eq!(found.proven(), Some(Count::Exact(10)));
1272 }
1273
1274 #[test]
1275 fn a_counter_stepping_away_from_a_not_equal_limit_is_not_a_loop_that_runs_no_times() {
1276 let it = counted(Type::int(32), 48, 15, 1, IntPred::Ne, Flags::NSW);
1280 assert_eq!(bound(&it.func), None);
1281 }
1282
1283 #[test]
1284 fn a_counter_stepping_over_a_not_equal_limit_never_arrives_either() {
1285 let it = counted(Type::int(32), 0, 10, 3, IntPred::Ne, Flags::NSW);
1288 assert_eq!(bound(&it.func), None);
1289 }
1290
1291 #[test]
1292 fn an_estimate_is_the_count_when_there_is_one_and_a_guess_when_there_is_not() {
1293 let counted_loop = counted(Type::int(32), 0, 7, 1, IntPred::Slt, Flags::NSW);
1294 let (cfg, loops) = analyse(&counted_loop.func);
1295 let id = loops.roots()[0];
1296 let estimate = Scev::new(&counted_loop.func, &cfg, &loops).estimate(id);
1297 assert_eq!(estimate.iterations(), 7);
1298 assert!(!estimate.is_guess());
1299
1300 let uncounted = counted(Type::int(32), 0, 100, 0, IntPred::Slt, Flags::NSW);
1303 let (cfg, loops) = analyse(&uncounted.func);
1304 let id = loops.roots()[0];
1305 let estimate = Scev::new(&uncounted.func, &cfg, &loops).estimate(id);
1306 assert!(estimate.is_guess());
1307 assert_eq!(estimate.iterations(), super::ASSUMED_ITERATIONS);
1308 }
1309
1310 #[test]
1311 fn a_value_the_loop_does_not_touch_is_invariant_rather_than_unknown() {
1312 let it = counted(Type::int(32), 0, 100, 1, IntPred::Slt, Flags::NSW);
1313 let (cfg, loops) = analyse(&it.func);
1314 let id = loops.roots()[0];
1315 let mut scev = Scev::new(&it.func, &cfg, &loops);
1316 assert_eq!(
1318 scev.evolution(id, it.counter).chrec().expect("it evolves").base,
1319 Invariant::number(0)
1320 );
1321 }
1322
1323 #[test]
1324 fn every_assumption_says_what_it_is_in_a_line() {
1325 let it = counted(Type::int(8), 0, 100, 1, IntPred::Ult, Flags::NONE);
1326 let found = bound(&it.func).expect("it is counted");
1327 for assumption in found.assumptions() {
1328 let line = assumption.describe();
1329 assert!(!line.is_empty());
1330 assert!(!line.contains('\n'), "an assumption is one line: {line}");
1331 }
1332 }
1333}