Analyse storage liveness and preserve it during generator transformation
This commit is contained in:
parent
539f2083de
commit
efc7d46188
@ -132,6 +132,11 @@ pub trait BitwiseOperator {
|
||||
fn join(&self, pred1: usize, pred2: usize) -> usize;
|
||||
}
|
||||
|
||||
pub struct Intersect;
|
||||
impl BitwiseOperator for Intersect {
|
||||
#[inline]
|
||||
fn join(&self, a: usize, b: usize) -> usize { a & b }
|
||||
}
|
||||
pub struct Union;
|
||||
impl BitwiseOperator for Union {
|
||||
#[inline]
|
||||
|
@ -15,7 +15,7 @@ use std::mem;
|
||||
use std::ops::{Deref, DerefMut, Range};
|
||||
use std::slice;
|
||||
use bitslice::{BitSlice, Word};
|
||||
use bitslice::{bitwise, Union, Subtract};
|
||||
use bitslice::{bitwise, Union, Subtract, Intersect};
|
||||
use indexed_vec::Idx;
|
||||
|
||||
/// Represents a set (or packed family of sets), of some element type
|
||||
@ -164,6 +164,10 @@ impl<T: Idx> IdxSet<T> {
|
||||
bitwise(self.words_mut(), other.words(), &Subtract)
|
||||
}
|
||||
|
||||
pub fn intersect(&mut self, other: &IdxSet<T>) -> bool {
|
||||
bitwise(self.words_mut(), other.words(), &Intersect)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<T> {
|
||||
Iter {
|
||||
cur: None,
|
||||
|
@ -27,6 +27,10 @@ use super::drop_flag_effects_for_function_entry;
|
||||
use super::drop_flag_effects_for_location;
|
||||
use super::on_lookup_result_bits;
|
||||
|
||||
mod storage_liveness;
|
||||
|
||||
pub use self::storage_liveness::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) mod borrows;
|
||||
|
||||
|
82
src/librustc_mir/dataflow/impls/storage_liveness.rs
Normal file
82
src/librustc_mir/dataflow/impls/storage_liveness.rs
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
pub use super::*;
|
||||
|
||||
use rustc::mir::*;
|
||||
use dataflow::BitDenotation;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct MaybeStorageLive<'a, 'tcx: 'a> {
|
||||
mir: &'a Mir<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx: 'a> MaybeStorageLive<'a, 'tcx> {
|
||||
pub fn new(mir: &'a Mir<'tcx>)
|
||||
-> Self {
|
||||
MaybeStorageLive { mir: mir }
|
||||
}
|
||||
|
||||
pub fn mir(&self) -> &Mir<'tcx> {
|
||||
self.mir
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> BitDenotation for MaybeStorageLive<'a, 'tcx> {
|
||||
type Idx = Local;
|
||||
fn name() -> &'static str { "maybe_storage_live" }
|
||||
fn bits_per_block(&self) -> usize {
|
||||
self.mir.local_decls.len()
|
||||
}
|
||||
|
||||
fn start_block_effect(&self, _sets: &mut BlockSets<Local>) {
|
||||
// Nothing is live on function entry
|
||||
}
|
||||
|
||||
fn statement_effect(&self,
|
||||
sets: &mut BlockSets<Local>,
|
||||
loc: Location) {
|
||||
let stmt = &self.mir[loc.block].statements[loc.statement_index];
|
||||
|
||||
match stmt.kind {
|
||||
StatementKind::StorageLive(l) => sets.gen(&l),
|
||||
StatementKind::StorageDead(l) => sets.kill(&l),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn terminator_effect(&self,
|
||||
_sets: &mut BlockSets<Local>,
|
||||
_loc: Location) {
|
||||
// Terminators have no effect
|
||||
}
|
||||
|
||||
fn propagate_call_return(&self,
|
||||
_in_out: &mut IdxSet<Local>,
|
||||
_call_bb: mir::BasicBlock,
|
||||
_dest_bb: mir::BasicBlock,
|
||||
_dest_lval: &mir::Lvalue) {
|
||||
// Nothing to do when a call returns successfully
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> BitwiseOperator for MaybeStorageLive<'a, 'tcx> {
|
||||
#[inline]
|
||||
fn join(&self, pred1: usize, pred2: usize) -> usize {
|
||||
pred1 | pred2 // "maybe" means we union effects of both preds
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DataflowOperator for MaybeStorageLive<'a, 'tcx> {
|
||||
#[inline]
|
||||
fn bottom_value() -> bool {
|
||||
false // bottom = dead
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ use std::mem;
|
||||
use std::path::PathBuf;
|
||||
use std::usize;
|
||||
|
||||
pub use self::impls::{MaybeStorageLive};
|
||||
pub use self::impls::{MaybeInitializedLvals, MaybeUninitializedLvals};
|
||||
pub use self::impls::{DefinitelyInitializedLvals};
|
||||
pub use self::impls::borrows::{Borrows, BorrowData, BorrowIndex};
|
||||
@ -351,6 +352,29 @@ pub trait DataflowResultsConsumer<'a, 'tcx: 'a> {
|
||||
flow_state: &mut Self::FlowState);
|
||||
}
|
||||
|
||||
pub fn state_for_location<T: BitDenotation>(loc: Location,
|
||||
analysis: &T,
|
||||
result: &DataflowResults<T>)
|
||||
-> IdxSetBuf<T::Idx> {
|
||||
let mut entry = result.sets().on_entry_set_for(loc.block.index()).to_owned();
|
||||
|
||||
{
|
||||
let mut sets = BlockSets {
|
||||
on_entry: &mut entry.clone(),
|
||||
kill_set: &mut entry.clone(),
|
||||
gen_set: &mut entry,
|
||||
};
|
||||
|
||||
for stmt in 0..loc.statement_index {
|
||||
let mut stmt_loc = loc;
|
||||
stmt_loc.statement_index = stmt;
|
||||
analysis.statement_effect(&mut sets, stmt_loc);
|
||||
}
|
||||
}
|
||||
|
||||
entry
|
||||
}
|
||||
|
||||
pub struct DataflowAnalysis<'a, 'tcx: 'a, O> where O: BitDenotation
|
||||
{
|
||||
flow_state: DataflowState<O>,
|
||||
|
@ -38,7 +38,8 @@
|
||||
//! This pass computes the meaning of the state field and the MIR locals which are live
|
||||
//! across a suspension point. There are however two hardcoded generator states:
|
||||
//! 0 - Generator have not been resumed yet
|
||||
//! 1 - Generator has been poisoned
|
||||
//! 1 - Generator has returned / is completed
|
||||
//! 2 - Generator has been poisoned
|
||||
//!
|
||||
//! It also rewrites `return x` and `yield y` as setting a new generator state and returning
|
||||
//! GeneratorState::Complete(x) and GeneratorState::Yielded(y) respectively.
|
||||
@ -49,15 +50,13 @@
|
||||
//! the action to take.
|
||||
//!
|
||||
//! One of them is the implementation of Generator::resume.
|
||||
//! For generators which have already returned it panics.
|
||||
//! For generators with state 0 (unresumed) it starts the execution of the generator.
|
||||
//! For generators with state 1 (poisoned) it panics.
|
||||
//! For generators with state 1 (returned) and state 2 (poisoned) it panics.
|
||||
//! Otherwise it continues the execution from the last suspension point.
|
||||
//!
|
||||
//! The other function is the drop glue for the generator.
|
||||
//! For generators which have already returned it does nothing.
|
||||
//! For generators with state 0 (unresumed) it drops the upvars of the generator.
|
||||
//! For generators with state 1 (poisoned) it does nothing.
|
||||
//! 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;
|
||||
@ -65,19 +64,21 @@ use rustc::hir::def_id::DefId;
|
||||
use rustc::middle::const_val::ConstVal;
|
||||
use rustc::mir::*;
|
||||
use rustc::mir::transform::{MirPass, MirSource};
|
||||
use rustc::mir::visit::{LvalueContext, MutVisitor};
|
||||
use rustc::mir::visit::{LvalueContext, Visitor, MutVisitor};
|
||||
use rustc::ty::{self, TyCtxt, AdtDef, Ty, GeneratorInterior};
|
||||
use rustc::ty::subst::{Kind, Substs};
|
||||
use util::dump_mir;
|
||||
use util::liveness;
|
||||
use rustc_const_math::ConstInt;
|
||||
use rustc_data_structures::indexed_vec::Idx;
|
||||
use rustc_data_structures::indexed_set::IdxSetBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::borrow::Cow;
|
||||
use std::iter::once;
|
||||
use std::mem;
|
||||
use transform::simplify;
|
||||
use transform::no_landing_pads::no_landing_pads;
|
||||
use dataflow::{self, MaybeStorageLive, state_for_location};
|
||||
|
||||
pub struct StateTransform;
|
||||
|
||||
@ -126,6 +127,14 @@ fn self_arg() -> Local {
|
||||
Local::new(1)
|
||||
}
|
||||
|
||||
struct SuspensionPoint {
|
||||
state: u32,
|
||||
resume: BasicBlock,
|
||||
drop: Option<BasicBlock>,
|
||||
storage_liveness: liveness::LocalSet,
|
||||
storage_live: Option<BasicBlock>,
|
||||
}
|
||||
|
||||
struct TransformVisitor<'a, 'tcx: 'a> {
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
state_adt_ref: &'tcx AdtDef,
|
||||
@ -137,18 +146,16 @@ struct TransformVisitor<'a, 'tcx: 'a> {
|
||||
// Mapping from Local to (type of local, generator struct index)
|
||||
remap: HashMap<Local, (Ty<'tcx>, usize)>,
|
||||
|
||||
// The number of generator states. 0 is unresumed, 1 is poisoned. So this is initialized to 2
|
||||
bb_target_count: u32,
|
||||
mir_local_count: usize,
|
||||
|
||||
// Map from a (which block to resume execution at, which block to use to drop the generator)
|
||||
// to a generator state
|
||||
bb_targets: HashMap<(BasicBlock, Option<BasicBlock>), u32>,
|
||||
// A map from a suspension point in a block to the locals which have live storage at that point
|
||||
storage_liveness: HashMap<BasicBlock, liveness::LocalSet>,
|
||||
|
||||
// A list of suspension points, generated during the transform
|
||||
suspension_points: Vec<SuspensionPoint>,
|
||||
|
||||
// The original RETURN_POINTER local
|
||||
new_ret_local: Local,
|
||||
|
||||
// The block to resume execution when for Return
|
||||
return_block: BasicBlock,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> TransformVisitor<'a, 'tcx> {
|
||||
@ -225,28 +232,46 @@ impl<'a, 'tcx> MutVisitor<'tcx> for TransformVisitor<'a, 'tcx> {
|
||||
|
||||
let ret_val = match data.terminator().kind {
|
||||
TerminatorKind::Return => Some((1,
|
||||
self.return_block,
|
||||
None,
|
||||
Operand::Consume(Lvalue::Local(self.new_ret_local)),
|
||||
None)),
|
||||
TerminatorKind::Yield { ref value, resume, drop } => Some((0,
|
||||
resume,
|
||||
Some(resume),
|
||||
value.clone(),
|
||||
drop)),
|
||||
_ => None
|
||||
};
|
||||
|
||||
if let Some((state_idx, resume, v, drop)) = ret_val {
|
||||
let bb_idx = {
|
||||
let bb_targets = &mut self.bb_targets;
|
||||
let bb_target = &mut self.bb_target_count;
|
||||
*bb_targets.entry((resume, drop)).or_insert_with(|| {
|
||||
let target = *bb_target;
|
||||
*bb_target = target.checked_add(1).unwrap();
|
||||
target
|
||||
})
|
||||
};
|
||||
let source_info = data.terminator().source_info;
|
||||
data.statements.push(self.set_state(bb_idx, source_info));
|
||||
let state = if let Some(resume) = resume { // Yield
|
||||
let state = 3 + self.suspension_points.len() as u32;
|
||||
|
||||
let liveness = self.storage_liveness.get(&block).unwrap();
|
||||
|
||||
for i in 0..(self.mir_local_count) {
|
||||
let l = Local::new(i);
|
||||
if liveness.contains(&l) && !self.remap.contains_key(&l) {
|
||||
data.statements.push(Statement {
|
||||
source_info,
|
||||
kind: StatementKind::StorageDead(l),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.suspension_points.push(SuspensionPoint {
|
||||
state,
|
||||
resume,
|
||||
drop,
|
||||
storage_liveness: liveness.clone(),
|
||||
storage_live: None,
|
||||
});
|
||||
|
||||
state
|
||||
} else { // Return
|
||||
1 // state for returned
|
||||
};
|
||||
data.statements.push(self.set_state(state, source_info));
|
||||
data.statements.push(Statement {
|
||||
source_info,
|
||||
kind: StatementKind::Assign(Lvalue::Local(RETURN_POINTER),
|
||||
@ -286,16 +311,11 @@ fn make_generator_state_argument_indirect<'a, 'tcx>(
|
||||
|
||||
fn replace_result_variable<'tcx>(ret_ty: Ty<'tcx>,
|
||||
mir: &mut Mir<'tcx>) -> Local {
|
||||
let source_info = SourceInfo {
|
||||
span: mir.span,
|
||||
scope: ARGUMENT_VISIBILITY_SCOPE,
|
||||
};
|
||||
|
||||
let new_ret = LocalDecl {
|
||||
mutability: Mutability::Mut,
|
||||
ty: ret_ty,
|
||||
name: None,
|
||||
source_info,
|
||||
source_info: source_info(mir),
|
||||
internal: false,
|
||||
is_user_variable: false,
|
||||
};
|
||||
@ -311,33 +331,82 @@ fn replace_result_variable<'tcx>(ret_ty: Ty<'tcx>,
|
||||
new_ret_local
|
||||
}
|
||||
|
||||
struct StorageIgnored(liveness::LocalSet);
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for StorageIgnored {
|
||||
fn visit_statement(&mut self,
|
||||
_block: BasicBlock,
|
||||
statement: &Statement<'tcx>,
|
||||
_location: Location) {
|
||||
match statement.kind {
|
||||
StatementKind::StorageLive(l) |
|
||||
StatementKind::StorageDead(l) => { self.0.remove(&l); }
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn locals_live_across_suspend_points<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &Mir<'tcx>,
|
||||
source: MirSource) -> liveness::LocalSet {
|
||||
source: MirSource) ->
|
||||
(liveness::LocalSet,
|
||||
HashMap<BasicBlock, liveness::LocalSet>) {
|
||||
let dead_unwinds = IdxSetBuf::new_empty(mir.basic_blocks().len());
|
||||
let node_id = source.item_id();
|
||||
let analysis = MaybeStorageLive::new(mir);
|
||||
let storage_live =
|
||||
dataflow::do_dataflow(tcx, mir, node_id, &[], &dead_unwinds, analysis,
|
||||
|bd, p| &bd.mir().local_decls[p]);
|
||||
|
||||
let mut ignored = StorageIgnored(IdxSetBuf::new_filled(mir.basic_blocks().len()));
|
||||
ignored.visit_mir(mir);
|
||||
|
||||
let mut set = liveness::LocalSet::new_empty(mir.local_decls.len());
|
||||
let result = liveness::liveness_of_locals(mir);
|
||||
liveness::dump_mir(tcx, "generator_liveness", source, mir, &result);
|
||||
|
||||
let mut storage_liveness_map = HashMap::new();
|
||||
|
||||
for (block, data) in mir.basic_blocks().iter_enumerated() {
|
||||
if let TerminatorKind::Yield { .. } = data.terminator().kind {
|
||||
set.union(&result.outs[block]);
|
||||
let loc = Location {
|
||||
block: block,
|
||||
statement_index: data.statements.len(),
|
||||
};
|
||||
|
||||
let mut storage_liveness = state_for_location(loc, &analysis, &storage_live);
|
||||
|
||||
storage_liveness_map.insert(block, storage_liveness.clone());
|
||||
|
||||
// Mark locals without storage statements as always live
|
||||
storage_liveness.union(&ignored.0);
|
||||
|
||||
// Locals live are live at this point only if they are used across suspension points
|
||||
// and their storage is live
|
||||
storage_liveness.intersect(&result.outs[block]);
|
||||
|
||||
// Add the locals life at this suspension point to the set of locals which live across
|
||||
// any suspension points
|
||||
set.union(&storage_liveness);
|
||||
}
|
||||
}
|
||||
|
||||
// The generator argument is ignored
|
||||
set.remove(&self_arg());
|
||||
|
||||
set
|
||||
(set, storage_liveness_map)
|
||||
}
|
||||
|
||||
fn compute_layout<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
source: MirSource,
|
||||
interior: GeneratorInterior<'tcx>,
|
||||
mir: &mut Mir<'tcx>)
|
||||
-> (HashMap<Local, (Ty<'tcx>, usize)>, GeneratorLayout<'tcx>)
|
||||
-> (HashMap<Local, (Ty<'tcx>, usize)>,
|
||||
GeneratorLayout<'tcx>,
|
||||
HashMap<BasicBlock, liveness::LocalSet>)
|
||||
{
|
||||
// Use a liveness analysis to compute locals which are live across a suspension point
|
||||
let live_locals = locals_live_across_suspend_points(tcx, mir, source);
|
||||
let (live_locals, storage_liveness) = locals_live_across_suspend_points(tcx, mir, source);
|
||||
|
||||
// Erase regions from the types passed in from typeck so we can compare them with
|
||||
// MIR types
|
||||
@ -381,12 +450,31 @@ fn compute_layout<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
fields: vars
|
||||
};
|
||||
|
||||
(remap, layout)
|
||||
(remap, layout, storage_liveness)
|
||||
}
|
||||
|
||||
fn insert_entry_point<'tcx>(mir: &mut Mir<'tcx>,
|
||||
block: BasicBlockData<'tcx>) {
|
||||
mir.basic_blocks_mut().raw.insert(0, block);
|
||||
fn insert_switch<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &mut Mir<'tcx>,
|
||||
cases: Vec<(u32, BasicBlock)>,
|
||||
transform: &TransformVisitor<'a, 'tcx>) {
|
||||
let return_block = insert_return_block(mir);
|
||||
|
||||
let switch = TerminatorKind::SwitchInt {
|
||||
discr: Operand::Consume(transform.make_field(transform.state_field, tcx.types.u32)),
|
||||
switch_ty: tcx.types.u32,
|
||||
values: Cow::from(cases.iter().map(|&(i, _)| ConstInt::U32(i)).collect::<Vec<_>>()),
|
||||
targets: cases.iter().map(|&(_, d)| d).chain(once(return_block)).collect(),
|
||||
};
|
||||
|
||||
let source_info = source_info(mir);
|
||||
mir.basic_blocks_mut().raw.insert(0, BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
terminator: Some(Terminator {
|
||||
source_info,
|
||||
kind: switch,
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
|
||||
let blocks = mir.basic_blocks_mut().iter_mut();
|
||||
|
||||
@ -458,46 +546,29 @@ fn create_generator_drop_shim<'a, 'tcx>(
|
||||
drop_clean: BasicBlock) -> Mir<'tcx> {
|
||||
let mut mir = mir.clone();
|
||||
|
||||
let source_info = SourceInfo {
|
||||
span: mir.span,
|
||||
scope: ARGUMENT_VISIBILITY_SCOPE,
|
||||
};
|
||||
let source_info = source_info(&mir);
|
||||
|
||||
let return_block = BasicBlock::new(mir.basic_blocks().len());
|
||||
mir.basic_blocks_mut().push(BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
terminator: Some(Terminator {
|
||||
source_info,
|
||||
kind: TerminatorKind::Return,
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
|
||||
let mut cases: Vec<_> = transform.bb_targets.iter().filter_map(|(&(_, u), &s)| {
|
||||
u.map(|d| (s, d))
|
||||
let mut cases: Vec<_> = transform.suspension_points.iter().filter_map(|point| {
|
||||
point.drop.map(|drop| {
|
||||
// Make the point's storage live block goto the drop block
|
||||
let block = point.storage_live.unwrap();
|
||||
let term = Terminator {
|
||||
source_info,
|
||||
kind: TerminatorKind::Goto {
|
||||
target: drop,
|
||||
},
|
||||
};
|
||||
mir.basic_blocks_mut()[block].terminator = Some(term);
|
||||
(point.state, block)
|
||||
})
|
||||
}).collect();
|
||||
|
||||
cases.insert(0, (0, drop_clean));
|
||||
|
||||
// The poisoned state 1 falls through to the default case which is just to return
|
||||
// The returned state 1 and the poisoned state 2 falls through to
|
||||
// the default case which is just to return
|
||||
|
||||
let switch = TerminatorKind::SwitchInt {
|
||||
discr: Operand::Consume(transform.make_field(transform.state_field, tcx.types.u32)),
|
||||
switch_ty: tcx.types.u32,
|
||||
values: Cow::from(cases.iter().map(|&(i, _)| {
|
||||
ConstInt::U32(i)
|
||||
}).collect::<Vec<_>>()),
|
||||
targets: cases.iter().map(|&(_, d)| d).chain(once(return_block)).collect(),
|
||||
};
|
||||
|
||||
insert_entry_point(&mut mir, BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
terminator: Some(Terminator {
|
||||
source_info,
|
||||
kind: switch,
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
insert_switch(tcx, &mut mir, cases, &transform);
|
||||
|
||||
for block in mir.basic_blocks_mut() {
|
||||
let kind = &mut block.terminator_mut().kind;
|
||||
@ -507,11 +578,6 @@ fn create_generator_drop_shim<'a, 'tcx>(
|
||||
}
|
||||
|
||||
// Replace the return variable
|
||||
let source_info = SourceInfo {
|
||||
span: mir.span,
|
||||
scope: ARGUMENT_VISIBILITY_SCOPE,
|
||||
};
|
||||
|
||||
mir.return_ty = tcx.mk_nil();
|
||||
mir.local_decls[RETURN_POINTER] = LocalDecl {
|
||||
mutability: Mutability::Mut,
|
||||
@ -548,8 +614,23 @@ fn create_generator_drop_shim<'a, 'tcx>(
|
||||
mir
|
||||
}
|
||||
|
||||
fn insert_panic_on_resume_after_return<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &mut Mir<'tcx>) {
|
||||
fn insert_return_block<'tcx>(mir: &mut Mir<'tcx>) -> BasicBlock {
|
||||
let return_block = BasicBlock::new(mir.basic_blocks().len());
|
||||
let source_info = source_info(mir);
|
||||
mir.basic_blocks_mut().push(BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
terminator: Some(Terminator {
|
||||
source_info,
|
||||
kind: TerminatorKind::Return,
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
return_block
|
||||
}
|
||||
|
||||
fn insert_panic_block<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &mut Mir<'tcx>,
|
||||
message: AssertMessage<'tcx>) -> BasicBlock {
|
||||
let assert_block = BasicBlock::new(mir.basic_blocks().len());
|
||||
let term = TerminatorKind::Assert {
|
||||
cond: Operand::Constant(box Constant {
|
||||
@ -563,16 +644,12 @@ fn insert_panic_on_resume_after_return<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
},
|
||||
}),
|
||||
expected: true,
|
||||
msg: AssertMessage::GeneratorResumedAfterReturn,
|
||||
msg: message,
|
||||
target: assert_block,
|
||||
cleanup: None,
|
||||
};
|
||||
|
||||
let source_info = SourceInfo {
|
||||
span: mir.span,
|
||||
scope: ARGUMENT_VISIBILITY_SCOPE,
|
||||
};
|
||||
|
||||
let source_info = source_info(mir);
|
||||
mir.basic_blocks_mut().push(BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
terminator: Some(Terminator {
|
||||
@ -581,11 +658,13 @@ fn insert_panic_on_resume_after_return<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
|
||||
assert_block
|
||||
}
|
||||
|
||||
fn create_generator_resume_function<'a, 'tcx>(
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mut transform: TransformVisitor<'a, 'tcx>,
|
||||
transform: TransformVisitor<'a, 'tcx>,
|
||||
def_id: DefId,
|
||||
source: MirSource,
|
||||
mir: &mut Mir<'tcx>) {
|
||||
@ -597,61 +676,27 @@ fn create_generator_resume_function<'a, 'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
let source_info = SourceInfo {
|
||||
span: mir.span,
|
||||
scope: ARGUMENT_VISIBILITY_SCOPE,
|
||||
};
|
||||
|
||||
let poisoned_block = BasicBlock::new(mir.basic_blocks().len());
|
||||
|
||||
let term = TerminatorKind::Assert {
|
||||
cond: Operand::Constant(box Constant {
|
||||
span: mir.span,
|
||||
ty: tcx.types.bool,
|
||||
literal: Literal::Value {
|
||||
value: tcx.mk_const(ty::Const {
|
||||
val: ConstVal::Bool(false),
|
||||
ty: tcx.types.bool
|
||||
}),
|
||||
let mut cases: Vec<_> = transform.suspension_points.iter().map(|point| {
|
||||
// Make the point's storage live block goto the resume block
|
||||
let block = point.storage_live.unwrap();
|
||||
let term = Terminator {
|
||||
source_info: source_info(mir),
|
||||
kind: TerminatorKind::Goto {
|
||||
target: point.resume,
|
||||
},
|
||||
}),
|
||||
expected: true,
|
||||
msg: AssertMessage::GeneratorResumedAfterPanic,
|
||||
target: transform.return_block,
|
||||
cleanup: None,
|
||||
};
|
||||
};
|
||||
mir.basic_blocks_mut()[block].terminator = Some(term);
|
||||
(point.state, block)
|
||||
}).collect();
|
||||
|
||||
mir.basic_blocks_mut().push(BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
terminator: Some(Terminator {
|
||||
source_info,
|
||||
kind: term,
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
// Jump to the entry point on the 0 state
|
||||
cases.insert(0, (0, BasicBlock::new(0)));
|
||||
// Panic when resumed on the returned (1) state
|
||||
cases.insert(1, (1, insert_panic_block(tcx, mir, AssertMessage::GeneratorResumedAfterReturn)));
|
||||
// Panic when resumed on the poisoned (2) state
|
||||
cases.insert(2, (2, insert_panic_block(tcx, mir, AssertMessage::GeneratorResumedAfterPanic)));
|
||||
|
||||
transform.bb_targets.insert((poisoned_block, None), 1);
|
||||
|
||||
let switch = TerminatorKind::SwitchInt {
|
||||
discr: Operand::Consume(transform.make_field(transform.state_field, tcx.types.u32)),
|
||||
switch_ty: tcx.types.u32,
|
||||
values: Cow::from(transform.bb_targets.values().map(|&i| {
|
||||
ConstInt::U32(i)
|
||||
}).collect::<Vec<_>>()),
|
||||
targets: transform.bb_targets.keys()
|
||||
.map(|&(k, _)| k)
|
||||
.chain(once(transform.return_block))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
insert_entry_point(mir, BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
terminator: Some(Terminator {
|
||||
source_info,
|
||||
kind: switch,
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
insert_switch(tcx, mir, cases, &transform);
|
||||
|
||||
make_generator_state_argument_indirect(tcx, def_id, mir);
|
||||
|
||||
@ -664,21 +709,15 @@ fn create_generator_resume_function<'a, 'tcx>(
|
||||
dump_mir(tcx, None, "generator_resume", &0, source, mir);
|
||||
}
|
||||
|
||||
fn insert_clean_drop<'a, 'tcx>(mir: &mut Mir<'tcx>) -> BasicBlock {
|
||||
let source_info = SourceInfo {
|
||||
fn source_info<'a, 'tcx>(mir: &Mir<'tcx>) -> SourceInfo {
|
||||
SourceInfo {
|
||||
span: mir.span,
|
||||
scope: ARGUMENT_VISIBILITY_SCOPE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let return_block = BasicBlock::new(mir.basic_blocks().len());
|
||||
mir.basic_blocks_mut().push(BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
terminator: Some(Terminator {
|
||||
source_info,
|
||||
kind: TerminatorKind::Return,
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
fn insert_clean_drop<'a, 'tcx>(mir: &mut Mir<'tcx>) -> BasicBlock {
|
||||
let return_block = insert_return_block(mir);
|
||||
|
||||
// Create a block to destroy an unresumed generators. This can only destroy upvars.
|
||||
let drop_clean = BasicBlock::new(mir.basic_blocks().len());
|
||||
@ -687,6 +726,7 @@ fn insert_clean_drop<'a, 'tcx>(mir: &mut Mir<'tcx>) -> BasicBlock {
|
||||
target: return_block,
|
||||
unwind: None,
|
||||
};
|
||||
let source_info = source_info(mir);
|
||||
mir.basic_blocks_mut().push(BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
terminator: Some(Terminator {
|
||||
@ -736,16 +776,11 @@ impl MirPass for StateTransform {
|
||||
|
||||
// Extract locals which are live across suspension point into `layout`
|
||||
// `remap` gives a mapping from local indices onto generator struct indices
|
||||
let (remap, layout) = compute_layout(tcx, source, interior, mir);
|
||||
// `storage_liveness` tells us which locals have live storage at suspension points
|
||||
let (remap, layout, storage_liveness) = compute_layout(tcx, source, interior, mir);
|
||||
|
||||
let state_field = mir.upvar_decls.len();
|
||||
|
||||
let mut bb_targets = HashMap::new();
|
||||
|
||||
// If we jump to the entry point, we should go to the initial 0 generator state.
|
||||
// FIXME: Could this result in the need for destruction for state 0?
|
||||
bb_targets.insert((BasicBlock::new(0), None), 0);
|
||||
|
||||
// Run the transformation which converts Lvalues from Local to generator struct
|
||||
// accesses for locals in `remap`.
|
||||
// It also rewrites `return x` and `yield y` as writing a new generator state and returning
|
||||
@ -755,14 +790,11 @@ impl MirPass for StateTransform {
|
||||
state_adt_ref,
|
||||
state_substs,
|
||||
remap,
|
||||
bb_target_count: 2,
|
||||
bb_targets,
|
||||
storage_liveness,
|
||||
mir_local_count: mir.local_decls.len(),
|
||||
suspension_points: Vec::new(),
|
||||
new_ret_local,
|
||||
state_field,
|
||||
|
||||
// For returns we will resume execution at the next added basic block.
|
||||
// This happens in `insert_panic_on_resume_after_return`
|
||||
return_block: BasicBlock::new(mir.basic_blocks().len()),
|
||||
};
|
||||
transform.visit_mir(mir);
|
||||
|
||||
@ -773,9 +805,6 @@ impl MirPass for StateTransform {
|
||||
mir.spread_arg = None;
|
||||
mir.generator_layout = Some(layout);
|
||||
|
||||
// Panic if we resumed after returning
|
||||
insert_panic_on_resume_after_return(tcx, mir);
|
||||
|
||||
// Insert `drop(generator_struct)` which is used to drop upvars for generators in
|
||||
// the unresumed (0) state.
|
||||
// This is expanded to a drop ladder in `elaborate_generator_drops`.
|
||||
@ -790,6 +819,30 @@ impl MirPass for StateTransform {
|
||||
|
||||
dump_mir(tcx, None, "generator_post-transform", &0, source, mir);
|
||||
|
||||
// Create StorageLive instruction blocks for suspension points
|
||||
for point in &mut transform.suspension_points {
|
||||
point.storage_live = Some(BasicBlock::new(mir.basic_blocks().len()));
|
||||
let source_info = source_info(mir);
|
||||
let mut statements = Vec::new();
|
||||
for i in 0..(transform.mir_local_count) {
|
||||
let l = Local::new(i);
|
||||
if point.storage_liveness.contains(&l) && !transform.remap.contains_key(&l) {
|
||||
statements.push(Statement {
|
||||
source_info,
|
||||
kind: StatementKind::StorageLive(l),
|
||||
});
|
||||
}
|
||||
}
|
||||
mir.basic_blocks_mut().push(BasicBlockData {
|
||||
statements,
|
||||
terminator: Some(Terminator {
|
||||
source_info,
|
||||
kind: TerminatorKind::Unreachable,
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
30
src/test/run-pass/generator/match-bindings.rs
Normal file
30
src/test/run-pass/generator/match-bindings.rs
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![feature(generators)]
|
||||
|
||||
enum Enum {
|
||||
A(String),
|
||||
B
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|| {
|
||||
loop {
|
||||
if let true = true {
|
||||
match Enum::A(String::new()) {
|
||||
Enum::A(_var) => {}
|
||||
Enum::B => {}
|
||||
}
|
||||
}
|
||||
yield;
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user