rune/core/gc/
heap.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
use super::{GcState, Trace};
use std::{
    alloc::Layout,
    cell::{Cell, UnsafeCell},
    fmt,
    mem::ManuallyDrop,
    ops::{Deref, DerefMut},
    ptr::NonNull,
};

union GcHeader {
    header: ManuallyDrop<HeaderData>,
    fwd_ptr: NonNull<u8>,
}

unsafe impl Send for GcHeader {}

impl GcHeader {
    const fn new(marked: bool) -> Self {
        GcHeader { header: ManuallyDrop::new(HeaderData::new(marked)) }
    }

    fn is_present(&self) -> bool {
        unsafe { self.header.is_present == HeaderData::PRESENT }
    }

    fn get_header(&self) -> Result<&HeaderData, NonNull<u8>> {
        if self.is_present() {
            Ok(unsafe { &*self.header })
        } else {
            Err(unsafe { self.fwd_ptr })
        }
    }
}

impl fmt::Debug for GcHeader {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.get_header() {
            Ok(header) => write!(f, "GcHeader {{ header: {:?} }}", header),
            Err(ptr) => write!(f, "GcHeader {{ fwd_ptr: {:?} }}", ptr),
        }
    }
}

// Layout if very important here, we need to make sure that the is_present bit
// is the first bit in the struct. That way we can use it to check if it is a
// header data or a forwarding pointer. This works because an object is always
// aligned to 8 bytes, so the first 3 bits will be 0 if it is a forwarding
// pointer.
#[repr(C, align(8))]
#[derive(Debug)]
struct HeaderData {
    is_present: u8,
    marked: Cell<bool>,
}

impl HeaderData {
    const PRESENT: u8 = 1;
    const fn new(marked: bool) -> Self {
        Self { is_present: Self::PRESENT, marked: Cell::new(marked) }
    }
}

/// A block of memory allocated on the heap that is managed by the garbage collector.
#[repr(C)]
#[derive(Debug)]
pub(crate) struct GcHeap<T: ?Sized> {
    header: UnsafeCell<GcHeader>,
    data: T,
}

unsafe impl<T: Sync> Sync for GcHeap<T> {}

impl<T> GcHeap<T> {
    pub(in crate::core) const fn new(data: T, constant: bool) -> Self {
        Self {
            // if the block is const, mark the object so it will not be traced
            header: UnsafeCell::new(GcHeader::new(constant)),
            data,
        }
    }

    /// Allocate a new object that is not managed by the garbage collector. This
    /// is used for "pure" storage of objects that are allocated at the start of
    /// the program and never freed. Only used for the Symbol map at the moment.
    pub(in crate::core) const fn new_pure(data: T) -> Self {
        Self::new(data, true)
    }

    fn header(&self) -> &GcHeader {
        unsafe { &*self.header.get() }
    }

    pub(in crate::core) fn forward(&self, fwd_ptr: NonNull<u8>) {
        let header = unsafe { &mut *self.header.get() };
        header.fwd_ptr = fwd_ptr;
    }

    pub(in crate::core) fn allocation_state(&self) -> AllocState {
        match self.header().get_header() {
            Ok(header) => {
                if header.marked.get() {
                    AllocState::Global
                } else {
                    AllocState::Unmoved
                }
            }
            Err(fwd) => AllocState::Forwarded(fwd),
        }
    }

    fn is_marked(&self) -> bool {
        self.header().get_header().unwrap().marked.get()
    }
}

pub(in crate::core) enum AllocState {
    Forwarded(NonNull<u8>),
    Global,
    Unmoved,
}

pub(in crate::core) trait Markable {
    type Value;
    fn move_value(&self, _to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
        None
    }
}

impl<'a, T: Markable<Value = NonNull<T>>> Markable for &'a T {
    type Value = &'a T;

    fn move_value(&self, to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
        let val = (*self).move_value(to_space);
        val.map(|(ptr, moved)| (unsafe { ptr.as_ref() }, moved))
    }
}

#[macro_export]
macro_rules! NewtypeMarkable {
    (() $vis:vis struct $name:ident $($tail:tt)+) => {
        impl $crate::core::gc::Markable for $name {
            type Value = std::ptr::NonNull<Self>;

            fn move_value(&self, to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
                match self.0.move_value(to_space) {
                    Some((ptr, moved)) => Some((ptr.cast::<Self>(), moved)),
                    None => None,
                }
            }
        }
    };
}

impl<T> Markable for GcHeap<T> {
    type Value = NonNull<Self>;

    fn move_value(&self, to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
        use std::ptr;
        match self.header().get_header() {
            Ok(header) => {
                if header.marked.get() {
                    // The object is global and should not be moved
                    return None;
                }
                // move to to_space
                let layout = Layout::for_value(self);
                let to_ptr = to_space.alloc_layout(layout);
                unsafe {
                    let src = ptr::from_ref(self);
                    let dst = to_ptr.cast::<Self>().as_ptr();
                    ptr::copy_nonoverlapping(src, dst, 1);
                }
                // write forwarding pointer
                self.forward(to_ptr);
                // return new address
                Some((to_ptr.cast::<Self>(), true))
            }
            Err(fwd) => Some((fwd.cast::<Self>(), false)),
        }
    }
}

impl<T: Trace> Trace for GcHeap<T> {
    fn trace(&self, state: &mut GcState) {
        // This is type is only one that contains mark bits, so it is where we
        // actually mark the object
        if !self.is_marked() {
            self.data.trace(state);
        }
    }
}

impl<T: PartialEq> PartialEq for GcHeap<T> {
    fn eq(&self, other: &Self) -> bool {
        self.data == other.data
    }
}

impl<T: PartialEq> PartialEq<T> for GcHeap<T> {
    fn eq(&self, other: &T) -> bool {
        self.data == *other
    }
}

impl<T: Eq> Eq for GcHeap<T> {}

impl<T> Deref for GcHeap<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.data
    }
}

impl<T> DerefMut for GcHeap<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.data
    }
}

impl<T: fmt::Display> fmt::Display for GcHeap<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", &self.data)
    }
}