From 1813ef0df525db364023c150d0a4b42b3ce4615a Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Tue, 23 May 2023 11:41:27 +0200 Subject: [PATCH 1/4] feat: declare no-std, feature gate log --- Cargo.toml | 5 +++-- src/lib.rs | 5 ++++- src/undo_log.rs | 2 ++ src/unify/mod.rs | 5 +++++ tests/external_undo_log.rs | 2 ++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b606aed..6b7b724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,10 @@ readme = "README.md" keywords = ["unification", "union-find"] [features] +std = [ ] bench = [ ] -persistent = [ "dogged" ] +persistent = [ "std", "dogged" ] [dependencies] dogged = { version = "0.2.0", optional = true } -log = "0.4" +log = { version = "0.4", optional = true } diff --git a/src/lib.rs b/src/lib.rs index aed3524..09f1bbd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,9 +10,12 @@ //! An implementation of union-find. See the `unify` module for more //! details. - #![cfg_attr(feature = "bench", feature(test))] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +#[cfg(feature = "log")] #[macro_use] extern crate log; diff --git a/src/undo_log.rs b/src/undo_log.rs index 93c3803..c9d0a16 100644 --- a/src/undo_log.rs +++ b/src/undo_log.rs @@ -181,6 +181,7 @@ impl Snapshots for VecLog { where R: Rollback, { + #[cfg(feature = "log")] debug!("rollback_to({})", snapshot.undo_len); self.assert_open_snapshot(&snapshot); @@ -196,6 +197,7 @@ impl Snapshots for VecLog { } fn commit(&mut self, snapshot: Snapshot) { + #[cfg(feature = "log")] debug!("commit({})", snapshot.undo_len); self.assert_open_snapshot(&snapshot); diff --git a/src/unify/mod.rs b/src/unify/mod.rs index 604f3bd..afe84f4 100644 --- a/src/unify/mod.rs +++ b/src/unify/mod.rs @@ -272,6 +272,7 @@ impl UnificationTable { /// Reverses all changes since the last snapshot. Also /// removes any keys that have been created since then. pub fn rollback_to(&mut self, snapshot: Snapshot) { + #[cfg(feature = "log")] debug!("{}: rollback_to()", S::tag()); self.values.rollback_to(snapshot.snapshot); } @@ -279,6 +280,7 @@ impl UnificationTable { /// 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) { + #[cfg(feature = "log")] debug!("{}: commit()", S::tag()); self.values.commit(snapshot.snapshot); } @@ -310,6 +312,7 @@ impl UnificationTable { let len = self.values.len(); let key: S::Key = UnifyKey::from_index(len as u32); self.values.push(VarValue::new_var(key, value)); + #[cfg(feature = "log")] debug!("{}: created new key: {:?}", S::tag(), key); key } @@ -369,6 +372,7 @@ impl UnificationTable { OP: FnOnce(&mut VarValue), { self.values.update(key.index() as usize, op); + #[cfg(feature = "log")] debug!("Updated variable {:?} to {:?}", key, self.value(key)); } @@ -381,6 +385,7 @@ impl UnificationTable { /// your key are non-trivial, you would probably prefer to call /// `unify_var_var` below. fn unify_roots(&mut self, key_a: S::Key, key_b: S::Key, new_value: S::Value) { + #[cfg(feature = "log")] debug!("unify(key_a={:?}, key_b={:?})", key_a, key_b); let rank_a = self.value(key_a).rank; diff --git a/tests/external_undo_log.rs b/tests/external_undo_log.rs index 2537826..04e3514 100644 --- a/tests/external_undo_log.rs +++ b/tests/external_undo_log.rs @@ -151,6 +151,7 @@ impl Snapshots for TypeVariableUndoLogs { where R: Rollback, { + #[cfg(feature = "log")] debug!("rollback_to({})", snapshot.undo_len); if self.logs.len() > snapshot.undo_len { @@ -172,6 +173,7 @@ impl Snapshots for TypeVariableUndoLogs { } fn commit(&mut self, snapshot: Self::Snapshot) { + #[cfg(feature = "log")] debug!("commit({})", snapshot.undo_len); if self.num_open_snapshots == 1 { From 4319159fff582b9650ed4cf716c86ea6df069216 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Tue, 23 May 2023 11:50:46 +0200 Subject: [PATCH 2/4] feat: convert to no-std --- src/bitvec.rs | 301 ------------------------------------- src/lib.rs | 2 + src/snapshot_vec.rs | 9 +- src/undo_log.rs | 5 +- src/unify/backing_vec.rs | 5 +- src/unify/mod.rs | 7 +- src/unify/tests.rs | 6 +- tests/external_undo_log.rs | 1 + 8 files changed, 24 insertions(+), 312 deletions(-) delete mode 100644 src/bitvec.rs diff --git a/src/bitvec.rs b/src/bitvec.rs deleted file mode 100644 index 3677c8c..0000000 --- a/src/bitvec.rs +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2015 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 very simple BitVector type. -pub struct BitVector { - data: Vec, -} - -impl BitVector { - pub fn new(num_bits: usize) -> BitVector { - let num_words = u64s(num_bits); - BitVector { data: vec![0; num_words] } - } - - pub fn contains(&self, bit: usize) -> bool { - let (word, mask) = word_mask(bit); - (self.data[word] & mask) != 0 - } - - /// Returns true if the bit has changed. - pub fn insert(&mut self, bit: usize) -> bool { - let (word, mask) = word_mask(bit); - let data = &mut self.data[word]; - let value = *data; - let new_value = value | mask; - *data = new_value; - new_value != value - } - - pub fn insert_all(&mut self, all: &BitVector) -> bool { - assert!(self.data.len() == all.data.len()); - let mut changed = false; - for (i, j) in self.data.iter_mut().zip(&all.data) { - let value = *i; - *i = value | *j; - if value != *i { - changed = true; - } - } - changed - } - - pub fn grow(&mut self, num_bits: usize) { - let num_words = u64s(num_bits); - let extra_words = self.data.len() - num_words; - self.data.extend((0..extra_words).map(|_| 0)); - } - - /// Iterates over indexes of set bits in a sorted order - pub fn iter<'a>(&'a self) -> BitVectorIter<'a> { - BitVectorIter { - iter: self.data.iter(), - current: 0, - idx: 0, - } - } -} - -pub struct BitVectorIter<'a> { - iter: ::std::slice::Iter<'a, u64>, - current: u64, - idx: usize, -} - -impl<'a> Iterator for BitVectorIter<'a> { - type Item = usize; - fn next(&mut self) -> Option { - while self.current == 0 { - self.current = if let Some(&i) = self.iter.next() { - if i == 0 { - self.idx += 64; - continue; - } else { - self.idx = u64s(self.idx) * 64; - i - } - } else { - return None; - } - } - let offset = self.current.trailing_zeros() as usize; - self.current >>= offset; - self.current >>= 1; // shift otherwise overflows for 0b1000_0000_…_0000 - self.idx += offset + 1; - return Some(self.idx - 1); - } -} - -/// A "bit matrix" is basically a square matrix of booleans -/// represented as one gigantic bitvector. In other words, it is as if -/// you have N bitvectors, each of length N. Note that `elements` here is `N`/ -#[derive(Clone)] -pub struct BitMatrix { - elements: usize, - vector: Vec, -} - -impl BitMatrix { - // Create a new `elements x elements` matrix, initially empty. - pub fn new(elements: usize) -> BitMatrix { - // For every element, we need one bit for every other - // element. Round up to an even number of u64s. - let u64s_per_elem = u64s(elements); - BitMatrix { - elements: elements, - vector: vec![0; elements * u64s_per_elem], - } - } - - /// The range of bits for a given element. - fn range(&self, element: usize) -> (usize, usize) { - let u64s_per_elem = u64s(self.elements); - let start = element * u64s_per_elem; - (start, start + u64s_per_elem) - } - - pub fn add(&mut self, source: usize, target: usize) -> bool { - let (start, _) = self.range(source); - let (word, mask) = word_mask(target); - let mut vector = &mut self.vector[..]; - let v1 = vector[start + word]; - let v2 = v1 | mask; - vector[start + word] = v2; - v1 != v2 - } - - /// Do the bits from `source` contain `target`? - /// - /// Put another way, if the matrix represents (transitive) - /// reachability, can `source` reach `target`? - pub fn contains(&self, source: usize, target: usize) -> bool { - let (start, _) = self.range(source); - let (word, mask) = word_mask(target); - (self.vector[start + word] & mask) != 0 - } - - /// Returns those indices that are reachable from both `a` and - /// `b`. This is an O(n) operation where `n` is the number of - /// elements (somewhat independent from the actual size of the - /// intersection, in particular). - pub fn intersection(&self, a: usize, b: usize) -> Vec { - let (a_start, a_end) = self.range(a); - let (b_start, b_end) = self.range(b); - let mut result = Vec::with_capacity(self.elements); - for (base, (i, j)) in (a_start..a_end).zip(b_start..b_end).enumerate() { - let mut v = self.vector[i] & self.vector[j]; - for bit in 0..64 { - if v == 0 { - break; - } - if v & 0x1 != 0 { - result.push(base * 64 + bit); - } - v >>= 1; - } - } - result - } - - /// Add the bits from `read` to the bits from `write`, - /// return true if anything changed. - /// - /// This is used when computing transitive reachability because if - /// you have an edge `write -> read`, because in that case - /// `write` can reach everything that `read` can (and - /// potentially more). - pub fn merge(&mut self, read: usize, write: usize) -> bool { - let (read_start, read_end) = self.range(read); - let (write_start, write_end) = self.range(write); - let vector = &mut self.vector[..]; - let mut changed = false; - for (read_index, write_index) in (read_start..read_end).zip(write_start..write_end) { - let v1 = vector[write_index]; - let v2 = v1 | vector[read_index]; - vector[write_index] = v2; - changed = changed | (v1 != v2); - } - changed - } -} - -fn u64s(elements: usize) -> usize { - (elements + 63) / 64 -} - -fn word_mask(index: usize) -> (usize, u64) { - let word = index / 64; - let mask = 1 << (index % 64); - (word, mask) -} - -#[test] -fn bitvec_iter_works() { - let mut bitvec = BitVector::new(100); - bitvec.insert(1); - bitvec.insert(10); - bitvec.insert(19); - bitvec.insert(62); - bitvec.insert(63); - bitvec.insert(64); - bitvec.insert(65); - bitvec.insert(66); - bitvec.insert(99); - assert_eq!(bitvec.iter().collect::>(), - [1, 10, 19, 62, 63, 64, 65, 66, 99]); -} - -#[test] -fn bitvec_iter_works_2() { - let mut bitvec = BitVector::new(300); - bitvec.insert(1); - bitvec.insert(10); - bitvec.insert(19); - bitvec.insert(62); - bitvec.insert(66); - bitvec.insert(99); - bitvec.insert(299); - assert_eq!(bitvec.iter().collect::>(), - [1, 10, 19, 62, 66, 99, 299]); - -} - -#[test] -fn bitvec_iter_works_3() { - let mut bitvec = BitVector::new(319); - bitvec.insert(0); - bitvec.insert(127); - bitvec.insert(191); - bitvec.insert(255); - bitvec.insert(319); - assert_eq!(bitvec.iter().collect::>(), [0, 127, 191, 255, 319]); -} - -#[test] -fn union_two_vecs() { - let mut vec1 = BitVector::new(65); - let mut vec2 = BitVector::new(65); - assert!(vec1.insert(3)); - assert!(!vec1.insert(3)); - assert!(vec2.insert(5)); - assert!(vec2.insert(64)); - assert!(vec1.insert_all(&vec2)); - assert!(!vec1.insert_all(&vec2)); - assert!(vec1.contains(3)); - assert!(!vec1.contains(4)); - assert!(vec1.contains(5)); - assert!(!vec1.contains(63)); - assert!(vec1.contains(64)); -} - -#[test] -fn grow() { - let mut vec1 = BitVector::new(65); - assert!(vec1.insert(3)); - assert!(!vec1.insert(3)); - assert!(vec1.insert(5)); - assert!(vec1.insert(64)); - vec1.grow(128); - assert!(vec1.contains(3)); - assert!(vec1.contains(5)); - assert!(vec1.contains(64)); - assert!(!vec1.contains(126)); -} - -#[test] -fn matrix_intersection() { - let mut vec1 = BitMatrix::new(200); - - // (*) Elements reachable from both 2 and 65. - - vec1.add(2, 3); - vec1.add(2, 6); - vec1.add(2, 10); // (*) - vec1.add(2, 64); // (*) - vec1.add(2, 65); - vec1.add(2, 130); - vec1.add(2, 160); // (*) - - vec1.add(64, 133); - - vec1.add(65, 2); - vec1.add(65, 8); - vec1.add(65, 10); // (*) - vec1.add(65, 64); // (*) - vec1.add(65, 68); - vec1.add(65, 133); - vec1.add(65, 160); // (*) - - let intersection = vec1.intersection(2, 64); - assert!(intersection.is_empty()); - - let intersection = vec1.intersection(2, 65); - assert_eq!(intersection, &[10, 64, 160]); -} diff --git a/src/lib.rs b/src/lib.rs index 09f1bbd..e967798 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ #![cfg_attr(feature = "bench", feature(test))] #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "std")] +extern crate core; extern crate alloc; #[cfg(feature = "log")] diff --git a/src/snapshot_vec.rs b/src/snapshot_vec.rs index efde49a..f765b30 100644 --- a/src/snapshot_vec.rs +++ b/src/snapshot_vec.rs @@ -21,10 +21,11 @@ use self::UndoLog::*; -use std::fmt; -use std::marker::PhantomData; -use std::mem; -use std::ops; +use core::fmt; +use core::marker::PhantomData; +use core::mem; +use core::ops; +use alloc::vec::Vec; use undo_log::{Rollback, Snapshots, UndoLogs, VecLog}; diff --git a/src/undo_log.rs b/src/undo_log.rs index c9d0a16..ece4554 100644 --- a/src/undo_log.rs +++ b/src/undo_log.rs @@ -8,6 +8,9 @@ //! Since the `*Storage` variants do not have an undo log `with_log` must be called with the //! unified log before any mutating actions. +use core::ops; +use alloc::vec::Vec; + /// A trait which allows undo actions (`T`) to be pushed which can be used to rollback actio at a /// later time if needed. /// @@ -222,7 +225,7 @@ impl VecLog { } } -impl std::ops::Index for VecLog { +impl ops::Index for VecLog { type Output = T; fn index(&self, key: usize) -> &T { &self.log[key] diff --git a/src/unify/backing_vec.rs b/src/unify/backing_vec.rs index 7c1eb2c..58622ae 100644 --- a/src/unify/backing_vec.rs +++ b/src/unify/backing_vec.rs @@ -1,8 +1,9 @@ #[cfg(feature = "persistent")] use dogged::DVec; use snapshot_vec as sv; -use std::marker::PhantomData; -use std::ops::{self, Range}; +use core::marker::PhantomData; +use core::ops::{self, Range}; +use alloc::vec::Vec; use undo_log::{Rollback, Snapshots, UndoLogs, VecLog}; diff --git a/src/unify/mod.rs b/src/unify/mod.rs index afe84f4..ffa0fc0 100644 --- a/src/unify/mod.rs +++ b/src/unify/mod.rs @@ -31,9 +31,10 @@ //! The best way to see how it is used is to read the `tests.rs` file; //! search for e.g. `UnitKey`. -use std::fmt::Debug; -use std::marker; -use std::ops::Range; +use core::fmt::Debug; +use core::marker; +use core::ops::Range; +use alloc::vec::Vec; use snapshot_vec::{self as sv, UndoLog}; use undo_log::{UndoLogs, VecLog}; diff --git a/src/unify/tests.rs b/src/unify/tests.rs index 5665aba..0818f5f 100644 --- a/src/unify/tests.rs +++ b/src/unify/tests.rs @@ -14,9 +14,12 @@ #[cfg(feature = "bench")] extern crate test; + #[cfg(feature = "bench")] use self::test::Bencher; -use std::cmp; +use core::cmp; +use alloc::vec::Vec; +use alloc::vec; #[cfg(feature = "persistent")] use unify::Persistent; use unify::{EqUnifyValue, InPlace, InPlaceUnificationTable, NoError, UnifyKey, UnifyValue}; @@ -385,6 +388,7 @@ impl UnifyKey for OrderedKey { b: OrderedKey, b_rank: &OrderedRank, ) -> Option<(OrderedKey, OrderedKey)> { + #[cfg(feature = "std")] println!("{:?} vs {:?}", a_rank, b_rank); if a_rank > b_rank { Some((a, b)) diff --git a/tests/external_undo_log.rs b/tests/external_undo_log.rs index 04e3514..6bdaf9d 100644 --- a/tests/external_undo_log.rs +++ b/tests/external_undo_log.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "log")] #[macro_use] extern crate log; extern crate ena; From a0fb25d9b2717147c399c5149e9baecb0c3b6e57 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Mon, 5 Jun 2023 09:41:36 +0200 Subject: [PATCH 3/4] feat: apply suggestions from code-review --- src/lib.rs | 8 +++++++- src/undo_log.rs | 2 -- src/unify/mod.rs | 5 ----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e967798..1e67518 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,12 +18,18 @@ extern crate core; extern crate alloc; #[cfg(feature = "log")] -#[macro_use] extern crate log; #[cfg(feature = "persistent")] extern crate dogged; +macro_rules! debug { + ($($tt:tt)*) => { + #[cfg(feature = "log")] + log::debug!($($tt)*); + }; +} + pub mod snapshot_vec; pub mod undo_log; pub mod unify; diff --git a/src/undo_log.rs b/src/undo_log.rs index ece4554..d40e13c 100644 --- a/src/undo_log.rs +++ b/src/undo_log.rs @@ -184,7 +184,6 @@ impl Snapshots for VecLog { where R: Rollback, { - #[cfg(feature = "log")] debug!("rollback_to({})", snapshot.undo_len); self.assert_open_snapshot(&snapshot); @@ -200,7 +199,6 @@ impl Snapshots for VecLog { } fn commit(&mut self, snapshot: Snapshot) { - #[cfg(feature = "log")] debug!("commit({})", snapshot.undo_len); self.assert_open_snapshot(&snapshot); diff --git a/src/unify/mod.rs b/src/unify/mod.rs index ffa0fc0..1b80e6f 100644 --- a/src/unify/mod.rs +++ b/src/unify/mod.rs @@ -273,7 +273,6 @@ impl UnificationTable { /// Reverses all changes since the last snapshot. Also /// removes any keys that have been created since then. pub fn rollback_to(&mut self, snapshot: Snapshot) { - #[cfg(feature = "log")] debug!("{}: rollback_to()", S::tag()); self.values.rollback_to(snapshot.snapshot); } @@ -281,7 +280,6 @@ impl UnificationTable { /// 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) { - #[cfg(feature = "log")] debug!("{}: commit()", S::tag()); self.values.commit(snapshot.snapshot); } @@ -313,7 +311,6 @@ impl UnificationTable { let len = self.values.len(); let key: S::Key = UnifyKey::from_index(len as u32); self.values.push(VarValue::new_var(key, value)); - #[cfg(feature = "log")] debug!("{}: created new key: {:?}", S::tag(), key); key } @@ -373,7 +370,6 @@ impl UnificationTable { OP: FnOnce(&mut VarValue), { self.values.update(key.index() as usize, op); - #[cfg(feature = "log")] debug!("Updated variable {:?} to {:?}", key, self.value(key)); } @@ -386,7 +382,6 @@ impl UnificationTable { /// your key are non-trivial, you would probably prefer to call /// `unify_var_var` below. fn unify_roots(&mut self, key_a: S::Key, key_b: S::Key, new_value: S::Value) { - #[cfg(feature = "log")] debug!("unify(key_a={:?}, key_b={:?})", key_a, key_b); let rank_a = self.value(key_a).rank; From e32df28ebd96a1cd3c9d44eda501cf76bde28fb2 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Mon, 5 Jun 2023 09:42:02 +0200 Subject: [PATCH 4/4] feat: apply suggestions from code-review to integration tests --- tests/external_undo_log.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/external_undo_log.rs b/tests/external_undo_log.rs index 6bdaf9d..c1d8bc3 100644 --- a/tests/external_undo_log.rs +++ b/tests/external_undo_log.rs @@ -1,8 +1,14 @@ #[cfg(feature = "log")] -#[macro_use] extern crate log; extern crate ena; +macro_rules! debug { + ($($tt:tt)*) => { + #[cfg(feature = "log")] + log::debug!($($tt)*); + }; +} + use ena::{ snapshot_vec as sv, undo_log::{Rollback, Snapshots, UndoLogs}, @@ -152,7 +158,6 @@ impl Snapshots for TypeVariableUndoLogs { where R: Rollback, { - #[cfg(feature = "log")] debug!("rollback_to({})", snapshot.undo_len); if self.logs.len() > snapshot.undo_len { @@ -174,7 +179,6 @@ impl Snapshots for TypeVariableUndoLogs { } fn commit(&mut self, snapshot: Self::Snapshot) { - #[cfg(feature = "log")] debug!("commit({})", snapshot.undo_len); if self.num_open_snapshots == 1 {