use rucc_ir::{Flags, IntPred};
use super::{Bits, PAIRS, Range, clamp, mask, sign_bit, signed_limits};
const COUNTS: usize = 16;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Truth {
Always,
Never,
Either,
}
#[must_use]
pub fn add(a: Range, b: Range, flags: Flags) -> Range {
assert_eq!(a.width(), b.width(), "these are ranges of different widths");
let width = a.width();
per_pair(a, b, |(al, ah), (bl, bh)| {
let wrapped = wrapping(al.wrapping_add(bl), span(ah - al, bh - bl, width), width);
clamped(
wrapped,
(al, ah),
(bl, bh),
flags,
width,
|(al, ah), (bl, bh)| (al.saturating_add(bl), ah.saturating_add(bh)),
|(al, ah), (bl, bh)| {
let lo = al.checked_add(bl).filter(|&lo| lo <= mask(width))?;
Some((lo, ah.saturating_add(bh)))
},
)
})
}
#[must_use]
pub fn sub(a: Range, b: Range, flags: Flags) -> Range {
assert_eq!(a.width(), b.width(), "these are ranges of different widths");
let width = a.width();
per_pair(a, b, |(al, ah), (bl, bh)| {
let wrapped = wrapping(al.wrapping_sub(bh), span(ah - al, bh - bl, width), width);
clamped(
wrapped,
(al, ah),
(bl, bh),
flags,
width,
|(al, ah), (bl, bh)| (al.saturating_sub(bh), ah.saturating_sub(bl)),
|(al, ah), (bl, bh)| {
let hi = ah.checked_sub(bl)?;
Some((al.saturating_sub(bh), hi))
},
)
})
}
#[must_use]
pub fn neg(a: Range, flags: Flags) -> Range {
sub(Range::exactly(0, a.width()), a, flags)
}
#[must_use]
pub fn mul(a: Range, b: Range, flags: Flags) -> Range {
assert_eq!(a.width(), b.width(), "these are ranges of different widths");
let width = a.width();
if a.is_empty() || b.is_empty() {
return Range::empty(width);
}
let zeros = a.bits().low_zeros().saturating_add(b.bits().low_zeros()).min(width);
let low = if zeros >= width {
Bits::exactly(0, width)
} else {
Bits::from_parts(0, mask(width) << zeros, width)
};
per_pair(a, b, |(al, ah), (bl, bh)| {
let wrapped = if al == ah && bl == bh {
Range::exactly(al.wrapping_mul(bl), width)
} else {
match (al.checked_mul(bl), ah.checked_mul(bh)) {
(Some(low), Some(high)) if high <= mask(width) => Range::between(low, high, width),
_ => Range::full(width),
}
};
clamped(
wrapped,
(al, ah),
(bl, bh),
flags,
width,
|(al, ah), (bl, bh)| {
let corners = [
al.saturating_mul(bl),
al.saturating_mul(bh),
ah.saturating_mul(bl),
ah.saturating_mul(bh),
];
let least = corners.into_iter().min().expect("four corners");
(least, corners.into_iter().max().expect("four corners"))
},
|(al, ah), (bl, bh)| {
let lo = al.checked_mul(bl).filter(|&lo| lo <= mask(width))?;
Some((lo, ah.saturating_mul(bh)))
},
)
})
.narrow(low)
}
#[must_use]
pub fn and(a: Range, b: Range) -> Range {
assert_eq!(a.width(), b.width(), "these are ranges of different widths");
let width = a.width();
let (Some((_, ah)), Some((_, bh))) = (a.unsigned_bounds(), b.unsigned_bounds()) else {
return Range::empty(width);
};
let ones = ones(a) & ones(b);
let zeros = zeros(a, width) | zeros(b, width);
let bits = Bits::from_parts(ones, mask(width) & !ones & !zeros, width);
Range::between(0, ah.min(bh), width).narrow(bits)
}
#[must_use]
pub fn or(a: Range, b: Range) -> Range {
assert_eq!(a.width(), b.width(), "these are ranges of different widths");
let width = a.width();
let (Some((al, _)), Some((bl, _))) = (a.unsigned_bounds(), b.unsigned_bounds()) else {
return Range::empty(width);
};
let ones = ones(a) | ones(b);
let zeros = zeros(a, width) & zeros(b, width);
let bits = Bits::from_parts(ones, mask(width) & !ones & !zeros, width);
Range::between(al.max(bl), mask(width), width).narrow(bits)
}
#[must_use]
pub fn xor(a: Range, b: Range) -> Range {
assert_eq!(a.width(), b.width(), "these are ranges of different widths");
let width = a.width();
if a.is_empty() || b.is_empty() {
return Range::empty(width);
}
let known = !a.bits().unknown_bits() & !b.bits().unknown_bits();
let value = (a.bits().value() ^ b.bits().value()) & known;
Range::full(width).narrow(Bits::from_parts(value, mask(width) & !known, width))
}
#[must_use]
pub fn not(a: Range) -> Range {
let width = a.width();
let pairs: Vec<(u128, u128)> =
a.pairs().iter().map(|&(lo, hi)| (mask(width) - hi, mask(width) - lo)).collect();
Range::from_pairs(&pairs, width)
}
#[must_use]
pub fn shl(a: Range, count: Range, flags: Flags) -> Range {
shift(a, count, flags, Kind::Left)
}
#[must_use]
pub fn lshr(a: Range, count: Range, flags: Flags) -> Range {
shift(a, count, flags, Kind::Logical)
}
#[must_use]
pub fn ashr(a: Range, count: Range, flags: Flags) -> Range {
shift(a, count, flags, Kind::Arithmetic)
}
#[must_use]
pub fn trunc(a: Range, to: u32) -> Range {
let to = clamp(to);
let mut pairs: Vec<(u128, u128)> = Vec::with_capacity(PAIRS * 2);
for &(lo, hi) in a.pairs() {
if hi - lo >= mask(to) {
return Range::full(to);
}
let (lo, hi) = (lo & mask(to), hi & mask(to));
if lo <= hi {
pairs.push((lo, hi));
} else {
pairs.push((0, hi));
pairs.push((lo, mask(to)));
}
}
Range::from_pairs(&pairs, to)
}
#[must_use]
pub fn zext(a: Range, to: u32) -> Range {
let to = clamp(to);
if to <= a.width() {
return trunc(a, to);
}
Range::from_pairs(a.pairs(), to).narrow(Bits::from_parts(0, mask(a.width()), to))
}
#[must_use]
pub fn sext(a: Range, to: u32) -> Range {
let to = clamp(to);
let from = a.width();
if to <= from {
return trunc(a, to);
}
let boundary = sign_bit(from);
let lift = mask(to) - mask(from);
let mut pairs: Vec<(u128, u128)> = Vec::with_capacity(PAIRS * 2);
for &(lo, hi) in a.pairs() {
if lo < boundary {
pairs.push((lo, hi.min(boundary - 1)));
}
if hi >= boundary {
pairs.push((lo.max(boundary) + lift, hi + lift));
}
}
Range::from_pairs(&pairs, to)
}
#[must_use]
pub fn compare(pred: IntPred, a: Range, b: Range) -> Truth {
if a.is_empty() || b.is_empty() {
return Truth::Either;
}
match (possible(pred, a, b), possible(pred.inverse(), a, b)) {
(true, false) => Truth::Always,
(false, true) => Truth::Never,
_ => Truth::Either,
}
}
#[must_use]
pub fn narrow_for(pred: IntPred, a: Range, b: Range) -> Range {
assert_eq!(a.width(), b.width(), "these are ranges of different widths");
let width = a.width();
if a.is_empty() || b.is_empty() {
return Range::empty(width);
}
let (ul, uh) = b.unsigned_bounds().expect("not empty");
let (sl, sh) = b.signed_bounds().expect("not empty");
let (low, high) = signed_limits(width);
let allowed = match pred {
IntPred::Eq => b,
IntPred::Ne => match b.singleton() {
Some(value) => Range::other_than(value, width),
None => return a,
},
IntPred::Ult if uh == 0 => Range::empty(width),
IntPred::Ult => Range::between(0, uh - 1, width),
IntPred::Ule => Range::between(0, uh, width),
IntPred::Ugt if ul == mask(width) => Range::empty(width),
IntPred::Ugt => Range::between(ul + 1, mask(width), width),
IntPred::Uge => Range::between(ul, mask(width), width),
IntPred::Slt => Range::signed_between(low, sh.saturating_sub(1), width),
IntPred::Sle => Range::signed_between(low, sh, width),
IntPred::Sgt => Range::signed_between(sl.saturating_add(1), high, width),
IntPred::Sge => Range::signed_between(sl, high, width),
};
a.intersect(allowed)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Undo {
AddLeft,
SubRight,
SubLeft,
Neg,
Not,
Xor,
Zext(u32),
Sext(u32),
}
#[must_use]
pub fn backward(undo: Undo, result: Range, other: Range) -> Range {
let width = result.width();
match undo {
Undo::AddLeft => sub(result, other, Flags::NONE),
Undo::SubRight => sub(other, result, Flags::NONE),
Undo::SubLeft => add(result, other, Flags::NONE),
Undo::Neg => neg(result, Flags::NONE),
Undo::Not => not(result),
Undo::Xor => xor(result, other),
Undo::Zext(from) => trunc(result.intersect(zext(Range::full(from), width)), from),
Undo::Sext(from) => trunc(result.intersect(sext(Range::full(from), width)), from),
}
}
fn possible(pred: IntPred, a: Range, b: Range) -> bool {
let (Some((ul, uh)), Some((vl, vh))) = (a.unsigned_bounds(), b.unsigned_bounds()) else {
return false;
};
let (Some((sl, sh)), Some((tl, th))) = (a.signed_bounds(), b.signed_bounds()) else {
return false;
};
match pred {
IntPred::Eq => !a.intersect(b).is_empty(),
IntPred::Ne => !matches!((a.singleton(), b.singleton()), (Some(x), Some(y)) if x == y),
IntPred::Ult => ul < vh,
IntPred::Ule => ul <= vh,
IntPred::Ugt => uh > vl,
IntPred::Uge => uh >= vl,
IntPred::Slt => sl < th,
IntPred::Sle => sl <= th,
IntPred::Sgt => sh > tl,
IntPred::Sge => sh >= tl,
}
}
fn ones(a: Range) -> u128 {
a.bits().value()
}
fn zeros(a: Range, width: u32) -> u128 {
!a.bits().value() & !a.bits().unknown_bits() & mask(width)
}
fn span(a: u128, b: u128, width: u32) -> Option<u128> {
match a.checked_add(b) {
Some(span) if span < mask(width) => Some(span),
_ => None,
}
}
fn per_pair(a: Range, b: Range, each: impl Fn((u128, u128), (u128, u128)) -> Range) -> Range {
let width = a.width();
if a.is_empty() || b.is_empty() {
return Range::empty(width);
}
let mut out = Range::empty(width);
for &left in a.pairs() {
for &right in b.pairs() {
out = out.union(each(left, right));
}
}
out
}
fn wrapping(lo: u128, span: Option<u128>, width: u32) -> Range {
let Some(span) = span else {
return Range::full(width);
};
let lo = lo & mask(width);
Range::between(lo, lo.wrapping_add(span) & mask(width), width)
}
fn clamped(
wrapped: Range,
a: (u128, u128),
b: (u128, u128),
flags: Flags,
width: u32,
signed_window: impl Fn((i128, i128), (i128, i128)) -> (i128, i128),
unsigned_window: impl Fn((u128, u128), (u128, u128)) -> Option<(u128, u128)>,
) -> Range {
let mut range = wrapped;
if flags.contains(Flags::NSW) {
let (lo, hi) = signed_window(as_signed(a, width), as_signed(b, width));
range = range.intersect(Range::signed_between(lo, hi, width));
}
if flags.contains(Flags::NUW) {
range = match unsigned_window(a, b) {
Some((lo, hi)) if lo <= mask(width) => {
range.intersect(Range::between(lo, hi.min(mask(width)), width))
}
_ => Range::empty(width),
};
}
range
}
fn as_signed(interval: (u128, u128), width: u32) -> (i128, i128) {
Range::between(interval.0, interval.1, width).signed_bounds().expect("not empty")
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Left,
Logical,
Arithmetic,
}
fn shift(a: Range, count: Range, flags: Flags, kind: Kind) -> Range {
let width = a.width();
if a.is_empty() || count.is_empty() {
return Range::empty(width);
}
let Some((low, _)) = count.unsigned_bounds() else {
return Range::empty(width);
};
if low >= u128::from(width) {
return Range::full(width);
}
match count.list(COUNTS) {
Some(counts) => {
let mut range = Range::empty(width);
for at in counts {
if at >= u128::from(width) {
return Range::full(width);
}
range = range.union(one_shift(a, at as u32, flags, kind, width));
}
range
}
None => coarse(a, low as u32, width, kind),
}
}
fn one_shift(a: Range, at: u32, flags: Flags, kind: Kind, width: u32) -> Range {
match kind {
Kind::Left => mul(a, Range::exactly(1u128 << at, width), flags),
Kind::Logical => {
let pairs: Vec<(u128, u128)> =
a.pairs().iter().map(|&(lo, hi)| (lo >> at, hi >> at)).collect();
Range::from_pairs(&pairs, width)
}
Kind::Arithmetic => {
let Some((lo, hi)) = a.signed_bounds() else {
return Range::empty(width);
};
Range::signed_between(lo >> at, hi >> at, width)
}
}
}
fn coarse(a: Range, low: u32, width: u32, kind: Kind) -> Range {
match kind {
Kind::Left => Range::full(width).narrow(Bits::from_parts(0, mask(width) << low, width)),
Kind::Logical => Range::between(0, mask(width) >> low, width),
Kind::Arithmetic => {
let Some((lo, hi)) = a.signed_bounds() else {
return Range::empty(width);
};
Range::signed_between(lo.min(0), hi.max(-1), width)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::range::signed;
type Forwards = Box<dyn Fn(u128, u128) -> u128>;
const W: u32 = 3;
fn all() -> Vec<Range> {
let mut ranges = Vec::new();
for subset in 0u32..1 << (1u32 << W) {
let values: Vec<u128> =
(0..=mask(W)).filter(|&value| subset & (1 << value) != 0).collect();
let mut pairs: Vec<(u128, u128)> = Vec::new();
for &value in &values {
match pairs.last_mut() {
Some(last) if last.1 + 1 == value => last.1 = value,
_ => pairs.push((value, value)),
}
}
if pairs.len() > PAIRS {
continue;
}
let range = Range::from_pairs(&pairs, W);
if held(range) == values {
ranges.push(range);
}
}
ranges
}
fn held(range: Range) -> Vec<u128> {
(0..=mask(range.width())).filter(|&value| range.contains(value)).collect()
}
fn check(got: Range, want: &[u128], what: &str, sharp: bool) {
for value in want {
assert!(got.contains(*value), "{what} lost {value:#x}, got {got:?}");
}
if !sharp {
return;
}
let listed: Vec<u128> = held(got);
assert_eq!(listed, want, "{what} is vaguer than it has any excuse to be");
}
fn binary(
name: &str,
op: impl Fn(Range, Range) -> Range,
truth: impl Fn(u128, u128) -> Option<u128>,
) {
let ranges = all();
for &a in &ranges {
for &b in &ranges {
let mut want: Vec<u128> = Vec::new();
for x in held(a) {
for y in held(b) {
if let Some(value) = truth(x, y) {
if !want.contains(&value) {
want.push(value);
}
}
}
}
want.sort_unstable();
let sharp = a.singleton().is_some() && b.singleton().is_some();
check(op(a, b), &want, &format!("{name}({a:?}, {b:?})"), sharp);
}
}
}
fn unary(name: &str, op: impl Fn(Range) -> Range, truth: impl Fn(u128) -> u128) {
for a in all() {
let mut want: Vec<u128> = held(a).into_iter().map(&truth).collect();
want.sort_unstable();
want.dedup();
check(op(a), &want, &format!("{name}({a:?})"), a.singleton().is_some());
}
}
fn runs(values: &[u128]) -> usize {
let mut count = 0;
let mut previous: Option<u128> = None;
for &value in values {
match previous {
Some(last) if last + 1 == value => {}
_ => count += 1,
}
previous = Some(value);
}
count
}
fn as_signed(value: u128) -> i128 {
signed(value, W)
}
fn fits_signed(value: i128) -> bool {
let (low, high) = signed_limits(W);
(low..=high).contains(&value)
}
#[test]
fn addition_wraps_and_says_so() {
binary("add", |a, b| add(a, b, Flags::NONE), |x, y| Some(x.wrapping_add(y) & mask(W)));
}
#[test]
fn addition_that_promised_not_to_overflow_leaves_out_the_pairs_that_would_have() {
binary(
"add nsw",
|a, b| add(a, b, Flags::NSW),
|x, y| {
let sum = as_signed(x) + as_signed(y);
fits_signed(sum).then(|| x.wrapping_add(y) & mask(W))
},
);
binary("add nuw", |a, b| add(a, b, Flags::NUW), |x, y| (x + y <= mask(W)).then_some(x + y));
}
#[test]
fn subtraction_wraps_and_says_so() {
binary("sub", |a, b| sub(a, b, Flags::NONE), |x, y| Some(x.wrapping_sub(y) & mask(W)));
binary(
"sub nsw",
|a, b| sub(a, b, Flags::NSW),
|x, y| {
let difference = as_signed(x) - as_signed(y);
fits_signed(difference).then(|| x.wrapping_sub(y) & mask(W))
},
);
binary("sub nuw", |a, b| sub(a, b, Flags::NUW), |x, y| (x >= y).then(|| x - y));
}
#[test]
fn the_overflow_promise_is_checked_against_each_pairing_and_not_the_whole_range() {
let a = Range::from_pairs(&[(1, 1), (4, 4)], 3);
let b = Range::from_pairs(&[(3, 3), (6, 6)], 3);
assert_eq!(add(a, b, Flags::NSW).singleton(), Some(7));
assert_eq!(add(a, b, Flags::NONE).list(8), Some(vec![2, 4, 7]));
}
#[test]
fn a_promise_that_nothing_can_keep_proves_the_code_unreachable() {
let hundred = Range::exactly(100, 8);
assert!(add(hundred, hundred, Flags::NSW).is_empty());
assert_eq!(add(hundred, hundred, Flags::NONE).singleton(), Some(200));
assert!(add(Range::exactly(200, 8), hundred, Flags::NUW).is_empty());
}
#[test]
fn negation_is_zero_minus_it() {
unary("neg", |a| neg(a, Flags::NONE), |x| x.wrapping_neg() & mask(W));
}
#[test]
fn multiplication_wraps_and_says_so() {
binary("mul", |a, b| mul(a, b, Flags::NONE), |x, y| Some(x.wrapping_mul(y) & mask(W)));
binary(
"mul nsw",
|a, b| mul(a, b, Flags::NSW),
|x, y| {
let product = as_signed(x) * as_signed(y);
fits_signed(product).then(|| x.wrapping_mul(y) & mask(W))
},
);
binary("mul nuw", |a, b| mul(a, b, Flags::NUW), |x, y| (x * y <= mask(W)).then_some(x * y));
}
#[test]
fn a_product_of_even_numbers_is_known_to_be_a_multiple_of_four() {
let evens = Range::full(32).narrow(Bits::from_parts(0, mask(32) - 1, 32));
let product = mul(evens, evens, Flags::NONE);
assert_eq!(product.bits().low_zeros(), 2);
assert!(!product.contains(2));
assert!(product.contains(4));
}
#[test]
fn the_bitwise_operations_are_what_they_do_to_every_pair() {
binary("and", and, |x, y| Some(x & y));
binary("or", or, |x, y| Some(x | y));
binary("xor", xor, |x, y| Some(x ^ y));
unary("not", not, |x| !x & mask(W));
}
#[test]
fn the_shifts_are_what_they_do_to_every_pair() {
let counts = Range::between(0, u128::from(W) - 1, W);
for count in all() {
let count = count.intersect(counts);
if count.is_empty() {
continue;
}
for a in all() {
let sharp = a.singleton().is_some() && count.singleton().is_some();
for (name, got) in [
("shl", shl(a, count, Flags::NONE)),
("lshr", lshr(a, count, Flags::NONE)),
("ashr", ashr(a, count, Flags::NONE)),
] {
let mut want: Vec<u128> = Vec::new();
for x in held(a) {
for at in held(count) {
let at = at as u32;
let value = match name {
"shl" => (x << at) & mask(W),
"lshr" => x >> at,
_ => (as_signed(x) >> at) as u128 & mask(W),
};
if !want.contains(&value) {
want.push(value);
}
}
}
want.sort_unstable();
check(got, &want, &format!("{name}({a:?}, {count:?})"), sharp);
}
}
}
}
#[test]
fn a_shift_count_that_might_be_too_large_gives_up_rather_than_guessing() {
let a = Range::exactly(1, 8);
assert!(shl(a, Range::between(8, 9, 8), Flags::NONE).is_full());
assert!(shl(a, Range::between(7, 8, 8), Flags::NONE).is_full());
assert_eq!(shl(a, Range::exactly(7, 8), Flags::NONE).singleton(), Some(0x80));
}
#[test]
fn a_shift_count_with_more_values_than_are_worth_walking_still_says_something() {
let wide = Range::between(4, 31, 32);
let shifted = shl(Range::full(32), wide, Flags::NONE);
assert_eq!(shifted.bits().low_zeros(), 4);
assert_eq!(
lshr(Range::full(32), wide, Flags::NONE).unsigned_bounds(),
Some((0, 0x0fff_ffff))
);
}
#[test]
fn the_casts_are_what_they_do_to_every_value() {
for a in all() {
for to in 1..=6u32 {
let mut want: Vec<u128> =
held(a).into_iter().map(|x| x & mask(to)).collect::<Vec<_>>();
want.sort_unstable();
want.dedup();
let sharp = runs(&want) <= PAIRS;
check(trunc(a, to), &want, &format!("trunc({a:?}, {to})"), sharp);
let mut want: Vec<u128> = held(a).into_iter().map(|x| x & mask(W)).collect();
want.sort_unstable();
want.dedup();
let sharp = runs(&want) <= PAIRS;
check(zext(a, W + to), &want, &format!("zext({a:?}, {})", W + to), sharp);
let mut want: Vec<u128> =
held(a).into_iter().map(|x| as_signed(x) as u128 & mask(W + to)).collect();
want.sort_unstable();
want.dedup();
let sharp = runs(&want) <= PAIRS;
check(sext(a, W + to), &want, &format!("sext({a:?}, {})", W + to), sharp);
}
}
}
#[test]
fn truncating_a_run_that_wraps_round_is_still_exact() {
let range = Range::between(0xfe, 0x101, 32);
let low = trunc(range, 8);
assert_eq!(held(low), [0x00, 0x01, 0xfe, 0xff]);
}
#[test]
fn a_comparison_is_settled_only_when_every_pair_agrees() {
let ranges = all();
for &a in &ranges {
for &b in &ranges {
for pred in IntPred::all() {
let mut yes = false;
let mut no = false;
for x in held(a) {
for y in held(b) {
if holds(pred, x, y) {
yes = true;
} else {
no = true;
}
}
}
let want = match (yes, no) {
(true, false) => Truth::Always,
(false, true) => Truth::Never,
_ => Truth::Either,
};
let got = compare(pred, a, b);
if want == Truth::Either {
assert_eq!(got, Truth::Either, "{pred} {a:?} {b:?}");
} else {
assert!(
got == want || got == Truth::Either,
"{pred} {a:?} {b:?} said {got:?} and it is {want:?}"
);
}
}
}
}
}
#[test]
fn narrowing_for_a_comparison_keeps_every_value_that_could_satisfy_it() {
let ranges = all();
for &a in &ranges {
for &b in &ranges {
for pred in IntPred::all() {
let mut want: Vec<u128> = Vec::new();
for x in held(a) {
if held(b).into_iter().any(|y| holds(pred, x, y)) {
want.push(x);
}
}
let sharp = runs(&want) <= PAIRS && b.singleton().is_some();
check(narrow_for(pred, a, b), &want, &format!("{pred} {a:?} {b:?}"), sharp);
}
}
}
}
#[test]
fn a_branch_on_a_constant_bound_gives_the_range_the_bound_says() {
let full = Range::full(32);
let ten = Range::exactly(10, 32);
assert_eq!(narrow_for(IntPred::Ult, full, ten).unsigned_bounds(), Some((0, 9)));
assert_eq!(narrow_for(IntPred::Uge, full, ten).unsigned_bounds(), Some((10, 0xffff_ffff)));
assert_eq!(
narrow_for(IntPred::Slt, full, ten).signed_bounds(),
Some((i128::from(i32::MIN), 9))
);
assert!(narrow_for(IntPred::Ne, full, Range::exactly(0, 32)).nonzero());
}
#[test]
fn the_inverses_take_a_result_back_to_an_operand_that_could_have_made_it() {
let ranges = all();
for &result in &ranges {
for &other in &ranges {
let cases: [(Undo, Forwards); 6] = [
(Undo::AddLeft, Box::new(|r: u128, o: u128| r.wrapping_sub(o) & mask(W))),
(Undo::SubRight, Box::new(|r: u128, o: u128| o.wrapping_sub(r) & mask(W))),
(Undo::SubLeft, Box::new(|r: u128, o: u128| r.wrapping_add(o) & mask(W))),
(Undo::Neg, Box::new(|r: u128, _| r.wrapping_neg() & mask(W))),
(Undo::Not, Box::new(|r: u128, _| !r & mask(W))),
(Undo::Xor, Box::new(|r: u128, o: u128| r ^ o)),
];
for (undo, forwards) in cases {
let mut want: Vec<u128> = Vec::new();
for r in held(result) {
for o in held(other) {
let value = forwards(r, o);
if !want.contains(&value) {
want.push(value);
}
}
}
want.sort_unstable();
let got = backward(undo, result, other);
for value in &want {
assert!(
got.contains(*value),
"{undo:?} of {result:?} and {other:?} lost {value:#x}"
);
}
}
}
}
}
#[test]
fn undoing_an_extension_narrows_and_can_prove_a_path_dead() {
let result = Range::between(0x0f0, 0x1ff, 32);
assert_eq!(
backward(Undo::Zext(8), result, Range::full(32)).unsigned_bounds(),
Some((0xf0, 0xff))
);
let impossible = Range::between(0x100, 0x1ff, 32);
assert!(backward(Undo::Sext(8), impossible, Range::full(32)).is_empty());
}
fn holds(pred: IntPred, x: u128, y: u128) -> bool {
let (sx, sy) = (as_signed(x), as_signed(y));
match pred {
IntPred::Eq => x == y,
IntPred::Ne => x != y,
IntPred::Ult => x < y,
IntPred::Ule => x <= y,
IntPred::Ugt => x > y,
IntPred::Uge => x >= y,
IntPred::Slt => sx < sy,
IntPred::Sle => sx <= sy,
IntPred::Sgt => sx > sy,
IntPred::Sge => sx >= sy,
}
}
}