diff --git a/src/librustc_borrowck/borrowck/mir/dataflow/impls.rs b/src/librustc_borrowck/borrowck/mir/dataflow/impls.rs new file mode 100644 index 00000000000..c9a23e3f288 --- /dev/null +++ b/src/librustc_borrowck/borrowck/mir/dataflow/impls.rs @@ -0,0 +1,584 @@ +// Copyright 2012-2016 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 rustc::ty::TyCtxt; +use rustc::mir::repr::{self, Mir}; + +use super::super::gather_moves::{Location}; +use super::super::gather_moves::{MoveData, MoveOut, MoveOutIndex, MovePath, MovePathIndex}; +use super::super::DropFlagState; +use super::super::drop_flag_effects_for_function_entry; +use super::super::drop_flag_effects_for_location; +use super::super::on_all_children_bits; + +use super::{BitDenotation, BlockSets, DataflowOperator}; + +use bitslice::BitSlice; // adds set_bit/get_bit to &[usize] bitvector rep. +use bitslice::{BitwiseOperator}; +use indexed_set::{Idx, IdxSet}; + +use std::marker::PhantomData; + +// Dataflow analyses are built upon some interpretation of the +// bitvectors attached to each basic block, represented via a +// zero-sized structure. +// +// Note on PhantomData: Each interpretation will need to instantiate +// the `Bit` and `Ctxt` associated types, and in this case, those +// associated types need an associated lifetime `'tcx`. The +// interpretive structures are zero-sized, so they all need to carry a +// `PhantomData` representing how the structures relate to the `'tcx` +// lifetime. +// +// But, since all of the uses of `'tcx` are solely via instances of +// `Ctxt` that are passed into the `BitDenotation` methods, we can +// consistently use a `PhantomData` that is just a function over a +// `&Ctxt` (== `&MoveData<'tcx>). + +/// `MaybeInitializedLvals` tracks all l-values that might be +/// initialized upon reaching a particular point in the control flow +/// for a function. +/// +/// For example, in code like the following, we have corresponding +/// dataflow information shown in the right-hand comments. +/// +/// ```rust +/// struct S; +/// fn foo(pred: bool) { // maybe-init: +/// // {} +/// let a = S; let b = S; let c; let d; // {a, b} +/// +/// if pred { +/// drop(a); // { b} +/// b = S; // { b} +/// +/// } else { +/// drop(b); // {a} +/// d = S; // {a, d} +/// +/// } // {a, b, d} +/// +/// c = S; // {a, b, c, d} +/// } +/// ``` +/// +/// To determine whether an l-value *must* be initialized at a +/// particular control-flow point, one can take the set-difference +/// between this data and the data from `MaybeUninitializedLvals` at the +/// corresponding control-flow point. +/// +/// Similarly, at a given `drop` statement, the set-intersection +/// between this data and `MaybeUninitializedLvals` yields the set of +/// l-values that would require a dynamic drop-flag at that statement. +#[derive(Debug, Default)] +pub struct MaybeInitializedLvals<'a, 'tcx: 'a> { + // See "Note on PhantomData" above. + phantom: PhantomData, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)> +} + +/// `MaybeUninitializedLvals` tracks all l-values that might be +/// uninitialized upon reaching a particular point in the control flow +/// for a function. +/// +/// For example, in code like the following, we have corresponding +/// dataflow information shown in the right-hand comments. +/// +/// ```rust +/// struct S; +/// fn foo(pred: bool) { // maybe-uninit: +/// // {a, b, c, d} +/// let a = S; let b = S; let c; let d; // { c, d} +/// +/// if pred { +/// drop(a); // {a, c, d} +/// b = S; // {a, c, d} +/// +/// } else { +/// drop(b); // { b, c, d} +/// d = S; // { b, c } +/// +/// } // {a, b, c, d} +/// +/// c = S; // {a, b, d} +/// } +/// ``` +/// +/// To determine whether an l-value *must* be uninitialized at a +/// particular control-flow point, one can take the set-difference +/// between this data and the data from `MaybeInitializedLvals` at the +/// corresponding control-flow point. +/// +/// Similarly, at a given `drop` statement, the set-intersection +/// between this data and `MaybeInitializedLvals` yields the set of +/// l-values that would require a dynamic drop-flag at that statement. +#[derive(Debug, Default)] +pub struct MaybeUninitializedLvals<'a, 'tcx: 'a> { + // See "Note on PhantomData" above. + phantom: PhantomData, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)> +} + +/// `DefinitelyInitializedLvals` tracks all l-values that are definitely +/// initialized upon reaching a particular point in the control flow +/// for a function. +/// +/// FIXME: Note that once flow-analysis is complete, this should be +/// the set-complement of MaybeUninitializedLvals; thus we can get rid +/// of one or the other of these two. I'm inclined to get rid of +/// MaybeUninitializedLvals, simply because the sets will tend to be +/// smaller in this analysis and thus easier for humans to process +/// when debugging. +/// +/// For example, in code like the following, we have corresponding +/// dataflow information shown in the right-hand comments. +/// +/// ```rust +/// struct S; +/// fn foo(pred: bool) { // definite-init: +/// // { } +/// let a = S; let b = S; let c; let d; // {a, b } +/// +/// if pred { +/// drop(a); // { b, } +/// b = S; // { b, } +/// +/// } else { +/// drop(b); // {a, } +/// d = S; // {a, d} +/// +/// } // { } +/// +/// c = S; // { c } +/// } +/// ``` +/// +/// To determine whether an l-value *may* be uninitialized at a +/// particular control-flow point, one can take the set-complement +/// of this data. +/// +/// Similarly, at a given `drop` statement, the set-difference between +/// this data and `MaybeInitializedLvals` yields the set of l-values +/// that would require a dynamic drop-flag at that statement. +#[derive(Debug, Default)] +pub struct DefinitelyInitializedLvals<'a, 'tcx: 'a> { + // See "Note on PhantomData" above. + phantom: PhantomData, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)> +} + +/// `MovingOutStatements` tracks the statements that perform moves out +/// of particular l-values. More precisely, it tracks whether the +/// *effect* of such moves (namely, the uninitialization of the +/// l-value 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 l-value. +/// +/// The resulting dataflow is a more enriched version of +/// `MaybeUninitializedLvals`. Both structures on their own only tell +/// you if an l-value *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 meessage may need to report. +#[derive(Debug, Default)] +pub struct MovingOutStatements<'a, 'tcx: 'a> { + // See "Note on PhantomData" above. + phantom: PhantomData, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)> +} + +impl<'a, 'tcx> MaybeInitializedLvals<'a, 'tcx> { + fn update_bits(sets: &mut BlockSets, path: MovePathIndex, + state: DropFlagState) + { + match state { + DropFlagState::Absent => sets.kill(&path), + DropFlagState::Present => sets.gen(&path), + } + } +} + +impl<'a, 'tcx> MaybeUninitializedLvals<'a, 'tcx> { + fn update_bits(sets: &mut BlockSets, path: MovePathIndex, + state: DropFlagState) + { + match state { + DropFlagState::Absent => sets.gen(&path), + DropFlagState::Present => sets.kill(&path), + } + } +} + +impl<'a, 'tcx> DefinitelyInitializedLvals<'a, 'tcx> { + fn update_bits(sets: &mut BlockSets, path: MovePathIndex, + state: DropFlagState) + { + match state { + DropFlagState::Absent => sets.kill(&path), + DropFlagState::Present => sets.gen(&path), + } + } +} + +impl<'a, 'tcx> BitDenotation for MaybeInitializedLvals<'a, 'tcx> { + type Idx = MovePathIndex; + type Bit = MovePath<'tcx>; + type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>); + fn name() -> &'static str { "maybe_init" } + fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { + ctxt.2.move_paths.len() + } + fn interpret<'c>(&self, ctxt: &'c Self::Ctxt, idx: usize) -> &'c Self::Bit { + &ctxt.2.move_paths[MovePathIndex::new(idx)] + } + fn start_block_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets) + { + drop_flag_effects_for_function_entry( + ctxt.0, ctxt.1, &ctxt.2, + |path, s| { + assert!(s == DropFlagState::Present); + sets.on_entry.add(&path); + }); + } + + fn statement_effect(&self, + ctxt: &Self::Ctxt, + sets: &mut BlockSets, + bb: repr::BasicBlock, + idx: usize) + { + drop_flag_effects_for_location( + ctxt.0, ctxt.1, &ctxt.2, + Location { block: bb, index: idx }, + |path, s| Self::update_bits(sets, path, s) + ) + } + + fn terminator_effect(&self, + ctxt: &Self::Ctxt, + sets: &mut BlockSets, + bb: repr::BasicBlock, + statements_len: usize) + { + drop_flag_effects_for_location( + ctxt.0, ctxt.1, &ctxt.2, + Location { block: bb, index: statements_len }, + |path, s| Self::update_bits(sets, path, s) + ) + } + + fn propagate_call_return(&self, + ctxt: &Self::Ctxt, + in_out: &mut IdxSet, + _call_bb: repr::BasicBlock, + _dest_bb: repr::BasicBlock, + dest_lval: &repr::Lvalue) { + // when a call returns successfully, that means we need to set + // the bits for that dest_lval to 1 (initialized). + let move_data = &ctxt.2; + let move_path_index = move_data.rev_lookup.find(dest_lval); + on_all_children_bits(ctxt.0, ctxt.1, &ctxt.2, + move_path_index, + |mpi| { in_out.add(&mpi); }); + } +} + +impl<'a, 'tcx> BitDenotation for MaybeUninitializedLvals<'a, 'tcx> { + type Idx = MovePathIndex; + type Bit = MovePath<'tcx>; + type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>); + fn name() -> &'static str { "maybe_uninit" } + fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { + ctxt.2.move_paths.len() + } + fn interpret<'c>(&self, ctxt: &'c Self::Ctxt, idx: usize) -> &'c Self::Bit { + &ctxt.2.move_paths[MovePathIndex::new(idx)] + } + + // sets on_entry bits for Arg lvalues + fn start_block_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets) { + // set all bits to 1 (uninit) before gathering counterevidence + for e in sets.on_entry.words_mut() { *e = !0; } + + drop_flag_effects_for_function_entry( + ctxt.0, ctxt.1, &ctxt.2, + |path, s| { + assert!(s == DropFlagState::Present); + sets.on_entry.remove(&path); + }); + } + + fn statement_effect(&self, + ctxt: &Self::Ctxt, + sets: &mut BlockSets, + bb: repr::BasicBlock, + idx: usize) + { + drop_flag_effects_for_location( + ctxt.0, ctxt.1, &ctxt.2, + Location { block: bb, index: idx }, + |path, s| Self::update_bits(sets, path, s) + ) + } + + fn terminator_effect(&self, + ctxt: &Self::Ctxt, + sets: &mut BlockSets, + bb: repr::BasicBlock, + statements_len: usize) + { + drop_flag_effects_for_location( + ctxt.0, ctxt.1, &ctxt.2, + Location { block: bb, index: statements_len }, + |path, s| Self::update_bits(sets, path, s) + ) + } + + fn propagate_call_return(&self, + ctxt: &Self::Ctxt, + in_out: &mut IdxSet, + _call_bb: repr::BasicBlock, + _dest_bb: repr::BasicBlock, + dest_lval: &repr::Lvalue) { + // when a call returns successfully, that means we need to set + // the bits for that dest_lval to 1 (initialized). + let move_path_index = ctxt.2.rev_lookup.find(dest_lval); + on_all_children_bits(ctxt.0, ctxt.1, &ctxt.2, + move_path_index, + |mpi| { in_out.remove(&mpi); }); + } +} + +impl<'a, 'tcx> BitDenotation for DefinitelyInitializedLvals<'a, 'tcx> { + type Idx = MovePathIndex; + type Bit = MovePath<'tcx>; + type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>); + fn name() -> &'static str { "definite_init" } + fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { + ctxt.2.move_paths.len() + } + fn interpret<'c>(&self, ctxt: &'c Self::Ctxt, idx: usize) -> &'c Self::Bit { + &ctxt.2.move_paths[MovePathIndex::new(idx)] + } + + // sets on_entry bits for Arg lvalues + fn start_block_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets) { + for e in sets.on_entry.words_mut() { *e = 0; } + + drop_flag_effects_for_function_entry( + ctxt.0, ctxt.1, &ctxt.2, + |path, s| { + assert!(s == DropFlagState::Present); + sets.on_entry.add(&path); + }); + } + + fn statement_effect(&self, + ctxt: &Self::Ctxt, + sets: &mut BlockSets, + bb: repr::BasicBlock, + idx: usize) + { + drop_flag_effects_for_location( + ctxt.0, ctxt.1, &ctxt.2, + Location { block: bb, index: idx }, + |path, s| Self::update_bits(sets, path, s) + ) + } + + fn terminator_effect(&self, + ctxt: &Self::Ctxt, + sets: &mut BlockSets, + bb: repr::BasicBlock, + statements_len: usize) + { + drop_flag_effects_for_location( + ctxt.0, ctxt.1, &ctxt.2, + Location { block: bb, index: statements_len }, + |path, s| Self::update_bits(sets, path, s) + ) + } + + fn propagate_call_return(&self, + ctxt: &Self::Ctxt, + in_out: &mut IdxSet, + _call_bb: repr::BasicBlock, + _dest_bb: repr::BasicBlock, + dest_lval: &repr::Lvalue) { + // when a call returns successfully, that means we need to set + // the bits for that dest_lval to 1 (initialized). + let move_path_index = ctxt.2.rev_lookup.find(dest_lval); + on_all_children_bits(ctxt.0, ctxt.1, &ctxt.2, + move_path_index, + |mpi| { in_out.add(&mpi); }); + } +} + +impl<'a, 'tcx> BitDenotation for MovingOutStatements<'a, 'tcx> { + type Idx = MoveOutIndex; + type Bit = MoveOut; + type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>); + fn name() -> &'static str { "moving_out" } + fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { + ctxt.2.moves.len() + } + fn interpret<'c>(&self, ctxt: &'c Self::Ctxt, idx: usize) -> &'c Self::Bit { + &ctxt.2.moves[idx] + } + fn start_block_effect(&self,_move_data: &Self::Ctxt, _sets: &mut BlockSets) { + // no move-statements have been executed prior to function + // execution, so this method has no effect on `_sets`. + } + fn statement_effect(&self, + ctxt: &Self::Ctxt, + sets: &mut BlockSets, + bb: repr::BasicBlock, + idx: usize) { + let &(tcx, mir, ref move_data) = ctxt; + let stmt = &mir.basic_block_data(bb).statements[idx]; + let loc_map = &move_data.loc_map; + let path_map = &move_data.path_map; + let rev_lookup = &move_data.rev_lookup; + + let loc = Location { block: bb, index: idx }; + debug!("stmt {:?} at loc {:?} moves out of move_indexes {:?}", + stmt, loc, &loc_map[loc]); + for move_index in &loc_map[loc] { + // Every path deinitialized by a *particular move* + // has corresponding bit, "gen'ed" (i.e. set) + // here, in dataflow vector + zero_to_one(sets.gen_set.words_mut(), *move_index); + } + let bits_per_block = self.bits_per_block(ctxt); + match stmt.kind { + repr::StatementKind::Assign(ref lvalue, _) => { + // assigning into this `lvalue` kills all + // MoveOuts from it, and *also* all MoveOuts + // for children and associated fragment sets. + let move_path_index = rev_lookup.find(lvalue); + on_all_children_bits(tcx, + mir, + move_data, + move_path_index, + |mpi| for moi in &path_map[mpi] { + assert!(moi.idx() < bits_per_block); + sets.kill_set.add(&moi); + }); + } + } + } + + fn terminator_effect(&self, + ctxt: &Self::Ctxt, + sets: &mut BlockSets, + bb: repr::BasicBlock, + statements_len: usize) + { + let &(_tcx, mir, ref move_data) = ctxt; + let term = mir.basic_block_data(bb).terminator.as_ref().unwrap(); + let loc_map = &move_data.loc_map; + let loc = Location { block: bb, index: statements_len }; + debug!("terminator {:?} at loc {:?} moves out of move_indexes {:?}", + term, loc, &loc_map[loc]); + let bits_per_block = self.bits_per_block(ctxt); + for move_index in &loc_map[loc] { + assert!(move_index.idx() < bits_per_block); + zero_to_one(sets.gen_set.words_mut(), *move_index); + } + } + + fn propagate_call_return(&self, + ctxt: &Self::Ctxt, + in_out: &mut IdxSet, + _call_bb: repr::BasicBlock, + _dest_bb: repr::BasicBlock, + dest_lval: &repr::Lvalue) { + let move_data = &ctxt.2; + let move_path_index = move_data.rev_lookup.find(dest_lval); + let bits_per_block = self.bits_per_block(ctxt); + + let path_map = &move_data.path_map; + on_all_children_bits(ctxt.0, + ctxt.1, + move_data, + move_path_index, + |mpi| for moi in &path_map[mpi] { + assert!(moi.idx() < bits_per_block); + in_out.remove(&moi); + }); + } +} + +fn zero_to_one(bitvec: &mut [usize], move_index: MoveOutIndex) { + let retval = bitvec.set_bit(move_index.idx()); + assert!(retval); +} + +impl<'a, 'tcx> BitwiseOperator for MovingOutStatements<'a, 'tcx> { + #[inline] + fn join(&self, pred1: usize, pred2: usize) -> usize { + pred1 | pred2 // moves from both preds are in scope + } +} + +impl<'a, 'tcx> BitwiseOperator for MaybeInitializedLvals<'a, 'tcx> { + #[inline] + fn join(&self, pred1: usize, pred2: usize) -> usize { + pred1 | pred2 // "maybe" means we union effects of both preds + } +} + +impl<'a, 'tcx> BitwiseOperator for MaybeUninitializedLvals<'a, 'tcx> { + #[inline] + fn join(&self, pred1: usize, pred2: usize) -> usize { + pred1 | pred2 // "maybe" means we union effects of both preds + } +} + +impl<'a, 'tcx> BitwiseOperator for DefinitelyInitializedLvals<'a, 'tcx> { + #[inline] + fn join(&self, pred1: usize, pred2: usize) -> usize { + pred1 & pred2 // "definitely" means we intersect effects of both preds + } +} + +// The way that dataflow fixed point iteration works, you want to +// start at bottom and work your way to a fixed point. Control-flow +// merges will apply the `join` operator to each block entry's current +// state (which starts at that bottom value). +// +// This means, for propagation across the graph, that you either want +// to start at all-zeroes and then use Union as your merge when +// propagating, or you start at all-ones and then use Intersect as +// your merge when propagating. + +impl<'a, 'tcx> DataflowOperator for MovingOutStatements<'a, 'tcx> { + #[inline] + fn bottom_value() -> bool { + false // bottom = no loans in scope by default + } +} + +impl<'a, 'tcx> DataflowOperator for MaybeInitializedLvals<'a, 'tcx> { + #[inline] + fn bottom_value() -> bool { + false // bottom = uninitialized + } +} + +impl<'a, 'tcx> DataflowOperator for MaybeUninitializedLvals<'a, 'tcx> { + #[inline] + fn bottom_value() -> bool { + false // bottom = initialized (start_block_effect counters this at outset) + } +} + +impl<'a, 'tcx> DataflowOperator for DefinitelyInitializedLvals<'a, 'tcx> { + #[inline] + fn bottom_value() -> bool { + true // bottom = initialized (start_block_effect counters this at outset) + } +} diff --git a/src/librustc_borrowck/borrowck/mir/dataflow/mod.rs b/src/librustc_borrowck/borrowck/mir/dataflow/mod.rs index e1459ad2abe..97e29fe064f 100644 --- a/src/librustc_borrowck/borrowck/mir/dataflow/mod.rs +++ b/src/librustc_borrowck/borrowck/mir/dataflow/mod.rs @@ -13,24 +13,23 @@ use std::fmt::Debug; use std::io; -use std::marker::PhantomData; use std::mem; use std::path::PathBuf; use std::usize; use super::MirBorrowckCtxtPreDataflow; -use super::gather_moves::{Location, MoveData, MovePathIndex, MoveOutIndex}; -use super::gather_moves::{MoveOut, MovePath}; -use super::DropFlagState; +use super::gather_moves::{MoveData}; -use bitslice::BitSlice; // adds set_bit/get_bit to &[usize] bitvector rep. use bitslice::{bitwise, BitwiseOperator}; use indexed_set::{Idx, IdxSet, OwnIdxSet}; pub use self::sanity_check::sanity_check_via_rustc_peek; +pub use self::impls::{MaybeInitializedLvals, MaybeUninitializedLvals}; +pub use self::impls::{DefinitelyInitializedLvals, MovingOutStatements}; mod graphviz; mod sanity_check; +mod impls; pub trait Dataflow { fn dataflow(&mut self); @@ -550,567 +549,3 @@ fn propagate_bits_into_entry_set_for(&mut self, } } } - -// Dataflow analyses are built upon some interpretation of the -// bitvectors attached to each basic block, represented via a -// zero-sized structure. -// -// Note on PhantomData: Each interpretation will need to instantiate -// the `Bit` and `Ctxt` associated types, and in this case, those -// associated types need an associated lifetime `'tcx`. The -// interpretive structures are zero-sized, so they all need to carry a -// `PhantomData` representing how the structures relate to the `'tcx` -// lifetime. -// -// But, since all of the uses of `'tcx` are solely via instances of -// `Ctxt` that are passed into the `BitDenotation` methods, we can -// consistently use a `PhantomData` that is just a function over a -// `&Ctxt` (== `&MoveData<'tcx>). - -/// `MaybeInitializedLvals` tracks all l-values that might be -/// initialized upon reaching a particular point in the control flow -/// for a function. -/// -/// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. -/// -/// ```rust -/// struct S; -/// fn foo(pred: bool) { // maybe-init: -/// // {} -/// let a = S; let b = S; let c; let d; // {a, b} -/// -/// if pred { -/// drop(a); // { b} -/// b = S; // { b} -/// -/// } else { -/// drop(b); // {a} -/// d = S; // {a, d} -/// -/// } // {a, b, d} -/// -/// c = S; // {a, b, c, d} -/// } -/// ``` -/// -/// To determine whether an l-value *must* be initialized at a -/// particular control-flow point, one can take the set-difference -/// between this data and the data from `MaybeUninitializedLvals` at the -/// corresponding control-flow point. -/// -/// Similarly, at a given `drop` statement, the set-intersection -/// between this data and `MaybeUninitializedLvals` yields the set of -/// l-values that would require a dynamic drop-flag at that statement. -#[derive(Debug, Default)] -pub struct MaybeInitializedLvals<'a, 'tcx: 'a> { - // See "Note on PhantomData" above. - phantom: PhantomData, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)> -} - -/// `MaybeUninitializedLvals` tracks all l-values that might be -/// uninitialized upon reaching a particular point in the control flow -/// for a function. -/// -/// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. -/// -/// ```rust -/// struct S; -/// fn foo(pred: bool) { // maybe-uninit: -/// // {a, b, c, d} -/// let a = S; let b = S; let c; let d; // { c, d} -/// -/// if pred { -/// drop(a); // {a, c, d} -/// b = S; // {a, c, d} -/// -/// } else { -/// drop(b); // { b, c, d} -/// d = S; // { b, c } -/// -/// } // {a, b, c, d} -/// -/// c = S; // {a, b, d} -/// } -/// ``` -/// -/// To determine whether an l-value *must* be uninitialized at a -/// particular control-flow point, one can take the set-difference -/// between this data and the data from `MaybeInitializedLvals` at the -/// corresponding control-flow point. -/// -/// Similarly, at a given `drop` statement, the set-intersection -/// between this data and `MaybeInitializedLvals` yields the set of -/// l-values that would require a dynamic drop-flag at that statement. -#[derive(Debug, Default)] -pub struct MaybeUninitializedLvals<'a, 'tcx: 'a> { - // See "Note on PhantomData" above. - phantom: PhantomData, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)> -} - -/// `DefinitelyInitializedLvals` tracks all l-values that are definitely -/// initialized upon reaching a particular point in the control flow -/// for a function. -/// -/// FIXME: Note that once flow-analysis is complete, this should be -/// the set-complement of MaybeUninitializedLvals; thus we can get rid -/// of one or the other of these two. I'm inclined to get rid of -/// MaybeUninitializedLvals, simply because the sets will tend to be -/// smaller in this analysis and thus easier for humans to process -/// when debugging. -/// -/// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. -/// -/// ```rust -/// struct S; -/// fn foo(pred: bool) { // definite-init: -/// // { } -/// let a = S; let b = S; let c; let d; // {a, b } -/// -/// if pred { -/// drop(a); // { b, } -/// b = S; // { b, } -/// -/// } else { -/// drop(b); // {a, } -/// d = S; // {a, d} -/// -/// } // { } -/// -/// c = S; // { c } -/// } -/// ``` -/// -/// To determine whether an l-value *may* be uninitialized at a -/// particular control-flow point, one can take the set-complement -/// of this data. -/// -/// Similarly, at a given `drop` statement, the set-difference between -/// this data and `MaybeInitializedLvals` yields the set of l-values -/// that would require a dynamic drop-flag at that statement. -#[derive(Debug, Default)] -pub struct DefinitelyInitializedLvals<'a, 'tcx: 'a> { - // See "Note on PhantomData" above. - phantom: PhantomData, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)> -} - -/// `MovingOutStatements` tracks the statements that perform moves out -/// of particular l-values. More precisely, it tracks whether the -/// *effect* of such moves (namely, the uninitialization of the -/// l-value 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 l-value. -/// -/// The resulting dataflow is a more enriched version of -/// `MaybeUninitializedLvals`. Both structures on their own only tell -/// you if an l-value *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 meessage may need to report. -#[derive(Debug, Default)] -pub struct MovingOutStatements<'a, 'tcx: 'a> { - // See "Note on PhantomData" above. - phantom: PhantomData, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)> -} - -impl<'a, 'tcx> BitDenotation for MovingOutStatements<'a, 'tcx> { - type Idx = MoveOutIndex; - type Bit = MoveOut; - type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>); - fn name() -> &'static str { "moving_out" } - fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { - ctxt.2.moves.len() - } - fn interpret<'c>(&self, ctxt: &'c Self::Ctxt, idx: usize) -> &'c Self::Bit { - &ctxt.2.moves[idx] - } - fn start_block_effect(&self,_move_data: &Self::Ctxt, _sets: &mut BlockSets) { - // no move-statements have been executed prior to function - // execution, so this method has no effect on `_sets`. - } - fn statement_effect(&self, - ctxt: &Self::Ctxt, - sets: &mut BlockSets, - bb: repr::BasicBlock, - idx: usize) { - let &(tcx, mir, ref move_data) = ctxt; - let stmt = &mir.basic_block_data(bb).statements[idx]; - let loc_map = &move_data.loc_map; - let path_map = &move_data.path_map; - let rev_lookup = &move_data.rev_lookup; - - let loc = Location { block: bb, index: idx }; - debug!("stmt {:?} at loc {:?} moves out of move_indexes {:?}", - stmt, loc, &loc_map[loc]); - for move_index in &loc_map[loc] { - // Every path deinitialized by a *particular move* - // has corresponding bit, "gen'ed" (i.e. set) - // here, in dataflow vector - zero_to_one(sets.gen_set.words_mut(), *move_index); - } - let bits_per_block = self.bits_per_block(ctxt); - match stmt.kind { - repr::StatementKind::Assign(ref lvalue, _) => { - // assigning into this `lvalue` kills all - // MoveOuts from it, and *also* all MoveOuts - // for children and associated fragment sets. - let move_path_index = rev_lookup.find(lvalue); - super::on_all_children_bits(tcx, - mir, - move_data, - move_path_index, - |mpi| for moi in &path_map[mpi] { - assert!(moi.idx() < bits_per_block); - sets.kill_set.add(&moi); - }); - } - } - } - - fn terminator_effect(&self, - ctxt: &Self::Ctxt, - sets: &mut BlockSets, - bb: repr::BasicBlock, - statements_len: usize) - { - let &(_tcx, mir, ref move_data) = ctxt; - let term = mir.basic_block_data(bb).terminator.as_ref().unwrap(); - let loc_map = &move_data.loc_map; - let loc = Location { block: bb, index: statements_len }; - debug!("terminator {:?} at loc {:?} moves out of move_indexes {:?}", - term, loc, &loc_map[loc]); - let bits_per_block = self.bits_per_block(ctxt); - for move_index in &loc_map[loc] { - assert!(move_index.idx() < bits_per_block); - zero_to_one(sets.gen_set.words_mut(), *move_index); - } - } - - fn propagate_call_return(&self, - ctxt: &Self::Ctxt, - in_out: &mut IdxSet, - _call_bb: repr::BasicBlock, - _dest_bb: repr::BasicBlock, - dest_lval: &repr::Lvalue) { - let move_data = &ctxt.2; - let move_path_index = move_data.rev_lookup.find(dest_lval); - let bits_per_block = self.bits_per_block(ctxt); - - let path_map = &move_data.path_map; - super::on_all_children_bits(ctxt.0, - ctxt.1, - move_data, - move_path_index, - |mpi| for moi in &path_map[mpi] { - assert!(moi.idx() < bits_per_block); - in_out.remove(&moi); - }); - } -} - -impl<'a, 'tcx> MaybeInitializedLvals<'a, 'tcx> { - fn update_bits(sets: &mut BlockSets, path: MovePathIndex, - state: super::DropFlagState) - { - match state { - DropFlagState::Absent => sets.kill(&path), - DropFlagState::Present => sets.gen(&path), - } - } -} - -impl<'a, 'tcx> MaybeUninitializedLvals<'a, 'tcx> { - fn update_bits(sets: &mut BlockSets, path: MovePathIndex, - state: super::DropFlagState) - { - match state { - DropFlagState::Absent => sets.gen(&path), - DropFlagState::Present => sets.kill(&path), - } - } -} - -impl<'a, 'tcx> DefinitelyInitializedLvals<'a, 'tcx> { - fn update_bits(sets: &mut BlockSets, path: MovePathIndex, - state: super::DropFlagState) - { - match state { - DropFlagState::Absent => sets.kill(&path), - DropFlagState::Present => sets.gen(&path), - } - } -} - -impl<'a, 'tcx> BitDenotation for MaybeInitializedLvals<'a, 'tcx> { - type Idx = MovePathIndex; - type Bit = MovePath<'tcx>; - type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>); - fn name() -> &'static str { "maybe_init" } - fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { - ctxt.2.move_paths.len() - } - fn interpret<'c>(&self, ctxt: &'c Self::Ctxt, idx: usize) -> &'c Self::Bit { - &ctxt.2.move_paths[MovePathIndex::new(idx)] - } - fn start_block_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets) - { - super::drop_flag_effects_for_function_entry( - ctxt.0, ctxt.1, &ctxt.2, - |path, s| { - assert!(s == DropFlagState::Present); - sets.on_entry.add(&path); - }); - } - - fn statement_effect(&self, - ctxt: &Self::Ctxt, - sets: &mut BlockSets, - bb: repr::BasicBlock, - idx: usize) - { - super::drop_flag_effects_for_location( - ctxt.0, ctxt.1, &ctxt.2, - Location { block: bb, index: idx }, - |path, s| Self::update_bits(sets, path, s) - ) - } - - fn terminator_effect(&self, - ctxt: &Self::Ctxt, - sets: &mut BlockSets, - bb: repr::BasicBlock, - statements_len: usize) - { - super::drop_flag_effects_for_location( - ctxt.0, ctxt.1, &ctxt.2, - Location { block: bb, index: statements_len }, - |path, s| Self::update_bits(sets, path, s) - ) - } - - fn propagate_call_return(&self, - ctxt: &Self::Ctxt, - in_out: &mut IdxSet, - _call_bb: repr::BasicBlock, - _dest_bb: repr::BasicBlock, - dest_lval: &repr::Lvalue) { - // when a call returns successfully, that means we need to set - // the bits for that dest_lval to 1 (initialized). - let move_data = &ctxt.2; - let move_path_index = move_data.rev_lookup.find(dest_lval); - super::on_all_children_bits( - ctxt.0, ctxt.1, &ctxt.2, - move_path_index, - |mpi| { in_out.add(&mpi); } - ); - } -} - -impl<'a, 'tcx> BitDenotation for MaybeUninitializedLvals<'a, 'tcx> { - type Idx = MovePathIndex; - type Bit = MovePath<'tcx>; - type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>); - fn name() -> &'static str { "maybe_uninit" } - fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { - ctxt.2.move_paths.len() - } - fn interpret<'c>(&self, ctxt: &'c Self::Ctxt, idx: usize) -> &'c Self::Bit { - &ctxt.2.move_paths[MovePathIndex::new(idx)] - } - - // sets on_entry bits for Arg lvalues - fn start_block_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets) { - // set all bits to 1 (uninit) before gathering counterevidence - for e in sets.on_entry.words_mut() { *e = !0; } - - super::drop_flag_effects_for_function_entry( - ctxt.0, ctxt.1, &ctxt.2, - |path, s| { - assert!(s == DropFlagState::Present); - sets.on_entry.remove(&path); - }); - } - - fn statement_effect(&self, - ctxt: &Self::Ctxt, - sets: &mut BlockSets, - bb: repr::BasicBlock, - idx: usize) - { - super::drop_flag_effects_for_location( - ctxt.0, ctxt.1, &ctxt.2, - Location { block: bb, index: idx }, - |path, s| Self::update_bits(sets, path, s) - ) - } - - fn terminator_effect(&self, - ctxt: &Self::Ctxt, - sets: &mut BlockSets, - bb: repr::BasicBlock, - statements_len: usize) - { - super::drop_flag_effects_for_location( - ctxt.0, ctxt.1, &ctxt.2, - Location { block: bb, index: statements_len }, - |path, s| Self::update_bits(sets, path, s) - ) - } - - fn propagate_call_return(&self, - ctxt: &Self::Ctxt, - in_out: &mut IdxSet, - _call_bb: repr::BasicBlock, - _dest_bb: repr::BasicBlock, - dest_lval: &repr::Lvalue) { - // when a call returns successfully, that means we need to set - // the bits for that dest_lval to 1 (initialized). - let move_path_index = ctxt.2.rev_lookup.find(dest_lval); - super::on_all_children_bits( - ctxt.0, ctxt.1, &ctxt.2, - move_path_index, - |mpi| { in_out.remove(&mpi); } - ); - } -} - -impl<'a, 'tcx> BitDenotation for DefinitelyInitializedLvals<'a, 'tcx> { - type Idx = MovePathIndex; - type Bit = MovePath<'tcx>; - type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>); - fn name() -> &'static str { "definite_init" } - fn bits_per_block(&self, ctxt: &Self::Ctxt) -> usize { - ctxt.2.move_paths.len() - } - fn interpret<'c>(&self, ctxt: &'c Self::Ctxt, idx: usize) -> &'c Self::Bit { - &ctxt.2.move_paths[MovePathIndex::new(idx)] - } - - // sets on_entry bits for Arg lvalues - fn start_block_effect(&self, ctxt: &Self::Ctxt, sets: &mut BlockSets) { - for e in sets.on_entry.words_mut() { *e = 0; } - - super::drop_flag_effects_for_function_entry( - ctxt.0, ctxt.1, &ctxt.2, - |path, s| { - assert!(s == DropFlagState::Present); - sets.on_entry.add(&path); - }); - } - - fn statement_effect(&self, - ctxt: &Self::Ctxt, - sets: &mut BlockSets, - bb: repr::BasicBlock, - idx: usize) - { - super::drop_flag_effects_for_location( - ctxt.0, ctxt.1, &ctxt.2, - Location { block: bb, index: idx }, - |path, s| Self::update_bits(sets, path, s) - ) - } - - fn terminator_effect(&self, - ctxt: &Self::Ctxt, - sets: &mut BlockSets, - bb: repr::BasicBlock, - statements_len: usize) - { - super::drop_flag_effects_for_location( - ctxt.0, ctxt.1, &ctxt.2, - Location { block: bb, index: statements_len }, - |path, s| Self::update_bits(sets, path, s) - ) - } - - fn propagate_call_return(&self, - ctxt: &Self::Ctxt, - in_out: &mut IdxSet, - _call_bb: repr::BasicBlock, - _dest_bb: repr::BasicBlock, - dest_lval: &repr::Lvalue) { - // when a call returns successfully, that means we need to set - // the bits for that dest_lval to 1 (initialized). - let move_path_index = ctxt.2.rev_lookup.find(dest_lval); - super::on_all_children_bits( - ctxt.0, ctxt.1, &ctxt.2, - move_path_index, - |mpi| { in_out.add(&mpi); } - ); - } -} - -fn zero_to_one(bitvec: &mut [usize], move_index: MoveOutIndex) { - let retval = bitvec.set_bit(move_index.idx()); - assert!(retval); -} - -impl<'a, 'tcx> BitwiseOperator for MovingOutStatements<'a, 'tcx> { - #[inline] - fn join(&self, pred1: usize, pred2: usize) -> usize { - pred1 | pred2 // moves from both preds are in scope - } -} - -impl<'a, 'tcx> BitwiseOperator for MaybeInitializedLvals<'a, 'tcx> { - #[inline] - fn join(&self, pred1: usize, pred2: usize) -> usize { - pred1 | pred2 // "maybe" means we union effects of both preds - } -} - -impl<'a, 'tcx> BitwiseOperator for MaybeUninitializedLvals<'a, 'tcx> { - #[inline] - fn join(&self, pred1: usize, pred2: usize) -> usize { - pred1 | pred2 // "maybe" means we union effects of both preds - } -} - -impl<'a, 'tcx> BitwiseOperator for DefinitelyInitializedLvals<'a, 'tcx> { - #[inline] - fn join(&self, pred1: usize, pred2: usize) -> usize { - pred1 & pred2 // "definitely" means we intersect effects of both preds - } -} - -// The way that dataflow fixed point iteration works, you want to -// start at bottom and work your way to a fixed point. Control-flow -// merges will apply the `join` operator to each block entry's current -// state (which starts at that bottom value). -// -// This means, for propagation across the graph, that you either want -// to start at all-zeroes and then use Union as your merge when -// propagating, or you start at all-ones and then use Intersect as -// your merge when propagating. - -impl<'a, 'tcx> DataflowOperator for MovingOutStatements<'a, 'tcx> { - #[inline] - fn bottom_value() -> bool { - false // bottom = no loans in scope by default - } -} - -impl<'a, 'tcx> DataflowOperator for MaybeInitializedLvals<'a, 'tcx> { - #[inline] - fn bottom_value() -> bool { - false // bottom = uninitialized - } -} - -impl<'a, 'tcx> DataflowOperator for MaybeUninitializedLvals<'a, 'tcx> { - #[inline] - fn bottom_value() -> bool { - false // bottom = initialized (start_block_effect counters this at outset) - } -} - -impl<'a, 'tcx> DataflowOperator for DefinitelyInitializedLvals<'a, 'tcx> { - #[inline] - fn bottom_value() -> bool { - true // bottom = initialized - } -} -