use rustc_middle::{
mir::{self, TerminatorKind},
ty::{self, Instance, Ty, TypeVisitableExt, TypingEnv},
};
use crate::{
fold::{Folder, Reach},
sinks::SinkTable,
state::{State, root_of},
value::{Bounds, Fact, Known, Value},
};
pub const DEPTH: u32 = 3;
pub const BUDGET: u32 = 4096;
#[derive(Debug, Clone, Copy)]
pub enum Returns<'tcx> {
Never,
Held(Value<'tcx>),
Anything,
}
impl<'tcx> Returns<'tcx> {
pub fn met(self, value: Option<Value<'tcx>>) -> Self {
match (self, value) {
(Self::Anything, _) | (_, None) => Self::Anything,
(Self::Never, Some(found)) => Self::Held(found),
(Self::Held(held), Some(found)) => {
held.join(found).map_or(Self::Anything, Self::Held)
}
}
}
pub const fn claim(self) -> Option<Value<'tcx>> {
match self {
Self::Held(value) => Some(value),
Self::Never | Self::Anything => None,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Found<'tcx> {
pub value: Option<Value<'tcx>>,
pub quiet: bool,
}
struct Carried<'tcx> {
slot: Option<mir::Local>,
alike: bool,
fact: Fact<'tcx>,
}
pub const fn portable(value: Value<'_>) -> Option<Value<'_>> {
match value {
Value::Exact(_) | Value::Other(_) | Value::Within(_) => Some(value),
Value::Length(_) => None,
}
}
impl<'tcx> Folder<'_, 'tcx> {
pub fn inspect(
&mut self,
state: &State<'tcx>,
func: &mir::Operand<'tcx>,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
) -> Found<'tcx> {
let Some(ty) =
self.monomorphize(func.ty(&self.mir.local_decls, self.tcx))
else {
return Found::default();
};
if let Some(value) = self.contracted(state, ty, args, destination) {
return Found {
value: Some(value),
quiet: false,
};
}
if !self.worth_folding(state, args, destination) {
return Found::default();
}
self.folded(state, ty, args)
}
fn worth_folding(
&self,
state: &State<'tcx>,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
) -> bool {
let known = |arg: &rustc_span::Spanned<mir::Operand<'tcx>>| {
let fact = self.fact(state, &arg.node);
fact.tag.is_some() || fact.value.and_then(portable).is_some()
};
if args.iter().any(known) {
return true;
}
let result = destination.ty(&self.mir.local_decls, self.tcx).ty;
self.monomorphize(result)
.and_then(|ty| self.width(ty))
.is_some()
}
fn contracted(
&self,
state: &State<'tcx>,
func: Ty<'tcx>,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
) -> Option<Value<'tcx>> {
let ty::FnDef(did, _) = *func.kind() else {
return None;
};
if self.tcx.crate_name(did.krate).as_str() != "core" {
return None;
}
match SinkTable::def_path(self.tcx, did).as_str() {
"slice::len" => {
let receiver = args.first()?;
let (mir::Operand::Copy(place) | mir::Operand::Move(place)) =
&receiver.node
else {
return None;
};
Some(Value::Length(root_of(state, self.slot_of(place)?)))
}
"cmp::Ord::max" => self.picked(state, true, args),
"cmp::Ord::min" => self.picked(state, false, args),
"num::nonzero::get" => {
let receiver = args.first()?;
let source = self.monomorphize(
receiver.node.ty(&self.mir.local_decls, self.tcx),
)?;
if !self.is_nonzero(source) {
return None;
}
self.apart_from_zero(
destination.ty(&self.mir.local_decls, self.tcx).ty,
)
}
_ => None,
}
}
fn folded(
&mut self,
state: &State<'tcx>,
func: Ty<'tcx>,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
) -> Found<'tcx> {
let Some(callee) = self.target(func) else {
return Found::default();
};
let mir = self.tcx.instance_mir(callee.def);
if mir.arg_count != args.len() {
return Found::default();
}
let mut folder = Folder::new(
self.tcx,
callee,
TypingEnv::fully_monomorphized(),
mir,
self.depth.saturating_add(1),
self.budget,
);
let entry = self.carried(state, &folder, args);
let reach = folder.run(entry);
self.budget = folder.budget;
Found {
value: folder.returns.claim(),
quiet: Self::silent(mir, &reach),
}
}
fn target(&self, func: Ty<'tcx>) -> Option<Instance<'tcx>> {
if self.depth >= DEPTH || self.budget == 0 {
return None;
}
let ty::FnDef(did, generics) = *func.kind() else {
return None;
};
let generics = generics.no_bound_vars()?;
if generics.has_param() {
return None;
}
let callee = Instance::try_resolve(self.tcx, self.env, did, generics)
.ok()
.flatten()?;
if callee == self.inst {
return None;
}
let ty::InstanceKind::Item(def) = callee.def else {
return None;
};
self.tcx.is_mir_available(def).then_some(callee)
}
fn silent(mir: &mir::Body<'tcx>, reach: &Reach) -> bool {
for (bb, data) in mir.basic_blocks.iter_enumerated() {
if !reach.is_live(bb) {
continue;
}
let Some(term) = &data.terminator else {
return false;
};
let silent = match &term.kind {
TerminatorKind::Assert { .. } => reach.is_settled(bb),
TerminatorKind::Goto { .. }
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Return
| TerminatorKind::Unreachable
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. } => true,
_ => false,
};
if !silent {
return false;
}
}
true
}
pub fn carried(
&self,
state: &State<'tcx>,
callee: &Folder<'_, 'tcx>,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
) -> State<'tcx> {
let mut entry = callee.blank();
let carried: Vec<Carried<'tcx>> = args
.iter()
.enumerate()
.map(|(index, arg)| self.about(state, callee, index, arg))
.collect();
for (index, held) in carried.iter().enumerate() {
let local = mir::Local::from_usize(index.saturating_add(1));
if callee.escapes(local) || !held.alike {
continue;
}
let order = held.fact.order.and_then(|(rel, of)| {
let at = carried
.iter()
.position(|other| other.alike && other.slot == Some(of))?;
Some((rel, mir::Local::from_usize(at.saturating_add(1))))
});
let fact = Fact { order, ..held.fact };
if fact == Fact::default() {
continue;
}
if let Some(slot) = entry.get_mut(local.as_usize()) {
*slot = fact;
}
}
entry
}
fn about(
&self,
state: &State<'tcx>,
callee: &Folder<'_, 'tcx>,
index: usize,
arg: &rustc_span::Spanned<mir::Operand<'tcx>>,
) -> Carried<'tcx> {
let local = mir::Local::from_usize(index.saturating_add(1));
let slot = match &arg.node {
mir::Operand::Copy(place) | mir::Operand::Move(place) => {
self.slot_of(place).map(|slot| root_of(state, slot))
}
_ => None,
};
let param = callee
.mir
.local_decls
.get(local)
.and_then(|decl| callee.monomorphize(decl.ty));
let passed =
self.monomorphize(arg.node.ty(&self.mir.local_decls, self.tcx));
let alike = param.is_some() && param == passed;
let held = self.fact(state, &arg.node);
let fact = Fact {
value: held.value.and_then(portable).filter(|value| {
param == value.ty()
}),
same: None,
..held
};
Carried { slot, alike, fact }
}
fn picked(
&self,
state: &State<'tcx>,
larger: bool,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
) -> Option<Value<'tcx>> {
let [left, right] = args else {
return None;
};
let left = self.spread(state, &left.node)?;
let right = self.spread(state, &right.node)?;
let pick = |a: Known<'tcx>, b: Known<'tcx>| {
let above = a.order(b)? == std::cmp::Ordering::Greater;
Some(if above == larger { a } else { b })
};
Bounds::new(pick(left.lo, right.lo)?, pick(left.hi, right.hi)?)
.map(Value::Within)
}
fn spread(
&self,
state: &State<'tcx>,
operand: &mir::Operand<'tcx>,
) -> Option<Bounds<'tcx>> {
let ty =
self.monomorphize(operand.ty(&self.mir.local_decls, self.tcx))?;
let whole = self.whole(ty)?;
let Some(value) = self.fact(state, operand).value else {
return Some(whole);
};
if value.ty() != Some(ty) {
return Some(whole);
}
Some(
Value::Within(whole)
.refined(value)
.bounds()
.unwrap_or(whole),
)
}
pub fn whole(&self, ty: Ty<'tcx>) -> Option<Bounds<'tcx>> {
if !matches!(ty.kind(), ty::Int(_) | ty::Uint(_)) {
return None;
}
let seed = Known {
bits: 0,
ty,
width: self.width(ty)?,
};
Bounds::new(seed.type_min(), seed.type_max())
}
pub fn is_nonzero(&self, ty: Ty<'tcx>) -> bool {
let ty::Adt(def, _) = ty.kind() else {
return false;
};
self.tcx.get_diagnostic_item(rustc_span::sym::NonZero)
== Some(def.did())
}
pub fn apart_from_zero(&self, ty: Ty<'tcx>) -> Option<Value<'tcx>> {
let ty = self.monomorphize(ty)?;
let width = self.width(ty)?;
Some(Value::other_than(Known { bits: 0, ty, width }))
}
}