use std::collections::VecDeque;
use rustc_middle::{
mir::{self, BasicBlock, BinOp, TerminatorKind, UnwindAction},
ty::{self, Instance, Ty, TyCtxt, TypeVisitableExt, TypingEnv},
};
pub struct Reach {
live: Vec<bool>,
settled: Vec<bool>,
}
impl Reach {
fn everything(blocks: usize) -> Self {
Self {
live: vec![true; blocks],
settled: vec![false; blocks],
}
}
pub fn is_live(&self, bb: BasicBlock) -> bool {
self.live.get(bb.as_usize()).copied().unwrap_or(true)
}
pub fn is_settled(&self, bb: BasicBlock) -> bool {
self.settled.get(bb.as_usize()).copied().unwrap_or(false)
}
}
pub fn reachable<'tcx>(
tcx: TyCtxt<'tcx>,
inst: Instance<'tcx>,
env: TypingEnv<'tcx>,
mir: &mir::Body<'tcx>,
) -> Reach {
Folder {
tcx,
inst,
env,
mir,
escaped: escaping(mir),
}
.run()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Known<'tcx> {
bits: u128,
ty: Ty<'tcx>,
width: u32,
}
impl Known<'_> {
fn is_signed(self) -> bool {
matches!(self.ty.kind(), ty::Int(_))
}
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
}
const fn truth(self) -> bool {
self.bits != 0
}
}
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)]
enum Value<'tcx> {
Exact(Known<'tcx>),
Other(Known<'tcx>),
}
impl<'tcx> Value<'tcx> {
const fn exact(self) -> Option<Known<'tcx>> {
match self {
Self::Exact(known) => Some(known),
Self::Other(_) => None,
}
}
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)
}
}
type State<'tcx> = Vec<Option<Value<'tcx>>>;
#[derive(Debug, Clone, Copy)]
struct Subject<'tcx> {
read: mir::Local,
ty: Ty<'tcx>,
width: u32,
compared: Option<Compared<'tcx>>,
}
#[derive(Debug, Clone, Copy)]
struct Compared<'tcx> {
equality: bool,
local: mir::Local,
against: Known<'tcx>,
}
struct Work<'tcx> {
entry: Vec<Option<State<'tcx>>>,
queued: Vec<bool>,
queue: VecDeque<BasicBlock>,
}
impl<'tcx> Work<'tcx> {
fn new(blocks: usize) -> Self {
Self {
entry: vec![None; blocks],
queued: vec![false; blocks],
queue: VecDeque::new(),
}
}
fn merge(&mut self, bb: BasicBlock, incoming: State<'tcx>) {
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) {
if held.is_some() && held != arriving {
*held = None;
changed = true;
}
}
if !changed {
return;
}
}
}
if let Some(queued) = self.queued.get_mut(bb.as_usize())
&& !*queued
{
*queued = true;
self.queue.push_back(bb);
}
}
fn is_drained(&self) -> bool {
self.queue.is_empty()
}
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))
}
}
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
}
struct Folder<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
inst: Instance<'tcx>,
env: TypingEnv<'tcx>,
mir: &'a mir::Body<'tcx>,
escaped: Vec<bool>,
}
impl<'tcx> Folder<'_, 'tcx> {
fn escapes(&self, local: mir::Local) -> bool {
self.escaped.get(local.as_usize()).copied().unwrap_or(true)
}
fn run(&self) -> Reach {
let blocks = self.mir.basic_blocks.len();
let locals = self.mir.local_decls.len();
let mut reach = Reach {
live: vec![false; blocks],
settled: vec![false; blocks],
};
let mut work = Work::new(blocks);
work.merge(mir::START_BLOCK, vec![None; locals]);
let bound = blocks
.saturating_mul(locals.saturating_add(1))
.saturating_add(blocks)
.saturating_add(1);
for _ in 0..bound {
if work.is_drained() {
return reach;
}
if let Some((bb, state)) = work.pop() {
self.visit(bb, state, &mut reach, &mut work);
}
}
Reach::everything(blocks)
}
fn visit(
&self,
bb: BasicBlock,
mut state: State<'tcx>,
reach: &mut Reach,
work: &mut Work<'tcx>,
) {
if let Some(slot) = reach.settled.get_mut(bb.as_usize()) {
*slot = false;
}
let block = &self.mir.basic_blocks[bb];
for stmt in &block.statements {
if !self.statement(&mut state, stmt) {
return;
}
}
if let Some(slot) = reach.live.get_mut(bb.as_usize()) {
*slot = true;
}
let Some(term) = &block.terminator else {
return;
};
self.terminator(bb, &term.kind, state, reach, work);
}
fn statement(
&self,
state: &mut State<'tcx>,
stmt: &mir::Statement<'tcx>,
) -> bool {
match &stmt.kind {
mir::StatementKind::Assign(pair) => {
let (place, rvalue) = &**pair;
match place.as_local() {
Some(local) if !self.escapes(local) => {
let value = self.rvalue(state, rvalue);
if let Some(slot) = state.get_mut(local.as_usize()) {
*slot = value;
}
}
_ => forget(state, place.local),
}
}
mir::StatementKind::SetDiscriminant { place, .. } => {
forget(state, place.local);
}
mir::StatementKind::StorageLive(local)
| mir::StatementKind::StorageDead(local) => {
forget(state, *local);
}
mir::StatementKind::Intrinsic(intrinsic) => {
if let mir::NonDivergingIntrinsic::Assume(operand) =
&**intrinsic
&& self
.exact(state, operand)
.is_some_and(|value| !value.truth())
{
return false;
}
}
mir::StatementKind::FakeRead(..)
| mir::StatementKind::PlaceMention(..)
| mir::StatementKind::AscribeUserType(..)
| mir::StatementKind::Coverage(..)
| mir::StatementKind::ConstEvalCounter
| mir::StatementKind::Nop
| mir::StatementKind::BackwardIncompatibleDropHint { .. } => {}
}
true
}
fn terminator(
&self,
bb: BasicBlock,
kind: &TerminatorKind<'tcx>,
state: State<'tcx>,
reach: &mut Reach,
work: &mut Work<'tcx>,
) {
match kind {
TerminatorKind::Goto { target } => work.merge(*target, state),
TerminatorKind::SwitchInt { discr, targets } => {
if let Some(value) = self.exact(&state, discr) {
work.merge(targets.target_for_value(value.bits), state);
return;
}
let subject = self.subject_of(bb, discr);
let mut taken = Vec::new();
for (value, target) in targets.iter() {
taken.push(value);
work.merge(
target,
refined(&state, subject, Some(value), true),
);
}
let rest = match taken.as_slice() {
[only] => Some(*only),
_ => None,
};
work.merge(
targets.otherwise(),
refined(&state, subject, rest, false),
);
}
TerminatorKind::Assert {
cond,
expected,
target,
unwind,
..
} => self.assertion(
bb,
(cond, *expected, *target),
*unwind,
state,
reach,
work,
),
TerminatorKind::Call {
destination,
target,
unwind,
..
} => {
let mut after = state.clone();
forget(&mut after, destination.local);
if let Some(target) = target {
work.merge(*target, after);
}
unwind_to(*unwind, &state, work);
}
TerminatorKind::Drop {
place,
target,
unwind,
drop,
..
} => {
let mut after = state.clone();
forget(&mut after, place.local);
work.merge(*target, after.clone());
if let Some(drop) = *drop {
work.merge(drop, after);
}
unwind_to(*unwind, &state, work);
}
_ => Self::onward(kind, state, work),
}
}
fn onward(
kind: &TerminatorKind<'tcx>,
state: State<'tcx>,
work: &mut Work<'tcx>,
) {
match kind {
TerminatorKind::FalseEdge {
real_target,
imaginary_target,
} => {
work.merge(*real_target, state.clone());
work.merge(*imaginary_target, state);
}
TerminatorKind::FalseUnwind {
real_target,
unwind,
} => {
work.merge(*real_target, state.clone());
unwind_to(*unwind, &state, work);
}
TerminatorKind::Return
| TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Unreachable
| TerminatorKind::CoroutineDrop
| TerminatorKind::TailCall { .. } => {}
_ => {
let blank = vec![None; state.len()];
for succ in kind.successors() {
work.merge(succ, blank.clone());
}
}
}
}
fn subject_of(
&self,
bb: BasicBlock,
discr: &mir::Operand<'tcx>,
) -> Option<Subject<'tcx>> {
let (mir::Operand::Copy(place) | mir::Operand::Move(place)) = discr
else {
return None;
};
let read = place.as_local()?;
if self.escapes(read) {
return None;
}
let ty =
self.monomorphize(discr.ty(&self.mir.local_decls, self.tcx))?;
Some(Subject {
read,
ty,
width: self.width(ty)?,
compared: ty
.is_bool()
.then(|| self.comparison_behind(bb, read))
.flatten(),
})
}
fn comparison_behind(
&self,
bb: BasicBlock,
result: mir::Local,
) -> Option<Compared<'tcx>> {
let block = &self.mir.basic_blocks[bb];
let at = block.statements.iter().rposition(|s| writes(s, result))?;
let mir::StatementKind::Assign(pair) = &block.statements[at].kind
else {
return None;
};
if pair.0.as_local() != Some(result) {
return None;
}
let mir::Rvalue::BinaryOp(op, operands) = &pair.1 else {
return None;
};
let equality = match op {
BinOp::Eq => true,
BinOp::Ne => false,
_ => return None,
};
let (local, against) = self.compared(&operands.0, &operands.1)?;
if self.escapes(local) {
return None;
}
let after = &block.statements[at.saturating_add(1)..];
if after.iter().any(|s| writes(s, local) || writes(s, result)) {
return None;
}
Some(Compared {
equality,
local,
against,
})
}
fn compared(
&self,
left: &mir::Operand<'tcx>,
right: &mir::Operand<'tcx>,
) -> Option<(mir::Local, Known<'tcx>)> {
let read = |operand: &mir::Operand<'tcx>| match operand {
mir::Operand::Copy(place) | mir::Operand::Move(place) => {
place.as_local()
}
_ => None,
};
let value = |operand: &mir::Operand<'tcx>| match operand {
mir::Operand::Constant(konst) => self.constant(konst),
_ => None,
};
match (read(left), value(right)) {
(Some(local), Some(against)) => Some((local, against)),
_ => Some((read(right)?, value(left)?)),
}
}
fn assertion(
&self,
bb: BasicBlock,
assert: (&mir::Operand<'tcx>, bool, BasicBlock),
unwind: UnwindAction,
state: State<'tcx>,
reach: &mut Reach,
work: &mut Work<'tcx>,
) {
let (cond, expected, target) = assert;
let proved = self.subject_of(bb, cond);
let held = Some(u128::from(expected));
match self.exact(&state, cond).map(Known::truth) {
Some(actual) if actual == expected => {
if let Some(slot) = reach.settled.get_mut(bb.as_usize()) {
*slot = true;
}
work.merge(target, state);
}
Some(_) => unwind_to(unwind, &state, work),
None => {
work.merge(target, refined(&state, proved, held, true));
unwind_to(unwind, &state, work);
}
}
}
fn rvalue(
&self,
state: &State<'tcx>,
rvalue: &mir::Rvalue<'tcx>,
) -> Option<Value<'tcx>> {
match rvalue {
mir::Rvalue::Use(operand, _) => self.operand(state, operand),
mir::Rvalue::Cast(mir::CastKind::IntToInt, operand, ty) => {
self.cast(state, operand, *ty)
}
mir::Rvalue::BinaryOp(op, pair) => {
let left = self.operand(state, &pair.0)?;
let right = self.operand(state, &pair.1)?;
self.binary(*op, left, right)
}
mir::Rvalue::UnaryOp(mir::UnOp::Not, operand) => {
let value = self.exact(state, operand)?;
let bits = if value.ty.is_bool() {
u128::from(!value.truth())
} else {
truncate(!value.bits, value.width)
};
Some(Value::Exact(Known { bits, ..value }))
}
_ => None,
}
}
fn exact(
&self,
state: &State<'tcx>,
operand: &mir::Operand<'tcx>,
) -> Option<Known<'tcx>> {
self.operand(state, operand)?.exact()
}
fn operand(
&self,
state: &State<'tcx>,
operand: &mir::Operand<'tcx>,
) -> Option<Value<'tcx>> {
match operand {
mir::Operand::Copy(place) | mir::Operand::Move(place) => {
let local = place.as_local()?;
*state.get(local.as_usize())?
}
mir::Operand::Constant(konst) => {
self.constant(konst).map(Value::Exact)
}
mir::Operand::RuntimeChecks(check) => {
self.boolean(check.value(self.tcx.sess)).map(Value::Exact)
}
}
}
fn constant(&self, konst: &mir::ConstOperand<'tcx>) -> Option<Known<'tcx>> {
if self.inst.args.has_param() {
return None;
}
let konst = self
.inst
.try_instantiate_mir_and_normalize_erasing_regions(
self.tcx,
self.env,
ty::EarlyBinder::bind(self.tcx, konst.const_),
)
.ok()?;
let ty = konst.ty();
let width = self.width(ty)?;
let bits = konst.try_eval_bits(self.tcx, self.env)?;
Some(Known {
bits: truncate(bits, width),
ty,
width,
})
}
fn cast(
&self,
state: &State<'tcx>,
operand: &mir::Operand<'tcx>,
ty: Ty<'tcx>,
) -> Option<Value<'tcx>> {
let value = self.operand(state, operand)?;
let ty = self.monomorphize(ty)?;
let width = self.width(ty)?;
match value {
Value::Exact(known) => {
Some(Value::Exact(Self::converted(known, ty, width)))
}
Value::Other(known) if width >= known.width => {
Some(Value::other_than(Self::converted(known, ty, width)))
}
Value::Other(_) => None,
}
}
fn converted(value: Known<'tcx>, ty: Ty<'tcx>, width: u32) -> Known<'tcx> {
let extended = if value.is_signed() {
value.as_signed().cast_unsigned()
} else {
value.bits
};
Known {
bits: truncate(extended, width),
ty,
width,
}
}
fn binary(
&self,
op: BinOp,
left: Value<'tcx>,
right: Value<'tcx>,
) -> Option<Value<'tcx>> {
match (left, right) {
(Value::Exact(left), Value::Exact(right)) => {
self.settled(op, left, right).map(Value::Exact)
}
(Value::Exact(known), Value::Other(ruled_out))
| (Value::Other(ruled_out), Value::Exact(known))
if known == ruled_out =>
{
match op {
BinOp::Eq => self.boolean(false).map(Value::Exact),
BinOp::Ne => self.boolean(true).map(Value::Exact),
_ => None,
}
}
_ => None,
}
}
fn settled(
&self,
op: BinOp,
left: Known<'tcx>,
right: Known<'tcx>,
) -> Option<Known<'tcx>> {
if left.ty != right.ty || left.width != right.width {
return None;
}
if matches!(op, BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor) {
let bits = match op {
BinOp::BitAnd => left.bits & right.bits,
BinOp::BitOr => left.bits | right.bits,
_ => left.bits ^ right.bits,
};
return Some(Known {
bits: truncate(bits, left.width),
..left
});
}
let order = if left.is_signed() {
left.as_signed().cmp(&right.as_signed())
} else {
left.bits.cmp(&right.bits)
};
let verdict = match op {
BinOp::Eq => order.is_eq(),
BinOp::Ne => order.is_ne(),
BinOp::Lt => order.is_lt(),
BinOp::Le => order.is_le(),
BinOp::Gt => order.is_gt(),
BinOp::Ge => order.is_ge(),
_ => return None,
};
self.boolean(verdict)
}
fn boolean(&self, value: bool) -> Option<Known<'tcx>> {
let ty = self.tcx.types.bool;
Some(Known {
bits: u128::from(value),
ty,
width: self.width(ty)?,
})
}
fn width(&self, ty: Ty<'tcx>) -> Option<u32> {
if !matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_))
{
return None;
}
let layout = self.tcx.layout_of(self.env.as_query_input(ty)).ok()?;
u32::try_from(layout.size.bits()).ok()
}
fn monomorphize(&self, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
self.inst
.try_instantiate_mir_and_normalize_erasing_regions(
self.tcx,
self.env,
ty::EarlyBinder::bind(self.tcx, ty),
)
.ok()
}
}
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, settle(read, matched));
if let Some(compared) = subject.compared {
let held = if matched { value == 1 } else { value == 0 };
learn(
&mut next,
compared.local,
settle(compared.against, held == compared.equality),
);
}
next
}
fn settle(known: Known<'_>, holds: bool) -> Value<'_> {
if holds {
Value::Exact(known)
} else {
Value::other_than(known)
}
}
fn learn<'tcx>(state: &mut State<'tcx>, local: mir::Local, value: Value<'tcx>) {
if let Some(slot) = state.get_mut(local.as_usize())
&& slot.is_none()
{
*slot = Some(value);
}
}
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,
}
}
fn forget(state: &mut State<'_>, local: mir::Local) {
if let Some(slot) = state.get_mut(local.as_usize()) {
*slot = None;
}
}
fn unwind_to<'tcx>(
unwind: UnwindAction,
state: &State<'tcx>,
work: &mut Work<'tcx>,
) {
if let UnwindAction::Cleanup(target) = unwind {
work.merge(target, state.clone());
}
}