From 373fc932aa7adecb9c2e78455835cb6341b73552 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 30 Aug 2018 18:54:32 -0300 Subject: [PATCH] Make move out computation lazy --- src/librustc/mir/mod.rs | 22 +++ .../borrow_check/error_reporting.rs | 90 ++++++++++-- src/librustc_mir/borrow_check/flows.rs | 18 +-- src/librustc_mir/borrow_check/mod.rs | 16 +-- src/librustc_mir/dataflow/impls/mod.rs | 129 +----------------- src/librustc_mir/dataflow/mod.rs | 16 +-- src/test/ui/hygiene/fields-move.nll.stderr | 8 -- 7 files changed, 110 insertions(+), 189 deletions(-) diff --git a/src/librustc/mir/mod.rs b/src/librustc/mir/mod.rs index 0840f333c876b..c9374b6bfc7ce 100644 --- a/src/librustc/mir/mod.rs +++ b/src/librustc/mir/mod.rs @@ -203,6 +203,28 @@ impl<'tcx> Mir<'tcx> { ReadGuard::map(self.predecessors(), |p| &p[bb]) } + #[inline] + pub fn predecessor_locations(&self, loc: Location) -> impl Iterator + '_ { + let if_zero_locations = if loc.statement_index == 0 { + let predecessor_blocks = self.predecessors_for(loc.block); + let num_predecessor_blocks = predecessor_blocks.len(); + Some((0 .. num_predecessor_blocks) + .map(move |i| predecessor_blocks[i]) + .map(move |bb| self.terminator_loc(bb)) + ) + } else { + None + }; + + let if_not_zero_locations = if loc.statement_index == 0 { + None + } else { + Some(Location { block: loc.block, statement_index: loc.statement_index - 1 }) + }; + + if_zero_locations.into_iter().flatten().chain(if_not_zero_locations) + } + #[inline] pub fn dominators(&self) -> Dominators { dominators(self) diff --git a/src/librustc_mir/borrow_check/error_reporting.rs b/src/librustc_mir/borrow_check/error_reporting.rs index f1df135f7ee83..efd804983067a 100644 --- a/src/librustc_mir/borrow_check/error_reporting.rs +++ b/src/librustc_mir/borrow_check/error_reporting.rs @@ -15,6 +15,7 @@ use rustc::mir::{BindingForm, BorrowKind, ClearCrossCrate, Field, Local}; use rustc::mir::{LocalDecl, LocalKind, Location, Operand, Place}; use rustc::mir::{ProjectionElem, Rvalue, Statement, StatementKind}; use rustc::ty; +use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::indexed_vec::Idx; use rustc_data_structures::sync::Lrc; use rustc_errors::DiagnosticBuilder; @@ -24,8 +25,9 @@ use super::borrow_set::BorrowData; use super::{Context, MirBorrowckCtxt}; use super::{InitializationRequiringAction, PrefixSet}; +use dataflow::drop_flag_effects; use dataflow::move_paths::MovePathIndex; -use dataflow::{FlowAtLocation, MovingOutStatements}; +use dataflow::move_paths::indexes::MoveOutIndex; use util::borrowck_errors::{BorrowckErrors, Origin}; impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { @@ -35,17 +37,14 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { desired_action: InitializationRequiringAction, (place, span): (&Place<'tcx>, Span), mpi: MovePathIndex, - curr_move_out: &FlowAtLocation>, ) { let use_spans = self .move_spans(place, context.loc) .or_else(|| self.borrow_spans(span, context.loc)); let span = use_spans.args_or_use(); - let mois = self.move_data.path_map[mpi] - .iter() - .filter(|moi| curr_move_out.contains(moi)) - .collect::>(); + let mois = self.get_moved_indexes(context, mpi); + debug!("report_use_of_moved_or_uninitialized: mois={:?}", mois); if mois.is_empty() { let root_place = self.prefixes(&place, PrefixSet::All).last().unwrap(); @@ -93,7 +92,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { let mut is_loop_move = false; for moi in &mois { - let move_out = self.move_data.moves[**moi]; + let move_out = self.move_data.moves[*moi]; let moved_place = &self.move_data.move_paths[move_out.path].place; let move_spans = self.move_spans(moved_place, move_out.source); @@ -148,7 +147,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { }; if needs_note { - let mpi = self.move_data.moves[*mois[0]].path; + let mpi = self.move_data.moves[mois[0]].path; let place = &self.move_data.move_paths[mpi].place; if let Some(ty) = self.retrieve_type_for_place(place) { @@ -521,6 +520,81 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { err } + fn get_moved_indexes( + &mut self, + context: Context, + mpi: MovePathIndex, + ) -> Vec { + let mir = self.mir; + + let mut stack = Vec::new(); + stack.extend(mir.predecessor_locations(context.loc)); + + let mut visited = FxHashSet(); + let mut result = vec![]; + + 'dfs: + while let Some(l) = stack.pop() { + debug!("report_use_of_moved_or_uninitialized: current_location={:?}", l); + + if !visited.insert(l) { + continue; + } + + // check for moves + let stmt_kind = mir[l.block].statements.get(l.statement_index).map(|s| &s.kind); + if let Some(StatementKind::StorageDead(..)) = stmt_kind { + // this analysis only tries to find moves explicitly + // written by the user, so we ignore the move-outs + // created by `StorageDead` and at the beginning + // of a function. + } else { + for moi in &self.move_data.loc_map[l] { + debug!("report_use_of_moved_or_uninitialized: moi={:?}", moi); + if self.move_data.moves[*moi].path == mpi { + debug!("report_use_of_moved_or_uninitialized: found"); + result.push(*moi); + + // Strictly speaking, we could continue our DFS here. There may be + // other moves that can reach the point of error. But it is kind of + // confusing to highlight them. + // + // Example: + // + // ``` + // let a = vec![]; + // let b = a; + // let c = a; + // drop(a); // <-- current point of error + // ``` + // + // Because we stop the DFS here, we only highlight `let c = a`, + // and not `let b = a`. We will of course also report an error at + // `let c = a` which highlights `let b = a` as the move. + continue 'dfs; + } + } + } + + // check for inits + let mut any_match = false; + drop_flag_effects::for_location_inits( + self.tcx, + self.mir, + self.move_data, + l, + |m| if m == mpi { any_match = true; }, + ); + if any_match { + continue 'dfs; + } + + stack.extend(mir.predecessor_locations(l)); + } + + result + } + pub(super) fn report_illegal_mutation_of_borrowed( &mut self, context: Context, diff --git a/src/librustc_mir/borrow_check/flows.rs b/src/librustc_mir/borrow_check/flows.rs index 192fa2b9eeaf7..6b964fec74fdd 100644 --- a/src/librustc_mir/borrow_check/flows.rs +++ b/src/librustc_mir/borrow_check/flows.rs @@ -24,7 +24,7 @@ use polonius_engine::Output; use dataflow::move_paths::indexes::BorrowIndex; use dataflow::move_paths::HasMoveData; use dataflow::Borrows; -use dataflow::{EverInitializedPlaces, MovingOutStatements}; +use dataflow::EverInitializedPlaces; use dataflow::{FlowAtLocation, FlowsAtLocation}; use dataflow::MaybeUninitializedPlaces; use either::Either; @@ -35,7 +35,6 @@ use std::rc::Rc; crate struct Flows<'b, 'gcx: 'tcx, 'tcx: 'b> { borrows: FlowAtLocation>, pub uninits: FlowAtLocation>, - pub move_outs: FlowAtLocation>, pub ever_inits: FlowAtLocation>, /// Polonius Output @@ -46,14 +45,12 @@ impl<'b, 'gcx, 'tcx> Flows<'b, 'gcx, 'tcx> { crate fn new( borrows: FlowAtLocation>, uninits: FlowAtLocation>, - move_outs: FlowAtLocation>, ever_inits: FlowAtLocation>, polonius_output: Option>>, ) -> Self { Flows { borrows, uninits, - move_outs, ever_inits, polonius_output, } @@ -79,7 +76,6 @@ macro_rules! each_flow { ($this:ident, $meth:ident($arg:ident)) => { FlowAtLocation::$meth(&mut $this.borrows, $arg); FlowAtLocation::$meth(&mut $this.uninits, $arg); - FlowAtLocation::$meth(&mut $this.move_outs, $arg); FlowAtLocation::$meth(&mut $this.ever_inits, $arg); }; } @@ -146,18 +142,6 @@ impl<'b, 'gcx, 'tcx> fmt::Display for Flows<'b, 'gcx, 'tcx> { }); s.push_str("] "); - s.push_str("move_out: ["); - let mut saw_one = false; - self.move_outs.each_state_bit(|mpi_move_out| { - if saw_one { - s.push_str(", "); - }; - saw_one = true; - let move_out = &self.move_outs.operator().move_data().moves[mpi_move_out]; - s.push_str(&format!("{:?}", move_out)); - }); - s.push_str("] "); - s.push_str("ever_init: ["); let mut saw_one = false; self.ever_inits.each_state_bit(|mpi_ever_init| { diff --git a/src/librustc_mir/borrow_check/mod.rs b/src/librustc_mir/borrow_check/mod.rs index 16d34082642bd..3536947b25ebf 100644 --- a/src/librustc_mir/borrow_check/mod.rs +++ b/src/librustc_mir/borrow_check/mod.rs @@ -43,7 +43,7 @@ use dataflow::DataflowResultsConsumer; use dataflow::FlowAtLocation; use dataflow::MoveDataParamEnv; use dataflow::{do_dataflow, DebugFormatted}; -use dataflow::{EverInitializedPlaces, MovingOutStatements}; +use dataflow::EverInitializedPlaces; use dataflow::{MaybeInitializedPlaces, MaybeUninitializedPlaces}; use util::borrowck_errors::{BorrowckErrors, Origin}; @@ -186,15 +186,6 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>( MaybeUninitializedPlaces::new(tcx, mir, &mdpe), |bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]), )); - let flow_move_outs = FlowAtLocation::new(do_dataflow( - tcx, - mir, - id, - &attributes, - &dead_unwinds, - MovingOutStatements::new(tcx, mir, &mdpe), - |bd, i| DebugFormatted::new(&bd.move_data().moves[i]), - )); let flow_ever_inits = FlowAtLocation::new(do_dataflow( tcx, mir, @@ -268,7 +259,6 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>( let mut state = Flows::new( flow_borrows, flow_uninits, - flow_move_outs, flow_ever_inits, polonius_output, ); @@ -1617,7 +1607,6 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { let place = self.base_path(place_span.0); let maybe_uninits = &flow_state.uninits; - let curr_move_outs = &flow_state.move_outs; // Bad scenarios: // @@ -1663,7 +1652,6 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { desired_action, place_span, mpi, - curr_move_outs, ); return; // don't bother finding other problems. } @@ -1691,7 +1679,6 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { let place = self.base_path(place_span.0); let maybe_uninits = &flow_state.uninits; - let curr_move_outs = &flow_state.move_outs; // Bad scenarios: // @@ -1727,7 +1714,6 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { desired_action, place_span, child_mpi, - curr_move_outs, ); return; // don't bother finding other problems. } diff --git a/src/librustc_mir/dataflow/impls/mod.rs b/src/librustc_mir/dataflow/impls/mod.rs index ee3bba840c67b..62c6018809f39 100644 --- a/src/librustc_mir/dataflow/impls/mod.rs +++ b/src/librustc_mir/dataflow/impls/mod.rs @@ -22,13 +22,13 @@ use super::MoveDataParamEnv; use util::elaborate_drops::DropFlagState; -use super::move_paths::{HasMoveData, MoveData, MoveOutIndex, MovePathIndex, InitIndex}; +use super::move_paths::{HasMoveData, MoveData, MovePathIndex, InitIndex}; use super::move_paths::{LookupResult, InitKind}; use super::{BitDenotation, BlockSets, InitialFlow}; use super::drop_flag_effects_for_function_entry; use super::drop_flag_effects_for_location; -use super::{on_lookup_result_bits, for_location_inits}; +use super::on_lookup_result_bits; mod storage_liveness; @@ -211,40 +211,6 @@ impl<'a, 'gcx, 'tcx: 'a> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, ' fn move_data(&self) -> &MoveData<'tcx> { &self.mdpe.move_data } } -/// `MovingOutStatements` tracks the statements that perform moves out -/// of particular places. More precisely, it tracks whether the -/// *effect* of such moves (namely, the uninitialization of the -/// place in question) can reach some point in the control-flow of -/// the function, or if that effect is "killed" by some intervening -/// operation reinitializing that place. -/// -/// The resulting dataflow is a more enriched version of -/// `MaybeUninitializedPlaces`. Both structures on their own only tell -/// you if a place *might* be uninitialized at a given point in the -/// control flow. But `MovingOutStatements` also includes the added -/// data of *which* particular statement causing the deinitialization -/// that the borrow checker's error message may need to report. -#[allow(dead_code)] -pub struct MovingOutStatements<'a, 'gcx: 'tcx, 'tcx: 'a> { - tcx: TyCtxt<'a, 'gcx, 'tcx>, - mir: &'a Mir<'tcx>, - mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>, -} - -impl<'a, 'gcx: 'tcx, 'tcx: 'a> MovingOutStatements<'a, 'gcx, 'tcx> { - pub fn new(tcx: TyCtxt<'a, 'gcx, 'tcx>, - mir: &'a Mir<'tcx>, - mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>) - -> Self - { - MovingOutStatements { tcx: tcx, mir: mir, mdpe: mdpe } - } -} - -impl<'a, 'gcx, 'tcx> HasMoveData<'tcx> for MovingOutStatements<'a, 'gcx, 'tcx> { - fn move_data(&self) -> &MoveData<'tcx> { &self.mdpe.move_data } -} - /// `EverInitializedPlaces` tracks all places that might have ever been /// initialized upon reaching a particular point in the control flow /// for a function, without an intervening `Storage Dead`. @@ -488,83 +454,6 @@ impl<'a, 'gcx, 'tcx> BitDenotation for DefinitelyInitializedPlaces<'a, 'gcx, 'tc } } -impl<'a, 'gcx, 'tcx> BitDenotation for MovingOutStatements<'a, 'gcx, 'tcx> { - type Idx = MoveOutIndex; - fn name() -> &'static str { "moving_out" } - fn bits_per_block(&self) -> usize { - self.move_data().moves.len() - } - - fn start_block_effect(&self, _sets: &mut IdxSet) { - // no move-statements have been executed prior to function - // execution, so this method has no effect on `_sets`. - } - - fn statement_effect(&self, - sets: &mut BlockSets, - location: Location) { - let (tcx, mir, move_data) = (self.tcx, self.mir, self.move_data()); - let stmt = &mir[location.block].statements[location.statement_index]; - let loc_map = &move_data.loc_map; - let path_map = &move_data.path_map; - - match stmt.kind { - // this analysis only tries to find moves explicitly - // written by the user, so we ignore the move-outs - // created by `StorageDead` and at the beginning - // of a function. - mir::StatementKind::StorageDead(_) => {} - _ => { - debug!("stmt {:?} at loc {:?} moves out of move_indexes {:?}", - stmt, location, &loc_map[location]); - // Every path deinitialized by a *particular move* - // has corresponding bit, "gen'ed" (i.e. set) - // here, in dataflow vector - sets.gen_all_and_assert_dead(&loc_map[location]); - } - } - - for_location_inits(tcx, mir, move_data, location, - |mpi| sets.kill_all(&path_map[mpi])); - } - - fn terminator_effect(&self, - sets: &mut BlockSets, - location: Location) - { - let (tcx, mir, move_data) = (self.tcx, self.mir, self.move_data()); - let term = mir[location.block].terminator(); - let loc_map = &move_data.loc_map; - let path_map = &move_data.path_map; - - debug!("terminator {:?} at loc {:?} moves out of move_indexes {:?}", - term, location, &loc_map[location]); - sets.gen_all_and_assert_dead(&loc_map[location]); - - for_location_inits(tcx, mir, move_data, location, - |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(); - - 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); - }); - } -} - impl<'a, 'gcx, 'tcx> BitDenotation for EverInitializedPlaces<'a, 'gcx, 'tcx> { type Idx = InitIndex; fn name() -> &'static str { "ever_init" } @@ -682,13 +571,6 @@ impl<'a, 'gcx, 'tcx> BitwiseOperator for DefinitelyInitializedPlaces<'a, 'gcx, ' } } -impl<'a, 'gcx, 'tcx> BitwiseOperator for MovingOutStatements<'a, 'gcx, 'tcx> { - #[inline] - fn join(&self, pred1: Word, pred2: Word) -> Word { - pred1 | pred2 // moves from both preds are in scope - } -} - impl<'a, 'gcx, 'tcx> BitwiseOperator for EverInitializedPlaces<'a, 'gcx, 'tcx> { #[inline] fn join(&self, pred1: Word, pred2: Word) -> Word { @@ -727,13 +609,6 @@ impl<'a, 'gcx, 'tcx> InitialFlow for DefinitelyInitializedPlaces<'a, 'gcx, 'tcx> } } -impl<'a, 'gcx, 'tcx> InitialFlow for MovingOutStatements<'a, 'gcx, 'tcx> { - #[inline] - fn bottom_value() -> bool { - false // bottom = no loans in scope by default - } -} - impl<'a, 'gcx, 'tcx> InitialFlow for EverInitializedPlaces<'a, 'gcx, 'tcx> { #[inline] fn bottom_value() -> bool { diff --git a/src/librustc_mir/dataflow/mod.rs b/src/librustc_mir/dataflow/mod.rs index fd6569feb5c20..bee7d443c8540 100644 --- a/src/librustc_mir/dataflow/mod.rs +++ b/src/librustc_mir/dataflow/mod.rs @@ -28,7 +28,7 @@ use std::usize; pub use self::impls::{MaybeStorageLive}; pub use self::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces}; -pub use self::impls::{DefinitelyInitializedPlaces, MovingOutStatements}; +pub use self::impls::DefinitelyInitializedPlaces; pub use self::impls::EverInitializedPlaces; pub use self::impls::borrows::Borrows; pub use self::impls::HaveBeenBorrowedLocals; @@ -38,7 +38,7 @@ pub(crate) use self::drop_flag_effects::*; use self::move_paths::MoveData; mod at_location; -mod drop_flag_effects; +pub mod drop_flag_effects; mod graphviz; mod impls; pub mod move_paths; @@ -511,18 +511,6 @@ impl<'a, E:Idx> BlockSets<'a, E> { } } - fn gen_all_and_assert_dead(&mut self, i: I) - where I: IntoIterator, - I::Item: Borrow - { - for j in i { - let j = j.borrow(); - let retval = self.gen_set.add(j); - self.kill_set.remove(j); - assert!(retval); - } - } - fn kill(&mut self, e: &E) { self.gen_set.remove(e); self.kill_set.add(e); diff --git a/src/test/ui/hygiene/fields-move.nll.stderr b/src/test/ui/hygiene/fields-move.nll.stderr index 56b77714991ca..9b25a865f921e 100644 --- a/src/test/ui/hygiene/fields-move.nll.stderr +++ b/src/test/ui/hygiene/fields-move.nll.stderr @@ -1,9 +1,6 @@ error[E0382]: use of moved value: `foo.x` --> $DIR/fields-move.rs:28:9 | -LL | $foo.x - | ------ value moved here -... LL | $foo.x //~ ERROR use of moved value: `foo.x` | ^^^^^^ value used here after move ... @@ -28,14 +25,9 @@ LL | assert_two_copies(copy_modern!(foo), foo.x); //~ ERROR use of moved val error[E0382]: use of moved value: `foo.x` --> $DIR/fields-move.rs:39:42 | -LL | $foo.x - | ------ value moved here -... LL | $foo.x //~ ERROR use of moved value: `foo.x` | ------ value moved here ... -LL | assert_two_copies(copy_modern!(foo), foo.x); //~ ERROR use of moved value: `foo.x` - | ----- value moved here LL | assert_two_copies(copy_legacy!(foo), foo.x); //~ ERROR use of moved value: `foo.x` | ^^^^^ value used here after move |