Skip to content

Commit

Permalink
correct subtle bug in the type variable code
Browse files Browse the repository at this point in the history
  • Loading branch information
nikomatsakis authored and sgrif committed Mar 1, 2018
1 parent 57a593f commit ccd92c2
Showing 1 changed file with 63 additions and 108 deletions.
171 changes: 63 additions & 108 deletions src/librustc/infer/type_variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ use syntax::ast;
use syntax_pos::Span;
use ty::{self, Ty};

use std::cmp::min;
use std::marker::PhantomData;
use std::u32;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::snapshot_vec as sv;
use rustc_data_structures::unify as ut;

pub struct TypeVariableTable<'tcx> {
values: sv::SnapshotVec<Delegate>,
/// Extra data for each type variable, such as the origin. This is
/// not stored in the unification table since, when we inquire
/// after the origin of a variable X, we want the origin of **that
/// variable X**, not the origin of some other variable Y with
/// which X has been unified.
var_data: Vec<TypeVariableData>,

/// Two variables are unified in `eq_relations` when we have a
/// constraint `?X == ?Y`. This table also stores, for each key,
Expand Down Expand Up @@ -82,21 +85,20 @@ enum TypeVariableValue<'tcx> {
}

pub struct Snapshot<'tcx> {
snapshot: sv::Snapshot,
/// number of variables at the time of the snapshot
num_vars: usize,

/// snapshot from the `eq_relations` table
eq_snapshot: ut::Snapshot<ut::InPlace<TyVidEqKey<'tcx>>>,
sub_snapshot: ut::Snapshot<ut::InPlace<ty::TyVid>>,
}

struct Instantiate {
vid: ty::TyVid,
/// snapshot from the `sub_relations` table
sub_snapshot: ut::Snapshot<ut::InPlace<ty::TyVid>>,
}

struct Delegate;

impl<'tcx> TypeVariableTable<'tcx> {
pub fn new() -> TypeVariableTable<'tcx> {
TypeVariableTable {
values: sv::SnapshotVec::new(),
var_data: Vec::new(),
eq_relations: ut::UnificationTable::new(),
sub_relations: ut::UnificationTable::new(),
}
Expand All @@ -107,15 +109,15 @@ impl<'tcx> TypeVariableTable<'tcx> {
/// Note that this function does not return care whether
/// `vid` has been unified with something else or not.
pub fn var_diverges<'a>(&'a self, vid: ty::TyVid) -> bool {
self.values.get(vid.index as usize).diverging
self.var_data[vid.index as usize].diverging
}

/// Returns the origin that was given when `vid` was created.
///
/// Note that this function does not return care whether
/// `vid` has been unified with something else or not.
pub fn var_origin(&self, vid: ty::TyVid) -> &TypeVariableOrigin {
&self.values.get(vid.index as usize).origin
&self.var_data[vid.index as usize].origin
}

/// Records that `a == b`, depending on `dir`.
Expand Down Expand Up @@ -147,11 +149,6 @@ impl<'tcx> TypeVariableTable<'tcx> {
"instantiating type variable `{:?}` twice: new-value = {:?}, old-value={:?}",
vid, ty, self.eq_relations.probe_value(vid));
self.eq_relations.union_value(vid, TypeVariableValue::Known { value: ty });

// Hack: we only need this so that `types_escaping_snapshot`
// can see what has been unified; see the Delegate impl for
// more details.
self.values.record(Instantiate { vid: vid });
}

/// Creates a new type variable.
Expand All @@ -173,11 +170,8 @@ impl<'tcx> TypeVariableTable<'tcx> {
let sub_key = self.sub_relations.new_key(());
assert_eq!(eq_key.vid, sub_key);

let index = self.values.push(TypeVariableData {
origin,
diverging,
});
assert_eq!(eq_key.vid.index, index as u32);
assert_eq!(self.var_data.len(), sub_key.index as usize);
self.var_data.push(TypeVariableData { origin, diverging });

debug!("new_var(index={:?}, diverging={:?}, origin={:?}", eq_key.vid, diverging, origin);

Expand All @@ -186,7 +180,7 @@ impl<'tcx> TypeVariableTable<'tcx> {

/// Returns the number of type variables created thus far.
pub fn num_vars(&self) -> usize {
self.values.len()
self.var_data.len()
}

/// Returns the "root" variable of `vid` in the `eq_relations`
Expand Down Expand Up @@ -246,7 +240,7 @@ impl<'tcx> TypeVariableTable<'tcx> {
/// be processed in a stack-like fashion.
pub fn snapshot(&mut self) -> Snapshot<'tcx> {
Snapshot {
snapshot: self.values.start_snapshot(),
num_vars: self.var_data.len(),
eq_snapshot: self.eq_relations.snapshot(),
sub_snapshot: self.sub_relations.snapshot(),
}
Expand All @@ -256,30 +250,21 @@ impl<'tcx> TypeVariableTable<'tcx> {
/// snapshots created since that point must already have been
/// committed or rolled back.
pub fn rollback_to(&mut self, s: Snapshot<'tcx>) {
debug!("rollback_to{:?}", {
for action in self.values.actions_since_snapshot(&s.snapshot) {
match *action {
sv::UndoLog::NewElem(index) => {
debug!("inference variable _#{}t popped", index)
}
_ => { }
}
}
});

let Snapshot { snapshot, eq_snapshot, sub_snapshot } = s;
self.values.rollback_to(snapshot);
let Snapshot { num_vars, eq_snapshot, sub_snapshot } = s;
debug!("type_variables::rollback_to(num_vars = {})", num_vars);
assert!(self.var_data.len() >= num_vars);
self.eq_relations.rollback_to(eq_snapshot);
self.sub_relations.rollback_to(sub_snapshot);
self.var_data.truncate(num_vars);
}

/// Commits all changes since the snapshot was created, making
/// them permanent (unless this snapshot was created within
/// another snapshot). Any snapshots created since that point
/// must already have been committed or rolled back.
pub fn commit(&mut self, s: Snapshot<'tcx>) {
let Snapshot { snapshot, eq_snapshot, sub_snapshot } = s;
self.values.commit(snapshot);
let Snapshot { num_vars, eq_snapshot, sub_snapshot } = s;
debug!("type_variables::commit(num_vars = {})", num_vars);
self.eq_relations.commit(eq_snapshot);
self.sub_relations.commit(sub_snapshot);
}
Expand All @@ -288,19 +273,12 @@ impl<'tcx> TypeVariableTable<'tcx> {
/// ty-variables created during the snapshot, and the values
/// `{V2}` are the root variables that they were unified with,
/// along with their origin.
pub fn types_created_since_snapshot(&mut self, s: &Snapshot<'tcx>) -> TypeVariableMap {
let actions_since_snapshot = self.values.actions_since_snapshot(&s.snapshot);

actions_since_snapshot
pub fn types_created_since_snapshot(&mut self, snapshot: &Snapshot<'tcx>) -> TypeVariableMap {
self.var_data
.iter()
.filter_map(|action| match action {
&sv::UndoLog::NewElem(index) => Some(ty::TyVid { index: index as u32 }),
_ => None,
})
.map(|vid| {
let origin = self.values.get(vid.index as usize).origin.clone();
(vid, origin)
})
.enumerate()
.skip(snapshot.num_vars) // skip those that existed when snapshot was taken
.map(|(index, data)| (ty::TyVid { index: index as u32 }, data.origin))
.collect()
}

Expand All @@ -310,47 +288,45 @@ impl<'tcx> TypeVariableTable<'tcx> {
/// a type variable `V0`, then we started the snapshot, then we
/// created a type variable `V1`, unifed `V0` with `T0`, and
/// unified `V1` with `T1`, this function would return `{T0}`.
pub fn types_escaping_snapshot(&mut self, s: &Snapshot<'tcx>) -> Vec<Ty<'tcx>> {
let mut new_elem_threshold = u32::MAX;
let mut escaping_types = Vec::new();
let actions_since_snapshot = self.values.actions_since_snapshot(&s.snapshot);
debug!("actions_since_snapshot.len() = {}", actions_since_snapshot.len());
for action in actions_since_snapshot {
match *action {
sv::UndoLog::NewElem(index) => {
// if any new variables were created during the
// snapshot, remember the lower index (which will
// always be the first one we see). Note that this
// action must precede those variables being
// specified.
new_elem_threshold = min(new_elem_threshold, index as u32);
debug!("NewElem({}) new_elem_threshold={}", index, new_elem_threshold);
}

sv::UndoLog::Other(Instantiate { vid, .. }) => {
if vid.index < new_elem_threshold {
// quick check to see if this variable was
// created since the snapshot started or not.
let escaping_type = match self.eq_relations.probe_value(vid) {
TypeVariableValue::Unknown => bug!(),
TypeVariableValue::Known { value } => value,
};
escaping_types.push(escaping_type);
}
debug!("SpecifyVar({:?}) new_elem_threshold={}", vid, new_elem_threshold);
}

_ => { }
}
}

pub fn types_escaping_snapshot(&mut self, snapshot: &Snapshot<'tcx>) -> Vec<Ty<'tcx>> {
// We want to select only those instantiations that have
// occurred since the snapshot *and* which affect some
// variable that existed prior to the snapshot. This code just
// affects all instantiatons that ever occurred which affect
// variables prior to the snapshot.
//
// It's hard to do better than this, though, without changing
// the unification table to prefer "lower" vids -- the problem
// is that we may have a variable X (from before the snapshot)
// and Y (from after the snapshot) which get unified, with Y
// chosen as the new root. Now we are "instantiating" Y with a
// value, but it escapes into X, but we wouldn't readily see
// that. (In fact, earlier revisions of this code had this
// bug; it was introduced when we added the `eq_relations`
// table, but it's hard to create rust code that triggers it.)
//
// We could tell the table to prefer lower vids, and then we would
// see the case above, but we would get less-well-balanced trees.
//
// Since I hope to kill the leak-check in this branch, and
// that's the code which uses this logic anyway, I'm going to
// use the less efficient algorithm for now.
let mut escaping_types = Vec::with_capacity(snapshot.num_vars);
escaping_types.extend(
(0..snapshot.num_vars) // for all variables that pre-exist the snapshot...
.map(|i| ty::TyVid { index: i as u32 })
.filter_map(|vid| match self.eq_relations.probe_value(vid) {
TypeVariableValue::Unknown => None,
TypeVariableValue::Known { value } => Some(value),
})); // ...collect what types they've been instantiated with.
debug!("types_escaping_snapshot = {:?}", escaping_types);
escaping_types
}

/// Returns indices of all variables that are not yet
/// instantiated.
pub fn unsolved_variables(&mut self) -> Vec<ty::TyVid> {
(0..self.values.len())
(0..self.var_data.len())
.filter_map(|i| {
let vid = ty::TyVid { index: i as u32 };
if self.probe(vid).is_some() {
Expand All @@ -362,27 +338,6 @@ impl<'tcx> TypeVariableTable<'tcx> {
.collect()
}
}

impl sv::SnapshotVecDelegate for Delegate {
type Value = TypeVariableData;
type Undo = Instantiate;

fn reverse(_values: &mut Vec<TypeVariableData>, _action: Instantiate) {
// We don't actually have to *do* anything to reverse an
// instanation; the value for a variable is stored in the
// `eq_relations` and hence its rollback code will handle
// it. In fact, we could *almost* just remove the
// `SnapshotVec` entirely, except that we would have to
// reproduce *some* of its logic, since we want to know which
// type variables have been instantiated since the snapshot
// was started, so we can implement `types_escaping_snapshot`.
//
// (If we extended the `UnificationTable` to let us see which
// values have been unified and so forth, that might also
// suffice.)
}
}

///////////////////////////////////////////////////////////////////////////

/// These structs (a newtyped TyVid) are used as the unification key
Expand Down

0 comments on commit ccd92c2

Please sign in to comment.