foundry_evm_symbolic/runtime/expr/
hashcons.rs1use alloy_primitives::map::foldhash::fast::FixedState;
2use hashbrown::{HashTable, hash_table::Entry};
3use std::{
4 cmp::Ordering,
5 fmt,
6 hash::{BuildHasher, Hash, Hasher},
7 sync::{Arc, Weak},
8};
9
10pub(in crate::runtime) struct HashConsed<T> {
15 inner: Arc<HashConsedInner<T>>,
16}
17
18struct HashConsedInner<T> {
19 hash: u64,
20 value: T,
21}
22
23impl<T> HashConsed<T> {
24 #[inline]
25 pub(in crate::runtime::expr) fn stable_hash_cmp(&self, other: &Self) -> Ordering {
26 self.inner.hash.cmp(&other.inner.hash)
27 }
28
29 #[inline]
30 pub(in crate::runtime) fn value(&self) -> &T {
31 &self.inner.value
32 }
33
34 #[inline]
35 pub(in crate::runtime) fn into_value(self) -> T
36 where
37 T: Clone,
38 {
39 match Arc::try_unwrap(self.inner) {
40 Ok(inner) => inner.value,
41 Err(inner) => inner.value.clone(),
42 }
43 }
44}
45
46impl<T> Clone for HashConsed<T> {
47 #[inline]
48 fn clone(&self) -> Self {
49 Self { inner: self.inner.clone() }
50 }
51}
52
53impl<T> PartialEq for HashConsed<T> {
54 #[inline]
55 fn eq(&self, other: &Self) -> bool {
56 Arc::ptr_eq(&self.inner, &other.inner)
57 }
58}
59
60impl<T> Eq for HashConsed<T> {}
61
62impl<T> Hash for HashConsed<T> {
63 #[inline]
64 fn hash<H: Hasher>(&self, state: &mut H) {
65 self.inner.hash.hash(state);
66 }
67}
68
69impl<T: PartialOrd> PartialOrd for HashConsed<T> {
70 #[inline]
71 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
72 self.value().partial_cmp(other.value())
73 }
74}
75
76impl<T: Ord> Ord for HashConsed<T> {
77 #[inline]
78 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
79 self.value().cmp(other.value())
80 }
81}
82
83impl<T: fmt::Debug> fmt::Debug for HashConsed<T> {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 self.value().fmt(f)
86 }
87}
88
89type HashConsHasher = FixedState;
90
91pub(in crate::runtime) struct HashCons<T> {
97 table: HashTable<HashConsEntry<T>>,
98 hash_builder: HashConsHasher,
99}
100
101struct HashConsEntry<T> {
102 hash: u64,
103 value: Weak<HashConsedInner<T>>,
104}
105
106impl<T> HashConsEntry<T> {
107 const fn hash(&self) -> u64 {
108 self.hash
109 }
110}
111
112impl<T> HashCons<T> {
113 pub(in crate::runtime) fn new() -> Self {
114 Self { table: HashTable::new(), hash_builder: HashConsHasher::default() }
115 }
116
117 fn hash<Q: Hash + ?Sized>(&self, value: &Q) -> u64 {
118 self.hash_builder.hash_one(value)
119 }
120}
121
122impl<T: Eq + Hash> HashCons<T> {
123 pub(in crate::runtime) fn make(&mut self, value: T) -> HashConsed<T> {
124 let hash = self.hash(&value);
125 let mut found = None;
126 match self.table.entry(
127 hash,
128 |entry| {
129 if entry.hash == hash
130 && let Some(existing) = entry.value.upgrade()
131 && existing.value == value
132 {
133 found = Some(existing);
134 true
135 } else {
136 false
137 }
138 },
139 HashConsEntry::hash,
140 ) {
141 Entry::Occupied(_) => HashConsed { inner: found.expect("matched live value") },
142 Entry::Vacant(entry) => {
143 let inner = HashConsedInner { hash, value };
144 let inner = Arc::new(inner);
145 entry.insert(HashConsEntry { hash, value: Arc::downgrade(&inner) });
146 HashConsed { inner }
147 }
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn make_reuses_existing_value() {
158 let mut table = HashCons::<String>::new();
159
160 let first = table.make("same".to_string());
161 let second = table.make("same".to_string());
162
163 assert_eq!(first, second);
164 assert_eq!(first.inner.hash, second.inner.hash);
165 }
166
167 #[test]
168 fn make_keeps_distinct_values_apart() {
169 let mut table = HashCons::<String>::new();
170
171 let first = table.make("first".to_string());
172 let second = table.make("second".to_string());
173
174 assert_ne!(first, second);
175 }
176
177 #[test]
178 fn dropped_values_are_not_reused() {
179 let mut table = HashCons::<String>::new();
180
181 let first = table.make("same".to_string());
182 let weak = Arc::downgrade(&first.inner);
183 drop(first);
184 assert!(weak.upgrade().is_none());
185
186 let second = table.make("same".to_string());
187
188 assert_eq!(second.value().as_str(), "same");
189 assert!(weak.upgrade().is_none());
190 }
191
192 #[test]
193 fn equality_is_pointer_only() {
194 let mut first_table = HashCons::<String>::new();
195 let mut second_table = HashCons::<String>::new();
196
197 let first = first_table.make("same".to_string());
198 let second = second_table.make("same".to_string());
199
200 assert_ne!(first, second);
201 assert_eq!(first.value(), second.value());
202 }
203}