compute union-find of locals flowing into the output of statics

Co-authored-by: lqd <remy.rakic+github@gmail.com>
Co-authored-by: nikomatsakis <niko@alum.mit.edu>
This commit is contained in:
Niko Matsakis 2018-08-02 14:14:20 +03:00
parent db5476571d
commit 341a07c4c3
5 changed files with 203 additions and 24 deletions

View file

@ -0,0 +1,161 @@
// Copyright 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 <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.
//! A MIR walk gathering a union-crfind of assigned locals, for the purpose of locating the ones
//! escaping into the output.
use rustc::mir::visit::Visitor;
use rustc::mir::*;
use rustc_data_structures::indexed_vec::Idx;
use rustc_data_structures::unify as ut;
crate struct EscapingLocals {
unification_table: ut::UnificationTable<ut::InPlace<AssignedLocal>>,
}
impl EscapingLocals {
crate fn compute(mir: &Mir<'_>) -> Self {
let mut visitor = GatherAssignedLocalsVisitor::new();
visitor.visit_mir(mir);
EscapingLocals { unification_table: visitor.unification_table }
}
/// True if `local` is known to escape into static
/// memory.
crate fn escapes_into_return(&mut self, local: Local) -> bool {
let return_place = AssignedLocal::from(RETURN_PLACE);
let other_place = AssignedLocal::from(local);
self.unification_table.unioned(return_place, other_place)
}
}
/// The MIR visitor gathering the union-find of the locals used in
/// assignments.
struct GatherAssignedLocalsVisitor {
unification_table: ut::UnificationTable<ut::InPlace<AssignedLocal>>,
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
struct AssignedLocal(u32);
impl ut::UnifyKey for AssignedLocal {
type Value = ();
fn index(&self) -> u32 {
self.0
}
fn from_index(i: u32) -> AssignedLocal {
AssignedLocal(i)
}
fn tag() -> &'static str {
"AssignedLocal"
}
}
impl From<Local> for AssignedLocal {
fn from(item: Local) -> Self {
// newtype_indexes use usize but are u32s.
assert!(item.index() < ::std::u32::MAX as usize);
AssignedLocal(item.index() as u32)
}
}
impl GatherAssignedLocalsVisitor {
fn new() -> Self {
Self {
unification_table: ut::UnificationTable::new(),
}
}
fn union_locals_if_needed(&mut self, lvalue: Option<Local>, rvalue: Option<Local>) {
if let Some(lvalue) = lvalue {
if let Some(rvalue) = rvalue {
if lvalue != rvalue {
self.unification_table
.union(AssignedLocal::from(lvalue), AssignedLocal::from(rvalue));
}
}
}
}
}
// Returns the potential `Local` associated to this `Place` or `PlaceProjection`
fn find_local_in_place(place: &Place) -> Option<Local> {
match place {
Place::Local(local) => Some(*local),
// If you do e.g. `x = a.f` then only *part* of the type of
// `a` escapes into `x` (the part contained in `f`); if `a`'s
// type has regions that don't appear in `f`, those might not
// escape.
Place::Projection(..) => None,
Place::Static { .. } | Place::Promoted { .. } => None,
}
}
// Returns the potential `Local` in this `Operand`.
fn find_local_in_operand(op: &Operand) -> Option<Local> {
// Conservatively check a subset of `Operand`s we know our
// benchmarks track, for example `html5ever`.
match op {
Operand::Copy(place) | Operand::Move(place) => find_local_in_place(place),
Operand::Constant(_) => None,
}
}
impl<'tcx> Visitor<'tcx> for GatherAssignedLocalsVisitor {
fn visit_mir(&mut self, mir: &Mir<'tcx>) {
// We need as many union-find keys as there are locals
for _ in 0..mir.local_decls.len() {
self.unification_table.new_key(());
}
self.super_mir(mir);
}
fn visit_assign(
&mut self,
block: BasicBlock,
place: &Place<'tcx>,
rvalue: &Rvalue<'tcx>,
location: Location,
) {
let local = find_local_in_place(place);
// Conservatively check a subset of `Rvalue`s we know our
// benchmarks track, for example `html5ever`.
match rvalue {
Rvalue::Use(op) => self.union_locals_if_needed(local, find_local_in_operand(op)),
Rvalue::Ref(_, _, place) => {
self.union_locals_if_needed(local, find_local_in_place(place))
}
Rvalue::Cast(kind, op, _) => match kind {
CastKind::Unsize => self.union_locals_if_needed(local, find_local_in_operand(op)),
_ => (),
},
Rvalue::Aggregate(_, ops) => {
for rvalue in ops.iter().map(find_local_in_operand) {
self.union_locals_if_needed(local, rvalue);
}
}
_ => (),
};
self.super_assign(block, place, rvalue, location);
}
}

View file

@ -16,9 +16,10 @@
//! liveness code so that it only operates over variables with regions in their
//! types, instead of all variables.
use borrow_check::nll::escaping_locals::EscapingLocals;
use rustc::mir::{Local, Mir};
use rustc::ty::TypeFoldable;
use rustc_data_structures::indexed_vec::IndexVec;
use rustc::mir::{Mir, Local};
use util::liveness::LiveVariableMap;
use rustc_data_structures::indexed_vec::Idx;
@ -29,14 +30,13 @@ use rustc_data_structures::indexed_vec::Idx;
crate struct NllLivenessMap {
/// For each local variable, contains either None (if the type has no regions)
/// or Some(i) with a suitable index.
pub from_local: IndexVec<Local, Option<LocalWithRegion>>,
/// For each LocalWithRegion, maps back to the original Local index.
pub to_local: IndexVec<LocalWithRegion, Local>,
from_local: IndexVec<Local, Option<LocalWithRegion>>,
/// For each LocalWithRegion, maps back to the original Local index.
to_local: IndexVec<LocalWithRegion, Local>,
}
impl LiveVariableMap for NllLivenessMap {
fn from_local(&self, local: Local) -> Option<Self::LiveVar> {
self.from_local[local]
}
@ -55,21 +55,33 @@ impl LiveVariableMap for NllLivenessMap {
impl NllLivenessMap {
/// Iterates over the variables in Mir and assigns each Local whose type contains
/// regions a LocalWithRegion index. Returns a map for converting back and forth.
pub fn compute(mir: &Mir) -> Self {
crate fn compute(mir: &Mir<'_>) -> Self {
let mut escaping_locals = EscapingLocals::compute(mir);
let mut to_local = IndexVec::default();
let from_local: IndexVec<Local,Option<_>> = mir
let from_local: IndexVec<Local, Option<_>> = mir
.local_decls
.iter_enumerated()
.map(|(local, local_decl)| {
if local_decl.ty.has_free_regions() {
if escaping_locals.escapes_into_return(local) {
// If the local escapes into the return value,
// then the return value will force all of the
// regions in its type to outlive free regions
// (e.g., `'static`) and hence liveness is not
// needed. This is particularly important for big
// statics.
None
} else if local_decl.ty.has_free_regions() {
Some(to_local.push(local))
} else {
None
}
else {
None
}
}).collect();
Self { from_local, to_local }
Self {
from_local,
to_local,
}
}
}

View file

@ -39,7 +39,9 @@ use polonius_engine::{Algorithm, Output};
use util as mir_util;
use util::pretty::{self, ALIGN};
mod constraints;
mod constraint_generation;
mod escaping_locals;
pub mod explain_borrow;
mod facts;
mod invalidation;
@ -49,8 +51,6 @@ crate mod type_check;
mod universal_regions;
crate mod liveness_map;
mod constraints;
use self::facts::AllFacts;
use self::region_infer::RegionInferenceContext;
use self::universal_regions::UniversalRegions;
@ -120,6 +120,7 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
location_table,
borrow_set,
&liveness,
&liveness_map,
&mut all_facts,
flow_inits,
move_data,

View file

@ -37,6 +37,7 @@ pub(super) fn generate<'gcx, 'tcx>(
cx: &mut TypeChecker<'_, 'gcx, 'tcx>,
mir: &Mir<'tcx>,
liveness: &LivenessResults<LocalWithRegion>,
liveness_map: &NllLivenessMap,
flow_inits: &mut FlowAtLocation<MaybeInitializedPlaces<'_, 'gcx, 'tcx>>,
move_data: &MoveData<'tcx>,
) {
@ -44,10 +45,10 @@ pub(super) fn generate<'gcx, 'tcx>(
cx,
mir,
liveness,
liveness_map,
flow_inits,
move_data,
drop_data: FxHashMap(),
map: &NllLivenessMap::compute(mir),
};
for bb in mir.basic_blocks().indices() {
@ -65,10 +66,10 @@ where
cx: &'gen mut TypeChecker<'typeck, 'gcx, 'tcx>,
mir: &'gen Mir<'tcx>,
liveness: &'gen LivenessResults<LocalWithRegion>,
liveness_map: &'gen NllLivenessMap,
flow_inits: &'gen mut FlowAtLocation<MaybeInitializedPlaces<'flow, 'gcx, 'tcx>>,
move_data: &'gen MoveData<'tcx>,
drop_data: FxHashMap<Ty<'tcx>, DropData<'tcx>>,
map: &'gen NllLivenessMap,
}
struct DropData<'tcx> {
@ -86,9 +87,9 @@ impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flo
self.liveness
.regular
.simulate_block(self.mir, bb, self.map, |location, live_locals| {
.simulate_block(self.mir, bb, self.liveness_map, |location, live_locals| {
for live_local in live_locals.iter() {
let local = self.map.from_live_var(live_local);
let local = self.liveness_map.from_live_var(live_local);
let live_local_ty = self.mir.local_decls[local].ty;
Self::push_type_live_constraint(&mut self.cx, live_local_ty, location);
}
@ -97,7 +98,7 @@ impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flo
let mut all_live_locals: Vec<(Location, Vec<LocalWithRegion>)> = vec![];
self.liveness
.drop
.simulate_block(self.mir, bb, self.map, |location, live_locals| {
.simulate_block(self.mir, bb, self.liveness_map, |location, live_locals| {
all_live_locals.push((location, live_locals.iter().collect()));
});
debug!(
@ -124,7 +125,7 @@ impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flo
});
}
let local = self.map.from_live_var(live_local);
let local = self.liveness_map.from_live_var(live_local);
let mpi = self.move_data.rev_lookup.find_local(local);
if let Some(initialized_child) = self.flow_inits.has_any_child_of(mpi) {
debug!(
@ -133,7 +134,7 @@ impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flo
self.move_data.move_paths[initialized_child]
);
let local = self.map.from_live_var(live_local);
let local = self.liveness_map.from_live_var(live_local);
let live_local_ty = self.mir.local_decls[local].ty;
self.add_drop_live_constraint(live_local, live_local_ty, location);
}

View file

@ -15,9 +15,12 @@ use borrow_check::borrow_set::BorrowSet;
use borrow_check::location::LocationTable;
use borrow_check::nll::constraints::{ConstraintSet, OutlivesConstraint};
use borrow_check::nll::facts::AllFacts;
use borrow_check::nll::region_infer::values::{RegionValueElements, LivenessValues};
use borrow_check::nll::liveness_map::NllLivenessMap;
use borrow_check::nll::region_infer::values::{LivenessValues, RegionValueElements};
use borrow_check::nll::region_infer::{ClosureRegionRequirementsExt, TypeTest};
use borrow_check::nll::type_check::free_region_relations::{CreateResult, UniversalRegionRelations};
use borrow_check::nll::type_check::free_region_relations::{
CreateResult, UniversalRegionRelations,
};
use borrow_check::nll::universal_regions::UniversalRegions;
use borrow_check::nll::LocalWithRegion;
use borrow_check::nll::ToRegionVid;
@ -116,6 +119,7 @@ pub(crate) fn type_check<'gcx, 'tcx>(
location_table: &LocationTable,
borrow_set: &BorrowSet<'tcx>,
liveness: &LivenessResults<LocalWithRegion>,
liveness_map: &NllLivenessMap,
all_facts: &mut Option<AllFacts>,
flow_inits: &mut FlowAtLocation<MaybeInitializedPlaces<'_, 'gcx, 'tcx>>,
move_data: &MoveData<'tcx>,
@ -166,7 +170,7 @@ pub(crate) fn type_check<'gcx, 'tcx>(
Some(&mut borrowck_context),
Some(errors_buffer),
|cx| {
liveness::generate(cx, mir, liveness, flow_inits, move_data);
liveness::generate(cx, mir, liveness, liveness_map, flow_inits, move_data);
cx.equate_inputs_and_outputs(
mir,
mir_def_id,