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 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)
}
}
#[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>,
}
impl<'tcx> Fact<'tcx> {
pub const fn of(value: Value<'tcx>) -> Self {
Self {
value: Some(value),
order: None,
same: None,
}
}
pub fn agreed(self, other: Self) -> Self {
Self {
value: (self.value == other.value).then_some(self.value).flatten(),
order: (self.order == other.order).then_some(self.order).flatten(),
same: (self.same == other.same).then_some(self.same).flatten(),
}
}
}
#[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,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Against<'tcx> {
Constant(Known<'tcx>),
Length(mir::Local),
}
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 const fn from_left(op: mir::BinOp) -> mir::BinOp {
mirrored(op)
}
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> {
use mir::BinOp::{Ge, Gt, Le, Lt};
if let (Some((rel, of)), Some(Value::Length(len))) =
(left.order, right.value)
&& of == len
{
match (rel, op) {
(LenRel::Below, Lt | Le) | (LenRel::AtMost, Le) => {
return Some(true);
}
(LenRel::Below, Ge | Gt) | (LenRel::AtMost, Gt) => {
return Some(false);
}
_ => {}
}
}
if let (Some(Value::Length(_)), Some(_)) = (left.value, right.order) {
return compare(mirrored(op), right, left);
}
values_compare(op, left.value?, right.value?)
}
fn values_compare<'tcx>(
op: mir::BinOp,
left: Value<'tcx>,
right: Value<'tcx>,
) -> Option<bool> {
use std::cmp::Ordering;
use mir::BinOp::{Eq, Ge, Gt, Le, Lt, Ne};
match (left, right) {
(Value::Exact(a), Value::Exact(b)) => {
let order = a.order(b)?;
Some(match op {
Eq => order == Ordering::Equal,
Ne => order != Ordering::Equal,
Lt => order == Ordering::Less,
Le => order != Ordering::Greater,
Gt => order == Ordering::Greater,
Ge => order != Ordering::Less,
_ => return None,
})
}
(Value::Exact(known), Value::Other(ruled_out))
| (Value::Other(ruled_out), Value::Exact(known))
if known == ruled_out =>
{
match op {
Eq => Some(false),
Ne => Some(true),
_ => None,
}
}
(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)) => match op {
Lt => match a.hi.order(b.lo)? {
Ordering::Less => Some(true),
_ => (a.lo.order(b.hi)? != Ordering::Less).then_some(false),
},
Gt => values_compare(Lt, Value::Within(b), Value::Within(a)),
Le => match a.hi.order(b.lo)? {
Ordering::Less | Ordering::Equal => Some(true),
Ordering::Greater => {
(a.lo.order(b.hi)? == Ordering::Greater).then_some(false)
}
},
Ge => values_compare(Le, Value::Within(b), Value::Within(a)),
Eq | Ne => {
let apart = a.hi.order(b.lo)? == Ordering::Less
|| b.hi.order(a.lo)? == Ordering::Less;
if !apart {
return None;
}
Some(op == Ne)
}
_ => None,
},
_ => None,
}
}
fn range_compare<'tcx>(
op: mir::BinOp,
range: Bounds<'tcx>,
k: Known<'tcx>,
) -> Option<bool> {
use std::cmp::Ordering;
use mir::BinOp::{Eq, Ge, Gt, Le, Lt, Ne};
match op {
Lt => match range.hi.order(k)? {
Ordering::Less => Some(true),
_ => (range.lo.order(k)? != Ordering::Less).then_some(false),
},
Le => match range.hi.order(k)? {
Ordering::Less | Ordering::Equal => Some(true),
Ordering::Greater => {
(range.lo.order(k)? == Ordering::Greater).then_some(false)
}
},
Gt => match range.lo.order(k)? {
Ordering::Greater => Some(true),
_ => (range.hi.order(k)? != Ordering::Greater).then_some(false),
},
Ge => match range.lo.order(k)? {
Ordering::Greater | Ordering::Equal => Some(true),
Ordering::Less => {
(range.hi.order(k)? == Ordering::Less).then_some(false)
}
},
Eq => {
if range.admits(k)? {
(range.lo == range.hi && range.lo == k).then_some(true)
} else {
Some(false)
}
}
Ne => {
if range.admits(k)? {
(range.lo == range.hi && range.lo == k).then_some(false)
} else {
Some(true)
}
}
_ => None,
}
}