Skip to content

Drop Elaboration Elaboration #72283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 19, 2020
Merged
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
13 changes: 12 additions & 1 deletion src/librustc_mir/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,18 @@ impl<'a, 'tcx> DropElaborator<'a, 'tcx> for DropShimElaborator<'a, 'tcx> {
}

fn drop_style(&self, _path: Self::Path, mode: DropFlagMode) -> DropStyle {
if let DropFlagMode::Shallow = mode { DropStyle::Static } else { DropStyle::Open }
match mode {
DropFlagMode::Shallow => {
// Drops for the contained fields are "shallow" and "static" - they will simply call
// the field's own drop glue.
DropStyle::Static
}
DropFlagMode::Deep => {
// The top-level drop is "deep" and "open" - it will be elaborated to a drop ladder
// dropping each field contained in the value.
DropStyle::Open
}
}
}

fn get_drop_flag(&mut self, _path: Self::Path) -> Option<Operand<'tcx>> {
Expand Down
95 changes: 87 additions & 8 deletions src/librustc_mir/util/elaborate_drops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ use std::fmt;

use std::convert::TryInto;

/// The value of an inserted drop flag.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum DropFlagState {
Present, // i.e., initialized
Absent, // i.e., deinitialized or "moved"
/// The tracked value is initialized and needs to be dropped when leaving its scope.
Present,

/// The tracked value is uninitialized or was moved out of and does not need to be dropped when
/// leaving its scope.
Absent,
}

impl DropFlagState {
Expand All @@ -27,23 +32,42 @@ impl DropFlagState {
}
}

/// Describes how/if a value should be dropped.
#[derive(Debug)]
pub enum DropStyle {
/// The value is already dead at the drop location, no drop will be executed.
Dead,

/// The value is known to always be initialized at the drop location, drop will always be
/// executed.
Static,

/// Whether the value needs to be dropped depends on its drop flag.
Conditional,

/// An "open" drop is one where only the fields of a value are dropped.
///
/// For example, this happens when moving out of a struct field: The rest of the struct will be
/// dropped in such an "open" drop. It is also used to generate drop glue for the individual
/// components of a value, for example for dropping array elements.
Open,
}

/// Which drop flags to affect/check with an operation.
#[derive(Debug)]
pub enum DropFlagMode {
/// Only affect the top-level drop flag, not that of any contained fields.
Shallow,
/// Affect all nested drop flags in addition to the top-level one.
Deep,
}

/// Describes if unwinding is necessary and where to unwind to if a panic occurs.
#[derive(Copy, Clone, Debug)]
pub enum Unwind {
/// Unwind to this block.
To(BasicBlock),
/// Already in an unwind path, any panic will cause an abort.
InCleanup,
}

Expand Down Expand Up @@ -74,20 +98,58 @@ impl Unwind {
}

pub trait DropElaborator<'a, 'tcx>: fmt::Debug {
/// The type representing paths that can be moved out of.
///
/// Users can move out of individual fields of a struct, such as `a.b.c`. This type is used to
/// represent such move paths. Sometimes tracking individual move paths is not necessary, in
/// which case this may be set to (for example) `()`.
type Path: Copy + fmt::Debug;

// Accessors

fn patch(&mut self) -> &mut MirPatch<'tcx>;
fn body(&self) -> &'a Body<'tcx>;
fn tcx(&self) -> TyCtxt<'tcx>;
fn param_env(&self) -> ty::ParamEnv<'tcx>;

// Drop logic

/// Returns how `path` should be dropped, given `mode`.
fn drop_style(&self, path: Self::Path, mode: DropFlagMode) -> DropStyle;

/// Returns the drop flag of `path` as a MIR `Operand` (or `None` if `path` has no drop flag).
fn get_drop_flag(&mut self, path: Self::Path) -> Option<Operand<'tcx>>;

/// Modifies the MIR patch so that the drop flag of `path` (if any) is cleared at `location`.
///
/// If `mode` is deep, drop flags of all child paths should also be cleared by inserting
/// additional statements.
fn clear_drop_flag(&mut self, location: Location, path: Self::Path, mode: DropFlagMode);

// Subpaths

/// Returns the subpath of a field of `path` (or `None` if there is no dedicated subpath).
///
/// If this returns `None`, `field` will not get a dedicated drop flag.
fn field_subpath(&self, path: Self::Path, field: Field) -> Option<Self::Path>;

/// Returns the subpath of a dereference of `path` (or `None` if there is no dedicated subpath).
///
/// If this returns `None`, `*path` will not get a dedicated drop flag.
///
/// This is only relevant for `Box<T>`, where the contained `T` can be moved out of the box.
fn deref_subpath(&self, path: Self::Path) -> Option<Self::Path>;

/// Returns the subpath of downcasting `path` to one of its variants.
///
/// If this returns `None`, the downcast of `path` will not get a dedicated drop flag.
fn downcast_subpath(&self, path: Self::Path, variant: VariantIdx) -> Option<Self::Path>;

/// Returns the subpath of indexing a fixed-size array `path`.
///
/// If this returns `None`, elements of `path` will not get a dedicated drop flag.
///
/// This is only relevant for array patterns, which can move out of individual array elements.
fn array_subpath(&self, path: Self::Path, index: u32, size: u32) -> Option<Self::Path>;
}

Expand All @@ -106,6 +168,14 @@ where
unwind: Unwind,
}

/// "Elaborates" a drop of `place`/`path` and patches `bb`'s terminator to execute it.
///
/// The passed `elaborator` is used to determine what should happen at the drop terminator. It
/// decides whether the drop can be statically determined or whether it needs a dynamic drop flag,
/// and whether the drop is "open", ie. should be expanded to drop all subfields of the dropped
/// value.
///
/// When this returns, the MIR patch in the `elaborator` contains the necessary changes.
pub fn elaborate_drop<'b, 'tcx, D>(
elaborator: &mut D,
source_info: SourceInfo,
Expand Down Expand Up @@ -346,9 +416,7 @@ where
let interior = self.tcx().mk_place_deref(self.place);
let interior_path = self.elaborator.deref_subpath(self.path);

let succ = self.succ; // FIXME(#43234)
let unwind = self.unwind;
let succ = self.box_free_block(adt, substs, succ, unwind);
let succ = self.box_free_block(adt, substs, self.succ, self.unwind);
let unwind_succ =
self.unwind.map(|unwind| self.box_free_block(adt, substs, unwind, Unwind::InCleanup));

Expand Down Expand Up @@ -829,6 +897,8 @@ where
self.drop_flag_test_block(drop_block, succ, unwind)
}

/// Creates a block that resets the drop flag. If `mode` is deep, all children drop flags will
/// also be cleared.
fn drop_flag_reset_block(
&mut self,
mode: DropFlagMode,
Expand All @@ -850,13 +920,15 @@ where

fn elaborated_drop_block(&mut self) -> BasicBlock {
debug!("elaborated_drop_block({:?})", self);
let unwind = self.unwind; // FIXME(#43234)
let succ = self.succ;
let blk = self.drop_block(succ, unwind);
let blk = self.drop_block(self.succ, self.unwind);
self.elaborate_drop(blk);
blk
}

/// Creates a block that frees the backing memory of a `Box` if its drop is required (either
/// statically or by checking its drop flag).
///
/// The contained value will not be dropped.
fn box_free_block(
&mut self,
adt: &'tcx ty::AdtDef,
Expand All @@ -868,6 +940,8 @@ where
self.drop_flag_test_block(block, target, unwind)
}

/// Creates a block that frees the backing memory of a `Box` (without dropping the contained
/// value).
fn unelaborated_free_block(
&mut self,
adt: &'tcx ty::AdtDef,
Expand Down Expand Up @@ -914,6 +988,11 @@ where
self.new_block(unwind, block)
}

/// Returns the block to jump to in order to test the drop flag and execute the drop.
///
/// Depending on the required `DropStyle`, this might be a generated block with an `if`
/// terminator (for dynamic/open drops), or it might be `on_set` or `on_unset` itself, in case
/// the drop can be statically determined.
fn drop_flag_test_block(
&mut self,
on_set: BasicBlock,
Expand Down