use std::collections::VecDeque;
use rustc_middle::{
mir::{self, BasicBlock, BinOp, UnwindAction},
ty::Ty,
};
use crate::value::{
self, Against, Bounds, Fact, Known, Taught, Value, truncate,
};
pub const PRECISE: u32 = 2;
pub const STEPS: usize = 12;
pub type State<'tcx> = Vec<Fact<'tcx>>;
#[derive(Debug, Clone, Copy)]
pub struct Subject<'tcx> {
pub read: mir::Local,
pub ty: Ty<'tcx>,
pub width: u32,
pub compared: Option<Compared<'tcx>>,
}
#[derive(Debug, Clone, Copy)]
pub struct Compared<'tcx> {
pub op: BinOp,
pub local: mir::Local,
pub against: Against<'tcx>,
pub source: Option<mir::Local>,
}
pub struct Work<'tcx> {
entry: Vec<Option<State<'tcx>>>,
queued: Vec<bool>,
changes: Vec<u32>,
queue: VecDeque<BasicBlock>,
}
impl<'tcx> Work<'tcx> {
pub fn new(blocks: usize) -> Self {
Self {
entry: vec![None; blocks],
queued: vec![false; blocks],
changes: vec![0; blocks],
queue: VecDeque::new(),
}
}
pub fn merge(&mut self, bb: BasicBlock, incoming: State<'tcx>) {
let widen = self
.changes
.get(bb.as_usize())
.is_some_and(|seen| *seen >= PRECISE);
let Some(slot) = self.entry.get_mut(bb.as_usize()) else {
return;
};
match slot {
None => *slot = Some(incoming),
Some(existing) => {
let mut changed = false;
for (held, arriving) in existing.iter_mut().zip(&incoming) {
let mut next = held.joined(*arriving);
if next == *held {
continue;
}
if widen {
next = next.widened(*held);
}
*held = next;
changed = true;
}
if !changed {
return;
}
}
}
if let Some(seen) = self.changes.get_mut(bb.as_usize()) {
*seen = seen.saturating_add(1);
}
if let Some(queued) = self.queued.get_mut(bb.as_usize())
&& !*queued
{
*queued = true;
self.queue.push_back(bb);
}
}
pub fn is_drained(&self) -> bool {
self.queue.is_empty()
}
pub fn pop(&mut self) -> Option<(BasicBlock, State<'tcx>)> {
let bb = self.queue.pop_front()?;
if let Some(queued) = self.queued.get_mut(bb.as_usize()) {
*queued = false;
}
let state = self.entry.get(bb.as_usize())?.clone()?;
Some((bb, state))
}
}
pub fn escaping(mir: &mir::Body<'_>) -> Vec<bool> {
let mut escaped = vec![false; mir.local_decls.len()];
for block in mir.basic_blocks.iter() {
for stmt in &block.statements {
let mir::StatementKind::Assign(pair) = &stmt.kind else {
continue;
};
let (mir::Rvalue::Ref(_, _, place)
| mir::Rvalue::RawPtr(_, place)
| mir::Rvalue::Reborrow(_, _, place)) = &pair.1
else {
continue;
};
if let Some(slot) = escaped.get_mut(place.local.as_usize()) {
*slot = true;
}
}
}
escaped
}
pub fn root_of(state: &State<'_>, local: mir::Local) -> mir::Local {
state
.get(local.as_usize())
.and_then(|fact| fact.same)
.unwrap_or(local)
}
pub fn refined<'tcx>(
state: &State<'tcx>,
subject: Option<Subject<'tcx>>,
value: Option<u128>,
matched: bool,
) -> State<'tcx> {
let mut next = state.clone();
let (Some(subject), Some(value)) = (subject, value) else {
return next;
};
let read = Known {
bits: truncate(value, subject.width),
ty: subject.ty,
width: subject.width,
};
learn(
&mut next,
subject.read,
Taught::Value(settle(read, matched)),
);
if let Some(compared) = subject.compared {
let truth = if matched { value == 1 } else { value == 0 };
if let Some(fact) = value::fact_of(compared.op, compared.against, truth)
{
learn(&mut next, compared.local, fact);
}
}
next
}
pub fn settle(known: Known<'_>, holds: bool) -> Value<'_> {
if holds {
Value::Exact(known)
} else {
Value::other_than(known)
}
}
pub fn learn<'tcx>(
state: &mut State<'tcx>,
local: mir::Local,
taught: Taught<'tcx>,
) {
if let Taught::Value(value) = taught
&& let Some(Value::Length(of)) =
state.get(local.as_usize()).and_then(|slot| slot.value)
{
stretch(state, of, value);
return;
}
let Some(slot) = state.get_mut(local.as_usize()) else {
return;
};
match taught {
Taught::Value(value) => {
slot.value =
Some(slot.value.map_or(value, |held| held.refined(value)));
}
Taught::Order(rel, of) => {
if slot.order.is_none() {
slot.order = Some((rel, of));
}
}
}
}
pub fn stretch<'tcx>(
state: &mut State<'tcx>,
of: mir::Local,
taught: Value<'tcx>,
) {
let Some(slot) = state.get_mut(of.as_usize()) else {
return;
};
let whole = taught
.anchor()
.and_then(|end| Bounds::new(end.type_min(), end.type_max()));
let Some(held) = slot.extent.or(whole) else {
return;
};
slot.extent = Value::Within(held).refined(taught).bounds();
}
pub fn writes(stmt: &mir::Statement<'_>, local: mir::Local) -> bool {
match &stmt.kind {
mir::StatementKind::Assign(pair) => pair.0.local == local,
mir::StatementKind::SetDiscriminant { place, .. } => {
place.local == local
}
mir::StatementKind::StorageLive(other)
| mir::StatementKind::StorageDead(other) => *other == local,
mir::StatementKind::FakeRead(..)
| mir::StatementKind::PlaceMention(..)
| mir::StatementKind::AscribeUserType(..)
| mir::StatementKind::Coverage(..)
| mir::StatementKind::ConstEvalCounter
| mir::StatementKind::Nop
| mir::StatementKind::BackwardIncompatibleDropHint { .. } => false,
mir::StatementKind::Intrinsic(_) => true,
}
}
pub fn forget(state: &mut State<'_>, local: mir::Local) {
for slot in state.iter_mut() {
if slot.value.is_some_and(|value| value.leans_on(local)) {
slot.value = None;
}
if slot.order.is_some_and(|(_, of)| of == local) {
slot.order = None;
}
if slot.same == Some(local) {
slot.same = None;
}
}
if let Some(slot) = state.get_mut(local.as_usize()) {
*slot = Fact::default();
}
}
pub fn unwind_to<'tcx>(
unwind: UnwindAction,
state: &State<'tcx>,
work: &mut Work<'tcx>,
) {
if let UnwindAction::Cleanup(target) = unwind {
work.merge(target, state.clone());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Step {
Deref,
Field(u32),
Variant(u32),
}
const REACH: usize = 3;
const TRACKED: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Path {
pub base: mir::Local,
steps: [Option<Step>; REACH],
}
impl Path {
fn of(place: &mir::Place<'_>) -> Option<Self> {
if place.projection.is_empty() {
return None;
}
let mut steps = [None; REACH];
for (slot, element) in steps.iter_mut().zip(place.projection) {
*slot = Some(match element {
mir::ProjectionElem::Deref => Step::Deref,
mir::ProjectionElem::Field(field, _) => {
Step::Field(field.as_u32())
}
mir::ProjectionElem::Downcast(_, variant) => {
Step::Variant(variant.as_u32())
}
_ => return None,
});
}
if place.projection.len() > REACH {
return None;
}
Some(Self {
base: place.local,
steps,
})
}
pub fn behind_pointer(self) -> bool {
self.steps.iter().flatten().any(|step| *step == Step::Deref)
}
}
pub struct Places {
paths: Vec<Path>,
first: usize,
}
impl Places {
pub fn of(mir: &mir::Body<'_>) -> Self {
let mut collect = Collect { found: Vec::new() };
mir::visit::Visitor::visit_body(&mut collect, mir);
Self {
paths: collect.found,
first: mir.local_decls.len(),
}
}
pub const fn len(&self) -> usize {
self.paths.len()
}
pub fn slot(&self, place: &mir::Place<'_>) -> Option<mir::Local> {
let path = Path::of(place)?;
let at = self.paths.iter().position(|held| *held == path)?;
Some(mir::Local::from_usize(self.first.saturating_add(at)))
}
pub fn path(&self, slot: mir::Local) -> Option<Path> {
self.paths
.get(slot.as_usize().checked_sub(self.first)?)
.copied()
}
pub fn each(&self) -> impl Iterator<Item = (mir::Local, Path)> + '_ {
self.paths.iter().enumerate().map(|(at, path)| {
(mir::Local::from_usize(self.first.saturating_add(at)), *path)
})
}
}
struct Collect {
found: Vec<Path>,
}
impl<'tcx> mir::visit::Visitor<'tcx> for Collect {
fn visit_place(
&mut self,
place: &mir::Place<'tcx>,
_: mir::visit::PlaceContext,
_: mir::Location,
) {
if self.found.len() >= TRACKED {
return;
}
let Some(path) = Path::of(place) else {
return;
};
if !self.found.contains(&path) {
self.found.push(path);
}
}
}
pub fn sweep_base(state: &mut State<'_>, places: &Places, base: mir::Local) {
for (slot, path) in places.each() {
if path.base == base {
forget(state, slot);
}
}
}
pub fn sweep_aliased(state: &mut State<'_>, places: &Places, escaped: &[bool]) {
for (slot, path) in places.each() {
if path.behind_pointer()
|| escaped.get(path.base.as_usize()).copied().unwrap_or(true)
{
forget(state, slot);
}
}
}