use rustc_middle::{
mir,
ty::{self, Ty},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Known<'tcx> {
pub bits: u128,
pub ty: Ty<'tcx>,
pub width: u32,
}
impl Known<'_> {
pub fn is_signed(self) -> bool {
matches!(self.ty.kind(), ty::Int(_))
}
pub const fn as_signed(self) -> i128 {
let Some(shift) = 128u32.checked_sub(self.width) else {
return self.bits.cast_signed();
};
if shift == 0 || shift == 128 {
return self.bits.cast_signed();
}
(self.bits << shift).cast_signed() >> shift
}
pub const fn truth(self) -> bool {
self.bits != 0
}
pub fn order(self, other: Self) -> Option<std::cmp::Ordering> {
if self.ty != other.ty || self.width != other.width {
return None;
}
Some(if self.is_signed() {
self.as_signed().cmp(&other.as_signed())
} else {
self.bits.cmp(&other.bits)
})
}
pub fn type_min(self) -> Self {
let bits = if self.is_signed() {
truncate(1u128 << (self.width.saturating_sub(1)), self.width)
} else {
0
};
Self { bits, ..self }
}
pub fn type_max(self) -> Self {
let all = truncate(u128::MAX, self.width);
let bits = if self.is_signed() { all >> 1 } else { all };
Self { bits, ..self }
}
pub fn predecessor(self) -> Option<Self> {
if self == self.type_min() {
return None;
}
Some(Self {
bits: truncate(self.bits.wrapping_sub(1), self.width),
..self
})
}
pub fn successor(self) -> Option<Self> {
if self == self.type_max() {
return None;
}
Some(Self {
bits: truncate(self.bits.wrapping_add(1), self.width),
..self
})
}
pub fn arith(self, op: mir::BinOp, other: Self) -> Option<Self> {
use mir::BinOp::{Add, Mul, Sub};
if self.ty != other.ty || self.width != other.width {
return None;
}
let bits = if self.is_signed() {
let (left, right) = (self.as_signed(), other.as_signed());
let value = match op {
Add => left.checked_add(right)?,
Sub => left.checked_sub(right)?,
Mul => left.checked_mul(right)?,
_ => return None,
};
if value < self.type_min().as_signed()
|| value > self.type_max().as_signed()
{
return None;
}
truncate(value.cast_unsigned(), self.width)
} else {
let value = match op {
Add => self.bits.checked_add(other.bits)?,
Sub => self.bits.checked_sub(other.bits)?,
Mul => self.bits.checked_mul(other.bits)?,
_ => return None,
};
if value > self.type_max().bits {
return None;
}
value
};
Some(Self { bits, ..self })
}
pub fn shifted(self, op: mir::BinOp, amount: u32) -> Option<Self> {
use mir::BinOp::{Shl, ShlUnchecked, Shr, ShrUnchecked};
if amount >= self.width {
return None;
}
match op {
Shr | ShrUnchecked => {
let bits = if self.is_signed() {
truncate(
(self.as_signed() >> amount).cast_unsigned(),
self.width,
)
} else {
self.bits >> amount
};
Some(Self { bits, ..self })
}
Shl | ShlUnchecked => {
let factor = Self {
bits: 1u128 << amount,
..self
};
if factor.is_signed() && factor.as_signed() < 0 {
return None;
}
self.arith(mir::BinOp::Mul, factor)
}
_ => None,
}
}
pub fn saturated(self) -> Option<Self> {
if self.is_signed() && self.as_signed() < 0 {
return None;
}
let bits = self
.bits
.checked_ilog2()
.map_or(0, |top| truncate(u128::MAX, top.saturating_add(1)));
Some(Self { bits, ..self })
}
pub fn quotient(self, other: Self) -> Option<Self> {
if self.ty != other.ty || self.is_signed() || other.bits == 0 {
return None;
}
Some(Self {
bits: self.bits / other.bits,
..self
})
}
pub fn lesser(self, other: Self) -> Option<Self> {
Some(if self.order(other)? == std::cmp::Ordering::Greater {
other
} else {
self
})
}
pub fn greater(self, other: Self) -> Option<Self> {
Some(if self.order(other)? == std::cmp::Ordering::Greater {
self
} else {
other
})
}
pub fn nonnegative(self) -> bool {
!self.is_signed() || self.as_signed() >= 0
}
pub const fn zero(self) -> Self {
Self { bits: 0, ..self }
}
}
#[allow(
clippy::cast_possible_truncation,
reason = "the value is checked against the counter's range first"
)]
const fn clipped(by: u64) -> u32 {
if by > u32::MAX as u64 {
u32::MAX
} else {
by as u32
}
}
const RANKS: usize = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Ranks {
held: [Option<(LenRel, mir::Local)>; RANKS],
}
impl Ranks {
pub const fn none_held() -> Self {
Self {
held: [None; RANKS],
}
}
pub fn of(rel: LenRel, of: mir::Local) -> Self {
let mut ranks = Self::default();
ranks.add(rel, of);
ranks
}
pub fn is_empty(self) -> bool {
self.held.iter().all(Option::is_none)
}
pub fn against(self, of: mir::Local) -> Option<LenRel> {
self.held
.iter()
.flatten()
.find(|(_, held)| *held == of)
.map(|(rel, _)| *rel)
}
pub fn first(self) -> Option<(LenRel, mir::Local)> {
self.held.iter().flatten().copied().next()
}
pub fn each(self) -> impl Iterator<Item = (LenRel, mir::Local)> {
self.held.into_iter().flatten()
}
pub fn add(&mut self, rel: LenRel, of: mir::Local) {
for slot in &mut self.held {
match slot {
Some((held, named)) if *named == of => {
*held = held.sharper(rel);
return;
}
Some(_) => {}
None => {
*slot = Some((rel, of));
return;
}
}
}
}
pub fn joined(self, other: Self) -> Self {
self.joined_with(other, false, false)
}
pub fn joined_with(self, other: Self, mine: bool, theirs: bool) -> Self {
let mut ranks = Self::default();
for (rel, of) in self.each() {
if let Some(held) = other.against(of) {
ranks.add(rel.weaker(held), of);
} else if theirs {
ranks.add(LenRel::AT_MOST, of);
}
}
if mine {
for (_, of) in other.each() {
if self.against(of).is_none() {
ranks.add(LenRel::AT_MOST, of);
}
}
}
ranks
}
pub fn widened(self, from: Self) -> Self {
let mut ranks = Self::default();
for (rel, of) in self.each() {
let shrinking =
from.against(of).is_some_and(|was| rel.short < was.short);
let kept = if shrinking {
LenRel {
short: rel.short.min(1),
}
} else {
rel
};
ranks.add(kept, of);
}
ranks
}
pub fn forget(&mut self, local: mir::Local) {
for slot in &mut self.held {
if slot.is_some_and(|(_, of)| of == local) {
*slot = None;
}
}
}
}
pub const STOPS: usize = 8;
pub struct Thresholds {
steps: [u128; STOPS],
held: usize,
}
impl Thresholds {
pub const fn none() -> Self {
Self {
steps: [0; STOPS],
held: 0,
}
}
pub fn add(&mut self, value: u128) {
let Some(held) = self
.steps
.get(..self.held)
.filter(|held| self.held < STOPS && !held.contains(&value))
else {
return;
};
let at = held
.iter()
.position(|step| *step > value)
.unwrap_or(self.held);
self.steps.copy_within(at..self.held, at.saturating_add(1));
if let Some(slot) = self.steps.get_mut(at) {
*slot = value;
self.held = self.held.saturating_add(1);
}
}
fn over<'tcx>(&self, end: Known<'tcx>) -> Known<'tcx> {
let ceiling = end.type_max();
if end.is_signed() {
return ceiling;
}
for step in self.steps.get(..self.held).unwrap_or_default() {
if *step >= end.bits && *step <= ceiling.bits {
return Known { bits: *step, ..end };
}
}
ceiling
}
fn under<'tcx>(&self, end: Known<'tcx>) -> Known<'tcx> {
let floor = end.type_min();
if end.is_signed() {
return floor;
}
for step in self.steps.get(..self.held).unwrap_or_default().iter().rev()
{
if *step <= end.bits {
return Known { bits: *step, ..end };
}
}
floor
}
}
pub const fn truncate(bits: u128, width: u32) -> u128 {
match 1u128.checked_shl(width) {
Some(above) => bits & above.wrapping_sub(1),
None => bits,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Bounds<'tcx> {
pub lo: Known<'tcx>,
pub hi: Known<'tcx>,
}
impl<'tcx> Bounds<'tcx> {
pub fn new(lo: Known<'tcx>, hi: Known<'tcx>) -> Option<Self> {
(lo.order(hi)? != std::cmp::Ordering::Greater)
.then_some(Self { lo, hi })
}
fn admits(self, value: Known<'tcx>) -> Option<bool> {
let above = self.lo.order(value)? != std::cmp::Ordering::Greater;
let below = value.order(self.hi)? != std::cmp::Ordering::Greater;
Some(above && below)
}
fn hull(self, other: Self) -> Option<Self> {
Self::new(self.lo.lesser(other.lo)?, self.hi.greater(other.hi)?)
}
fn overlap(self, other: Self) -> Option<Self> {
Self::new(self.lo.greater(other.lo)?, self.hi.lesser(other.hi)?)
}
pub fn covering(values: &[Known<'tcx>]) -> Option<Self> {
let (first, rest) = values.split_first()?;
let mut span = Self {
lo: *first,
hi: *first,
};
for value in rest {
span = span.hull(Self {
lo: *value,
hi: *value,
})?;
}
Some(span)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LenRel {
pub short: u32,
}
impl LenRel {
pub const AT_MOST: Self = Self { short: 0 };
pub const BELOW: Self = Self { short: 1 };
pub const fn is_below(self) -> bool {
self.short >= 1
}
pub fn sharper(self, other: Self) -> Self {
Self {
short: self.short.max(other.short),
}
}
pub fn weaker(self, other: Self) -> Self {
Self {
short: self.short.min(other.short),
}
}
pub fn raised(self, by: u64) -> Option<Self> {
let by = u32::try_from(by).ok()?;
self.short.checked_sub(by).map(|short| Self { short })
}
pub const fn lowered(self, by: u64) -> Self {
Self {
short: self.short.saturating_add(clipped(by)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Value<'tcx> {
Exact(Known<'tcx>),
Other(Known<'tcx>),
Within(Bounds<'tcx>),
Length(mir::Local),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Fact<'tcx> {
pub value: Option<Value<'tcx>>,
pub order: Ranks,
pub same: Option<mir::Local>,
pub extent: Option<Bounds<'tcx>>,
pub address: bool,
pub tag: Option<u128>,
pub paired: Option<mir::Local>,
pub over: Option<(mir::Local, u128)>,
pub spans: Option<mir::Local>,
}
impl<'tcx> Fact<'tcx> {
pub const fn blank() -> Self {
Self {
value: None,
order: Ranks::none_held(),
same: None,
extent: None,
address: false,
tag: None,
paired: None,
spans: None,
over: None,
}
}
pub const fn of(value: Value<'tcx>) -> Self {
let mut fact = Self::blank();
fact.value = Some(value);
fact
}
pub fn joined(self, other: Self) -> Self {
Self {
value: match (self.value, other.value) {
(Some(held), Some(arriving)) => held
.join(arriving)
.or_else(|| sized(self)?.join(sized(other)?)),
_ => None,
},
order: self.order.joined_with(
other.order,
Self::zeroed(self),
Self::zeroed(other),
),
same: (self.same == other.same).then_some(self.same).flatten(),
extent: match (self.extent, other.extent) {
(Some(held), Some(arriving)) => held.hull(arriving),
_ => None,
},
address: self.address && other.address,
tag: (self.tag == other.tag).then_some(self.tag).flatten(),
paired: (self.paired == other.paired)
.then_some(self.paired)
.flatten(),
spans: (self.spans == other.spans).then_some(self.spans).flatten(),
over: (self.over == other.over).then_some(self.over).flatten(),
}
}
fn zeroed(self) -> bool {
self.value
.and_then(Value::bounds)
.is_some_and(|span| !span.hi.is_signed() && span.hi.bits == 0)
}
pub fn widened(self, from: Self, stops: &Thresholds) -> Self {
Self {
value: match (self.value, from.value) {
(Some(now), Some(was)) if now == was => Some(now),
(Some(now), Some(was)) => now.widened(was, stops),
_ => None,
},
extent: match (self.extent, from.extent) {
(Some(now), Some(was)) if now == was => Some(now),
(Some(now), Some(was)) => Value::Within(now)
.widened(Value::Within(was), stops)
.and_then(Value::bounds),
_ => None,
},
order: self.order.widened(from.order),
..self
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Taught<'tcx> {
Value(Value<'tcx>),
Order(LenRel, mir::Local),
Alike(mir::Local),
Apart(mir::Local),
}
impl<'tcx> Value<'tcx> {
pub const fn exact(self) -> Option<Known<'tcx>> {
match self {
Self::Exact(known) => Some(known),
_ => None,
}
}
pub fn other_than(known: Known<'tcx>) -> Self {
if known.ty.is_bool() && known.bits <= 1 {
return Self::Exact(Known {
bits: 1 - known.bits,
..known
});
}
Self::Other(known)
}
pub fn leans_on(self, local: mir::Local) -> bool {
match self {
Self::Exact(_) | Self::Other(_) | Self::Within(_) => false,
Self::Length(other) => other == local,
}
}
pub const fn ty(self) -> Option<Ty<'tcx>> {
match self.anchor() {
Some(known) => Some(known.ty),
None => None,
}
}
pub const fn anchor(self) -> Option<Known<'tcx>> {
match self {
Self::Exact(known) | Self::Other(known) => Some(known),
Self::Within(bounds) => Some(bounds.lo),
Self::Length(_) => None,
}
}
pub const fn bounds(self) -> Option<Bounds<'tcx>> {
match self {
Self::Exact(known) => Some(Bounds {
lo: known,
hi: known,
}),
Self::Within(bounds) => Some(bounds),
_ => None,
}
}
fn admits(self, value: Known<'tcx>) -> Option<bool> {
use std::cmp::Ordering::Equal;
match self {
Self::Exact(known) => Some(known.order(value)? == Equal),
Self::Other(ruled_out) => Some(ruled_out.order(value)? != Equal),
Self::Within(bounds) => bounds.admits(value),
Self::Length(_) => None,
}
}
pub fn join(self, other: Self) -> Option<Self> {
if self == other {
return Some(self);
}
if let (Self::Other(ruled_out), rest) | (rest, Self::Other(ruled_out)) =
(self, other)
{
return (!rest.admits(ruled_out)?)
.then_some(Self::Other(ruled_out));
}
self.bounds()?.hull(other.bounds()?).map(Self::Within)
}
pub fn widened(self, from: Self, stops: &Thresholds) -> Option<Self> {
let (now, was) = (self.bounds()?, from.bounds()?);
let lo = if now.lo == was.lo {
now.lo
} else {
stops.under(now.lo)
};
let hi = if now.hi == was.hi {
now.hi
} else {
stops.over(now.hi)
};
Bounds::new(lo, hi).map(Self::Within)
}
pub fn refined(self, taught: Self) -> Self {
self.narrowed(taught).unwrap_or(self)
}
fn narrowed(self, taught: Self) -> Option<Self> {
match (self, taught) {
(Self::Exact(_) | Self::Length(_), _) => None,
(_, Self::Exact(known)) => {
self.admits(known)?.then_some(Self::Exact(known))
}
(Self::Other(ruled_out), Self::Within(bounds))
| (Self::Within(bounds), Self::Other(ruled_out)) => {
Self::without(bounds, ruled_out)
}
(Self::Within(held), Self::Within(bounds)) => {
held.overlap(bounds).map(Self::Within)
}
(Self::Other(_) | Self::Within(_), _) => None,
}
}
fn without(range: Bounds<'tcx>, ruled_out: Known<'tcx>) -> Option<Self> {
if !range.admits(ruled_out)? {
return Some(Self::Within(range));
}
if range.lo == ruled_out {
return Bounds::new(ruled_out.successor()?, range.hi)
.map(Self::Within);
}
if range.hi == ruled_out {
return Bounds::new(range.lo, ruled_out.predecessor()?)
.map(Self::Within);
}
None
}
}
pub const fn stepped(op: mir::BinOp, step: u128) -> Option<bool> {
use mir::BinOp::{Eq, Ge, Gt, Le, Lt, Ne};
let same = step == 0;
Some(match op {
Eq | Ge => same,
Ne | Lt => !same,
Le => true,
Gt => false,
_ => return None,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Against<'tcx> {
Constant(Known<'tcx>),
Length(mir::Local, LenRel),
Place(mir::Local, LenRel),
}
pub const fn mirrored(op: mir::BinOp) -> mir::BinOp {
use mir::BinOp::{Ge, Gt, Le, Lt};
match op {
Lt => Gt,
Le => Ge,
Gt => Lt,
Ge => Le,
other => other,
}
}
pub const fn negated(op: mir::BinOp) -> mir::BinOp {
use mir::BinOp::{Eq, Ge, Gt, Le, Lt, Ne};
match op {
Lt => Ge,
Le => Gt,
Gt => Le,
Ge => Lt,
Eq => Ne,
Ne => Eq,
other => other,
}
}
pub fn fact_of(
op: mir::BinOp,
against: Against<'_>,
holds: bool,
) -> Option<Taught<'_>> {
let op = if holds { op } else { negated(op) };
match against {
Against::Constant(k) => constant_fact(op, k).map(Taught::Value),
Against::Length(of, under) => length_fact(op, of, true, under),
Against::Place(of, under) => length_fact(op, of, false, under),
}
}
fn constant_fact(op: mir::BinOp, k: Known<'_>) -> Option<Value<'_>> {
use mir::BinOp::{Eq, Ge, Gt, Le, Lt, Ne};
let bounds = |lo, hi| Bounds::new(lo, hi).map(Value::Within);
match op {
Eq => Some(Value::Exact(k)),
Ne => Some(Value::other_than(k)),
Lt => bounds(k.type_min(), k.predecessor()?),
Le => bounds(k.type_min(), k),
Gt => bounds(k.successor()?, k.type_max()),
Ge => bounds(k, k.type_max()),
_ => None,
}
}
const fn length_fact<'tcx>(
op: mir::BinOp,
of: mir::Local,
measures: bool,
under: LenRel,
) -> Option<Taught<'tcx>> {
match op {
mir::BinOp::Lt => Some(Taught::Order(under.lowered(1), of)),
mir::BinOp::Le => Some(Taught::Order(under, of)),
mir::BinOp::Eq if measures => Some(Taught::Alike(of)),
mir::BinOp::Ne if measures => Some(Taught::Apart(of)),
_ => None,
}
}
pub fn compare<'tcx>(
op: mir::BinOp,
left: Fact<'tcx>,
right: Fact<'tcx>,
) -> Option<bool> {
if let Some(settled) = measured_against(op, left, right) {
return Some(settled);
}
if let Some(settled) = measured_against(mirrored(op), right, left) {
return Some(settled);
}
if let Some(settled) = alike(op, left, right) {
return Some(settled);
}
values_compare(op, sized(left)?, sized(right)?)
}
fn alike(op: mir::BinOp, left: Fact<'_>, right: Fact<'_>) -> Option<bool> {
use mir::BinOp::{Eq, Ge, Gt, Le, Lt, Ne};
let (Some(Value::Length(here)), Some(Value::Length(there))) =
(left.value, right.value)
else {
return None;
};
let together = (left.paired.is_some() && left.paired == right.paired)
|| (left.spans.is_some() && left.spans == right.spans);
if here != there
&& !together
&& left.paired != Some(there)
&& right.paired != Some(here)
{
return None;
}
match op {
Eq | Le | Ge => Some(true),
Ne | Lt | Gt => Some(false),
_ => None,
}
}
fn measured_against(
op: mir::BinOp,
left: Fact<'_>,
right: Fact<'_>,
) -> Option<bool> {
let named = |of| left.order.against(of);
let measured = match right.value {
Some(Value::Length(of)) => Some(of),
_ => None,
};
let rel = measured
.and_then(named)
.or_else(|| right.same.and_then(named))
.or_else(|| right.paired.and_then(named))?;
ordered_by(op, rel)
}
pub const fn ordered_by(op: mir::BinOp, rel: LenRel) -> Option<bool> {
use mir::BinOp::{Ge, Gt, Le, Lt};
match op {
Le => Some(true),
Gt => Some(false),
Lt if rel.is_below() => Some(true),
Ge if rel.is_below() => Some(false),
_ => None,
}
}
pub const fn sized(fact: Fact<'_>) -> Option<Value<'_>> {
match (fact.value, fact.extent) {
(Some(Value::Length(_)), Some(bounds)) => Some(Value::Within(bounds)),
(value, _) => value,
}
}
pub fn pinned(fact: Fact<'_>) -> Option<Known<'_>> {
match opened(sized(fact)?) {
Value::Exact(known) => Some(known),
_ => None,
}
}
fn opened(value: Value<'_>) -> Value<'_> {
match value {
Value::Other(ruled_out) => {
Bounds::new(ruled_out.type_min(), ruled_out.type_max())
.and_then(|whole| Value::without(whole, ruled_out))
.unwrap_or(value)
}
Value::Within(bounds) if bounds.lo == bounds.hi => {
Value::Exact(bounds.lo)
}
_ => value,
}
}
fn values_compare<'tcx>(
op: mir::BinOp,
left: Value<'tcx>,
right: Value<'tcx>,
) -> Option<bool> {
use mir::BinOp::{Eq, Ne};
match (opened(left), opened(right)) {
(Value::Exact(a), Value::Exact(b)) => {
range_compare(op, Bounds { lo: a, hi: a }, b)
}
(Value::Exact(known), Value::Other(ruled_out))
| (Value::Other(ruled_out), Value::Exact(known))
if known == ruled_out =>
{
matches!(op, Eq | Ne).then_some(op == Ne)
}
(Value::Within(range), Value::Exact(k)) => range_compare(op, range, k),
(Value::Exact(k), Value::Within(range)) => {
range_compare(mirrored(op), range, k)
}
(Value::Within(a), Value::Within(b)) => spans_compare(op, a, b),
_ => None,
}
}
pub fn spans_compare<'tcx>(
op: mir::BinOp,
a: Bounds<'tcx>,
b: Bounds<'tcx>,
) -> Option<bool> {
use std::cmp::Ordering::{Equal, Greater, Less};
use mir::BinOp::{Eq, Ge, Gt, Le, Lt, Ne};
match op {
Lt => match a.hi.order(b.lo)? {
Less => Some(true),
_ => (a.lo.order(b.hi)? != Less).then_some(false),
},
Le => match a.hi.order(b.lo)? {
Less | Equal => Some(true),
Greater => (a.lo.order(b.hi)? == Greater).then_some(false),
},
Gt => spans_compare(Lt, b, a),
Ge => spans_compare(Le, b, a),
Eq | Ne => {
let apart = a.hi.order(b.lo)? == Less || b.hi.order(a.lo)? == Less;
apart.then_some(op == Ne)
}
_ => None,
}
}
fn range_compare<'tcx>(
op: mir::BinOp,
range: Bounds<'tcx>,
k: Known<'tcx>,
) -> Option<bool> {
use mir::BinOp::{Eq, Ne};
if let Some(settled) = spans_compare(op, range, Bounds { lo: k, hi: k }) {
return Some(settled);
}
let single = range.lo == range.hi && range.lo == k;
(single && matches!(op, Eq | Ne)).then_some(op == Eq)
}