use rustc_middle::{
mir,
ty::{self, Ty},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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 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)]
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> {
use std::cmp::Ordering::Greater;
let lo = if self.lo.order(other.lo)? == Greater {
other.lo
} else {
self.lo
};
let hi = if self.hi.order(other.hi)? == Greater {
self.hi
} else {
other.hi
};
Self::new(lo, hi)
}
fn overlap(self, other: Self) -> Option<Self> {
use std::cmp::Ordering::Greater;
let lo = if self.lo.order(other.lo)? == Greater {
self.lo
} else {
other.lo
};
let hi = if self.hi.order(other.hi)? == Greater {
other.hi
} else {
self.hi
};
Self::new(lo, 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)]
pub enum LenRel {
Below,
AtMost,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Value<'tcx> {
Exact(Known<'tcx>),
Other(Known<'tcx>),
Within(Bounds<'tcx>),
Length(mir::Local),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Fact<'tcx> {
pub value: Option<Value<'tcx>>,
pub order: Option<(LenRel, mir::Local)>,
pub same: Option<mir::Local>,
pub extent: Option<Bounds<'tcx>>,
pub address: bool,
pub tag: Option<u128>,
}
impl<'tcx> Fact<'tcx> {
pub const fn of(value: Value<'tcx>) -> Self {
Self {
value: Some(value),
order: None,
same: None,
extent: None,
address: false,
tag: None,
}
}
pub fn joined(self, other: Self) -> Self {
Self {
value: match (self.value, other.value) {
(Some(held), Some(arriving)) => held.join(arriving),
_ => None,
},
order: (self.order == other.order).then_some(self.order).flatten(),
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(),
}
}
pub fn widened(self, from: Self) -> Self {
Self {
value: match (self.value, from.value) {
(Some(now), Some(was)) => now.widened(was),
_ => None,
},
extent: match (self.extent, from.extent) {
(Some(now), Some(was)) => Value::Within(now)
.widened(Value::Within(was))
.and_then(Value::bounds),
_ => None,
},
..self
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Taught<'tcx> {
Value(Value<'tcx>),
Order(LenRel, 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) -> Option<Self> {
let (now, was) = (self.bounds()?, from.bounds()?);
let lo = if now.lo == was.lo {
now.lo
} else {
now.lo.type_min()
};
let hi = if now.hi == was.hi {
now.hi
} else {
now.hi.type_max()
};
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
}
}
#[derive(Debug, Clone, Copy)]
pub enum Against<'tcx> {
Constant(Known<'tcx>),
Length(mir::Local),
}
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,
}
}
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) => length_fact(op, of),
}
}
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,
) -> Option<Taught<'tcx>> {
match op {
mir::BinOp::Lt => Some(Taught::Order(LenRel::Below, of)),
mir::BinOp::Le => Some(Taught::Order(LenRel::AtMost, 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);
}
values_compare(op, sized(left)?, sized(right)?)
}
fn measured_against(
op: mir::BinOp,
left: Fact<'_>,
right: Fact<'_>,
) -> Option<bool> {
use mir::BinOp::{Ge, Gt, Le, Lt};
let (Some((rel, of)), Some(Value::Length(len))) = (left.order, right.value)
else {
return None;
};
if of != len {
return None;
}
match (rel, op) {
(LenRel::Below, Lt | Le) | (LenRel::AtMost, Le) => Some(true),
(LenRel::Below, Ge | Gt) | (LenRel::AtMost, Gt) => Some(false),
_ => None,
}
}
const fn sized(fact: Fact<'_>) -> Option<Value<'_>> {
match (fact.value, fact.extent) {
(Some(Value::Length(_)), Some(bounds)) => Some(Value::Within(bounds)),
(value, _) => value,
}
}
fn values_compare<'tcx>(
op: mir::BinOp,
left: Value<'tcx>,
right: Value<'tcx>,
) -> Option<bool> {
use mir::BinOp::{Eq, Ne};
match (left, 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,
}
}
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)
}