aiscript_arena/
barrier.rs

1//! Write barrier management.
2
3use core::mem;
4use core::ops::{Deref, DerefMut};
5
6#[cfg(doc)]
7use crate::Gc;
8
9/// An (interiorly-)mutable reference inside a GC'd object graph.
10///
11/// This type can only exist behind a reference; it is typically obtained by calling
12/// [`Gc::write`] on a [`Gc`] pointer or by using the [`field!`] projection macro
13/// on a pre-existing `&Write<T>`.
14#[non_exhaustive]
15#[repr(transparent)]
16pub struct Write<T: ?Sized> {
17    // Public so that the `field!` macro can pattern-match on it; the `non_exhaustive` attribute
18    // prevents 3rd-party code from instanciating the struct directly.
19    #[doc(hidden)]
20    pub __inner: T,
21}
22
23impl<T: ?Sized> Deref for Write<T> {
24    type Target = T;
25
26    #[inline(always)]
27    fn deref(&self) -> &Self::Target {
28        &self.__inner
29    }
30}
31
32impl<T: ?Sized> DerefMut for Write<T> {
33    #[inline(always)]
34    fn deref_mut(&mut self) -> &mut Self::Target {
35        &mut self.__inner
36    }
37}
38
39impl<T: ?Sized> Write<T> {
40    /// Asserts that the given reference can be safely written to.
41    ///
42    /// # Safety
43    /// In order to maintain the invariants of the garbage collector, no new [`Gc`] pointers
44    /// may be adopted by the referenced value as a result of the interior mutability enabled
45    /// by this wrapper, unless [`Gc::write`] is invoked manually on the parent [`Gc`]
46    /// pointer during the current arena callback.
47    #[inline(always)]
48    pub unsafe fn assume(v: &T) -> &Self {
49        unsafe {
50            // SAFETY: `Self` is `repr(transparent)`.
51            mem::transmute(v)
52        }
53    }
54
55    /// Gets a writable reference to non-GC'd data.
56    ///
57    /// This is safe, as `'static` types can never hold [`Gc`] pointers.
58    #[inline]
59    pub fn from_static(v: &T) -> &Self
60    where
61        T: 'static,
62    {
63        // SAFETY: `Self` is `repr(transparent)`.
64        unsafe { mem::transmute(v) }
65    }
66
67    /// Gets a writable reference from a `&mut T`.
68    ///
69    /// This is safe, as exclusive access already implies writability.
70    #[inline]
71    pub fn from_mut(v: &mut T) -> &mut Self {
72        // SAFETY: `Self` is `repr(transparent)`.
73        unsafe { mem::transmute(v) }
74    }
75
76    /// Implementation detail of `write_field!`; same safety requirements as `assume`.
77    #[inline(always)]
78    #[doc(hidden)]
79    pub unsafe fn __from_ref_and_ptr(v: &T, _: *const T) -> &Self {
80        unsafe {
81            // SAFETY: `Self` is `repr(transparent)`.
82            mem::transmute(v)
83        }
84    }
85
86    /// Unlocks the referenced value, providing full interior mutability.
87    #[inline]
88    pub fn unlock(&self) -> &T::Unlocked
89    where
90        T: Unlock,
91    {
92        // SAFETY: a `&Write<T>` implies that a write barrier was triggered on the parent `Gc`.
93        unsafe { self.__inner.unlock_unchecked() }
94    }
95}
96
97impl<T> Write<Option<T>> {
98    /// Converts from `&Write<Option<T>>` to `Option<&Write<T>>`.
99    #[inline(always)]
100    pub fn as_write(&self) -> Option<&Write<T>> {
101        // SAFETY: this is simple destructuring
102        unsafe {
103            match &self.__inner {
104                None => None,
105                Some(v) => Some(Write::assume(v)),
106            }
107        }
108    }
109}
110
111impl<T, E> Write<Result<T, E>> {
112    /// Converts from `&Write<Result<T, E>>` to `Result<&Write<T>, &Write<E>>`.
113    #[inline(always)]
114    pub fn as_write(&self) -> Result<&Write<T>, &Write<E>> {
115        // SAFETY: this is simple destructuring
116        unsafe {
117            match &self.__inner {
118                Ok(v) => Ok(Write::assume(v)),
119                Err(e) => Err(Write::assume(e)),
120            }
121        }
122    }
123}
124
125/// Types that support additional operations (typically, mutation) when behind a write barrier.
126pub trait Unlock {
127    /// This will typically be a cell-like type providing some sort of interior mutability.
128    type Unlocked: ?Sized;
129
130    /// Provides unsafe access to the unlocked type, *without* triggering a write barrier.
131    ///
132    /// # Safety
133    ///
134    /// In order to maintain the invariants of the garbage collector, no new `Gc` pointers
135    /// may be adopted by as a result of the interior mutability afforded by the unlocked value,
136    /// unless the write barrier for the containing `Gc` pointer is invoked manually before
137    /// collection is triggered.
138    unsafe fn unlock_unchecked(&self) -> &Self::Unlocked;
139}
140
141/// Macro for named field projection behind [`Write`] references.
142///
143/// # Usage
144///
145/// ```
146/// # use aiscript_arena::barrier::{field, Write};
147/// struct Container<T> {
148///     field: T,
149/// }
150///
151/// // fn project<T>(v: &Write<Container<T>>) -> &Write<T> {
152/// //    field!(v, Container, field)
153/// // }
154/// ```
155///
156/// # Limitations
157///
158/// This macro only support structs with named fields; tuples and enums aren't supported.
159#[doc(inline)]
160pub use crate::__field as field;
161
162// Actual macro item, hidden so that it doesn't show at the crate root.
163#[macro_export]
164#[doc(hidden)]
165macro_rules! __field {
166    ($value:expr, $type:path, $field:ident) => {
167        // SAFETY:
168        // For this to be sound, we need to prevent deref coercions from happening, as they may
169        // access nested `Gc` pointers, which would violate the write barrier invariant. This is
170        // guaranteed as follows:
171        // - the destructuring pattern, unlike a simple field access, cannot call `Deref`;
172        // - similarly, the `__from_ref_and_ptr` method takes both a reference (for the lifetime)
173        //   and a pointer, causing a compilation failure if the first argument was coerced.
174        match $value {
175            $crate::barrier::Write {
176                __inner: $type { ref $field, .. },
177                ..
178            } => unsafe { $crate::barrier::Write::__from_ref_and_ptr($field, $field as *const _) },
179        }
180    };
181}
182
183/// Shorthand for [`field!`]`(...).`[`unlock()`](Write::unlock).
184#[doc(inline)]
185pub use crate::__unlock as unlock;
186
187// Actual macro item, hidden so that it doesn't show at the crate root.
188#[macro_export]
189#[doc(hidden)]
190macro_rules! __unlock {
191    ($value:expr, $type:path, $field:ident) => {
192        $crate::barrier::field!($value, $type, $field).unlock()
193    };
194}