Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2025-09-08"
channel = "nightly-2026-03-08"
components = [ "rustc-dev", "rust-src", "llvm-tools-preview", "rust-analyzer" ]
17 changes: 12 additions & 5 deletions src/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,16 @@ fn fn_operand<'tcx>(
}))
}

pub fn mir_borrowck_skip_formula_fn(
tcx: rustc_middle::ty::TyCtxt<'_>,
pub fn mir_borrowck_skip_formula_fn<'tcx>(
tcx: rustc_middle::ty::TyCtxt<'tcx>,
local_def_id: rustc_span::def_id::LocalDefId,
) -> rustc_middle::query::queries::mir_borrowck::ProvidedValue<'_> {
) -> Result<
&'tcx rustc_data_structures::fx::FxIndexMap<
rustc_span::def_id::LocalDefId,
rustc_middle::ty::DefinitionSiteHiddenType<'tcx>,
>,
rustc_span::ErrorGuaranteed,
> {
// TODO: unify impl with local_def::Analyzer
// if the def is closure defined in formula_fn
let root_def_id = tcx.typeck_root_def_id(local_def_id.to_def_id());
Expand All @@ -68,8 +74,9 @@ pub fn mir_borrowck_skip_formula_fn(

if is_annotated_as_formula_fn {
tracing::debug!(?local_def_id, "skipping borrow check for formula fn");
let dummy_result = rustc_middle::mir::ConcreteOpaqueTypes(Default::default());
return Ok(tcx.arena.alloc(dummy_result));
return Ok(tcx
.arena
.alloc(rustc_data_structures::fx::FxIndexMap::default()));
}

(rustc_interface::DEFAULT_QUERY_PROVIDERS
Expand Down
11 changes: 9 additions & 2 deletions src/analyze/basic_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,14 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
let ty = match &operand {
Operand::Copy(place) | Operand::Move(place) => self.env.place_type(*place),
Operand::Constant(operand) => self.const_ty(&operand.const_),
Operand::RuntimeChecks(kind) => {
let enabled = match kind {
mir::RuntimeChecks::UbChecks => self.tcx.sess.ub_checks(),
mir::RuntimeChecks::ContractChecks => self.tcx.sess.contract_checks(),
mir::RuntimeChecks::OverflowChecks => self.tcx.sess.overflow_checks(),
};
PlaceTypeBuilder::default().build(rty::Type::bool(), chc::Term::bool(enabled))
}
};
tracing::debug!(operand = ?operand, ty = %ty.display(), "operand_type");
ty
Expand All @@ -479,7 +487,6 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
fn rvalue_type(&mut self, rvalue: Rvalue<'tcx>) -> PlaceType {
match rvalue {
Rvalue::Use(operand) => self.operand_type(operand),
Rvalue::CopyForDeref(place) => self.env.place_type(self.elaborate_place(&place)),
Rvalue::UnaryOp(op, operand) => {
let operand_ty = self.operand_type(operand);

Expand Down Expand Up @@ -639,7 +646,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
}
Rvalue::Cast(
mir::CastKind::PointerCoercion(
mir_ty::adjustment::PointerCoercion::ReifyFnPointer,
mir_ty::adjustment::PointerCoercion::ReifyFnPointer(_),
_,
),
operand,
Expand Down
37 changes: 30 additions & 7 deletions src/analyze/basic_block/drop_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::{BTreeSet, HashMap};

use rustc_index::bit_set::DenseBitSet;
use rustc_middle::mir::{self, BasicBlock, Body, Local};
use rustc_middle::ty::TyCtxt;
use rustc_mir_dataflow::{impls::MaybeLiveLocals, ResultsCursor};

#[derive(Debug, Clone, Default)]
Expand All @@ -17,8 +18,12 @@ pub struct DropPoints {
}

impl DropPoints {
pub fn builder<'mir, 'tcx>(body: &'mir Body<'tcx>) -> DropPointsBuilder<'mir, 'tcx> {
pub fn builder<'mir, 'tcx>(
tcx: TyCtxt<'tcx>,
body: &'mir Body<'tcx>,
) -> DropPointsBuilder<'mir, 'tcx> {
DropPointsBuilder {
tcx,
body,
bb_ins_cache: HashMap::new(),
}
Expand Down Expand Up @@ -64,8 +69,9 @@ impl DropPoints {
}
}

#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct DropPointsBuilder<'mir, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'mir Body<'tcx>,
bb_ins_cache: HashMap<BasicBlock, DenseBitSet<Local>>,
}
Expand All @@ -75,28 +81,45 @@ pub struct DropPointsBuilder<'mir, 'tcx> {
/// drop obligation (including resolving any mutable-borrow prophecies it owns)
/// moves to the destination and it must not be dropped at the move site.
///
/// Ownership is transferred by a `move` operand, and equally by a `copy` operand whose type is
/// not `Copy`: runtime MIR reads a local that is dead afterwards with `copy` regardless of
/// whether the type can actually be duplicated.
///
/// Only owned (non-reference) operands are reported: `move`d references are
/// turned into reborrows by `ReborrowVisitor`/`RustCallVisitor`, so the source
/// local remains live and must still be dropped.
fn moved_locals<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
bb: BasicBlock,
statement_index: usize,
) -> DenseBitSet<Local> {
struct Visitor<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
locals: DenseBitSet<Local>,
}
impl<'tcx> Visitor<'_, 'tcx> {
fn place_is_copy(&self, place: mir::Place<'tcx>) -> bool {
let ty = place.ty(&self.body.local_decls, self.tcx).ty;
self.tcx
.type_is_copy_modulo_regions(self.body.typing_env(self.tcx), ty)
}
}
impl<'tcx> mir::visit::Visitor<'tcx> for Visitor<'_, 'tcx> {
fn visit_operand(&mut self, operand: &mir::Operand<'tcx>, _location: mir::Location) {
if let mir::Operand::Move(place) = operand {
if place.projection.is_empty() && !self.body.local_decls[place.local].ty.is_ref() {
self.locals.insert(place.local);
}
let place = match operand {
mir::Operand::Move(place) => place,
mir::Operand::Copy(place) if !self.place_is_copy(*place) => place,
_ => return,
};
if place.projection.is_empty() && !self.body.local_decls[place.local].ty.is_ref() {
self.locals.insert(place.local);
}
}
}
let mut visitor = Visitor {
tcx,
body,
locals: DenseBitSet::new_empty(body.local_decls.len()),
};
Expand Down Expand Up @@ -192,7 +215,7 @@ impl<'mir, 'tcx> DropPointsBuilder<'mir, 'tcx> {
t.insert(def);
}
t.subtract(&last_live_locals);
t.subtract(&moved_locals(self.body, bb, statement_index));
t.subtract(&moved_locals(self.tcx, self.body, bb, statement_index));
t
};
last_live_locals = live_locals;
Expand Down
138 changes: 73 additions & 65 deletions src/analyze/local_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ fn stmt_str_literal(stmt: &rustc_hir::Stmt) -> Option<String> {
}
}

fn is_raw_const_ptr(ty: mir_ty::Ty<'_>) -> bool {
matches!(ty.kind(), mir_ty::TyKind::RawPtr(_, mutbl) if mutbl.is_not())
}

/// An implementation of the typing of local definitions.
///
/// The current implementation only applies to function definitions. The entry point is
Expand Down Expand Up @@ -181,7 +185,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
.tcx
.opt_associated_item(self.local_def_id.to_def_id())?;
let trait_item_id = impl_item_assoc
.trait_item_def_id
.trait_item_def_id()
.and_then(|id| id.as_local())?;

if trait_item_id == self.local_def_id {
Expand All @@ -198,11 +202,14 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
return None;
}

let trait_ref = self.tcx.impl_trait_ref(impl_did)?.instantiate_identity();
let trait_ref = self
.tcx
.impl_opt_trait_ref(impl_did)?
.instantiate_identity();
let trait_item_did = self
.tcx
.associated_item(self.local_def_id.to_def_id())
.trait_item_def_id
.trait_item_def_id()
.unwrap();
self.ctx.def_ty_with_args(trait_item_did, trait_ref.args)
}
Expand Down Expand Up @@ -387,54 +394,80 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
if !lhs.projection.as_ref().is_empty() {
return None;
}
let lhs_local = lhs.local;
Some((lhs.local, self.deref_alias_source(rvalue)?))
}

if let mir::Rvalue::CopyForDeref(place) = &rvalue {
return Some((lhs_local, *place));
/// The place an rvalue reads through when it is one of the aliases deref elaboration
/// introduces, or `None` when the rvalue is an ordinary read.
fn deref_alias_source(&self, rvalue: &mir::Rvalue<'tcx>) -> Option<mir::Place<'tcx>> {
match rvalue {
mir::Rvalue::Use(mir::Operand::Copy(place)) => {
if self.is_mut_ref_field(*place) || self.is_box_behind_ref(*place) {
return Some(*place);
}
self.box_of_nonnull_field(*place)
}
mir::Rvalue::Cast(mir::CastKind::Transmute, mir::Operand::Copy(place), cast_ty)
if is_raw_const_ptr(*cast_ty) =>
{
self.box_of_nonnull_field(*place)
.or_else(|| self.place_ty(*place).is_box().then_some(*place))
}
_ => None,
}
}

let unique_did = self.ctx.def_ids.unique()?;
let nonnull_did = self.ctx.def_ids.nonnull()?;
fn place_ty(&self, place: mir::Place<'tcx>) -> mir_ty::Ty<'tcx> {
place.ty(&self.body.local_decls, self.tcx).ty
}

/// Whether a place reads a `&mut T` out of a struct field, the alias created so that the
/// result can be dereffed — a closure's captured `&mut`, for instance. A field projection is
/// required, since a plain `_ret = copy _param` is a genuine read rather than an alias.
fn is_mut_ref_field(&self, place: mir::Place<'tcx>) -> bool {
if !matches!(
place.projection.last(),
Some(mir::ProjectionElem::Field(..))
) {
return false;
}
matches!(
self.place_ty(place).kind(),
mir_ty::TyKind::Ref(_, _, mir::Mutability::Mut)
)
}

/// Whether a place reads a `Box<T>` through a reference to it, as in `copy (*ref_to_box)`.
fn is_box_behind_ref(&self, place: mir::Place<'tcx>) -> bool {
matches!(place.projection.as_slice(), [mir::ProjectionElem::Deref])
&& self.place_ty(place).is_box()
}

/// The `Box<T>` under `box.Field(0, Unique<T>).Field(0, NonNull<T>)`, the pointer field that
/// box deref elaboration reads before transmuting it to a raw pointer.
fn box_of_nonnull_field(&self, place: mir::Place<'tcx>) -> Option<mir::Place<'tcx>> {
use mir::ProjectionElem::Field;
use rustc_abi::FieldIdx;
const ZERO_FIELD: FieldIdx = FieldIdx::from_u32(0);

// Box deref pattern: `(_box.0.0 as *const T) Transmute`
// projection = [..., Field(0, Unique<T>), Field(0, NonNull<T>)], transmuted to *const T
let mir::Rvalue::Cast(mir::CastKind::Transmute, mir::Operand::Copy(place), cast_ty) =
&rvalue
else {
return None;
};
if !matches!(cast_ty.kind(), mir_ty::TyKind::RawPtr(_, mutbl) if mutbl.is_not()) {
return None;
}
let Some((rest, [Field(ZERO_FIELD, ty0), Field(ZERO_FIELD, ty1)])) =
place.projection.as_slice().split_last_chunk::<2>()
let (rest, [Field(ZERO_FIELD, unique_ty), Field(ZERO_FIELD, nonnull_ty)]) =
place.projection.as_slice().split_last_chunk::<2>()?
else {
return None;
};
let rest_place = mir::Place {
let box_place = mir::Place {
local: place.local,
projection: self.tcx.mk_place_elems(rest),
};
let local_ty = rest_place.ty(&self.body.local_decls, self.tcx).ty;
if !local_ty.is_box() {
return None;
}
let inner_ty = local_ty.boxed_ty()?;
if !matches!(ty0.kind(), mir_ty::TyKind::Adt(def, args)
if def.did() == unique_did && args.type_at(0) == inner_ty)
{
return None;
}
if !matches!(ty1.kind(), mir_ty::TyKind::Adt(def, args)
if def.did() == nonnull_did && args.type_at(0) == inner_ty)
{
return None;
}
Some((lhs_local, rest_place))
let inner_ty = self.place_ty(box_place).boxed_ty()?;
let wraps_inner = |ty: mir_ty::Ty<'tcx>, did| {
matches!(ty.kind(), mir_ty::TyKind::Adt(def, args)
if def.did() == did && args.type_at(0) == inner_ty)
};
let unique_did = self.ctx.def_ids.unique()?;
let nonnull_did = self.ctx.def_ids.nonnull()?;
(wraps_inner(*unique_ty, unique_did) && wraps_inner(*nonnull_ty, nonnull_did))
.then_some(box_place)
}

fn unelaborate_derefs(&mut self) {
Expand Down Expand Up @@ -623,33 +656,8 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
let move_data = {
// XXX: what...
let mut body = self.body.clone();
struct Visitor {
deref_temps: DenseBitSet<Local>,
}
impl<'tcx> mir::visit::Visitor<'tcx> for Visitor {
fn visit_assign(
&mut self,
place: &mir::Place<'tcx>,
rvalue: &mir::Rvalue<'tcx>,
_location: mir::Location,
) {
if let mir::Rvalue::CopyForDeref { .. } = rvalue {
self.deref_temps.insert(place.local);
}
}
}
let mut visitor = Visitor {
deref_temps: DenseBitSet::new_empty(body.local_decls.len()),
};
use mir::visit::Visitor as _;
visitor.visit_body(&body);
for (local, local_decl) in body.local_decls.iter_enumerated_mut() {
let local_info = if visitor.deref_temps.contains(local) {
mir::LocalInfo::DerefTemp
} else {
mir::LocalInfo::Boring
};
local_decl.local_info = mir::ClearCrossCrate::Set(Box::new(local_info));
for local_decl in &mut body.local_decls {
local_decl.local_info = mir::ClearCrossCrate::Set(Box::new(mir::LocalInfo::Boring));
}
MoveData::gather_moves(&body, self.tcx, |_| true)
};
Expand Down Expand Up @@ -896,7 +904,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
.iterate_to_fixpoint(self.tcx, &self.body, None)
.into_results_cursor(&self.body);

let mut builder = analyze::basic_block::DropPoints::builder(&self.body);
let mut builder = analyze::basic_block::DropPoints::builder(self.tcx, &self.body);
for (bb, _data) in mir::traversal::postorder(&self.body) {
let span = tracing::info_span!("refine_basic_block", ?bb);
let _guard = span.enter();
Expand Down
Loading