Skip to content

Speed up nearest_common_ancestor. #50106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 25, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 60 additions & 80 deletions src/librustc/middle/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use ty;

use std::fmt;
use std::mem;
use rustc_data_structures::small_vec::SmallVec;
use rustc_data_structures::sync::Lrc;
use syntax::codemap;
use syntax::ast;
Expand Down Expand Up @@ -677,96 +678,75 @@ impl<'tcx> ScopeTree {
-> Scope {
if scope_a == scope_b { return scope_a; }

// [1] The initial values for `a_buf` and `b_buf` are not used.
// The `ancestors_of` function will return some prefix that
// is re-initialized with new values (or else fallback to a
// heap-allocated vector).
let mut a_buf: [Scope; 32] = [scope_a /* [1] */; 32];
let mut a_vec: Vec<Scope> = vec![];
let mut b_buf: [Scope; 32] = [scope_b /* [1] */; 32];
let mut b_vec: Vec<Scope> = vec![];
let parent_map = &self.parent_map;
let a_ancestors = ancestors_of(parent_map, scope_a, &mut a_buf, &mut a_vec);
let b_ancestors = ancestors_of(parent_map, scope_b, &mut b_buf, &mut b_vec);
let mut a_index = a_ancestors.len() - 1;
let mut b_index = b_ancestors.len() - 1;

// Here, [ab]_ancestors is a vector going from narrow to broad.
// The end of each vector will be the item where the scope is
// defined; if there are any common ancestors, then the tails of
// the vector will be the same. So basically we want to walk
// backwards from the tail of each vector and find the first point
// where they diverge. If one vector is a suffix of the other,
// then the corresponding scope is a superscope of the other.

if a_ancestors[a_index] != b_ancestors[b_index] {
// In this case, the two regions belong to completely
// different functions. Compare those fn for lexical
// nesting. The reasoning behind this is subtle. See the
// "Modeling closures" section of the README in
// infer::region_constraints for more details.
let a_root_scope = a_ancestors[a_index];
let b_root_scope = a_ancestors[a_index];
return match (a_root_scope.data(), b_root_scope.data()) {
(ScopeData::Destruction(a_root_id),
ScopeData::Destruction(b_root_id)) => {
if self.closure_is_enclosed_by(a_root_id, b_root_id) {
// `a` is enclosed by `b`, hence `b` is the ancestor of everything in `a`
scope_b
} else if self.closure_is_enclosed_by(b_root_id, a_root_id) {
// `b` is enclosed by `a`, hence `a` is the ancestor of everything in `b`
scope_a
} else {
// neither fn encloses the other
bug!()
}
// Process the lists in tandem from the innermost scope, recording the
// scopes seen so far. The first scope that comes up for a second time
// is the nearest common ancestor.
//
// Note: another way to compute the nearest common ancestor is to get
// the full scope chain for both scopes and then compare the chains to
// find the first scope in a common tail. But getting a parent scope
// requires a hash table lookup, and we often have very long scope
// chains (10s or 100s of scopes) that only differ by a few elements at
// the start. So this algorithm is faster.
let mut ma = Some(scope_a);
let mut mb = Some(scope_b);
let mut seen: SmallVec<[Scope; 32]> = SmallVec::new();
loop {
if let Some(a) = ma {
if seen.iter().position(|s| *s == a).is_some() {
return a;
}
_ => {
// root ids are always Node right now
bug!()
seen.push(a);
ma = self.parent_map.get(&a).map(|s| *s);
}

if let Some(b) = mb {
if seen.iter().position(|s| *s == b).is_some() {
return b;
}
};
}
seen.push(b);
mb = self.parent_map.get(&b).map(|s| *s);
}

loop {
// Loop invariant: a_ancestors[a_index] == b_ancestors[b_index]
// for all indices between a_index and the end of the array
if a_index == 0 { return scope_a; }
if b_index == 0 { return scope_b; }
a_index -= 1;
b_index -= 1;
if a_ancestors[a_index] != b_ancestors[b_index] {
return a_ancestors[a_index + 1];
if ma.is_none() && mb.is_none() {
break;
}
}
};

fn ancestors_of<'a, 'tcx>(parent_map: &FxHashMap<Scope, Scope>,
scope: Scope,
buf: &'a mut [Scope; 32],
vec: &'a mut Vec<Scope>)
-> &'a [Scope] {
// debug!("ancestors_of(scope={:?})", scope);
fn outermost_scope(parent_map: &FxHashMap<Scope, Scope>, scope: Scope) -> Scope {
let mut scope = scope;

let mut i = 0;
while i < 32 {
buf[i] = scope;
match parent_map.get(&scope) {
Some(&superscope) => scope = superscope,
_ => return &buf[..i+1]
}
i += 1;
loop {
match parent_map.get(&scope) {
Some(&superscope) => scope = superscope,
None => break scope,
}
}
}

*vec = Vec::with_capacity(64);
vec.extend_from_slice(buf);
loop {
vec.push(scope);
match parent_map.get(&scope) {
Some(&superscope) => scope = superscope,
_ => return &*vec
// In this (rare) case, the two regions belong to completely different
// functions. Compare those fn for lexical nesting. The reasoning
// behind this is subtle. See the "Modeling closures" section of the
// README in infer::region_constraints for more details.
let a_root_scope = outermost_scope(&self.parent_map, scope_a);
let b_root_scope = outermost_scope(&self.parent_map, scope_b);
match (a_root_scope.data(), b_root_scope.data()) {
(ScopeData::Destruction(a_root_id),
ScopeData::Destruction(b_root_id)) => {
if self.closure_is_enclosed_by(a_root_id, b_root_id) {
// `a` is enclosed by `b`, hence `b` is the ancestor of everything in `a`
scope_b
} else if self.closure_is_enclosed_by(b_root_id, a_root_id) {
// `b` is enclosed by `a`, hence `a` is the ancestor of everything in `b`
scope_a
} else {
// neither fn encloses the other
bug!()
}
}
_ => {
// root ids are always Node right now
bug!()
}
}
}

Expand Down