Skip to main content

boa_gc/pointers/
ephemeron.rs

1#![allow(clippy::doc_link_with_quotes)]
2
3use crate::{
4    Allocator, Gc, Tracer, finalizer_safe,
5    internals::EphemeronBox,
6    trace::{Finalize, Trace},
7};
8use std::ptr::NonNull;
9
10/// A key-value pair where the value becomes unaccesible when the key is garbage collected.
11///
12/// You can read more about ephemerons on:
13/// - Racket's page about [**ephemerons**][eph], which gives a brief overview.
14/// - Barry Hayes' paper ["_Ephemerons_: a new finalization mechanism"][acm] which explains the topic
15///   in full detail.
16///
17///
18/// [eph]: https://docs.racket-lang.org/reference/ephemerons.html
19/// [acm]: https://dl.acm.org/doi/10.1145/263700.263733
20#[derive(Debug)]
21pub struct Ephemeron<K: Trace + ?Sized + 'static, V: Trace + 'static> {
22    inner_ptr: NonNull<EphemeronBox<K, V>>,
23}
24
25impl<K: Trace + ?Sized, V: Trace + Clone> Ephemeron<K, V> {
26    /// Gets the stored value of this `Ephemeron`, or `None` if the key was already garbage collected.
27    ///
28    /// This needs to return a clone of the value because holding a reference to it between
29    /// garbage collection passes could drop the underlying allocation, causing an Use After Free.
30    #[must_use]
31    pub fn value(&self) -> Option<V> {
32        // SAFETY: this is safe because `Ephemeron` is tracked to always point to a valid pointer
33        // `inner_ptr`.
34        unsafe { self.inner_ptr.as_ref().value().cloned() }
35    }
36
37    /// Gets the stored key of this `Ephemeron`, or `None` if the key was already garbage collected.
38    #[inline]
39    #[must_use]
40    pub fn key(&self) -> Option<Gc<K>> {
41        // SAFETY: this is safe because `Ephemeron` is tracked to always point to a valid pointer
42        // `inner_ptr`.
43        let key_ptr = unsafe { self.inner_ptr.as_ref().key_ptr() }?;
44
45        // SAFETY: Returned pointer is valid, so this is safe.
46        unsafe {
47            key_ptr.as_ref().inc_ref_count();
48        }
49
50        // SAFETY: The gc pointer's reference count has been incremented, so this is safe.
51        Some(unsafe { Gc::from_raw(key_ptr) })
52    }
53
54    /// Checks if the [`Ephemeron`] has a value.
55    #[must_use]
56    pub fn has_value(&self) -> bool {
57        // SAFETY: this is safe because `Ephemeron` is tracked to always point to a valid pointer
58        // `inner_ptr`.
59        unsafe { self.inner_ptr.as_ref().value().is_some() }
60    }
61}
62
63impl<K: Trace + ?Sized, V: Trace> Ephemeron<K, V> {
64    /// Creates a new `Ephemeron`.
65    #[must_use]
66    pub fn new(key: &Gc<K>, value: V) -> Self {
67        let inner_ptr = Allocator::alloc_ephemeron(EphemeronBox::new(key, value));
68        Self { inner_ptr }
69    }
70
71    /// Returns `true` if the two `Ephemeron`s point to the same allocation.
72    #[must_use]
73    pub fn ptr_eq(this: &Self, other: &Self) -> bool {
74        std::ptr::addr_eq(this.inner(), other.inner())
75    }
76
77    pub(crate) fn inner_ptr(&self) -> NonNull<EphemeronBox<K, V>> {
78        assert!(finalizer_safe());
79        self.inner_ptr
80    }
81
82    pub(crate) fn inner(&self) -> &EphemeronBox<K, V> {
83        // SAFETY: Please see Gc::inner_ptr()
84        unsafe { self.inner_ptr().as_ref() }
85    }
86
87    /// Constructs an `Ephemeron<K, V>` from a raw pointer.
88    ///
89    /// # Safety
90    ///
91    /// This function is unsafe because improper use may lead to memory corruption, double-free,
92    /// or misbehaviour of the garbage collector.
93    #[must_use]
94    pub(crate) const unsafe fn from_raw(inner_ptr: NonNull<EphemeronBox<K, V>>) -> Self {
95        Self { inner_ptr }
96    }
97}
98
99impl<K: Trace + ?Sized, V: Trace> Finalize for Ephemeron<K, V> {
100    fn finalize(&self) {
101        // SAFETY: inner_ptr should be alive when calling finalize.
102        // We don't call inner_ptr() to avoid overhead of calling finalizer_safe().
103        unsafe {
104            self.inner_ptr.as_ref().dec_ref_count();
105        }
106    }
107}
108
109// SAFETY: `Ephemeron`s trace implementation only marks its inner box because we want to stop
110// tracing through weakly held pointers.
111unsafe impl<K: Trace + ?Sized, V: Trace> Trace for Ephemeron<K, V> {
112    unsafe fn trace(&self, _tracer: &mut Tracer) {
113        // SAFETY: We need to mark the inner box of the `Ephemeron` since it is reachable
114        // from a root and this means it cannot be dropped.
115        unsafe {
116            self.inner().mark();
117        }
118    }
119
120    unsafe fn trace_non_roots(&self) {
121        self.inner().inc_non_root_count();
122    }
123
124    fn run_finalizer(&self) {
125        Finalize::finalize(self);
126    }
127}
128
129impl<K: Trace + ?Sized, V: Trace> Clone for Ephemeron<K, V> {
130    fn clone(&self) -> Self {
131        let ptr = self.inner_ptr();
132        self.inner().inc_ref_count();
133        // SAFETY: `&self` is a valid Ephemeron pointer.
134        unsafe { Self::from_raw(ptr) }
135    }
136}
137
138impl<K: Trace + ?Sized, V: Trace> Drop for Ephemeron<K, V> {
139    fn drop(&mut self) {
140        if finalizer_safe() {
141            Finalize::finalize(self);
142        }
143    }
144}