Speed up nearest_common_ancestor().
`nearest_common_ancestor()` uses an algorithm that requires computing the full scope chain for both scopes, which is expensive because each element involves a hash table lookup, and then looking for a common tail. This patch changes `nearest_common_ancestor()` to use a different algorithm, which starts at the given scopes and works outwards (i.e. up the scope tree) until a common ancestor is found. This is much faster because in most cases the common ancestor is found well before the end of the scope chains. Also, the use of a SmallVec avoids the need for any allocation most of the time.
This commit is contained in:
parent
144c0d5519
commit
cccd51cd6e
1 changed files with 65 additions and 85 deletions
|
|
@ -22,6 +22,7 @@ use ty;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use rustc_data_structures::small_vec::SmallVec;
|
||||||
use rustc_data_structures::sync::Lrc;
|
use rustc_data_structures::sync::Lrc;
|
||||||
use syntax::codemap;
|
use syntax::codemap;
|
||||||
use syntax::ast;
|
use syntax::ast;
|
||||||
|
|
@ -677,95 +678,74 @@ impl<'tcx> ScopeTree {
|
||||||
-> Scope {
|
-> Scope {
|
||||||
if scope_a == scope_b { return scope_a; }
|
if scope_a == scope_b { return scope_a; }
|
||||||
|
|
||||||
// [1] The initial values for `a_buf` and `b_buf` are not used.
|
// Process the lists in tandem from the innermost scope, recording the
|
||||||
// The `ancestors_of` function will return some prefix that
|
// scopes seen so far. The first scope that comes up for a second time
|
||||||
// is re-initialized with new values (or else fallback to a
|
// is the nearest common ancestor.
|
||||||
// heap-allocated vector).
|
//
|
||||||
let mut a_buf: [Scope; 32] = [scope_a /* [1] */; 32];
|
// Note: another way to compute the nearest common ancestor is to get
|
||||||
let mut a_vec: Vec<Scope> = vec![];
|
// the full scope chain for both scopes and then compare the chains to
|
||||||
let mut b_buf: [Scope; 32] = [scope_b /* [1] */; 32];
|
// find the first scope in a common tail. But getting a parent scope
|
||||||
let mut b_vec: Vec<Scope> = vec![];
|
// requires a hash table lookup, and we often have very long scope
|
||||||
let parent_map = &self.parent_map;
|
// chains (10s or 100s of scopes) that only differ by a few elements at
|
||||||
let a_ancestors = ancestors_of(parent_map, scope_a, &mut a_buf, &mut a_vec);
|
// the start. So this algorithm is faster.
|
||||||
let b_ancestors = ancestors_of(parent_map, scope_b, &mut b_buf, &mut b_vec);
|
let mut ma = Some(scope_a);
|
||||||
let mut a_index = a_ancestors.len() - 1;
|
let mut mb = Some(scope_b);
|
||||||
let mut b_index = b_ancestors.len() - 1;
|
let mut seen: SmallVec<[Scope; 32]> = SmallVec::new();
|
||||||
|
loop {
|
||||||
// Here, [ab]_ancestors is a vector going from narrow to broad.
|
if let Some(a) = ma {
|
||||||
// The end of each vector will be the item where the scope is
|
if seen.iter().position(|s| *s == a).is_some() {
|
||||||
// defined; if there are any common ancestors, then the tails of
|
return a;
|
||||||
// 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 = b_ancestors[b_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!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {
|
seen.push(a);
|
||||||
// root ids are always Node right now
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ma.is_none() && mb.is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn outermost_scope(parent_map: &FxHashMap<Scope, Scope>, scope: Scope) -> Scope {
|
||||||
|
let mut scope = scope;
|
||||||
|
loop {
|
||||||
|
match parent_map.get(&scope) {
|
||||||
|
Some(&superscope) => scope = superscope,
|
||||||
|
None => break scope,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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!()
|
bug!()
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
_ => {
|
||||||
|
// root ids are always Node right now
|
||||||
fn ancestors_of<'a, 'tcx>(parent_map: &FxHashMap<Scope, Scope>,
|
bug!()
|
||||||
scope: Scope,
|
|
||||||
buf: &'a mut [Scope; 32],
|
|
||||||
vec: &'a mut Vec<Scope>)
|
|
||||||
-> &'a [Scope] {
|
|
||||||
// debug!("ancestors_of(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
*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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue