structured_asn1/memory/map.rs
1//! Accounted storage and pre-admitted hashing for the existing hashbrown engine.
2
3use core::fmt;
4use core::hash::{BuildHasher, Hash};
5
6use hashbrown::hash_map::RawEntryMut;
7use hashbrown::{DefaultHashBuilder, HashMap, TryReserveError};
8
9use super::allocator::AccountAllocator;
10use super::{Memory, MemoryError, Work};
11
12/// A trusted key type's upper bound for hashing and equality work.
13///
14/// Implementations must cover all bytes examined by Hash and Eq. This is a
15/// contract for caller-provided code, not a value an untrusted document supplies.
16/// The bound must remain unchanged and successful while a key is stored, just
17/// as Hash and Eq must remain stable. Accounting retains this bound for rehashes.
18pub trait HashWork: Hash + Eq {
19 /// Work units for one hash/equality traversal, including constant overhead.
20 fn hash_work(&self) -> Result<u64, MemoryError>;
21}
22
23impl HashWork for usize {
24 fn hash_work(&self) -> Result<u64, MemoryError> {
25 Ok(1)
26 }
27}
28
29impl HashWork for u8 {
30 fn hash_work(&self) -> Result<u64, MemoryError> {
31 Ok(1)
32 }
33}
34
35impl HashWork for (usize, usize, usize) {
36 fn hash_work(&self) -> Result<u64, MemoryError> {
37 Ok(3)
38 }
39}
40
41impl HashWork for &[u8] {
42 fn hash_work(&self) -> Result<u64, MemoryError> {
43 Ok(self.len() as u64 + 1)
44 }
45}
46
47impl HashWork for crate::Oid<'_> {
48 fn hash_work(&self) -> Result<u64, MemoryError> {
49 Ok(self.as_bytes().len() as u64 + 1)
50 }
51}
52
53impl HashWork for (crate::Oid<'_>, crate::Oid<'_>) {
54 fn hash_work(&self) -> Result<u64, MemoryError> {
55 self.0
56 .hash_work()?
57 .checked_add(self.1.hash_work()?)
58 .ok_or(MemoryError::WorkExceeded)
59 }
60}
61
62/// A hash map whose backing allocation and operations share one resource domain.
63///
64/// Hashbrown supplies bucket/probing/SIMD machinery. Reservations include its
65/// control bytes and alignment, not merely the initialized entries. Hashing and
66/// actual collision comparisons are admitted before examining candidate keys.
67pub struct OwnedMap<K: HashWork, V, W: Work = Memory> {
68 values: HashMap<K, V, DefaultHashBuilder, AccountAllocator<W>>,
69 execution: W,
70 key_work: u64,
71 maximum_capacity: usize,
72}
73
74impl<K: HashWork + fmt::Debug, V: fmt::Debug, W: Work> fmt::Debug for OwnedMap<K, V, W> {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 self.values.fmt(f)
77 }
78}
79
80impl<K: HashWork, V, W: Work> OwnedMap<K, V, W> {
81 pub(super) fn new(execution: W) -> Self {
82 Self {
83 values: HashMap::with_hasher_in(
84 DefaultHashBuilder::default(),
85 AccountAllocator::new(execution.clone()),
86 ),
87 execution,
88 key_work: 0,
89 maximum_capacity: 0,
90 }
91 }
92
93 /// The shared account retained until the table is deallocated.
94 #[must_use]
95 pub fn memory(&self) -> &Memory {
96 self.execution.memory()
97 }
98
99 /// Number of initialized entries.
100 #[must_use]
101 pub fn len(&self) -> usize {
102 self.values.len()
103 }
104
105 /// Whether there are no entries.
106 #[must_use]
107 pub fn is_empty(&self) -> bool {
108 self.values.is_empty()
109 }
110
111 /// Borrowed entries after admitting a full control-bucket traversal in
112 /// the current execution domain, independently of retained storage.
113 /// Sparse holes count too. Partial consumption retains the conservative
114 /// prepayment; the opaque iterator exposes no cloning/restart operation.
115 ///
116 /// # Examples
117 /// ```
118 /// # extern crate alloc;
119 /// # use alloc::sync::Arc;
120 /// # use structured_asn1::memory::{Memory, MemoryAccount, MemoryError};
121 /// # let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
122 /// let mut map = memory.map();
123 /// map.try_insert(1usize, 42)?;
124 /// let mut entries = map.try_iter(&memory)?;
125 /// assert_eq!(entries.next(), Some((&1, &42)));
126 /// # Ok::<(), MemoryError>(())
127 /// ```
128 pub fn try_iter<'a, E: Work>(
129 &'a self,
130 execution: &E,
131 ) -> Result<impl Iterator<Item = (&'a K, &'a V)> + 'a + use<'a, K, V, W, E>, MemoryError> {
132 execution.charge_work(self.buckets()?)?;
133 Ok(self.values.iter())
134 }
135
136 fn buckets(&self) -> Result<u64, MemoryError> {
137 // Pinned hashbrown 0.17.1 raw::bucket_mask_to_capacity: tables with
138 // four/eight buckets keep one empty slot; larger tables use 7/8 of
139 // the buckets. Maximum capacity records each table immediately after
140 // reserve, before tombstones can reduce the public capacity value.
141 match self.maximum_capacity {
142 0 => Ok(0),
143 capacity @ 1..=7 => Ok(capacity as u64 + 1),
144 capacity => (capacity as u64 / 7)
145 .checked_mul(8)
146 .ok_or(MemoryError::WorkExceeded),
147 }
148 }
149
150 fn control_work(&self) -> Result<u64, MemoryError> {
151 let buckets = self.buckets()?;
152 // The pinned engine's narrowest control group is four bytes. Wider
153 // scalar/SIMD groups perform the same fixed group operations. One unit
154 // admits one group visit; triangular probing visits each group once.
155 Ok(buckets / 4)
156 }
157
158 fn rehash_work(&self) -> Result<u64, MemoryError> {
159 // reserve(1) can double the current buckets or rehash in place. Every
160 // retained key is hashed once; each slot search may visit all groups.
161 // In-place rehash additionally scans every bucket and swaps entries.
162 let buckets = self.buckets()?;
163 let groups = buckets.checked_mul(2).ok_or(MemoryError::WorkExceeded)? / 4;
164 let moves = (core::mem::size_of::<(K, V)>() as u64)
165 .checked_mul(2)
166 .ok_or(MemoryError::WorkExceeded)?;
167 groups
168 .checked_add(moves)
169 .and_then(|per_entry| per_entry.checked_mul(self.values.len() as u64))
170 .and_then(|work| work.checked_add(self.key_work))
171 .and_then(|work| {
172 buckets
173 .checked_mul(2)
174 .and_then(|scan| work.checked_add(scan))
175 })
176 .ok_or(MemoryError::WorkExceeded)
177 }
178
179 fn hash_key<E: Work>(&self, key: &K, execution: &E) -> Result<u64, MemoryError> {
180 let work = key
181 .hash_work()?
182 .checked_add(self.control_work()?)
183 .ok_or(MemoryError::WorkExceeded)?;
184 execution.charge_work(work)?;
185 Ok(self.values.hasher().hash_one(key))
186 }
187
188 /// Looks up a key after admitting its complete possible probe work.
189 /// The current execution domain pays independently of stored capacity.
190 ///
191 /// # Examples
192 /// ```
193 /// # extern crate alloc;
194 /// # use alloc::sync::Arc;
195 /// # use structured_asn1::memory::{Memory, MemoryAccount, MemoryError};
196 /// # let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
197 /// # let mut map = memory.map();
198 /// # map.try_insert(1usize, 42)?;
199 /// assert_eq!(map.get(&1, &memory)?, Some(&42));
200 /// assert_eq!(map.get(&2, &memory)?, None);
201 /// # Ok::<(), MemoryError>(())
202 /// ```
203 pub fn get(&self, key: &K, execution: &impl Work) -> Result<Option<&V>, MemoryError> {
204 let hash = self.hash_key(key, execution)?;
205 let mut failure = None;
206 let entry = self.values.raw_entry().from_hash(hash, |candidate| {
207 equivalent(execution, key, candidate, &mut failure)
208 });
209 if let Some(reason) = failure {
210 return Err(reason);
211 }
212 Ok(entry.map(|(_, value)| value))
213 }
214
215 /// Looks up an entry for mutation without exposing table growth.
216 /// Hashing, probing and equality use the supplied execution domain.
217 ///
218 /// # Examples
219 /// ```
220 /// # extern crate alloc;
221 /// # use alloc::sync::Arc;
222 /// # use structured_asn1::memory::{Memory, MemoryAccount, MemoryError};
223 /// # let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
224 /// # let mut map = memory.map();
225 /// # map.try_insert(1usize, 42)?;
226 /// if let Some(value) = map.get_mut(&1, &memory)? { *value = 7; }
227 /// assert_eq!(map.get(&1, &memory)?, Some(&7));
228 /// # Ok::<(), MemoryError>(())
229 /// ```
230 pub fn get_mut(
231 &mut self,
232 key: &K,
233 execution: &impl Work,
234 ) -> Result<Option<&mut V>, MemoryError> {
235 let hash = self.hash_key(key, execution)?;
236 let mut failure = None;
237 let entry = self.values.raw_entry_mut().from_hash(hash, |candidate| {
238 equivalent(execution, key, candidate, &mut failure)
239 });
240 if let Some(reason) = failure {
241 return Err(reason);
242 }
243 Ok(match entry {
244 RawEntryMut::Occupied(entry) => Some(entry.into_mut()),
245 RawEntryMut::Vacant(_) => None,
246 })
247 }
248
249 /// Inserts a value fallibly, or replaces the value for an existing key.
250 /// This storage mutation runs in the collection's original resource
251 /// domain, including hashing, rehashing, allocation and copying work.
252 /// To mutate under a different domain, build a new collection there from
253 /// explicitly admitted borrowed entries; retained capacity is not rebound.
254 pub fn try_insert(&mut self, key: K, value: V) -> Result<Option<V>, MemoryError> {
255 let hash = self.hash_key(&key, &self.execution)?;
256 let mut failure = None;
257 let entry = self.values.raw_entry_mut().from_hash(hash, |candidate| {
258 equivalent(&self.execution, &key, candidate, &mut failure)
259 });
260 if let Some(reason) = failure {
261 return Err(reason);
262 }
263 if let RawEntryMut::Occupied(entry) = entry {
264 let moves = (core::mem::size_of::<V>() as u64)
265 .checked_mul(2)
266 .ok_or(MemoryError::WorkExceeded)?;
267 self.execution.charge_work(moves)?;
268 return Ok(Some(core::mem::replace(entry.into_mut(), value)));
269 }
270 let key_work = self
271 .key_work
272 .checked_add(key.hash_work()?)
273 .ok_or(MemoryError::WorkExceeded)?;
274 if self.values.len() == self.values.capacity() {
275 // Include collision-dependent slot searches, not just key hashes.
276 self.execution.charge_work(self.rehash_work()?)?;
277 self.values.allocator().clear_failure();
278 self.values.try_reserve(1).map_err(|error| match error {
279 TryReserveError::CapacityOverflow => MemoryError::CapacityOverflow,
280 TryReserveError::AllocError { .. } => self.values.allocator().failure(),
281 })?;
282 self.maximum_capacity = self.maximum_capacity.max(self.values.capacity());
283 }
284 // Reuse the already computed hash after reserve. No key equality is
285 // needed: the first lookup established absence and resize preserves keys.
286 // raw_entry searches for a match; insert_hashed_nocheck subsequently
287 // searches for an insertion slot. Admit both control traversals.
288 self.execution.charge_work(
289 self.control_work()?
290 .checked_mul(2)
291 .and_then(|work| work.checked_add(core::mem::size_of::<(K, V)>() as u64))
292 .ok_or(MemoryError::WorkExceeded)?,
293 )?;
294 let RawEntryMut::Vacant(entry) = self.values.raw_entry_mut().from_hash(hash, |_| false)
295 else {
296 unreachable!("a predicate that always refuses cannot find an entry");
297 };
298 entry.insert_hashed_nocheck(hash, key, value);
299 self.key_work = key_work;
300 Ok(None)
301 }
302
303 /// Removes an entry, retaining table capacity and its storage charge.
304 /// The supplied execution domain pays for lookup and removal before mutation.
305 ///
306 /// # Examples
307 /// ```
308 /// # extern crate alloc;
309 /// # use alloc::sync::Arc;
310 /// # use structured_asn1::memory::{Memory, MemoryAccount, MemoryError};
311 /// # let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
312 /// # let mut map = memory.map();
313 /// # map.try_insert(1usize, 42)?;
314 /// let capacity_charge = memory.used();
315 /// assert_eq!(map.remove(&1, &memory)?, Some(42));
316 /// assert_eq!(memory.used(), capacity_charge);
317 /// # Ok::<(), MemoryError>(())
318 /// ```
319 pub fn remove(&mut self, key: &K, execution: &impl Work) -> Result<Option<V>, MemoryError> {
320 let hash = self.hash_key(key, execution)?;
321 let mut failure = None;
322 let entry = self.values.raw_entry_mut().from_hash(hash, |candidate| {
323 equivalent(execution, key, candidate, &mut failure)
324 });
325 if let Some(reason) = failure {
326 return Err(reason);
327 }
328 let entry = match entry {
329 RawEntryMut::Occupied(entry) => {
330 // Erase examines the adjacent control groups; returning the
331 // pair moves its storage. Admit both before changing the table.
332 let work = (core::mem::size_of::<(K, V)>() as u64)
333 .checked_mul(2)
334 .and_then(|work| work.checked_add(2))
335 .ok_or(MemoryError::WorkExceeded)?;
336 let removed_work = entry.key().hash_work()?;
337 execution.charge_work(work)?;
338 let (_, value) = entry.remove_entry();
339 self.key_work -= removed_work;
340 Some(value)
341 }
342 RawEntryMut::Vacant(_) => None,
343 };
344 Ok(entry)
345 }
346}
347
348/// A synthetic match stops hashbrown immediately on exhaustion. The caller
349/// checks failure before exposing or mutating the matched entry, so no false
350/// equality escapes and no expensive comparison runs after a refusal.
351fn equivalent<K: HashWork, W: Work>(
352 memory: &W,
353 query: &K,
354 candidate: &K,
355 failure: &mut Option<MemoryError>,
356) -> bool {
357 let cost = query.hash_work().and_then(|query| {
358 candidate.hash_work().and_then(|candidate| {
359 query
360 .checked_add(candidate)
361 .ok_or(MemoryError::WorkExceeded)
362 })
363 });
364 let admitted = cost.and_then(|cost| memory.charge_work(cost));
365 if let Err(reason) = admitted {
366 *failure = Some(reason);
367 return true;
368 }
369 query == candidate
370}