DefinitelyInitializedLvals
dataflow op (goal: move away from MaybeUninitializedLvals
)
This commit is contained in:
parent
8956789c35
commit
ee44f7ed27
3 changed files with 220 additions and 10 deletions
|
@ -661,6 +661,53 @@ pub struct MaybeUninitializedLvals<'a, 'tcx: 'a> {
|
||||||
phantom: PhantomData<Fn(&'a MoveData<'tcx>, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)>
|
phantom: PhantomData<Fn(&'a MoveData<'tcx>, 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<Fn(&'a MoveData<'tcx>, TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>)>
|
||||||
|
}
|
||||||
|
|
||||||
/// `MovingOutStatements` tracks the statements that perform moves out
|
/// `MovingOutStatements` tracks the statements that perform moves out
|
||||||
/// of particular l-values. More precisely, it tracks whether the
|
/// of particular l-values. More precisely, it tracks whether the
|
||||||
/// *effect* of such moves (namely, the uninitialization of the
|
/// *effect* of such moves (namely, the uninitialization of the
|
||||||
|
@ -809,6 +856,23 @@ impl<'a, 'tcx> MaybeUninitializedLvals<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> DefinitelyInitializedLvals<'a, 'tcx> {
|
||||||
|
fn update_bits(sets: &mut BlockSets, path: MovePathIndex,
|
||||||
|
state: super::DropFlagState)
|
||||||
|
{
|
||||||
|
match state {
|
||||||
|
DropFlagState::Dead => {
|
||||||
|
sets.gen_set.clear_bit(path.idx());
|
||||||
|
sets.kill_set.set_bit(path.idx());
|
||||||
|
}
|
||||||
|
DropFlagState::Live => {
|
||||||
|
sets.gen_set.set_bit(path.idx());
|
||||||
|
sets.kill_set.clear_bit(path.idx());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> BitDenotation for MaybeInitializedLvals<'a, 'tcx> {
|
impl<'a, 'tcx> BitDenotation for MaybeInitializedLvals<'a, 'tcx> {
|
||||||
type Bit = MovePath<'tcx>;
|
type Bit = MovePath<'tcx>;
|
||||||
type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>);
|
type Ctxt = (TyCtxt<'a, 'tcx, 'tcx>, &'a Mir<'tcx>, MoveData<'tcx>);
|
||||||
|
@ -940,6 +1004,72 @@ impl<'a, 'tcx> BitDenotation for MaybeUninitializedLvals<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> BitDenotation for DefinitelyInitializedLvals<'a, 'tcx> {
|
||||||
|
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 &mut sets.on_entry[..] { *e = 0; }
|
||||||
|
|
||||||
|
super::drop_flag_effects_for_function_entry(
|
||||||
|
ctxt.0, ctxt.1, &ctxt.2,
|
||||||
|
|path, s| {
|
||||||
|
assert!(s == DropFlagState::Live);
|
||||||
|
sets.on_entry.set_bit(path.idx());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 [usize],
|
||||||
|
_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.set_bit(mpi.idx()); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn zero_to_one(bitvec: &mut [usize], move_index: MoveOutIndex) {
|
fn zero_to_one(bitvec: &mut [usize], move_index: MoveOutIndex) {
|
||||||
let retval = bitvec.set_bit(move_index.idx());
|
let retval = bitvec.set_bit(move_index.idx());
|
||||||
assert!(retval);
|
assert!(retval);
|
||||||
|
@ -966,21 +1096,25 @@ impl<'a, 'tcx> BitwiseOperator for MaybeUninitializedLvals<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: I'm not sure it ever makes sense to use `true` for a
|
impl<'a, 'tcx> BitwiseOperator for DefinitelyInitializedLvals<'a, 'tcx> {
|
||||||
// DataflowOperator::initial_value implementation, because: the way
|
#[inline]
|
||||||
// that dataflow fixed point iteration works, you want to start at
|
fn join(&self, pred1: usize, pred2: usize) -> usize {
|
||||||
// bottom and work your way to a fixed point.
|
pred1 & pred2 // "definitely" means we intersect effects of both preds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: `DataflowOperator::initial_value` should be named
|
||||||
|
// `bottom_value`. The way that dataflow fixed point iteration works,
|
||||||
|
// you want to start at bottom and work your way to a fixed point.
|
||||||
|
// This needs to include the detail that the control-flow merges will
|
||||||
|
// apply the `join` operator above to current state (which starts at
|
||||||
|
// that bottom value).
|
||||||
//
|
//
|
||||||
// This means, for propagation across the graph, that you either want
|
// This means, for propagation across the graph, that you either want
|
||||||
// to start at all-zeroes and then use Union as your merge when
|
// 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
|
// propagating, or you start at all-ones and then use Intersect as
|
||||||
// your merge when propagating.
|
// your merge when propagating.
|
||||||
//
|
|
||||||
// (An alternative could be, when propagating from Block A into block
|
|
||||||
// B, to clear B's on_entry bits, and then iterate over all of B's
|
|
||||||
// immediate predecessors. This would require storing on_exit state
|
|
||||||
// for each block, however.)
|
|
||||||
|
|
||||||
impl<'a, 'tcx> DataflowOperator for MovingOutStatements<'a, 'tcx> {
|
impl<'a, 'tcx> DataflowOperator for MovingOutStatements<'a, 'tcx> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn initial_value() -> bool {
|
fn initial_value() -> bool {
|
||||||
|
@ -1002,6 +1136,13 @@ impl<'a, 'tcx> DataflowOperator for MaybeUninitializedLvals<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> DataflowOperator for DefinitelyInitializedLvals<'a, 'tcx> {
|
||||||
|
#[inline]
|
||||||
|
fn initial_value() -> bool {
|
||||||
|
true // bottom = initialized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn bitwise<Op:BitwiseOperator>(out_vec: &mut [usize],
|
fn bitwise<Op:BitwiseOperator>(out_vec: &mut [usize],
|
||||||
in_vec: &[usize],
|
in_vec: &[usize],
|
||||||
|
|
|
@ -32,6 +32,7 @@ use self::dataflow::{BitDenotation};
|
||||||
use self::dataflow::{Dataflow, DataflowAnalysis, DataflowResults};
|
use self::dataflow::{Dataflow, DataflowAnalysis, DataflowResults};
|
||||||
use self::dataflow::{HasMoveData};
|
use self::dataflow::{HasMoveData};
|
||||||
use self::dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals};
|
use self::dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals};
|
||||||
|
use self::dataflow::{DefinitelyInitializedLvals};
|
||||||
use self::gather_moves::{MoveData, MovePathIndex, Location};
|
use self::gather_moves::{MoveData, MovePathIndex, Location};
|
||||||
use self::gather_moves::{MovePathContent};
|
use self::gather_moves::{MovePathContent};
|
||||||
|
|
||||||
|
@ -78,6 +79,8 @@ pub fn borrowck_mir<'a, 'tcx: 'a>(
|
||||||
do_dataflow(tcx, mir, id, attributes, ctxt, MaybeInitializedLvals::default());
|
do_dataflow(tcx, mir, id, attributes, ctxt, MaybeInitializedLvals::default());
|
||||||
let (ctxt, flow_uninits) =
|
let (ctxt, flow_uninits) =
|
||||||
do_dataflow(tcx, mir, id, attributes, ctxt, MaybeUninitializedLvals::default());
|
do_dataflow(tcx, mir, id, attributes, ctxt, MaybeUninitializedLvals::default());
|
||||||
|
let (ctxt, flow_def_inits) =
|
||||||
|
do_dataflow(tcx, mir, id, attributes, ctxt, DefinitelyInitializedLvals::default());
|
||||||
|
|
||||||
if has_rustc_mir_with(attributes, "rustc_peek_maybe_init").is_some() {
|
if has_rustc_mir_with(attributes, "rustc_peek_maybe_init").is_some() {
|
||||||
dataflow::sanity_check_via_rustc_peek(bcx.tcx, mir, id, attributes, &ctxt, &flow_inits);
|
dataflow::sanity_check_via_rustc_peek(bcx.tcx, mir, id, attributes, &ctxt, &flow_inits);
|
||||||
|
@ -85,6 +88,9 @@ pub fn borrowck_mir<'a, 'tcx: 'a>(
|
||||||
if has_rustc_mir_with(attributes, "rustc_peek_maybe_uninit").is_some() {
|
if has_rustc_mir_with(attributes, "rustc_peek_maybe_uninit").is_some() {
|
||||||
dataflow::sanity_check_via_rustc_peek(bcx.tcx, mir, id, attributes, &ctxt, &flow_uninits);
|
dataflow::sanity_check_via_rustc_peek(bcx.tcx, mir, id, attributes, &ctxt, &flow_uninits);
|
||||||
}
|
}
|
||||||
|
if has_rustc_mir_with(attributes, "rustc_peek_definite_init").is_some() {
|
||||||
|
dataflow::sanity_check_via_rustc_peek(bcx.tcx, mir, id, attributes, &ctxt, &flow_def_inits);
|
||||||
|
}
|
||||||
let move_data = ctxt.2;
|
let move_data = ctxt.2;
|
||||||
|
|
||||||
if has_rustc_mir_with(attributes, "stop_after_dataflow").is_some() {
|
if has_rustc_mir_with(attributes, "stop_after_dataflow").is_some() {
|
||||||
|
|
63
src/test/compile-fail/mir-dataflow/def-inits-1.rs
Normal file
63
src/test/compile-fail/mir-dataflow/def-inits-1.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// 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 <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.
|
||||||
|
|
||||||
|
// General test of maybe_uninits state computed by MIR dataflow.
|
||||||
|
|
||||||
|
#![feature(rustc_attrs)]
|
||||||
|
#![feature(stmt_expr_attributes)]
|
||||||
|
|
||||||
|
use std::intrinsics::rustc_peek;
|
||||||
|
use std::mem::{drop, replace};
|
||||||
|
|
||||||
|
struct S(i32);
|
||||||
|
|
||||||
|
#[rustc_mir_borrowck]
|
||||||
|
#[rustc_mir(rustc_peek_definite_init,stop_after_dataflow)]
|
||||||
|
fn foo(test: bool, x: &mut S, y: S, mut z: S) -> S {
|
||||||
|
let ret;
|
||||||
|
// `ret` starts off uninitialized
|
||||||
|
unsafe { rustc_peek(&ret); } //~ ERROR rustc_peek: bit not set
|
||||||
|
|
||||||
|
// All function formal parameters start off initialized.
|
||||||
|
|
||||||
|
unsafe { rustc_peek(&x) };
|
||||||
|
unsafe { rustc_peek(&y) };
|
||||||
|
unsafe { rustc_peek(&z) };
|
||||||
|
|
||||||
|
ret = if test {
|
||||||
|
::std::mem::replace(x, y)
|
||||||
|
} else {
|
||||||
|
z = y;
|
||||||
|
z
|
||||||
|
};
|
||||||
|
|
||||||
|
// `z` may be uninitialized here.
|
||||||
|
unsafe { rustc_peek(&z); } //~ ERROR rustc_peek: bit not set
|
||||||
|
|
||||||
|
// `y` is definitely uninitialized here.
|
||||||
|
unsafe { rustc_peek(&y); } //~ ERROR rustc_peek: bit not set
|
||||||
|
|
||||||
|
// `x` is still (definitely) initialized (replace above is a reborrow).
|
||||||
|
unsafe { rustc_peek(&x); }
|
||||||
|
|
||||||
|
::std::mem::drop(x);
|
||||||
|
|
||||||
|
// `x` is *definitely* uninitialized here
|
||||||
|
unsafe { rustc_peek(&x); } //~ ERROR rustc_peek: bit not set
|
||||||
|
|
||||||
|
// `ret` is now definitely initialized (via `if` above).
|
||||||
|
unsafe { rustc_peek(&ret); }
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
foo(true, &mut S(13), S(14), S(15));
|
||||||
|
foo(false, &mut S(13), S(14), S(15));
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue