diff options
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_mir_transform/src/add_retag.rs | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/compiler/rustc_mir_transform/src/add_retag.rs b/compiler/rustc_mir_transform/src/add_retag.rs new file mode 100644 index 000000000..9c5896c4e --- /dev/null +++ b/compiler/rustc_mir_transform/src/add_retag.rs @@ -0,0 +1,186 @@ +//! This pass adds validation calls (AcquireValid, ReleaseValid) where appropriate. +//! It has to be run really early, before transformations like inlining, because +//! introducing these calls *adds* UB -- so, conceptually, this pass is actually part +//! of MIR building, and only after this pass we think of the program has having the +//! normal MIR semantics. + +use crate::MirPass; +use rustc_middle::mir::*; +use rustc_middle::ty::{self, Ty, TyCtxt}; + +pub struct AddRetag; + +/// Determines whether this place is "stable": Whether, if we evaluate it again +/// after the assignment, we can be sure to obtain the same place value. +/// (Concurrent accesses by other threads are no problem as these are anyway non-atomic +/// copies. Data races are UB.) +fn is_stable(place: PlaceRef<'_>) -> bool { + // Which place this evaluates to can change with any memory write, + // so cannot assume deref to be stable. + !place.has_deref() +} + +/// Determine whether this type may contain a reference (or box), and thus needs retagging. +/// We will only recurse `depth` times into Tuples/ADTs to bound the cost of this. +fn may_contain_reference<'tcx>(ty: Ty<'tcx>, depth: u32, tcx: TyCtxt<'tcx>) -> bool { + match ty.kind() { + // Primitive types that are not references + ty::Bool + | ty::Char + | ty::Float(_) + | ty::Int(_) + | ty::Uint(_) + | ty::RawPtr(..) + | ty::FnPtr(..) + | ty::Str + | ty::FnDef(..) + | ty::Never => false, + // References + ty::Ref(..) => true, + ty::Adt(..) if ty.is_box() => true, + // Compound types: recurse + ty::Array(ty, _) | ty::Slice(ty) => { + // This does not branch so we keep the depth the same. + may_contain_reference(*ty, depth, tcx) + } + ty::Tuple(tys) => { + depth == 0 || tys.iter().any(|ty| may_contain_reference(ty, depth - 1, tcx)) + } + ty::Adt(adt, subst) => { + depth == 0 + || adt.variants().iter().any(|v| { + v.fields.iter().any(|f| may_contain_reference(f.ty(tcx, subst), depth - 1, tcx)) + }) + } + // Conservative fallback + _ => true, + } +} + +impl<'tcx> MirPass<'tcx> for AddRetag { + fn is_enabled(&self, sess: &rustc_session::Session) -> bool { + sess.opts.unstable_opts.mir_emit_retag + } + + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + // We need an `AllCallEdges` pass before we can do any work. + super::add_call_guards::AllCallEdges.run_pass(tcx, body); + + let (span, arg_count) = (body.span, body.arg_count); + let basic_blocks = body.basic_blocks.as_mut(); + let local_decls = &body.local_decls; + let needs_retag = |place: &Place<'tcx>| { + // FIXME: Instead of giving up for unstable places, we should introduce + // a temporary and retag on that. + is_stable(place.as_ref()) + && may_contain_reference(place.ty(&*local_decls, tcx).ty, /*depth*/ 3, tcx) + && !local_decls[place.local].is_deref_temp() + }; + let place_base_raw = |place: &Place<'tcx>| { + // If this is a `Deref`, get the type of what we are deref'ing. + if place.has_deref() { + let ty = &local_decls[place.local].ty; + ty.is_unsafe_ptr() + } else { + // Not a deref, and thus not raw. + false + } + }; + + // PART 1 + // Retag arguments at the beginning of the start block. + { + // FIXME: Consider using just the span covering the function + // argument declaration. + let source_info = SourceInfo::outermost(span); + // Gather all arguments, skip return value. + let places = local_decls + .iter_enumerated() + .skip(1) + .take(arg_count) + .map(|(local, _)| Place::from(local)) + .filter(needs_retag); + // Emit their retags. + basic_blocks[START_BLOCK].statements.splice( + 0..0, + places.map(|place| Statement { + source_info, + kind: StatementKind::Retag(RetagKind::FnEntry, Box::new(place)), + }), + ); + } + + // PART 2 + // Retag return values of functions. Also escape-to-raw the argument of `drop`. + // We collect the return destinations because we cannot mutate while iterating. + let returns = basic_blocks + .iter_mut() + .filter_map(|block_data| { + match block_data.terminator().kind { + TerminatorKind::Call { target: Some(target), destination, .. } + if needs_retag(&destination) => + { + // Remember the return destination for later + Some((block_data.terminator().source_info, destination, target)) + } + + // `Drop` is also a call, but it doesn't return anything so we are good. + TerminatorKind::Drop { .. } | TerminatorKind::DropAndReplace { .. } => None, + // Not a block ending in a Call -> ignore. + _ => None, + } + }) + .collect::<Vec<_>>(); + // Now we go over the returns we collected to retag the return values. + for (source_info, dest_place, dest_block) in returns { + basic_blocks[dest_block].statements.insert( + 0, + Statement { + source_info, + kind: StatementKind::Retag(RetagKind::Default, Box::new(dest_place)), + }, + ); + } + + // PART 3 + // Add retag after assignment. + for block_data in basic_blocks { + // We want to insert statements as we iterate. To this end, we + // iterate backwards using indices. + for i in (0..block_data.statements.len()).rev() { + let (retag_kind, place) = match block_data.statements[i].kind { + // Retag-as-raw after escaping to a raw pointer, if the referent + // is not already a raw pointer. + StatementKind::Assign(box (lplace, Rvalue::AddressOf(_, ref rplace))) + if !place_base_raw(rplace) => + { + (RetagKind::Raw, lplace) + } + // Retag after assignments of reference type. + StatementKind::Assign(box (ref place, ref rvalue)) if needs_retag(place) => { + let kind = match rvalue { + Rvalue::Ref(_, borrow_kind, _) + if borrow_kind.allows_two_phase_borrow() => + { + RetagKind::TwoPhase + } + _ => RetagKind::Default, + }; + (kind, *place) + } + // Do nothing for the rest + _ => continue, + }; + // Insert a retag after the statement. + let source_info = block_data.statements[i].source_info; + block_data.statements.insert( + i + 1, + Statement { + source_info, + kind: StatementKind::Retag(retag_kind, Box::new(place)), + }, + ); + } + } + } +} |