summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_mir_transform/src/add_retag.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_mir_transform/src/add_retag.rs')
-rw-r--r--compiler/rustc_mir_transform/src/add_retag.rs186
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)),
+ },
+ );
+ }
+ }
+ }
+}