diff --git a/src/lib.rs b/src/lib.rs index 233ecf9..aed3524 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,4 +20,5 @@ extern crate log; extern crate dogged; pub mod snapshot_vec; +pub mod undo_log; pub mod unify; diff --git a/src/snapshot_vec.rs b/src/snapshot_vec.rs index 98088b5..efde49a 100644 --- a/src/snapshot_vec.rs +++ b/src/snapshot_vec.rs @@ -22,9 +22,12 @@ use self::UndoLog::*; use std::fmt; +use std::marker::PhantomData; use std::mem; use std::ops; +use undo_log::{Rollback, Snapshots, UndoLogs, VecLog}; + #[derive(Debug)] pub enum UndoLog { /// New variable with given index was created. @@ -37,33 +40,101 @@ pub enum UndoLog { Other(D::Undo), } -pub struct SnapshotVec { - values: Vec, - undo_log: Vec>, - num_open_snapshots: usize, +impl Rollback> for SnapshotVecStorage { + fn reverse(&mut self, undo: UndoLog) { + self.values.reverse(undo) + } +} +impl Rollback> for Vec { + fn reverse(&mut self, undo: UndoLog) { + match undo { + NewElem(i) => { + self.pop(); + assert!(Vec::len(self) == i); + } + + SetElem(i, v) => { + self[i] = v; + } + + Other(u) => { + D::reverse(self, u); + } + } + } +} + +pub trait VecLike: AsRef<[D::Value]> + AsMut<[D::Value]> + Rollback> +where + D: SnapshotVecDelegate, +{ + fn push(&mut self, item: D::Value); + fn len(&self) -> usize; + fn reserve(&mut self, size: usize); +} + +impl VecLike for Vec +where + D: SnapshotVecDelegate, +{ + fn push(&mut self, item: D::Value) { + Vec::push(self, item) + } + fn len(&self) -> usize { + Vec::len(self) + } + fn reserve(&mut self, size: usize) { + Vec::reserve(self, size) + } +} + +impl VecLike for &'_ mut Vec +where + D: SnapshotVecDelegate, +{ + fn push(&mut self, item: D::Value) { + Vec::push(self, item) + } + fn len(&self) -> usize { + Vec::len(self) + } + fn reserve(&mut self, size: usize) { + Vec::reserve(self, size) + } +} + +#[allow(type_alias_bounds)] +pub type SnapshotVecStorage = + SnapshotVec::Value>, ()>; + +pub struct SnapshotVec< + D: SnapshotVecDelegate, + V: VecLike = Vec<::Value>, + L = VecLog>, +> { + values: V, + undo_log: L, + _marker: PhantomData, } -impl fmt::Debug for SnapshotVec - where D: SnapshotVecDelegate, - D: fmt::Debug, - D::Undo: fmt::Debug, - D::Value: fmt::Debug +impl fmt::Debug for SnapshotVec +where + D: SnapshotVecDelegate, + V: VecLike + fmt::Debug, + L: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("SnapshotVec") .field("values", &self.values) .field("undo_log", &self.undo_log) - .field("num_open_snapshots", &self.num_open_snapshots) .finish() } } // Snapshots are tokens that should be created/consumed linearly. -pub struct Snapshot { - // Number of values at the time the snapshot was taken. +pub struct Snapshot { pub(crate) value_count: usize, - // Length of the undo log at the time the snapshot was taken. - undo_len: usize, + snapshot: S, } pub trait SnapshotVecDelegate { @@ -74,31 +145,77 @@ pub trait SnapshotVecDelegate { } // HACK(eddyb) manual impl avoids `Default` bound on `D`. -impl Default for SnapshotVec { +impl + Default, L: Default> Default for SnapshotVec { fn default() -> Self { SnapshotVec { - values: Vec::new(), - undo_log: Vec::new(), - num_open_snapshots: 0, + values: V::default(), + undo_log: Default::default(), + _marker: PhantomData, } } } -impl SnapshotVec { +impl + Default, L: Default> SnapshotVec { + /// Creates a new `SnapshotVec`. If `L` is set to `()` then most mutating functions will not + /// be accessible without calling `with_log` and supplying a compatibly `UndoLogs` instance. pub fn new() -> Self { Self::default() } +} - pub fn with_capacity(c: usize) -> SnapshotVec { +impl SnapshotVecStorage { + /// Creates a `SnapshotVec` using the `undo_log`, allowing mutating methods to be called + pub fn with_log<'a, L>( + &'a mut self, + undo_log: L, + ) -> SnapshotVec::Value>, L> + where + L: UndoLogs>, + { + SnapshotVec { + values: &mut self.values, + undo_log, + _marker: PhantomData, + } + } +} + +impl SnapshotVec, L> { + pub fn with_capacity(c: usize) -> Self { SnapshotVec { values: Vec::with_capacity(c), - undo_log: Vec::new(), - num_open_snapshots: 0, + undo_log: Default::default(), + _marker: PhantomData, } } +} + +impl, D: SnapshotVecDelegate, U> SnapshotVec { + pub fn len(&self) -> usize { + self.values.len() + } + + pub fn get(&self, index: usize) -> &D::Value { + &self.values.as_ref()[index] + } + + /// 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. + pub fn get_mut(&mut self, index: usize) -> &mut D::Value { + &mut self.values.as_mut()[index] + } + /// Reserve space for new values, just like an ordinary vec. + pub fn reserve(&mut self, additional: usize) { + // This is not affected by snapshots or anything. + self.values.reserve(additional); + } +} + +impl, D: SnapshotVecDelegate, L: UndoLogs>> SnapshotVec { fn in_snapshot(&self) -> bool { - self.num_open_snapshots > 0 + self.undo_log.in_snapshot() } pub fn record(&mut self, action: D::Undo) { @@ -107,10 +224,6 @@ impl SnapshotVec { } } - pub fn len(&self) -> usize { - self.values.len() - } - pub fn push(&mut self, elem: D::Value) -> usize { let len = self.values.len(); self.values.push(elem); @@ -122,28 +235,11 @@ impl SnapshotVec { len } - pub fn get(&self, index: usize) -> &D::Value { - &self.values[index] - } - - /// Reserve space for new values, just like an ordinary vec. - pub fn reserve(&mut self, additional: usize) { - // This is not affected by snapshots or anything. - self.values.reserve(additional); - } - - /// 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. - pub fn get_mut(&mut self, index: usize) -> &mut D::Value { - &mut self.values[index] - } - /// Updates the element at the given index. The old value will saved (and perhaps restored) if /// a snapshot is active. pub fn set(&mut self, index: usize, new_elem: D::Value) { - let old_elem = mem::replace(&mut self.values[index], new_elem); - if self.in_snapshot() { + let old_elem = mem::replace(&mut self.values.as_mut()[index], new_elem); + if self.undo_log.in_snapshot() { self.undo_log.push(SetElem(index, old_elem)); } } @@ -151,8 +247,8 @@ impl SnapshotVec { /// Updates all elements. Potentially more efficient -- but /// otherwise equivalent to -- invoking `set` for each element. pub fn set_all(&mut self, mut new_elems: impl FnMut(usize) -> D::Value) { - if !self.in_snapshot() { - for (index, slot) in self.values.iter_mut().enumerate() { + if !self.undo_log.in_snapshot() { + for (index, slot) in self.values.as_mut().iter_mut().enumerate() { *slot = new_elems(index); } } else { @@ -167,102 +263,72 @@ impl SnapshotVec { OP: FnOnce(&mut D::Value), D::Value: Clone, { - if self.in_snapshot() { - let old_elem = self.values[index].clone(); + if self.undo_log.in_snapshot() { + let old_elem = self.values.as_mut()[index].clone(); self.undo_log.push(SetElem(index, old_elem)); } - op(&mut self.values[index]); + op(&mut self.values.as_mut()[index]); } +} - pub fn start_snapshot(&mut self) -> Snapshot { - self.num_open_snapshots += 1; +impl SnapshotVec +where + D: SnapshotVecDelegate, + V: VecLike + Rollback>, + L: Snapshots>, +{ + pub fn start_snapshot(&mut self) -> Snapshot { Snapshot { value_count: self.values.len(), - undo_len: self.undo_log.len(), + snapshot: self.undo_log.start_snapshot(), } } - pub fn actions_since_snapshot(&self, snapshot: &Snapshot) -> &[UndoLog] { - &self.undo_log[snapshot.undo_len..] + pub fn actions_since_snapshot(&self, snapshot: &Snapshot) -> &[UndoLog] { + self.undo_log.actions_since_snapshot(&snapshot.snapshot) } - fn assert_open_snapshot(&self, snapshot: &Snapshot) { - // Failures here may indicate a failure to follow a stack discipline. - assert!(self.undo_log.len() >= snapshot.undo_len); - assert!(self.num_open_snapshots > 0); - } - - pub fn rollback_to(&mut self, snapshot: Snapshot) { - debug!("rollback_to({})", snapshot.undo_len); - - self.assert_open_snapshot(&snapshot); - - while self.undo_log.len() > snapshot.undo_len { - match self.undo_log.pop().unwrap() { - NewElem(i) => { - self.values.pop(); - assert!(self.values.len() == i); - } - - SetElem(i, v) => { - self.values[i] = v; - } - - Other(u) => { - D::reverse(&mut self.values, u); - } - } - } - - self.num_open_snapshots -= 1; + pub fn rollback_to(&mut self, snapshot: Snapshot) { + let values = &mut self.values; + self.undo_log.rollback_to(|| values, snapshot.snapshot); } /// 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.undo_len); - - self.assert_open_snapshot(&snapshot); - - if self.num_open_snapshots == 1 { - // The root snapshot. It's safe to clear the undo log because - // there's no snapshot further out that we might need to roll back - // to. - assert!(snapshot.undo_len == 0); - self.undo_log.clear(); - } - - self.num_open_snapshots -= 1; + pub fn commit(&mut self, snapshot: Snapshot) { + self.undo_log.commit(snapshot.snapshot); } } -impl ops::Deref for SnapshotVec { +impl, L> ops::Deref for SnapshotVec { type Target = [D::Value]; fn deref(&self) -> &[D::Value] { - &*self.values + self.values.as_ref() } } -impl ops::DerefMut for SnapshotVec { +impl, L> ops::DerefMut for SnapshotVec { fn deref_mut(&mut self) -> &mut [D::Value] { - &mut *self.values + self.values.as_mut() } } -impl ops::Index for SnapshotVec { +impl, L> ops::Index for SnapshotVec { type Output = D::Value; fn index(&self, index: usize) -> &D::Value { self.get(index) } } -impl ops::IndexMut for SnapshotVec { +impl, L> ops::IndexMut for SnapshotVec { fn index_mut(&mut self, index: usize) -> &mut D::Value { self.get_mut(index) } } -impl Extend for SnapshotVec { +impl + Extend> Extend + for SnapshotVec +{ fn extend(&mut self, iterable: T) where T: IntoIterator, @@ -272,21 +338,22 @@ impl Extend for SnapshotVec { let final_len = self.values.len(); if self.in_snapshot() { - self.undo_log.extend((initial_len..final_len).map(|len| NewElem(len))); + self.undo_log + .extend((initial_len..final_len).map(|len| NewElem(len))); } } } -impl Clone for SnapshotVec +impl Clone for SnapshotVec where - D::Value: Clone, - D::Undo: Clone, + V: VecLike + Clone, + L: Clone, { fn clone(&self) -> Self { SnapshotVec { values: self.values.clone(), undo_log: self.undo_log.clone(), - num_open_snapshots: self.num_open_snapshots, + _marker: PhantomData, } } } diff --git a/src/undo_log.rs b/src/undo_log.rs new file mode 100644 index 0000000..93c3803 --- /dev/null +++ b/src/undo_log.rs @@ -0,0 +1,248 @@ +//! Module which contains the snapshot/rollback functionality of the `ena` data structures. +//! +//! For most usecases this is just an internal implementation detail. However if many `ena` +//! data structures are used snapshotted simultaneously it is possible to use +//! `UnificationTableStorage`/`SnapshotVecStorage` instead and use a custom `UndoLogs` +//! type capable of recording the actions of all used data structures. +//! +//! Since the `*Storage` variants do not have an undo log `with_log` must be called with the +//! unified log before any mutating actions. + +/// A trait which allows undo actions (`T`) to be pushed which can be used to rollback actio at a +/// later time if needed. +/// +/// The undo actions themselves are opaque to `UndoLogs`, only specified `Rollback` implementations +/// need to know what an action is and how to reverse it. +pub trait UndoLogs { + /// True if a snapshot has started, false otherwise + fn in_snapshot(&self) -> bool { + self.num_open_snapshots() > 0 + } + + /// How many open snapshots this undo log currently has + fn num_open_snapshots(&self) -> usize; + + /// Pushes a new "undo item" onto the undo log. This method is invoked when some action is taken + /// (e.g., a variable is unified). It records the info needed to reverse that action should an + /// enclosing snapshot be rolleod back. + fn push(&mut self, undo: T); + + /// Removes all items from the undo log. + fn clear(&mut self); + + /// Extends the undo log with many undos. + fn extend(&mut self, undos: I) + where + Self: Sized, + I: IntoIterator, + { + undos.into_iter().for_each(|undo| self.push(undo)); + } +} + +impl<'a, T, U> UndoLogs for &'a mut U +where + U: UndoLogs, +{ + fn in_snapshot(&self) -> bool { + U::in_snapshot(self) + } + fn num_open_snapshots(&self) -> usize { + U::num_open_snapshots(self) + } + fn push(&mut self, undo: T) { + U::push(self, undo) + } + fn clear(&mut self) { + U::clear(self); + } + fn extend(&mut self, undos: I) + where + Self: Sized, + I: IntoIterator, + { + U::extend(self, undos) + } +} + +/// A trait which extends `UndoLogs` to allow snapshots to be done at specific points. Each snapshot can then be used to +/// rollback any changes to an underlying data structures if they were not desirable. +/// +/// Each snapshot must be consumed linearly with either `rollback_to` or `commit`. +pub trait Snapshots: UndoLogs { + type Snapshot; + + /// Returns true if `self` has made any changes since snapshot started. + fn has_changes(&self, snapshot: &Self::Snapshot) -> bool { + !self.actions_since_snapshot(snapshot).is_empty() + } + + /// Returns the slice of actions that were taken since the snapshot began. + fn actions_since_snapshot(&self, snapshot: &Self::Snapshot) -> &[T]; + + /// Starts a new snapshot. That snapshot must eventually either be committed via a call to + /// commit or rollback via rollback_to. Snapshots can be nested (i.e., you can start a snapshot + /// whilst another snapshot is in progress) but you must then commit or rollback the inner + /// snapshot before attempting to commit or rollback the outer snapshot. + fn start_snapshot(&mut self) -> Self::Snapshot; + + /// Rollback (undo) the changes made to `storage` since the snapshot. + fn rollback_to(&mut self, storage: impl FnOnce() -> R, snapshot: Self::Snapshot) + where + R: Rollback; + + /// Commit: keep the changes that have been made since the snapshot began + fn commit(&mut self, snapshot: Self::Snapshot); +} + +impl Snapshots for &'_ mut U +where + U: Snapshots, +{ + type Snapshot = U::Snapshot; + fn has_changes(&self, snapshot: &Self::Snapshot) -> bool { + U::has_changes(self, snapshot) + } + fn actions_since_snapshot(&self, snapshot: &Self::Snapshot) -> &[T] { + U::actions_since_snapshot(self, snapshot) + } + + fn start_snapshot(&mut self) -> Self::Snapshot { + U::start_snapshot(self) + } + fn rollback_to(&mut self, storage: impl FnOnce() -> R, snapshot: Self::Snapshot) + where + R: Rollback, + { + U::rollback_to(self, storage, snapshot) + } + + fn commit(&mut self, snapshot: Self::Snapshot) { + U::commit(self, snapshot) + } +} + +pub struct NoUndo; +impl UndoLogs for NoUndo { + fn num_open_snapshots(&self) -> usize { + 0 + } + fn push(&mut self, _undo: T) {} + fn clear(&mut self) {} +} + +/// A basic undo log. +#[derive(Clone, Debug)] +pub struct VecLog { + log: Vec, + num_open_snapshots: usize, +} + +impl Default for VecLog { + fn default() -> Self { + VecLog { + log: Vec::new(), + num_open_snapshots: 0, + } + } +} + +impl UndoLogs for VecLog { + fn num_open_snapshots(&self) -> usize { + self.num_open_snapshots + } + fn push(&mut self, undo: T) { + self.log.push(undo); + } + fn clear(&mut self) { + self.log.clear(); + self.num_open_snapshots = 0; + } +} + +impl Snapshots for VecLog { + type Snapshot = Snapshot; + + fn has_changes(&self, snapshot: &Self::Snapshot) -> bool { + self.log.len() > snapshot.undo_len + } + fn actions_since_snapshot(&self, snapshot: &Snapshot) -> &[T] { + &self.log[snapshot.undo_len..] + } + + fn start_snapshot(&mut self) -> Snapshot { + self.num_open_snapshots += 1; + Snapshot { + undo_len: self.log.len(), + } + } + + fn rollback_to(&mut self, values: impl FnOnce() -> R, snapshot: Snapshot) + where + R: Rollback, + { + debug!("rollback_to({})", snapshot.undo_len); + + self.assert_open_snapshot(&snapshot); + + if self.log.len() > snapshot.undo_len { + let mut values = values(); + while self.log.len() > snapshot.undo_len { + values.reverse(self.log.pop().unwrap()); + } + } + + self.num_open_snapshots -= 1; + } + + fn commit(&mut self, snapshot: Snapshot) { + debug!("commit({})", snapshot.undo_len); + + self.assert_open_snapshot(&snapshot); + + if self.num_open_snapshots == 1 { + // The root snapshot. It's safe to clear the undo log because + // there's no snapshot further out that we might need to roll back + // to. + assert!(snapshot.undo_len == 0); + self.log.clear(); + } + + self.num_open_snapshots -= 1; + } +} + +impl VecLog { + fn assert_open_snapshot(&self, snapshot: &Snapshot) { + // Failures here may indicate a failure to follow a stack discipline. + assert!(self.log.len() >= snapshot.undo_len); + assert!(self.num_open_snapshots > 0); + } +} + +impl std::ops::Index for VecLog { + type Output = T; + fn index(&self, key: usize) -> &T { + &self.log[key] + } +} + +/// A trait implemented for storage types (like `SnapshotVecStorage`) which can be rolled back using actions of type `U`. +pub trait Rollback { + fn reverse(&mut self, undo: U); +} + +impl Rollback for &'_ mut T +where + T: Rollback, +{ + fn reverse(&mut self, undo: U) { + T::reverse(self, undo) + } +} + +/// Snapshots are tokens that should be created/consumed linearly. +pub struct Snapshot { + // Length of the undo log at the time the snapshot was taken. + undo_len: usize, +} diff --git a/src/unify/backing_vec.rs b/src/unify/backing_vec.rs index ea4cca5..7c1eb2c 100644 --- a/src/unify/backing_vec.rs +++ b/src/unify/backing_vec.rs @@ -1,123 +1,151 @@ #[cfg(feature = "persistent")] use dogged::DVec; use snapshot_vec as sv; -use std::ops::{self, Range}; use std::marker::PhantomData; +use std::ops::{self, Range}; + +use undo_log::{Rollback, Snapshots, UndoLogs, VecLog}; -use super::{VarValue, UnifyKey, UnifyValue}; +use super::{UnifyKey, UnifyValue, VarValue}; #[allow(dead_code)] // rustc BUG #[allow(type_alias_bounds)] -type Key = ::Key; +type Key = ::Key; /// Largely internal trait implemented by the unification table /// backing store types. The most common such type is `InPlace`, /// which indicates a standard, mutable unification table. -pub trait UnificationStore: - ops::Index>> + Clone + Default -{ +pub trait UnificationStoreBase: ops::Index>> { type Key: UnifyKey; type Value: UnifyValue; - type Snapshot; - - fn start_snapshot(&mut self) -> Self::Snapshot; - - fn rollback_to(&mut self, snapshot: Self::Snapshot); - fn commit(&mut self, snapshot: Self::Snapshot); + fn len(&self) -> usize; - fn values_since_snapshot(&self, snapshot: &Self::Snapshot) -> Range; + fn tag() -> &'static str { + Self::Key::tag() + } +} - fn reset_unifications( - &mut self, - value: impl FnMut(u32) -> VarValue, - ); - - fn len(&self) -> usize; +pub trait UnificationStoreMut: UnificationStoreBase { + fn reset_unifications(&mut self, value: impl FnMut(u32) -> VarValue); fn push(&mut self, value: VarValue); fn reserve(&mut self, num_new_values: usize); fn update(&mut self, index: usize, op: F) - where F: FnOnce(&mut VarValue); + where + F: FnOnce(&mut VarValue); +} - fn tag() -> &'static str { - Self::Key::tag() - } +pub trait UnificationStore: UnificationStoreMut { + type Snapshot; + + fn start_snapshot(&mut self) -> Self::Snapshot; + + fn rollback_to(&mut self, snapshot: Self::Snapshot); + + fn commit(&mut self, snapshot: Self::Snapshot); + + fn values_since_snapshot(&self, snapshot: &Self::Snapshot) -> Range; } /// Backing store for an in-place unification table. /// Not typically used directly. #[derive(Clone, Debug)] -pub struct InPlace { - values: sv::SnapshotVec> +pub struct InPlace< + K: UnifyKey, + V: sv::VecLike> = Vec>, + L = VecLog>>, +> { + pub(crate) values: sv::SnapshotVec, V, L>, } // HACK(eddyb) manual impl avoids `Default` bound on `K`. -impl Default for InPlace { +impl> + Default, L: Default> Default for InPlace { fn default() -> Self { - InPlace { values: sv::SnapshotVec::new() } + InPlace { + values: sv::SnapshotVec::new(), + } } } -impl UnificationStore for InPlace { +impl UnificationStoreBase for InPlace +where + K: UnifyKey, + V: sv::VecLike>, +{ type Key = K; type Value = K::Value; - type Snapshot = sv::Snapshot; - #[inline] - fn start_snapshot(&mut self) -> Self::Snapshot { - self.values.start_snapshot() + fn len(&self) -> usize { + self.values.len() } +} +impl UnificationStoreMut for InPlace +where + K: UnifyKey, + V: sv::VecLike>, + L: UndoLogs>>, +{ #[inline] - fn rollback_to(&mut self, snapshot: Self::Snapshot) { - self.values.rollback_to(snapshot); + fn reset_unifications(&mut self, mut value: impl FnMut(u32) -> VarValue) { + self.values.set_all(|i| value(i as u32)); } #[inline] - fn commit(&mut self, snapshot: Self::Snapshot) { - self.values.commit(snapshot); + fn push(&mut self, value: VarValue) { + self.values.push(value); } #[inline] - fn values_since_snapshot(&self, snapshot: &Self::Snapshot) -> Range { - snapshot.value_count..self.len() + fn reserve(&mut self, num_new_values: usize) { + self.values.reserve(num_new_values); } #[inline] - fn reset_unifications( - &mut self, - mut value: impl FnMut(u32) -> VarValue, - ) { - self.values.set_all(|i| value(i as u32)); + fn update(&mut self, index: usize, op: F) + where + F: FnOnce(&mut VarValue), + { + self.values.update(index, op) } +} - fn len(&self) -> usize { - self.values.len() +impl UnificationStore for InPlace +where + K: UnifyKey, + V: sv::VecLike>, + L: Snapshots>>, +{ + type Snapshot = sv::Snapshot; + + #[inline] + fn start_snapshot(&mut self) -> Self::Snapshot { + self.values.start_snapshot() } #[inline] - fn push(&mut self, value: VarValue) { - self.values.push(value); + fn rollback_to(&mut self, snapshot: Self::Snapshot) { + self.values.rollback_to(snapshot); } #[inline] - fn reserve(&mut self, num_new_values: usize) { - self.values.reserve(num_new_values); + fn commit(&mut self, snapshot: Self::Snapshot) { + self.values.commit(snapshot); } #[inline] - fn update(&mut self, index: usize, op: F) - where F: FnOnce(&mut VarValue) - { - self.values.update(index, op) + fn values_since_snapshot(&self, snapshot: &Self::Snapshot) -> Range { + snapshot.value_count..self.len() } } -impl ops::Index for InPlace - where K: UnifyKey +impl ops::Index for InPlace +where + V: sv::VecLike>, + K: UnifyKey, { type Output = VarValue; fn index(&self, index: usize) -> &VarValue { @@ -125,8 +153,9 @@ impl ops::Index for InPlace } } +#[doc(hidden)] #[derive(Copy, Clone, Debug)] -struct Delegate(PhantomData); +pub struct Delegate(PhantomData); impl sv::SnapshotVecDelegate for Delegate { type Value = VarValue; @@ -135,61 +164,50 @@ impl sv::SnapshotVecDelegate for Delegate { fn reverse(_: &mut Vec>, _: ()) {} } +impl Rollback>> for super::UnificationTableStorage { + fn reverse(&mut self, undo: sv::UndoLog>) { + self.values.values.reverse(undo); + } +} + #[cfg(feature = "persistent")] #[derive(Clone, Debug)] pub struct Persistent { - values: DVec> + values: DVec>, } // HACK(eddyb) manual impl avoids `Default` bound on `K`. #[cfg(feature = "persistent")] impl Default for Persistent { fn default() -> Self { - Persistent { values: DVec::new() } + Persistent { + values: DVec::new(), + } } } #[cfg(feature = "persistent")] -impl UnificationStore for Persistent { +impl UnificationStoreBase for Persistent { type Key = K; type Value = K::Value; - type Snapshot = Self; - - #[inline] - fn start_snapshot(&mut self) -> Self::Snapshot { - self.clone() - } - - #[inline] - fn rollback_to(&mut self, snapshot: Self::Snapshot) { - *self = snapshot; - } - - #[inline] - fn commit(&mut self, _snapshot: Self::Snapshot) {} - #[inline] - fn values_since_snapshot(&self, snapshot: &Self::Snapshot) -> Range { - snapshot.len()..self.len() + fn len(&self) -> usize { + self.values.len() } +} +#[cfg(feature = "persistent")] +impl UnificationStoreMut for Persistent { #[inline] - fn reset_unifications( - &mut self, - mut value: impl FnMut(u32) -> VarValue, - ) { + fn reset_unifications(&mut self, mut value: impl FnMut(u32) -> VarValue) { // Without extending dogged, there isn't obviously a more // efficient way to do this. But it's pretty dumb. Maybe // dogged needs a `map`. - for i in 0 .. self.values.len() { + for i in 0..self.values.len() { self.values[i] = value(i as u32); } } - fn len(&self) -> usize { - self.values.len() - } - #[inline] fn push(&mut self, value: VarValue) { self.values.push(value); @@ -202,16 +220,41 @@ impl UnificationStore for Persistent { #[inline] fn update(&mut self, index: usize, op: F) - where F: FnOnce(&mut VarValue) + where + F: FnOnce(&mut VarValue), { let p = &mut self.values[index]; op(p); } } +#[cfg(feature = "persistent")] +impl UnificationStore for Persistent { + type Snapshot = Self; + + #[inline] + fn start_snapshot(&mut self) -> Self::Snapshot { + self.clone() + } + + #[inline] + fn rollback_to(&mut self, snapshot: Self::Snapshot) { + *self = snapshot; + } + + #[inline] + fn commit(&mut self, _snapshot: Self::Snapshot) {} + + #[inline] + fn values_since_snapshot(&self, snapshot: &Self::Snapshot) -> Range { + snapshot.len()..self.len() + } +} + #[cfg(feature = "persistent")] impl ops::Index for Persistent - where K: UnifyKey +where + K: UnifyKey, { type Output = VarValue; fn index(&self, index: usize) -> &VarValue { diff --git a/src/unify/mod.rs b/src/unify/mod.rs index 08f9b28..a26d699 100644 --- a/src/unify/mod.rs +++ b/src/unify/mod.rs @@ -31,17 +31,21 @@ //! The best way to see how it is used is to read the `tests.rs` file; //! search for e.g. `UnitKey`. -use std::marker; use std::fmt::Debug; +use std::marker; use std::ops::Range; +use snapshot_vec::{self as sv, UndoLog}; +use undo_log::{UndoLogs, VecLog}; + mod backing_vec; -pub use self::backing_vec::{InPlace, UnificationStore}; +pub use self::backing_vec::{ + Delegate, InPlace, UnificationStore, UnificationStoreBase, UnificationStoreMut, +}; #[cfg(feature = "persistent")] pub use self::backing_vec::Persistent; - #[cfg(test)] mod tests; @@ -156,10 +160,10 @@ pub struct NoError { /// time of the algorithm under control. For more information, see /// . #[derive(PartialEq, Clone, Debug)] -pub struct VarValue { // FIXME pub - parent: K, // if equal to self, this is a root +pub struct VarValue { + parent: K, // if equal to self, this is a root value: K::Value, // value assigned (only relevant to root) - rank: u32, // max depth (only relevant to root) + rank: u32, // max depth (only relevant to root) } /// Table of unification keys and their values. You must define a key type K @@ -176,14 +180,21 @@ pub struct VarValue { // FIXME pub /// - This implies that ordinary operations are quite a bit slower though. /// - Requires the `persistent` feature be selected in your Cargo.toml file. #[derive(Clone, Debug, Default)] -pub struct UnificationTable { +pub struct UnificationTable { /// Indicates the current value of each key. values: S, } +pub type UnificationStorage = Vec>; +pub type UnificationTableStorage = UnificationTable, ()>>; + /// A unification table that uses an "in-place" vector. #[allow(type_alias_bounds)] -pub type InPlaceUnificationTable = UnificationTable>; +pub type InPlaceUnificationTable< + K: UnifyKey, + V: sv::VecLike> = Vec>, + L = VecLog>>, +> = UnificationTable>; /// A unification table that uses a "persistent" vector. #[cfg(feature = "persistent")] @@ -232,17 +243,39 @@ impl VarValue { } } } +impl UnificationTableStorage +where + K: UnifyKey, +{ + /// Creates a `UnificationTable` using an external `undo_log`, allowing mutating methods to be + /// called if `L` does not implement `UndoLogs` + pub fn with_log<'a, L>( + &'a mut self, + undo_log: L, + ) -> UnificationTable, L>> + where + L: UndoLogs>>, + { + UnificationTable { + values: InPlace { + values: self.values.values.with_log(undo_log), + }, + } + } +} // We can't use V:LatticeValue, much as I would like to, // because frequently the pattern is that V=Option for some // other type parameter U, and we have no way to say // Option:LatticeValue. -impl UnificationTable { +impl UnificationTable { pub fn new() -> Self { Self::default() } +} +impl UnificationTable { /// Starts a new snapshot. Each snapshot must be either /// rolled back or committed in a "LIFO" (stack) order. pub fn snapshot(&mut self) -> Snapshot { @@ -266,6 +299,22 @@ impl UnificationTable { self.values.commit(snapshot.snapshot); } + /// Returns the keys of all variables created since the `snapshot`. + pub fn vars_since_snapshot(&self, snapshot: &Snapshot) -> Range { + let range = self.values.values_since_snapshot(&snapshot.snapshot); + S::Key::from_index(range.start as u32)..S::Key::from_index(range.end as u32) + } +} + +impl UnificationTable { + /// Returns the number of keys created so far. + pub fn len(&self) -> usize { + self.values.len() + } +} + +impl UnificationTable { + /// Starts a new snapshot. Each snapshot must be either /// Creates a fresh key with the given value. pub fn new_key(&mut self, value: S::Value) -> S::Key { let len = self.values.len(); @@ -284,10 +333,7 @@ impl UnificationTable { /// Clears all unifications that have been performed, resetting to /// the initial state. The values of each variable are given by /// the closure. - pub fn reset_unifications( - &mut self, - mut value: impl FnMut(S::Key) -> S::Value, - ) { + pub fn reset_unifications(&mut self, mut value: impl FnMut(S::Key) -> S::Value) { self.values.reset_unifications(|i| { let key = UnifyKey::from_index(i as u32); let value = value(key); @@ -295,20 +341,6 @@ impl UnificationTable { }); } - /// Returns the number of keys created so far. - pub fn len(&self) -> usize { - self.values.len() - } - - /// Returns the keys of all variables created since the `snapshot`. - pub fn vars_since_snapshot( - &self, - snapshot: &Snapshot, - ) -> Range { - let range = self.values.values_since_snapshot(&snapshot.snapshot); - S::Key::from_index(range.start as u32)..S::Key::from_index(range.end as u32) - } - /// Obtains the current value for a particular key. /// Not for end-users; they can use `probe_value`. fn value(&self, key: S::Key) -> &VarValue { @@ -370,13 +402,12 @@ impl UnificationTable { let rank_a = self.value(key_a).rank; let rank_b = self.value(key_b).rank; - if let Some((new_root, redirected)) = - S::Key::order_roots( - key_a, - &self.value(key_a).value, - key_b, - &self.value(key_b).value, - ) { + if let Some((new_root, redirected)) = S::Key::order_roots( + key_a, + &self.value(key_a).value, + key_b, + &self.value(key_b).value, + ) { // compute the new rank for the new root that they chose; // this may not be the optimal choice. let new_rank = if new_root == key_a { @@ -435,7 +466,7 @@ impl UnificationTable { impl UnificationTable where - S: UnificationStore, + S: UnificationStoreMut, K: UnifyKey, V: UnifyValue, { @@ -537,7 +568,6 @@ where } } - /////////////////////////////////////////////////////////////////////////// impl UnifyValue for () { @@ -554,14 +584,11 @@ impl UnifyValue for Option { fn unify_values(a: &Option, b: &Option) -> Result { match (a, b) { (&None, &None) => Ok(None), - (&Some(ref v), &None) | - (&None, &Some(ref v)) => Ok(Some(v.clone())), - (&Some(ref a), &Some(ref b)) => { - match V::unify_values(a, b) { - Ok(v) => Ok(Some(v)), - Err(err) => Err(err), - } - } + (&Some(ref v), &None) | (&None, &Some(ref v)) => Ok(Some(v.clone())), + (&Some(ref a), &Some(ref b)) => match V::unify_values(a, b) { + Ok(v) => Ok(Some(v)), + Err(err) => Err(err), + }, } } } diff --git a/src/unify/tests.rs b/src/unify/tests.rs index 481fd44..5665aba 100644 --- a/src/unify/tests.rs +++ b/src/unify/tests.rs @@ -17,10 +17,10 @@ extern crate test; #[cfg(feature = "bench")] use self::test::Bencher; use std::cmp; -use unify::{NoError, InPlace, InPlaceUnificationTable, UnifyKey, EqUnifyValue, UnifyValue}; -use unify::{UnificationStore, UnificationTable}; #[cfg(feature = "persistent")] use unify::Persistent; +use unify::{EqUnifyValue, InPlace, InPlaceUnificationTable, NoError, UnifyKey, UnifyValue}; +use unify::{UnificationStore, UnificationTable}; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] struct UnitKey(u32); @@ -40,7 +40,9 @@ impl UnifyKey for UnitKey { macro_rules! all_modes { ($name:ident for $t:ty => $body:tt) => { - fn test_body<$name: UnificationStore::Value>>() { + fn test_body< + $name: Clone + Default + UnificationStore::Value>, + >() { $body } @@ -48,7 +50,7 @@ macro_rules! all_modes { #[cfg(feature = "persistent")] test_body::>(); - } + }; } #[test] @@ -91,7 +93,9 @@ fn big_array() { } #[cfg(feature = "bench")] -fn big_array_bench_generic>(b: &mut Bencher) { +fn big_array_bench_generic>( + b: &mut Bencher, +) { let mut ut: UnificationTable = UnificationTable::new(); let mut keys = Vec::new(); const MAX: usize = 1 << 15; @@ -126,7 +130,9 @@ fn big_array_bench_Persistent(b: &mut Bencher) { } #[cfg(feature = "bench")] -fn big_array_bench_in_snapshot_generic>(b: &mut Bencher) { +fn big_array_bench_in_snapshot_generic>( + b: &mut Bencher, +) { let mut ut: UnificationTable = UnificationTable::new(); let mut keys = Vec::new(); const MAX: usize = 1 << 15; @@ -165,7 +171,11 @@ fn big_array_bench_in_snapshot_Persistent(b: &mut Bencher) { } #[cfg(feature = "bench")] -fn big_array_bench_clone_generic>(b: &mut Bencher) { +fn big_array_bench_clone_generic< + S: Default + Clone + UnificationStore, +>( + b: &mut Bencher, +) { let mut ut: UnificationTable = UnificationTable::new(); let mut keys = Vec::new(); const MAX: usize = 1 << 15; diff --git a/tests/external_undo_log.rs b/tests/external_undo_log.rs new file mode 100644 index 0000000..2537826 --- /dev/null +++ b/tests/external_undo_log.rs @@ -0,0 +1,202 @@ +#[macro_use] +extern crate log; +extern crate ena; + +use ena::{ + snapshot_vec as sv, + undo_log::{Rollback, Snapshots, UndoLogs}, + unify::{self as ut, EqUnifyValue, UnifyKey}, +}; + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +struct IntKey(u32); + +impl UnifyKey for IntKey { + type Value = Option; + fn index(&self) -> u32 { + self.0 + } + fn from_index(u: u32) -> IntKey { + IntKey(u) + } + fn tag() -> &'static str { + "IntKey" + } +} + +impl EqUnifyValue for IntKey {} + +enum UndoLog { + EqRelation(sv::UndoLog>), + Values(sv::UndoLog), +} + +impl From>> for UndoLog { + fn from(l: sv::UndoLog>) -> Self { + UndoLog::EqRelation(l) + } +} + +impl From> for UndoLog { + fn from(l: sv::UndoLog) -> Self { + UndoLog::Values(l) + } +} + +impl Rollback for TypeVariableStorage { + fn reverse(&mut self, undo: UndoLog) { + match undo { + UndoLog::EqRelation(undo) => self.eq_relations.reverse(undo), + UndoLog::Values(undo) => self.values.reverse(undo), + } + } +} + +#[derive(Default)] +struct TypeVariableStorage { + values: sv::SnapshotVecStorage, + + eq_relations: ut::UnificationTableStorage, +} + +impl TypeVariableStorage { + fn with_log<'a>(&'a mut self, undo_log: &'a mut TypeVariableUndoLogs) -> TypeVariableTable<'a> { + TypeVariableTable { + storage: self, + undo_log, + } + } + + fn len(&mut self) -> usize { + assert_eq!(self.values.len(), self.eq_relations.len()); + self.values.len() + } +} + +struct TypeVariableTable<'a> { + storage: &'a mut TypeVariableStorage, + + undo_log: &'a mut TypeVariableUndoLogs, +} + +impl TypeVariableTable<'_> { + fn new(&mut self, i: i32) -> IntKey { + self.storage.values.with_log(&mut self.undo_log).push(i); + self.storage + .eq_relations + .with_log(&mut self.undo_log) + .new_key(None) + } +} + +struct Snapshot { + undo_len: usize, +} + +struct TypeVariableUndoLogs { + logs: Vec, + num_open_snapshots: usize, +} + +impl Default for TypeVariableUndoLogs { + fn default() -> Self { + Self { + logs: Default::default(), + num_open_snapshots: Default::default(), + } + } +} + +impl UndoLogs for TypeVariableUndoLogs +where + UndoLog: From, +{ + fn num_open_snapshots(&self) -> usize { + self.num_open_snapshots + } + fn push(&mut self, undo: T) { + if self.in_snapshot() { + self.logs.push(undo.into()) + } + } + fn clear(&mut self) { + self.logs.clear(); + self.num_open_snapshots = 0; + } + fn extend(&mut self, undos: J) + where + Self: Sized, + J: IntoIterator, + { + if self.in_snapshot() { + self.logs.extend(undos.into_iter().map(UndoLog::from)) + } + } +} + +impl Snapshots for TypeVariableUndoLogs { + type Snapshot = Snapshot; + fn actions_since_snapshot(&self, snapshot: &Self::Snapshot) -> &[UndoLog] { + &self.logs[snapshot.undo_len..] + } + + fn start_snapshot(&mut self) -> Self::Snapshot { + self.num_open_snapshots += 1; + Snapshot { + undo_len: self.logs.len(), + } + } + + fn rollback_to(&mut self, values: impl FnOnce() -> R, snapshot: Self::Snapshot) + where + R: Rollback, + { + debug!("rollback_to({})", snapshot.undo_len); + + if self.logs.len() > snapshot.undo_len { + let mut values = values(); + while self.logs.len() > snapshot.undo_len { + values.reverse(self.logs.pop().unwrap()); + } + } + + if self.num_open_snapshots == 1 { + // The root snapshot. It's safe to clear the undo log because + // there's no snapshot further out that we might need to roll back + // to. + assert!(snapshot.undo_len == 0); + self.logs.clear(); + } + + self.num_open_snapshots -= 1; + } + + fn commit(&mut self, snapshot: Self::Snapshot) { + debug!("commit({})", snapshot.undo_len); + + if self.num_open_snapshots == 1 { + // The root snapshot. It's safe to clear the undo log because + // there's no snapshot further out that we might need to roll back + // to. + assert!(snapshot.undo_len == 0); + self.logs.clear(); + } + + self.num_open_snapshots -= 1; + } +} + +/// Tests that a undo log stored externally can be used with TypeVariableTable +#[test] +fn external_undo_log() { + let mut storage = TypeVariableStorage::default(); + let mut undo_log = TypeVariableUndoLogs::default(); + + let snapshot = undo_log.start_snapshot(); + storage.with_log(&mut undo_log).new(1); + storage.with_log(&mut undo_log).new(2); + assert_eq!(storage.len(), 2); + + undo_log.rollback_to(|| &mut storage, snapshot); + assert_eq!(storage.len(), 0); +}