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, LenRel, Ranks, Value},
};
pub const DEPTH: u32 = 3;
pub const BUDGET: u32 = 4096;
#[derive(Debug, Clone, Copy, Default)]
pub struct Returns<'tcx> {
held: Fact<'tcx>,
walked: bool,
partial: bool,
}
impl<'tcx> Returns<'tcx> {
pub fn met(self, left: Fact<'tcx>) -> Self {
Self {
held: if self.walked {
self.held.joined(left)
} else {
left
},
walked: true,
..self
}
}
pub const fn is_new(self) -> bool {
!self.walked
}
pub const fn given_up() -> Self {
Self {
held: Fact::blank(),
walked: false,
partial: true,
}
}
pub fn claim(self) -> Fact<'tcx> {
if self.partial || !self.walked {
return Fact::default();
}
self.held
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Found<'tcx> {
pub left: Fact<'tcx>,
pub quiet: bool,
}
struct Carried<'tcx> {
slot: Option<mir::Local>,
base: 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>,
after: &mut State<'tcx>,
) -> Found<'tcx> {
let Some(ty) =
self.monomorphize(func.ty(&self.mir.local_decls, self.tcx))
else {
return Found::default();
};
if let Some(left) = self.contracted(state, ty, args, destination) {
return Found { left, quiet: false };
}
self.folded(state, ty, args, destination, after)
}
fn contracted(
&self,
state: &State<'tcx>,
func: Ty<'tcx>,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
) -> Option<Fact<'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(Folder::measuring(Value::Length(root_of(
state,
self.slot_of(place)?,
))))
}
"cmp::Ord::max" => self.chosen(state, true, args),
"cmp::Ord::min" => self.chosen(state, false, args),
"intrinsics::ctlz"
| "intrinsics::ctlz_nonzero"
| "intrinsics::cttz"
| "intrinsics::cttz_nonzero"
| "intrinsics::ctpop" => {
self.counted(args, destination).map(Fact::of)
}
"num::nonzero::new" => self.wrapped(state, args, destination),
"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,
)
.map(Fact::of)
}
_ => None,
}
}
fn folded(
&mut self,
state: &State<'tcx>,
func: Ty<'tcx>,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
after: &mut State<'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;
self.handed_back(&folder, destination, after);
Found {
left: 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(&self, 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::Call { .. } => reach.is_quiet(bb),
TerminatorKind::Drop { place, .. } => {
let ty = place.ty(&mir.local_decls, self.tcx).ty;
!ty.needs_drop(self.tcx, TypingEnv::fully_monomorphized())
}
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 named = |of: mir::Local| {
let at = carried
.iter()
.position(|other| other.alike && other.slot == Some(of))?;
Some(mir::Local::from_usize(at.saturating_add(1)))
};
let mut order = Ranks::none_held();
for (rel, of) in held.fact.order.each() {
if let Some(there) = named(of) {
order.add(rel, there);
}
}
let paired = held.fact.paired.and_then(named);
let spans = held.fact.spans.and_then(named);
let over = held
.fact
.over
.and_then(|(of, step)| Some((named(of)?, step)));
let fact = Fact {
order,
paired,
over,
spans,
..held.fact
};
if fact == Fact::default() {
continue;
}
if let Some(slot) = entry.get_mut(local.as_usize()) {
*slot = fact;
}
}
Self::cut_alike(&carried, &mut entry);
self.handed_over(state, callee, &carried, &mut entry);
entry
}
fn cut_alike(carried: &[Carried<'tcx>], entry: &mut State<'tcx>) {
for (index, held) in carried.iter().enumerate() {
let Some(cut) = held.fact.spans.filter(|_| held.alike) else {
continue;
};
let Some(other) = carried.iter().enumerate().position(|(at, p)| {
at != index && p.alike && p.fact.spans == Some(cut)
}) else {
continue;
};
let local = mir::Local::from_usize(index.saturating_add(1));
let peer = mir::Local::from_usize(other.saturating_add(1));
if let Some(slot) = entry.get_mut(local.as_usize()) {
slot.paired = Some(peer);
}
}
}
fn handed_back(
&self,
callee: &Folder<'_, 'tcx>,
destination: mir::Place<'tcx>,
after: &mut State<'tcx>,
) {
if !destination.projection.is_empty() {
return;
}
for (path, fact) in &callee.returned {
if *fact == Fact::default() || !path.portable() {
continue;
}
let Some(slot) = self.places.at(path.rebased(destination.local))
else {
continue;
};
if self.escapes(slot) {
continue;
}
if let Some(cell) = after.get_mut(slot.as_usize()) {
*cell = *fact;
}
}
}
fn handed_over(
&self,
state: &State<'tcx>,
callee: &Folder<'_, 'tcx>,
carried: &[Carried<'tcx>],
entry: &mut State<'tcx>,
) {
let count = callee.places.len();
let first = callee.mir.local_decls.len();
for index in 0..count {
let slot = mir::Local::from_usize(first.saturating_add(index));
let Some(path) = callee.places.path(slot) else {
continue;
};
if callee.escapes(slot) || !path.portable() {
continue;
}
let Some(at) = path.base.as_usize().checked_sub(1) else {
continue;
};
let Some(held) = carried.get(at).filter(|held| held.alike) else {
continue;
};
let Some(mine) = held
.base
.and_then(|base| self.places.at(path.rebased(base)))
else {
continue;
};
let fact = Folder::abroad(Folder::known_at(state, mine));
if fact == Fact::default() {
continue;
}
if let Some(cell) = entry.get_mut(slot.as_usize()) {
*cell = fact;
}
}
}
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
};
let base = match &arg.node {
mir::Operand::Copy(place) | mir::Operand::Move(place) => {
place.projection.is_empty().then_some(place.local)
}
mir::Operand::Constant(_) | mir::Operand::RuntimeChecks(_) => None,
};
Carried {
slot,
base,
alike,
fact,
}
}
fn wrapped(
&self,
state: &State<'tcx>,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
) -> Option<Fact<'tcx>> {
let held = args.first()?;
let ty =
self.monomorphize(held.node.ty(&self.mir.local_decls, self.tcx))?;
let zero = Known {
bits: 0,
ty,
width: self.width(ty)?,
};
let apart = crate::value::compare(
mir::BinOp::Ne,
self.fact(state, &held.node),
Fact::of(Value::Exact(zero)),
)?;
if !apart {
return None;
}
let out = self
.monomorphize(destination.ty(&self.mir.local_decls, self.tcx).ty)?;
let ty::Adt(def, args) = out.kind() else {
return None;
};
if self.tcx.get_diagnostic_item(rustc_span::sym::Option)
!= Some(def.did())
{
return None;
}
let (at, _) = def
.variants()
.iter_enumerated()
.find(|(_, variant)| !variant.fields.is_empty())?;
Some(Fact {
tag: self.tag_of(def.did(), args, at),
..Fact::default()
})
}
fn counted(
&self,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
) -> Option<Value<'tcx>> {
let counted = self.monomorphize(
args.first()?.node.ty(&self.mir.local_decls, self.tcx),
)?;
let bits = u128::from(self.width(counted)?);
let ty = self
.monomorphize(destination.ty(&self.mir.local_decls, self.tcx).ty)?;
let width = self.width(ty)?;
let seed = Known { bits: 0, ty, width };
Bounds::new(seed, Known { bits, ..seed }).map(Value::Within)
}
fn chosen(
&self,
state: &State<'tcx>,
larger: bool,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
) -> Option<Fact<'tcx>> {
let fact = Fact {
value: self.picked(state, larger, args),
order: self.ranked(state, larger, args),
..Fact::default()
};
(fact != Fact::default()).then_some(fact)
}
fn ranked(
&self,
state: &State<'tcx>,
larger: bool,
args: &[rustc_span::Spanned<mir::Operand<'tcx>>],
) -> Ranks {
let [left, right] = args else {
return Ranks::none_held();
};
let bound = |operand: &mir::Operand<'tcx>| {
let fact = self.fact(state, operand);
match fact.value {
Some(Value::Length(of)) => Ranks::of(LenRel::AtMost, of),
_ => fact.order,
}
};
let (left, right) = (bound(&left.node), bound(&right.node));
if larger {
return left.joined(right);
}
let mut both = left;
for (rel, of) in right.each() {
both.add(rel, of);
}
both
}
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)
}
pub 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) = crate::value::sized(self.fact(state, operand)) 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>> {
let seed = Known {
bits: 0,
ty,
width: self.width(ty)?,
};
let top = match ty.kind() {
ty::Int(_) | ty::Uint(_) => seed.type_max(),
ty::Char => Known {
bits: u128::from(char::MAX as u32),
..seed
},
ty::Bool => Known { bits: 1, ..seed },
_ => return None,
};
Bounds::new(seed.type_min(), top)
}
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 }))
}
}