Skip to main content

boa_gc/pointers/
weak_map.rs

1// Implementation taken partly from https://docs.rs/hashbrown/0.14.0/src/hashbrown/lib.rs.html,
2// but with some adjustments to use `Ephemeron<K,V>` instead of `(K,V)`
3
4use hashbrown::{
5    DefaultHashBuilder, HashTable, TryReserveError,
6    hash_table::{Entry as RawEntry, Iter as RawIter},
7};
8
9use crate::{Allocator, Ephemeron, Finalize, Gc, GcRef, GcRefCell, Trace, custom_trace};
10use std::{fmt, hash::BuildHasher, marker::PhantomData};
11
12/// A map that holds weak references to its keys and is traced by the garbage collector.
13#[derive(Clone, Debug, Default, Finalize)]
14pub struct WeakMap<K: Trace + ?Sized + 'static, V: Trace + 'static> {
15    pub(crate) inner: Gc<GcRefCell<RawWeakMap<K, V>>>,
16}
17
18unsafe impl<K: Trace + ?Sized + 'static, V: Trace + 'static> Trace for WeakMap<K, V> {
19    custom_trace!(this, mark, {
20        mark(&this.inner);
21    });
22}
23
24impl<K: Trace + ?Sized, V: Trace> WeakMap<K, V> {
25    /// Creates a new `WeakMap`.
26    #[must_use]
27    #[inline]
28    pub fn new() -> Self {
29        Allocator::alloc_weak_map()
30    }
31
32    /// Inserts a key-value pair into the map.
33    #[inline]
34    pub fn insert(&mut self, key: &Gc<K>, value: V) {
35        self.inner.borrow_mut().insert(key, value);
36    }
37
38    /// Removes a key from the map, returning `true` if the key was previously in
39    /// the map.
40    #[inline]
41    pub fn remove(&mut self, key: &Gc<K>) -> bool {
42        self.inner.borrow_mut().remove(key)
43    }
44
45    /// Returns `true` if the map contains a value for the specified key.
46    #[must_use]
47    #[inline]
48    pub fn contains_key(&self, key: &Gc<K>) -> bool {
49        self.inner.borrow().contains_key(key)
50    }
51
52    /// Returns a reference to the ephemeron corresponding to the key.
53    #[must_use]
54    #[inline]
55    pub fn get<'a>(&'a self, key: &Gc<K>) -> Option<GcRef<'a, Ephemeron<K, V>>> {
56        GcRef::try_map(self.inner.borrow(), |inner| inner.get(key))
57    }
58}
59
60/// A hash map where the bucket type is an <code>[Ephemeron]\<K, V\></code>.
61///
62/// This data structure allows associating a <code>[Gc]\<K\></code> with a value `V` that will be
63/// invalidated when the `Gc<K>` gets collected. In other words, all key entries on the map are weakly
64/// held.
65pub(crate) struct RawWeakMap<K, V, S = DefaultHashBuilder>
66where
67    K: Trace + ?Sized + 'static,
68    V: Trace + 'static,
69{
70    hash_builder: S,
71    table: HashTable<Ephemeron<K, V>>,
72}
73
74impl<K, V, S> Finalize for RawWeakMap<K, V, S>
75where
76    K: Trace + ?Sized + 'static,
77    V: Trace + 'static,
78{
79}
80
81// SAFETY: The implementation correctly marks all ephemerons inside the map.
82unsafe impl<K, V, S> Trace for RawWeakMap<K, V, S>
83where
84    K: Trace + ?Sized + 'static,
85    V: Trace + 'static,
86{
87    custom_trace!(this, mark, {
88        for eph in this.iter() {
89            mark(eph);
90        }
91    });
92}
93
94impl<K, V, S> Default for RawWeakMap<K, V, S>
95where
96    S: Default,
97    K: Trace + ?Sized + 'static,
98    V: Trace + 'static,
99{
100    fn default() -> Self {
101        Self::with_hasher(Default::default())
102    }
103}
104
105impl<K, V> RawWeakMap<K, V, DefaultHashBuilder>
106where
107    K: Trace + ?Sized + 'static,
108    V: Trace + 'static,
109{
110    /// Creates an empty `RawWeakMap`.
111    ///
112    /// The map is initially created with a capacity of 0, so it will not allocate until it
113    /// is first inserted into.
114    pub(crate) fn new() -> Self {
115        Self::default()
116    }
117
118    /// Creates an empty `RawWeakMap` with the specified capacity.
119    ///
120    /// The map will be able to hold at least `capacity` elements without reallocating.
121    /// If `capacity` is 0, the map will not allocate.
122    #[allow(unused)]
123    pub(crate) fn with_capacity(capacity: usize) -> Self {
124        Self::with_capacity_and_hasher(capacity, DefaultHashBuilder::default())
125    }
126}
127
128impl<K, V, S> RawWeakMap<K, V, S>
129where
130    K: Trace + ?Sized + 'static,
131    V: Trace + 'static,
132{
133    /// Creates an empty `RawWeakMap` which will use the given hash builder to hash
134    /// keys.
135    ///
136    /// The map is initially created with a capacity of 0, so it will not allocate until it is first
137    /// inserted into.
138    pub(crate) const fn with_hasher(hash_builder: S) -> Self {
139        Self {
140            hash_builder,
141            table: HashTable::new(),
142        }
143    }
144
145    /// Creates an empty `RawWeakMap` with the specified capacity, using `hash_builder`
146    /// to hash the keys.
147    ///
148    /// The map will be able to hold at least `capacity` elements without reallocating.
149    /// If `capacity` is 0, the map will not allocate.
150    pub(crate) fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
151        Self {
152            hash_builder,
153            table: HashTable::with_capacity(capacity),
154        }
155    }
156
157    /// Returns a reference to the map's [`BuildHasher`].
158    #[allow(unused)]
159    pub(crate) const fn hasher(&self) -> &S {
160        &self.hash_builder
161    }
162
163    /// Returns the number of elements the map can hold without reallocating.
164    ///
165    /// This number is a lower bound; the map might be able to hold more, but is guaranteed to be
166    /// able to hold at least this many.
167    #[allow(unused)]
168    pub(crate) fn capacity(&self) -> usize {
169        self.table.capacity()
170    }
171
172    /// An iterator visiting all entries in arbitrary order.
173    /// The iterator element type is <code>[Ephemeron]<K, V></code>.
174    pub(crate) fn iter(&self) -> Iter<'_, K, V> {
175        Iter {
176            inner: self.table.iter(),
177            marker: PhantomData,
178        }
179    }
180
181    /// Returns the number of elements in the map.
182    ///
183    /// This is an upper bound; the map might contain some expired keys which haven't been
184    /// removed.
185    #[allow(unused)]
186    pub(crate) fn len(&self) -> usize {
187        self.table.len()
188    }
189
190    /// Returns `true` if the map contains no elements.
191    ///
192    /// This might return `false` if the map has expired keys that are still pending to be
193    /// cleaned up.
194    #[allow(unused)]
195    pub(crate) fn is_empty(&self) -> bool {
196        self.len() == 0
197    }
198
199    /// Retains only the elements specified by the predicate. Keeps the
200    /// allocated memory for reuse.
201    ///
202    /// In other words, remove all ephemerons <code>[Ephemeron]<K, V></code> such that
203    /// `f(&eph)` returns `false`.
204    /// The elements are visited in unsorted (and unspecified) order.
205    pub(crate) fn retain<F>(&mut self, mut f: F)
206    where
207        F: FnMut(&Ephemeron<K, V>) -> bool,
208    {
209        // SAFETY:
210        // - `item` is only used internally, which means it outlives self.
211        // - `item` pointer is not used after the call to `erase`.
212        self.table.retain(|item| f(item));
213    }
214
215    /// Clears the map, removing all key-value pairs. Keeps the allocated memory
216    /// for reuse.
217    #[allow(unused)]
218    pub(crate) fn clear(&mut self) {
219        self.table.clear();
220    }
221}
222
223impl<K, V, S> RawWeakMap<K, V, S>
224where
225    K: Trace + ?Sized + 'static,
226    V: Trace + 'static,
227    S: BuildHasher,
228{
229    /// Reserves capacity for at least `additional` more elements to be inserted
230    /// in the `RawWeakMap`. The collection may reserve more space to avoid
231    /// frequent reallocations.
232    ///
233    /// # Panics
234    ///
235    /// Panics if the new capacity exceeds [`isize::MAX`] bytes and [`abort`](std::process::abort)
236    /// the program in case of allocation error. Use [`try_reserve`](RawWeakMap::try_reserve) instead
237    /// if you want to handle memory allocation failure.
238    #[allow(unused)]
239    pub(crate) fn reserve(&mut self, additional: usize) {
240        self.table
241            .reserve(additional, make_hasher(&self.hash_builder));
242    }
243
244    /// Tries to reserve capacity for at least `additional` more elements to be inserted
245    /// in the given `RawWeakMap<K,V>`. The collection may reserve more space to avoid
246    /// frequent reallocations.
247    ///
248    /// # Errors
249    ///
250    /// If the capacity overflows, or the allocator reports a failure, then an error
251    /// is returned.
252    #[allow(unused)]
253    pub(crate) fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
254        self.table
255            .try_reserve(additional, make_hasher(&self.hash_builder))
256    }
257
258    /// Shrinks the capacity of the map as much as possible. It will drop
259    /// down as much as possible while maintaining the internal rules
260    /// and possibly leaving some space in accordance with the resize policy.
261    #[allow(unused)]
262    pub(crate) fn shrink_to_fit(&mut self) {
263        self.table
264            .shrink_to(0, make_hasher::<_, V, S>(&self.hash_builder));
265    }
266
267    /// Shrinks the capacity of the map with a lower limit. It will drop
268    /// down no lower than the supplied limit while maintaining the internal rules
269    /// and possibly leaving some space in accordance with the resize policy.
270    ///
271    /// This function does nothing if the current capacity is smaller than the
272    /// supplied minimum capacity.
273    #[allow(unused)]
274    pub(crate) fn shrink_to(&mut self, min_capacity: usize) {
275        self.table
276            .shrink_to(min_capacity, make_hasher::<_, V, S>(&self.hash_builder));
277    }
278
279    /// Returns the ephemeron corresponding to the supplied key.
280    pub(crate) fn get(&self, k: &Gc<K>) -> Option<&Ephemeron<K, V>> {
281        if self.table.is_empty() {
282            None
283        } else {
284            let hash = make_hash_from_gc(&self.hash_builder, k);
285            self.table.find(hash, equivalent_key(k))
286        }
287    }
288
289    /// Returns `true` if the map contains a value for the specified key.
290    pub(crate) fn contains_key(&self, k: &Gc<K>) -> bool {
291        self.get(k).is_some()
292    }
293
294    // Inserts a key-value pair into the map.
295    ///
296    /// If the map did not have this key present, [`None`] is returned.
297    ///
298    /// If the map did have this key present, the value is updated, and the old
299    /// value is returned. The key is not updated.
300    pub(crate) fn insert(&mut self, k: &Gc<K>, v: V) -> Option<Ephemeron<K, V>> {
301        let hash = make_hash_from_gc(&self.hash_builder, k);
302        let hasher = make_hasher(&self.hash_builder);
303        let entry = self.table.entry(hash, equivalent_key(k), hasher);
304        let (old, slot) = match entry {
305            RawEntry::Occupied(occupied_entry) => {
306                let (v, slot) = occupied_entry.remove();
307                (Some(v), slot)
308            }
309            RawEntry::Vacant(vacant_entry) => (None, vacant_entry),
310        };
311
312        slot.insert(Ephemeron::new(k, v));
313        old
314    }
315
316    /// Removes a key from the map, returning `true` if the key
317    /// was previously in the map. Keeps the allocated memory for reuse.
318    pub(crate) fn remove(&mut self, k: &Gc<K>) -> bool {
319        let hash = make_hash_from_gc(&self.hash_builder, k);
320        if let Ok(entry) = self.table.find_entry(hash, equivalent_key(k)) {
321            entry.remove();
322            true
323        } else {
324            false
325        }
326    }
327
328    /// Clears all the expired keys in the map.
329    pub(crate) fn clear_expired(&mut self) {
330        self.retain(|eph| eph.value().is_some());
331    }
332}
333
334pub(crate) struct Iter<'a, K, V>
335where
336    K: Trace + ?Sized + 'static,
337    V: Trace + 'static,
338{
339    inner: RawIter<'a, Ephemeron<K, V>>,
340    marker: PhantomData<&'a Ephemeron<K, V>>,
341}
342
343impl<K, V> Clone for Iter<'_, K, V>
344where
345    K: Trace + ?Sized + 'static,
346    V: Trace + 'static,
347{
348    #[inline]
349    fn clone(&self) -> Self {
350        Iter {
351            inner: self.inner.clone(),
352            marker: PhantomData,
353        }
354    }
355}
356
357impl<K, V> fmt::Debug for Iter<'_, K, V>
358where
359    K: Trace + ?Sized + 'static + fmt::Debug,
360    V: Trace + 'static + fmt::Debug,
361{
362    #[inline]
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        f.debug_list().entries(self.clone()).finish()
365    }
366}
367
368impl<'a, K, V> Iterator for Iter<'a, K, V>
369where
370    K: Trace + ?Sized + 'static,
371    V: Trace + 'static,
372{
373    type Item = &'a Ephemeron<K, V>;
374
375    #[inline]
376    fn next(&mut self) -> Option<Self::Item> {
377        self.inner.next()
378    }
379
380    #[inline]
381    fn size_hint(&self) -> (usize, Option<usize>) {
382        self.inner.size_hint()
383    }
384}
385
386impl<K, V, S> fmt::Debug for RawWeakMap<K, V, S>
387where
388    K: fmt::Debug + ?Sized + Trace + Finalize,
389    V: fmt::Debug + Trace + Finalize,
390{
391    #[inline]
392    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393        self.iter().fmt(f)
394    }
395}
396
397fn make_hasher<K, V, S>(hash_builder: &S) -> impl Fn(&Ephemeron<K, V>) -> u64 + '_
398where
399    S: BuildHasher,
400    K: Trace + ?Sized + 'static,
401    V: Trace + 'static,
402{
403    move |val| make_hash_from_eph::<K, V, S>(hash_builder, val)
404}
405
406fn make_hash_from_eph<K, V, S>(hash_builder: &S, eph: &Ephemeron<K, V>) -> u64
407where
408    S: BuildHasher,
409    K: Trace + ?Sized + 'static,
410    V: Trace + 'static,
411{
412    use std::hash::Hasher;
413    let mut state = hash_builder.build_hasher();
414    // TODO: Is this true for custom hashers? if not, rewrite `key` to be safe.
415    // SAFETY: The return value of `key` is only used to hash it, which
416    // cannot trigger a garbage collection,
417    unsafe {
418        if let Some(val) = eph.inner().key() {
419            std::ptr::hash(val, &mut state);
420        } else {
421            std::ptr::hash(eph.inner_ptr().as_ptr(), &mut state);
422        }
423    }
424    state.finish()
425}
426
427fn make_hash_from_gc<K, S>(hash_builder: &S, gc: &Gc<K>) -> u64
428where
429    S: BuildHasher,
430    K: Trace + ?Sized + 'static,
431{
432    use std::hash::Hasher;
433    let mut state = hash_builder.build_hasher();
434    std::ptr::hash(gc.inner_ptr().as_ptr(), &mut state);
435    state.finish()
436}
437
438fn equivalent_key<K, V>(k: &Gc<K>) -> impl Fn(&Ephemeron<K, V>) -> bool + '_
439where
440    K: Trace + ?Sized + 'static,
441    V: Trace + 'static,
442{
443    // SAFETY: The return value of `key` is only used inside eq, which
444    // cannot trigger a garbage collection.
445    move |eph| unsafe {
446        eph.inner().key().is_some_and(|val| {
447            let val: *const _ = val;
448            std::ptr::eq(val, k.inner_ptr().as_ptr())
449        })
450    }
451}