1use std::collections::{HashMap, HashSet};
2
3use crate::arena::Arena;
4use crate::ast::stmt::{BinaryOpKind, Expr, Literal, MatchArm, Pattern, Stmt};
5use crate::intern::Symbol;
6
7fn elem_type_enabled() -> bool {
17 crate::optimize::active_config().is_on(crate::optimization::Opt::ElemType)
18}
19
20#[derive(Clone, Debug, PartialEq)]
21enum Bound {
22 NegInf,
23 Finite(i64),
24 PosInf,
25}
26
27impl Bound {
28 fn add(&self, other: &Bound) -> Bound {
29 match (self, other) {
30 (Bound::Finite(a), Bound::Finite(b)) => {
31 match a.checked_add(*b) {
32 Some(r) => Bound::Finite(r),
33 None => if *a > 0 { Bound::PosInf } else { Bound::NegInf },
34 }
35 }
36 (Bound::PosInf, Bound::NegInf) | (Bound::NegInf, Bound::PosInf) => Bound::NegInf,
37 (Bound::PosInf, _) | (_, Bound::PosInf) => Bound::PosInf,
38 (Bound::NegInf, _) | (_, Bound::NegInf) => Bound::NegInf,
39 }
40 }
41
42 fn sub(&self, other: &Bound) -> Bound {
43 match (self, other) {
44 (Bound::Finite(a), Bound::Finite(b)) => {
45 match a.checked_sub(*b) {
46 Some(r) => Bound::Finite(r),
47 None => if *a > 0 { Bound::PosInf } else { Bound::NegInf },
48 }
49 }
50 (Bound::PosInf, Bound::PosInf) | (Bound::NegInf, Bound::NegInf) => Bound::NegInf,
51 (Bound::PosInf, _) | (_, Bound::NegInf) => Bound::PosInf,
52 (Bound::NegInf, _) | (_, Bound::PosInf) => Bound::NegInf,
53 }
54 }
55
56 fn mul(&self, other: &Bound) -> Bound {
57 if matches!(self, Bound::Finite(0)) || matches!(other, Bound::Finite(0)) {
60 return Bound::Finite(0);
61 }
62 if let (Bound::Finite(a), Bound::Finite(b)) = (self, other) {
63 return match a.checked_mul(*b) {
64 Some(r) => Bound::Finite(r),
65 None => {
66 if (*a > 0) == (*b > 0) {
67 Bound::PosInf
68 } else {
69 Bound::NegInf
70 }
71 }
72 };
73 }
74 let positive = |x: &Bound| match x {
77 Bound::PosInf => true,
78 Bound::NegInf => false,
79 Bound::Finite(v) => *v > 0,
80 };
81 if positive(self) == positive(other) {
82 Bound::PosInf
83 } else {
84 Bound::NegInf
85 }
86 }
87
88 fn div_by(&self, k: i64) -> Bound {
92 match self {
93 Bound::Finite(a) => Bound::Finite(a / k),
94 Bound::NegInf => {
95 if k > 0 {
96 Bound::NegInf
97 } else {
98 Bound::PosInf
99 }
100 }
101 Bound::PosInf => {
102 if k > 0 {
103 Bound::PosInf
104 } else {
105 Bound::NegInf
106 }
107 }
108 }
109 }
110
111 fn shr_by(&self, k: u32) -> Bound {
115 match self {
116 Bound::Finite(a) => Bound::Finite(a >> k),
117 Bound::NegInf => Bound::NegInf,
118 Bound::PosInf => Bound::PosInf,
119 }
120 }
121
122 fn cmp_bound(&self, other: &Self) -> std::cmp::Ordering {
123 match (self, other) {
124 (Bound::NegInf, Bound::NegInf) => std::cmp::Ordering::Equal,
125 (Bound::NegInf, _) => std::cmp::Ordering::Less,
126 (_, Bound::NegInf) => std::cmp::Ordering::Greater,
127 (Bound::PosInf, Bound::PosInf) => std::cmp::Ordering::Equal,
128 (Bound::PosInf, _) => std::cmp::Ordering::Greater,
129 (_, Bound::PosInf) => std::cmp::Ordering::Less,
130 (Bound::Finite(a), Bound::Finite(b)) => a.cmp(b),
131 }
132 }
133
134 fn min_bound(a: &Bound, b: &Bound) -> Bound {
135 if a.cmp_bound(b) == std::cmp::Ordering::Less { a.clone() } else { b.clone() }
136 }
137
138 fn max_bound(a: &Bound, b: &Bound) -> Bound {
139 if a.cmp_bound(b) == std::cmp::Ordering::Greater { a.clone() } else { b.clone() }
140 }
141}
142
143trait AbstractDomain: Clone {
151 fn top() -> Self;
153 fn bottom() -> Self;
155 fn join(&self, other: &Self) -> Self;
157 fn meet(&self, other: &Self) -> Self;
159 fn widen(&self, other: &Self) -> Self;
161 fn leq(&self, other: &Self) -> bool;
163}
164
165#[derive(Clone, Debug)]
166struct Interval {
167 lo: Bound,
168 hi: Bound,
169}
170
171impl Interval {
172 fn exact(n: i64) -> Self {
173 Interval { lo: Bound::Finite(n), hi: Bound::Finite(n) }
174 }
175
176 fn top() -> Self {
177 Interval { lo: Bound::NegInf, hi: Bound::PosInf }
178 }
179
180 fn non_negative() -> Self {
181 Interval { lo: Bound::Finite(0), hi: Bound::PosInf }
182 }
183
184 fn is_exact(&self) -> Option<i64> {
185 if let (Bound::Finite(a), Bound::Finite(b)) = (&self.lo, &self.hi) {
186 if a == b { return Some(*a); }
187 }
188 None
189 }
190
191 fn bottom() -> Self {
194 Interval { lo: Bound::PosInf, hi: Bound::NegInf }
195 }
196
197 fn is_bottom(&self) -> bool {
198 self.lo.cmp_bound(&self.hi) == std::cmp::Ordering::Greater
199 }
200
201 fn join(&self, other: &Interval) -> Interval {
202 if self.is_bottom() { return other.clone(); }
203 if other.is_bottom() { return self.clone(); }
204 Interval {
205 lo: Bound::min_bound(&self.lo, &other.lo),
206 hi: Bound::max_bound(&self.hi, &other.hi),
207 }
208 }
209
210 fn meet(&self, other: &Interval) -> Interval {
212 if self.is_bottom() || other.is_bottom() { return Interval::bottom(); }
213 let r = Interval {
214 lo: Bound::max_bound(&self.lo, &other.lo),
215 hi: Bound::min_bound(&self.hi, &other.hi),
216 };
217 if r.is_bottom() { Interval::bottom() } else { r }
218 }
219
220 fn widen(&self, other: &Interval) -> Interval {
223 if self.is_bottom() { return other.clone(); }
224 if other.is_bottom() { return self.clone(); }
225 const WIDENING_THRESHOLDS: &[i64] = &[-1000, -100, -10, -1, 0, 1, 10, 100, 1000];
230 let lo = if other.lo.cmp_bound(&self.lo) == std::cmp::Ordering::Less {
231 match other.lo {
232 Bound::Finite(v) => WIDENING_THRESHOLDS
233 .iter()
234 .rev()
235 .find(|&&t| t <= v)
236 .map(|&t| Bound::Finite(t))
237 .unwrap_or(Bound::NegInf),
238 _ => Bound::NegInf,
239 }
240 } else {
241 self.lo.clone()
242 };
243 let hi = if other.hi.cmp_bound(&self.hi) == std::cmp::Ordering::Greater {
244 match other.hi {
245 Bound::Finite(v) => WIDENING_THRESHOLDS
246 .iter()
247 .find(|&&t| t >= v)
248 .map(|&t| Bound::Finite(t))
249 .unwrap_or(Bound::PosInf),
250 _ => Bound::PosInf,
251 }
252 } else {
253 self.hi.clone()
254 };
255 Interval { lo, hi }
256 }
257
258 fn leq(&self, other: &Interval) -> bool {
260 if self.is_bottom() { return true; }
261 if other.is_bottom() { return false; }
262 other.lo.cmp_bound(&self.lo) != std::cmp::Ordering::Greater
263 && self.hi.cmp_bound(&other.hi) != std::cmp::Ordering::Greater
264 }
265
266 fn add(&self, other: &Interval) -> Interval {
267 Interval {
268 lo: self.lo.add(&other.lo),
269 hi: self.hi.add(&other.hi),
270 }
271 }
272
273 fn sub(&self, other: &Interval) -> Interval {
274 Interval {
275 lo: self.lo.sub(&other.hi),
276 hi: self.hi.sub(&other.lo),
277 }
278 }
279
280 fn mul(&self, other: &Interval) -> Interval {
281 let corners = [
286 self.lo.mul(&other.lo),
287 self.lo.mul(&other.hi),
288 self.hi.mul(&other.lo),
289 self.hi.mul(&other.hi),
290 ];
291 let mut lo = corners[0].clone();
292 let mut hi = corners[0].clone();
293 for c in &corners[1..] {
294 lo = Bound::min_bound(&lo, c);
295 hi = Bound::max_bound(&hi, c);
296 }
297 Interval { lo, hi }
298 }
299
300 fn div(&self, other: &Interval) -> Interval {
301 if let (Some(a), Some(b)) = (self.is_exact(), other.is_exact()) {
302 if b != 0 {
303 return Interval::exact(a.wrapping_div(b));
306 }
307 }
308 if let Some(k) = other.is_exact() {
314 if k > 0 {
315 return Interval { lo: self.lo.div_by(k), hi: self.hi.div_by(k) };
316 } else if k < 0 {
317 return Interval { lo: self.hi.div_by(k), hi: self.lo.div_by(k) };
318 }
319 }
320 Interval::top()
321 }
322
323 fn modulo(&self, other: &Interval) -> Interval {
324 if let (Some(a), Some(b)) = (self.is_exact(), other.is_exact()) {
325 if b != 0 {
326 return Interval::exact(a.wrapping_rem(b));
329 }
330 }
331 if let Some(k) = other.is_exact() {
338 if k != 0 {
339 let m = k.checked_abs().unwrap_or(i64::MAX).saturating_sub(1).max(0);
340 let nonneg = matches!(self.lo, Bound::Finite(v) if v >= 0);
341 let nonpos = matches!(self.hi, Bound::Finite(v) if v <= 0);
342 let lo = if nonneg { Bound::Finite(0) } else { Bound::Finite(-m) };
343 let hi = if nonpos { Bound::Finite(0) } else { Bound::Finite(m) };
344 return Interval { lo, hi };
345 }
346 }
347 let nonneg = matches!(self.lo, Bound::Finite(v) if v >= 0);
355 let nonpos = matches!(self.hi, Bound::Finite(v) if v <= 0);
356 if nonneg {
357 return Interval::non_negative();
358 }
359 if nonpos {
360 return Interval { lo: Bound::NegInf, hi: Bound::Finite(0) };
361 }
362 Interval::top()
363 }
364
365 fn shr(&self, other: &Interval) -> Interval {
372 if let Some(k) = other.is_exact() {
373 if (0..64).contains(&k) {
374 let k = k as u32;
375 return Interval { lo: self.lo.shr_by(k), hi: self.hi.shr_by(k) };
376 }
377 }
378 Interval::top()
379 }
380
381 fn definitely_gt(&self, other: &Interval) -> Option<bool> {
382 if self.lo.cmp_bound(&other.hi) == std::cmp::Ordering::Greater {
383 return Some(true);
384 }
385 if self.hi.cmp_bound(&other.lo) != std::cmp::Ordering::Greater {
386 return Some(false);
387 }
388 None
389 }
390
391 fn definitely_lt(&self, other: &Interval) -> Option<bool> {
392 if self.hi.cmp_bound(&other.lo) == std::cmp::Ordering::Less {
393 return Some(true);
394 }
395 if self.lo.cmp_bound(&other.hi) != std::cmp::Ordering::Less {
396 return Some(false);
397 }
398 None
399 }
400
401 fn definitely_gteq(&self, other: &Interval) -> Option<bool> {
402 if self.lo.cmp_bound(&other.hi) != std::cmp::Ordering::Less {
403 return Some(true);
404 }
405 if self.hi.cmp_bound(&other.lo) == std::cmp::Ordering::Less {
406 return Some(false);
407 }
408 None
409 }
410
411 fn definitely_lteq(&self, other: &Interval) -> Option<bool> {
412 if self.hi.cmp_bound(&other.lo) != std::cmp::Ordering::Greater {
413 return Some(true);
414 }
415 if self.lo.cmp_bound(&other.hi) == std::cmp::Ordering::Greater {
416 return Some(false);
417 }
418 None
419 }
420
421 fn definitely_eq(&self, other: &Interval) -> Option<bool> {
422 if let (Some(a), Some(b)) = (self.is_exact(), other.is_exact()) {
423 return Some(a == b);
424 }
425 None
426 }
427
428 fn definitely_neq(&self, other: &Interval) -> Option<bool> {
429 if self.hi.cmp_bound(&other.lo) == std::cmp::Ordering::Less
430 || self.lo.cmp_bound(&other.hi) == std::cmp::Ordering::Greater
431 {
432 return Some(true);
433 }
434 if let (Some(a), Some(b)) = (self.is_exact(), other.is_exact()) {
435 return Some(a != b);
436 }
437 None
438 }
439}
440
441impl AbstractDomain for Interval {
442 fn top() -> Self { Interval::top() }
443 fn bottom() -> Self { Interval::bottom() }
444 fn join(&self, other: &Self) -> Self { Interval::join(self, other) }
445 fn meet(&self, other: &Self) -> Self { Interval::meet(self, other) }
446 fn widen(&self, other: &Self) -> Self { Interval::widen(self, other) }
447 fn leq(&self, other: &Self) -> bool { Interval::leq(self, other) }
448}
449
450#[derive(Clone, Debug, PartialEq, Eq, Hash)]
454enum TypeTag {
455 Int,
456 Float,
457 Bool,
458 Text,
459 Char,
460 Nothing,
461 Duration,
462 Date,
463 Moment,
464 Span,
465 Time,
466}
467
468#[derive(Clone, Debug)]
472enum TypeAbstraction {
473 Bottom,
474 Concrete(TypeTag),
475 Union(std::collections::HashSet<TypeTag>),
476 Top,
477}
478
479impl TypeAbstraction {
480 const UNION_CAP: usize = 6;
483
484 fn tag_set(&self) -> Option<std::collections::HashSet<TypeTag>> {
487 match self {
488 TypeAbstraction::Top => None,
489 TypeAbstraction::Bottom => Some(std::collections::HashSet::new()),
490 TypeAbstraction::Concrete(t) => {
491 let mut s = std::collections::HashSet::new();
492 s.insert(t.clone());
493 Some(s)
494 }
495 TypeAbstraction::Union(s) => Some(s.clone()),
496 }
497 }
498
499 fn from_tags(s: std::collections::HashSet<TypeTag>) -> Self {
500 if s.is_empty() {
501 TypeAbstraction::Bottom
502 } else if s.len() == 1 {
503 TypeAbstraction::Concrete(s.into_iter().next().unwrap())
504 } else if s.len() > Self::UNION_CAP {
505 TypeAbstraction::Top
506 } else {
507 TypeAbstraction::Union(s)
508 }
509 }
510}
511
512impl PartialEq for TypeAbstraction {
513 fn eq(&self, other: &Self) -> bool {
514 self.tag_set() == other.tag_set()
515 }
516}
517
518impl AbstractDomain for TypeAbstraction {
519 fn top() -> Self { TypeAbstraction::Top }
520 fn bottom() -> Self { TypeAbstraction::Bottom }
521
522 fn join(&self, other: &Self) -> Self {
523 match (self.tag_set(), other.tag_set()) {
524 (None, _) | (_, None) => TypeAbstraction::Top,
525 (Some(a), Some(b)) => TypeAbstraction::from_tags(a.union(&b).cloned().collect()),
526 }
527 }
528
529 fn meet(&self, other: &Self) -> Self {
530 match (self.tag_set(), other.tag_set()) {
531 (None, _) => other.clone(),
532 (_, None) => self.clone(),
533 (Some(a), Some(b)) => TypeAbstraction::from_tags(a.intersection(&b).cloned().collect()),
534 }
535 }
536
537 fn widen(&self, other: &Self) -> Self { self.join(other) }
539
540 fn leq(&self, other: &Self) -> bool {
541 match (self.tag_set(), other.tag_set()) {
542 (_, None) => true, (None, Some(_)) => false, (Some(a), Some(b)) => a.is_subset(&b),
545 }
546 }
547}
548
549fn literal_type(lit: &Literal) -> TypeTag {
550 match lit {
551 Literal::Number(_) => TypeTag::Int,
552 Literal::Float(_) => TypeTag::Float,
553 Literal::Text(_) => TypeTag::Text,
554 Literal::Boolean(_) => TypeTag::Bool,
555 Literal::Nothing => TypeTag::Nothing,
556 Literal::Char(_) => TypeTag::Char,
557 Literal::Duration(_) => TypeTag::Duration,
558 Literal::Date(_) => TypeTag::Date,
559 Literal::Moment(_) => TypeTag::Moment,
560 Literal::Span { .. } => TypeTag::Span,
561 Literal::Time(_) => TypeTag::Time,
562 }
563}
564
565fn binop_type(op: BinaryOpKind, l: &TypeAbstraction, r: &TypeAbstraction) -> TypeAbstraction {
566 use BinaryOpKind::*;
567 let conc = |t: TypeTag| TypeAbstraction::Concrete(t);
568 let is = |a: &TypeAbstraction, t: TypeTag| matches!(a, TypeAbstraction::Concrete(x) if *x == t);
569 match op {
570 Lt | Gt | LtEq | GtEq | Eq | NotEq | ApproxEq => conc(TypeTag::Bool),
572 BitAnd | BitOr => TypeAbstraction::Top,
575 Concat => conc(TypeTag::Text),
577 SeqConcat => TypeAbstraction::Top,
579 Add => {
580 if is(l, TypeTag::Int) && is(r, TypeTag::Int) { conc(TypeTag::Int) }
581 else if is(l, TypeTag::Float) && is(r, TypeTag::Float) { conc(TypeTag::Float) }
582 else if is(l, TypeTag::Text) || is(r, TypeTag::Text) { conc(TypeTag::Text) }
583 else { TypeAbstraction::Top }
584 }
585 ExactDivide | Pow => TypeAbstraction::Top,
589 Subtract | Multiply | Divide | FloorDivide | Modulo => {
590 if is(l, TypeTag::Int) && is(r, TypeTag::Int) { conc(TypeTag::Int) }
591 else if is(l, TypeTag::Float) && is(r, TypeTag::Float) { conc(TypeTag::Float) }
592 else { TypeAbstraction::Top }
593 }
594 And | Or => {
595 if is(l, TypeTag::Bool) && is(r, TypeTag::Bool) { conc(TypeTag::Bool) }
596 else if is(l, TypeTag::Int) && is(r, TypeTag::Int) { conc(TypeTag::Int) }
597 else { TypeAbstraction::Top }
598 }
599 BitXor | Shl | Shr => {
600 if is(l, TypeTag::Int) && is(r, TypeTag::Int) { conc(TypeTag::Int) }
601 else { TypeAbstraction::Top }
602 }
603 }
604}
605
606fn eval_type(
607 expr: &Expr,
608 types: &HashMap<Symbol, TypeAbstraction>,
609 fn_returns: &HashMap<Symbol, TypeTag>,
610 elem_type: &HashMap<Symbol, TypeAbstraction>,
611) -> TypeAbstraction {
612 match expr {
613 Expr::Literal(lit) => TypeAbstraction::Concrete(literal_type(lit)),
614 Expr::Identifier(sym) => types.get(sym).cloned().unwrap_or(TypeAbstraction::Top),
615 Expr::BinaryOp { op, left, right } => {
616 let l = eval_type(left, types, fn_returns, elem_type);
617 let r = eval_type(right, types, fn_returns, elem_type);
618 binop_type(*op, &l, &r)
619 }
620 Expr::Not { operand } => match eval_type(operand, types, fn_returns, elem_type) {
621 TypeAbstraction::Concrete(TypeTag::Bool) => TypeAbstraction::Concrete(TypeTag::Bool),
622 TypeAbstraction::Concrete(TypeTag::Int) => TypeAbstraction::Concrete(TypeTag::Int),
623 _ => TypeAbstraction::Top,
624 },
625 Expr::Length { .. } => TypeAbstraction::Concrete(TypeTag::Int),
626 Expr::Contains { .. } => TypeAbstraction::Concrete(TypeTag::Bool),
627 Expr::Call { function, .. } => fn_returns
630 .get(function)
631 .map(|t| TypeAbstraction::Concrete(t.clone()))
632 .unwrap_or(TypeAbstraction::Top),
633 Expr::Index { collection: Expr::Identifier(sym), .. } if elem_type_enabled() => {
638 let t = elem_type.get(sym).cloned().unwrap_or(TypeAbstraction::Top);
639 if !matches!(t, TypeAbstraction::Top) {
640 crate::optimize::mark_fired(crate::optimization::Opt::ElemType);
641 }
642 t
643 }
644 _ => TypeAbstraction::Top,
645 }
646}
647
648#[derive(Clone, Debug)]
653enum CollectionShape {
654 Bottom,
656 Empty,
658 Singleton,
660 KnownSize(u64),
662 SizeRange(u64, u64),
664 NonEmpty,
666 Top,
668}
669
670impl CollectionShape {
671 fn empty_collection() -> Self { CollectionShape::Empty }
673
674 fn bounds(&self) -> Option<(u64, Option<u64>)> {
677 match self {
678 CollectionShape::Bottom => None,
679 CollectionShape::Empty => Some((0, Some(0))),
680 CollectionShape::Singleton => Some((1, Some(1))),
681 CollectionShape::KnownSize(n) => Some((*n, Some(*n))),
682 CollectionShape::SizeRange(lo, hi) => Some((*lo, Some(*hi))),
683 CollectionShape::NonEmpty => Some((1, None)),
684 CollectionShape::Top => Some((0, None)),
685 }
686 }
687
688 fn from_bounds(lo: u64, hi: Option<u64>) -> Self {
691 match hi {
692 Some(h) if lo > h => CollectionShape::Bottom,
693 Some(0) => CollectionShape::Empty,
694 Some(1) if lo == 1 => CollectionShape::Singleton,
695 Some(h) if lo == h => CollectionShape::KnownSize(h),
696 Some(h) => CollectionShape::SizeRange(lo, h),
697 None if lo == 0 => CollectionShape::Top,
698 None => CollectionShape::NonEmpty,
699 }
700 }
701
702 fn pushed(&self) -> Self {
704 match self.bounds() {
705 None => CollectionShape::Bottom,
706 Some((lo, hi)) => CollectionShape::from_bounds(lo + 1, hi.map(|h| h + 1)),
707 }
708 }
709
710 fn popped(&self) -> Self {
712 match self.bounds() {
713 None => CollectionShape::Bottom,
714 Some((lo, hi)) => {
715 CollectionShape::from_bounds(lo.saturating_sub(1), hi.map(|h| h.saturating_sub(1)))
716 }
717 }
718 }
719
720 fn is_definitely_nonempty(&self) -> bool {
721 matches!(self.bounds(), Some((lo, _)) if lo >= 1)
722 }
723
724 fn is_definitely_empty(&self) -> bool {
725 matches!(self.bounds(), Some((_, Some(0))))
726 }
727}
728
729impl PartialEq for CollectionShape {
730 fn eq(&self, other: &Self) -> bool {
731 self.bounds() == other.bounds()
732 }
733}
734
735impl AbstractDomain for CollectionShape {
736 fn top() -> Self { CollectionShape::Top }
737 fn bottom() -> Self { CollectionShape::Bottom }
738
739 fn join(&self, other: &Self) -> Self {
740 match (self.bounds(), other.bounds()) {
741 (None, _) => other.clone(),
742 (_, None) => self.clone(),
743 (Some((l1, h1)), Some((l2, h2))) => {
744 let lo = l1.min(l2);
745 let hi = match (h1, h2) {
746 (Some(a), Some(b)) => Some(a.max(b)),
747 _ => None,
748 };
749 CollectionShape::from_bounds(lo, hi)
750 }
751 }
752 }
753
754 fn meet(&self, other: &Self) -> Self {
755 match (self.bounds(), other.bounds()) {
756 (None, _) | (_, None) => CollectionShape::Bottom,
757 (Some((l1, h1)), Some((l2, h2))) => {
758 let lo = l1.max(l2);
759 let hi = match (h1, h2) {
760 (Some(a), Some(b)) => Some(a.min(b)),
761 (Some(a), None) => Some(a),
762 (None, Some(b)) => Some(b),
763 (None, None) => None,
764 };
765 CollectionShape::from_bounds(lo, hi)
766 }
767 }
768 }
769
770 fn widen(&self, other: &Self) -> Self {
771 match (self.bounds(), other.bounds()) {
772 (None, _) => other.clone(),
773 (_, None) => self.clone(),
774 (Some((l1, h1)), Some((l2, h2))) => {
775 let lo = if l2 < l1 { 0 } else { l1 };
776 let hi = match (h1, h2) {
777 (Some(a), Some(b)) if b <= a => Some(a),
778 _ => None,
779 };
780 CollectionShape::from_bounds(lo, hi)
781 }
782 }
783 }
784
785 fn leq(&self, other: &Self) -> bool {
786 match (self.bounds(), other.bounds()) {
787 (None, _) => true,
788 (_, None) => false,
789 (Some((l1, h1)), Some((l2, h2))) => {
790 let lo_ok = l2 <= l1;
791 let hi_ok = match (h1, h2) {
792 (_, None) => true,
793 (None, Some(_)) => false,
794 (Some(a), Some(b)) => a <= b,
795 };
796 lo_ok && hi_ok
797 }
798 }
799 }
800}
801
802#[derive(Clone, Debug, PartialEq, Eq)]
807enum Nullability {
808 Bottom,
810 Definite,
812 Null,
814 Maybe,
816}
817
818impl Nullability {
819 fn for_literal(lit: &Literal) -> Nullability {
821 match lit {
822 Literal::Nothing => Nullability::Null,
823 _ => Nullability::Definite,
824 }
825 }
826
827 fn for_matched_variant() -> Nullability {
830 Nullability::Definite
831 }
832}
833
834impl AbstractDomain for Nullability {
835 fn top() -> Self { Nullability::Maybe }
836 fn bottom() -> Self { Nullability::Bottom }
837
838 fn join(&self, other: &Self) -> Self {
839 use Nullability::*;
840 match (self, other) {
841 (Bottom, x) | (x, Bottom) => x.clone(),
842 (Maybe, _) | (_, Maybe) => Maybe,
843 (Definite, Definite) => Definite,
844 (Null, Null) => Null,
845 (Definite, Null) | (Null, Definite) => Maybe,
846 }
847 }
848
849 fn meet(&self, other: &Self) -> Self {
850 use Nullability::*;
851 match (self, other) {
852 (Maybe, x) | (x, Maybe) => x.clone(),
853 (Bottom, _) | (_, Bottom) => Bottom,
854 (Definite, Definite) => Definite,
855 (Null, Null) => Null,
856 (Definite, Null) | (Null, Definite) => Bottom,
857 }
858 }
859
860 fn widen(&self, other: &Self) -> Self { self.join(other) }
862
863 fn leq(&self, other: &Self) -> bool {
864 *self == Nullability::Bottom || *other == Nullability::Maybe || self == other
865 }
866}
867
868#[derive(Clone, Debug)]
873enum AliasInfo {
874 Bottom,
876 Unique,
878 MayAlias(HashSet<Symbol>),
880 Top,
882}
883
884enum AliasReach {
886 Bottom,
887 Finite(HashSet<Symbol>),
888 Anything,
889}
890
891impl AliasInfo {
892 fn reach(&self) -> AliasReach {
893 match self {
894 AliasInfo::Bottom => AliasReach::Bottom,
895 AliasInfo::Unique => AliasReach::Finite(HashSet::new()),
896 AliasInfo::MayAlias(s) => AliasReach::Finite(s.clone()),
897 AliasInfo::Top => AliasReach::Anything,
898 }
899 }
900
901 fn from_reach(r: AliasReach) -> Self {
902 match r {
903 AliasReach::Bottom => AliasInfo::Bottom,
904 AliasReach::Anything => AliasInfo::Top,
905 AliasReach::Finite(s) => {
906 if s.is_empty() { AliasInfo::Unique } else { AliasInfo::MayAlias(s) }
907 }
908 }
909 }
910}
911
912impl PartialEq for AliasInfo {
913 fn eq(&self, other: &Self) -> bool {
914 match (self.reach(), other.reach()) {
915 (AliasReach::Bottom, AliasReach::Bottom) => true,
916 (AliasReach::Anything, AliasReach::Anything) => true,
917 (AliasReach::Finite(a), AliasReach::Finite(b)) => a == b,
918 _ => false,
919 }
920 }
921}
922
923impl AbstractDomain for AliasInfo {
924 fn top() -> Self { AliasInfo::Top }
925 fn bottom() -> Self { AliasInfo::Bottom }
926
927 fn join(&self, other: &Self) -> Self {
928 match (self.reach(), other.reach()) {
929 (AliasReach::Bottom, _) => other.clone(),
930 (_, AliasReach::Bottom) => self.clone(),
931 (AliasReach::Anything, _) | (_, AliasReach::Anything) => AliasInfo::Top,
932 (AliasReach::Finite(a), AliasReach::Finite(b)) => {
933 AliasInfo::from_reach(AliasReach::Finite(a.union(&b).cloned().collect()))
934 }
935 }
936 }
937
938 fn meet(&self, other: &Self) -> Self {
939 match (self.reach(), other.reach()) {
940 (AliasReach::Bottom, _) | (_, AliasReach::Bottom) => AliasInfo::Bottom,
941 (AliasReach::Anything, _) => other.clone(),
942 (_, AliasReach::Anything) => self.clone(),
943 (AliasReach::Finite(a), AliasReach::Finite(b)) => {
944 AliasInfo::from_reach(AliasReach::Finite(a.intersection(&b).cloned().collect()))
945 }
946 }
947 }
948
949 fn widen(&self, other: &Self) -> Self { self.join(other) }
951
952 fn leq(&self, other: &Self) -> bool {
953 match (self.reach(), other.reach()) {
954 (AliasReach::Bottom, _) => true,
955 (_, AliasReach::Anything) => true,
956 (AliasReach::Anything, _) => false,
957 (_, AliasReach::Bottom) => false,
958 (AliasReach::Finite(a), AliasReach::Finite(b)) => a.is_subset(&b),
959 }
960 }
961}
962
963#[derive(Clone, Default)]
974struct AliasGraph {
975 edges: HashMap<Symbol, HashSet<Symbol>>,
976 tainted: HashSet<Symbol>,
977}
978
979impl AliasGraph {
980 fn new() -> Self {
981 AliasGraph::default()
982 }
983
984 fn link(&mut self, a: Symbol, b: Symbol) {
987 if a == b {
988 return;
989 }
990 self.edges.entry(a).or_default().insert(b);
991 self.edges.entry(b).or_default().insert(a);
992 if self.tainted.contains(&a) {
994 self.tainted.insert(b);
995 }
996 if self.tainted.contains(&b) {
997 self.tainted.insert(a);
998 }
999 }
1000
1001 fn taint(&mut self, a: Symbol) {
1003 self.tainted.insert(a);
1004 }
1005
1006 fn union_from(&mut self, other: &AliasGraph) -> bool {
1008 let mut grew = false;
1009 for (k, ns) in &other.edges {
1010 let e = self.edges.entry(*k).or_default();
1011 for n in ns {
1012 if e.insert(*n) {
1013 grew = true;
1014 }
1015 }
1016 }
1017 for t in &other.tainted {
1018 if self.tainted.insert(*t) {
1019 grew = true;
1020 }
1021 }
1022 grew
1023 }
1024
1025 fn definitely_distinct(&self, a: Symbol, b: Symbol) -> bool {
1028 if a == b || self.tainted.contains(&a) || self.tainted.contains(&b) {
1029 return false;
1030 }
1031 !self.may_alias(a).contains(&b)
1032 }
1033
1034 fn may_alias(&self, v: Symbol) -> HashSet<Symbol> {
1036 let mut seen = HashSet::new();
1037 let mut stack = vec![v];
1038 while let Some(x) = stack.pop() {
1039 if seen.insert(x) {
1040 if let Some(ns) = self.edges.get(&x) {
1041 for &n in ns {
1042 if !seen.contains(&n) {
1043 stack.push(n);
1044 }
1045 }
1046 }
1047 }
1048 }
1049 seen
1050 }
1051
1052 fn invalidated_by_mutation(&self, v: Symbol) -> HashSet<Symbol> {
1054 self.may_alias(v)
1055 }
1056
1057 fn unlink(&mut self, a: Symbol) {
1060 if let Some(ns) = self.edges.remove(&a) {
1061 for n in ns {
1062 if let Some(s) = self.edges.get_mut(&n) {
1063 s.remove(&a);
1064 }
1065 }
1066 }
1067 self.tainted.remove(&a);
1068 }
1069}
1070
1071#[derive(Clone)]
1072struct AbstractState {
1073 vars: HashMap<Symbol, Interval>,
1074 lengths: HashMap<Symbol, Interval>,
1075 elem: HashMap<Symbol, Interval>,
1081}
1082
1083impl AbstractState {
1084 fn new() -> Self {
1085 AbstractState {
1086 vars: HashMap::new(),
1087 lengths: HashMap::new(),
1088 elem: HashMap::new(),
1089 }
1090 }
1091
1092 fn get_var(&self, sym: &Symbol) -> Interval {
1093 self.vars.get(sym).cloned().unwrap_or(Interval::top())
1094 }
1095
1096 fn set_var(&mut self, sym: Symbol, range: Interval) {
1097 self.vars.insert(sym, range);
1098 }
1099
1100 fn get_length(&self, sym: &Symbol) -> Interval {
1101 self.lengths.get(sym).cloned().unwrap_or(Interval::non_negative())
1102 }
1103
1104 fn set_length(&mut self, sym: Symbol, range: Interval) {
1105 self.lengths.insert(sym, range);
1106 }
1107
1108 fn get_elem(&self, sym: &Symbol) -> Interval {
1110 self.elem.get(sym).cloned().unwrap_or(Interval::top())
1111 }
1112
1113 fn observe_elem(&mut self, sym: Symbol, range: Interval) {
1117 let joined = match self.elem.get(&sym) {
1118 Some(existing) => existing.join(&range),
1119 None => range,
1120 };
1121 self.elem.insert(sym, joined);
1122 }
1123
1124 fn clear_elem(&mut self, sym: &Symbol) {
1127 self.elem.remove(sym);
1128 }
1129}
1130
1131fn eval_expr(expr: &Expr, state: &AbstractState) -> Interval {
1132 match expr {
1133 Expr::Literal(Literal::Number(n)) => Interval::exact(*n),
1134 Expr::Literal(Literal::Boolean(_)) => Interval::top(),
1135 Expr::Literal(Literal::Float(_)) => Interval::top(),
1136 Expr::Identifier(sym) => state.get_var(sym),
1137 Expr::BinaryOp { op, left, right } => {
1138 let l = eval_expr(left, state);
1139 let r = eval_expr(right, state);
1140 match op {
1141 BinaryOpKind::Add => l.add(&r),
1142 BinaryOpKind::Subtract => l.sub(&r),
1143 BinaryOpKind::Multiply => l.mul(&r),
1144 BinaryOpKind::Divide => l.div(&r),
1145 BinaryOpKind::ExactDivide => Interval::top(),
1151 BinaryOpKind::Modulo => l.modulo(&r),
1152 BinaryOpKind::Shr => l.shr(&r),
1153 _ => Interval::top(),
1154 }
1155 }
1156 Expr::Length { collection } => {
1157 if let Expr::Identifier(sym) = collection {
1158 state.get_length(sym)
1159 } else {
1160 Interval::non_negative()
1161 }
1162 }
1163 Expr::Index { collection, .. } => {
1168 if let Expr::Identifier(sym) = collection {
1169 state.get_elem(sym)
1170 } else {
1171 Interval::top()
1172 }
1173 }
1174 _ => Interval::top(),
1175 }
1176}
1177
1178fn eval_condition(cond: &Expr, state: &AbstractState) -> Option<bool> {
1179 match cond {
1180 Expr::Literal(Literal::Boolean(b)) => Some(*b),
1181 Expr::BinaryOp { op, left, right } => {
1182 let l = eval_expr(left, state);
1183 let r = eval_expr(right, state);
1184 match op {
1185 BinaryOpKind::Gt => l.definitely_gt(&r),
1186 BinaryOpKind::Lt => l.definitely_lt(&r),
1187 BinaryOpKind::GtEq => l.definitely_gteq(&r),
1188 BinaryOpKind::LtEq => l.definitely_lteq(&r),
1189 BinaryOpKind::Eq => l.definitely_eq(&r),
1190 BinaryOpKind::NotEq => l.definitely_neq(&r),
1191 _ => None,
1192 }
1193 }
1194 Expr::Not { operand } => eval_condition(operand, state).map(|b| !b),
1195 _ => None,
1196 }
1197}
1198
1199fn narrow_state(cond: &Expr, state: &mut AbstractState) {
1200 match cond {
1201 Expr::BinaryOp { op: BinaryOpKind::Gt, left, right } => {
1202 if let Expr::Identifier(sym) = left {
1203 let r = eval_expr(right, state);
1204 if let Some(n) = r.is_exact() {
1205 let cur = state.get_var(sym);
1206 if let Some(new_lo) = n.checked_add(1) {
1207 state.set_var(*sym, Interval {
1208 lo: Bound::max_bound(&cur.lo, &Bound::Finite(new_lo)),
1209 hi: cur.hi,
1210 });
1211 }
1212 }
1213 }
1214 }
1215 Expr::BinaryOp { op: BinaryOpKind::Lt, left, right } => {
1216 if let Expr::Identifier(sym) = left {
1217 let r = eval_expr(right, state);
1218 if let Some(n) = r.is_exact() {
1219 let cur = state.get_var(sym);
1220 if let Some(new_hi) = n.checked_sub(1) {
1221 state.set_var(*sym, Interval {
1222 lo: cur.lo,
1223 hi: Bound::min_bound(&cur.hi, &Bound::Finite(new_hi)),
1224 });
1225 }
1226 }
1227 }
1228 }
1229 Expr::BinaryOp { op: BinaryOpKind::GtEq, left, right } => {
1230 if let Expr::Identifier(sym) = left {
1231 let r = eval_expr(right, state);
1232 if let Some(n) = r.is_exact() {
1233 let cur = state.get_var(sym);
1234 state.set_var(*sym, Interval {
1235 lo: Bound::max_bound(&cur.lo, &Bound::Finite(n)),
1236 hi: cur.hi,
1237 });
1238 }
1239 }
1240 }
1241 Expr::BinaryOp { op: BinaryOpKind::LtEq, left, right } => {
1242 if let Expr::Identifier(sym) = left {
1243 let r = eval_expr(right, state);
1244 if let Some(n) = r.is_exact() {
1245 let cur = state.get_var(sym);
1246 state.set_var(*sym, Interval {
1247 lo: cur.lo,
1248 hi: Bound::min_bound(&cur.hi, &Bound::Finite(n)),
1249 });
1250 }
1251 }
1252 }
1253 _ => {}
1254 }
1255}
1256
1257fn narrow_state_negated(cond: &Expr, state: &mut AbstractState) {
1258 match cond {
1259 Expr::BinaryOp { op: BinaryOpKind::Gt, left, right } => {
1260 if let Expr::Identifier(sym) = left {
1261 let r = eval_expr(right, state);
1262 if let Some(n) = r.is_exact() {
1263 let cur = state.get_var(sym);
1264 state.set_var(*sym, Interval {
1265 lo: cur.lo,
1266 hi: Bound::min_bound(&cur.hi, &Bound::Finite(n)),
1267 });
1268 }
1269 }
1270 }
1271 Expr::BinaryOp { op: BinaryOpKind::Lt, left, right } => {
1272 if let Expr::Identifier(sym) = left {
1273 let r = eval_expr(right, state);
1274 if let Some(n) = r.is_exact() {
1275 let cur = state.get_var(sym);
1276 state.set_var(*sym, Interval {
1277 lo: Bound::max_bound(&cur.lo, &Bound::Finite(n)),
1278 hi: cur.hi,
1279 });
1280 }
1281 }
1282 }
1283 Expr::BinaryOp { op: BinaryOpKind::GtEq, left, right } => {
1284 if let Expr::Identifier(sym) = left {
1285 let r = eval_expr(right, state);
1286 if let Some(n) = r.is_exact() {
1287 let cur = state.get_var(sym);
1288 if let Some(new_hi) = n.checked_sub(1) {
1289 state.set_var(*sym, Interval {
1290 lo: cur.lo,
1291 hi: Bound::min_bound(&cur.hi, &Bound::Finite(new_hi)),
1292 });
1293 }
1294 }
1295 }
1296 }
1297 Expr::BinaryOp { op: BinaryOpKind::LtEq, left, right } => {
1298 if let Expr::Identifier(sym) = left {
1299 let r = eval_expr(right, state);
1300 if let Some(n) = r.is_exact() {
1301 let cur = state.get_var(sym);
1302 if let Some(new_lo) = n.checked_add(1) {
1303 state.set_var(*sym, Interval {
1304 lo: Bound::max_bound(&cur.lo, &Bound::Finite(new_lo)),
1305 hi: cur.hi,
1306 });
1307 }
1308 }
1309 }
1310 }
1311 _ => {}
1312 }
1313}
1314
1315pub fn abstract_interp_stmts<'a>(
1316 stmts: Vec<Stmt<'a>>,
1317 expr_arena: &'a Arena<Expr<'a>>,
1318 stmt_arena: &'a Arena<Stmt<'a>>,
1319) -> Vec<Stmt<'a>> {
1320 let mut state = AbstractState::new();
1321 interp_block(stmts, &mut state, expr_arena, stmt_arena)
1322}
1323
1324fn interp_block<'a>(
1325 stmts: Vec<Stmt<'a>>,
1326 state: &mut AbstractState,
1327 expr_arena: &'a Arena<Expr<'a>>,
1328 stmt_arena: &'a Arena<Stmt<'a>>,
1329) -> Vec<Stmt<'a>> {
1330 let mut result = Vec::with_capacity(stmts.len());
1331
1332 for stmt in stmts {
1333 match stmt {
1334 Stmt::Let { var, ty, value, mutable } => {
1335 let range = eval_expr(value, state);
1336 state.set_var(var, range);
1337 if matches!(value, Expr::New { .. }) {
1338 state.set_length(var, Interval::exact(0));
1339 }
1340 result.push(Stmt::Let { var, ty, value, mutable });
1341 }
1342
1343 Stmt::Set { target, value } => {
1344 let range = eval_expr(value, state);
1345 state.set_var(target, range);
1346 result.push(Stmt::Set { target, value });
1347 }
1348
1349 Stmt::Push { value, collection } => {
1350 if let Expr::Identifier(sym) = collection {
1351 let cur_len = state.get_length(sym);
1352 state.set_length(*sym, cur_len.add(&Interval::exact(1)));
1353 }
1354 result.push(Stmt::Push { value, collection });
1355 }
1356
1357 Stmt::If { cond, then_block, else_block } => {
1358 if let Some(val) = eval_condition(cond, state) {
1359 let new_cond = expr_arena.alloc(Expr::Literal(Literal::Boolean(val)));
1360 if val {
1361 let mut then_state = state.clone();
1362 narrow_state(cond, &mut then_state);
1363 let new_then = interp_nested_block(then_block, &mut then_state, expr_arena, stmt_arena);
1364 *state = then_state;
1365 result.push(Stmt::If {
1366 cond: new_cond,
1367 then_block: new_then,
1368 else_block: None,
1369 });
1370 } else {
1371 if let Some(eb) = else_block {
1372 let mut else_state = state.clone();
1373 narrow_state_negated(cond, &mut else_state);
1374 let new_else = interp_nested_block(eb, &mut else_state, expr_arena, stmt_arena);
1375 *state = else_state;
1376 result.push(Stmt::If {
1377 cond: new_cond,
1378 then_block: stmt_arena.alloc_slice(vec![]),
1379 else_block: Some(new_else),
1380 });
1381 } else {
1382 result.push(Stmt::If {
1383 cond: new_cond,
1384 then_block: stmt_arena.alloc_slice(vec![]),
1385 else_block: None,
1386 });
1387 }
1388 }
1389 } else {
1390 let mut then_state = state.clone();
1391 narrow_state(cond, &mut then_state);
1392 let new_then = interp_nested_block(then_block, &mut then_state, expr_arena, stmt_arena);
1393
1394 let (new_else, else_state) = if let Some(eb) = else_block {
1395 let mut es = state.clone();
1396 narrow_state_negated(cond, &mut es);
1397 let ne = interp_nested_block(eb, &mut es, expr_arena, stmt_arena);
1398 (Some(ne), Some(es))
1399 } else {
1400 (None, None)
1401 };
1402
1403 if let Some(es) = else_state {
1404 join_states(state, &then_state, &es);
1405 } else {
1406 let orig = state.clone();
1407 join_states(state, &then_state, &orig);
1408 }
1409
1410 result.push(Stmt::If { cond, then_block: new_then, else_block: new_else });
1411 }
1412 }
1413
1414 Stmt::While { cond, body, decreasing } => {
1415 let mut loop_state = state.clone();
1416
1417 let loop_writes = collect_writes(body);
1418 let bounded_var = extract_bounded_var(cond);
1419
1420 for w in &loop_writes {
1423 loop_state.set_var(*w, Interval::top());
1424 }
1425
1426 narrow_state(cond, &mut loop_state);
1431
1432 let new_body = interp_nested_block(body, &mut loop_state, expr_arena, stmt_arena);
1433
1434 narrow_state_negated(cond, state);
1436 for w in &loop_writes {
1438 if Some(*w) != bounded_var {
1439 state.set_var(*w, Interval::top());
1440 }
1441 }
1442
1443 result.push(Stmt::While { cond, body: new_body, decreasing });
1444 }
1445
1446 Stmt::Repeat { pattern, iterable, body } => {
1447 let mut loop_state = state.clone();
1448
1449 if let Pattern::Identifier(var) = &pattern {
1450 loop_state.set_var(*var, Interval::top());
1451 }
1452
1453 let loop_writes = collect_writes(body);
1454 for w in &loop_writes {
1455 loop_state.set_var(*w, Interval::top());
1456 }
1457
1458 let new_body = interp_nested_block(body, &mut loop_state, expr_arena, stmt_arena);
1459
1460 for w in &loop_writes {
1461 state.set_var(*w, Interval::top());
1462 }
1463
1464 result.push(Stmt::Repeat { pattern, iterable, body: new_body });
1465 }
1466
1467 Stmt::FunctionDef { name, params, generics, body, return_type, is_native, native_path, is_exported, export_target, opt_flags } => {
1468 let mut func_state = AbstractState::new();
1469 let new_body = interp_nested_block(body, &mut func_state, expr_arena, stmt_arena);
1470 result.push(Stmt::FunctionDef {
1471 name, params, generics,
1472 body: new_body,
1473 return_type, is_native, native_path, is_exported, export_target, opt_flags,
1474 });
1475 }
1476
1477 Stmt::Inspect { target, arms, has_otherwise } => {
1478 let new_arms: Vec<_> = arms.into_iter().map(|arm| {
1479 let mut arm_state = state.clone();
1480 let new_body = interp_nested_block(arm.body, &mut arm_state, expr_arena, stmt_arena);
1481 crate::ast::stmt::MatchArm {
1482 enum_name: arm.enum_name,
1483 variant: arm.variant,
1484 bindings: arm.bindings,
1485 body: new_body,
1486 }
1487 }).collect();
1488 result.push(Stmt::Inspect { target, arms: new_arms, has_otherwise });
1489 }
1490
1491 Stmt::Zone { .. } => {
1492 result.push(stmt);
1495 }
1496
1497 Stmt::Concurrent { tasks } => {
1498 let mut sub_state = state.clone();
1499 let new_tasks = interp_nested_block(tasks, &mut sub_state, expr_arena, stmt_arena);
1500 result.push(Stmt::Concurrent { tasks: new_tasks });
1501 }
1502
1503 Stmt::Parallel { tasks } => {
1504 let mut sub_state = state.clone();
1505 let new_tasks = interp_nested_block(tasks, &mut sub_state, expr_arena, stmt_arena);
1506 result.push(Stmt::Parallel { tasks: new_tasks });
1507 }
1508
1509 other => {
1510 result.push(other);
1511 }
1512 }
1513 }
1514
1515 result
1516}
1517
1518fn interp_nested_block<'a>(
1519 block: &'a [Stmt<'a>],
1520 state: &mut AbstractState,
1521 expr_arena: &'a Arena<Expr<'a>>,
1522 stmt_arena: &'a Arena<Stmt<'a>>,
1523) -> &'a [Stmt<'a>] {
1524 let stmts: Vec<Stmt<'a>> = block.iter().cloned().collect();
1525 let result = interp_block(stmts, state, expr_arena, stmt_arena);
1526 stmt_arena.alloc_slice(result)
1527}
1528
1529fn join_states(out: &mut AbstractState, a: &AbstractState, b: &AbstractState) {
1530 let mut all_keys: std::collections::HashSet<Symbol> = a.vars.keys().cloned().collect();
1531 all_keys.extend(b.vars.keys().cloned());
1532
1533 for key in all_keys {
1534 let a_range = a.vars.get(&key).cloned().unwrap_or(Interval::top());
1535 let b_range = b.vars.get(&key).cloned().unwrap_or(Interval::top());
1536 out.set_var(key, a_range.join(&b_range));
1537 }
1538
1539 let mut len_keys: std::collections::HashSet<Symbol> = a.lengths.keys().cloned().collect();
1540 len_keys.extend(b.lengths.keys().cloned());
1541
1542 for key in len_keys {
1543 let a_len = a.lengths.get(&key).cloned().unwrap_or(Interval::non_negative());
1544 let b_len = b.lengths.get(&key).cloned().unwrap_or(Interval::non_negative());
1545 out.set_length(key, a_len.join(&b_len));
1546 }
1547
1548 let mut el_keys: std::collections::HashSet<Symbol> = a.elem.keys().cloned().collect();
1553 el_keys.extend(b.elem.keys().cloned());
1554 for key in el_keys {
1555 let a_el = a.get_elem(&key);
1556 let b_el = b.get_elem(&key);
1557 out.elem.insert(key, a_el.join(&b_el));
1558 }
1559}
1560
1561fn collect_writes(block: &[Stmt]) -> Vec<Symbol> {
1562 let mut writes = Vec::new();
1563 for stmt in block {
1564 collect_writes_stmt(stmt, &mut writes);
1565 }
1566 writes
1567}
1568
1569fn collect_writes_stmt(stmt: &Stmt, writes: &mut Vec<Symbol>) {
1570 match stmt {
1571 Stmt::Set { target, .. } => {
1572 if !writes.contains(target) {
1573 writes.push(*target);
1574 }
1575 }
1576 Stmt::If { then_block, else_block, .. } => {
1577 for s in *then_block { collect_writes_stmt(s, writes); }
1578 if let Some(eb) = else_block {
1579 for s in *eb { collect_writes_stmt(s, writes); }
1580 }
1581 }
1582 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
1583 for s in *body { collect_writes_stmt(s, writes); }
1584 }
1585 _ => {}
1586 }
1587}
1588
1589fn extract_bounded_var(cond: &Expr) -> Option<Symbol> {
1590 match cond {
1591 Expr::BinaryOp { op: BinaryOpKind::Lt | BinaryOpKind::LtEq | BinaryOpKind::Gt | BinaryOpKind::GtEq, left, .. } => {
1592 if let Expr::Identifier(sym) = left {
1593 Some(*sym)
1594 } else {
1595 None
1596 }
1597 }
1598 _ => None,
1599 }
1600}
1601
1602#[derive(Clone, Debug)]
1613pub(crate) struct AbstractValue {
1614 interval: Interval,
1615 ty: TypeAbstraction,
1616 shape: CollectionShape,
1617 nullability: Nullability,
1618 alias: AliasInfo,
1619}
1620
1621impl AbstractDomain for AbstractValue {
1622 fn top() -> Self {
1623 AbstractValue {
1624 interval: Interval::top(),
1625 ty: TypeAbstraction::Top,
1626 shape: CollectionShape::Top,
1627 nullability: Nullability::Maybe,
1628 alias: AliasInfo::Top,
1629 }
1630 }
1631
1632 fn bottom() -> Self {
1633 AbstractValue {
1634 interval: Interval::bottom(),
1635 ty: TypeAbstraction::Bottom,
1636 shape: CollectionShape::Bottom,
1637 nullability: Nullability::Bottom,
1638 alias: AliasInfo::Bottom,
1639 }
1640 }
1641
1642 fn join(&self, o: &Self) -> Self {
1643 AbstractValue {
1644 interval: self.interval.join(&o.interval),
1645 ty: self.ty.join(&o.ty),
1646 shape: self.shape.join(&o.shape),
1647 nullability: self.nullability.join(&o.nullability),
1648 alias: self.alias.join(&o.alias),
1649 }
1650 }
1651
1652 fn meet(&self, o: &Self) -> Self {
1653 AbstractValue {
1654 interval: self.interval.meet(&o.interval),
1655 ty: self.ty.meet(&o.ty),
1656 shape: self.shape.meet(&o.shape),
1657 nullability: self.nullability.meet(&o.nullability),
1658 alias: self.alias.meet(&o.alias),
1659 }
1660 }
1661
1662 fn widen(&self, o: &Self) -> Self {
1663 AbstractValue {
1664 interval: self.interval.widen(&o.interval),
1665 ty: self.ty.widen(&o.ty),
1666 shape: self.shape.widen(&o.shape),
1667 nullability: self.nullability.widen(&o.nullability),
1668 alias: self.alias.widen(&o.alias),
1669 }
1670 }
1671
1672 fn leq(&self, o: &Self) -> bool {
1673 self.interval.leq(&o.interval)
1674 && self.ty.leq(&o.ty)
1675 && self.shape.leq(&o.shape)
1676 && self.nullability.leq(&o.nullability)
1677 && self.alias.leq(&o.alias)
1678 }
1679}
1680
1681#[derive(Clone)]
1684pub(crate) struct RichAbstractState {
1685 intervals: AbstractState,
1686 types: HashMap<Symbol, TypeAbstraction>,
1687 shapes: HashMap<Symbol, CollectionShape>,
1688 nullability: HashMap<Symbol, Nullability>,
1689 aliases: AliasGraph,
1690 coll_vars: std::collections::HashSet<Symbol>,
1695 fn_returns: std::rc::Rc<HashMap<Symbol, TypeTag>>,
1697 length_def: HashMap<Symbol, (Symbol, i64)>,
1705 param_colls: std::collections::HashSet<Symbol>,
1710 scalar_def: HashMap<Symbol, super::affine::LinExpr>,
1717 scalar_upper: HashMap<Symbol, super::affine::LinExpr>,
1725 elem_upper: HashMap<Symbol, super::affine::LinExpr>,
1732 elem_type: HashMap<Symbol, TypeAbstraction>,
1743 scalar_lower: HashMap<Symbol, super::affine::LinExpr>,
1751 aot_entry_guard: bool,
1760}
1761
1762impl RichAbstractState {
1763 fn new() -> Self {
1764 RichAbstractState {
1765 intervals: AbstractState::new(),
1766 types: HashMap::new(),
1767 shapes: HashMap::new(),
1768 nullability: HashMap::new(),
1769 aliases: AliasGraph::new(),
1770 coll_vars: std::collections::HashSet::new(),
1771 fn_returns: std::rc::Rc::new(HashMap::new()),
1772 length_def: HashMap::new(),
1773 param_colls: std::collections::HashSet::new(),
1774 scalar_def: HashMap::new(),
1775 scalar_upper: HashMap::new(),
1776 elem_upper: HashMap::new(),
1777 elem_type: HashMap::new(),
1778 scalar_lower: HashMap::new(),
1779 aot_entry_guard: false,
1780 }
1781 }
1782
1783 pub(crate) fn value_of(&self, sym: Symbol) -> AbstractValue {
1785 let mut others = self.aliases.may_alias(sym);
1786 others.remove(&sym);
1787 let alias = if others.is_empty() {
1788 AliasInfo::Unique
1789 } else {
1790 AliasInfo::MayAlias(others)
1791 };
1792 AbstractValue {
1793 interval: self.intervals.get_var(&sym),
1794 ty: self.types.get(&sym).cloned().unwrap_or(TypeAbstraction::Top),
1795 shape: self.shapes.get(&sym).cloned().unwrap_or(CollectionShape::Top),
1796 nullability: self.nullability.get(&sym).cloned().unwrap_or(Nullability::Maybe),
1797 alias,
1798 }
1799 }
1800
1801 fn invalidate_var(&mut self, sym: Symbol) {
1805 self.intervals.set_var(sym, Interval::top());
1806 self.types.insert(sym, TypeAbstraction::Top);
1807 self.shapes.insert(sym, CollectionShape::Top);
1808 self.nullability.insert(sym, Nullability::Maybe);
1809 self.coll_vars.remove(&sym);
1810 self.intervals.clear_elem(&sym);
1811 let si = sym.index() as i64;
1812 self.scalar_upper.remove(&sym);
1813 self.scalar_upper.retain(|_, e| !e.coefficients.contains_key(&si));
1814 self.elem_upper.remove(&sym);
1815 self.elem_upper.retain(|_, e| !e.coefficients.contains_key(&si));
1816 self.elem_type.remove(&sym);
1817 self.scalar_lower.remove(&sym);
1818 self.scalar_lower.retain(|_, e| !e.coefficients.contains_key(&si));
1819 }
1820}
1821
1822#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1827pub enum ScalarKind {
1828 Int,
1829 Float,
1830 Bool,
1831 Text,
1832}
1833
1834#[derive(Clone, Debug, Default, PartialEq)]
1838pub struct VarProvenFacts {
1839 pub scalar: Option<ScalarKind>,
1841 pub int_range: Option<(i64, i64)>,
1843 pub nonneg: bool,
1845}
1846
1847#[derive(Default)]
1854pub struct OracleFacts {
1855 exprs: HashMap<usize, AbstractValue>,
1856 lengths: HashMap<usize, Interval>,
1859 collections: std::collections::HashSet<usize>,
1862 loop_aliases: HashMap<usize, AliasGraph>,
1867 suppress_exprs: bool,
1874 relational_inbounds: std::collections::HashSet<usize>,
1881 speculative_inbounds: std::collections::HashSet<usize>,
1886 hoist_descs: HashMap<usize, Vec<HoistDesc>>,
1894 positivity_guards: HashMap<usize, Vec<Symbol>>,
1901 map_caps: HashMap<Symbol, super::affine::LinExpr>,
1908 dense_map_key_proven: HashMap<usize, Symbol>,
1913 map_insert_cover: HashMap<Symbol, (super::affine::LinExpr, super::affine::LinExpr)>,
1919 map_key_covered: HashMap<usize, Symbol>,
1923}
1924
1925#[derive(Clone, Copy, Debug)]
1928pub struct HoistDesc {
1929 pub array: Symbol,
1930 pub bound: Symbol,
1931 pub iv: Symbol,
1932 pub add_max: i32,
1933 pub add_min: i32,
1934}
1935
1936impl OracleFacts {
1937 pub fn hoist_descs_for(&self, loop_ptr: usize) -> &[HoistDesc] {
1941 self.hoist_descs.get(&loop_ptr).map_or(&[], |v| v.as_slice())
1942 }
1943
1944 pub fn index_is_speculative(&self, index: &Expr) -> bool {
1948 self.speculative_inbounds.contains(&(index as *const Expr as usize))
1949 }
1950
1951 fn record(&mut self, e: &Expr, av: AbstractValue) {
1952 if self.suppress_exprs {
1953 return;
1954 }
1955 let key = e as *const Expr as usize;
1956 match self.exprs.get_mut(&key) {
1957 None => {
1958 self.exprs.insert(key, av);
1959 }
1960 Some(prev) => *prev = prev.join(&av),
1961 }
1962 }
1963
1964 fn record_length(&mut self, e: &Expr, len: Interval) {
1965 if self.suppress_exprs {
1966 return;
1967 }
1968 let key = e as *const Expr as usize;
1969 match self.lengths.get_mut(&key) {
1970 None => {
1971 self.lengths.insert(key, len);
1972 }
1973 Some(prev) => *prev = prev.join(&len),
1974 }
1975 }
1976
1977 pub fn expr_scalar(&self, e: &Expr) -> Option<ScalarKind> {
1979 let av = self.exprs.get(&(e as *const Expr as usize))?;
1980 match &av.ty {
1981 TypeAbstraction::Concrete(TypeTag::Int) => Some(ScalarKind::Int),
1982 TypeAbstraction::Concrete(TypeTag::Float) => Some(ScalarKind::Float),
1983 TypeAbstraction::Concrete(TypeTag::Bool) => Some(ScalarKind::Bool),
1984 TypeAbstraction::Concrete(TypeTag::Text) => Some(ScalarKind::Text),
1985 _ => None,
1986 }
1987 }
1988
1989 pub fn expr_int_range(&self, e: &Expr) -> Option<(i64, i64)> {
1992 self.expr_int_range_addr(e as *const Expr as usize)
1993 }
1994
1995 pub fn expr_proven_nonneg(&self, e: &Expr) -> bool {
2003 match self.exprs.get(&(e as *const Expr as usize)) {
2004 Some(av) => matches!(av.interval.lo, Bound::Finite(v) if v >= 0),
2005 None => false,
2006 }
2007 }
2008
2009 pub fn expr_int_range_addr(&self, addr: usize) -> Option<(i64, i64)> {
2012 let av = self.exprs.get(&addr)?;
2013 match (&av.interval.lo, &av.interval.hi) {
2014 (Bound::Finite(lo), Bound::Finite(hi)) if lo <= hi => Some((*lo, *hi)),
2015 _ => None,
2016 }
2017 }
2018
2019 pub fn summarize_variables(&self, stmts: &[Stmt]) -> Vec<(Symbol, VarProvenFacts)> {
2028 let mut by_sym: HashMap<Symbol, AbstractValue> = HashMap::new();
2029 collect_idents_block(stmts, &mut |sym, addr| {
2030 if let Some(av) = self.exprs.get(&addr) {
2031 match by_sym.get_mut(&sym) {
2032 None => {
2033 by_sym.insert(sym, av.clone());
2034 }
2035 Some(prev) => *prev = prev.join(av),
2036 }
2037 }
2038 });
2039 by_sym
2040 .into_iter()
2041 .map(|(sym, av)| {
2042 let int_range = match (&av.interval.lo, &av.interval.hi) {
2043 (Bound::Finite(lo), Bound::Finite(hi)) if lo <= hi => Some((*lo, *hi)),
2044 _ => None,
2045 };
2046 let scalar = match &av.ty {
2047 TypeAbstraction::Concrete(TypeTag::Int) => Some(ScalarKind::Int),
2048 TypeAbstraction::Concrete(TypeTag::Float) => Some(ScalarKind::Float),
2049 TypeAbstraction::Concrete(TypeTag::Bool) => Some(ScalarKind::Bool),
2050 TypeAbstraction::Concrete(TypeTag::Text) => Some(ScalarKind::Text),
2051 _ => None,
2052 };
2053 let nonneg = matches!(av.interval.lo, Bound::Finite(v) if v >= 0);
2054 (sym, VarProvenFacts { scalar, int_range, nonneg })
2055 })
2056 .collect()
2057 }
2058
2059 pub fn has_dense_map_capacity(&self, map: Symbol) -> bool {
2062 self.map_caps.contains_key(&map)
2063 }
2064
2065 pub fn map_cap_lin(&self, map: Symbol) -> Option<&super::affine::LinExpr> {
2069 self.map_caps.get(&map)
2070 }
2071
2072 pub fn dense_map_key_proven(&self, key: &Expr, map: Symbol) -> bool {
2075 self.dense_map_key_proven_addr(key as *const Expr as usize, map)
2076 }
2077
2078 pub fn dense_map_key_proven_addr(&self, key_addr: usize, map: Symbol) -> bool {
2081 self.dense_map_key_proven.get(&key_addr) == Some(&map)
2082 }
2083
2084 pub fn dense_map_has_full_coverage(&self, map: Symbol) -> bool {
2087 self.map_insert_cover.contains_key(&map)
2088 }
2089
2090 pub fn dense_key_covered_addr(&self, key_addr: usize, map: Symbol) -> bool {
2093 self.map_key_covered.get(&key_addr) == Some(&map)
2094 }
2095
2096 pub fn expr_is_tracked_collection(&self, e: &Expr) -> bool {
2100 self.collections.contains(&(e as *const Expr as usize))
2101 }
2102
2103 pub fn loop_handles_definitely_distinct(
2111 &self,
2112 loop_stmt: &Stmt,
2113 a: Symbol,
2114 b: Symbol,
2115 ) -> bool {
2116 let key = loop_stmt as *const Stmt as usize;
2117 match self.loop_aliases.get(&key) {
2118 Some(g) => g.definitely_distinct(a, b),
2119 None => false,
2120 }
2121 }
2122
2123 pub fn expr_len_range(&self, e: &Expr) -> Option<(i64, i64)> {
2125 let iv = self.lengths.get(&(e as *const Expr as usize))?;
2126 match (&iv.lo, &iv.hi) {
2127 (Bound::Finite(lo), Bound::Finite(hi)) if lo <= hi => Some((*lo, *hi)),
2128 _ => None,
2129 }
2130 }
2131
2132 pub fn index_proven_relational(&self, index: &Expr) -> bool {
2139 self.relational_inbounds.contains(&(index as *const Expr as usize))
2140 }
2141
2142 pub fn index_provably_in_bounds(&self, collection: &Expr, index: &Expr) -> bool {
2146 if self.relational_inbounds.contains(&(index as *const Expr as usize)) {
2150 return true;
2151 }
2152 let Some(len) = self.lengths.get(&(collection as *const Expr as usize)) else {
2155 return false;
2156 };
2157 let Some((ilo, ihi)) = self.expr_int_range(index) else { return false };
2158 let len_lo = match len.lo {
2159 Bound::Finite(v) => v,
2160 _ => return false,
2161 };
2162 ilo >= 1 && ihi <= len_lo
2163 }
2164
2165 pub fn index_positivity_guards(&self, index: &Expr) -> &[Symbol] {
2170 self.positivity_guards
2171 .get(&(index as *const Expr as usize))
2172 .map_or(&[], |v| v.as_slice())
2173 }
2174}
2175
2176fn record_expr(e: &Expr, st: &RichAbstractState, facts: &mut OracleFacts) {
2179 use crate::ast::stmt::StringPart;
2180 match e {
2181 Expr::BinaryOp { left, right, .. }
2182 | Expr::Union { left, right }
2183 | Expr::Intersection { left, right }
2184 | Expr::Range { start: left, end: right } => {
2185 record_expr(left, st, facts);
2186 record_expr(right, st, facts);
2187 }
2188 Expr::Not { operand } => record_expr(operand, st, facts),
2189 Expr::Call { args, .. } => {
2190 for a in args {
2191 record_expr(a, st, facts);
2192 }
2193 }
2194 Expr::CallExpr { callee, args } => {
2195 record_expr(callee, st, facts);
2196 for a in args {
2197 record_expr(a, st, facts);
2198 }
2199 }
2200 Expr::Index { collection, index } => {
2201 record_expr(collection, st, facts);
2202 record_expr(index, st, facts);
2203 }
2204 Expr::Slice { collection, start, end } => {
2205 record_expr(collection, st, facts);
2206 record_expr(start, st, facts);
2207 record_expr(end, st, facts);
2208 }
2209 Expr::Copy { expr } => record_expr(expr, st, facts),
2210 Expr::Give { value } => record_expr(value, st, facts),
2211 Expr::Length { collection } => record_expr(collection, st, facts),
2212 Expr::Contains { collection, value } => {
2213 record_expr(collection, st, facts);
2214 record_expr(value, st, facts);
2215 }
2216 Expr::List(items) | Expr::Tuple(items) => {
2217 for i in items {
2218 record_expr(i, st, facts);
2219 }
2220 }
2221 Expr::FieldAccess { object, .. } => record_expr(object, st, facts),
2222 Expr::New { init_fields, .. } => {
2223 for (_, fe) in init_fields {
2224 record_expr(fe, st, facts);
2225 }
2226 }
2227 Expr::NewVariant { fields, .. } => {
2228 for (_, fe) in fields {
2229 record_expr(fe, st, facts);
2230 }
2231 }
2232 Expr::OptionSome { value } => record_expr(value, st, facts),
2233 Expr::WithCapacity { value, capacity } => {
2234 record_expr(value, st, facts);
2235 record_expr(capacity, st, facts);
2236 }
2237 Expr::InterpolatedString(parts) => {
2238 for p in parts {
2239 if let StringPart::Expr { value, .. } = p {
2240 record_expr(value, st, facts);
2241 }
2242 }
2243 }
2244 _ => {}
2245 }
2246 let av = AbstractValue {
2247 interval: eval_expr(e, &st.intervals),
2248 ty: eval_type(e, &st.types, &st.fn_returns, &st.elem_type),
2249 shape: match e {
2250 Expr::Identifier(sym) => {
2251 st.shapes.get(sym).cloned().unwrap_or(CollectionShape::Top)
2252 }
2253 _ => CollectionShape::Top,
2254 },
2255 nullability: nullability_of_expr(e, st),
2256 alias: AliasInfo::top(),
2257 };
2258 facts.record(e, av);
2259 if let Expr::Identifier(sym) = e {
2260 facts.record_length(e, st.intervals.get_length(sym));
2261 if !facts.suppress_exprs && st.coll_vars.contains(sym) {
2262 facts.collections.insert(e as *const Expr as usize);
2263 }
2264 }
2265}
2266
2267fn collect_idents_block(stmts: &[Stmt], f: &mut dyn FnMut(Symbol, usize)) {
2273 for s in stmts {
2274 collect_idents_stmt(s, f);
2275 }
2276}
2277
2278fn collect_idents_stmt(stmt: &Stmt, f: &mut dyn FnMut(Symbol, usize)) {
2279 use crate::ast::stmt::ReadSource;
2280 match stmt {
2282 Stmt::Let { value, .. } | Stmt::Set { value, .. } => collect_idents_expr(value, f),
2283 Stmt::Return { value: Some(e) } => collect_idents_expr(e, f),
2284 Stmt::Call { args, .. } => {
2285 for a in args {
2286 collect_idents_expr(a, f);
2287 }
2288 }
2289 Stmt::If { cond, .. } | Stmt::While { cond, .. } => collect_idents_expr(cond, f),
2290 Stmt::Repeat { iterable, .. } => collect_idents_expr(iterable, f),
2291 Stmt::Show { object, .. } | Stmt::Give { object, .. } => collect_idents_expr(object, f),
2292 Stmt::Push { value, collection }
2293 | Stmt::Add { value, collection }
2294 | Stmt::Remove { value, collection } => {
2295 collect_idents_expr(value, f);
2296 collect_idents_expr(collection, f);
2297 }
2298 Stmt::SetIndex { collection, index, value } => {
2299 collect_idents_expr(collection, f);
2300 collect_idents_expr(index, f);
2301 collect_idents_expr(value, f);
2302 }
2303 Stmt::SetField { object, value, .. } => {
2304 collect_idents_expr(object, f);
2305 collect_idents_expr(value, f);
2306 }
2307 Stmt::Inspect { target, .. } => collect_idents_expr(target, f),
2308 Stmt::RuntimeAssert { condition, .. } => collect_idents_expr(condition, f),
2309 Stmt::Sleep { milliseconds } => collect_idents_expr(milliseconds, f),
2310 Stmt::ReadFrom { source: ReadSource::File(p), .. } => collect_idents_expr(p, f),
2311 _ => {}
2312 }
2313 match stmt {
2315 Stmt::If { then_block, else_block, .. } => {
2316 collect_idents_block(then_block, f);
2317 if let Some(eb) = else_block {
2318 collect_idents_block(eb, f);
2319 }
2320 }
2321 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => collect_idents_block(body, f),
2322 Stmt::FunctionDef { body, .. } => collect_idents_block(body, f),
2323 Stmt::Inspect { arms, .. } => {
2324 for arm in arms {
2325 collect_idents_block(arm.body, f);
2326 }
2327 }
2328 Stmt::Zone { body, .. } => collect_idents_block(body, f),
2329 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => collect_idents_block(tasks, f),
2330 _ => {}
2331 }
2332}
2333
2334fn collect_idents_expr(e: &Expr, f: &mut dyn FnMut(Symbol, usize)) {
2335 use crate::ast::stmt::StringPart;
2336 if let Expr::Identifier(sym) = e {
2337 f(*sym, e as *const Expr as usize);
2338 }
2339 match e {
2340 Expr::BinaryOp { left, right, .. }
2341 | Expr::Union { left, right }
2342 | Expr::Intersection { left, right }
2343 | Expr::Range { start: left, end: right } => {
2344 collect_idents_expr(left, f);
2345 collect_idents_expr(right, f);
2346 }
2347 Expr::Not { operand } => collect_idents_expr(operand, f),
2348 Expr::Call { args, .. } => {
2349 for a in args {
2350 collect_idents_expr(a, f);
2351 }
2352 }
2353 Expr::CallExpr { callee, args } => {
2354 collect_idents_expr(callee, f);
2355 for a in args {
2356 collect_idents_expr(a, f);
2357 }
2358 }
2359 Expr::Index { collection, index } => {
2360 collect_idents_expr(collection, f);
2361 collect_idents_expr(index, f);
2362 }
2363 Expr::Slice { collection, start, end } => {
2364 collect_idents_expr(collection, f);
2365 collect_idents_expr(start, f);
2366 collect_idents_expr(end, f);
2367 }
2368 Expr::Copy { expr } => collect_idents_expr(expr, f),
2369 Expr::Give { value } => collect_idents_expr(value, f),
2370 Expr::Length { collection } => collect_idents_expr(collection, f),
2371 Expr::Contains { collection, value } => {
2372 collect_idents_expr(collection, f);
2373 collect_idents_expr(value, f);
2374 }
2375 Expr::List(items) | Expr::Tuple(items) => {
2376 for i in items {
2377 collect_idents_expr(i, f);
2378 }
2379 }
2380 Expr::FieldAccess { object, .. } => collect_idents_expr(object, f),
2381 Expr::New { init_fields, .. } => {
2382 for (_, fe) in init_fields {
2383 collect_idents_expr(fe, f);
2384 }
2385 }
2386 Expr::NewVariant { fields, .. } => {
2387 for (_, fe) in fields {
2388 collect_idents_expr(fe, f);
2389 }
2390 }
2391 Expr::OptionSome { value } => collect_idents_expr(value, f),
2392 Expr::WithCapacity { value, capacity } => {
2393 collect_idents_expr(value, f);
2394 collect_idents_expr(capacity, f);
2395 }
2396 Expr::InterpolatedString(parts) => {
2397 for p in parts {
2398 if let StringPart::Expr { value, .. } = p {
2399 collect_idents_expr(value, f);
2400 }
2401 }
2402 }
2403 _ => {}
2404 }
2405}
2406
2407enum IvBound {
2409 LenOf(Symbol),
2411 Var { var: Symbol, headroom: i64 },
2417}
2418
2419struct IvGuard {
2421 iv: Symbol,
2422 bound: IvBound,
2423 strict: bool,
2425 iv_lo: i64,
2427}
2428
2429fn record_loop_index_bounds(
2441 cond: &Expr,
2442 body: &[Stmt],
2443 state: &RichAbstractState,
2444 facts: &mut OracleFacts,
2445) {
2446 let mut guards: Vec<IvGuard> = Vec::new();
2447 collect_iv_guards(cond, state, &mut guards);
2448 if guards.is_empty() {
2449 return;
2450 }
2451 if !body_is_index_proof_safe(body) {
2456 return;
2457 }
2458 let mut clobbered: std::collections::HashSet<Symbol> = std::collections::HashSet::new();
2459 collect_resized_arrays(body, &mut clobbered);
2460 record_proven_reads(body, &guards, &state.length_def, &mut clobbered, facts);
2461}
2462
2463#[derive(Default, Clone)]
2485struct AffineFacts {
2486 constraints: Vec<super::affine::Constraint>,
2487 syms: Vec<Symbol>,
2488}
2489
2490fn record_affine_index_bounds(
2491 cond: &Expr,
2492 body: &[Stmt],
2493 state: &RichAbstractState,
2494 entry: &RichAbstractState,
2495 facts: &mut OracleFacts,
2496) {
2497 if !body_is_index_proof_safe(body) {
2498 return;
2499 }
2500 let mut clobbered: std::collections::HashSet<Symbol> = std::collections::HashSet::new();
2501 collect_resized_arrays(body, &mut clobbered);
2502 let mutated: std::collections::HashSet<Symbol> =
2503 collect_mutations(body).into_iter().collect();
2504 let state = {
2511 let mut st = state.clone();
2512 augment_body_scalar_defs(body, &mut st);
2513 st
2514 };
2515 let inits: HashMap<Symbol, i64> = HashMap::new();
2516 let mut amb = AffineFacts::default();
2517 affine_add_loop_guards(cond, body, &state, Some(entry), &inits, &mut amb);
2518 affine_walk(body, &state, &clobbered, &mutated, &amb, &inits, facts);
2519}
2520
2521fn augment_body_scalar_defs(body: &[Stmt], st: &mut RichAbstractState) {
2528 let mut reassigned = std::collections::HashSet::new();
2531 collect_set_targets(body, &mut reassigned);
2532 let mut let_count: HashMap<Symbol, u32> = HashMap::new();
2534 for s in body {
2535 if let Stmt::Let { var, .. } = s {
2536 *let_count.entry(*var).or_insert(0) += 1;
2537 }
2538 }
2539 for s in body {
2540 if let Stmt::Let { var, value, .. } = s {
2541 if reassigned.contains(var)
2542 || let_count.get(var) != Some(&1)
2543 || st.scalar_def.contains_key(var)
2544 {
2545 continue;
2546 }
2547 if let Some(e) = super::affine::lin_of(value) {
2548 let rhs_stable = e.coefficients.keys().all(|&i| {
2554 let s = Symbol::from_index(i as usize);
2555 !reassigned.contains(&s) && let_count.get(&s).map_or(true, |&c| c <= 1)
2556 });
2557 if rhs_stable
2558 && !e.coefficients.is_empty()
2559 && !e.coefficients.contains_key(&(var.index() as i64))
2560 {
2561 st.scalar_def.insert(*var, e);
2562 }
2563 }
2564 }
2565 }
2566}
2567
2568fn collect_set_targets(stmts: &[Stmt], out: &mut std::collections::HashSet<Symbol>) {
2572 for s in stmts {
2573 if let Stmt::Set { target, .. } = s {
2574 out.insert(*target);
2575 }
2576 each_child_block(s, &mut |b| collect_set_targets(b, out));
2577 }
2578}
2579
2580fn affine_cmp(
2583 op: BinaryOpKind,
2584 a: &Expr,
2585 b: &Expr,
2586 negate: bool,
2587) -> Option<super::affine::Constraint> {
2588 use super::affine::{ge, gt, le, lin_of, lt};
2589 let (la, lb) = (lin_of(a)?, lin_of(b)?);
2590 Some(match (op, negate) {
2591 (BinaryOpKind::Lt, false) | (BinaryOpKind::GtEq, true) => lt(&la, &lb),
2592 (BinaryOpKind::LtEq, false) | (BinaryOpKind::Gt, true) => le(&la, &lb),
2593 (BinaryOpKind::Gt, false) | (BinaryOpKind::LtEq, true) => gt(&la, &lb),
2594 (BinaryOpKind::GtEq, false) | (BinaryOpKind::Lt, true) => ge(&la, &lb),
2595 _ => return None,
2596 })
2597}
2598
2599fn affine_add_cmp(cond: &Expr, negate: bool, out: &mut AffineFacts) {
2603 match cond {
2604 Expr::BinaryOp { op: BinaryOpKind::And, left, right } if !negate => {
2605 affine_add_cmp(left, false, out);
2606 affine_add_cmp(right, false, out);
2607 }
2608 Expr::BinaryOp {
2609 op:
2610 op @ (BinaryOpKind::Lt
2611 | BinaryOpKind::LtEq
2612 | BinaryOpKind::Gt
2613 | BinaryOpKind::GtEq),
2614 left,
2615 right,
2616 } => {
2617 if let Some(c) = affine_cmp(*op, left, right, negate) {
2618 out.constraints.push(c);
2619 affine_collect_syms(left, &mut out.syms);
2620 affine_collect_syms(right, &mut out.syms);
2621 }
2622 }
2623 _ => {}
2624 }
2625}
2626
2627fn affine_collect_syms(e: &Expr, out: &mut Vec<Symbol>) {
2629 match e {
2630 Expr::Identifier(s) => {
2631 if !out.contains(s) {
2632 out.push(*s);
2633 }
2634 }
2635 Expr::BinaryOp { left, right, .. } => {
2636 affine_collect_syms(left, out);
2637 affine_collect_syms(right, out);
2638 }
2639 _ => {}
2640 }
2641}
2642
2643fn write_keeps_ge(
2649 value: &Expr,
2650 iv: Symbol,
2651 lo: i64,
2652 state: &RichAbstractState,
2653 mutated: &std::collections::HashSet<Symbol>,
2654) -> bool {
2655 match value {
2656 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
2657 let is_iv = |e: &&Expr| matches!(e, Expr::Identifier(s) if *s == iv);
2658 let nonneg = |e: &&Expr| matches!(e, Expr::Literal(Literal::Number(n)) if *n >= 0);
2659 (is_iv(&left) && nonneg(&right)) || (nonneg(&left) && is_iv(&right))
2660 }
2661 Expr::Literal(Literal::Number(n)) => *n >= lo,
2662 Expr::Identifier(s) if !mutated.contains(s) => {
2663 matches!(state.intervals.get_var(s).lo, Bound::Finite(l) if l >= lo)
2664 }
2665 _ => false,
2666 }
2667}
2668
2669fn iv_lower_bound(
2678 iv: Symbol,
2679 body: &[Stmt],
2680 state: &RichAbstractState,
2681 mutated: &std::collections::HashSet<Symbol>,
2682 inits: &HashMap<Symbol, i64>,
2683) -> Option<i64> {
2684 let lo = match inits.get(&iv) {
2685 Some(c) => *c,
2686 None => match state.intervals.get_var(&iv).lo {
2687 Bound::Finite(l) => l,
2688 _ => return None,
2689 },
2690 };
2691 fn preserved(
2692 iv: Symbol,
2693 stmts: &[Stmt],
2694 lo: i64,
2695 state: &RichAbstractState,
2696 mutated: &std::collections::HashSet<Symbol>,
2697 ) -> bool {
2698 stmts.iter().all(|s| match s {
2699 Stmt::Set { target, value } if *target == iv => {
2700 write_keeps_ge(value, iv, lo, state, mutated)
2701 }
2702 Stmt::Let { var, value, .. } if *var == iv => {
2703 write_keeps_ge(value, iv, lo, state, mutated)
2704 }
2705 Stmt::If { then_block, else_block, .. } => {
2706 preserved(iv, then_block, lo, state, mutated)
2707 && else_block.map_or(true, |eb| preserved(iv, eb, lo, state, mutated))
2708 }
2709 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
2710 preserved(iv, body, lo, state, mutated)
2711 }
2712 _ => true,
2713 })
2714 }
2715 preserved(iv, body, lo, state, mutated).then_some(lo)
2716}
2717
2718pub(crate) fn self_increment(v: Symbol, value: &Expr) -> Option<i64> {
2724 if let Expr::BinaryOp { op: BinaryOpKind::Add, left, right } = value {
2725 if let (Expr::Identifier(s), Expr::Literal(Literal::Number(c))) = (&**left, &**right) {
2726 if *s == v {
2727 return Some(*c);
2728 }
2729 }
2730 if let (Expr::Literal(Literal::Number(c)), Expr::Identifier(s)) = (&**left, &**right) {
2731 if *s == v {
2732 return Some(*c);
2733 }
2734 }
2735 }
2736 None
2737}
2738
2739pub(crate) fn count_writes_of(stmts: &[Stmt], v: Symbol) -> usize {
2741 let mut n = 0;
2742 for s in stmts {
2743 match s {
2744 Stmt::Set { target, .. } if *target == v => n += 1,
2745 Stmt::Let { var, .. } if *var == v => n += 1,
2746 Stmt::If { then_block, else_block, .. } => {
2747 n += count_writes_of(then_block, v);
2748 if let Some(eb) = else_block {
2749 n += count_writes_of(eb, v);
2750 }
2751 }
2752 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => n += count_writes_of(body, v),
2753 _ => {}
2754 }
2755 }
2756 n
2757}
2758
2759fn top_level_unconditional_incs(body: &[Stmt]) -> Vec<(Symbol, i64)> {
2763 let mut out = Vec::new();
2764 for s in body {
2765 if let Stmt::Set { target, value } = s {
2766 if let Some(c) = self_increment(*target, value) {
2767 if c > 0 && count_writes_of(body, *target) == 1 {
2768 out.push((*target, c));
2769 }
2770 }
2771 }
2772 }
2773 out
2774}
2775
2776fn monotone_inc_within(stmts: &[Stmt], v: Symbol, in_loop: bool, total: &mut i64, count: &mut usize) -> bool {
2780 for s in stmts {
2781 match s {
2782 Stmt::Set { target, value } if *target == v => match self_increment(v, value) {
2783 Some(c) if c >= 0 && !in_loop => {
2784 *total += c;
2785 *count += 1;
2786 }
2787 _ => return false,
2788 },
2789 Stmt::Let { var, .. } if *var == v => return false,
2790 Stmt::If { then_block, else_block, .. } => {
2791 if !monotone_inc_within(then_block, v, in_loop, total, count) {
2792 return false;
2793 }
2794 if let Some(eb) = else_block {
2795 if !monotone_inc_within(eb, v, in_loop, total, count) {
2796 return false;
2797 }
2798 }
2799 }
2800 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
2801 if !monotone_inc_within(body, v, true, total, count) {
2802 return false;
2803 }
2804 }
2805 _ => {}
2806 }
2807 }
2808 true
2809}
2810
2811fn monotone_inc_vars(body: &[Stmt], limit: i64) -> Vec<Symbol> {
2814 let mut cands: Vec<Symbol> = Vec::new();
2815 fn collect(stmts: &[Stmt], out: &mut Vec<Symbol>) {
2816 for s in stmts {
2817 match s {
2818 Stmt::Set { target, value } if self_increment(*target, value).is_some() => {
2819 if !out.contains(target) {
2820 out.push(*target);
2821 }
2822 }
2823 Stmt::If { then_block, else_block, .. } => {
2824 collect(then_block, out);
2825 if let Some(eb) = else_block {
2826 collect(eb, out);
2827 }
2828 }
2829 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => collect(body, out),
2830 _ => {}
2831 }
2832 }
2833 }
2834 collect(body, &mut cands);
2835 cands
2836 .into_iter()
2837 .filter(|v| {
2838 let (mut total, mut count) = (0, 0);
2839 monotone_inc_within(body, *v, false, &mut total, &mut count)
2840 && count >= 1
2841 && total <= limit
2842 })
2843 .collect()
2844}
2845
2846fn derive_iv_le_invariants(
2856 body: &[Stmt],
2857 entry: &RichAbstractState,
2858 state: &RichAbstractState,
2859 mutated: &std::collections::HashSet<Symbol>,
2860 inits: &HashMap<Symbol, i64>,
2861 amb: &mut AffineFacts,
2862) {
2863 use super::affine::{ge, konst, le, var};
2864 for (j, dj) in top_level_unconditional_incs(body) {
2865 for i in monotone_inc_vars(body, dj) {
2866 if i == j {
2867 continue;
2868 }
2869 let equal_start = match (entry.scalar_def.get(&i), entry.scalar_def.get(&j)) {
2872 (Some(a), Some(b)) => a == b,
2873 _ => false,
2874 };
2875 if !equal_start {
2876 continue;
2877 }
2878 amb.constraints.push(le(&var(i), &var(j)));
2879 if let Some(lo) = iv_lower_bound(i, body, state, mutated, inits) {
2882 amb.constraints.push(ge(&var(i), &konst(lo)));
2883 }
2884 for s in [i, j] {
2885 if !amb.syms.contains(&s) {
2886 amb.syms.push(s);
2887 }
2888 }
2889 }
2890 }
2891}
2892
2893fn affine_add_loop_guards(
2894 cond: &Expr,
2895 body: &[Stmt],
2896 state: &RichAbstractState,
2897 entry: Option<&RichAbstractState>,
2898 inits: &HashMap<Symbol, i64>,
2899 amb: &mut AffineFacts,
2900) {
2901 affine_add_cmp(cond, false, amb);
2902 let mutated: std::collections::HashSet<Symbol> =
2903 collect_mutations(body).into_iter().collect();
2904 let mut ivs: Vec<Symbol> = Vec::new();
2905 affine_collect_syms(cond, &mut ivs);
2906 for iv in ivs {
2907 if let Some(lo) = iv_lower_bound(iv, body, state, &mutated, inits) {
2908 amb.constraints
2909 .push(super::affine::ge(&super::affine::var(iv), &super::affine::konst(lo)));
2910 if !amb.syms.contains(&iv) {
2911 amb.syms.push(iv);
2912 }
2913 }
2914 }
2915 if let Some(entry) = entry {
2919 derive_iv_le_invariants(body, entry, state, &mutated, inits, amb);
2920 }
2921}
2922
2923fn affine_drop_mentioning(amb: &mut AffineFacts, set: &std::collections::HashSet<Symbol>) {
2927 amb.constraints.retain(|c| {
2928 !set.iter().any(|s| c.expr.coefficients.contains_key(&(s.index() as i64)))
2929 });
2930 amb.syms.retain(|s| !set.contains(s));
2931}
2932
2933fn affine_drop_one(amb: &mut AffineFacts, sym: Symbol) {
2934 let k = sym.index() as i64;
2935 amb.constraints.retain(|c| !c.expr.coefficients.contains_key(&k));
2936 amb.syms.retain(|s| *s != sym);
2937}
2938
2939fn affine_prove_in_expr(
2942 e: &Expr,
2943 state: &RichAbstractState,
2944 clobbered: &std::collections::HashSet<Symbol>,
2945 mutated: &std::collections::HashSet<Symbol>,
2946 amb: &AffineFacts,
2947 facts: &mut OracleFacts,
2948) {
2949 if let Expr::Index { collection, index } = e {
2950 if let Expr::Identifier(m) = collection {
2952 if facts.map_caps.contains_key(m) {
2953 try_prove_dense_key(*m, index, state, mutated, amb, facts);
2954 }
2955 }
2956 try_prove_affine(collection, index, state, clobbered, mutated, amb, facts);
2957 }
2958 if let Expr::Contains { collection, value } = e {
2960 if let Expr::Identifier(m) = collection {
2961 if facts.map_caps.contains_key(m) {
2962 try_prove_dense_key(*m, value, state, mutated, amb, facts);
2963 }
2964 }
2965 }
2966 match e {
2967 Expr::BinaryOp { left, right, .. }
2968 | Expr::Union { left, right }
2969 | Expr::Intersection { left, right }
2970 | Expr::Range { start: left, end: right } => {
2971 affine_prove_in_expr(left, state, clobbered, mutated, amb, facts);
2972 affine_prove_in_expr(right, state, clobbered, mutated, amb, facts);
2973 }
2974 Expr::Not { operand } => affine_prove_in_expr(operand, state, clobbered, mutated, amb, facts),
2975 Expr::Contains { collection, value } => {
2976 affine_prove_in_expr(collection, state, clobbered, mutated, amb, facts);
2977 affine_prove_in_expr(value, state, clobbered, mutated, amb, facts);
2978 }
2979 Expr::Index { collection, index } => {
2980 affine_prove_in_expr(collection, state, clobbered, mutated, amb, facts);
2981 affine_prove_in_expr(index, state, clobbered, mutated, amb, facts);
2982 }
2983 Expr::Call { args, .. } => {
2984 for a in args {
2985 affine_prove_in_expr(a, state, clobbered, mutated, amb, facts);
2986 }
2987 }
2988 Expr::Length { collection } => {
2989 affine_prove_in_expr(collection, state, clobbered, mutated, amb, facts)
2990 }
2991 _ => {}
2992 }
2993}
2994
2995fn affine_length_lb(
2998 arr: Symbol,
2999 state: &RichAbstractState,
3000) -> Option<(super::affine::LinExpr, Vec<Symbol>)> {
3001 use super::affine::{konst, var};
3002 if let Some((n, off)) = state.length_def.get(&arr) {
3003 return Some((var(*n).add(&konst(*off)), vec![*n]));
3004 }
3005 if let Bound::Finite(l) = state.intervals.get_length(&arr).lo {
3006 return Some((konst(l), vec![]));
3007 }
3008 None
3009}
3010
3011fn affine_walk(
3014 stmts: &[Stmt],
3015 state: &RichAbstractState,
3016 clobbered: &std::collections::HashSet<Symbol>,
3017 mutated: &std::collections::HashSet<Symbol>,
3018 ambient: &AffineFacts,
3019 inits: &HashMap<Symbol, i64>,
3020 facts: &mut OracleFacts,
3021) {
3022 let mut local = ambient.clone();
3023 let mut inits = inits.clone();
3024 for stmt in stmts {
3025 for_each_direct_expr(stmt, &mut |e| {
3026 affine_prove_in_expr(e, state, clobbered, mutated, &local, facts);
3027 });
3028 if let Stmt::SetIndex { collection, index, .. } = stmt {
3029 if let Expr::Identifier(m) = collection {
3032 if facts.map_caps.contains_key(m) {
3033 try_prove_dense_key(*m, index, state, mutated, &local, facts);
3034 }
3035 }
3036 try_prove_affine(collection, index, state, clobbered, mutated, &local, facts);
3037 }
3038 match stmt {
3039 Stmt::If { cond, then_block, else_block } => {
3040 let mut then_facts = local.clone();
3041 affine_add_cmp(cond, false, &mut then_facts);
3042 affine_walk(then_block, state, clobbered, mutated, &then_facts, &inits, facts);
3043 if let Some(eb) = else_block {
3044 let mut else_facts = local.clone();
3045 affine_add_cmp(cond, true, &mut else_facts);
3046 affine_walk(eb, state, clobbered, mutated, &else_facts, &inits, facts);
3047 }
3048 }
3049 Stmt::While { cond, body, .. } => {
3050 let nested: std::collections::HashSet<Symbol> =
3054 collect_mutations(body).into_iter().collect();
3055 let mut inner = local.clone();
3056 affine_drop_mentioning(&mut inner, &nested);
3057 affine_add_loop_guards(cond, body, state, None, &inits, &mut inner);
3058 affine_walk(body, state, clobbered, mutated, &inner, &inits, facts);
3059 }
3060 Stmt::Repeat { body, .. } => {
3061 affine_walk(body, state, clobbered, mutated, &local, &inits, facts);
3062 }
3063 _ => {}
3064 }
3065 match stmt {
3069 Stmt::Set { target, value } => {
3070 if let Expr::Literal(Literal::Number(n)) = value {
3071 inits.insert(*target, *n);
3072 } else {
3073 inits.remove(target);
3074 }
3075 affine_drop_one(&mut local, *target);
3076 }
3077 Stmt::Let { var, value, .. } => {
3078 if let Expr::Literal(Literal::Number(n)) = value {
3079 inits.insert(*var, *n);
3080 } else {
3081 inits.remove(var);
3082 }
3083 affine_drop_one(&mut local, *var);
3084 }
3085 _ => {}
3086 }
3087 }
3088}
3089
3090fn try_prove_affine(
3093 collection: &Expr,
3094 index: &Expr,
3095 state: &RichAbstractState,
3096 clobbered: &std::collections::HashSet<Symbol>,
3097 mutated: &std::collections::HashSet<Symbol>,
3098 amb: &AffineFacts,
3099 facts: &mut OracleFacts,
3100) {
3101 use super::affine::{konst, le, lin_of, prove, var};
3102 let Expr::Identifier(arr) = collection else { return };
3103 if clobbered.contains(arr) {
3104 return;
3105 }
3106 let key = index as *const Expr as usize;
3107 if facts.relational_inbounds.contains(&key) {
3108 return; }
3110 let Some(e_lin) = lin_of(index) else { return };
3111 let Some((len_lb, len_syms)) = affine_length_lb(*arr, state) else {
3112 return;
3113 };
3114
3115 let mut system = amb.constraints.clone();
3116 let mut syms = amb.syms.clone();
3117 affine_collect_syms(index, &mut syms);
3118 for s in len_syms {
3119 if !syms.contains(&s) {
3120 syms.push(s);
3121 }
3122 }
3123 for &s in &syms {
3124 if let Some(def) = state.scalar_def.get(&s) {
3127 let sv = var(s);
3128 system.push(le(&sv, def));
3129 system.push(le(def, &sv));
3130 }
3131 if !mutated.contains(&s) {
3136 let iv = state.intervals.get_var(&s);
3137 if let Bound::Finite(lo) = iv.lo {
3138 system.push(le(&konst(lo), &var(s)));
3139 }
3140 if let Bound::Finite(hi) = iv.hi {
3141 system.push(le(&var(s), &konst(hi)));
3142 }
3143 }
3144 if let Some(l) = state.scalar_lower.get(&s) {
3157 system.push(le(l, &var(s)));
3158 }
3159 }
3160
3161 if !super::affine::consistent(&system) {
3164 return;
3165 }
3166 let lower = e_lin.add(&konst(-1));
3168 let upper = len_lb.sub(&e_lin);
3169 if !prove(&system, &lower) {
3170 return;
3171 }
3172 if prove(&system, &upper) {
3174 facts.relational_inbounds.insert(key);
3175 return;
3176 }
3177 let mut mod_divisors: Vec<Symbol> = Vec::new();
3183 for &s in &syms {
3184 if let Some(u) = state.scalar_upper.get(&s) {
3185 system.push(le(&var(s), u));
3186 if let Some(m) = mod_upper_divisor(u) {
3187 mod_divisors.push(m);
3188 }
3189 }
3190 }
3191 if super::affine::consistent(&system) && prove(&system, &upper) {
3192 facts.relational_inbounds.insert(key);
3193 if !mod_divisors.is_empty() {
3194 facts.positivity_guards.entry(key).or_default().extend(mod_divisors);
3195 }
3196 }
3197}
3198
3199fn try_prove_dense_key(
3207 map: Symbol,
3208 key: &Expr,
3209 state: &RichAbstractState,
3210 mutated: &std::collections::HashSet<Symbol>,
3211 amb: &AffineFacts,
3212 facts: &mut OracleFacts,
3213) {
3214 use super::affine::{consistent, konst, le, lin_of, prove, var};
3215 let key_addr = key as *const Expr as usize;
3216 if facts.dense_map_key_proven.contains_key(&key_addr) {
3217 return;
3218 }
3219 let Some(cap) = facts.map_caps.get(&map).cloned() else {
3220 return;
3221 };
3222 let Some(k_lin) = lin_of(key) else { return };
3223
3224 let mut system = amb.constraints.clone();
3232 let mut syms = amb.syms.clone();
3233 affine_collect_syms(key, &mut syms);
3234 let mut reached: std::collections::HashSet<Symbol> = syms.iter().copied().collect();
3235 let mut w = 0;
3236 while w < syms.len() {
3237 let s = syms[w];
3238 w += 1;
3239 for chained in [
3240 state.scalar_def.get(&s),
3241 state.scalar_upper.get(&s),
3242 state.scalar_lower.get(&s),
3243 ] {
3244 if let Some(e) = chained {
3245 for &idx in e.coefficients.keys() {
3246 let t = Symbol::from_index(idx as usize);
3247 if reached.insert(t) {
3248 syms.push(t);
3249 }
3250 }
3251 }
3252 }
3253 }
3254 for &s in &syms {
3255 if let Some(def) = state.scalar_def.get(&s) {
3256 let sv = var(s);
3257 system.push(le(&sv, def));
3258 system.push(le(def, &sv));
3259 }
3260 if !mutated.contains(&s) {
3261 let iv = state.intervals.get_var(&s);
3262 if let Bound::Finite(lo) = iv.lo {
3263 system.push(le(&konst(lo), &var(s)));
3264 }
3265 if let Bound::Finite(hi) = iv.hi {
3266 system.push(le(&var(s), &konst(hi)));
3267 }
3268 }
3269 if let Some(l) = state.scalar_lower.get(&s) {
3270 system.push(le(l, &var(s)));
3271 }
3272 if let Some(u) = state.scalar_upper.get(&s) {
3273 system.push(le(&var(s), u));
3274 }
3275 }
3276
3277 if !consistent(&system) {
3279 return;
3280 }
3281 if prove(&system, &k_lin) && prove(&system, &cap.sub(&k_lin)) {
3283 facts.dense_map_key_proven.insert(key_addr, map);
3284 if let Some((a, b)) = facts.map_insert_cover.get(&map).cloned() {
3288 if prove(&system, &k_lin.sub(&a)) && prove(&system, &b.sub(&k_lin)) {
3289 facts.map_key_covered.insert(key_addr, map);
3290 }
3291 }
3292 }
3293}
3294
3295fn collect_iv_guards(cond: &Expr, state: &RichAbstractState, out: &mut Vec<IvGuard>) {
3300 match cond {
3301 Expr::BinaryOp { op: BinaryOpKind::And, left, right } => {
3302 collect_iv_guards(left, state, out);
3303 collect_iv_guards(right, state, out);
3304 }
3305 Expr::BinaryOp { op, left, right } => {
3306 let (iv_e, bnd_e, strict): (&Expr, &Expr, bool) = match op {
3307 BinaryOpKind::Lt => (*left, *right, true),
3308 BinaryOpKind::LtEq => (*left, *right, false),
3309 BinaryOpKind::Gt => (*right, *left, true),
3310 BinaryOpKind::GtEq => (*right, *left, false),
3311 _ => return,
3312 };
3313 let Expr::Identifier(iv) = iv_e else { return };
3314 let iv_lo = match state.intervals.get_var(iv).lo {
3319 Bound::Finite(lo) => lo,
3320 _ => i64::MIN,
3321 };
3322 let bound = match bnd_e {
3323 Expr::Length { collection } => match &**collection {
3324 Expr::Identifier(arr) => IvBound::LenOf(*arr),
3325 _ => return,
3326 },
3327 Expr::Identifier(v) => IvBound::Var { var: *v, headroom: 0 },
3328 _ => match var_minus_bound(bnd_e, state) {
3330 Some((v, headroom)) => IvBound::Var { var: v, headroom },
3331 None => return,
3332 },
3333 };
3334 out.push(IvGuard { iv: *iv, bound, strict, iv_lo });
3335 }
3336 _ => {}
3337 }
3338}
3339
3340fn var_minus_bound(b: &Expr, state: &RichAbstractState) -> Option<(Symbol, i64)> {
3345 match b {
3346 Expr::Identifier(v) => Some((*v, 0)),
3347 Expr::BinaryOp { op: BinaryOpKind::Subtract, left, right } => {
3348 let (v, head) = var_minus_bound(left, state)?;
3349 let r_lo = match eval_expr(right, &state.intervals).lo {
3352 Bound::Finite(lo) => lo,
3353 _ => return None,
3354 };
3355 if r_lo < 0 {
3356 return None;
3357 }
3358 Some((v, head.saturating_add(r_lo)))
3359 }
3360 _ => None,
3361 }
3362}
3363
3364fn affine_of(e: &Expr) -> Option<(Symbol, i64)> {
3367 match e {
3368 Expr::Identifier(s) => Some((*s, 0)),
3369 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
3370 match (&**left, &**right) {
3371 (Expr::Identifier(s), Expr::Literal(Literal::Number(k))) => Some((*s, *k)),
3372 (Expr::Literal(Literal::Number(k)), Expr::Identifier(s)) => Some((*s, *k)),
3373 _ => None,
3374 }
3375 }
3376 Expr::BinaryOp { op: BinaryOpKind::Subtract, left, right } => {
3377 match (&**left, &**right) {
3378 (Expr::Identifier(s), Expr::Literal(Literal::Number(k))) => Some((*s, -*k)),
3379 _ => None,
3380 }
3381 }
3382 _ => None,
3383 }
3384}
3385
3386fn try_record_index(
3391 collection: &Expr,
3392 index: &Expr,
3393 guards: &[IvGuard],
3394 length_def: &HashMap<Symbol, (Symbol, i64)>,
3395 clobbered: &std::collections::HashSet<Symbol>,
3396 facts: &mut OracleFacts,
3397) {
3398 let Expr::Identifier(arr) = collection else { return };
3399 if clobbered.contains(arr) {
3400 return;
3401 }
3402 let Some((iv, k)) = affine_of(index) else { return };
3403 let proven = guards
3404 .iter()
3405 .any(|g| g.iv == iv && !clobbered.contains(&g.iv) && guard_proves(g, *arr, k, length_def));
3406 if proven {
3407 facts.relational_inbounds.insert(index as *const Expr as usize);
3408 }
3409}
3410
3411fn guard_proves(
3414 g: &IvGuard,
3415 arr: Symbol,
3416 k: i64,
3417 length_def: &HashMap<Symbol, (Symbol, i64)>,
3418) -> bool {
3419 if g.iv_lo.saturating_add(k) < 1 {
3421 return false;
3422 }
3423 let slack = if g.strict { 1 } else { 0 };
3425 match &g.bound {
3426 IvBound::LenOf(g_arr) => *g_arr == arr && k - slack <= 0,
3428 IvBound::Var { var, headroom } => match length_def.get(&arr) {
3432 Some((n, off)) => *n == *var && k - slack - headroom <= *off,
3433 None => false,
3434 },
3435 }
3436}
3437
3438fn var_rebound_in(sym: Symbol, body: &[Stmt]) -> bool {
3440 body.iter().any(|s| match s {
3441 Stmt::Set { target, .. } => *target == sym,
3442 Stmt::Let { var, .. } => *var == sym,
3443 Stmt::If { then_block, else_block, .. } => {
3444 var_rebound_in(sym, then_block)
3445 || matches!(else_block, Some(eb) if var_rebound_in(sym, eb))
3446 }
3447 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => var_rebound_in(sym, body),
3448 _ => false,
3449 })
3450}
3451
3452fn is_positive_increment(value: &Expr, iv: Symbol) -> bool {
3454 if let Expr::BinaryOp { op: BinaryOpKind::Add, left, right } = value {
3455 let is_iv = |e: &Expr| matches!(e, Expr::Identifier(s) if *s == iv);
3456 let amt = match (&**left, &**right) {
3457 (l, Expr::Literal(Literal::Number(c))) if is_iv(l) => Some(*c),
3458 (Expr::Literal(Literal::Number(c)), r) if is_iv(r) => Some(*c),
3459 _ => None,
3460 };
3461 return amt.is_some_and(|c| c >= 1);
3462 }
3463 false
3464}
3465
3466fn iv_increment_is_last(iv: Symbol, body: &[Stmt]) -> bool {
3471 let Some((last, rest)) = body.split_last() else { return false };
3472 let last_is_inc = matches!(last, Stmt::Set { target, value }
3473 if *target == iv && is_positive_increment(value, iv));
3474 last_is_inc && !var_rebound_in(iv, rest)
3475}
3476
3477fn consider_hoist_access(
3480 collection: &Expr,
3481 index: &Expr,
3482 iv: Symbol,
3483 state: &RichAbstractState,
3484 body: &[Stmt],
3485 facts: &mut OracleFacts,
3486 per_array: &mut HashMap<Symbol, (i64, i64)>,
3487) {
3488 let Expr::Identifier(arr) = collection else { return };
3489 if !state.coll_vars.contains(arr) || var_rebound_in(*arr, body) || array_resized_in(*arr, body) {
3493 return;
3494 }
3495 if !state.param_colls.contains(arr) {
3500 return;
3501 }
3502 let Some((aiv, k)) = affine_of(index) else { return };
3503 if aiv != iv {
3504 return;
3505 }
3506 let key = index as *const Expr as usize;
3507 if facts.relational_inbounds.contains(&key) {
3508 return; }
3510 facts.speculative_inbounds.insert(key);
3511 let entry = per_array.entry(*arr).or_insert((k, k));
3512 entry.0 = entry.0.min(k);
3513 entry.1 = entry.1.max(k);
3514}
3515
3516fn walk_hoist_accesses(
3518 stmts: &[Stmt],
3519 iv: Symbol,
3520 state: &RichAbstractState,
3521 body: &[Stmt],
3522 facts: &mut OracleFacts,
3523 per_array: &mut HashMap<Symbol, (i64, i64)>,
3524) {
3525 for s in stmts {
3526 for_each_direct_expr(s, &mut |e| {
3527 walk_hoist_in_expr(e, iv, state, body, facts, per_array)
3528 });
3529 if let Stmt::SetIndex { collection, index, .. } = s {
3530 consider_hoist_access(collection, index, iv, state, body, facts, per_array);
3531 }
3532 match s {
3533 Stmt::If { then_block, else_block, .. } => {
3534 walk_hoist_accesses(then_block, iv, state, body, facts, per_array);
3535 if let Some(eb) = else_block {
3536 walk_hoist_accesses(eb, iv, state, body, facts, per_array);
3537 }
3538 }
3539 Stmt::While { body: b, .. } | Stmt::Repeat { body: b, .. } => {
3540 walk_hoist_accesses(b, iv, state, body, facts, per_array);
3541 }
3542 _ => {}
3543 }
3544 }
3545}
3546
3547fn walk_hoist_in_expr(
3549 e: &Expr,
3550 iv: Symbol,
3551 state: &RichAbstractState,
3552 body: &[Stmt],
3553 facts: &mut OracleFacts,
3554 per_array: &mut HashMap<Symbol, (i64, i64)>,
3555) {
3556 use crate::ast::stmt::StringPart;
3557 if let Expr::Index { collection, index } = e {
3558 consider_hoist_access(collection, index, iv, state, body, facts, per_array);
3559 }
3560 let mut go = |x: &Expr| walk_hoist_in_expr(x, iv, state, body, facts, per_array);
3561 match e {
3562 Expr::BinaryOp { left, right, .. }
3563 | Expr::Union { left, right }
3564 | Expr::Intersection { left, right }
3565 | Expr::Range { start: left, end: right } => {
3566 go(left);
3567 go(right);
3568 }
3569 Expr::Not { operand } => go(operand),
3570 Expr::Call { args, .. } => args.iter().for_each(|a| go(a)),
3571 Expr::CallExpr { callee, args } => {
3572 go(callee);
3573 args.iter().for_each(|a| go(a));
3574 }
3575 Expr::Index { collection, index } => {
3576 go(collection);
3577 go(index);
3578 }
3579 Expr::Slice { collection, start, end } => {
3580 go(collection);
3581 go(start);
3582 go(end);
3583 }
3584 Expr::Copy { expr } => go(expr),
3585 Expr::Give { value } => go(value),
3586 Expr::Length { collection } => go(collection),
3587 Expr::Contains { collection, value } => {
3588 go(collection);
3589 go(value);
3590 }
3591 Expr::List(items) | Expr::Tuple(items) => items.iter().for_each(|i| go(i)),
3592 Expr::FieldAccess { object, .. } => go(object),
3593 Expr::New { init_fields, .. } => init_fields.iter().for_each(|(_, fe)| go(fe)),
3594 Expr::NewVariant { fields, .. } => fields.iter().for_each(|(_, fe)| go(fe)),
3595 Expr::OptionSome { value } => go(value),
3596 Expr::WithCapacity { value, capacity } => {
3597 go(value);
3598 go(capacity);
3599 }
3600 Expr::InterpolatedString(parts) => {
3601 for p in parts {
3602 if let StringPart::Expr { value, .. } = p {
3603 go(value);
3604 }
3605 }
3606 }
3607 _ => {}
3608 }
3609}
3610
3611fn record_hoist_speculation(
3619 cond: &Expr,
3620 body: &[Stmt],
3621 state: &RichAbstractState,
3622 facts: &mut OracleFacts,
3623 loop_key: usize,
3624) {
3625 if !body_is_index_proof_safe(body) {
3628 return;
3629 }
3630 let mut guards: Vec<IvGuard> = Vec::new();
3631 collect_iv_guards(cond, state, &mut guards);
3632 let Some(g) = guards.iter().find(|g| {
3635 matches!(g.bound, IvBound::Var { headroom: 0, var } if !var_rebound_in(var, body))
3636 && iv_increment_is_last(g.iv, body)
3637 }) else {
3638 return;
3639 };
3640 let IvBound::Var { var: bound, .. } = g.bound else {
3641 return;
3642 };
3643 if bound == g.iv {
3644 return;
3645 }
3646 let mut per_array: HashMap<Symbol, (i64, i64)> = HashMap::new();
3647 walk_hoist_accesses(body, g.iv, state, body, facts, &mut per_array);
3648 if per_array.is_empty() {
3649 return;
3650 }
3651 let strict = if g.strict { 1 } else { 0 };
3652 let descs: Vec<HoistDesc> = per_array
3653 .into_iter()
3654 .filter_map(|(array, (kmin, kmax))| {
3655 Some(HoistDesc {
3656 array,
3657 bound,
3658 iv: g.iv,
3659 add_max: i32::try_from(kmax - strict).ok()?,
3660 add_min: i32::try_from(kmin).ok()?,
3661 })
3662 })
3663 .collect();
3664 if !descs.is_empty() {
3665 facts.hoist_descs.entry(loop_key).or_default().extend(descs);
3666 }
3667}
3668
3669enum BuildLength {
3671 Symbolic(Symbol, Symbol, i64),
3673 Concrete(Symbol, i64),
3676}
3677
3678fn infer_build_length(
3686 cond: &Expr,
3687 body: &[Stmt],
3688 entry: &RichAbstractState,
3689) -> Vec<BuildLength> {
3690 let (c_e, b_e, strict) = match cond {
3691 Expr::BinaryOp { op, left, right } => {
3692 let (l, r) = (&**left, &**right);
3693 match op {
3694 BinaryOpKind::Lt => (l, r, true),
3695 BinaryOpKind::LtEq => (l, r, false),
3696 _ => return Vec::new(),
3697 }
3698 }
3699 _ => return Vec::new(),
3700 };
3701 let Expr::Identifier(c) = c_e else { return Vec::new() };
3702 let c = *c;
3703 let bound_var = match b_e {
3705 Expr::Identifier(n) => Some(*n),
3706 Expr::Literal(Literal::Number(_)) => None,
3707 _ => return Vec::new(),
3708 };
3709 if bound_var == Some(c) {
3710 return Vec::new();
3711 }
3712 let iv = entry.intervals.get_var(&c);
3714 let c0 = match (iv.lo, iv.hi) {
3715 (Bound::Finite(lo), Bound::Finite(hi)) if lo == hi => lo,
3716 _ => return Vec::new(),
3717 };
3718 let mut push_counts: HashMap<Symbol, u32> = HashMap::new();
3725 let mut increments = 0;
3726 for s in body {
3727 match s {
3728 Stmt::Push { collection, .. } => {
3729 let Expr::Identifier(a) = &**collection else {
3730 return Vec::new();
3731 };
3732 *push_counts.entry(*a).or_insert(0) += 1;
3733 }
3734 Stmt::Set { target, value } => {
3735 if *target == c {
3736 if !is_increment_by_one(value, c) {
3737 return Vec::new();
3738 }
3739 increments += 1;
3740 } else if bound_var == Some(*target) {
3741 return Vec::new(); }
3743 }
3744 Stmt::Let { var, .. } => {
3745 if *var == c || bound_var == Some(*var) {
3746 return Vec::new();
3747 }
3748 }
3749 Stmt::Show { .. }
3752 | Stmt::RuntimeAssert { .. }
3753 | Stmt::Assert { .. }
3754 | Stmt::Trust { .. } => {}
3755 _ => return Vec::new(),
3756 }
3757 }
3758 if increments != 1 {
3759 return Vec::new();
3760 }
3761 let off = if strict { -c0 } else { -c0 + 1 };
3763 let mut arrays: Vec<(Symbol, u32)> = push_counts.into_iter().collect();
3765 arrays.sort_by_key(|(a, _)| a.index());
3766 let mut out = Vec::new();
3767 for (arr, count) in arrays {
3768 if count != 1 || arr == c || bound_var == Some(arr) {
3769 continue;
3770 }
3771 let entry_len = entry.intervals.get_length(&arr);
3773 if !matches!(
3774 (entry_len.lo, entry_len.hi),
3775 (Bound::Finite(0), Bound::Finite(0))
3776 ) {
3777 continue;
3778 }
3779 match (bound_var, b_e) {
3780 (Some(n), _) => out.push(BuildLength::Symbolic(arr, n, off)),
3781 (None, Expr::Literal(Literal::Number(bn))) => {
3782 out.push(BuildLength::Concrete(arr, (*bn + off).max(0)))
3783 }
3784 _ => {}
3785 }
3786 }
3787 out
3788}
3789
3790fn is_increment_by_one(value: &Expr, c: Symbol) -> bool {
3792 if let Expr::BinaryOp { op: BinaryOpKind::Add, left, right } = value {
3793 let is_c = |e: &Expr| matches!(e, Expr::Identifier(s) if *s == c);
3794 let is_one = |e: &Expr| matches!(e, Expr::Literal(Literal::Number(1)));
3795 return (is_c(&**left) && is_one(&**right)) || (is_one(&**left) && is_c(&**right));
3796 }
3797 false
3798}
3799
3800fn body_is_index_proof_safe(body: &[Stmt]) -> bool {
3807 body.iter().all(|s| match s {
3808 Stmt::Let { .. }
3809 | Stmt::Set { .. }
3810 | Stmt::SetIndex { .. }
3811 | Stmt::Push { .. }
3812 | Stmt::Pop { .. }
3813 | Stmt::Add { .. }
3814 | Stmt::Remove { .. }
3815 | Stmt::Show { .. }
3816 | Stmt::Return { .. }
3817 | Stmt::Break
3818 | Stmt::RuntimeAssert { .. }
3819 | Stmt::Assert { .. }
3820 | Stmt::Trust { .. } => true,
3821 Stmt::If { then_block, else_block, .. } => {
3822 body_is_index_proof_safe(then_block)
3823 && match else_block {
3824 Some(eb) => body_is_index_proof_safe(eb),
3825 None => true,
3826 }
3827 }
3828 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => body_is_index_proof_safe(body),
3829 _ => false,
3830 })
3831}
3832
3833fn collect_resized_arrays(body: &[Stmt], out: &mut std::collections::HashSet<Symbol>) {
3843 for s in body {
3844 match s {
3845 Stmt::Push { .. } | Stmt::Add { .. } => {}
3849 Stmt::Remove { collection, .. } => {
3851 if let Expr::Identifier(c) = &**collection {
3852 out.insert(*c);
3853 }
3854 }
3855 Stmt::Pop { collection, into } => {
3856 if let Expr::Identifier(c) = &**collection {
3857 out.insert(*c);
3858 }
3859 if let Some(v) = into {
3860 out.insert(*v);
3861 }
3862 }
3863 Stmt::Let { value, .. } | Stmt::Set { value, .. } => {
3867 if let Expr::Identifier(src) = &**value {
3868 out.insert(*src);
3869 }
3870 }
3871 Stmt::If { then_block, else_block, .. } => {
3872 collect_resized_arrays(then_block, out);
3873 if let Some(eb) = else_block {
3874 collect_resized_arrays(eb, out);
3875 }
3876 }
3877 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
3878 collect_resized_arrays(body, out);
3879 }
3880 _ => {}
3881 }
3882 }
3883}
3884
3885fn array_resized_in(sym: Symbol, body: &[Stmt]) -> bool {
3888 let hits = |c: &Expr| matches!(c, Expr::Identifier(s) if *s == sym);
3889 body.iter().any(|s| match s {
3890 Stmt::Push { collection, .. }
3891 | Stmt::Add { collection, .. }
3892 | Stmt::Remove { collection, .. } => hits(collection),
3893 Stmt::Pop { collection, into } => {
3894 hits(collection) || matches!(into, Some(v) if *v == sym)
3895 }
3896 Stmt::If { then_block, else_block, .. } => {
3897 array_resized_in(sym, then_block)
3898 || matches!(else_block, Some(eb) if array_resized_in(sym, eb))
3899 }
3900 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => array_resized_in(sym, body),
3901 _ => false,
3902 })
3903}
3904
3905fn record_proven_reads(
3911 stmts: &[Stmt],
3912 guards: &[IvGuard],
3913 length_def: &HashMap<Symbol, (Symbol, i64)>,
3914 clobbered: &mut std::collections::HashSet<Symbol>,
3915 facts: &mut OracleFacts,
3916) {
3917 for stmt in stmts {
3918 for_each_direct_expr(stmt, &mut |e| {
3919 record_index_reads(e, guards, length_def, clobbered, facts)
3920 });
3921 if let Stmt::SetIndex { collection, index, .. } = stmt {
3924 try_record_index(collection, index, guards, length_def, clobbered, facts);
3925 }
3926 match stmt {
3927 Stmt::If { then_block, else_block, .. } => {
3928 let mut c_then = clobbered.clone();
3929 record_proven_reads(then_block, guards, length_def, &mut c_then, facts);
3930 let mut c_else = clobbered.clone();
3931 if let Some(eb) = else_block {
3932 record_proven_reads(eb, guards, length_def, &mut c_else, facts);
3933 }
3934 clobbered.extend(c_then);
3935 clobbered.extend(c_else);
3936 }
3937 Stmt::While { body, .. } => {
3938 let mut inner = clobbered.clone();
3939 for m in collect_mutations(body) {
3940 inner.insert(m);
3941 }
3942 record_proven_reads(body, guards, length_def, &mut inner.clone(), facts);
3943 *clobbered = inner;
3944 }
3945 Stmt::Repeat { pattern, body, .. } => {
3946 if let Some(v) = pattern_loop_var(pattern) {
3947 clobbered.insert(v);
3948 }
3949 for m in collect_mutations(body) {
3950 clobbered.insert(m);
3951 }
3952 }
3953 _ => {}
3954 }
3955 match stmt {
3958 Stmt::Set { target, .. } => {
3959 clobbered.insert(*target);
3960 }
3961 Stmt::Let { var, .. } => {
3962 clobbered.insert(*var);
3963 }
3964 _ => {}
3965 }
3966 }
3967}
3968
3969fn for_each_direct_expr(stmt: &Stmt, f: &mut impl FnMut(&Expr)) {
3972 match stmt {
3973 Stmt::Let { value, .. } | Stmt::Set { value, .. } => f(value),
3974 Stmt::SetIndex { collection, index, value } => {
3975 f(collection);
3976 f(index);
3977 f(value);
3978 }
3979 Stmt::Show { object, .. } => f(object),
3980 Stmt::Push { value, .. } | Stmt::Add { value, .. } | Stmt::Remove { value, .. } => f(value),
3983 Stmt::Return { value: Some(v) } => f(v),
3984 Stmt::RuntimeAssert { condition, .. } => f(condition),
3985 Stmt::If { cond, .. } => f(cond),
3986 Stmt::While { cond, decreasing, .. } => {
3987 f(cond);
3988 if let Some(d) = decreasing {
3989 f(d);
3990 }
3991 }
3992 Stmt::Repeat { iterable, .. } => f(iterable),
3993 _ => {}
3994 }
3995}
3996
3997fn record_index_reads(
4001 e: &Expr,
4002 guards: &[IvGuard],
4003 length_def: &HashMap<Symbol, (Symbol, i64)>,
4004 clobbered: &std::collections::HashSet<Symbol>,
4005 facts: &mut OracleFacts,
4006) {
4007 use crate::ast::stmt::StringPart;
4008 if let Expr::Index { collection, index } = e {
4009 try_record_index(collection, index, guards, length_def, clobbered, facts);
4010 }
4011 match e {
4012 Expr::BinaryOp { left, right, .. }
4013 | Expr::Union { left, right }
4014 | Expr::Intersection { left, right }
4015 | Expr::Range { start: left, end: right } => {
4016 record_index_reads(left, guards, length_def, clobbered, facts);
4017 record_index_reads(right, guards, length_def, clobbered, facts);
4018 }
4019 Expr::Not { operand } => record_index_reads(operand, guards, length_def, clobbered, facts),
4020 Expr::Call { args, .. } => {
4021 for a in args {
4022 record_index_reads(a, guards, length_def, clobbered, facts);
4023 }
4024 }
4025 Expr::CallExpr { callee, args } => {
4026 record_index_reads(callee, guards, length_def, clobbered, facts);
4027 for a in args {
4028 record_index_reads(a, guards, length_def, clobbered, facts);
4029 }
4030 }
4031 Expr::Index { collection, index } => {
4032 record_index_reads(collection, guards, length_def, clobbered, facts);
4033 record_index_reads(index, guards, length_def, clobbered, facts);
4034 }
4035 Expr::Slice { collection, start, end } => {
4036 record_index_reads(collection, guards, length_def, clobbered, facts);
4037 record_index_reads(start, guards, length_def, clobbered, facts);
4038 record_index_reads(end, guards, length_def, clobbered, facts);
4039 }
4040 Expr::Copy { expr } => record_index_reads(expr, guards, length_def, clobbered, facts),
4041 Expr::Give { value } => record_index_reads(value, guards, length_def, clobbered, facts),
4042 Expr::Length { collection } => record_index_reads(collection, guards, length_def, clobbered, facts),
4043 Expr::Contains { collection, value } => {
4044 record_index_reads(collection, guards, length_def, clobbered, facts);
4045 record_index_reads(value, guards, length_def, clobbered, facts);
4046 }
4047 Expr::List(items) | Expr::Tuple(items) => {
4048 for it in items {
4049 record_index_reads(it, guards, length_def, clobbered, facts);
4050 }
4051 }
4052 Expr::FieldAccess { object, .. } => record_index_reads(object, guards, length_def, clobbered, facts),
4053 Expr::New { init_fields, .. } => {
4054 for (_, fe) in init_fields {
4055 record_index_reads(fe, guards, length_def, clobbered, facts);
4056 }
4057 }
4058 Expr::NewVariant { fields, .. } => {
4059 for (_, fe) in fields {
4060 record_index_reads(fe, guards, length_def, clobbered, facts);
4061 }
4062 }
4063 Expr::OptionSome { value } => record_index_reads(value, guards, length_def, clobbered, facts),
4064 Expr::WithCapacity { value, capacity } => {
4065 record_index_reads(value, guards, length_def, clobbered, facts);
4066 record_index_reads(capacity, guards, length_def, clobbered, facts);
4067 }
4068 Expr::InterpolatedString(parts) => {
4069 for p in parts {
4070 if let StringPart::Expr { value, .. } = p {
4071 record_index_reads(value, guards, length_def, clobbered, facts);
4072 }
4073 }
4074 }
4075 _ => {}
4076 }
4077}
4078
4079fn each_child_block(s: &Stmt, f: &mut impl FnMut(&[Stmt])) {
4085 match s {
4086 Stmt::If { then_block, else_block, .. } => {
4087 f(then_block);
4088 if let Some(eb) = else_block {
4089 f(eb);
4090 }
4091 }
4092 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => f(body),
4093 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => f(tasks),
4094 Stmt::Inspect { arms, .. } => {
4095 for a in arms {
4096 f(a.body);
4097 }
4098 }
4099 Stmt::FunctionDef { body, .. } => f(body),
4100 _ => {}
4101 }
4102}
4103
4104fn collect_reassigned(stmts: &[Stmt]) -> std::collections::HashSet<Symbol> {
4112 fn walk(
4113 stmts: &[Stmt],
4114 set_targets: &mut std::collections::HashSet<Symbol>,
4115 let_counts: &mut HashMap<Symbol, u32>,
4116 ) {
4117 for s in stmts {
4118 match s {
4119 Stmt::Set { target, .. } => {
4120 set_targets.insert(*target);
4121 }
4122 Stmt::Let { var, .. } => {
4123 *let_counts.entry(*var).or_insert(0) += 1;
4124 }
4125 _ => {}
4126 }
4127 each_child_block(s, &mut |b| walk(b, set_targets, let_counts));
4128 }
4129 }
4130 let mut set_targets = std::collections::HashSet::new();
4131 let mut let_counts: HashMap<Symbol, u32> = HashMap::new();
4132 walk(stmts, &mut set_targets, &mut let_counts);
4133 for (sym, count) in let_counts {
4134 if count > 1 {
4135 set_targets.insert(sym);
4136 }
4137 }
4138 set_targets
4139}
4140
4141fn gather_map_caps(
4149 stmts: &[Stmt],
4150 interner: &crate::intern::Interner,
4151 reassigned: &std::collections::HashSet<Symbol>,
4152 out: &mut HashMap<Symbol, super::affine::LinExpr>,
4153 poisoned: &mut std::collections::HashSet<Symbol>,
4154) {
4155 for s in stmts {
4156 if let Stmt::Let { var, value, .. } = s {
4157 if let Expr::WithCapacity { value: inner, capacity } = value {
4158 if let Expr::New { type_name, .. } = inner {
4159 if matches!(interner.resolve(*type_name), "Map" | "HashMap") {
4160 if let Some(cap_lin) = super::affine::lin_of(capacity) {
4161 let mut cap_syms = Vec::new();
4162 affine_collect_syms(capacity, &mut cap_syms);
4163 let invariant = cap_syms.iter().all(|s| !reassigned.contains(s));
4164 if poisoned.contains(var) {
4165 } else if !invariant {
4167 out.remove(var);
4168 poisoned.insert(*var);
4169 } else if out.insert(*var, cap_lin).is_some() {
4170 out.remove(var);
4172 poisoned.insert(*var);
4173 }
4174 }
4175 }
4176 }
4177 }
4178 }
4179 each_child_block(s, &mut |b| gather_map_caps(b, interner, reassigned, out, poisoned));
4180 }
4181}
4182
4183fn counted_loop_bound<'a>(cond: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
4187 let Expr::BinaryOp { op, left, right } = cond else { return None };
4188 if !matches!(op, BinaryOpKind::Lt | BinaryOpKind::LtEq) {
4189 return None;
4190 }
4191 let Expr::Identifier(_) = *left else { return None };
4192 Some(*right)
4193}
4194
4195fn collect_bare_new_maps(
4199 stmts: &[Stmt],
4200 interner: &crate::intern::Interner,
4201 out: &mut std::collections::HashSet<Symbol>,
4202) {
4203 for s in stmts {
4204 if let Stmt::Let { var, value, .. } = s {
4205 if let Expr::New { type_name, .. } = value {
4206 if matches!(interner.resolve(*type_name), "Map" | "HashMap") {
4207 out.insert(*var);
4208 }
4209 }
4210 }
4211 each_child_block(s, &mut |b| collect_bare_new_maps(b, interner, out));
4212 }
4213}
4214
4215fn collect_loop_inserted_maps(
4218 stmts: &[Stmt],
4219 candidates: &std::collections::HashSet<Symbol>,
4220 out: &mut Vec<Symbol>,
4221) {
4222 for s in stmts {
4223 if let Stmt::SetIndex { collection: Expr::Identifier(m), .. } = s {
4224 if candidates.contains(m) {
4225 out.push(*m);
4226 }
4227 }
4228 each_child_block(s, &mut |b| collect_loop_inserted_maps(b, candidates, out));
4229 }
4230}
4231
4232fn gather_implicit_map_caps(
4243 stmts: &[Stmt],
4244 interner: &crate::intern::Interner,
4245 reassigned: &std::collections::HashSet<Symbol>,
4246 poisoned: &std::collections::HashSet<Symbol>,
4247 out: &mut HashMap<Symbol, super::affine::LinExpr>,
4248) {
4249 let mut bare = std::collections::HashSet::new();
4250 collect_bare_new_maps(stmts, interner, &mut bare);
4251 if bare.is_empty() {
4252 return;
4253 }
4254 fn walk(
4255 stmts: &[Stmt],
4256 bare: &std::collections::HashSet<Symbol>,
4257 reassigned: &std::collections::HashSet<Symbol>,
4258 poisoned: &std::collections::HashSet<Symbol>,
4259 out: &mut HashMap<Symbol, super::affine::LinExpr>,
4260 ) {
4261 for s in stmts {
4262 if let Stmt::While { cond, body, .. } = s {
4263 if let Some(bound) = counted_loop_bound(cond) {
4264 if let Some(b_lin) = super::affine::lin_of(bound) {
4265 let mut syms = Vec::new();
4266 affine_collect_syms(bound, &mut syms);
4267 if syms.iter().all(|x| !reassigned.contains(x)) {
4268 let mut inserted = Vec::new();
4269 collect_loop_inserted_maps(body, bare, &mut inserted);
4270 for m in inserted {
4271 if !poisoned.contains(&m) && !out.contains_key(&m) {
4272 out.insert(m, b_lin.clone());
4273 }
4274 }
4275 }
4276 }
4277 }
4278 }
4279 each_child_block(s, &mut |b| walk(b, bare, reassigned, poisoned, out));
4280 }
4281 }
4282 walk(stmts, &bare, reassigned, poisoned, out);
4283}
4284
4285pub fn lin_to_rust(e: &super::affine::LinExpr, interner: &crate::intern::Interner) -> Option<String> {
4291 let mut coeffs: Vec<(i64, i64)> = Vec::new();
4292 for (idx, c) in e.coefficients.iter() {
4293 let ci = c.to_i64()?;
4294 if ci != 0 {
4295 coeffs.push((*idx, ci));
4296 }
4297 }
4298 coeffs.sort_by_key(|&(idx, _)| idx);
4299 let mut terms: Vec<String> = Vec::new();
4300 for (idx, ci) in coeffs {
4301 let name = interner.resolve(Symbol::from_index(idx as usize));
4302 terms.push(if ci == 1 {
4303 name.to_string()
4304 } else {
4305 format!("{} * {}", ci, name)
4306 });
4307 }
4308 let k = e.constant.to_i64()?;
4309 if k != 0 || terms.is_empty() {
4310 terms.push(k.to_string());
4311 }
4312 Some(terms.join(" + "))
4313}
4314
4315fn count_insert_sites(stmts: &[Stmt], out: &mut HashMap<Symbol, u32>) {
4319 for s in stmts {
4320 if let Stmt::SetIndex { collection: Expr::Identifier(m), .. } = s {
4321 *out.entry(*m).or_insert(0) += 1;
4322 }
4323 each_child_block(s, &mut |b| count_insert_sites(b, out));
4324 }
4325}
4326
4327fn match_insert_loop(
4336 cond: &Expr,
4337 body: &[Stmt],
4338 map_caps: &HashMap<Symbol, super::affine::LinExpr>,
4339) -> Option<(Symbol, Symbol, super::affine::LinExpr)> {
4340 use super::affine::{konst, lin_of};
4341 let Expr::BinaryOp { op, left, right } = cond else { return None };
4343 let Expr::Identifier(iv) = left else { return None };
4344 let b_lin = match op {
4345 BinaryOpKind::Lt => lin_of(right)?.sub(&konst(1)),
4346 BinaryOpKind::LtEq => lin_of(right)?,
4347 _ => return None,
4348 };
4349 if body.len() != 2 {
4350 return None;
4351 }
4352 let mut found_map: Option<Symbol> = None;
4353 let mut found_incr = false;
4354 for s in body {
4355 match s {
4356 Stmt::SetIndex { collection: Expr::Identifier(m), index: Expr::Identifier(ix), .. }
4358 if ix == iv && map_caps.contains_key(m) =>
4359 {
4360 if found_map.is_some() {
4361 return None;
4362 }
4363 found_map = Some(*m);
4364 }
4365 Stmt::Set { target, value: Expr::BinaryOp { op: BinaryOpKind::Add, left: l, right: r } }
4367 if target == iv =>
4368 {
4369 let iv_plus_one = matches!((l, r),
4370 (Expr::Identifier(a), Expr::Literal(Literal::Number(1))) if a == iv)
4371 || matches!((l, r),
4372 (Expr::Literal(Literal::Number(1)), Expr::Identifier(a)) if a == iv);
4373 if !iv_plus_one {
4374 return None;
4375 }
4376 found_incr = true;
4377 }
4378 _ => return None,
4379 }
4380 }
4381 match (found_map, found_incr) {
4382 (Some(m), true) => Some((m, *iv, b_lin)),
4383 _ => None,
4384 }
4385}
4386
4387fn gather_insert_coverage(
4393 stmts: &[Stmt],
4394 map_caps: &HashMap<Symbol, super::affine::LinExpr>,
4395 insert_counts: &HashMap<Symbol, u32>,
4396 inits: &mut HashMap<Symbol, i64>,
4397 out: &mut HashMap<Symbol, (super::affine::LinExpr, super::affine::LinExpr)>,
4398) {
4399 use super::affine::konst;
4400 for s in stmts {
4401 if let Stmt::While { cond, body, .. } = s {
4402 if let Some((m, iv, b_lin)) = match_insert_loop(cond, body, map_caps) {
4403 if insert_counts.get(&m) == Some(&1) {
4404 if let Some(&a) = inits.get(&iv) {
4405 out.entry(m).or_insert((konst(a), b_lin));
4406 }
4407 }
4408 }
4409 }
4410 each_child_block(s, &mut |b| {
4411 gather_insert_coverage(b, map_caps, insert_counts, &mut inits.clone(), out)
4412 });
4413 match s {
4416 Stmt::Let { var, value: Expr::Literal(Literal::Number(n)), .. } => {
4417 inits.insert(*var, *n);
4418 }
4419 Stmt::Let { var, .. } => {
4420 inits.remove(var);
4421 }
4422 Stmt::Set { target, value: Expr::Literal(Literal::Number(n)) } => {
4423 inits.insert(*target, *n);
4424 }
4425 Stmt::Set { target, .. } => {
4426 inits.remove(target);
4427 }
4428 _ => {}
4429 }
4430 }
4431}
4432
4433pub fn oracle_analyze(stmts: &[Stmt]) -> OracleFacts {
4434 let mut facts = OracleFacts::default();
4435 let mut st = RichAbstractState::new();
4436 rich_walk_block(stmts, &mut st, &mut facts);
4437 for s in stmts {
4439 if let Stmt::FunctionDef { params, body, .. } = s {
4440 let mut fst = RichAbstractState::new();
4441 for (psym, ty) in params.iter() {
4442 if let crate::ast::stmt::TypeExpr::Primitive(t) = ty {
4443 fst.types.insert(*psym, type_tag_for_name(*t));
4446 }
4447 fst.aliases.taint(*psym);
4450 }
4451 rich_walk_block(body, &mut fst, &mut facts);
4452 }
4453 }
4454 strip_concurrent_loop_snapshots(stmts, &mut facts, false);
4455 facts
4456}
4457
4458fn type_tag_for_name(_name: Symbol) -> TypeAbstraction {
4463 TypeAbstraction::Top
4466}
4467
4468pub fn oracle_analyze_with(stmts: &[Stmt], interner: &crate::intern::Interner) -> OracleFacts {
4474 oracle_analyze_with_opts(stmts, interner, false)
4475}
4476
4477pub fn oracle_analyze_with_entry_guards(
4483 stmts: &[Stmt],
4484 interner: &crate::intern::Interner,
4485) -> OracleFacts {
4486 oracle_analyze_with_opts(stmts, interner, true)
4487}
4488
4489fn oracle_analyze_with_opts(
4492 stmts: &[Stmt],
4493 interner: &crate::intern::Interner,
4494 aot_entry_guard: bool,
4495) -> OracleFacts {
4496 let mut facts = OracleFacts::default();
4497 {
4501 let reassigned = collect_reassigned(stmts);
4502 let mut poisoned = std::collections::HashSet::new();
4503 gather_map_caps(stmts, interner, &reassigned, &mut facts.map_caps, &mut poisoned);
4504 gather_implicit_map_caps(stmts, interner, &reassigned, &poisoned, &mut facts.map_caps);
4509 let mut insert_counts = HashMap::new();
4512 count_insert_sites(stmts, &mut insert_counts);
4513 let mut inits = HashMap::new();
4514 gather_insert_coverage(stmts, &facts.map_caps, &insert_counts, &mut inits, &mut facts.map_insert_cover);
4515 }
4516 let mut fn_returns: HashMap<Symbol, TypeTag> = HashMap::new();
4520 for s in stmts {
4521 if let Stmt::FunctionDef { name, return_type: Some(ty), .. } = s {
4522 if let crate::ast::stmt::TypeExpr::Primitive(t)
4523 | crate::ast::stmt::TypeExpr::Named(t) = ty
4524 {
4525 let tag = match interner.resolve(*t) {
4526 "Int" => Some(TypeTag::Int),
4527 "Float" => Some(TypeTag::Float),
4528 "Bool" => Some(TypeTag::Bool),
4529 "Text" => Some(TypeTag::Text),
4530 _ => None,
4531 };
4532 if let Some(tag) = tag {
4533 fn_returns.insert(*name, tag);
4534 }
4535 }
4536 }
4537 }
4538 let fn_returns = std::rc::Rc::new(fn_returns);
4539 let mut st = RichAbstractState::new();
4540 st.fn_returns = fn_returns.clone();
4541 rich_walk_block(stmts, &mut st, &mut facts);
4542 for s in stmts {
4543 if let Stmt::FunctionDef { params, body, .. } = s {
4544 let mut fst = RichAbstractState::new();
4545 fst.fn_returns = fn_returns.clone();
4546 fst.aot_entry_guard = aot_entry_guard;
4547 for (psym, ty) in params.iter() {
4548 match ty {
4549 crate::ast::stmt::TypeExpr::Primitive(t) => {
4550 let tag = match interner.resolve(*t) {
4551 "Int" => TypeAbstraction::Concrete(TypeTag::Int),
4552 "Float" => TypeAbstraction::Concrete(TypeTag::Float),
4553 "Bool" => TypeAbstraction::Concrete(TypeTag::Bool),
4554 "Text" => TypeAbstraction::Concrete(TypeTag::Text),
4555 _ => TypeAbstraction::Top,
4556 };
4557 fst.types.insert(*psym, tag);
4558 }
4559 crate::ast::stmt::TypeExpr::Generic { base, .. }
4563 if matches!(interner.resolve(*base), "Seq" | "List" | "Array") =>
4564 {
4565 fst.coll_vars.insert(*psym);
4566 fst.param_colls.insert(*psym);
4567 }
4568 _ => {}
4569 }
4570 fst.aliases.taint(*psym);
4573 }
4574 if aot_entry_guard {
4586 if let Some(g) =
4587 crate::codegen::entry_guard::detect_entry_guard(params, body, interner)
4588 {
4589 fst.intervals
4590 .set_var(g.lo, Interval { lo: Bound::Finite(1), hi: Bound::PosInf });
4591 fst.intervals
4592 .set_var(g.hi, Interval { lo: Bound::Finite(1), hi: Bound::PosInf });
4593 fst.length_def.insert(g.arr, (g.hi, 0));
4594 }
4595 }
4596 rich_walk_block(body, &mut fst, &mut facts);
4597 }
4598 }
4599 strip_concurrent_loop_snapshots(stmts, &mut facts, false);
4600 facts
4601}
4602
4603fn strip_concurrent_loop_snapshots(stmts: &[Stmt], facts: &mut OracleFacts, in_concurrent: bool) {
4607 for stmt in stmts {
4608 match stmt {
4609 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
4610 if in_concurrent {
4611 facts.loop_aliases.remove(&(stmt as *const Stmt as usize));
4612 }
4613 strip_concurrent_loop_snapshots(body, facts, in_concurrent);
4614 }
4615 Stmt::If { then_block, else_block, .. } => {
4616 strip_concurrent_loop_snapshots(then_block, facts, in_concurrent);
4617 if let Some(eb) = else_block {
4618 strip_concurrent_loop_snapshots(eb, facts, in_concurrent);
4619 }
4620 }
4621 Stmt::Zone { body, .. } => {
4622 strip_concurrent_loop_snapshots(body, facts, in_concurrent);
4623 }
4624 Stmt::FunctionDef { body, .. } => {
4625 strip_concurrent_loop_snapshots(body, facts, in_concurrent);
4626 }
4627 Stmt::Inspect { arms, .. } => {
4628 for arm in arms {
4629 strip_concurrent_loop_snapshots(arm.body, facts, in_concurrent);
4630 }
4631 }
4632 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
4633 strip_concurrent_loop_snapshots(tasks, facts, true);
4634 }
4635 _ => {}
4636 }
4637 }
4638}
4639
4640pub(crate) fn rich_abstract_interp_stmts<'a>(
4641 stmts: Vec<Stmt<'a>>,
4642 _expr_arena: &'a Arena<Expr<'a>>,
4643 _stmt_arena: &'a Arena<Stmt<'a>>,
4644) -> (Vec<Stmt<'a>>, RichAbstractState) {
4645 let mut st = RichAbstractState::new();
4646 let mut facts = OracleFacts::default();
4647 rich_walk_block(&stmts, &mut st, &mut facts);
4648 (stmts, st)
4649}
4650
4651fn rich_walk_block(block: &[Stmt], st: &mut RichAbstractState, facts: &mut OracleFacts) {
4652 for stmt in block {
4653 record_stmt_exprs(stmt, st, facts);
4654 rich_walk_stmt(stmt, st, facts);
4655 }
4656}
4657
4658fn record_stmt_exprs(stmt: &Stmt, st: &RichAbstractState, facts: &mut OracleFacts) {
4661 use crate::ast::stmt::ReadSource;
4662 match stmt {
4663 Stmt::Let { value, .. } | Stmt::Set { value, .. } => record_expr(value, st, facts),
4664 Stmt::Return { value: Some(e) } => record_expr(e, st, facts),
4665 Stmt::Call { args, .. } => {
4666 for a in args {
4667 record_expr(a, st, facts);
4668 }
4669 }
4670 Stmt::If { cond, .. } | Stmt::While { cond, .. } => record_expr(cond, st, facts),
4671 Stmt::Repeat { iterable, .. } => record_expr(iterable, st, facts),
4672 Stmt::Show { object, .. } | Stmt::Give { object, .. } => record_expr(object, st, facts),
4673 Stmt::Push { value, collection }
4674 | Stmt::Add { value, collection }
4675 | Stmt::Remove { value, collection } => {
4676 record_expr(value, st, facts);
4677 record_expr(collection, st, facts);
4678 }
4679 Stmt::SetIndex { collection, index, value } => {
4680 record_expr(collection, st, facts);
4681 record_expr(index, st, facts);
4682 record_expr(value, st, facts);
4683 }
4684 Stmt::SetField { object, value, .. } => {
4685 record_expr(object, st, facts);
4686 record_expr(value, st, facts);
4687 }
4688 Stmt::Inspect { target, .. } => record_expr(target, st, facts),
4689 Stmt::RuntimeAssert { condition, .. } => record_expr(condition, st, facts),
4690 Stmt::Sleep { milliseconds } => record_expr(milliseconds, st, facts),
4691 Stmt::ReadFrom { source: ReadSource::File(p), .. } => record_expr(p, st, facts),
4692 _ => {}
4693 }
4694}
4695
4696fn rich_walk_stmt(stmt: &Stmt, st: &mut RichAbstractState, facts: &mut OracleFacts) {
4697 match stmt {
4698 Stmt::Let { var, value, .. } => rich_bind(*var, value, st),
4699 Stmt::Set { target, value } => rich_bind(*target, value, st),
4700 Stmt::Push { collection, value } | Stmt::Add { collection, value } => {
4701 rich_grow(collection, st);
4702 observe_written_elem(collection, value, st);
4703 }
4704 Stmt::Pop { collection, into } => {
4705 rich_shrink(collection, st);
4706 if let Some(v) = into {
4707 st.invalidate_var(*v);
4708 st.aliases.unlink(*v);
4709 st.aliases.taint(*v);
4712 }
4713 }
4714 Stmt::Remove { collection, .. } => rich_shrink(collection, st),
4715 Stmt::SetIndex { collection, value, .. } => observe_written_elem(collection, value, st),
4719 Stmt::SetField { object, .. } => {
4720 if let Expr::Identifier(s) = *object {
4721 for a in st.aliases.may_alias(*s) {
4722 st.shapes.insert(a, CollectionShape::Top);
4723 }
4724 }
4725 }
4726 Stmt::If { cond, then_block, else_block } => {
4727 rich_walk_if(cond, then_block, *else_block, st, facts);
4728 }
4729 Stmt::While { cond, body, .. } => {
4730 let key = stmt as *const Stmt as usize;
4731 rich_walk_loop(Some(cond), body, st, None, facts, Some(key));
4732 }
4733 Stmt::Repeat { pattern, body, .. } => {
4734 let key = stmt as *const Stmt as usize;
4735 rich_walk_loop(None, body, st, pattern_loop_var(pattern), facts, Some(key));
4736 }
4737 Stmt::Inspect { target, arms, .. } => rich_walk_inspect(target, arms, st, facts),
4738 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
4739 rich_walk_block(tasks, st, facts)
4740 }
4741 Stmt::Zone { body, .. } => {
4742 let prev = facts.suppress_exprs;
4749 facts.suppress_exprs = true;
4750 rich_walk_block(body, st, facts);
4751 facts.suppress_exprs = prev;
4752 }
4753 Stmt::Call { args, .. } => {
4754 for arg in args {
4756 if let Expr::Identifier(s) = *arg {
4757 for a in st.aliases.may_alias(*s) {
4758 st.shapes.insert(a, CollectionShape::Top);
4759 st.intervals.set_length(a, Interval::non_negative());
4760 }
4761 }
4762 }
4763 }
4764 Stmt::Give { object, .. } => {
4765 if let Expr::Identifier(s) = *object {
4766 st.invalidate_var(*s);
4767 st.aliases.unlink(*s);
4768 }
4769 }
4770 Stmt::ReadFrom { var, .. } => {
4771 st.invalidate_var(*var);
4772 st.aliases.unlink(*var);
4773 }
4774 _ => {}
4775 }
4776}
4777
4778fn value_upper(value: &Expr, st: &RichAbstractState) -> Option<super::affine::LinExpr> {
4792 use super::affine::{konst, var};
4793 match value {
4794 Expr::Literal(Literal::Number(k)) => Some(konst(*k)),
4795 Expr::Identifier(s) => st.scalar_upper.get(s).cloned(),
4796 Expr::Index { collection, .. } => match collection {
4797 Expr::Identifier(arr) => st.elem_upper.get(arr).cloned(),
4798 _ => None,
4799 },
4800 Expr::BinaryOp { op: BinaryOpKind::Modulo, right, .. } => match right {
4801 Expr::Identifier(n) => Some(var(*n).sub(&konst(1))),
4802 _ => None,
4803 },
4804 _ => None,
4805 }
4806}
4807
4808fn value_lower(value: &Expr, st: &RichAbstractState) -> Option<super::affine::LinExpr> {
4814 use super::affine::konst;
4815 match value {
4816 Expr::Literal(Literal::Number(k)) => Some(konst(*k)),
4817 Expr::Identifier(s) => st.scalar_lower.get(s).cloned(),
4818 Expr::Index { collection, .. } => match collection {
4819 Expr::Identifier(arr) => match st.intervals.get_elem(arr).lo {
4820 Bound::Finite(lo) => Some(konst(lo)),
4821 _ => None,
4822 },
4823 _ => None,
4824 },
4825 _ => None,
4826 }
4827}
4828
4829fn join_sym_upper(
4835 a: &super::affine::LinExpr,
4836 b: &super::affine::LinExpr,
4837 st: &RichAbstractState,
4838) -> Option<super::affine::LinExpr> {
4839 if a == b {
4840 return Some(a.clone());
4841 }
4842 if lin_ge_zero(&b.sub(a), st) {
4843 return Some(b.clone()); }
4845 if lin_ge_zero(&a.sub(b), st) {
4846 return Some(a.clone());
4847 }
4848 if is_nonpos_const(a) && mod_upper_divisor(b).is_some() {
4856 return Some(b.clone());
4857 }
4858 if is_nonpos_const(b) && mod_upper_divisor(a).is_some() {
4859 return Some(a.clone());
4860 }
4861 None
4862}
4863
4864fn mod_upper_divisor(e: &super::affine::LinExpr) -> Option<Symbol> {
4869 if e.coefficients.len() != 1 || e.constant.to_i64() != Some(-1) {
4870 return None;
4871 }
4872 let (&idx, coeff) = e.coefficients.iter().next()?;
4873 (coeff.to_i64() == Some(1)).then(|| Symbol::from_index(idx as usize))
4874}
4875
4876fn is_nonpos_const(e: &super::affine::LinExpr) -> bool {
4878 e.coefficients.is_empty() && !e.constant.is_positive()
4879}
4880
4881fn lin_ge_zero(e: &super::affine::LinExpr, st: &RichAbstractState) -> bool {
4886 let Some(k) = e.constant.to_i64() else { return false };
4887 let mut acc = Interval::exact(k);
4888 for (idx, coeff) in &e.coefficients {
4889 let Some(c) = coeff.to_i64() else { return false };
4890 let sym = Symbol::from_index(*idx as usize);
4891 acc = acc.add(&Interval::exact(c).mul(&st.intervals.get_var(&sym)));
4892 }
4893 matches!(acc.lo, Bound::Finite(v) if v >= 0)
4894}
4895
4896fn observe_written_elem(collection: &Expr, value: &Expr, st: &mut RichAbstractState) {
4897 if let Expr::Identifier(sym) = collection {
4898 let v = eval_expr(value, &st.intervals);
4899 let sym_up = value_upper(value, st);
4902 let sym_ty = eval_type(value, &st.types, &st.fn_returns, &st.elem_type);
4905 for a in st.aliases.may_alias(*sym) {
4906 let was_fresh = st.intervals.get_elem(&a).is_bottom();
4909 st.intervals.observe_elem(a, v.clone());
4910 if was_fresh {
4915 st.elem_type.insert(a, sym_ty.clone());
4916 } else {
4917 let joined = match st.elem_type.get(&a) {
4918 Some(existing) => existing.join(&sym_ty),
4919 None => TypeAbstraction::Top,
4920 };
4921 st.elem_type.insert(a, joined);
4922 }
4923 match &sym_up {
4924 None => {
4927 st.elem_upper.remove(&a);
4928 }
4929 Some(nb) => {
4930 if was_fresh {
4931 st.elem_upper.insert(a, nb.clone());
4932 } else if let Some(existing) = st.elem_upper.get(&a).cloned() {
4933 match join_sym_upper(&existing, nb, st) {
4934 Some(m) => {
4935 st.elem_upper.insert(a, m);
4936 }
4937 None => {
4938 st.elem_upper.remove(&a);
4939 }
4940 }
4941 }
4942 }
4945 }
4946 }
4947 }
4948}
4949
4950fn rich_bind(var: Symbol, value: &Expr, st: &mut RichAbstractState) {
4951 st.aliases.unlink(var);
4952 st.length_def.remove(&var);
4956 st.length_def.retain(|_, (n, _)| *n != var);
4957 st.scalar_def.remove(&var);
4960 let vi = var.index() as i64;
4961 st.scalar_def.retain(|_, e| !e.coefficients.contains_key(&vi));
4962 st.scalar_upper.remove(&var);
4966 st.scalar_upper.retain(|_, e| !e.coefficients.contains_key(&vi));
4967 st.elem_upper.remove(&var);
4968 st.elem_upper.retain(|_, e| !e.coefficients.contains_key(&vi));
4969 st.elem_type.remove(&var);
4973 st.scalar_lower.remove(&var);
4974 st.scalar_lower.retain(|_, e| !e.coefficients.contains_key(&vi));
4975 st.param_colls.remove(&var);
4978
4979 let iv = eval_expr(value, &st.intervals);
4980 st.intervals.set_var(var, iv);
4981 let value_ty = eval_type(value, &st.types, &st.fn_returns, &st.elem_type);
4982 st.types.insert(var, value_ty);
4983 st.nullability.insert(var, nullability_of_expr(value, st));
4984 st.intervals.clear_elem(&var);
4987 if let Some(e) = super::affine::lin_of(value) {
4994 if !e.coefficients.is_empty() && !e.coefficients.contains_key(&(var.index() as i64)) {
4995 st.scalar_def.insert(var, e);
4996 }
4997 }
4998 if let Some(u) = value_upper(value, st) {
5003 if !u.coefficients.contains_key(&(var.index() as i64)) {
5004 st.scalar_upper.insert(var, u);
5005 }
5006 }
5007 if let Some(l) = value_lower(value, st) {
5008 if !l.coefficients.contains_key(&(var.index() as i64)) {
5009 st.scalar_lower.insert(var, l);
5010 }
5011 }
5012 if let Expr::Length { collection } = value {
5020 if let Expr::Identifier(a) = &**collection {
5021 if *a != var {
5022 st.length_def.insert(*a, (var, 0));
5023 }
5024 }
5025 }
5026
5027 match value {
5028 Expr::New { .. } => {
5029 st.shapes.insert(var, CollectionShape::Empty);
5030 st.intervals.set_length(var, Interval::exact(0));
5031 st.coll_vars.insert(var);
5032 st.intervals.observe_elem(var, Interval::bottom());
5036 }
5037 Expr::List(items) | Expr::Tuple(items) => {
5038 let n = items.len() as u64;
5039 st.shapes.insert(var, CollectionShape::from_bounds(n, Some(n)));
5040 st.intervals.set_length(var, Interval::exact(items.len() as i64));
5041 st.coll_vars.insert(var);
5042 let mut el = Interval::bottom();
5044 for it in items.iter() {
5045 el = el.join(&eval_expr(it, &st.intervals));
5046 }
5047 st.intervals.observe_elem(var, el);
5048 let mut item_iter = items.iter();
5051 if let Some(first) = item_iter.next() {
5052 let mut ty = eval_type(first, &st.types, &st.fn_returns, &st.elem_type);
5053 for it in item_iter {
5054 ty = ty.join(&eval_type(it, &st.types, &st.fn_returns, &st.elem_type));
5055 }
5056 st.elem_type.insert(var, ty);
5057 }
5058 }
5059 Expr::Identifier(src) => {
5060 st.aliases.link(var, *src);
5062 let shape = st.shapes.get(src).cloned().unwrap_or(CollectionShape::Top);
5063 st.shapes.insert(var, shape);
5064 let len = st.intervals.get_length(src);
5065 st.intervals.set_length(var, len);
5066 if st.intervals.elem.contains_key(src) {
5068 let e = st.intervals.get_elem(src);
5069 st.intervals.observe_elem(var, e);
5070 }
5071 if let Some(t) = st.elem_type.get(src).cloned() {
5073 st.elem_type.insert(var, t);
5074 }
5075 if st.coll_vars.contains(src) {
5076 st.coll_vars.insert(var);
5077 if st.param_colls.contains(src) {
5079 st.param_colls.insert(var);
5080 }
5081 } else {
5082 st.coll_vars.remove(&var);
5083 }
5084 if st.aot_entry_guard {
5092 if let Some(&(n, off)) = st.length_def.get(src) {
5093 if n != var {
5094 st.length_def.insert(var, (n, off));
5095 }
5096 }
5097 }
5098 }
5099 Expr::Copy { expr } | Expr::WithCapacity { value: expr, .. } => {
5100 st.shapes.insert(var, CollectionShape::Top);
5102 let from_coll = matches!(
5103 expr,
5104 Expr::Identifier(s) if st.coll_vars.contains(s)
5105 ) || matches!(expr, Expr::New { .. } | Expr::List(_) | Expr::Slice { .. });
5106 if from_coll {
5107 st.coll_vars.insert(var);
5108 } else {
5109 st.coll_vars.remove(&var);
5110 }
5111 }
5112 Expr::Slice { .. } => {
5113 st.shapes.insert(var, CollectionShape::Top);
5115 st.coll_vars.insert(var);
5116 }
5117 _ => {
5118 st.shapes.insert(var, CollectionShape::Top);
5120 st.coll_vars.remove(&var);
5121 match value {
5126 Expr::Literal(_)
5127 | Expr::BinaryOp { .. }
5128 | Expr::Not { .. }
5129 | Expr::Length { .. }
5130 | Expr::Contains { .. }
5131 | Expr::Range { .. }
5132 | Expr::Union { .. }
5133 | Expr::Intersection { .. }
5134 | Expr::InterpolatedString(_)
5135 | Expr::OptionNone => {}
5136 _ => st.aliases.taint(var),
5137 }
5138 }
5139 }
5140}
5141
5142fn rich_grow(collection: &Expr, st: &mut RichAbstractState) {
5145 if let Expr::Identifier(sym) = collection {
5146 let new_shape = st.shapes.get(sym).cloned().unwrap_or(CollectionShape::Top).pushed();
5147 let new_len = st.intervals.get_length(sym).add(&Interval::exact(1));
5148 for a in st.aliases.may_alias(*sym) {
5149 st.shapes.insert(a, new_shape.clone());
5150 st.intervals.set_length(a, new_len.clone());
5151 st.length_def.remove(&a);
5154 }
5155 }
5156}
5157
5158fn rich_shrink(collection: &Expr, st: &mut RichAbstractState) {
5160 if let Expr::Identifier(sym) = collection {
5161 let new_shape = st.shapes.get(sym).cloned().unwrap_or(CollectionShape::Top).popped();
5162 let new_len = st.intervals.get_length(sym).sub(&Interval::exact(1));
5163 for a in st.aliases.may_alias(*sym) {
5164 st.shapes.insert(a, new_shape.clone());
5165 st.intervals.set_length(a, new_len.clone());
5166 st.length_def.remove(&a);
5168 }
5169 }
5170}
5171
5172fn nullability_of_expr(expr: &Expr, st: &RichAbstractState) -> Nullability {
5173 match expr {
5174 Expr::Literal(lit) => Nullability::for_literal(lit),
5175 Expr::Identifier(s) => st.nullability.get(s).cloned().unwrap_or(Nullability::Maybe),
5176 Expr::New { .. }
5177 | Expr::NewVariant { .. }
5178 | Expr::List(_)
5179 | Expr::Tuple(_)
5180 | Expr::Range { .. }
5181 | Expr::BinaryOp { .. }
5182 | Expr::Not { .. }
5183 | Expr::Length { .. }
5184 | Expr::Contains { .. } => Nullability::Definite,
5185 _ => Nullability::Maybe,
5187 }
5188}
5189
5190fn pattern_loop_var(p: &Pattern) -> Option<Symbol> {
5191 match p {
5192 Pattern::Identifier(s) => Some(*s),
5193 _ => None,
5194 }
5195}
5196
5197fn rich_walk_if(
5198 cond: &Expr,
5199 then_block: &[Stmt],
5200 else_block: Option<&[Stmt]>,
5201 st: &mut RichAbstractState,
5202 facts: &mut OracleFacts,
5203) {
5204 match eval_condition(cond, &st.intervals) {
5205 Some(true) => {
5206 narrow_state(cond, &mut st.intervals);
5207 rich_walk_block(then_block, st, facts);
5208 }
5209 Some(false) => {
5210 if let Some(eb) = else_block {
5211 narrow_state_negated(cond, &mut st.intervals);
5212 rich_walk_block(eb, st, facts);
5213 }
5214 }
5215 None => {
5216 let mut then_st = st.clone();
5217 narrow_state(cond, &mut then_st.intervals);
5218 rich_walk_block(then_block, &mut then_st, facts);
5219
5220 let mut else_st = st.clone();
5221 narrow_state_negated(cond, &mut else_st.intervals);
5222 if let Some(eb) = else_block {
5223 rich_walk_block(eb, &mut else_st, facts);
5224 }
5225
5226 *st = rich_join(&then_st, &else_st);
5227 }
5228 }
5229}
5230
5231fn refine_invariant_bound_from_guard(
5237 cond: &Expr,
5238 pre: &RichAbstractState,
5239 mutated: &[Symbol],
5240 out: &mut AbstractState,
5241) {
5242 if let Expr::BinaryOp { op: BinaryOpKind::And, left, right } = cond {
5243 refine_invariant_bound_from_guard(left, pre, mutated, out);
5244 refine_invariant_bound_from_guard(right, pre, mutated, out);
5245 return;
5246 }
5247 let Expr::BinaryOp { op, left, right } = cond else { return };
5248 let (iv_e, bnd_e, strict) = match op {
5249 BinaryOpKind::Lt => (left, right, true),
5250 BinaryOpKind::LtEq => (left, right, false),
5251 BinaryOpKind::Gt => (right, left, true),
5252 BinaryOpKind::GtEq => (right, left, false),
5253 _ => return,
5254 };
5255 let (Expr::Identifier(iv), Expr::Identifier(bnd)) = (iv_e, bnd_e) else { return };
5256 if mutated.contains(bnd) {
5257 return; }
5259 let Bound::Finite(i0) = pre.intervals.get_var(iv).lo else { return };
5260 let Some(new_lo) = (if strict { i0.checked_add(1) } else { Some(i0) }) else { return };
5261 let cur = out.get_var(bnd);
5262 out.set_var(
5263 *bnd,
5264 Interval { lo: Bound::max_bound(&cur.lo, &Bound::Finite(new_lo)), hi: cur.hi },
5265 );
5266}
5267
5268fn rich_walk_loop(
5274 cond: Option<&Expr>,
5275 body: &[Stmt],
5276 st: &mut RichAbstractState,
5277 loop_var: Option<Symbol>,
5278 facts: &mut OracleFacts,
5279 loop_key: Option<usize>,
5280) {
5281 let mut mutated = collect_mutations(body);
5282 if let Some(lv) = loop_var {
5283 if !mutated.contains(&lv) {
5284 mutated.push(lv);
5285 }
5286 }
5287 let mut inside = st.clone();
5290 if let Some(lv) = loop_var {
5291 inside.invalidate_var(lv);
5292 inside.aliases.unlink(lv);
5293 inside.aliases.taint(lv);
5294 }
5295 if let Some(c) = cond {
5296 narrow_state(c, &mut inside.intervals);
5297 refine_invariant_bound_from_guard(c, st, &mutated, &mut inside.intervals);
5303 }
5304
5305 let mut sink = OracleFacts::default();
5308 let mut converged = false;
5309 for pass in 0..12 {
5310 let mut next = inside.clone();
5311 rich_walk_block(body, &mut next, &mut sink);
5312 if let Some(c) = cond {
5313 narrow_state(c, &mut next.intervals);
5314 }
5315 let mut stable = true;
5318 for &m in &mutated {
5319 let inside_fresh_elem = inside.intervals.get_elem(&m).is_bottom();
5322 let cur_iv = inside.intervals.get_var(&m);
5323 let nxt_iv = next.intervals.get_var(&m);
5324 let wid = cur_iv.widen(&nxt_iv);
5325 if !wid.leq(&cur_iv) || !cur_iv.leq(&wid) {
5326 stable = false;
5327 }
5328 inside.intervals.set_var(m, wid);
5329 let cur_len = inside.intervals.get_length(&m);
5330 let nxt_len = next.intervals.get_length(&m);
5331 let wid_len = cur_len.widen(&nxt_len);
5332 inside.intervals.set_length(m, wid_len);
5333
5334 if inside.intervals.elem.contains_key(&m) || next.intervals.elem.contains_key(&m) {
5343 let cur_el = inside.intervals.get_elem(&m);
5344 let nxt_el = next.intervals.get_elem(&m);
5345 let merged = if pass < 3 {
5346 cur_el.join(&nxt_el)
5347 } else {
5348 cur_el.widen(&nxt_el)
5349 };
5350 if !(merged.leq(&cur_el) && cur_el.leq(&merged)) {
5351 stable = false;
5352 }
5353 inside.intervals.elem.insert(m, merged);
5354 }
5355
5356 let merged_sym = if inside_fresh_elem {
5361 next.elem_upper.get(&m).cloned()
5362 } else {
5363 match (inside.elem_upper.get(&m).cloned(), next.elem_upper.get(&m).cloned()) {
5364 (Some(e), Some(n)) => join_sym_upper(&e, &n, &next),
5365 _ => None,
5366 }
5367 };
5368 if inside.elem_upper.get(&m) != merged_sym.as_ref() {
5369 stable = false;
5370 }
5371 match merged_sym {
5372 Some(l) => {
5373 inside.elem_upper.insert(m, l);
5374 }
5375 None => {
5376 inside.elem_upper.remove(&m);
5377 }
5378 }
5379 let merged_et = if inside_fresh_elem {
5386 next.elem_type.get(&m).cloned()
5387 } else {
5388 match (inside.elem_type.get(&m).cloned(), next.elem_type.get(&m).cloned()) {
5389 (Some(e), Some(n)) => Some(e.join(&n)),
5390 _ => None,
5391 }
5392 };
5393 if inside.elem_type.get(&m) != merged_et.as_ref() {
5394 stable = false;
5395 }
5396 match merged_et {
5397 Some(t) => {
5398 inside.elem_type.insert(m, t);
5399 }
5400 None => {
5401 inside.elem_type.remove(&m);
5402 }
5403 }
5404 let merged_su = match (
5410 inside.scalar_upper.get(&m).cloned(),
5411 next.scalar_upper.get(&m).cloned(),
5412 ) {
5413 (Some(a), Some(b)) if a == b => Some(a),
5414 (None, Some(b)) => Some(b),
5415 _ => None,
5416 };
5417 if inside.scalar_upper.get(&m) != merged_su.as_ref() {
5418 stable = false;
5419 }
5420 match merged_su {
5421 Some(l) => {
5422 inside.scalar_upper.insert(m, l);
5423 }
5424 None => {
5425 inside.scalar_upper.remove(&m);
5426 }
5427 }
5428 let merged_sl = match (
5429 inside.scalar_lower.get(&m).cloned(),
5430 next.scalar_lower.get(&m).cloned(),
5431 ) {
5432 (Some(a), Some(b)) if a == b => Some(a),
5433 (None, Some(b)) => Some(b),
5434 _ => None,
5435 };
5436 if inside.scalar_lower.get(&m) != merged_sl.as_ref() {
5437 stable = false;
5438 }
5439 match merged_sl {
5440 Some(l) => {
5441 inside.scalar_lower.insert(m, l);
5442 }
5443 None => {
5444 inside.scalar_lower.remove(&m);
5445 }
5446 }
5447
5448 let cur_ty = inside.types.get(&m).cloned().unwrap_or(TypeAbstraction::Top);
5449 let nxt_ty = next.types.get(&m).cloned().unwrap_or(TypeAbstraction::Top);
5450 let j = cur_ty.join(&nxt_ty);
5451 if !(j.leq(&cur_ty) && cur_ty.leq(&j)) {
5452 stable = false;
5453 }
5454 inside.types.insert(m, j);
5455
5456 let cur_sh = inside.shapes.get(&m).cloned().unwrap_or(CollectionShape::Top);
5457 let nxt_sh = next.shapes.get(&m).cloned().unwrap_or(CollectionShape::Top);
5458 inside.shapes.insert(m, cur_sh.widen(&nxt_sh));
5459
5460 if !next.coll_vars.contains(&m) {
5463 inside.coll_vars.remove(&m);
5464 }
5465
5466 let cur_n = inside.nullability.get(&m).cloned().unwrap_or(Nullability::Maybe);
5467 let nxt_n = next.nullability.get(&m).cloned().unwrap_or(Nullability::Maybe);
5468 inside.nullability.insert(m, cur_n.join(&nxt_n));
5469 }
5470 if inside.aliases.union_from(&next.aliases) {
5475 stable = false;
5476 }
5477 if stable {
5478 converged = true;
5479 break;
5480 }
5481 }
5482
5483 if converged {
5484 if let Some(key) = loop_key {
5488 match facts.loop_aliases.entry(key) {
5489 std::collections::hash_map::Entry::Occupied(mut e) => {
5490 e.get_mut().union_from(&inside.aliases);
5491 }
5492 std::collections::hash_map::Entry::Vacant(v) => {
5493 v.insert(inside.aliases.clone());
5494 }
5495 }
5496 }
5497 let mut record_state = inside.clone();
5502 if let Some(c) = cond {
5503 narrow_state(c, &mut record_state.intervals);
5504 }
5505 if let Some(c) = cond {
5511 record_loop_index_bounds(c, body, &record_state, facts);
5512 record_affine_index_bounds(c, body, &record_state, st, facts);
5518 if let Some(key) = loop_key {
5519 record_hoist_speculation(c, body, &record_state, facts, key);
5520 }
5521 }
5522 rich_walk_block(body, &mut record_state, facts);
5523 let mut after = rich_join(st, &inside);
5524 if let Some(c) = cond {
5525 narrow_state_negated(c, &mut after.intervals);
5526 for bl in infer_build_length(c, body, st) {
5533 match bl {
5534 BuildLength::Symbolic(arr, n, off) => {
5535 after.length_def.insert(arr, (n, off));
5536 }
5537 BuildLength::Concrete(arr, len) => {
5538 after.intervals.set_length(arr, Interval::exact(len));
5539 }
5540 }
5541 }
5542 }
5543 *st = after;
5544 } else {
5545 if let Some(c) = cond {
5547 narrow_state_negated(c, &mut st.intervals);
5548 }
5549 for m in mutated {
5550 st.intervals.set_var(m, Interval::top());
5551 st.intervals.set_length(m, Interval::non_negative());
5552 st.types.insert(m, TypeAbstraction::Top);
5553 st.shapes.insert(m, CollectionShape::Top);
5554 st.nullability.insert(m, Nullability::Maybe);
5555 st.coll_vars.remove(&m);
5556 }
5557 }
5558}
5559
5560fn rich_walk_inspect(
5561 target: &Expr,
5562 arms: &[MatchArm],
5563 st: &mut RichAbstractState,
5564 facts: &mut OracleFacts,
5565) {
5566 if arms.is_empty() {
5567 return;
5568 }
5569 let mut acc: Option<RichAbstractState> = None;
5570 for arm in arms {
5571 let mut arm_st = st.clone();
5572 if arm.variant.is_some() {
5575 if let Expr::Identifier(sym) = target {
5576 arm_st.nullability.insert(*sym, Nullability::Definite);
5577 }
5578 for (_field, binding) in &arm.bindings {
5579 arm_st.nullability.insert(*binding, Nullability::Definite);
5580 }
5581 }
5582 for (_field, binding) in &arm.bindings {
5585 arm_st.aliases.unlink(*binding);
5586 arm_st.aliases.taint(*binding);
5587 }
5588 rich_walk_block(arm.body, &mut arm_st, facts);
5589 acc = Some(match acc {
5590 None => arm_st,
5591 Some(prev) => rich_join(&prev, &arm_st),
5592 });
5593 }
5594 if let Some(joined) = acc {
5595 *st = joined;
5596 }
5597}
5598
5599fn rich_join(a: &RichAbstractState, b: &RichAbstractState) -> RichAbstractState {
5600 let mut intervals = AbstractState::new();
5601 join_states(&mut intervals, &a.intervals, &b.intervals);
5602 RichAbstractState {
5603 intervals,
5604 types: join_maps(&a.types, &b.types),
5605 shapes: join_maps(&a.shapes, &b.shapes),
5606 nullability: join_maps(&a.nullability, &b.nullability),
5607 aliases: alias_union(&a.aliases, &b.aliases),
5608 coll_vars: a.coll_vars.intersection(&b.coll_vars).cloned().collect(),
5610 fn_returns: a.fn_returns.clone(),
5611 length_def: a
5613 .length_def
5614 .iter()
5615 .filter(|(k, v)| b.length_def.get(k) == Some(v))
5616 .map(|(k, v)| (*k, *v))
5617 .collect(),
5618 param_colls: a.param_colls.union(&b.param_colls).copied().collect(),
5620 scalar_def: a
5623 .scalar_def
5624 .iter()
5625 .filter(|(k, v)| b.scalar_def.get(*k) == Some(*v))
5626 .map(|(k, v)| (*k, v.clone()))
5627 .collect(),
5628 scalar_upper: a
5632 .scalar_upper
5633 .iter()
5634 .filter(|(k, v)| b.scalar_upper.get(*k) == Some(*v))
5635 .map(|(k, v)| (*k, v.clone()))
5636 .collect(),
5637 elem_upper: join_elem_upper_maps(a, b),
5638 elem_type: join_elem_type_maps(a, b),
5639 scalar_lower: a
5640 .scalar_lower
5641 .iter()
5642 .filter(|(k, v)| b.scalar_lower.get(*k) == Some(*v))
5643 .map(|(k, v)| (*k, v.clone()))
5644 .collect(),
5645 aot_entry_guard: a.aot_entry_guard || b.aot_entry_guard,
5648 }
5649}
5650
5651fn join_elem_upper_maps(
5658 a: &RichAbstractState,
5659 b: &RichAbstractState,
5660) -> HashMap<Symbol, super::affine::LinExpr> {
5661 let mut out = HashMap::new();
5662 let keys: HashSet<Symbol> = a.elem_upper.keys().chain(b.elem_upper.keys()).cloned().collect();
5663 for key in keys {
5664 let a_bot = a.intervals.get_elem(&key).is_bottom();
5665 let b_bot = b.intervals.get_elem(&key).is_bottom();
5666 let v = if a_bot {
5667 b.elem_upper.get(&key).cloned()
5668 } else if b_bot {
5669 a.elem_upper.get(&key).cloned()
5670 } else {
5671 match (a.elem_upper.get(&key), b.elem_upper.get(&key)) {
5672 (Some(x), Some(y)) => join_sym_upper(x, y, b),
5673 _ => None,
5674 }
5675 };
5676 if let Some(l) = v {
5677 out.insert(key, l);
5678 }
5679 }
5680 out
5681}
5682
5683fn join_elem_type_maps(
5689 a: &RichAbstractState,
5690 b: &RichAbstractState,
5691) -> HashMap<Symbol, TypeAbstraction> {
5692 let mut out = HashMap::new();
5693 let keys: HashSet<Symbol> = a.elem_type.keys().chain(b.elem_type.keys()).cloned().collect();
5694 for key in keys {
5695 let a_bot = a.intervals.get_elem(&key).is_bottom();
5696 let b_bot = b.intervals.get_elem(&key).is_bottom();
5697 let v = if a_bot {
5698 b.elem_type.get(&key).cloned()
5699 } else if b_bot {
5700 a.elem_type.get(&key).cloned()
5701 } else {
5702 match (a.elem_type.get(&key), b.elem_type.get(&key)) {
5703 (Some(x), Some(y)) => Some(x.join(y)),
5704 _ => None,
5705 }
5706 };
5707 if let Some(t) = v {
5708 out.insert(key, t);
5709 }
5710 }
5711 out
5712}
5713
5714fn join_maps<V: AbstractDomain>(
5715 a: &HashMap<Symbol, V>,
5716 b: &HashMap<Symbol, V>,
5717) -> HashMap<Symbol, V> {
5718 let mut out = HashMap::new();
5719 let keys: HashSet<Symbol> = a.keys().chain(b.keys()).cloned().collect();
5720 for k in keys {
5721 let top = V::top();
5722 let va = a.get(&k).unwrap_or(&top);
5723 let vb = b.get(&k).unwrap_or(&top);
5724 out.insert(k, va.join(vb));
5725 }
5726 out
5727}
5728
5729fn alias_union(a: &AliasGraph, b: &AliasGraph) -> AliasGraph {
5730 let mut out = a.clone();
5731 out.union_from(b);
5732 out
5733}
5734
5735fn collect_mutations(block: &[Stmt]) -> Vec<Symbol> {
5736 let mut out = Vec::new();
5737 for s in block {
5738 collect_mut_stmt(s, &mut out);
5739 }
5740 out
5741}
5742
5743fn add_unique(sym: Symbol, out: &mut Vec<Symbol>) {
5744 if !out.contains(&sym) {
5745 out.push(sym);
5746 }
5747}
5748
5749fn collect_mut_stmt(s: &Stmt, out: &mut Vec<Symbol>) {
5750 match s {
5751 Stmt::Set { target, .. } => add_unique(*target, out),
5752 Stmt::Let { var, .. } => add_unique(*var, out),
5753 Stmt::Push { collection, .. }
5754 | Stmt::Add { collection, .. }
5755 | Stmt::Remove { collection, .. }
5756 | Stmt::SetIndex { collection, .. } => {
5757 if let Expr::Identifier(sym) = *collection {
5758 add_unique(*sym, out);
5759 }
5760 }
5761 Stmt::Pop { collection, into } => {
5762 if let Expr::Identifier(sym) = *collection {
5763 add_unique(*sym, out);
5764 }
5765 if let Some(v) = into {
5766 add_unique(*v, out);
5767 }
5768 }
5769 Stmt::SetField { object, .. } => {
5770 if let Expr::Identifier(sym) = *object {
5771 add_unique(*sym, out);
5772 }
5773 }
5774 Stmt::ReadFrom { var, .. } => add_unique(*var, out),
5775 Stmt::If { then_block, else_block, .. } => {
5776 for st in *then_block {
5777 collect_mut_stmt(st, out);
5778 }
5779 if let Some(eb) = else_block {
5780 for st in *eb {
5781 collect_mut_stmt(st, out);
5782 }
5783 }
5784 }
5785 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
5786 for st in *body {
5787 collect_mut_stmt(st, out);
5788 }
5789 }
5790 Stmt::Inspect { arms, .. } => {
5791 for arm in arms {
5792 for st in arm.body {
5793 collect_mut_stmt(st, out);
5794 }
5795 }
5796 }
5797 _ => {}
5798 }
5799}
5800
5801#[cfg(test)]
5802mod domain_tests {
5803 use super::*;
5804
5805 fn iv(lo: i64, hi: i64) -> Interval {
5806 Interval { lo: Bound::Finite(lo), hi: Bound::Finite(hi) }
5807 }
5808
5809 #[test]
5810 fn interval_top_and_bottom() {
5811 let t = <Interval as AbstractDomain>::top();
5812 assert!(matches!(t.lo, Bound::NegInf));
5813 assert!(matches!(t.hi, Bound::PosInf));
5814 assert!(!t.is_bottom());
5815
5816 let b = <Interval as AbstractDomain>::bottom();
5817 assert!(b.is_bottom());
5818 }
5819
5820 #[test]
5821 fn interval_join_is_least_upper_bound() {
5822 let j = iv(1, 3).join(&iv(5, 7));
5824 assert_eq!(j.lo, Bound::Finite(1));
5825 assert_eq!(j.hi, Bound::Finite(7));
5826 let jb = Interval::bottom().join(&iv(2, 4));
5828 assert_eq!(jb.lo, Bound::Finite(2));
5829 assert_eq!(jb.hi, Bound::Finite(4));
5830 let jb2 = iv(2, 4).join(&Interval::bottom());
5831 assert_eq!(jb2.lo, Bound::Finite(2));
5832 assert_eq!(jb2.hi, Bound::Finite(4));
5833 }
5834
5835 #[test]
5836 fn interval_meet_is_greatest_lower_bound() {
5837 let m = iv(1, 5).meet(&iv(3, 7));
5839 assert_eq!(m.lo, Bound::Finite(3));
5840 assert_eq!(m.hi, Bound::Finite(5));
5841 assert!(iv(1, 2).meet(&iv(5, 6)).is_bottom());
5843 let t = Interval::top().meet(&iv(2, 4));
5845 assert_eq!(t.lo, Bound::Finite(2));
5846 assert_eq!(t.hi, Bound::Finite(4));
5847 }
5848
5849 #[test]
5850 fn interval_widen_climbs_the_threshold_ladder_then_to_infinity() {
5851 let w = iv(0, 5).widen(&iv(0, 10));
5857 assert_eq!(w.lo, Bound::Finite(0));
5858 assert_eq!(w.hi, Bound::Finite(10));
5859 let w_mid = iv(0, 5).widen(&iv(0, 11));
5860 assert_eq!(w_mid.hi, Bound::Finite(100));
5861 let w_inf = iv(0, 5).widen(&iv(0, 1001));
5863 assert!(matches!(w_inf.hi, Bound::PosInf));
5864 let w2 = iv(0, 5).widen(&iv(-3, 5));
5866 assert_eq!(w2.lo, Bound::Finite(-10));
5867 assert_eq!(w2.hi, Bound::Finite(5));
5868 let w2_inf = iv(0, 5).widen(&iv(-1001, 5));
5870 assert!(matches!(w2_inf.lo, Bound::NegInf));
5871 let w3 = iv(0, 5).widen(&iv(0, 5));
5873 assert_eq!(w3.lo, Bound::Finite(0));
5874 assert_eq!(w3.hi, Bound::Finite(5));
5875 let w4 = Interval::bottom().widen(&iv(2, 4));
5877 assert_eq!(w4.lo, Bound::Finite(2));
5878 assert_eq!(w4.hi, Bound::Finite(4));
5879 let mut cur = iv(0, 1);
5882 let mut steps = 0;
5883 loop {
5884 let grown = Interval {
5885 lo: cur.lo.clone(),
5886 hi: match cur.hi {
5887 Bound::Finite(v) => Bound::Finite(v.saturating_add(v.abs() + 1)),
5888 ref b => b.clone(),
5889 },
5890 };
5891 let next = cur.widen(&grown);
5892 steps += 1;
5893 if next.leq(&cur) && cur.leq(&next) {
5894 break;
5895 }
5896 cur = next;
5897 assert!(steps <= 12, "widening must terminate within the ladder");
5898 }
5899 assert!(matches!(cur.hi, Bound::PosInf));
5900 }
5901
5902 #[test]
5903 fn interval_leq_is_the_subset_order() {
5904 assert!(iv(3, 5).leq(&iv(1, 7)));
5905 assert!(!iv(1, 7).leq(&iv(3, 5)));
5906 assert!(Interval::bottom().leq(&iv(1, 2)));
5908 assert!(iv(1, 2).leq(&<Interval as AbstractDomain>::top()));
5909 assert!(iv(1, 2).leq(&iv(1, 2)));
5911 }
5912
5913 #[test]
5914 fn interval_lattice_laws_hold() {
5915 let a = iv(1, 5);
5916 let b = iv(3, 9);
5917 assert!(a.leq(&a.join(&b)));
5919 assert!(b.leq(&a.join(&b)));
5920 assert!(a.meet(&b).leq(&a));
5922 assert!(a.meet(&b).leq(&b));
5923 assert!(a.join(&b).leq(&b.join(&a)) && b.join(&a).leq(&a.join(&b)));
5925 assert!(a.meet(&b).leq(&b.meet(&a)) && b.meet(&a).leq(&a.meet(&b)));
5926 }
5927}
5928
5929#[cfg(test)]
5930mod type_domain_tests {
5931 use super::*;
5932 use std::collections::HashMap;
5933
5934 fn empty_types() -> HashMap<Symbol, TypeAbstraction> {
5935 HashMap::new()
5936 }
5937
5938 fn union(tags: &[TypeTag]) -> TypeAbstraction {
5939 TypeAbstraction::Union(tags.iter().cloned().collect())
5940 }
5941
5942 #[test]
5943 fn literal_types_are_concrete() {
5944 assert_eq!(eval_type(&Expr::Literal(Literal::Number(5)), &empty_types(), &HashMap::new(), &HashMap::new()),
5945 TypeAbstraction::Concrete(TypeTag::Int));
5946 assert_eq!(eval_type(&Expr::Literal(Literal::Float(1.5)), &empty_types(), &HashMap::new(), &HashMap::new()),
5947 TypeAbstraction::Concrete(TypeTag::Float));
5948 assert_eq!(eval_type(&Expr::Literal(Literal::Boolean(true)), &empty_types(), &HashMap::new(), &HashMap::new()),
5949 TypeAbstraction::Concrete(TypeTag::Bool));
5950 assert_eq!(eval_type(&Expr::Literal(Literal::Nothing), &empty_types(), &HashMap::new(), &HashMap::new()),
5951 TypeAbstraction::Concrete(TypeTag::Nothing));
5952 assert_eq!(eval_type(&Expr::Literal(Literal::Char('a')), &empty_types(), &HashMap::new(), &HashMap::new()),
5953 TypeAbstraction::Concrete(TypeTag::Char));
5954 assert_eq!(eval_type(&Expr::Literal(Literal::Duration(60)), &empty_types(), &HashMap::new(), &HashMap::new()),
5955 TypeAbstraction::Concrete(TypeTag::Duration));
5956 }
5957
5958 #[test]
5959 fn arithmetic_and_comparison_result_types() {
5960 let five = Expr::Literal(Literal::Number(5));
5961 let three = Expr::Literal(Literal::Number(3));
5962 let add = Expr::BinaryOp { op: BinaryOpKind::Add, left: &five, right: &three };
5964 assert_eq!(eval_type(&add, &empty_types(), &HashMap::new(), &HashMap::new()), TypeAbstraction::Concrete(TypeTag::Int));
5965 let lt = Expr::BinaryOp { op: BinaryOpKind::Lt, left: &five, right: &three };
5967 assert_eq!(eval_type(<, &empty_types(), &HashMap::new(), &HashMap::new()), TypeAbstraction::Concrete(TypeTag::Bool));
5968 let f1 = Expr::Literal(Literal::Float(1.0));
5970 let f2 = Expr::Literal(Literal::Float(2.0));
5971 let fadd = Expr::BinaryOp { op: BinaryOpKind::Add, left: &f1, right: &f2 };
5972 assert_eq!(eval_type(&fadd, &empty_types(), &HashMap::new(), &HashMap::new()), TypeAbstraction::Concrete(TypeTag::Float));
5973 let mixed = Expr::BinaryOp { op: BinaryOpKind::Add, left: &five, right: &f1 };
5975 assert_eq!(eval_type(&mixed, &empty_types(), &HashMap::new(), &HashMap::new()), TypeAbstraction::Top);
5976 let txt = Expr::Literal(Literal::Text(Symbol::EMPTY));
5978 let concat = Expr::BinaryOp { op: BinaryOpKind::Add, left: &txt, right: &three };
5979 assert_eq!(eval_type(&concat, &empty_types(), &HashMap::new(), &HashMap::new()), TypeAbstraction::Concrete(TypeTag::Text));
5980 }
5981
5982 #[test]
5983 fn let_binds_value_type_in_env() {
5984 let x = Symbol::EMPTY;
5986 let mut env = empty_types();
5987 let value = Expr::Literal(Literal::Number(5));
5988 env.insert(x, eval_type(&value, &env, &HashMap::new(), &HashMap::new()));
5989 assert_eq!(env.get(&x), Some(&TypeAbstraction::Concrete(TypeTag::Int)));
5990 assert_eq!(eval_type(&Expr::Identifier(x), &env, &HashMap::new(), &HashMap::new()),
5992 TypeAbstraction::Concrete(TypeTag::Int));
5993 let unbound = empty_types();
5995 assert_eq!(eval_type(&Expr::Identifier(x), &unbound, &HashMap::new(), &HashMap::new()), TypeAbstraction::Top);
5996 }
5997
5998 #[test]
5999 fn type_join_merges_distinct_into_union_and_collapses() {
6000 let i = TypeAbstraction::Concrete(TypeTag::Int);
6001 let b = TypeAbstraction::Concrete(TypeTag::Bool);
6002 assert_eq!(i.join(&b), union(&[TypeTag::Int, TypeTag::Bool]));
6004 assert_eq!(i.join(&i), i);
6006 assert_eq!(union(&[TypeTag::Int]), i);
6008 assert_eq!(i.join(&TypeAbstraction::Top), TypeAbstraction::Top);
6010 assert_eq!(i.join(&TypeAbstraction::Bottom), i);
6011 assert_eq!(TypeAbstraction::Bottom.join(&i), i);
6012 }
6013
6014 #[test]
6015 fn type_meet_intersects() {
6016 let i = TypeAbstraction::Concrete(TypeTag::Int);
6017 let b = TypeAbstraction::Concrete(TypeTag::Bool);
6018 let ib = union(&[TypeTag::Int, TypeTag::Bool]);
6019 assert_eq!(i.meet(&ib), i);
6021 assert_eq!(i.meet(&b), TypeAbstraction::Bottom);
6023 assert_eq!(TypeAbstraction::Top.meet(&i), i);
6025 assert_eq!(TypeAbstraction::Bottom.meet(&i), TypeAbstraction::Bottom);
6027 }
6028
6029 #[test]
6030 fn type_lattice_order_and_laws() {
6031 let i = TypeAbstraction::Concrete(TypeTag::Int);
6032 let ib = union(&[TypeTag::Int, TypeTag::Bool]);
6033 assert!(TypeAbstraction::Bottom.leq(&i));
6035 assert!(i.leq(&TypeAbstraction::Top));
6036 assert!(i.leq(&ib));
6038 assert!(!ib.leq(&i));
6039 assert!(i.leq(&i));
6041 let b = TypeAbstraction::Concrete(TypeTag::Bool);
6043 assert!(i.leq(&i.join(&b)));
6044 assert!(b.leq(&i.join(&b)));
6045 assert_eq!(i.widen(&b), i.join(&b));
6046 }
6047}
6048
6049#[cfg(test)]
6050mod shape_domain_tests {
6051 use super::*;
6052
6053 #[test]
6054 fn new_collection_is_empty_and_push_pop_track_size() {
6055 assert_eq!(CollectionShape::empty_collection(), CollectionShape::Empty);
6056 assert_eq!(CollectionShape::Empty.pushed(), CollectionShape::Singleton);
6058 assert_eq!(CollectionShape::Singleton.pushed(), CollectionShape::KnownSize(2));
6059 assert_eq!(CollectionShape::KnownSize(2).pushed(), CollectionShape::KnownSize(3));
6060 assert_eq!(CollectionShape::KnownSize(3).popped(), CollectionShape::KnownSize(2));
6062 assert_eq!(CollectionShape::Singleton.popped(), CollectionShape::Empty);
6063 assert_eq!(CollectionShape::Empty.popped(), CollectionShape::Empty);
6065 assert_eq!(CollectionShape::Top.pushed(), CollectionShape::NonEmpty);
6067 }
6068
6069 #[test]
6070 fn nonempty_and_empty_predicates() {
6071 assert!(CollectionShape::Singleton.is_definitely_nonempty());
6072 assert!(CollectionShape::KnownSize(5).is_definitely_nonempty());
6073 assert!(CollectionShape::NonEmpty.is_definitely_nonempty());
6074 assert!(!CollectionShape::Empty.is_definitely_nonempty());
6075 assert!(!CollectionShape::Top.is_definitely_nonempty());
6076 assert!(!CollectionShape::KnownSize(0).is_definitely_nonempty());
6077
6078 assert!(CollectionShape::Empty.is_definitely_empty());
6079 assert!(CollectionShape::KnownSize(0).is_definitely_empty());
6080 assert!(!CollectionShape::Singleton.is_definitely_empty());
6081 assert!(!CollectionShape::Top.is_definitely_empty());
6082 }
6083
6084 #[test]
6085 fn shape_canonical_equality() {
6086 assert_eq!(CollectionShape::KnownSize(0), CollectionShape::Empty);
6088 assert_eq!(CollectionShape::KnownSize(1), CollectionShape::Singleton);
6089 assert_eq!(CollectionShape::SizeRange(1, 1), CollectionShape::Singleton);
6090 }
6091
6092 #[test]
6093 fn shape_lattice_join_meet_leq() {
6094 assert_eq!(CollectionShape::Empty.join(&CollectionShape::Singleton),
6096 CollectionShape::SizeRange(0, 1));
6097 assert_eq!(CollectionShape::Singleton.join(&CollectionShape::KnownSize(3)),
6098 CollectionShape::SizeRange(1, 3));
6099 assert_eq!(CollectionShape::Singleton.join(&CollectionShape::Top),
6100 CollectionShape::Top);
6101 assert_eq!(CollectionShape::Empty.meet(&CollectionShape::Singleton),
6103 CollectionShape::Bottom);
6104 assert_eq!(CollectionShape::Top.meet(&CollectionShape::Singleton),
6105 CollectionShape::Singleton);
6106 assert!(CollectionShape::Singleton.leq(&CollectionShape::NonEmpty));
6108 assert!(CollectionShape::KnownSize(2).leq(&CollectionShape::SizeRange(0, 5)));
6109 assert!(CollectionShape::Empty.leq(&CollectionShape::Top));
6110 assert!(CollectionShape::Bottom.leq(&CollectionShape::Empty));
6111 assert!(!CollectionShape::Top.leq(&CollectionShape::NonEmpty));
6112 }
6113
6114 #[test]
6115 fn shape_widening_converges_growing_loops() {
6116 let w = CollectionShape::KnownSize(1).widen(&CollectionShape::KnownSize(2));
6118 assert_eq!(w, CollectionShape::NonEmpty);
6119 let w2 = CollectionShape::KnownSize(2).widen(&CollectionShape::KnownSize(1));
6121 assert!(w2.leq(&CollectionShape::Top));
6122 }
6123}
6124
6125#[cfg(test)]
6126mod nullability_domain_tests {
6127 use super::*;
6128
6129 #[test]
6130 fn top_and_bottom() {
6131 assert_eq!(<Nullability as AbstractDomain>::top(), Nullability::Maybe);
6132 assert_eq!(<Nullability as AbstractDomain>::bottom(), Nullability::Bottom);
6133 }
6134
6135 #[test]
6136 fn literal_nullability() {
6137 assert_eq!(Nullability::for_literal(&Literal::Nothing), Nullability::Null);
6139 assert_eq!(Nullability::for_literal(&Literal::Number(5)), Nullability::Definite);
6140 assert_eq!(Nullability::for_literal(&Literal::Boolean(false)), Nullability::Definite);
6141 }
6142
6143 #[test]
6144 fn matched_variant_binding_is_definite() {
6145 assert_eq!(Nullability::for_matched_variant(), Nullability::Definite);
6148 }
6149
6150 #[test]
6151 fn nullability_join_meet() {
6152 assert_eq!(Nullability::Definite.join(&Nullability::Null), Nullability::Maybe);
6154 assert_eq!(Nullability::Definite.join(&Nullability::Definite), Nullability::Definite);
6155 assert_eq!(Nullability::Bottom.join(&Nullability::Null), Nullability::Null);
6156 assert_eq!(Nullability::Maybe.join(&Nullability::Definite), Nullability::Maybe);
6157 assert_eq!(Nullability::Definite.meet(&Nullability::Null), Nullability::Bottom);
6159 assert_eq!(Nullability::Maybe.meet(&Nullability::Definite), Nullability::Definite);
6160 assert_eq!(Nullability::Definite.meet(&Nullability::Definite), Nullability::Definite);
6161 }
6162
6163 #[test]
6164 fn nullability_order_and_widen() {
6165 assert!(Nullability::Bottom.leq(&Nullability::Definite));
6166 assert!(Nullability::Definite.leq(&Nullability::Maybe));
6167 assert!(Nullability::Null.leq(&Nullability::Maybe));
6168 assert!(Nullability::Definite.leq(&Nullability::Definite));
6169 assert!(!Nullability::Definite.leq(&Nullability::Null));
6170 assert!(!Nullability::Maybe.leq(&Nullability::Definite));
6171 assert_eq!(Nullability::Definite.widen(&Nullability::Null),
6173 Nullability::Definite.join(&Nullability::Null));
6174 }
6175}
6176
6177#[cfg(test)]
6178mod alias_domain_tests {
6179 use super::*;
6180 use crate::intern::Interner;
6181 use std::collections::HashSet;
6182
6183 fn set(syms: &[Symbol]) -> HashSet<Symbol> {
6184 syms.iter().cloned().collect()
6185 }
6186
6187 #[test]
6188 fn alias_lattice_ops() {
6189 let mut it = Interner::new();
6190 let s = it.intern("s");
6191 let t = it.intern("t");
6192 assert_eq!(<AliasInfo as AbstractDomain>::top(), AliasInfo::Top);
6193 assert_eq!(<AliasInfo as AbstractDomain>::bottom(), AliasInfo::Bottom);
6194 assert_eq!(AliasInfo::MayAlias(HashSet::new()), AliasInfo::Unique);
6196 assert_eq!(AliasInfo::MayAlias(set(&[s])).join(&AliasInfo::MayAlias(set(&[t]))),
6198 AliasInfo::MayAlias(set(&[s, t])));
6199 assert_eq!(AliasInfo::Unique.join(&AliasInfo::MayAlias(set(&[s]))),
6200 AliasInfo::MayAlias(set(&[s])));
6201 assert_eq!(AliasInfo::Top.join(&AliasInfo::Unique), AliasInfo::Top);
6202 assert_eq!(AliasInfo::MayAlias(set(&[s, t])).meet(&AliasInfo::MayAlias(set(&[s]))),
6204 AliasInfo::MayAlias(set(&[s])));
6205 assert_eq!(AliasInfo::Top.meet(&AliasInfo::MayAlias(set(&[s]))),
6206 AliasInfo::MayAlias(set(&[s])));
6207 assert!(AliasInfo::Unique.leq(&AliasInfo::MayAlias(set(&[s]))));
6209 assert!(AliasInfo::MayAlias(set(&[s])).leq(&AliasInfo::MayAlias(set(&[s, t]))));
6210 assert!(AliasInfo::MayAlias(set(&[s])).leq(&AliasInfo::Top));
6211 assert!(AliasInfo::Bottom.leq(&AliasInfo::Unique));
6212 assert!(!AliasInfo::Top.leq(&AliasInfo::Unique));
6213 assert_eq!(AliasInfo::MayAlias(set(&[s])).widen(&AliasInfo::MayAlias(set(&[t]))),
6215 AliasInfo::MayAlias(set(&[s])).join(&AliasInfo::MayAlias(set(&[t]))));
6216 }
6217
6218 #[test]
6219 fn aliasing_mutation_invalidates_shared_allocation() {
6220 let mut it = Interner::new();
6221 let a = it.intern("a");
6222 let items = it.intern("items");
6223 let c = it.intern("c"); let mut g = AliasGraph::new();
6226 g.link(a, items); let inval = g.invalidated_by_mutation(a);
6230 assert!(inval.contains(&items));
6231 assert!(inval.contains(&a));
6232 assert!(!inval.contains(&c));
6233
6234 assert!(g.invalidated_by_mutation(items).contains(&a));
6236
6237 assert_eq!(g.invalidated_by_mutation(c), set(&[c]));
6239
6240 g.unlink(a);
6242 assert!(!g.invalidated_by_mutation(items).contains(&a));
6243 }
6244
6245 #[test]
6246 fn alias_transitivity() {
6247 let mut it = Interner::new();
6248 let a = it.intern("a");
6249 let b = it.intern("b");
6250 let c = it.intern("c");
6251 let mut g = AliasGraph::new();
6252 g.link(a, b); g.link(b, c); let inval = g.invalidated_by_mutation(a);
6256 assert!(inval.contains(&b) && inval.contains(&c));
6257 }
6258}
6259
6260#[cfg(test)]
6261mod product_lattice_tests {
6262 use super::*;
6263
6264 fn av(iv: (i64, i64), t: TypeTag, sh: CollectionShape, n: Nullability) -> AbstractValue {
6265 AbstractValue {
6266 interval: Interval { lo: Bound::Finite(iv.0), hi: Bound::Finite(iv.1) },
6267 ty: TypeAbstraction::Concrete(t),
6268 shape: sh,
6269 nullability: n,
6270 alias: AliasInfo::Unique,
6271 }
6272 }
6273
6274 #[test]
6275 fn product_top_is_all_top() {
6276 let t = <AbstractValue as AbstractDomain>::top();
6277 assert_eq!(t.ty, TypeAbstraction::Top);
6278 assert_eq!(t.shape, CollectionShape::Top);
6279 assert_eq!(t.nullability, Nullability::Maybe);
6280 assert_eq!(t.alias, AliasInfo::Top);
6281 assert!(t.interval.lo == Bound::NegInf && t.interval.hi == Bound::PosInf);
6282 }
6283
6284 #[test]
6285 fn product_join_is_componentwise() {
6286 let a = av((1, 1), TypeTag::Int, CollectionShape::Singleton, Nullability::Definite);
6287 let b = av((3, 3), TypeTag::Int, CollectionShape::KnownSize(3), Nullability::Definite);
6288 let j = a.join(&b);
6289 assert_eq!(j.interval.lo, Bound::Finite(1));
6291 assert_eq!(j.interval.hi, Bound::Finite(3));
6292 assert_eq!(j.ty, TypeAbstraction::Concrete(TypeTag::Int));
6294 assert_eq!(j.shape, CollectionShape::SizeRange(1, 3));
6296 assert_eq!(j.nullability, Nullability::Definite);
6298 }
6299
6300 #[test]
6301 fn product_join_mixed_types_widens_each_component() {
6302 let a = av((0, 0), TypeTag::Int, CollectionShape::Empty, Nullability::Null);
6303 let b = av((5, 5), TypeTag::Bool, CollectionShape::Singleton, Nullability::Definite);
6304 let j = a.join(&b);
6305 assert_eq!(j.interval.lo, Bound::Finite(0));
6306 assert_eq!(j.interval.hi, Bound::Finite(5));
6307 assert_eq!(j.ty, TypeAbstraction::Union([TypeTag::Int, TypeTag::Bool].into_iter().collect()));
6309 assert_eq!(j.shape, CollectionShape::SizeRange(0, 1));
6310 assert_eq!(j.nullability, Nullability::Maybe);
6312 }
6313
6314 #[test]
6315 fn product_leq_and_widen_componentwise() {
6316 let a = av((1, 1), TypeTag::Int, CollectionShape::Singleton, Nullability::Definite);
6317 let top = <AbstractValue as AbstractDomain>::top();
6318 assert!(a.leq(&top));
6319 assert!(!top.leq(&a));
6320 let b = av((1, 9), TypeTag::Int, CollectionShape::KnownSize(9), Nullability::Definite);
6323 let w = a.widen(&b);
6324 assert_eq!(w.interval.hi, Bound::Finite(10));
6325 assert_eq!(w.ty, TypeAbstraction::Concrete(TypeTag::Int));
6326 let c = av((1, 5000), TypeTag::Int, CollectionShape::KnownSize(9), Nullability::Definite);
6328 let w2 = a.widen(&c);
6329 assert!(matches!(w2.interval.hi, Bound::PosInf));
6330 }
6331}
6332
6333#[cfg(test)]
6334mod rich_walk_tests {
6335 use super::*;
6336 use crate::arena::Arena;
6337 use crate::intern::Interner;
6338
6339 fn lit_num<'a>(ea: &'a Arena<Expr<'a>>, n: i64) -> &'a Expr<'a> {
6340 ea.alloc(Expr::Literal(Literal::Number(n)))
6341 }
6342 fn ident<'a>(ea: &'a Arena<Expr<'a>>, s: Symbol) -> &'a Expr<'a> {
6343 ea.alloc(Expr::Identifier(s))
6344 }
6345 fn add<'a>(ea: &'a Arena<Expr<'a>>, l: &'a Expr<'a>, r: &'a Expr<'a>) -> &'a Expr<'a> {
6346 ea.alloc(Expr::BinaryOp { op: BinaryOpKind::Add, left: l, right: r })
6347 }
6348 fn new_seq<'a>(ea: &'a Arena<Expr<'a>>, name: Symbol) -> &'a Expr<'a> {
6349 ea.alloc(Expr::New { type_name: name, type_args: vec![], init_fields: vec![] })
6350 }
6351 fn let_stmt<'a>(var: Symbol, value: &'a Expr<'a>) -> Stmt<'a> {
6352 Stmt::Let { var, ty: None, value, mutable: true }
6353 }
6354
6355 #[test]
6356 fn let_chain_tracks_type_and_interval() {
6357 let ea: Arena<Expr> = Arena::new();
6358 let sa: Arena<Stmt> = Arena::new();
6359 let mut it = Interner::new();
6360 let x = it.intern("x");
6361 let y = it.intern("y");
6362 let five = lit_num(&ea, 5);
6364 let xref = ident(&ea, x);
6365 let seven = lit_num(&ea, 7);
6366 let sum = add(&ea, xref, seven);
6367 let stmts = vec![let_stmt(x, five), let_stmt(y, sum)];
6368
6369 let (_out, st) = rich_abstract_interp_stmts(stmts, &ea, &sa);
6370 let vx = st.value_of(x);
6371 let vy = st.value_of(y);
6372 assert_eq!(vx.ty, TypeAbstraction::Concrete(TypeTag::Int));
6373 assert_eq!(vx.interval.is_exact(), Some(5));
6374 assert_eq!(vy.ty, TypeAbstraction::Concrete(TypeTag::Int));
6375 assert_eq!(vy.interval.is_exact(), Some(12));
6376 assert_eq!(vx.nullability, Nullability::Definite);
6377 }
6378
6379 #[test]
6380 fn new_and_push_track_shape() {
6381 let ea: Arena<Expr> = Arena::new();
6382 let sa: Arena<Stmt> = Arena::new();
6383 let mut it = Interner::new();
6384 let items = it.intern("items");
6385 let seq_ty = it.intern("Seq");
6386 let new_e = new_seq(&ea, seq_ty);
6388 let ten = lit_num(&ea, 10);
6389 let items_ref = ident(&ea, items);
6390 let stmts = vec![
6391 let_stmt(items, new_e),
6392 Stmt::Push { value: ten, collection: items_ref },
6393 ];
6394 let (_out, st) = rich_abstract_interp_stmts(stmts, &ea, &sa);
6395 let v = st.value_of(items);
6396 assert_eq!(v.shape, CollectionShape::Singleton);
6397 assert!(v.shape.is_definitely_nonempty());
6398 }
6399
6400 #[test]
6401 fn alias_push_keeps_aliases_consistent() {
6402 let ea: Arena<Expr> = Arena::new();
6403 let sa: Arena<Stmt> = Arena::new();
6404 let mut it = Interner::new();
6405 let items = it.intern("items");
6406 let a = it.intern("a");
6407 let seq_ty = it.intern("Seq");
6408 let new_e = new_seq(&ea, seq_ty);
6410 let one = lit_num(&ea, 1);
6411 let two = lit_num(&ea, 2);
6412 let items_ref1 = ident(&ea, items);
6413 let items_ref2 = ident(&ea, items);
6414 let a_ref = ident(&ea, a);
6415 let stmts = vec![
6416 let_stmt(items, new_e),
6417 Stmt::Push { value: one, collection: items_ref1 },
6418 let_stmt(a, items_ref2), Stmt::Push { value: two, collection: a_ref }, ];
6421 let (_out, st) = rich_abstract_interp_stmts(stmts, &ea, &sa);
6422 let vitems = st.value_of(items);
6423 let va = st.value_of(a);
6424 assert!(vitems.shape.is_definitely_nonempty());
6426 assert_eq!(vitems.shape, va.shape);
6427 assert_eq!(vitems.shape, CollectionShape::KnownSize(2));
6428 }
6429
6430 #[test]
6431 fn if_branches_join() {
6432 let ea: Arena<Expr> = Arena::new();
6433 let sa: Arena<Stmt> = Arena::new();
6434 let mut it = Interner::new();
6435 let x = it.intern("x");
6436 let n = it.intern("n"); let zero = lit_num(&ea, 0);
6439 let nref = ident(&ea, n);
6440 let five = lit_num(&ea, 5);
6441 let cond = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::Gt, left: nref, right: five });
6442 let ten = lit_num(&ea, 10);
6443 let twenty = lit_num(&ea, 20);
6444 let then_block: &[Stmt] = sa.alloc_slice(vec![Stmt::Set { target: x, value: ten }]);
6445 let else_block: &[Stmt] = sa.alloc_slice(vec![Stmt::Set { target: x, value: twenty }]);
6446 let stmts = vec![
6447 let_stmt(x, zero),
6448 Stmt::If { cond, then_block, else_block: Some(else_block) },
6449 ];
6450 let (_out, st) = rich_abstract_interp_stmts(stmts, &ea, &sa);
6451 let vx = st.value_of(x);
6452 assert_eq!(vx.ty, TypeAbstraction::Concrete(TypeTag::Int));
6454 assert_eq!(vx.interval.lo, Bound::Finite(10));
6455 assert_eq!(vx.interval.hi, Bound::Finite(20));
6456 }
6457}