//! For generators with state 1 (returned) and state 2 (poisoned) it does nothing.
//! Otherwise it drops all the values in scope at the last suspension point.
-use rustc::hir;
-use rustc::hir::def_id::DefId;
-use rustc::mir::*;
-use rustc::mir::visit::{PlaceContext, Visitor, MutVisitor};
-use rustc::ty::{self, TyCtxt, AdtDef, Ty};
-use rustc::ty::GeneratorSubsts;
-use rustc::ty::layout::VariantIdx;
-use rustc::ty::subst::SubstsRef;
+use crate::dataflow::impls::{
+ MaybeBorrowedLocals, MaybeLiveLocals, MaybeRequiresStorage, MaybeStorageLive,
+};
+use crate::dataflow::{self, Analysis};
+use crate::transform::no_landing_pads::no_landing_pads;
+use crate::transform::simplify;
+use crate::transform::{MirPass, MirSource};
+use crate::util::dump_mir;
+use crate::util::expand_aggregate;
+use crate::util::storage;
use rustc_data_structures::fx::FxHashMap;
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_hir::lang_items::LangItem;
+use rustc_index::bit_set::{BitMatrix, BitSet};
use rustc_index::vec::{Idx, IndexVec};
-use rustc_index::bit_set::{BitSet, BitMatrix};
+use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
+use rustc_middle::mir::*;
+use rustc_middle::ty::subst::{Subst, SubstsRef};
+use rustc_middle::ty::GeneratorSubsts;
+use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt};
+use rustc_target::abi::VariantIdx;
+use rustc_target::spec::PanicStrategy;
use std::borrow::Cow;
-use std::iter;
-use std::mem;
-use crate::transform::{MirPass, MirSource};
-use crate::transform::simplify;
-use crate::transform::no_landing_pads::no_landing_pads;
-use crate::dataflow::{DataflowResults, DataflowResultsConsumer, FlowAtLocation};
-use crate::dataflow::{do_dataflow, DebugFormatted, DataflowResultsCursor};
-use crate::dataflow::{MaybeStorageLive, HaveBeenBorrowedLocals, RequiresStorage};
-use crate::util::dump_mir;
-use crate::util::liveness;
+use std::{iter, ops};
pub struct StateTransform;
self.tcx
}
- fn visit_local(&mut self,
- local: &mut Local,
- _: PlaceContext,
- _: Location) {
+ fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
if *local == self.from {
*local = self.to;
}
}
- fn process_projection_elem(
- &mut self,
- elem: &PlaceElem<'tcx>,
- ) -> Option<PlaceElem<'tcx>> {
- match elem {
- PlaceElem::Index(local) if *local == self.from => {
- Some(PlaceElem::Index(self.to))
+ fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) {
+ match terminator.kind {
+ TerminatorKind::Return => {
+ // Do not replace the implicit `_0` access here, as that's not possible. The
+ // transform already handles `return` correctly.
}
- _ => None,
+ _ => self.super_terminator(terminator, location),
}
}
}
self.tcx
}
- fn visit_local(&mut self,
- local: &mut Local,
- _: PlaceContext,
- _: Location) {
- assert_ne!(*local, self_arg());
+ fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
+ assert_ne!(*local, SELF_ARG);
}
- fn visit_place(&mut self,
- place: &mut Place<'tcx>,
- context: PlaceContext,
- location: Location) {
- if place.base == PlaceBase::Local(self_arg()) {
- replace_base(place, Place {
- base: PlaceBase::Local(self_arg()),
- projection: self.tcx().intern_place_elems(&vec![ProjectionElem::Deref]),
- }, self.tcx);
+ fn visit_place(&mut self, place: &mut Place<'tcx>, context: PlaceContext, location: Location) {
+ if place.local == SELF_ARG {
+ replace_base(
+ place,
+ Place {
+ local: SELF_ARG,
+ projection: self.tcx().intern_place_elems(&[ProjectionElem::Deref]),
+ },
+ self.tcx,
+ );
} else {
- self.visit_place_base(&mut place.base, context, location);
+ self.visit_local(&mut place.local, context, location);
for elem in place.projection.iter() {
if let PlaceElem::Index(local) = elem {
- assert_ne!(*local, self_arg());
+ assert_ne!(local, SELF_ARG);
}
}
}
self.tcx
}
- fn visit_local(&mut self,
- local: &mut Local,
- _: PlaceContext,
- _: Location) {
- assert_ne!(*local, self_arg());
+ fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
+ assert_ne!(*local, SELF_ARG);
}
fn visit_place(&mut self, place: &mut Place<'tcx>, context: PlaceContext, location: Location) {
- if place.base == PlaceBase::Local(self_arg()) {
+ if place.local == SELF_ARG {
replace_base(
place,
Place {
- base: PlaceBase::Local(self_arg()),
- projection: self.tcx().intern_place_elems(&vec![ProjectionElem::Field(
- Field::new(0),
- self.ref_gen_ty,
+ local: SELF_ARG,
+ projection: self.tcx().intern_place_elems(&[ProjectionElem::Field(
+ Field::new(0),
+ self.ref_gen_ty,
)]),
},
self.tcx,
);
} else {
- self.visit_place_base(&mut place.base, context, location);
+ self.visit_local(&mut place.local, context, location);
for elem in place.projection.iter() {
if let PlaceElem::Index(local) = elem {
- assert_ne!(*local, self_arg());
+ assert_ne!(local, SELF_ARG);
}
}
}
}
fn replace_base<'tcx>(place: &mut Place<'tcx>, new_base: Place<'tcx>, tcx: TyCtxt<'tcx>) {
- place.base = new_base.base;
+ place.local = new_base.local;
let mut new_projection = new_base.projection.to_vec();
new_projection.append(&mut place.projection.to_vec());
place.projection = tcx.intern_place_elems(&new_projection);
}
-fn self_arg() -> Local {
- Local::new(1)
-}
+const SELF_ARG: Local = Local::from_u32(1);
-/// Generator have not been resumed yet
+/// Generator has not been resumed yet.
const UNRESUMED: usize = GeneratorSubsts::UNRESUMED;
-/// Generator has returned / is completed
+/// Generator has returned / is completed.
const RETURNED: usize = GeneratorSubsts::RETURNED;
-/// Generator has been poisoned
+/// Generator has panicked and is poisoned.
const POISONED: usize = GeneratorSubsts::POISONED;
-struct SuspensionPoint {
+/// A `yield` point in the generator.
+struct SuspensionPoint<'tcx> {
+ /// State discriminant used when suspending or resuming at this point.
state: usize,
+ /// The block to jump to after resumption.
resume: BasicBlock,
+ /// Where to move the resume argument after resumption.
+ resume_arg: Place<'tcx>,
+ /// Which block to jump to if the generator is dropped in this state.
drop: Option<BasicBlock>,
- storage_liveness: liveness::LiveVarSet,
+ /// Set of locals that have live storage while at this suspension point.
+ storage_liveness: BitSet<Local>,
}
struct TransformVisitor<'tcx> {
remap: FxHashMap<Local, (Ty<'tcx>, VariantIdx, usize)>,
// A map from a suspension point in a block to the locals which have live storage at that point
- // FIXME(eddyb) This should use `IndexVec<BasicBlock, Option<_>>`.
- storage_liveness: FxHashMap<BasicBlock, liveness::LiveVarSet>,
+ storage_liveness: IndexVec<BasicBlock, Option<BitSet<Local>>>,
// A list of suspension points, generated during the transform
- suspension_points: Vec<SuspensionPoint>,
+ suspension_points: Vec<SuspensionPoint<'tcx>>,
+
+ // The set of locals that have no `StorageLive`/`StorageDead` annotations.
+ always_live_locals: storage::AlwaysLiveLocals,
// The original RETURN_PLACE local
new_ret_local: Local,
}
impl TransformVisitor<'tcx> {
- // Make a GeneratorState rvalue
- fn make_state(&self, idx: VariantIdx, val: Operand<'tcx>) -> Rvalue<'tcx> {
- let adt = AggregateKind::Adt(self.state_adt_ref, idx, self.state_substs, None, None);
- Rvalue::Aggregate(box adt, vec![val])
+ // Make a GeneratorState variant assignment. `core::ops::GeneratorState` only has single
+ // element tuple variants, so we can just write to the downcasted first field and then set the
+ // discriminant to the appropriate variant.
+ fn make_state(
+ &self,
+ idx: VariantIdx,
+ val: Operand<'tcx>,
+ source_info: SourceInfo,
+ ) -> impl Iterator<Item = Statement<'tcx>> {
+ let kind = AggregateKind::Adt(self.state_adt_ref, idx, self.state_substs, None, None);
+ assert_eq!(self.state_adt_ref.variants[idx].fields.len(), 1);
+ let ty = self
+ .tcx
+ .type_of(self.state_adt_ref.variants[idx].fields[0].did)
+ .subst(self.tcx, self.state_substs);
+ expand_aggregate(
+ Place::return_place(),
+ std::iter::once((val, ty)),
+ kind,
+ source_info,
+ self.tcx,
+ )
}
// Create a Place referencing a generator struct field
fn make_field(&self, variant_index: VariantIdx, idx: usize, ty: Ty<'tcx>) -> Place<'tcx> {
- let self_place = Place::from(self_arg());
+ let self_place = Place::from(SELF_ARG);
let base = self.tcx.mk_place_downcast_unnamed(self_place, variant_index);
let mut projection = base.projection.to_vec();
projection.push(ProjectionElem::Field(Field::new(idx), ty));
- Place {
- base: base.base,
- projection: self.tcx.intern_place_elems(&projection),
- }
+ Place { local: base.local, projection: self.tcx.intern_place_elems(&projection) }
}
// Create a statement which changes the discriminant
fn set_discr(&self, state_disc: VariantIdx, source_info: SourceInfo) -> Statement<'tcx> {
- let self_place = Place::from(self_arg());
+ let self_place = Place::from(SELF_ARG);
Statement {
source_info,
kind: StatementKind::SetDiscriminant {
// Create a statement which reads the discriminant into a temporary
fn get_discr(&self, body: &mut Body<'tcx>) -> (Statement<'tcx>, Place<'tcx>) {
- let temp_decl = LocalDecl::new_internal(self.tcx.types.isize, body.span);
+ let temp_decl = LocalDecl::new(self.discr_ty, body.span).internal();
let local_decls_len = body.local_decls.push(temp_decl);
let temp = Place::from(local_decls_len);
- let self_place = Place::from(self_arg());
+ let self_place = Place::from(SELF_ARG);
let assign = Statement {
- source_info: source_info(body),
- kind: StatementKind::Assign(box(temp.clone(), Rvalue::Discriminant(self_place))),
+ source_info: SourceInfo::outermost(body.span),
+ kind: StatementKind::Assign(box (temp, Rvalue::Discriminant(self_place))),
};
(assign, temp)
}
self.tcx
}
- fn visit_local(&mut self,
- local: &mut Local,
- _: PlaceContext,
- _: Location) {
+ fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
assert_eq!(self.remap.get(local), None);
}
fn visit_place(
&mut self,
place: &mut Place<'tcx>,
- context: PlaceContext,
- location: Location,
+ _context: PlaceContext,
+ _location: Location,
) {
- if let PlaceBase::Local(l) = place.base {
- // Replace an Local in the remap with a generator struct access
- if let Some(&(ty, variant_index, idx)) = self.remap.get(&l) {
- replace_base(place, self.make_field(variant_index, idx, ty), self.tcx);
- }
- } else {
- self.visit_place_base(&mut place.base, context, location);
-
- for elem in place.projection.iter() {
- if let PlaceElem::Index(local) = elem {
- assert_ne!(*local, self_arg());
- }
- }
+ // Replace an Local in the remap with a generator struct access
+ if let Some(&(ty, variant_index, idx)) = self.remap.get(&place.local) {
+ replace_base(place, self.make_field(variant_index, idx, ty), self.tcx);
}
}
- fn visit_basic_block_data(&mut self,
- block: BasicBlock,
- data: &mut BasicBlockData<'tcx>) {
+ fn visit_basic_block_data(&mut self, block: BasicBlock, data: &mut BasicBlockData<'tcx>) {
// Remove StorageLive and StorageDead statements for remapped locals
- data.retain_statements(|s| {
- match s.kind {
- StatementKind::StorageLive(l) | StatementKind::StorageDead(l) => {
- !self.remap.contains_key(&l)
- }
- _ => true
+ data.retain_statements(|s| match s.kind {
+ StatementKind::StorageLive(l) | StatementKind::StorageDead(l) => {
+ !self.remap.contains_key(&l)
}
+ _ => true,
});
let ret_val = match data.terminator().kind {
- TerminatorKind::Return => Some((VariantIdx::new(1),
+ TerminatorKind::Return => Some((
+ VariantIdx::new(1),
None,
Operand::Move(Place::from(self.new_ret_local)),
- None)),
- TerminatorKind::Yield { ref value, resume, drop } => Some((VariantIdx::new(0),
- Some(resume),
- value.clone(),
- drop)),
- _ => None
+ None,
+ )),
+ TerminatorKind::Yield { ref value, resume, resume_arg, drop } => {
+ Some((VariantIdx::new(0), Some((resume, resume_arg)), value.clone(), drop))
+ }
+ _ => None,
};
if let Some((state_idx, resume, v, drop)) = ret_val {
let source_info = data.terminator().source_info;
// We must assign the value first in case it gets declared dead below
- data.statements.push(Statement {
- source_info,
- kind: StatementKind::Assign(
- box(
- Place::return_place(),
- self.make_state(state_idx, v)
- )
- ),
- });
- let state = if let Some(resume) = resume { // Yield
+ data.statements.extend(self.make_state(state_idx, v, source_info));
+ let state = if let Some((resume, resume_arg)) = resume {
+ // Yield
let state = 3 + self.suspension_points.len();
+ // The resume arg target location might itself be remapped if its base local is
+ // live across a yield.
+ let resume_arg =
+ if let Some(&(ty, variant, idx)) = self.remap.get(&resume_arg.local) {
+ self.make_field(variant, idx, ty)
+ } else {
+ resume_arg
+ };
+
self.suspension_points.push(SuspensionPoint {
state,
resume,
+ resume_arg,
drop,
- storage_liveness: self.storage_liveness.get(&block).unwrap().clone(),
+ storage_liveness: self.storage_liveness[block].clone().unwrap(),
});
VariantIdx::new(state)
- } else { // Return
+ } else {
+ // Return
VariantIdx::new(RETURNED) // state for returned
};
data.statements.push(self.set_discr(state, source_info));
- data.terminator.as_mut().unwrap().kind = TerminatorKind::Return;
+ data.terminator_mut().kind = TerminatorKind::Return;
}
self.super_basic_block_data(block, data);
}
}
-fn make_generator_state_argument_indirect<'tcx>(
- tcx: TyCtxt<'tcx>,
- def_id: DefId,
- body: &mut Body<'tcx>,
-) {
+fn make_generator_state_argument_indirect<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let gen_ty = body.local_decls.raw[1].ty;
- let region = ty::ReFree(ty::FreeRegion {
- scope: def_id,
- bound_region: ty::BoundRegion::BrEnv,
- });
-
- let region = tcx.mk_region(region);
-
- let ref_gen_ty = tcx.mk_ref(region, ty::TypeAndMut {
- ty: gen_ty,
- mutbl: hir::MutMutable
- });
+ let ref_gen_ty =
+ tcx.mk_ref(tcx.lifetimes.re_erased, ty::TypeAndMut { ty: gen_ty, mutbl: Mutability::Mut });
// Replace the by value generator argument
body.local_decls.raw[1].ty = ref_gen_ty;
fn make_generator_state_argument_pinned<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let ref_gen_ty = body.local_decls.raw[1].ty;
- let pin_did = tcx.lang_items().pin_type().unwrap();
+ let pin_did = tcx.require_lang_item(LangItem::Pin, Some(body.span));
let pin_adt_ref = tcx.adt_def(pin_did);
let substs = tcx.intern_substs(&[ref_gen_ty.into()]);
let pin_ref_gen_ty = tcx.mk_adt(pin_adt_ref, substs);
PinArgVisitor { ref_gen_ty, tcx }.visit_body(body);
}
-fn replace_result_variable<'tcx>(
- ret_ty: Ty<'tcx>,
+/// Allocates a new local and replaces all references of `local` with it. Returns the new local.
+///
+/// `local` will be changed to a new local decl with type `ty`.
+///
+/// Note that the new local will be uninitialized. It is the caller's responsibility to assign some
+/// valid value to it before its first use.
+fn replace_local<'tcx>(
+ local: Local,
+ ty: Ty<'tcx>,
body: &mut Body<'tcx>,
tcx: TyCtxt<'tcx>,
) -> Local {
- let source_info = source_info(body);
- let new_ret = LocalDecl {
- mutability: Mutability::Mut,
- ty: ret_ty,
- user_ty: UserTypeProjections::none(),
- name: None,
- source_info,
- visibility_scope: source_info.scope,
- internal: false,
- is_block_tail: None,
- is_user_variable: None,
- };
- let new_ret_local = Local::new(body.local_decls.len());
- body.local_decls.push(new_ret);
- body.local_decls.swap(RETURN_PLACE, new_ret_local);
-
- RenameLocalVisitor {
- from: RETURN_PLACE,
- to: new_ret_local,
- tcx,
- }.visit_body(body);
+ let new_decl = LocalDecl::new(ty, body.span);
+ let new_local = body.local_decls.push(new_decl);
+ body.local_decls.swap(local, new_local);
- new_ret_local
-}
-
-struct StorageIgnored(liveness::LiveVarSet);
+ RenameLocalVisitor { from: local, to: new_local, tcx }.visit_body(body);
-impl<'tcx> Visitor<'tcx> for StorageIgnored {
- fn visit_statement(&mut self,
- statement: &Statement<'tcx>,
- _location: Location) {
- match statement.kind {
- StatementKind::StorageLive(l) |
- StatementKind::StorageDead(l) => { self.0.remove(l); }
- _ => (),
- }
- }
+ new_local
}
struct LivenessInfo {
/// Which locals are live across any suspension point.
- ///
- /// GeneratorSavedLocal is indexed in terms of the elements in this set;
- /// i.e. GeneratorSavedLocal::new(1) corresponds to the second local
- /// included in this set.
- live_locals: liveness::LiveVarSet,
+ saved_locals: GeneratorSavedLocals,
/// The set of saved locals live at each suspension point.
live_locals_at_suspension_points: Vec<BitSet<GeneratorSavedLocal>>,
+ /// Parallel vec to the above with SourceInfo for each yield terminator.
+ source_info_at_suspension_points: Vec<SourceInfo>,
+
/// For every saved local, the set of other saved locals that are
/// storage-live at the same time as this local. We cannot overlap locals in
/// the layout which have conflicting storage.
/// For every suspending block, the locals which are storage-live across
/// that suspension point.
- storage_liveness: FxHashMap<BasicBlock, liveness::LiveVarSet>,
+ storage_liveness: IndexVec<BasicBlock, Option<BitSet<Local>>>,
}
fn locals_live_across_suspend_points(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
source: MirSource<'tcx>,
+ always_live_locals: &storage::AlwaysLiveLocals,
movable: bool,
) -> LivenessInfo {
- let dead_unwinds = BitSet::new_empty(body.basic_blocks().len());
let def_id = source.def_id();
+ let body_ref: &Body<'_> = &body;
// Calculate when MIR locals have live storage. This gives us an upper bound of their
// lifetimes.
- let storage_live_analysis = MaybeStorageLive::new(body);
- let storage_live_results =
- do_dataflow(tcx, body, def_id, &[], &dead_unwinds, storage_live_analysis,
- |bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
- let mut storage_live_cursor = DataflowResultsCursor::new(&storage_live_results, body);
-
- // Find the MIR locals which do not use StorageLive/StorageDead statements.
- // The storage of these locals are always live.
- let mut ignored = StorageIgnored(BitSet::new_filled(body.local_decls.len()));
- ignored.visit_body(body);
+ let mut storage_live = MaybeStorageLive::new(always_live_locals.clone())
+ .into_engine(tcx, body_ref, def_id)
+ .iterate_to_fixpoint()
+ .into_results_cursor(body_ref);
// Calculate the MIR locals which have been previously
// borrowed (even if they are still active).
- let borrowed_locals_analysis = HaveBeenBorrowedLocals::new(body);
let borrowed_locals_results =
- do_dataflow(tcx, body, def_id, &[], &dead_unwinds, borrowed_locals_analysis,
- |bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
- let mut borrowed_locals_cursor = DataflowResultsCursor::new(&borrowed_locals_results, body);
+ MaybeBorrowedLocals::all_borrows().into_engine(tcx, body_ref, def_id).iterate_to_fixpoint();
+
+ let mut borrowed_locals_cursor =
+ dataflow::ResultsCursor::new(body_ref, &borrowed_locals_results);
// Calculate the MIR locals that we actually need to keep storage around
// for.
- let requires_storage_analysis = RequiresStorage::new(body, &borrowed_locals_results);
- let requires_storage_results =
- do_dataflow(tcx, body, def_id, &[], &dead_unwinds, requires_storage_analysis,
- |bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
- let mut requires_storage_cursor = DataflowResultsCursor::new(&requires_storage_results, body);
+ let requires_storage_results = MaybeRequiresStorage::new(body, &borrowed_locals_results)
+ .into_engine(tcx, body_ref, def_id)
+ .iterate_to_fixpoint();
+ let mut requires_storage_cursor =
+ dataflow::ResultsCursor::new(body_ref, &requires_storage_results);
// Calculate the liveness of MIR locals ignoring borrows.
- let mut live_locals = liveness::LiveVarSet::new_empty(body.local_decls.len());
- let mut liveness = liveness::liveness_of_locals(
- body,
- );
- liveness::dump_mir(
- tcx,
- "generator_liveness",
- source,
- body,
- &liveness,
- );
+ let mut liveness = MaybeLiveLocals
+ .into_engine(tcx, body_ref, def_id)
+ .iterate_to_fixpoint()
+ .into_results_cursor(body_ref);
- let mut storage_liveness_map = FxHashMap::default();
+ let mut storage_liveness_map = IndexVec::from_elem(None, body.basic_blocks());
let mut live_locals_at_suspension_points = Vec::new();
+ let mut source_info_at_suspension_points = Vec::new();
+ let mut live_locals_at_any_suspension_point = BitSet::new_empty(body.local_decls.len());
for (block, data) in body.basic_blocks().iter_enumerated() {
if let TerminatorKind::Yield { .. } = data.terminator().kind {
- let loc = Location {
- block: block,
- statement_index: data.statements.len(),
- };
+ let loc = Location { block, statement_index: data.statements.len() };
+
+ liveness.seek_to_block_end(block);
+ let mut live_locals = liveness.get().clone();
if !movable {
// The `liveness` variable contains the liveness of MIR locals ignoring borrows.
// If a borrow is converted to a raw reference, we must also assume that it lives
// forever. Note that the final liveness is still bounded by the storage liveness
// of the local, which happens using the `intersect` operation below.
- borrowed_locals_cursor.seek(loc);
- liveness.outs[block].union(borrowed_locals_cursor.get());
+ borrowed_locals_cursor.seek_before_primary_effect(loc);
+ live_locals.union(borrowed_locals_cursor.get());
}
- storage_live_cursor.seek(loc);
- let storage_liveness = storage_live_cursor.get();
-
// Store the storage liveness for later use so we can restore the state
// after a suspension point
- storage_liveness_map.insert(block, storage_liveness.clone());
-
- requires_storage_cursor.seek(loc);
- let storage_required = requires_storage_cursor.get().clone();
+ storage_live.seek_before_primary_effect(loc);
+ storage_liveness_map[block] = Some(storage_live.get().clone());
// Locals live are live at this point only if they are used across
// suspension points (the `liveness` variable)
// and their storage is required (the `storage_required` variable)
- let mut live_locals_here = storage_required;
- live_locals_here.intersect(&liveness.outs[block]);
+ requires_storage_cursor.seek_before_primary_effect(loc);
+ live_locals.intersect(requires_storage_cursor.get());
- // The generator argument is ignored
- live_locals_here.remove(self_arg());
+ // The generator argument is ignored.
+ live_locals.remove(SELF_ARG);
- debug!("loc = {:?}, live_locals_here = {:?}", loc, live_locals_here);
+ debug!("loc = {:?}, live_locals = {:?}", loc, live_locals);
// Add the locals live at this suspension point to the set of locals which live across
// any suspension points
- live_locals.union(&live_locals_here);
+ live_locals_at_any_suspension_point.union(&live_locals);
- live_locals_at_suspension_points.push(live_locals_here);
+ live_locals_at_suspension_points.push(live_locals);
+ source_info_at_suspension_points.push(data.terminator().source_info);
}
}
- debug!("live_locals = {:?}", live_locals);
+
+ debug!("live_locals_anywhere = {:?}", live_locals_at_any_suspension_point);
+ let saved_locals = GeneratorSavedLocals(live_locals_at_any_suspension_point);
// Renumber our liveness_map bitsets to include only the locals we are
// saving.
let live_locals_at_suspension_points = live_locals_at_suspension_points
.iter()
- .map(|live_here| renumber_bitset(&live_here, &live_locals))
+ .map(|live_here| saved_locals.renumber_bitset(&live_here))
.collect();
let storage_conflicts = compute_storage_conflicts(
- body,
- &live_locals,
- &ignored,
- requires_storage_results);
+ body_ref,
+ &saved_locals,
+ always_live_locals.clone(),
+ requires_storage_results,
+ );
LivenessInfo {
- live_locals,
+ saved_locals,
live_locals_at_suspension_points,
+ source_info_at_suspension_points,
storage_conflicts,
storage_liveness: storage_liveness_map,
}
}
-/// Renumbers the items present in `stored_locals` and applies the renumbering
-/// to 'input`.
+/// The set of `Local`s that must be saved across yield points.
///
-/// For example, if `stored_locals = [1, 3, 5]`, this would be renumbered to
-/// `[0, 1, 2]`. Thus, if `input = [3, 5]` we would return `[1, 2]`.
-fn renumber_bitset(input: &BitSet<Local>, stored_locals: &liveness::LiveVarSet)
--> BitSet<GeneratorSavedLocal> {
- assert!(stored_locals.superset(&input), "{:?} not a superset of {:?}", stored_locals, input);
- let mut out = BitSet::new_empty(stored_locals.count());
- for (idx, local) in stored_locals.iter().enumerate() {
- let saved_local = GeneratorSavedLocal::from(idx);
- if input.contains(local) {
- out.insert(saved_local);
+/// `GeneratorSavedLocal` is indexed in terms of the elements in this set;
+/// i.e. `GeneratorSavedLocal::new(1)` corresponds to the second local
+/// included in this set.
+struct GeneratorSavedLocals(BitSet<Local>);
+
+impl GeneratorSavedLocals {
+ /// Returns an iterator over each `GeneratorSavedLocal` along with the `Local` it corresponds
+ /// to.
+ fn iter_enumerated(&self) -> impl '_ + Iterator<Item = (GeneratorSavedLocal, Local)> {
+ self.iter().enumerate().map(|(i, l)| (GeneratorSavedLocal::from(i), l))
+ }
+
+ /// Transforms a `BitSet<Local>` that contains only locals saved across yield points to the
+ /// equivalent `BitSet<GeneratorSavedLocal>`.
+ fn renumber_bitset(&self, input: &BitSet<Local>) -> BitSet<GeneratorSavedLocal> {
+ assert!(self.superset(&input), "{:?} not a superset of {:?}", self.0, input);
+ let mut out = BitSet::new_empty(self.count());
+ for (saved_local, local) in self.iter_enumerated() {
+ if input.contains(local) {
+ out.insert(saved_local);
+ }
+ }
+ out
+ }
+
+ fn get(&self, local: Local) -> Option<GeneratorSavedLocal> {
+ if !self.contains(local) {
+ return None;
}
+
+ let idx = self.iter().take_while(|&l| l < local).count();
+ Some(GeneratorSavedLocal::new(idx))
+ }
+}
+
+impl ops::Deref for GeneratorSavedLocals {
+ type Target = BitSet<Local>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
}
- debug!("renumber_bitset({:?}, {:?}) => {:?}", input, stored_locals, out);
- out
}
/// For every saved local, looks for which locals are StorageLive at the same
/// computation; see `GeneratorLayout` for more.
fn compute_storage_conflicts(
body: &'mir Body<'tcx>,
- stored_locals: &liveness::LiveVarSet,
- ignored: &StorageIgnored,
- requires_storage: DataflowResults<'tcx, RequiresStorage<'mir, 'tcx>>,
+ saved_locals: &GeneratorSavedLocals,
+ always_live_locals: storage::AlwaysLiveLocals,
+ requires_storage: dataflow::Results<'tcx, MaybeRequiresStorage<'mir, 'tcx>>,
) -> BitMatrix<GeneratorSavedLocal, GeneratorSavedLocal> {
- assert_eq!(body.local_decls.len(), ignored.0.domain_size());
- assert_eq!(body.local_decls.len(), stored_locals.domain_size());
+ assert_eq!(body.local_decls.len(), saved_locals.domain_size());
+
debug!("compute_storage_conflicts({:?})", body.span);
- debug!("ignored = {:?}", ignored.0);
+ debug!("always_live = {:?}", always_live_locals);
- // Storage ignored locals are not eligible for overlap, since their storage
- // is always live.
- let mut ineligible_locals = ignored.0.clone();
- ineligible_locals.intersect(&stored_locals);
+ // Locals that are always live or ones that need to be stored across
+ // suspension points are not eligible for overlap.
+ let mut ineligible_locals = always_live_locals.into_inner();
+ ineligible_locals.intersect(saved_locals);
// Compute the storage conflicts for all eligible locals.
let mut visitor = StorageConflictVisitor {
body,
- stored_locals: &stored_locals,
+ saved_locals: &saved_locals,
local_conflicts: BitMatrix::from_row_n(&ineligible_locals, body.local_decls.len()),
};
- let mut state = FlowAtLocation::new(requires_storage);
- visitor.analyze_results(&mut state);
+
+ requires_storage.visit_reachable_with(body, &mut visitor);
+
let local_conflicts = visitor.local_conflicts;
// Compress the matrix using only stored locals (Local -> GeneratorSavedLocal).
// However, in practice these bitsets are not usually large. The layout code
// also needs to keep track of how many conflicts each local has, so it's
// simpler to keep it this way for now.
- let mut storage_conflicts = BitMatrix::new(stored_locals.count(), stored_locals.count());
- for (idx_a, local_a) in stored_locals.iter().enumerate() {
- let saved_local_a = GeneratorSavedLocal::new(idx_a);
+ let mut storage_conflicts = BitMatrix::new(saved_locals.count(), saved_locals.count());
+ for (saved_local_a, local_a) in saved_locals.iter_enumerated() {
if ineligible_locals.contains(local_a) {
// Conflicts with everything.
storage_conflicts.insert_all_into_row(saved_local_a);
} else {
// Keep overlap information only for stored locals.
- for (idx_b, local_b) in stored_locals.iter().enumerate() {
- let saved_local_b = GeneratorSavedLocal::new(idx_b);
+ for (saved_local_b, local_b) in saved_locals.iter_enumerated() {
if local_conflicts.contains(local_a, local_b) {
storage_conflicts.insert(saved_local_a, saved_local_b);
}
storage_conflicts
}
-struct StorageConflictVisitor<'body, 'tcx, 's> {
- body: &'body Body<'tcx>,
- stored_locals: &'s liveness::LiveVarSet,
+struct StorageConflictVisitor<'mir, 'tcx, 's> {
+ body: &'mir Body<'tcx>,
+ saved_locals: &'s GeneratorSavedLocals,
// FIXME(tmandry): Consider using sparse bitsets here once we have good
// benchmarks for generators.
local_conflicts: BitMatrix<Local, Local>,
}
-impl<'body, 'tcx, 's> DataflowResultsConsumer<'body, 'tcx>
- for StorageConflictVisitor<'body, 'tcx, 's>
-{
- type FlowState = FlowAtLocation<'tcx, RequiresStorage<'body, 'tcx>>;
-
- fn body(&self) -> &'body Body<'tcx> {
- self.body
- }
-
- fn visit_block_entry(&mut self,
- block: BasicBlock,
- flow_state: &Self::FlowState) {
- // statement_index is only used for logging, so this is fine.
- self.apply_state(flow_state, Location { block, statement_index: 0 });
- }
+impl dataflow::ResultsVisitor<'mir, 'tcx> for StorageConflictVisitor<'mir, 'tcx, '_> {
+ type FlowState = BitSet<Local>;
- fn visit_statement_entry(&mut self,
- loc: Location,
- _stmt: &Statement<'tcx>,
- flow_state: &Self::FlowState) {
- self.apply_state(flow_state, loc);
+ fn visit_statement_before_primary_effect(
+ &mut self,
+ state: &Self::FlowState,
+ _statement: &'mir Statement<'tcx>,
+ loc: Location,
+ ) {
+ self.apply_state(state, loc);
}
- fn visit_terminator_entry(&mut self,
- loc: Location,
- _term: &Terminator<'tcx>,
- flow_state: &Self::FlowState) {
- self.apply_state(flow_state, loc);
+ fn visit_terminator_before_primary_effect(
+ &mut self,
+ state: &Self::FlowState,
+ _terminator: &'mir Terminator<'tcx>,
+ loc: Location,
+ ) {
+ self.apply_state(state, loc);
}
}
impl<'body, 'tcx, 's> StorageConflictVisitor<'body, 'tcx, 's> {
- fn apply_state(&mut self,
- flow_state: &FlowAtLocation<'tcx, RequiresStorage<'body, 'tcx>>,
- loc: Location) {
+ fn apply_state(&mut self, flow_state: &BitSet<Local>, loc: Location) {
// Ignore unreachable blocks.
- match self.body.basic_blocks()[loc.block].terminator().kind {
- TerminatorKind::Unreachable => return,
- _ => (),
- };
+ if self.body.basic_blocks()[loc.block].terminator().kind == TerminatorKind::Unreachable {
+ return;
+ }
- let mut eligible_storage_live = flow_state.as_dense().clone();
- eligible_storage_live.intersect(&self.stored_locals);
+ let mut eligible_storage_live = flow_state.clone();
+ eligible_storage_live.intersect(&self.saved_locals);
for local in eligible_storage_live.iter() {
self.local_conflicts.union_row_with(&eligible_storage_live, local);
}
}
-fn compute_layout<'tcx>(
+/// Validates the typeck view of the generator against the actual set of types saved between
+/// yield points.
+fn sanitize_witness<'tcx>(
tcx: TyCtxt<'tcx>,
- source: MirSource<'tcx>,
+ body: &Body<'tcx>,
+ did: DefId,
+ witness: Ty<'tcx>,
upvars: &Vec<Ty<'tcx>>,
- interior: Ty<'tcx>,
- movable: bool,
- body: &mut Body<'tcx>,
-) -> (
- FxHashMap<Local, (Ty<'tcx>, VariantIdx, usize)>,
- GeneratorLayout<'tcx>,
- FxHashMap<BasicBlock, liveness::LiveVarSet>,
+ saved_locals: &GeneratorSavedLocals,
) {
- // Use a liveness analysis to compute locals which are live across a suspension point
- let LivenessInfo {
- live_locals, live_locals_at_suspension_points, storage_conflicts, storage_liveness
- } = locals_live_across_suspend_points(tcx, body, source, movable);
-
- // Erase regions from the types passed in from typeck so we can compare them with
- // MIR types
let allowed_upvars = tcx.erase_regions(upvars);
- let allowed = match interior.kind {
+ let allowed = match witness.kind {
ty::GeneratorWitness(s) => tcx.erase_late_bound_regions(&s),
- _ => bug!(),
+ _ => {
+ tcx.sess.delay_span_bug(
+ body.span,
+ &format!("unexpected generator witness type {:?}", witness.kind),
+ );
+ return;
+ }
};
+ let param_env = tcx.param_env(did);
+
for (local, decl) in body.local_decls.iter_enumerated() {
- // Ignore locals which are internal or not live
- if !live_locals.contains(local) || decl.internal {
+ // Ignore locals which are internal or not saved between yields.
+ if !saved_locals.contains(local) || decl.internal {
continue;
}
+ let decl_ty = tcx.normalize_erasing_regions(param_env, decl.ty);
// Sanity check that typeck knows about the type of locals which are
// live across a suspension point
- if !allowed.contains(&decl.ty) && !allowed_upvars.contains(&decl.ty) {
- span_bug!(body.span,
- "Broken MIR: generator contains type {} in MIR, \
+ if !allowed.contains(&decl_ty) && !allowed_upvars.contains(&decl_ty) {
+ span_bug!(
+ body.span,
+ "Broken MIR: generator contains type {} in MIR, \
but typeck only knows about {}",
- decl.ty,
- interior);
+ decl.ty,
+ witness,
+ );
}
}
+}
- let dummy_local = LocalDecl::new_internal(tcx.mk_unit(), body.span);
+fn compute_layout<'tcx>(
+ liveness: LivenessInfo,
+ body: &mut Body<'tcx>,
+) -> (
+ FxHashMap<Local, (Ty<'tcx>, VariantIdx, usize)>,
+ GeneratorLayout<'tcx>,
+ IndexVec<BasicBlock, Option<BitSet<Local>>>,
+) {
+ let LivenessInfo {
+ saved_locals,
+ live_locals_at_suspension_points,
+ source_info_at_suspension_points,
+ storage_conflicts,
+ storage_liveness,
+ } = liveness;
- // Gather live locals and their indices replacing values in body.local_decls
- // with a dummy to avoid changing local indices.
+ // Gather live local types and their indices.
let mut locals = IndexVec::<GeneratorSavedLocal, _>::new();
let mut tys = IndexVec::<GeneratorSavedLocal, _>::new();
- let mut decls = IndexVec::<GeneratorSavedLocal, _>::new();
- for (idx, local) in live_locals.iter().enumerate() {
- let var = mem::replace(&mut body.local_decls[local], dummy_local.clone());
+ for (saved_local, local) in saved_locals.iter_enumerated() {
locals.push(local);
- tys.push(var.ty);
- decls.push(var);
- debug!("generator saved local {:?} => {:?}", GeneratorSavedLocal::from(idx), local);
+ tys.push(body.local_decls[local].ty);
+ debug!("generator saved local {:?} => {:?}", saved_local, local);
}
// Leave empty variants for the UNRESUMED, RETURNED, and POISONED states.
+ // In debuginfo, these will correspond to the beginning (UNRESUMED) or end
+ // (RETURNED, POISONED) of the function.
const RESERVED_VARIANTS: usize = 3;
+ let body_span = body.source_scopes[OUTERMOST_SOURCE_SCOPE].span;
+ let mut variant_source_info: IndexVec<VariantIdx, SourceInfo> = [
+ SourceInfo::outermost(body_span.shrink_to_lo()),
+ SourceInfo::outermost(body_span.shrink_to_hi()),
+ SourceInfo::outermost(body_span.shrink_to_hi()),
+ ]
+ .iter()
+ .copied()
+ .collect();
// Build the generator variant field list.
// Create a map from local indices to generator struct indices.
remap.entry(locals[saved_local]).or_insert((tys[saved_local], variant_index, idx));
}
variant_fields.push(fields);
+ variant_source_info.push(source_info_at_suspension_points[suspension_point_idx]);
}
debug!("generator variant_fields = {:?}", variant_fields);
debug!("generator storage_conflicts = {:#?}", storage_conflicts);
- let layout = GeneratorLayout {
- field_tys: tys,
- variant_fields,
- storage_conflicts,
- __local_debuginfo_codegen_only_do_not_use: decls,
- };
+ let layout =
+ GeneratorLayout { field_tys: tys, variant_fields, variant_source_info, storage_conflicts };
(remap, layout, storage_liveness)
}
+/// Replaces the entry point of `body` with a block that switches on the generator discriminant and
+/// dispatches to blocks according to `cases`.
+///
+/// After this function, the former entry point of the function will be bb1.
fn insert_switch<'tcx>(
body: &mut Body<'tcx>,
cases: Vec<(usize, BasicBlock)>,
targets: cases.iter().map(|&(_, d)| d).chain(iter::once(default_block)).collect(),
};
- let source_info = source_info(body);
- body.basic_blocks_mut().raw.insert(0, BasicBlockData {
- statements: vec![assign],
- terminator: Some(Terminator {
- source_info,
- kind: switch,
- }),
- is_cleanup: false,
- });
+ let source_info = SourceInfo::outermost(body.span);
+ body.basic_blocks_mut().raw.insert(
+ 0,
+ BasicBlockData {
+ statements: vec![assign],
+ terminator: Some(Terminator { source_info, kind: switch }),
+ is_cleanup: false,
+ },
+ );
let blocks = body.basic_blocks_mut().iter_mut();
}
fn elaborate_generator_drops<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, body: &mut Body<'tcx>) {
+ use crate::shim::DropShimElaborator;
use crate::util::elaborate_drops::{elaborate_drop, Unwind};
use crate::util::patch::MirPatch;
- use crate::shim::DropShimElaborator;
// Note that `elaborate_drops` only drops the upvars of a generator, and
// this is ok because `open_drop` can only be reached within that own
// generator's resume function.
let param_env = tcx.param_env(def_id);
- let gen = self_arg();
- let mut elaborator = DropShimElaborator {
- body: body,
- patch: MirPatch::new(body),
- tcx,
- param_env
- };
+ let mut elaborator = DropShimElaborator { body, patch: MirPatch::new(body), tcx, param_env };
for (block, block_data) in body.basic_blocks().iter_enumerated() {
let (target, unwind, source_info) = match block_data.terminator() {
- Terminator {
- source_info,
- kind: TerminatorKind::Drop {
- location,
- target,
- unwind
- }
- } => {
- if let Some(local) = location.as_local() {
- if local == gen {
+ Terminator { source_info, kind: TerminatorKind::Drop { place, target, unwind } } => {
+ if let Some(local) = place.as_local() {
+ if local == SELF_ARG {
(target, unwind, source_info)
} else {
continue;
elaborate_drop(
&mut elaborator,
*source_info,
- &Place::from(gen),
+ Place::from(SELF_ARG),
(),
*target,
unwind,
fn create_generator_drop_shim<'tcx>(
tcx: TyCtxt<'tcx>,
transform: &TransformVisitor<'tcx>,
- def_id: DefId,
source: MirSource<'tcx>,
gen_ty: Ty<'tcx>,
- body: &Body<'tcx>,
+ body: &mut Body<'tcx>,
drop_clean: BasicBlock,
) -> Body<'tcx> {
let mut body = body.clone();
+ body.arg_count = 1; // make sure the resume argument is not included here
- let source_info = source_info(&body);
+ let source_info = SourceInfo::outermost(body.span);
- let mut cases = create_cases(&mut body, transform, |point| point.drop);
+ let mut cases = create_cases(&mut body, transform, Operation::Drop);
cases.insert(0, (UNRESUMED, drop_clean));
}
// Replace the return variable
- body.local_decls[RETURN_PLACE] = LocalDecl {
- mutability: Mutability::Mut,
- ty: tcx.mk_unit(),
- user_ty: UserTypeProjections::none(),
- name: None,
- source_info,
- visibility_scope: source_info.scope,
- internal: false,
- is_block_tail: None,
- is_user_variable: None,
- };
+ body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(tcx.mk_unit(), source_info);
- make_generator_state_argument_indirect(tcx, def_id, &mut body);
+ make_generator_state_argument_indirect(tcx, &mut body);
// Change the generator argument from &mut to *mut
- body.local_decls[self_arg()] = LocalDecl {
- mutability: Mutability::Mut,
- ty: tcx.mk_ptr(ty::TypeAndMut {
- ty: gen_ty,
- mutbl: hir::Mutability::MutMutable,
- }),
- user_ty: UserTypeProjections::none(),
- name: None,
+ body.local_decls[SELF_ARG] = LocalDecl::with_source_info(
+ tcx.mk_ptr(ty::TypeAndMut { ty: gen_ty, mutbl: hir::Mutability::Mut }),
source_info,
- visibility_scope: source_info.scope,
- internal: false,
- is_block_tail: None,
- is_user_variable: None,
- };
+ );
if tcx.sess.opts.debugging_opts.mir_emit_retag {
// Alias tracking must know we changed the type
- body.basic_blocks_mut()[START_BLOCK].statements.insert(0, Statement {
- source_info,
- kind: StatementKind::Retag(RetagKind::Raw, box Place::from(self_arg())),
- })
+ body.basic_blocks_mut()[START_BLOCK].statements.insert(
+ 0,
+ Statement {
+ source_info,
+ kind: StatementKind::Retag(RetagKind::Raw, box Place::from(SELF_ARG)),
+ },
+ )
}
no_landing_pads(tcx, &mut body);
// unrelated code from the resume part of the function
simplify::remove_dead_blocks(&mut body);
- dump_mir(tcx, None, "generator_drop", &0, source, &mut body, |_, _| Ok(()) );
+ dump_mir(tcx, None, "generator_drop", &0, source, &body, |_, _| Ok(()));
body
}
fn insert_term_block<'tcx>(body: &mut Body<'tcx>, kind: TerminatorKind<'tcx>) -> BasicBlock {
- let term_block = BasicBlock::new(body.basic_blocks().len());
- let source_info = source_info(body);
+ let source_info = SourceInfo::outermost(body.span);
body.basic_blocks_mut().push(BasicBlockData {
statements: Vec::new(),
- terminator: Some(Terminator {
- source_info,
- kind,
- }),
+ terminator: Some(Terminator { source_info, kind }),
is_cleanup: false,
- });
- term_block
+ })
}
fn insert_panic_block<'tcx>(
cleanup: None,
};
- let source_info = source_info(body);
+ let source_info = SourceInfo::outermost(body.span);
body.basic_blocks_mut().push(BasicBlockData {
statements: Vec::new(),
- terminator: Some(Terminator {
- source_info,
- kind: term,
- }),
+ terminator: Some(Terminator { source_info, kind: term }),
is_cleanup: false,
});
assert_block
}
+fn can_return<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool {
+ // Returning from a function with an uninhabited return type is undefined behavior.
+ if body.return_ty().conservative_is_privately_uninhabited(tcx) {
+ return false;
+ }
+
+ // If there's a return terminator the function may return.
+ for block in body.basic_blocks() {
+ if let TerminatorKind::Return = block.terminator().kind {
+ return true;
+ }
+ }
+
+ // Otherwise the function can't return.
+ false
+}
+
+fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool {
+ // Nothing can unwind when landing pads are off.
+ if tcx.sess.panic_strategy() == PanicStrategy::Abort {
+ return false;
+ }
+
+ // Unwinds can only start at certain terminators.
+ for block in body.basic_blocks() {
+ match block.terminator().kind {
+ // These never unwind.
+ TerminatorKind::Goto { .. }
+ | TerminatorKind::SwitchInt { .. }
+ | TerminatorKind::Abort
+ | TerminatorKind::Return
+ | TerminatorKind::Unreachable
+ | TerminatorKind::GeneratorDrop
+ | TerminatorKind::FalseEdge { .. }
+ | TerminatorKind::FalseUnwind { .. }
+ | TerminatorKind::InlineAsm { .. } => {}
+
+ // Resume will *continue* unwinding, but if there's no other unwinding terminator it
+ // will never be reached.
+ TerminatorKind::Resume => {}
+
+ TerminatorKind::Yield { .. } => {
+ unreachable!("`can_unwind` called before generator transform")
+ }
+
+ // These may unwind.
+ TerminatorKind::Drop { .. }
+ | TerminatorKind::DropAndReplace { .. }
+ | TerminatorKind::Call { .. }
+ | TerminatorKind::Assert { .. } => return true,
+ }
+ }
+
+ // If we didn't find an unwinding terminator, the function cannot unwind.
+ false
+}
+
fn create_generator_resume_function<'tcx>(
tcx: TyCtxt<'tcx>,
transform: TransformVisitor<'tcx>,
- def_id: DefId,
source: MirSource<'tcx>,
body: &mut Body<'tcx>,
+ can_return: bool,
) {
+ let can_unwind = can_unwind(tcx, body);
+
// Poison the generator when it unwinds
- for block in body.basic_blocks_mut() {
- let source_info = block.terminator().source_info;
- if let &TerminatorKind::Resume = &block.terminator().kind {
- block.statements.push(
- transform.set_discr(VariantIdx::new(POISONED), source_info));
+ if can_unwind {
+ let source_info = SourceInfo::outermost(body.span);
+ let poison_block = body.basic_blocks_mut().push(BasicBlockData {
+ statements: vec![transform.set_discr(VariantIdx::new(POISONED), source_info)],
+ terminator: Some(Terminator { source_info, kind: TerminatorKind::Resume }),
+ is_cleanup: true,
+ });
+
+ for (idx, block) in body.basic_blocks_mut().iter_enumerated_mut() {
+ let source_info = block.terminator().source_info;
+
+ if let TerminatorKind::Resume = block.terminator().kind {
+ // An existing `Resume` terminator is redirected to jump to our dedicated
+ // "poisoning block" above.
+ if idx != poison_block {
+ *block.terminator_mut() = Terminator {
+ source_info,
+ kind: TerminatorKind::Goto { target: poison_block },
+ };
+ }
+ } else if !block.is_cleanup {
+ // Any terminators that *can* unwind but don't have an unwind target set are also
+ // pointed at our poisoning block (unless they're part of the cleanup path).
+ if let Some(unwind @ None) = block.terminator_mut().unwind_mut() {
+ *unwind = Some(poison_block);
+ }
+ }
}
}
- let mut cases = create_cases(body, &transform, |point| Some(point.resume));
+ let mut cases = create_cases(body, &transform, Operation::Resume);
- use rustc::mir::interpret::PanicInfo::{
- GeneratorResumedAfterPanic,
- GeneratorResumedAfterReturn,
- };
+ use rustc_middle::mir::AssertKind::{ResumedAfterPanic, ResumedAfterReturn};
// Jump to the entry point on the unresumed
cases.insert(0, (UNRESUMED, BasicBlock::new(0)));
- // Panic when resumed on the returned state
- cases.insert(1, (RETURNED, insert_panic_block(tcx, body, GeneratorResumedAfterReturn)));
- // Panic when resumed on the poisoned state
- cases.insert(2, (POISONED, insert_panic_block(tcx, body, GeneratorResumedAfterPanic)));
+
+ // Panic when resumed on the returned or poisoned state
+ let generator_kind = body.generator_kind.unwrap();
+
+ if can_unwind {
+ cases.insert(
+ 1,
+ (POISONED, insert_panic_block(tcx, body, ResumedAfterPanic(generator_kind))),
+ );
+ }
+
+ if can_return {
+ cases.insert(
+ 1,
+ (RETURNED, insert_panic_block(tcx, body, ResumedAfterReturn(generator_kind))),
+ );
+ }
insert_switch(body, cases, &transform, TerminatorKind::Unreachable);
- make_generator_state_argument_indirect(tcx, def_id, body);
+ make_generator_state_argument_indirect(tcx, body);
make_generator_state_argument_pinned(tcx, body);
no_landing_pads(tcx, body);
// unrelated code from the drop part of the function
simplify::remove_dead_blocks(body);
- dump_mir(tcx, None, "generator_resume", &0, source, body, |_, _| Ok(()) );
-}
-
-fn source_info(body: &Body<'_>) -> SourceInfo {
- SourceInfo {
- span: body.span,
- scope: OUTERMOST_SOURCE_SCOPE,
- }
+ dump_mir(tcx, None, "generator_resume", &0, source, body, |_, _| Ok(()));
}
fn insert_clean_drop(body: &mut Body<'_>) -> BasicBlock {
let return_block = insert_term_block(body, TerminatorKind::Return);
+ let term =
+ TerminatorKind::Drop { place: Place::from(SELF_ARG), target: return_block, unwind: None };
+ let source_info = SourceInfo::outermost(body.span);
+
// Create a block to destroy an unresumed generators. This can only destroy upvars.
- let drop_clean = BasicBlock::new(body.basic_blocks().len());
- let term = TerminatorKind::Drop {
- location: Place::from(self_arg()),
- target: return_block,
- unwind: None,
- };
- let source_info = source_info(body);
body.basic_blocks_mut().push(BasicBlockData {
statements: Vec::new(),
- terminator: Some(Terminator {
- source_info,
- kind: term,
- }),
+ terminator: Some(Terminator { source_info, kind: term }),
is_cleanup: false,
- });
+ })
+}
- drop_clean
+/// An operation that can be performed on a generator.
+#[derive(PartialEq, Copy, Clone)]
+enum Operation {
+ Resume,
+ Drop,
+}
+
+impl Operation {
+ fn target_block(self, point: &SuspensionPoint<'_>) -> Option<BasicBlock> {
+ match self {
+ Operation::Resume => Some(point.resume),
+ Operation::Drop => point.drop,
+ }
+ }
}
-fn create_cases<'tcx, F>(
+fn create_cases<'tcx>(
body: &mut Body<'tcx>,
transform: &TransformVisitor<'tcx>,
- target: F,
-) -> Vec<(usize, BasicBlock)>
-where
- F: Fn(&SuspensionPoint) -> Option<BasicBlock>,
-{
- let source_info = source_info(body);
-
- transform.suspension_points.iter().filter_map(|point| {
- // Find the target for this suspension point, if applicable
- target(point).map(|target| {
- let block = BasicBlock::new(body.basic_blocks().len());
- let mut statements = Vec::new();
-
- // Create StorageLive instructions for locals with live storage
- for i in 0..(body.local_decls.len()) {
- let l = Local::new(i);
- if point.storage_liveness.contains(l) && !transform.remap.contains_key(&l) {
+ operation: Operation,
+) -> Vec<(usize, BasicBlock)> {
+ let source_info = SourceInfo::outermost(body.span);
+
+ transform
+ .suspension_points
+ .iter()
+ .filter_map(|point| {
+ // Find the target for this suspension point, if applicable
+ operation.target_block(point).map(|target| {
+ let mut statements = Vec::new();
+
+ // Create StorageLive instructions for locals with live storage
+ for i in 0..(body.local_decls.len()) {
+ if i == 2 {
+ // The resume argument is live on function entry. Don't insert a
+ // `StorageLive`, or the following `Assign` will read from uninitialized
+ // memory.
+ continue;
+ }
+
+ let l = Local::new(i);
+ let needs_storage_live = point.storage_liveness.contains(l)
+ && !transform.remap.contains_key(&l)
+ && !transform.always_live_locals.contains(l);
+ if needs_storage_live {
+ statements
+ .push(Statement { source_info, kind: StatementKind::StorageLive(l) });
+ }
+ }
+
+ if operation == Operation::Resume {
+ // Move the resume argument to the destination place of the `Yield` terminator
+ let resume_arg = Local::new(2); // 0 = return, 1 = self
statements.push(Statement {
source_info,
- kind: StatementKind::StorageLive(l),
+ kind: StatementKind::Assign(box (
+ point.resume_arg,
+ Rvalue::Use(Operand::Move(resume_arg.into())),
+ )),
});
}
- }
- // Then jump to the real target
- body.basic_blocks_mut().push(BasicBlockData {
- statements,
- terminator: Some(Terminator {
- source_info,
- kind: TerminatorKind::Goto {
- target,
- },
- }),
- is_cleanup: false,
- });
-
- (point.state, block)
+ // Then jump to the real target
+ let block = body.basic_blocks_mut().push(BasicBlockData {
+ statements,
+ terminator: Some(Terminator {
+ source_info,
+ kind: TerminatorKind::Goto { target },
+ }),
+ is_cleanup: false,
+ });
+
+ (point.state, block)
+ })
})
- }).collect()
+ .collect()
}
impl<'tcx> MirPass<'tcx> for StateTransform {
yield_ty
} else {
// This only applies to generators
- return
+ return;
};
assert!(body.generator_drop.is_none());
let (upvars, interior, discr_ty, movable) = match gen_ty.kind {
ty::Generator(_, substs, movability) => {
let substs = substs.as_generator();
- (substs.upvar_tys(def_id, tcx).collect(),
- substs.witness(def_id, tcx),
- substs.discr_ty(tcx),
- movability == hir::GeneratorMovability::Movable)
+ (
+ substs.upvar_tys().collect(),
+ substs.witness(),
+ substs.discr_ty(tcx),
+ movability == hir::Movability::Movable,
+ )
+ }
+ _ => {
+ tcx.sess
+ .delay_span_bug(body.span, &format!("unexpected generator type {}", gen_ty));
+ return;
}
- _ => bug!(),
};
// Compute GeneratorState<yield_ty, return_ty>
- let state_did = tcx.lang_items().gen_state().unwrap();
+ let state_did = tcx.require_lang_item(LangItem::GeneratorState, None);
let state_adt_ref = tcx.adt_def(state_did);
- let state_substs = tcx.intern_substs(&[
- yield_ty.into(),
- body.return_ty().into(),
- ]);
+ let state_substs = tcx.intern_substs(&[yield_ty.into(), body.return_ty().into()]);
let ret_ty = tcx.mk_adt(state_adt_ref, state_substs);
// We rename RETURN_PLACE which has type mir.return_ty to new_ret_local
// RETURN_PLACE then is a fresh unused local with type ret_ty.
- let new_ret_local = replace_result_variable(ret_ty, body, tcx);
+ let new_ret_local = replace_local(RETURN_PLACE, ret_ty, body, tcx);
+
+ // We also replace the resume argument and insert an `Assign`.
+ // This is needed because the resume argument `_2` might be live across a `yield`, in which
+ // case there is no `Assign` to it that the transform can turn into a store to the generator
+ // state. After the yield the slot in the generator state would then be uninitialized.
+ let resume_local = Local::new(2);
+ let new_resume_local =
+ replace_local(resume_local, body.local_decls[resume_local].ty, body, tcx);
+
+ // When first entering the generator, move the resume argument into its new local.
+ let source_info = SourceInfo::outermost(body.span);
+ let stmts = &mut body.basic_blocks_mut()[BasicBlock::new(0)].statements;
+ stmts.insert(
+ 0,
+ Statement {
+ source_info,
+ kind: StatementKind::Assign(box (
+ new_resume_local.into(),
+ Rvalue::Use(Operand::Move(resume_local.into())),
+ )),
+ },
+ );
+
+ let always_live_locals = storage::AlwaysLiveLocals::new(&body);
+
+ let liveness_info =
+ locals_live_across_suspend_points(tcx, body, source, &always_live_locals, movable);
+
+ sanitize_witness(tcx, body, def_id, interior, &upvars, &liveness_info.saved_locals);
+
+ if tcx.sess.opts.debugging_opts.validate_mir {
+ let mut vis = EnsureGeneratorFieldAssignmentsNeverAlias {
+ assigned_local: None,
+ saved_locals: &liveness_info.saved_locals,
+ storage_conflicts: &liveness_info.storage_conflicts,
+ };
+
+ vis.visit_body(body);
+ }
// Extract locals which are live across suspension point into `layout`
// `remap` gives a mapping from local indices onto generator struct indices
// `storage_liveness` tells us which locals have live storage at suspension points
- let (remap, layout, storage_liveness) = compute_layout(
- tcx,
- source,
- &upvars,
- interior,
- movable,
- body);
+ let (remap, layout, storage_liveness) = compute_layout(liveness_info, body);
+
+ let can_return = can_return(tcx, body);
// Run the transformation which converts Places from Local to generator struct
// accesses for locals in `remap`.
state_substs,
remap,
storage_liveness,
+ always_live_locals,
suspension_points: Vec::new(),
new_ret_local,
discr_ty,
};
transform.visit_body(body);
- // Update our MIR struct to reflect the changed we've made
+ // Update our MIR struct to reflect the changes we've made
body.yield_ty = None;
- body.arg_count = 1;
+ body.arg_count = 2; // self, resume arg
body.spread_arg = None;
body.generator_layout = Some(layout);
// This is expanded to a drop ladder in `elaborate_generator_drops`.
let drop_clean = insert_clean_drop(body);
- dump_mir(tcx, None, "generator_pre-elab", &0, source, body, |_, _| Ok(()) );
+ dump_mir(tcx, None, "generator_pre-elab", &0, source, body, |_, _| Ok(()));
// Expand `drop(generator_struct)` to a drop ladder which destroys upvars.
// If any upvars are moved out of, drop elaboration will handle upvar destruction.
// However we need to also elaborate the code generated by `insert_clean_drop`.
elaborate_generator_drops(tcx, def_id, body);
- dump_mir(tcx, None, "generator_post-transform", &0, source, body, |_, _| Ok(()) );
+ dump_mir(tcx, None, "generator_post-transform", &0, source, body, |_, _| Ok(()));
// Create a copy of our MIR and use it to create the drop shim for the generator
- let drop_shim = create_generator_drop_shim(tcx,
- &transform,
- def_id,
- source,
- gen_ty,
- &body,
- drop_clean);
+ let drop_shim =
+ create_generator_drop_shim(tcx, &transform, source, gen_ty, body, drop_clean);
body.generator_drop = Some(box drop_shim);
// Create the Generator::resume function
- create_generator_resume_function(tcx, transform, def_id, source, body);
+ create_generator_resume_function(tcx, transform, source, body, can_return);
+ }
+}
+
+/// Looks for any assignments between locals (e.g., `_4 = _5`) that will both be converted to fields
+/// in the generator state machine but whose storage is not marked as conflicting
+///
+/// Validation needs to happen immediately *before* `TransformVisitor` is invoked, not after.
+///
+/// This condition would arise when the assignment is the last use of `_5` but the initial
+/// definition of `_4` if we weren't extra careful to mark all locals used inside a statement as
+/// conflicting. Non-conflicting generator saved locals may be stored at the same location within
+/// the generator state machine, which would result in ill-formed MIR: the left-hand and right-hand
+/// sides of an assignment may not alias. This caused a miscompilation in [#73137].
+///
+/// [#73137]: https://github.com/rust-lang/rust/issues/73137
+struct EnsureGeneratorFieldAssignmentsNeverAlias<'a> {
+ saved_locals: &'a GeneratorSavedLocals,
+ storage_conflicts: &'a BitMatrix<GeneratorSavedLocal, GeneratorSavedLocal>,
+ assigned_local: Option<GeneratorSavedLocal>,
+}
+
+impl EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
+ fn saved_local_for_direct_place(&self, place: Place<'_>) -> Option<GeneratorSavedLocal> {
+ if place.is_indirect() {
+ return None;
+ }
+
+ self.saved_locals.get(place.local)
+ }
+
+ fn check_assigned_place(&mut self, place: Place<'tcx>, f: impl FnOnce(&mut Self)) {
+ if let Some(assigned_local) = self.saved_local_for_direct_place(place) {
+ assert!(self.assigned_local.is_none(), "`check_assigned_place` must not recurse");
+
+ self.assigned_local = Some(assigned_local);
+ f(self);
+ self.assigned_local = None;
+ }
+ }
+}
+
+impl Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
+ fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
+ let lhs = match self.assigned_local {
+ Some(l) => l,
+ None => {
+ // This visitor only invokes `visit_place` for the right-hand side of an assignment
+ // and only after setting `self.assigned_local`. However, the default impl of
+ // `Visitor::super_body` may call `visit_place` with a `NonUseContext` for places
+ // with debuginfo. Ignore them here.
+ assert!(!context.is_use());
+ return;
+ }
+ };
+
+ let rhs = match self.saved_local_for_direct_place(*place) {
+ Some(l) => l,
+ None => return,
+ };
+
+ if !self.storage_conflicts.contains(lhs, rhs) {
+ bug!(
+ "Assignment between generator saved locals whose storage is not \
+ marked as conflicting: {:?}: {:?} = {:?}",
+ location,
+ lhs,
+ rhs,
+ );
+ }
+ }
+
+ fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
+ match &statement.kind {
+ StatementKind::Assign(box (lhs, rhs)) => {
+ self.check_assigned_place(*lhs, |this| this.visit_rvalue(rhs, location));
+ }
+
+ // FIXME: Does `llvm_asm!` have any aliasing requirements?
+ StatementKind::LlvmInlineAsm(_) => {}
+
+ StatementKind::FakeRead(..)
+ | StatementKind::SetDiscriminant { .. }
+ | StatementKind::StorageLive(_)
+ | StatementKind::StorageDead(_)
+ | StatementKind::Retag(..)
+ | StatementKind::AscribeUserType(..)
+ | StatementKind::Coverage(..)
+ | StatementKind::Nop => {}
+ }
+ }
+
+ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
+ // Checking for aliasing in terminators is probably overkill, but until we have actual
+ // semantics, we should be conservative here.
+ match &terminator.kind {
+ TerminatorKind::Call {
+ func,
+ args,
+ destination: Some((dest, _)),
+ cleanup: _,
+ from_hir_call: _,
+ fn_span: _,
+ } => {
+ self.check_assigned_place(*dest, |this| {
+ this.visit_operand(func, location);
+ for arg in args {
+ this.visit_operand(arg, location);
+ }
+ });
+ }
+
+ TerminatorKind::Yield { value, resume: _, resume_arg, drop: _ } => {
+ self.check_assigned_place(*resume_arg, |this| this.visit_operand(value, location));
+ }
+
+ // FIXME: Does `asm!` have any aliasing requirements?
+ TerminatorKind::InlineAsm { .. } => {}
+
+ TerminatorKind::Call { .. }
+ | TerminatorKind::Goto { .. }
+ | TerminatorKind::SwitchInt { .. }
+ | TerminatorKind::Resume
+ | TerminatorKind::Abort
+ | TerminatorKind::Return
+ | TerminatorKind::Unreachable
+ | TerminatorKind::Drop { .. }
+ | TerminatorKind::DropAndReplace { .. }
+ | TerminatorKind::Assert { .. }
+ | TerminatorKind::GeneratorDrop
+ | TerminatorKind::FalseEdge { .. }
+ | TerminatorKind::FalseUnwind { .. } => {}
+ }
}
}