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}