diff --git a/src/librustc_data_structures/access_tracker.rs b/src/librustc_data_structures/access_tracker.rs new file mode 100644 index 0000000000000..8250ae1cfba50 --- /dev/null +++ b/src/librustc_data_structures/access_tracker.rs @@ -0,0 +1,50 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::ops::{Deref, DerefMut}; + +/// Takes ownership of `T` and tracks whether it was accessed mutably +/// (via `DerefMut`). You can access this via the `maybe_mutated` fn. +#[derive(Clone, Debug)] +pub struct AccessTracker { + value: T, + mutated: bool, +} + +impl AccessTracker { + pub fn new(value: T) -> Self { + AccessTracker { value, mutated: false } + } + + /// True if the owned value was accessed mutably (so far). + pub fn maybe_mutated(this: &Self) -> bool { + this.mutated + } + + pub fn into_inner(this: Self) -> (T, bool) { + (this.value, this.mutated) + } +} + +impl Deref for AccessTracker { + type Target = T; + + fn deref(&self) -> &T { + &self.value + } +} + +impl DerefMut for AccessTracker { + fn deref_mut(&mut self) -> &mut T { + self.mutated = true; + &mut self.value + } +} + diff --git a/src/librustc_data_structures/indexed_set.rs b/src/librustc_data_structures/indexed_set.rs index 223e08de826ce..7266c1f064bf1 100644 --- a/src/librustc_data_structures/indexed_set.rs +++ b/src/librustc_data_structures/indexed_set.rs @@ -161,6 +161,11 @@ impl IdxSet { } } + /// True if there are no elements + pub fn is_empty(&self) -> bool { + self.bits.iter().all(|&b| b == 0) + } + /// Removes all elements pub fn clear(&mut self) { for b in &mut self.bits { diff --git a/src/librustc_data_structures/lib.rs b/src/librustc_data_structures/lib.rs index a35ef2f7ce7ba..946d0f57ea45c 100644 --- a/src/librustc_data_structures/lib.rs +++ b/src/librustc_data_structures/lib.rs @@ -52,6 +52,7 @@ extern crate stable_deref_trait; pub use rustc_serialize::hex::ToHex; pub mod array_vec; +pub mod access_tracker; pub mod accumulate_vec; pub mod small_vec; pub mod base_n; diff --git a/src/librustc_mir/dataflow/impls/borrows.rs b/src/librustc_mir/dataflow/impls/borrows.rs index f543a33b130b6..6e2131b1e774d 100644 --- a/src/librustc_mir/dataflow/impls/borrows.rs +++ b/src/librustc_mir/dataflow/impls/borrows.rs @@ -19,11 +19,12 @@ use rustc::ty::RegionKind; use rustc::ty::RegionKind::ReScope; use rustc::util::nodemap::{FxHashMap, FxHashSet}; +use rustc_data_structures::access_tracker::AccessTracker; use rustc_data_structures::bitslice::{BitwiseOperator}; use rustc_data_structures::indexed_set::{IdxSet}; use rustc_data_structures::indexed_vec::{Idx, IndexVec}; -use dataflow::{BitDenotation, BlockSets, InitialFlow}; +use dataflow::{BitDenotation, BlockSets, EdgeKind, InitialFlow}; pub use dataflow::indexes::{BorrowIndex, ReserveOrActivateIndex}; use borrow_check::nll::region_infer::RegionInferenceContext; use borrow_check::nll::ToRegionVid; @@ -677,12 +678,14 @@ impl<'a, 'gcx, 'tcx> BitDenotation for Reservations<'a, 'gcx, 'tcx> { self.0.terminator_effect_on_borrows(sets, location, false); } - fn propagate_call_return(&self, - _in_out: &mut IdxSet, - _call_bb: mir::BasicBlock, - _dest_bb: mir::BasicBlock, - _dest_place: &mir::Place) { - // there are no effects on borrows from method call return... + fn edge_effect( + &self, + _sets: &mut AccessTracker<&mut BlockSets>, + _source_block: mir::BasicBlock, + _edge_kind: EdgeKind<'_>, + _target_terminator: mir::BasicBlock, + ) { + // there are no effects on borrows from edges... // // ... but if overwriting a place can affect flow state, then // latter is not true; see NOTE on Assign case in @@ -738,12 +741,14 @@ impl<'a, 'gcx, 'tcx> BitDenotation for ActiveBorrows<'a, 'gcx, 'tcx> { self.0.terminator_effect_on_borrows(sets, location, true); } - fn propagate_call_return(&self, - _in_out: &mut IdxSet, - _call_bb: mir::BasicBlock, - _dest_bb: mir::BasicBlock, - _dest_place: &mir::Place) { - // there are no effects on borrows from method call return... + fn edge_effect( + &self, + _sets: &mut AccessTracker<&mut BlockSets>, + _source_block: mir::BasicBlock, + _edge_kind: EdgeKind<'_>, + _target_terminator: mir::BasicBlock, + ) { + // there are no effects on borrows from edges... // // ... but If overwriting a place can affect flow state, then // latter is not true; see NOTE on Assign case in diff --git a/src/librustc_mir/dataflow/impls/mod.rs b/src/librustc_mir/dataflow/impls/mod.rs index 106a88e703c79..35d244925e918 100644 --- a/src/librustc_mir/dataflow/impls/mod.rs +++ b/src/librustc_mir/dataflow/impls/mod.rs @@ -14,6 +14,7 @@ use rustc::ty::TyCtxt; use rustc::mir::{self, Mir, Location}; +use rustc_data_structures::access_tracker::AccessTracker; use rustc_data_structures::bitslice::{BitwiseOperator}; use rustc_data_structures::indexed_set::{IdxSet}; use rustc_data_structures::indexed_vec::Idx; @@ -23,7 +24,7 @@ use util::elaborate_drops::DropFlagState; use super::move_paths::{HasMoveData, MoveData, MoveOutIndex, MovePathIndex, InitIndex}; use super::move_paths::{LookupResult, InitKind}; -use super::{BitDenotation, BlockSets, InitialFlow}; +use super::{BitDenotation, BlockSets, EdgeKind, InitialFlow}; use super::drop_flag_effects_for_function_entry; use super::drop_flag_effects_for_location; @@ -292,7 +293,7 @@ impl<'a, 'gcx, 'tcx> HasMoveData<'tcx> for EverInitializedLvals<'a, 'gcx, 'tcx> impl<'a, 'gcx, 'tcx> MaybeInitializedLvals<'a, 'gcx, 'tcx> { - fn update_bits(sets: &mut BlockSets, path: MovePathIndex, + fn update_bits(sets: &mut BlockSets<'_, MovePathIndex>, path: MovePathIndex, state: DropFlagState) { match state { @@ -303,7 +304,7 @@ impl<'a, 'gcx, 'tcx> MaybeInitializedLvals<'a, 'gcx, 'tcx> { } impl<'a, 'gcx, 'tcx> MaybeUninitializedLvals<'a, 'gcx, 'tcx> { - fn update_bits(sets: &mut BlockSets, path: MovePathIndex, + fn update_bits(sets: &mut BlockSets<'_, MovePathIndex>, path: MovePathIndex, state: DropFlagState) { match state { @@ -314,7 +315,7 @@ impl<'a, 'gcx, 'tcx> MaybeUninitializedLvals<'a, 'gcx, 'tcx> { } impl<'a, 'gcx, 'tcx> DefinitelyInitializedLvals<'a, 'gcx, 'tcx> { - fn update_bits(sets: &mut BlockSets, path: MovePathIndex, + fn update_bits(sets: &mut BlockSets<'_, MovePathIndex>, path: MovePathIndex, state: DropFlagState) { match state { @@ -341,7 +342,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for MaybeInitializedLvals<'a, 'gcx, 'tcx> { } fn statement_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, MovePathIndex>, location: Location) { drop_flag_effects_for_location( @@ -352,7 +353,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for MaybeInitializedLvals<'a, 'gcx, 'tcx> { } fn terminator_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, MovePathIndex>, location: Location) { drop_flag_effects_for_location( @@ -362,16 +363,29 @@ impl<'a, 'gcx, 'tcx> BitDenotation for MaybeInitializedLvals<'a, 'gcx, 'tcx> { ) } - fn propagate_call_return(&self, - in_out: &mut IdxSet, - _call_bb: mir::BasicBlock, - _dest_bb: mir::BasicBlock, - dest_place: &mir::Place) { - // when a call returns successfully, that means we need to set - // the bits for that dest_place to 1 (initialized). - on_lookup_result_bits(self.tcx, self.mir, self.move_data(), - self.move_data().rev_lookup.find(dest_place), - |mpi| { in_out.add(&mpi); }); + fn edge_effect( + &self, + sets: &mut AccessTracker<&mut BlockSets>, + _source_block: mir::BasicBlock, + edge_kind: EdgeKind<'_>, + _target_block: mir::BasicBlock, + ) { + match edge_kind { + EdgeKind::CallReturn(dest_place) => { + // when a call returns successfully, that means we need to set + // the bits for that dest_place to 1 (initialized). + on_lookup_result_bits( + self.tcx, + self.mir, + self.move_data(), + self.move_data().rev_lookup.find(dest_place), + |mpi| sets.gen(&mpi), + ); + } + + EdgeKind::Noop | EdgeKind::FalseEdge => { + } + } } } @@ -396,7 +410,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for MaybeUninitializedLvals<'a, 'gcx, 'tcx> { } fn statement_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, MovePathIndex>, location: Location) { drop_flag_effects_for_location( @@ -407,7 +421,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for MaybeUninitializedLvals<'a, 'gcx, 'tcx> { } fn terminator_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, MovePathIndex>, location: Location) { drop_flag_effects_for_location( @@ -417,16 +431,29 @@ impl<'a, 'gcx, 'tcx> BitDenotation for MaybeUninitializedLvals<'a, 'gcx, 'tcx> { ) } - fn propagate_call_return(&self, - in_out: &mut IdxSet, - _call_bb: mir::BasicBlock, - _dest_bb: mir::BasicBlock, - dest_place: &mir::Place) { - // when a call returns successfully, that means we need to set - // the bits for that dest_place to 0 (initialized). - on_lookup_result_bits(self.tcx, self.mir, self.move_data(), - self.move_data().rev_lookup.find(dest_place), - |mpi| { in_out.remove(&mpi); }); + fn edge_effect( + &self, + sets: &mut AccessTracker<&mut BlockSets>, + _source_block: mir::BasicBlock, + edge_kind: EdgeKind<'_>, + _target_block: mir::BasicBlock, + ) { + match edge_kind { + EdgeKind::CallReturn(dest_place) => { + // when a call returns successfully, that means we need to set + // the bits for that dest_place to 0 (initialized). + on_lookup_result_bits( + self.tcx, + self.mir, + self.move_data(), + self.move_data().rev_lookup.find(dest_place), + |mpi| sets.kill(&mpi), + ); + } + + EdgeKind::Noop | EdgeKind::FalseEdge => { + } + } } } @@ -450,7 +477,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for DefinitelyInitializedLvals<'a, 'gcx, 'tcx } fn statement_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, MovePathIndex>, location: Location) { drop_flag_effects_for_location( @@ -461,7 +488,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for DefinitelyInitializedLvals<'a, 'gcx, 'tcx } fn terminator_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, MovePathIndex>, location: Location) { drop_flag_effects_for_location( @@ -471,16 +498,29 @@ impl<'a, 'gcx, 'tcx> BitDenotation for DefinitelyInitializedLvals<'a, 'gcx, 'tcx ) } - fn propagate_call_return(&self, - in_out: &mut IdxSet, - _call_bb: mir::BasicBlock, - _dest_bb: mir::BasicBlock, - dest_place: &mir::Place) { - // when a call returns successfully, that means we need to set - // the bits for that dest_place to 1 (initialized). - on_lookup_result_bits(self.tcx, self.mir, self.move_data(), - self.move_data().rev_lookup.find(dest_place), - |mpi| { in_out.add(&mpi); }); + fn edge_effect( + &self, + sets: &mut AccessTracker<&mut BlockSets>, + _source_block: mir::BasicBlock, + edge_kind: EdgeKind<'_>, + _target_block: mir::BasicBlock, + ) { + match edge_kind { + EdgeKind::CallReturn(dest_place) => { + // when a call returns successfully, that means we need to set + // the bits for that dest_place to 1 (initialized). + on_lookup_result_bits( + self.tcx, + self.mir, + self.move_data(), + self.move_data().rev_lookup.find(dest_place), + |mpi| sets.gen(&mpi), + ); + } + + EdgeKind::Noop | EdgeKind::FalseEdge => { + } + } } } @@ -497,7 +537,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for MovingOutStatements<'a, 'gcx, 'tcx> { } fn statement_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, MoveOutIndex>, location: Location) { let (tcx, mir, move_data) = (self.tcx, self.mir, self.move_data()); let stmt = &mir[location.block].statements[location.statement_index]; @@ -525,7 +565,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for MovingOutStatements<'a, 'gcx, 'tcx> { } fn terminator_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, MoveOutIndex>, location: Location) { let (tcx, mir, move_data) = (self.tcx, self.mir, self.move_data()); @@ -541,23 +581,34 @@ impl<'a, 'gcx, 'tcx> BitDenotation for MovingOutStatements<'a, 'gcx, 'tcx> { |mpi| sets.kill_all(&path_map[mpi])); } - fn propagate_call_return(&self, - in_out: &mut IdxSet, - _call_bb: mir::BasicBlock, - _dest_bb: mir::BasicBlock, - dest_place: &mir::Place) { - let move_data = self.move_data(); - let bits_per_block = self.bits_per_block(); + fn edge_effect( + &self, + sets: &mut AccessTracker<&mut BlockSets>, + _source_block: mir::BasicBlock, + edge_kind: EdgeKind<'_>, + _target_terminator: mir::BasicBlock, + ) { + match edge_kind { + EdgeKind::CallReturn(dest_place) => { + let move_data = self.move_data(); + let bits_per_block = self.bits_per_block(); + + let path_map = &move_data.path_map; + on_lookup_result_bits( + self.tcx, + self.mir, + move_data, + move_data.rev_lookup.find(dest_place), + |mpi| for moi in &path_map[mpi] { + assert!(moi.index() < bits_per_block); + sets.kill(&moi); + }, + ); + } - let path_map = &move_data.path_map; - on_lookup_result_bits(self.tcx, - self.mir, - move_data, - move_data.rev_lookup.find(dest_place), - |mpi| for moi in &path_map[mpi] { - assert!(moi.index() < bits_per_block); - in_out.remove(&moi); - }); + EdgeKind::Noop | EdgeKind::FalseEdge => { + } + } } } @@ -575,7 +626,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for EverInitializedLvals<'a, 'gcx, 'tcx> { } fn statement_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, InitIndex>, location: Location) { let (_, mir, move_data) = (self.tcx, self.mir, self.move_data()); let stmt = &mir[location.block].statements[location.statement_index]; @@ -622,7 +673,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for EverInitializedLvals<'a, 'gcx, 'tcx> { } fn terminator_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, InitIndex>, location: Location) { let (mir, move_data) = (self.mir, self.move_data()); @@ -637,22 +688,31 @@ impl<'a, 'gcx, 'tcx> BitDenotation for EverInitializedLvals<'a, 'gcx, 'tcx> { ); } - fn propagate_call_return(&self, - in_out: &mut IdxSet, - call_bb: mir::BasicBlock, - _dest_bb: mir::BasicBlock, - _dest_place: &mir::Place) { - let move_data = self.move_data(); - let bits_per_block = self.bits_per_block(); - let init_loc_map = &move_data.init_loc_map; + fn edge_effect( + &self, + sets: &mut AccessTracker<&mut BlockSets>, + source_block: mir::BasicBlock, + edge_kind: EdgeKind<'_>, + _target_block: mir::BasicBlock, + ) { + match edge_kind { + EdgeKind::CallReturn(_) => { + let move_data = self.move_data(); + let bits_per_block = self.bits_per_block(); + let init_loc_map = &move_data.init_loc_map; + + let call_loc = Location { + block: source_block, + statement_index: self.mir[source_block].statements.len(), + }; + for init_index in &init_loc_map[call_loc] { + assert!(init_index.index() < bits_per_block); + sets.gen(init_index); + } + } - let call_loc = Location { - block: call_bb, - statement_index: self.mir[call_bb].statements.len(), - }; - for init_index in &init_loc_map[call_loc] { - assert!(init_index.index() < bits_per_block); - in_out.add(init_index); + EdgeKind::Noop | EdgeKind::FalseEdge => { + } } } } diff --git a/src/librustc_mir/dataflow/impls/storage_liveness.rs b/src/librustc_mir/dataflow/impls/storage_liveness.rs index dea61542ac4e2..7840e61355890 100644 --- a/src/librustc_mir/dataflow/impls/storage_liveness.rs +++ b/src/librustc_mir/dataflow/impls/storage_liveness.rs @@ -10,8 +10,9 @@ pub use super::*; -use rustc::mir::*; use dataflow::BitDenotation; +use rustc::mir::*; +use rustc_data_structures::access_tracker::AccessTracker; #[derive(Copy, Clone)] pub struct MaybeStorageLive<'a, 'tcx: 'a> { @@ -58,12 +59,14 @@ impl<'a, 'tcx> BitDenotation for MaybeStorageLive<'a, 'tcx> { // Terminators have no effect } - fn propagate_call_return(&self, - _in_out: &mut IdxSet, - _call_bb: mir::BasicBlock, - _dest_bb: mir::BasicBlock, - _dest_place: &mir::Place) { - // Nothing to do when a call returns successfully + fn edge_effect( + &self, + _sets: &mut AccessTracker<&mut BlockSets>, + _source_block: mir::BasicBlock, + _edge_kind: EdgeKind<'_>, + _target_terminator: mir::BasicBlock, + ) { + // No special effects on edges } } diff --git a/src/librustc_mir/dataflow/mod.rs b/src/librustc_mir/dataflow/mod.rs index b18fb7c7b9cce..f7699c8697e8d 100644 --- a/src/librustc_mir/dataflow/mod.rs +++ b/src/librustc_mir/dataflow/mod.rs @@ -10,16 +10,18 @@ use syntax::ast::{self, MetaItem}; +use rustc_data_structures::access_tracker::AccessTracker; use rustc_data_structures::indexed_set::{IdxSet, IdxSetBuf}; use rustc_data_structures::indexed_vec::Idx; use rustc_data_structures::bitslice::{bitwise, BitwiseOperator}; use rustc::ty::{self, TyCtxt}; -use rustc::mir::{self, Mir, BasicBlock, BasicBlockData, Location, Statement, Terminator}; +use rustc::mir::{self, BasicBlock, BasicBlockData, Location, Mir, Place, Statement, Terminator}; use rustc::session::Session; use std::borrow::{Borrow, Cow}; use std::fmt; +use std::iter; use std::io; use std::mem; use std::path::PathBuf; @@ -237,6 +239,10 @@ impl<'b, 'a: 'b, 'tcx: 'a, BD> PropagationContext<'b, 'a, 'tcx, BD> where BD: Bi { fn walk_cfg(&mut self, in_out: &mut IdxSet) { let mir = self.builder.mir; + let bits_per_block = self.builder.flow_state.sets.bits_per_block; + let mut temp_gens = IdxSetBuf::new_empty(bits_per_block); + let mut temp_kills = IdxSetBuf::new_empty(bits_per_block); + let mut scratch_buf = IdxSetBuf::new_empty(bits_per_block); for (bb_idx, bb_data) in mir.basic_blocks().iter().enumerate() { let builder = &mut self.builder; { @@ -246,8 +252,19 @@ impl<'b, 'a: 'b, 'tcx: 'a, BD> PropagationContext<'b, 'a, 'tcx, BD> where BD: Bi in_out.union(sets.gen_set); in_out.subtract(sets.kill_set); } + + let sets = &mut BlockSets { + on_entry: in_out, + gen_set: &mut temp_gens, + kill_set: &mut temp_kills, + }; + builder.propagate_bits_into_graph_successors_of( - in_out, &mut self.changed, (mir::BasicBlock::new(bb_idx), bb_data)); + sets, + &mut scratch_buf, + &mut self.changed, + (mir::BasicBlock::new(bb_idx), bb_data), + ); } } } @@ -555,11 +572,20 @@ impl<'a, E:Idx> BlockSets<'a, E> { self.on_entry.union(&self.gen_set); self.on_entry.subtract(&self.kill_set); } + + fn clear_local_effect(&mut self) { + self.gen_set.clear(); + self.kill_set.clear(); + } + + fn has_empty_local_effect(&mut self) -> bool { + self.gen_set.is_empty() && self.kill_set.is_empty() + } } impl AllSets { pub fn bits_per_block(&self) -> usize { self.bits_per_block } - pub fn for_block(&mut self, block_idx: usize) -> BlockSets { + pub fn for_block(&mut self, block_idx: usize) -> BlockSets<'_, E> { let offset = self.words_per_block * block_idx; let range = E::new(offset)..E::new(offset + self.words_per_block); BlockSets { @@ -612,11 +638,10 @@ pub trait BitDenotation: BitwiseOperator { /// `sets.on_entry` to that local clone into `statement_effect` and /// `terminator_effect`). /// - /// When its false, no local clone is constucted; instead a - /// reference directly into `on_entry` is passed along via - /// `sets.on_entry` instead, which represents the flow state at - /// the block's start, not necessarily the state immediately prior - /// to the statement/terminator under analysis. + /// When its false, no local clone is constucted; instead it is + /// undefined what `on_entry` points to (in practice, it will + /// frequently be a reference the flow state at the block's start, + /// but you should not rely on that). /// /// In either case, the passed reference is mutable; but this is a /// wart from using the `BlockSets` type in the API; the intention @@ -674,7 +699,7 @@ pub trait BitDenotation: BitwiseOperator { /// `bb_data` is the sequence of statements identified by `bb` in /// the MIR. fn statement_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, Self::Idx>, location: Location); /// Similar to `terminator_effect`, except it applies @@ -701,35 +726,59 @@ pub trait BitDenotation: BitwiseOperator { /// block, represented via GEN and KILL sets. /// /// The effects applied here cannot depend on which branch the - /// terminator took. + /// terminator took. Hence they are best understood as the effects + /// up to -- but not including -- the branches. fn terminator_effect(&self, - sets: &mut BlockSets, + sets: &mut BlockSets<'_, Self::Idx>, location: Location); /// Mutates the block-sets according to the (flow-dependent) - /// effect of a successful return from a Call terminator. + /// effect of a particular outgoing edge from a terminator. For + /// many terminators/operators, this is a no-op, since the effect + /// of the terminator is not dependent on which branch is taken + /// and hence can be accumulated via `terminator_effect`. + /// + /// One example where this callback is needed involves Call + /// terminators. In the case of a call terminator: /// - /// If basic-block BB_x ends with a call-instruction that, upon - /// successful return, flows to BB_y, then this method will be - /// called on the exit flow-state of BB_x in order to set up the - /// entry flow-state of BB_y. + /// tmp0 = call foo(...) /// - /// This is used, in particular, as a special case during the - /// "propagate" loop where all of the basic blocks are repeatedly - /// visited. Since the effects of a Call terminator are - /// flow-dependent, the current MIR cannot encode them via just - /// GEN and KILL sets attached to the block, and so instead we add - /// this extra machinery to represent the flow-dependent effect. + /// the assignment to `tmp0` only occurs if the call returns + /// normally (without unwinding). Therefore, we wish to apply the + /// effect of considering `tmp0` to be initialized only on the one + /// edge. /// - /// FIXME: Right now this is a bit of a wart in the API. It might - /// be better to represent this as an additional gen- and - /// kill-sets associated with each edge coming out of the basic - /// block. - fn propagate_call_return(&self, - in_out: &mut IdxSet, - call_bb: mir::BasicBlock, - dest_bb: mir::BasicBlock, - dest_place: &mir::Place); + /// The `edge_kind` parameter can be used to determine what sort + /// of terminator this is (you may need to add variants, though, + /// as the current set is somewhat minimal). + /// + /// Note that, during propagation, edge-specific effects are not + /// accumulated into the overall gen-kill sets for a block, and + /// hence this function will be called repeatedly as we iterate to + /// a fixed point. But so long as you define this callback (and + /// the rest) as a "pure function", this need not concern you. + fn edge_effect( + &self, + sets: &mut AccessTracker<&mut BlockSets>, + source_block: mir::BasicBlock, + edge_kind: EdgeKind<'_>, + target_terminator: mir::BasicBlock, + ); +} + +#[derive(Copy, Clone)] +pub enum EdgeKind<'mir> { + /// A standard edge -- one where the terminator does not + /// perform any action along the edge. The edge may be a normal + /// or an unwinding edge. + Noop, + + /// An edge that doesn't really execute at runtime. + FalseEdge, + + /// A "call return" edge, where the return value of a call (a call + /// that did not unwind) is stored into its destination. + CallReturn(&'mir Place<'mir>), } impl<'a, 'tcx, D> DataflowAnalysis<'a, 'tcx, D> where D: BitDenotation @@ -805,9 +854,10 @@ impl<'a, 'tcx: 'a, D> DataflowAnalysis<'a, 'tcx, D> where D: BitDenotation /// unwind target). fn propagate_bits_into_graph_successors_of( &mut self, - in_out: &mut IdxSet, + sets: &mut BlockSets<'_, D::Idx>, + scratch_buf: &mut IdxSet, changed: &mut bool, - (bb, bb_data): (mir::BasicBlock, &mir::BasicBlockData)) + (bb, bb_data): (mir::BasicBlock, &mir::BasicBlockData<'tcx>)) { match bb_data.terminator().kind { mir::TerminatorKind::Return | @@ -815,61 +865,156 @@ impl<'a, 'tcx: 'a, D> DataflowAnalysis<'a, 'tcx, D> where D: BitDenotation mir::TerminatorKind::Abort | mir::TerminatorKind::GeneratorDrop | mir::TerminatorKind::Unreachable => {} - mir::TerminatorKind::Goto { ref target } | - mir::TerminatorKind::Assert { ref target, cleanup: None, .. } | - mir::TerminatorKind::Yield { resume: ref target, drop: None, .. } | - mir::TerminatorKind::Drop { ref target, location: _, unwind: None } | + mir::TerminatorKind::Goto { target } | + mir::TerminatorKind::Assert { target, cleanup: None, .. } | + mir::TerminatorKind::Yield { resume: target, drop: None, .. } | + mir::TerminatorKind::Drop { target, location: _, unwind: None } | mir::TerminatorKind::DropAndReplace { - ref target, value: _, location: _, unwind: None + target, value: _, location: _, unwind: None } => { - self.propagate_bits_into_entry_set_for(in_out, changed, target); + self.propagate_bits_across_edges( + sets, + scratch_buf, + changed, + bb, + iter::once((target, EdgeKind::Noop)), + ) } - mir::TerminatorKind::Yield { resume: ref target, drop: Some(ref drop), .. } => { - self.propagate_bits_into_entry_set_for(in_out, changed, target); - self.propagate_bits_into_entry_set_for(in_out, changed, drop); + mir::TerminatorKind::Yield { resume: target, drop: Some(drop), .. } => { + self.propagate_bits_across_edges( + sets, + scratch_buf, + changed, + bb, + iter::once((target, EdgeKind::Noop)) + .chain(iter::once((drop, EdgeKind::Noop))), + ) } - mir::TerminatorKind::Assert { ref target, cleanup: Some(ref unwind), .. } | - mir::TerminatorKind::Drop { ref target, location: _, unwind: Some(ref unwind) } | + mir::TerminatorKind::Assert { target, cleanup: Some(unwind), .. } | + mir::TerminatorKind::Drop { target, location: _, unwind: Some(unwind) } | mir::TerminatorKind::DropAndReplace { - ref target, value: _, location: _, unwind: Some(ref unwind) + target, value: _, location: _, unwind: Some(unwind) } => { - self.propagate_bits_into_entry_set_for(in_out, changed, target); - if !self.dead_unwinds.contains(&bb) { - self.propagate_bits_into_entry_set_for(in_out, changed, unwind); - } + let all_targets = [(target, EdgeKind::Noop), (unwind, EdgeKind::Noop)]; + let unwind_is_dead = self.dead_unwinds.contains(&bb); + let targets = if unwind_is_dead { &all_targets[..1] } else { &all_targets[..] }; + self.propagate_bits_across_edges( + sets, + scratch_buf, + changed, + bb, + targets.iter().cloned(), + ) } mir::TerminatorKind::SwitchInt { ref targets, .. } => { - for target in targets { - self.propagate_bits_into_entry_set_for(in_out, changed, target); - } + self.propagate_bits_across_edges( + sets, + scratch_buf, + changed, + bb, + targets.into_iter().map(|&target| (target, EdgeKind::Noop)), + ) } - mir::TerminatorKind::Call { ref cleanup, ref destination, func: _, args: _ } => { - if let Some(ref unwind) = *cleanup { + mir::TerminatorKind::Call { cleanup, ref destination, func: _, args: _ } => { + let mut unwind_edge = None; + let mut normal_edge = None; + + if let Some(unwind) = cleanup { if !self.dead_unwinds.contains(&bb) { - self.propagate_bits_into_entry_set_for(in_out, changed, unwind); + unwind_edge = Some((unwind, EdgeKind::Noop)); } } - if let Some((ref dest_place, ref dest_bb)) = *destination { - // N.B.: This must be done *last*, after all other - // propagation, as documented in comment above. - self.flow_state.operator.propagate_call_return( - in_out, bb, *dest_bb, dest_place); - self.propagate_bits_into_entry_set_for(in_out, changed, dest_bb); + + if let Some((dest_place, dest_bb)) = destination { + normal_edge = Some((*dest_bb, EdgeKind::CallReturn(dest_place))); } + + self.propagate_bits_across_edges( + sets, + scratch_buf, + changed, + bb, + unwind_edge.into_iter().chain(normal_edge), + ) + } + mir::TerminatorKind::FalseEdges { real_target, ref imaginary_targets } => { + self.propagate_bits_across_edges( + sets, + scratch_buf, + changed, + bb, + iter::once((real_target, EdgeKind::Noop)) + .chain( + imaginary_targets.into_iter() + .map(|&target| (target, EdgeKind::FalseEdge)), + ), + ) } - mir::TerminatorKind::FalseEdges { ref real_target, ref imaginary_targets } => { - self.propagate_bits_into_entry_set_for(in_out, changed, real_target); - for target in imaginary_targets { - self.propagate_bits_into_entry_set_for(in_out, changed, target); + } + } + + #[allow(non_camel_case_types)] // FIXME + fn propagate_bits_across_edges<'ek>( + &mut self, + sets: &mut BlockSets<'_, D::Idx>, + scratch_buf: &mut IdxSet, + changed: &mut bool, + source: mir::BasicBlock, + targets: impl IntoIterator)>, + ) { + // When true, the initial value of `sets.on_entry` has been copied + // into `scratch_buf`. + let mut is_saved = false; + + // When true, the value of `sets.on_entry` has been changed + // from its initial value (and not yet restored). + let mut is_dirty = false; + + // Just in case some previous caller left them dirty, clear + // the gen/kill sets to start. + sets.clear_local_effect(); + + for (target, edge_kind) in targets { + if is_dirty { + // Some previous edge generated (and applied) gen/kill + // effects. Undo them. + assert!(is_saved); + sets.clear_local_effect(); + sets.on_entry.clone_from(scratch_buf); + is_dirty = false; + } + + // Compute the gen/kill sets for this edge. + let sets_mutated = { + let mut tracked_sets = AccessTracker::new(&mut *sets); + self.flow_state.operator.edge_effect(&mut tracked_sets, source, edge_kind, target); + AccessTracker::maybe_mutated(&tracked_sets) + }; + + // If those gen/kill sets are non-empty, apply them. + if sets_mutated { + if !is_saved { + // But first, save the "pristine" on-entry set so + // that we can restore it for other edges. + scratch_buf.clone_from(&sets.on_entry); + is_saved = true; } + + sets.apply_local_effect(); + is_dirty = true; + } else { + debug_assert!(sets.has_empty_local_effect()); } + + // Update the on-entry set for `target`. + self.propagate_bits_into_entry_set_for(&sets.on_entry, changed, target); } } fn propagate_bits_into_entry_set_for(&mut self, in_out: &IdxSet, changed: &mut bool, - bb: &mir::BasicBlock) { + bb: mir::BasicBlock) { let entry_set = self.flow_state.sets.for_block(bb.index()).on_entry; let set_changed = bitwise(entry_set.words_mut(), in_out.words(), diff --git a/src/librustc_mir/lib.rs b/src/librustc_mir/lib.rs index 1699ad0f19cf6..46651a411818f 100644 --- a/src/librustc_mir/lib.rs +++ b/src/librustc_mir/lib.rs @@ -37,6 +37,7 @@ Rust MIR: a lowered representation of Rust. Also: an experiment! #![feature(collection_placement)] #![feature(nonzero)] #![feature(underscore_lifetimes)] +#![feature(universal_impl_trait)] #[macro_use] extern crate bitflags;