region_infer: BitMatrix representation of region values

This should be more efficient than allocating two BTreeSets for every
region variable?—as it is written in #45670.
This commit is contained in:
Zack M. Davis 2017-11-20 16:23:26 -08:00 committed by Niko Matsakis
parent a9cb25b23a
commit 22b31758ec
2 changed files with 187 additions and 157 deletions

View File

@ -160,7 +160,7 @@ fn dump_mir_results<'a, 'gcx, 'tcx>(
match pass_where {
// Before the CFG, dump out the values for each region variable.
PassWhere::BeforeCFG => for region in regioncx.regions() {
writeln!(out, "| {:?}: {:?}", region, regioncx.region_value(region))?;
writeln!(out, "| {:?}: {}", region, regioncx.region_value_str(region))?;
},
// Before each basic block, dump out the values

View File

@ -18,7 +18,9 @@
use rustc::ty::{self, RegionVid};
use rustc_data_structures::indexed_vec::IndexVec;
use rustc_data_structures::fx::FxHashSet;
use std::collections::BTreeSet;
use rustc_data_structures::bitvec::BitMatrix;
use rustc_data_structures::indexed_vec::Idx;
use std::collections::BTreeMap;
use std::fmt;
use syntax_pos::Span;
@ -33,15 +35,25 @@ pub struct RegionInferenceContext<'tcx> {
/// regions, these start out empty and steadily grow, though for
/// each free region R they start out containing the entire CFG
/// and `end(R)`.
liveness_constraints: IndexVec<RegionVid, Region>,
///
/// In this `BitMatrix` representation, the rows are the region
/// variables and the columns are the free regions and MIR locations.
liveness_constraints: BitMatrix,
/// The final inferred values of the inference variables; `None`
/// until `solve` is invoked.
inferred_values: Option<IndexVec<RegionVid, Region>>,
inferred_values: Option<BitMatrix>,
/// The constraints we have accumulated and used during solving.
constraints: Vec<Constraint>,
/// A map from each MIR Location to its column index in
/// `liveness_constraints`/`inferred_values`. (The first N columns are
/// the free regions.)
point_indices: BTreeMap<Location, usize>,
num_free_regions: usize,
free_region_map: &'tcx FreeRegionMap<'tcx>,
}
@ -57,42 +69,6 @@ struct RegionDefinition<'tcx> {
name: Option<ty::Region<'tcx>>,
}
/// The value of an individual region variable. Region variables
/// consist of a set of points in the CFG as well as a set of "free
/// regions", which are sometimes written as `end(R)`. These
/// correspond to the named lifetimes and refer to portions of the
/// caller's control-flow graph -- specifically some portion that can
/// be reached after we return.
#[derive(Clone, Default, PartialEq, Eq)]
struct Region {
points: BTreeSet<Location>,
free_regions: BTreeSet<RegionVid>,
}
impl fmt::Debug for Region {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
formatter
.debug_set()
.entries(&self.points)
.entries(&self.free_regions)
.finish()
}
}
impl Region {
fn add_point(&mut self, point: Location) -> bool {
self.points.insert(point)
}
fn add_free_region(&mut self, region: RegionVid) -> bool {
self.free_regions.insert(region)
}
fn contains_point(&self, point: Location) -> bool {
self.points.contains(&point)
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Constraint {
// NB. The ordering here is not significant for correctness, but
@ -119,6 +95,21 @@ impl<'tcx> RegionInferenceContext<'tcx> {
/// regions defined in `free_regions`.
pub fn new(var_origins: VarOrigins, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx>) -> Self {
let num_region_variables = var_origins.len();
let num_free_regions = free_regions.indices.len();
let mut num_points = 0;
let mut point_indices = BTreeMap::new();
for (block, block_data) in mir.basic_blocks().iter_enumerated() {
for statement_index in 0..block_data.statements.len() + 1 {
let location = Location {
block,
statement_index,
};
point_indices.insert(location, num_free_regions + num_points);
num_points += 1;
}
}
// Create a RegionDefinition for each inference variable.
let definitions = var_origins
@ -127,14 +118,19 @@ pub fn new(var_origins: VarOrigins, free_regions: &FreeRegions<'tcx>, mir: &Mir<
.collect();
let mut result = Self {
definitions: definitions,
liveness_constraints: IndexVec::from_elem_n(Region::default(), num_region_variables),
definitions,
liveness_constraints: BitMatrix::new(
num_region_variables,
num_free_regions + num_points,
),
inferred_values: None,
constraints: Vec::new(),
point_indices,
num_free_regions,
free_region_map: free_regions.free_region_map,
};
result.init_free_regions(free_regions, mir);
result.init_free_regions(free_regions);
result
}
@ -158,7 +154,7 @@ pub fn new(var_origins: VarOrigins, free_regions: &FreeRegions<'tcx>, mir: &Mir<
/// and (b) any free regions that it outlives, which in this case
/// is just itself. R1 (`'b`) in contrast also outlives `'a` and
/// hence contains R0 and R1.
fn init_free_regions(&mut self, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx>) {
fn init_free_regions(&mut self, free_regions: &FreeRegions<'tcx>) {
let FreeRegions {
indices,
free_region_map: _,
@ -175,27 +171,15 @@ fn init_free_regions(&mut self, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx
// Initialize the name and a few other details.
self.definitions[variable].name = Some(free_region);
// Add all nodes in the CFG to `definition.value`.
for (block, block_data) in mir.basic_blocks().iter_enumerated() {
let liveness_constraint = &mut self.liveness_constraints[variable];
for statement_index in 0..block_data.statements.len() + 1 {
let location = Location {
block,
statement_index,
};
liveness_constraint.add_point(location);
}
// Add all nodes in the CFG to liveness constraints
for (_location, point_index) in &self.point_indices {
self.liveness_constraints
.add(variable.index(), *point_index);
}
// Add `end(X)` into the set for X.
self.liveness_constraints[variable].add_free_region(variable);
debug!(
"init_free_regions: region variable for `{:?}` is `{:?}` with value `{:?}`",
free_region,
variable,
self.liveness_constraints[variable],
);
self.liveness_constraints
.add(variable.index(), variable.index());
}
}
@ -206,27 +190,76 @@ pub fn regions(&self) -> impl Iterator<Item = RegionVid> {
/// Returns true if the region `r` contains the point `p`.
///
/// Until `solve()` executes, this value is not particularly meaningful.
/// Panics if called before `solve()` executes,
pub fn region_contains_point(&self, r: RegionVid, p: Location) -> bool {
let inferred_values = self.inferred_values
.as_ref()
.expect("region values not yet inferred");
inferred_values[r].contains_point(p)
self.region_contains_point_in_matrix(inferred_values, r, p)
}
/// True if given region `r` contains the point `p`, when
/// evaluated in the set of region values `matrix`.
fn region_contains_point_in_matrix(
&self,
matrix: &BitMatrix,
r: RegionVid,
p: Location,
) -> bool {
let point_index = self.point_indices
.get(&p)
.expect("point index should be known");
matrix.contains(r.index(), *point_index)
}
/// True if given region `r` contains the `end(s)`, when
/// evaluated in the set of region values `matrix`.
fn region_contains_region_in_matrix(
&self,
matrix: &BitMatrix,
r: RegionVid,
s: RegionVid
) -> bool {
matrix.contains(r.index(), s.index())
}
/// Returns access to the value of `r` for debugging purposes.
pub(super) fn region_value(&self, r: RegionVid) -> &fmt::Debug {
pub(super) fn region_value_str(&self, r: RegionVid) -> String {
let inferred_values = self.inferred_values
.as_ref()
.expect("region values not yet inferred");
&inferred_values[r]
let mut result = String::new();
result.push_str("{");
let mut sep = "";
for &point in self.point_indices.keys() {
if self.region_contains_point_in_matrix(inferred_values, r, point) {
result.push_str(&format!("{}{:?}", sep, point));
sep = ", ";
}
}
for fr in (0 .. self.num_free_regions).map(RegionVid::new) {
if self.region_contains_region_in_matrix(inferred_values, r, fr) {
result.push_str(&format!("{}{:?}", sep, fr));
sep = ", ";
}
}
result.push_str("}");
result
}
/// Indicates that the region variable `v` is live at the point `point`.
pub(super) fn add_live_point(&mut self, v: RegionVid, point: Location) {
pub(super) fn add_live_point(&mut self, v: RegionVid, point: Location) -> bool {
debug!("add_live_point({:?}, {:?})", v, point);
assert!(self.inferred_values.is_none(), "values already inferred");
self.liveness_constraints[v].add_point(point);
let point_index = self.point_indices
.get(&point)
.expect("point index should be known");
self.liveness_constraints.add(v.index(), *point_index)
}
/// Indicates that the region variable `sup` must outlive `sub` is live at the point `point`.
@ -285,26 +318,26 @@ fn check_free_region(
) {
let inferred_values = self.inferred_values.as_ref().unwrap();
let fr_name = fr_definition.name.unwrap();
let fr_value = &inferred_values[fr];
let fr_value = inferred_values.iter(fr.index());
// Find every region `o` such that `fr: o`
// (because `fr` includes `end(o)`).
for &outlived_fr in &fr_value.free_regions {
for outlived_fr in fr_value.take_while(|&i| i < self.num_free_regions) {
// `fr` includes `end(fr)`, that's not especially
// interesting.
if fr == outlived_fr {
if fr.index() == outlived_fr {
continue;
}
let outlived_fr_definition = &self.definitions[outlived_fr];
let outlived_fr_definition = &self.definitions[RegionVid::new(outlived_fr)];
let outlived_fr_name = outlived_fr_definition.name.unwrap();
// Check that `o <= fr`. If not, report an error.
if !self.free_region_map
.sub_free_regions(outlived_fr_name, fr_name)
{
// worst error msg ever
let blame_span = self.blame_span(fr, outlived_fr);
// FIXME: worst error msg ever
let blame_span = self.blame_span(fr, RegionVid::new(outlived_fr));
infcx.tcx.sess.span_err(
blame_span,
&format!(
@ -323,7 +356,6 @@ fn check_free_region(
/// feasible, but we check this later.
fn propagate_constraints(&mut self, mir: &Mir<'tcx>) {
let mut changed = true;
let mut dfs = Dfs::new(mir);
debug!("propagate_constraints()");
debug!("propagate_constraints: constraints={:#?}", {
@ -342,15 +374,18 @@ fn propagate_constraints(&mut self, mir: &Mir<'tcx>) {
for constraint in &self.constraints {
debug!("propagate_constraints: constraint={:?}", constraint);
let sub = &inferred_values[constraint.sub].clone();
let sup_value = &mut inferred_values[constraint.sup];
// Grow the value as needed to accommodate the
// outlives constraint.
if dfs.copy(sub, sup_value, constraint.point) {
debug!("propagate_constraints: sub={:?}", sub);
debug!("propagate_constraints: sup={:?}", sup_value);
if self.copy(
&mut inferred_values,
mir,
constraint.sub,
constraint.sup,
constraint.point,
) {
debug!("propagate_constraints: sub={:?}", constraint.sub);
debug!("propagate_constraints: sup={:?}", constraint.sup);
changed = true;
}
}
@ -360,6 +395,77 @@ fn propagate_constraints(&mut self, mir: &Mir<'tcx>) {
self.inferred_values = Some(inferred_values);
}
fn copy(
&self,
inferred_values: &mut BitMatrix,
mir: &Mir<'tcx>,
from_region: RegionVid,
to_region: RegionVid,
start_point: Location,
) -> bool {
let mut changed = false;
let mut stack = vec![];
let mut visited = FxHashSet();
stack.push(start_point);
while let Some(p) = stack.pop() {
debug!(" copy: p={:?}", p);
if !self.region_contains_point_in_matrix(inferred_values, from_region, p) {
debug!(" not in from-region");
continue;
}
if !visited.insert(p) {
debug!(" already visited");
continue;
}
let point_index = self.point_indices.get(&p).unwrap();
changed |= inferred_values.add(to_region.index(), *point_index);
let block_data = &mir[p.block];
let successor_points = if p.statement_index < block_data.statements.len() {
vec![
Location {
statement_index: p.statement_index + 1,
..p
},
]
} else {
block_data
.terminator()
.successors()
.iter()
.map(|&basic_block| {
Location {
statement_index: 0,
block: basic_block,
}
})
.collect::<Vec<_>>()
};
if successor_points.is_empty() {
// If we reach the END point in the graph, then copy
// over any skolemized end points in the `from_region`
// and make sure they are included in the `to_region`.
let free_region_indices = inferred_values
.iter(from_region.index())
.take_while(|&i| i < self.num_free_regions)
.collect::<Vec<_>>();
for fr in &free_region_indices {
changed |= inferred_values.add(to_region.index(), *fr);
}
} else {
stack.extend(successor_points);
}
}
changed
}
/// Tries to finds a good span to blame for the fact that `fr1`
/// contains `fr2`.
fn blame_span(&self, fr1: RegionVid, fr2: RegionVid) -> Span {
@ -413,82 +519,6 @@ fn dependencies(&self, r0: RegionVid) -> IndexVec<RegionVid, bool> {
}
}
struct Dfs<'a, 'tcx: 'a> {
mir: &'a Mir<'tcx>,
}
impl<'a, 'tcx> Dfs<'a, 'tcx> {
fn new(mir: &'a Mir<'tcx>) -> Self {
Self { mir }
}
fn copy(
&mut self,
from_region: &Region,
to_region: &mut Region,
start_point: Location,
) -> bool {
let mut changed = false;
let mut stack = vec![];
let mut visited = FxHashSet();
stack.push(start_point);
while let Some(p) = stack.pop() {
debug!(" dfs: p={:?}", p);
if !from_region.contains_point(p) {
debug!(" not in from-region");
continue;
}
if !visited.insert(p) {
debug!(" already visited");
continue;
}
changed |= to_region.add_point(p);
let block_data = &self.mir[p.block];
let successor_points = if p.statement_index < block_data.statements.len() {
vec![
Location {
statement_index: p.statement_index + 1,
..p
},
]
} else {
block_data
.terminator()
.successors()
.iter()
.map(|&basic_block| {
Location {
statement_index: 0,
block: basic_block,
}
})
.collect::<Vec<_>>()
};
if successor_points.is_empty() {
// If we reach the END point in the graph, then copy
// over any skolemized end points in the `from_region`
// and make sure they are included in the `to_region`.
debug!(" dfs: free_regions={:?}", from_region.free_regions);
for &fr in &from_region.free_regions {
changed |= to_region.free_regions.insert(fr);
}
} else {
stack.extend(successor_points);
}
}
changed
}
}
impl<'tcx> RegionDefinition<'tcx> {
fn new(origin: RegionVariableOrigin) -> Self {
// Create a new region definition. Note that, for free