rune/core/gc/
heap.rs

1use super::{GcState, Trace};
2use std::{
3    alloc::Layout,
4    cell::{Cell, UnsafeCell},
5    fmt,
6    mem::ManuallyDrop,
7    ops::{Deref, DerefMut},
8    ptr::NonNull,
9};
10
11union GcHeader {
12    header: ManuallyDrop<HeaderData>,
13    fwd_ptr: NonNull<u8>,
14}
15
16unsafe impl Send for GcHeader {}
17
18impl GcHeader {
19    const fn new(marked: bool) -> Self {
20        GcHeader { header: ManuallyDrop::new(HeaderData::new(marked)) }
21    }
22
23    fn is_present(&self) -> bool {
24        unsafe { self.header.is_present == HeaderData::PRESENT }
25    }
26
27    fn get_header(&self) -> Result<&HeaderData, NonNull<u8>> {
28        if self.is_present() {
29            Ok(unsafe { &*self.header })
30        } else {
31            Err(unsafe { self.fwd_ptr })
32        }
33    }
34}
35
36impl fmt::Debug for GcHeader {
37    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38        match self.get_header() {
39            Ok(header) => write!(f, "GcHeader {{ header: {header:?} }}"),
40            Err(ptr) => write!(f, "GcHeader {{ fwd_ptr: {ptr:?} }}"),
41        }
42    }
43}
44
45// Layout if very important here, we need to make sure that the is_present bit
46// is the first bit in the struct. That way we can use it to check if it is a
47// header data or a forwarding pointer. This works because an object is always
48// aligned to 8 bytes, so the first 3 bits will be 0 if it is a forwarding
49// pointer.
50#[repr(C, align(8))]
51#[derive(Debug)]
52struct HeaderData {
53    is_present: u8,
54    marked: Cell<bool>,
55}
56
57impl HeaderData {
58    const PRESENT: u8 = 1;
59    const fn new(marked: bool) -> Self {
60        Self { is_present: Self::PRESENT, marked: Cell::new(marked) }
61    }
62}
63
64/// A block of memory allocated on the heap that is managed by the garbage collector.
65#[repr(C)]
66#[derive(Debug)]
67pub(crate) struct GcHeap<T: ?Sized> {
68    header: UnsafeCell<GcHeader>,
69    data: T,
70}
71
72unsafe impl<T: Sync> Sync for GcHeap<T> {}
73
74impl<T> GcHeap<T> {
75    pub(in crate::core) const fn new(data: T, constant: bool) -> Self {
76        Self {
77            // if the block is const, mark the object so it will not be traced
78            header: UnsafeCell::new(GcHeader::new(constant)),
79            data,
80        }
81    }
82
83    /// Allocate a new object that is not managed by the garbage collector. This
84    /// is used for "pure" storage of objects that are allocated at the start of
85    /// the program and never freed. Only used for the Symbol map at the moment.
86    pub(in crate::core) const fn new_pure(data: T) -> Self {
87        Self::new(data, true)
88    }
89
90    fn header(&self) -> &GcHeader {
91        unsafe { &*self.header.get() }
92    }
93
94    pub(in crate::core) fn forward(&self, fwd_ptr: NonNull<u8>) {
95        let header = unsafe { &mut *self.header.get() };
96        header.fwd_ptr = fwd_ptr;
97    }
98
99    pub(in crate::core) fn allocation_state(&self) -> AllocState {
100        match self.header().get_header() {
101            Ok(header) => {
102                if header.marked.get() {
103                    AllocState::Global
104                } else {
105                    AllocState::Unmoved
106                }
107            }
108            Err(fwd) => AllocState::Forwarded(fwd),
109        }
110    }
111
112    fn is_marked(&self) -> bool {
113        self.header().get_header().unwrap().marked.get()
114    }
115}
116
117pub(in crate::core) enum AllocState {
118    Forwarded(NonNull<u8>),
119    Global,
120    Unmoved,
121}
122
123/// This trait is defined for types that can be moved by the GC. For types that
124/// don't have any other pointers in them, they can use the implementation of
125/// this trait on `GcHeap`, which will just copy the object.
126pub(in crate::core) trait GcMoveable {
127    type Value;
128    fn move_value(&self, _to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
129        None
130    }
131}
132
133impl<'a, T: GcMoveable<Value = NonNull<T>>> GcMoveable for &'a T {
134    type Value = &'a T;
135
136    fn move_value(&self, to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
137        let val = (*self).move_value(to_space);
138        val.map(|(ptr, moved)| (unsafe { ptr.as_ref() }, moved))
139    }
140}
141
142#[macro_export]
143macro_rules! derive_GcMoveable {
144    ($name:ident) => {
145        impl $crate::core::gc::GcMoveable for $name {
146            type Value = std::ptr::NonNull<Self>;
147
148            fn move_value(&self, to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
149                match self.0.move_value(to_space) {
150                    Some((ptr, moved)) => Some((ptr.cast::<Self>(), moved)),
151                    None => None,
152                }
153            }
154        }
155    };
156}
157
158impl<T> GcMoveable for GcHeap<T> {
159    type Value = NonNull<Self>;
160
161    fn move_value(&self, to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
162        use std::ptr;
163        match self.header().get_header() {
164            Ok(header) => {
165                if header.marked.get() {
166                    // The object is global and should not be moved
167                    return None;
168                }
169                // move to to_space
170                let layout = Layout::for_value(self);
171                let to_ptr = to_space.alloc_layout(layout);
172                unsafe {
173                    let src = ptr::from_ref(self);
174                    let dst = to_ptr.cast::<Self>().as_ptr();
175                    ptr::copy_nonoverlapping(src, dst, 1);
176                }
177                // write forwarding pointer
178                self.forward(to_ptr);
179                // return new address
180                Some((to_ptr.cast::<Self>(), true))
181            }
182            Err(fwd) => Some((fwd.cast::<Self>(), false)),
183        }
184    }
185}
186
187impl<T: Trace> Trace for GcHeap<T> {
188    fn trace(&self, state: &mut GcState) {
189        // This is type is only one that contains mark bits, so it is where we
190        // actually mark the object
191        if !self.is_marked() {
192            self.data.trace(state);
193        }
194    }
195}
196
197impl<T: PartialEq> PartialEq for GcHeap<T> {
198    fn eq(&self, other: &Self) -> bool {
199        self.data == other.data
200    }
201}
202
203impl<T: PartialEq> PartialEq<T> for GcHeap<T> {
204    fn eq(&self, other: &T) -> bool {
205        self.data == *other
206    }
207}
208
209impl<T: Eq> Eq for GcHeap<T> {}
210
211impl<T> Deref for GcHeap<T> {
212    type Target = T;
213
214    fn deref(&self) -> &Self::Target {
215        &self.data
216    }
217}
218
219impl<T> DerefMut for GcHeap<T> {
220    fn deref_mut(&mut self) -> &mut Self::Target {
221        &mut self.data
222    }
223}
224
225impl<T: fmt::Display> fmt::Display for GcHeap<T> {
226    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
227        write!(f, "{}", &self.data)
228    }
229}