From d6e5797e4130c9925734b0a42b4e8d5a1a23073a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 22 Jul 2014 07:40:51 -0400 Subject: [PATCH] Introduce snapshot_vec abstraction --- src/librustc/lib.rs | 1 + src/librustc/middle/typeck/infer/mod.rs | 6 +- src/librustc/middle/typeck/infer/unify.rs | 160 +++--------------- src/librustc/util/snapshot_vec.rs | 195 ++++++++++++++++++++++ 4 files changed, 225 insertions(+), 137 deletions(-) create mode 100644 src/librustc/util/snapshot_vec.rs diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index 9b3cc2b6a0a..ebce1a2a69b 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -136,6 +136,7 @@ pub mod util { pub mod common; pub mod ppaux; pub mod nodemap; + pub mod snapshot_vec; } pub mod lib { diff --git a/src/librustc/middle/typeck/infer/mod.rs b/src/librustc/middle/typeck/infer/mod.rs index c6312ec4663..e672b8b310f 100644 --- a/src/librustc/middle/typeck/infer/mod.rs +++ b/src/librustc/middle/typeck/infer/mod.rs @@ -555,13 +555,13 @@ impl<'a> InferCtxt<'a> { self.type_unification_table .borrow_mut() - .rollback_to(self.tcx, type_snapshot); + .rollback_to(type_snapshot); self.int_unification_table .borrow_mut() - .rollback_to(self.tcx, int_snapshot); + .rollback_to(int_snapshot); self.float_unification_table .borrow_mut() - .rollback_to(self.tcx, float_snapshot); + .rollback_to(float_snapshot); self.region_vars .rollback_to(region_vars_snapshot); } diff --git a/src/librustc/middle/typeck/infer/unify.rs b/src/librustc/middle/typeck/infer/unify.rs index 33bdded5234..fd722d36966 100644 --- a/src/librustc/middle/typeck/infer/unify.rs +++ b/src/librustc/middle/typeck/infer/unify.rs @@ -16,9 +16,9 @@ use middle::typeck::infer::{Bounds, uok, ures}; use middle::typeck::infer::InferCtxt; use std::cell::RefCell; use std::fmt::Show; -use std::mem; use syntax::ast; use util::ppaux::Repr; +use util::snapshot_vec as sv; /** * This trait is implemented by any type that can serve as a type @@ -82,13 +82,8 @@ pub struct UnificationTable { /** * Indicates the current value of each key. */ - values: Vec>, - /** - * When a snapshot is active, logs each change made to the table - * so that they can be unrolled. - */ - undo_log: Vec>, + values: sv::SnapshotVec,(),Delegate>, } /** @@ -96,29 +91,9 @@ pub struct UnificationTable { * made during the snapshot may either be *committed* or *rolled back*. */ pub struct Snapshot { - // Ensure that this snapshot is keyed to the table type. - marker1: marker::CovariantType, - - // Snapshots are tokens that should be created/consumed linearly. - marker2: marker::NoCopy, - - // Length of the undo log at the time the snapshot was taken. - length: uint, -} - -#[deriving(PartialEq)] -enum UndoLog { - /// Indicates where a snapshot started. - OpenSnapshot, - - /// Indicates a snapshot that has been committed. - CommittedSnapshot, - - /// New variable with given index was created. - NewVar(uint), - - /// Variable with given index was changed *from* the given value. - SetVar(uint, VarValue), + // Link snapshot to the key type `K` of the table. + marker: marker::CovariantType, + snapshot: sv::Snapshot, } /** @@ -131,6 +106,8 @@ pub struct Node { pub rank: uint, } +pub struct Delegate; + // We can't use V:LatticeValue, much as I would like to, // because frequently the pattern is that V=Bounds for some // other type parameter U, and we have no way to say @@ -139,77 +116,26 @@ pub struct Node { impl> UnificationTable { pub fn new() -> UnificationTable { UnificationTable { - values: Vec::new(), - undo_log: Vec::new() + values: sv::SnapshotVec::new(Delegate), } } - pub fn in_snapshot(&self) -> bool { - /*! True if a snapshot has been started. */ - - self.undo_log.len() > 0 - } - /** * Starts a new snapshot. Each snapshot must be either * rolled back or committed in a "LIFO" (stack) order. */ pub fn snapshot(&mut self) -> Snapshot { - let length = self.undo_log.len(); - debug!("{}: snapshot at length {}", - UnifyKey::tag(None::), - length); - self.undo_log.push(OpenSnapshot); - Snapshot { length: length, - marker1: marker::CovariantType, - marker2: marker::NoCopy } - } - - fn assert_open_snapshot(&self, snapshot: &Snapshot) { - // Or else there was a failure to follow a stack discipline: - assert!(self.undo_log.len() > snapshot.length); - - // Invariant established by start_snapshot(): - assert!(*self.undo_log.get(snapshot.length) == OpenSnapshot); + Snapshot { marker: marker::CovariantType::, + snapshot: self.values.start_snapshot() } } /** * Reverses all changes since the last snapshot. Also * removes any keys that have been created since then. */ - pub fn rollback_to(&mut self, tcx: &ty::ctxt, snapshot: Snapshot) { - debug!("{}: rollback_to({})", - UnifyKey::tag(None::), - snapshot.length); - - self.assert_open_snapshot(&snapshot); - - while self.undo_log.len() > snapshot.length + 1 { - match self.undo_log.pop().unwrap() { - OpenSnapshot => { - // This indicates a failure to obey the stack discipline. - tcx.sess.bug("Cannot rollback an uncommitted snapshot"); - } - - CommittedSnapshot => { - // This occurs when there are nested snapshots and - // the inner is committed but outer is rolled back. - } - - NewVar(i) => { - assert!(self.values.len() == i); - self.values.pop(); - } - - SetVar(i, v) => { - *self.values.get_mut(i) = v; - } - } - } - - let v = self.undo_log.pop().unwrap(); - assert!(v == OpenSnapshot); - assert!(self.undo_log.len() == snapshot.length); + pub fn rollback_to(&mut self, snapshot: Snapshot) { + debug!("{}: rollback_to()", UnifyKey::tag(None::)); + self.values.rollback_to(snapshot.snapshot); } /** @@ -217,28 +143,12 @@ impl> UnificationTable { * can still be undone if there is a snapshot further out. */ pub fn commit(&mut self, snapshot: Snapshot) { - debug!("{}: commit({})", - UnifyKey::tag(None::), - snapshot.length); - - self.assert_open_snapshot(&snapshot); - - if snapshot.length == 0 { - // The root snapshot. - self.undo_log.truncate(0); - } else { - *self.undo_log.get_mut(snapshot.length) = CommittedSnapshot; - } + debug!("{}: commit()", UnifyKey::tag(None::)); + self.values.commit(snapshot.snapshot); } pub fn new_key(&mut self, value: V) -> K { - let index = self.values.len(); - - if self.in_snapshot() { - self.undo_log.push(NewVar(index)); - } - - self.values.push(Root(value, 0)); + let index = self.values.push(Root(value, 0)); let k = UnifyKey::from_index(index); debug!("{}: created new key: {}", UnifyKey::tag(None::), @@ -246,20 +156,6 @@ impl> UnificationTable { k } - fn swap_value(&mut self, - index: uint, - new_value: VarValue) - -> VarValue - { - /*! - * Primitive operation to swap a value in the var array. - * Caller should update the undo log if we are in a snapshot. - */ - - let loc = self.values.get_mut(index); - mem::replace(loc, new_value) - } - pub fn get(&mut self, tcx: &ty::ctxt, vid: K) -> Node { /*! * Find the root node for `vid`. This uses the standard @@ -274,15 +170,7 @@ impl> UnificationTable { let node: Node = self.get(tcx, redirect.clone()); if node.key != redirect { // Path compression - let old_value = - self.swap_value(index, Redirect(node.key.clone())); - - // If we are in a snapshot, record this compression, - // because it's possible that the unification which - // caused it will be rolled back later. - if self.in_snapshot() { - self.undo_log.push(SetVar(index, old_value)); - } + self.values.set(index, Redirect(node.key.clone())); } node } @@ -310,15 +198,12 @@ impl> UnificationTable { */ assert!(self.is_root(&key)); - assert!(self.in_snapshot()); debug!("Updating variable {} to {}", key.repr(tcx), new_value.repr(tcx)); - let index = key.index(); - let old_value = self.swap_value(index, new_value); - self.undo_log.push(SetVar(index, old_value)); + self.values.set(key.index(), new_value); } pub fn unify(&mut self, @@ -359,6 +244,12 @@ impl> UnificationTable { } } +impl sv::SnapshotVecDelegate,()> for Delegate { + fn reverse(&mut self, _: &mut Vec>, _: ()) { + fail!("Nothing to reverse"); + } +} + /////////////////////////////////////////////////////////////////////////// // Code to handle simple keys like ints, floats---anything that // doesn't have a subtyping relationship we need to worry about. @@ -373,7 +264,8 @@ pub trait SimplyUnifiable : Clone + PartialEq + Repr { pub fn err(a_is_expected: bool, a_t: V, - b_t: V) -> ures { + b_t: V) + -> ures { if a_is_expected { Err(SimplyUnifiable::to_type_err( ty::expected_found {expected: a_t, found: b_t})) diff --git a/src/librustc/util/snapshot_vec.rs b/src/librustc/util/snapshot_vec.rs new file mode 100644 index 00000000000..60e50c84c61 --- /dev/null +++ b/src/librustc/util/snapshot_vec.rs @@ -0,0 +1,195 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/*! + * A utility class for implementing "snapshottable" things; a + * snapshottable data structure permits you to take a snapshot (via + * `start_snapshot`) and then, after making some changes, elect either + * to rollback to the start of the snapshot or commit those changes. + * + * This vector is intended to be used as part of an abstraction, not + * serve as a complete abstraction on its own. As such, while it will + * roll back most changes on its own, it also supports a `get_mut` + * operation that gives you an abitrary mutable pointer into the + * vector. To ensure that any changes you make this with this pointer + * are rolled back, you must invoke `record` to record any changes you + * make and also supplying a delegate capable of reversing those + * changes. + */ + +use std::kinds::marker; +use std::mem; + +#[deriving(PartialEq)] +enum UndoLog { + /// Indicates where a snapshot started. + OpenSnapshot, + + /// Indicates a snapshot that has been committed. + CommittedSnapshot, + + /// New variable with given index was created. + NewElem(uint), + + /// Variable with given index was changed *from* the given value. + SetElem(uint, T), + + /// Extensible set of actions + Other(U) +} + +pub struct SnapshotVec { + values: Vec, + undo_log: Vec>, + delegate: D +} + +pub struct Snapshot { + // Snapshots are tokens that should be created/consumed linearly. + marker: marker::NoCopy, + + // Length of the undo log at the time the snapshot was taken. + length: uint, +} + +pub trait SnapshotVecDelegate { + fn reverse(&mut self, values: &mut Vec, action: U); +} + +impl> SnapshotVec { + pub fn new(delegate: D) -> SnapshotVec { + SnapshotVec { + values: Vec::new(), + undo_log: Vec::new(), + delegate: delegate + } + } + + fn in_snapshot(&self) -> bool { + !self.undo_log.is_empty() + } + + pub fn record(&mut self, action: U) { + if self.in_snapshot() { + self.undo_log.push(Other(action)); + } + } + + pub fn push(&mut self, elem: T) -> uint { + let len = self.values.len(); + self.values.push(elem); + + if self.in_snapshot() { + self.undo_log.push(NewElem(len)); + } + + len + } + + pub fn get<'a>(&'a self, index: uint) -> &'a T { + self.values.get(index) + } + + pub fn get_mut<'a>(&'a mut self, index: uint) -> &'a mut T { + /*! + * Returns a mutable pointer into the vec; whatever changes + * you make here cannot be undone automatically, so you should + * be sure call `record()` with some sort of suitable undo + * action. + */ + + self.values.get_mut(index) + } + + pub fn set(&mut self, index: uint, new_elem: T) { + /*! + * Updates the element at the given index. The old value will + * saved (and perhaps restored) if a snapshot is active. + */ + + let old_elem = mem::replace(self.values.get_mut(index), new_elem); + if self.in_snapshot() { + self.undo_log.push(SetElem(index, old_elem)); + } + } + + pub fn start_snapshot(&mut self) -> Snapshot { + let length = self.undo_log.len(); + self.undo_log.push(OpenSnapshot); + Snapshot { length: length, + marker: marker::NoCopy } + } + + fn assert_open_snapshot(&self, snapshot: &Snapshot) { + // Or else there was a failure to follow a stack discipline: + assert!(self.undo_log.len() > snapshot.length); + + // Invariant established by start_snapshot(): + assert!( + match *self.undo_log.get(snapshot.length) { + OpenSnapshot => true, + _ => false + }); + } + + pub fn rollback_to(&mut self, snapshot: Snapshot) { + debug!("rollback_to({})", snapshot.length); + + self.assert_open_snapshot(&snapshot); + + while self.undo_log.len() > snapshot.length + 1 { + match self.undo_log.pop().unwrap() { + OpenSnapshot => { + // This indicates a failure to obey the stack discipline. + fail!("Cannot rollback an uncommited snapshot"); + } + + CommittedSnapshot => { + // This occurs when there are nested snapshots and + // the inner is commited but outer is rolled back. + } + + NewElem(i) => { + self.values.pop(); + assert!(self.values.len() == i); + } + + SetElem(i, v) => { + *self.values.get_mut(i) = v; + } + + Other(u) => { + self.delegate.reverse(&mut self.values, u); + } + } + } + + let v = self.undo_log.pop().unwrap(); + assert!(match v { OpenSnapshot => true, _ => false }); + assert!(self.undo_log.len() == snapshot.length); + } + + /** + * Commits all changes since the last snapshot. Of course, they + * can still be undone if there is a snapshot further out. + */ + pub fn commit(&mut self, snapshot: Snapshot) { + debug!("commit({})", snapshot.length); + + self.assert_open_snapshot(&snapshot); + + if snapshot.length == 0 { + // The root snapshot. + self.undo_log.truncate(0); + } else { + *self.undo_log.get_mut(snapshot.length) = CommittedSnapshot; + } + } +}