diff --git a/Cargo.lock b/Cargo.lock index 4bd2d4f9420..b1a60599b38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17860,6 +17860,13 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared-dsa" +version = "0.1.0" +dependencies = [ + "rustc-hash 2.1.1", +] + [[package]] name = "shell-words" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 71908c8be26..e40c3a5236e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -248,6 +248,7 @@ members = [ "third_party/move/move-vm/test-utils", "third_party/move/move-vm/transactional-tests", "third_party/move/move-vm/types", + "third_party/move/shared-dsa", "third_party/move/testing-infra/transactional-test-runner", "third_party/move/tools/move-abigen", "third_party/move/tools/move-asm", @@ -783,6 +784,7 @@ rocksdb = { version = "0.24.0", features = ["lz4"] } rsa = { version = "0.9.6" } rstack-self = { version = "0.3.0", features = ["dw"], default-features = false } rstest = "0.15.0" +rustc-hash = "2" rusty-fork = "0.3.0" rustversion = "1.0.14" scopeguard = "1.2.0" @@ -931,6 +933,7 @@ move-vm-test-utils = { path = "third_party/move/move-vm/test-utils", features = "table-extension", ] } move-vm-types = { path = "third_party/move/move-vm/types" } +shared-dsa = { path = "third_party/move/shared-dsa" } [profile.release] debug = true diff --git a/third_party/move/shared-dsa/Cargo.toml b/third_party/move/shared-dsa/Cargo.toml new file mode 100644 index 00000000000..28d14c47b60 --- /dev/null +++ b/third_party/move/shared-dsa/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "shared-dsa" +version = "0.1.0" + +# Workspace inherited keys +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +publish = { workspace = true } +repository = { workspace = true } +rust-version = { workspace = true } + +[dependencies] +rustc-hash = { workspace = true } diff --git a/third_party/move/shared-dsa/src/lib.rs b/third_party/move/shared-dsa/src/lib.rs new file mode 100644 index 00000000000..185a6d397e4 --- /dev/null +++ b/third_party/move/shared-dsa/src/lib.rs @@ -0,0 +1,12 @@ +// Copyright (c) Aptos Foundation +// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE + +//! Collection of data structures and algorithms, for shared use across +//! various crates. + +mod unordered_map; +mod unordered_set; + +pub use std::collections::hash_map::{Entry, OccupiedEntry, VacantEntry}; +pub use unordered_map::UnorderedMap; +pub use unordered_set::UnorderedSet; diff --git a/third_party/move/shared-dsa/src/unordered_map.rs b/third_party/move/shared-dsa/src/unordered_map.rs new file mode 100644 index 00000000000..5246c311ead --- /dev/null +++ b/third_party/move/shared-dsa/src/unordered_map.rs @@ -0,0 +1,260 @@ +// Copyright (c) Aptos Foundation +// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE + +use rustc_hash::{FxBuildHasher, FxHashMap}; +use std::{borrow::Borrow, collections::hash_map::Entry, fmt, hash::Hash, iter::FromIterator}; + +/// A fast, non-cryptographic, non-hash-DoS-resistant hash map. +/// Iteration over keys or key-value pairs are not exposed to avoid any +/// reliance on non-deterministic ordering. +#[derive(Clone)] +pub struct UnorderedMap { + inner: FxHashMap, +} + +// --------------------------------------------------------------------------- +// Methods that require no bounds on K/V +// --------------------------------------------------------------------------- + +impl UnorderedMap { + #[inline] + pub fn new() -> Self { + Self { + inner: FxHashMap::default(), + } + } + + /// Creates an empty map pre-allocated to hold at least `capacity` elements + /// without reallocation. The load factor is handled internally, so pass the + /// number of elements you expect, not an inflated value. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self { + inner: FxHashMap::with_capacity_and_hasher(capacity, FxBuildHasher), + } + } + + #[inline] + pub fn len(&self) -> usize { + self.inner.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + #[inline] + pub fn clear(&mut self) { + self.inner.clear(); + } +} + +// --------------------------------------------------------------------------- +// Methods that require K: Hash + Eq +// --------------------------------------------------------------------------- + +impl UnorderedMap { + #[inline] + pub fn get(&self, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.inner.get(k) + } + + #[inline] + pub fn get_mut(&mut self, k: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.inner.get_mut(k) + } + + #[inline] + pub fn contains_key(&self, k: &Q) -> bool + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.inner.contains_key(k) + } + + /// Inserts a key-value pair. Returns the previous value if the key was + /// already present, or `None` if it was newly inserted. + #[inline] + pub fn insert(&mut self, k: K, v: V) -> Option { + self.inner.insert(k, v) + } + + /// Removes a key, returning its value if the key was present. + #[inline] + pub fn remove(&mut self, k: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.inner.remove(k) + } + + /// Removes a key, returning the key-value pair if the key was present. + #[inline] + pub fn remove_entry(&mut self, k: &Q) -> Option<(K, V)> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.inner.remove_entry(k) + } + + #[inline] + pub fn entry(&mut self, key: K) -> Entry<'_, K, V> { + self.inner.entry(key) + } + + /// Reserves space for at least `additional` more elements. Pass the number + /// of elements you expect to add, not an inflated value — the load factor + /// is handled internally. + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.inner.reserve(additional); + } +} + +// --------------------------------------------------------------------------- +// Trait implementations +// --------------------------------------------------------------------------- + +impl Default for UnorderedMap { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl fmt::Debug for UnorderedMap { + /// Only shows the length to avoid exposing arbitrary iteration order. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UnorderedMap") + .field("len", &self.inner.len()) + .finish() + } +} + +impl PartialEq for UnorderedMap { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Eq for UnorderedMap {} + +impl FromIterator<(K, V)> for UnorderedMap { + #[inline] + fn from_iter>(iter: I) -> Self { + Self { + inner: iter.into_iter().collect(), + } + } +} + +impl Extend<(K, V)> for UnorderedMap { + #[inline] + fn extend>(&mut self, iter: I) { + self.inner.extend(iter); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_insert_get_remove() { + let mut map = UnorderedMap::new(); + assert!(map.is_empty()); + + assert_eq!(map.insert("a", 1), None); + assert_eq!(map.insert("b", 2), None); + assert_eq!(map.len(), 2); + + assert_eq!(map.get("a"), Some(&1)); + assert_eq!(map.get("c"), None); + assert!(map.contains_key("b")); + + assert_eq!(map.remove("a"), Some(1)); + assert_eq!(map.len(), 1); + assert!(!map.contains_key("a")); + } + + #[test] + fn test_get_mut() { + let mut map = UnorderedMap::new(); + map.insert("x", 10); + *map.get_mut("x").unwrap() = 20; + assert_eq!(map.get("x"), Some(&20)); + } + + #[test] + fn test_remove_entry() { + let mut map = UnorderedMap::new(); + map.insert("k", 42); + assert_eq!(map.remove_entry("k"), Some(("k", 42))); + assert!(map.is_empty()); + } + + #[test] + fn test_entry_api() { + let mut map = UnorderedMap::new(); + map.entry("a").or_insert(1); + map.entry("a").and_modify(|v| *v += 10); + assert_eq!(map.get("a"), Some(&11)); + } + + #[test] + fn test_clear() { + let mut map = UnorderedMap::with_capacity(16); + map.insert(1, 1); + map.clear(); + assert!(map.is_empty()); + } + + #[test] + fn test_default() { + let map: UnorderedMap = Default::default(); + assert!(map.is_empty()); + } + + #[test] + fn test_eq() { + let a: UnorderedMap<_, _> = [(1, 2), (3, 4)].into_iter().collect(); + let b: UnorderedMap<_, _> = [(3, 4), (1, 2)].into_iter().collect(); + assert_eq!(a, b); + } + + #[test] + fn test_extend() { + let mut map = UnorderedMap::new(); + map.insert(1, "a"); + map.extend([(2, "b"), (3, "c")]); + assert_eq!(map.len(), 3); + } + + #[test] + fn test_reserve() { + let mut map = UnorderedMap::new(); + map.insert(1, 1); + map.reserve(100); + } + + #[test] + fn test_debug() { + let mut map = UnorderedMap::new(); + map.insert(1, 2); + let s = format!("{:?}", map); + assert!(s.contains("len: 1")); + } +} diff --git a/third_party/move/shared-dsa/src/unordered_set.rs b/third_party/move/shared-dsa/src/unordered_set.rs new file mode 100644 index 00000000000..2eb63683de1 --- /dev/null +++ b/third_party/move/shared-dsa/src/unordered_set.rs @@ -0,0 +1,270 @@ +// Copyright (c) Aptos Foundation +// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE + +use rustc_hash::{FxBuildHasher, FxHashSet}; +use std::{borrow::Borrow, fmt, hash::Hash, iter::FromIterator}; + +/// A fast, non-cryptographic, non-hash-DoS-resistant hash set. +/// Iteration over elements is not exposed to avoid any reliance on +/// non-deterministic ordering. +#[derive(Clone)] +pub struct UnorderedSet { + inner: FxHashSet, +} + +// --------------------------------------------------------------------------- +// Methods that require no bounds on K +// --------------------------------------------------------------------------- + +impl UnorderedSet { + #[inline] + pub fn new() -> Self { + Self { + inner: FxHashSet::default(), + } + } + + /// Creates an empty set pre-allocated to hold at least `capacity` elements + /// without reallocation. The load factor is handled internally, so pass the + /// number of elements you expect, not an inflated value. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self { + inner: FxHashSet::with_capacity_and_hasher(capacity, FxBuildHasher), + } + } + + #[inline] + pub fn len(&self) -> usize { + self.inner.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + #[inline] + pub fn clear(&mut self) { + self.inner.clear(); + } +} + +// --------------------------------------------------------------------------- +// Methods that require K: Hash + Eq +// --------------------------------------------------------------------------- + +impl UnorderedSet { + #[inline] + pub fn contains(&self, value: &Q) -> bool + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.inner.contains(value) + } + + #[inline] + pub fn get(&self, value: &Q) -> Option<&K> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.inner.get(value) + } + + /// Inserts a value. Returns `true` if the value was newly inserted, or + /// `false` if it was already present. + #[inline] + pub fn insert(&mut self, value: K) -> bool { + self.inner.insert(value) + } + + #[inline] + pub fn replace(&mut self, value: K) -> Option { + self.inner.replace(value) + } + + /// Removes a value. Returns `true` if the value was present. + #[inline] + pub fn remove(&mut self, value: &Q) -> bool + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.inner.remove(value) + } + + #[inline] + pub fn take(&mut self, value: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.inner.take(value) + } + + /// Reserves space for at least `additional` more elements. Pass the number + /// of elements you expect to add, not an inflated value — the load factor + /// is handled internally. + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.inner.reserve(additional); + } + + #[inline] + pub fn is_subset(&self, other: &UnorderedSet) -> bool { + self.inner.is_subset(&other.inner) + } + + #[inline] + pub fn is_superset(&self, other: &UnorderedSet) -> bool { + self.inner.is_superset(&other.inner) + } + + #[inline] + pub fn is_disjoint(&self, other: &UnorderedSet) -> bool { + self.inner.is_disjoint(&other.inner) + } +} + +// --------------------------------------------------------------------------- +// Trait implementations +// --------------------------------------------------------------------------- + +impl Default for UnorderedSet { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl fmt::Debug for UnorderedSet { + /// Only shows the length to avoid exposing arbitrary iteration order. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UnorderedSet") + .field("len", &self.inner.len()) + .finish() + } +} + +impl PartialEq for UnorderedSet { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Eq for UnorderedSet {} + +impl FromIterator for UnorderedSet { + #[inline] + fn from_iter>(iter: I) -> Self { + Self { + inner: iter.into_iter().collect(), + } + } +} + +impl Extend for UnorderedSet { + #[inline] + fn extend>(&mut self, iter: I) { + self.inner.extend(iter); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_insert_contains_remove() { + let mut set = UnorderedSet::new(); + assert!(set.is_empty()); + + assert!(set.insert(1)); + assert!(set.insert(2)); + assert!(!set.insert(1)); // duplicate + assert_eq!(set.len(), 2); + + assert!(set.contains(&1)); + assert!(!set.contains(&3)); + + assert!(set.remove(&1)); + assert!(!set.remove(&1)); + assert_eq!(set.len(), 1); + } + + #[test] + fn test_get_and_take() { + let mut set = UnorderedSet::new(); + set.insert(42); + assert_eq!(set.get(&42), Some(&42)); + assert_eq!(set.take(&42), Some(42)); + assert!(set.is_empty()); + } + + #[test] + fn test_replace() { + let mut set = UnorderedSet::new(); + assert_eq!(set.replace(1), None); + assert_eq!(set.replace(1), Some(1)); + } + + #[test] + fn test_clear() { + let mut set = UnorderedSet::with_capacity(16); + set.insert(1); + set.clear(); + assert!(set.is_empty()); + } + + #[test] + fn test_set_relations() { + let a: UnorderedSet = [1, 2, 3].into_iter().collect(); + let b: UnorderedSet = [1, 2, 3, 4, 5].into_iter().collect(); + let c: UnorderedSet = [6, 7].into_iter().collect(); + + assert!(a.is_subset(&b)); + assert!(!b.is_subset(&a)); + assert!(b.is_superset(&a)); + assert!(a.is_disjoint(&c)); + assert!(!a.is_disjoint(&b)); + } + + #[test] + fn test_default() { + let set: UnorderedSet = Default::default(); + assert!(set.is_empty()); + } + + #[test] + fn test_eq() { + let a: UnorderedSet = [1, 2, 3].into_iter().collect(); + let b: UnorderedSet = [3, 1, 2].into_iter().collect(); + assert_eq!(a, b); + } + + #[test] + fn test_extend() { + let mut set = UnorderedSet::new(); + set.insert(1); + set.extend([2, 3, 4]); + assert_eq!(set.len(), 4); + } + + #[test] + fn test_reserve() { + let mut set = UnorderedSet::new(); + set.insert(1); + set.reserve(100); + } + + #[test] + fn test_debug() { + let mut set = UnorderedSet::new(); + set.insert(42); + let s = format!("{:?}", set); + assert!(s.contains("len: 1")); + } +}