Skip to main content

catallaxy_runtime/
mutator.rs

1//! Per-thread runtime context: identity, allocation, and the handle stack that roots values held
2//! by native code across calls back into the interpreter.
3
4use std::sync::atomic::AtomicU64;
5use std::sync::Arc;
6
7// SAFETY: a Mutator is moved to the thread it serves before that thread runs.
8unsafe impl Send for Mutator {}
9
10use catallaxy_gc::{Header, LocalHeap, ObjState, ThreadRecord};
11use num_bigint::BigInt;
12use num_traits::ToPrimitive;
13
14use crate::class::{Class, BUILTINS};
15use crate::dict::DictTable;
16use crate::object::*;
17use crate::value::{fits_small, Value};
18
19/// A recoverable interruption of execution.
20#[derive(Debug)]
21pub enum Trap {
22    /// A panic: unwinds the current thread.
23    Panic(String),
24    /// Non-local control flow (break/return out of a block) unwinding to an interpreter frame.
25    Unwind { frame: u64, kind: u8, value: Value },
26    /// `exit(code)`.
27    Exit(i32),
28}
29
30pub type RtResult<T> = Result<T, Trap>;
31
32pub fn panic<T>(msg: impl Into<String>) -> RtResult<T> {
33    Err(Trap::Panic(msg.into()))
34}
35
36pub struct Mutator {
37    pub tid: u16,
38    pub rec: Arc<ThreadRecord>,
39    /// The thread's allocator. Shared with the collector (which retires its pages while the
40    /// thread is stopped), so it is only ever accessed through this raw pointer.
41    heap: *mut LocalHeap,
42    /// Values rooted by native code. Traced as roots by the collector, which reads them while
43    /// this thread is stopped, so they live in a separate allocation reached by raw pointer.
44    handles: *mut Vec<Value>,
45}
46
47/// An index into the handle stack, valid inside the [`Mutator::scope`] that created it.
48#[derive(Copy, Clone)]
49pub struct Handle(usize);
50
51impl Mutator {
52    /// Creates the context for an already-registered thread.
53    pub fn new(rec: Arc<ThreadRecord>) -> Self {
54        let heap = Box::into_raw(Box::new(LocalHeap::new(rec.tid)));
55        let handles = Box::into_raw(Box::new(Vec::new()));
56        Mutator { tid: rec.tid, rec, heap, handles }
57    }
58
59    /// Raw pointer to the allocator, for registration with the collector.
60    pub fn heap_ptr(&self) -> *mut LocalHeap {
61        self.heap
62    }
63
64    /// Returns the thread's pages to the global pool (on thread exit, in native state).
65    pub fn release_pages(&mut self) {
66        // SAFETY: the heap pointer is valid for the mutator's lifetime; the caller is in native
67        // state so the collector is not using it concurrently... except a collection may be
68        // running, which is why callers hold the thread out of `Running` via unregistration.
69        unsafe { (*self.heap).release_pages() }
70    }
71
72    // ---- handles ------------------------------------------------------------------------
73
74    /// Runs `f` with a handle scope; handles rooted inside are released afterwards.
75    pub fn scope<R>(&mut self, f: impl FnOnce(&mut Mutator) -> R) -> R {
76        let base = self.handles_len();
77        let r = f(self);
78        // SAFETY: only this thread mutates its handles, never during a collection.
79        unsafe { (*self.handles).truncate(base) };
80        r
81    }
82    pub fn root(&mut self, v: Value) -> Handle {
83        unsafe {
84            (*self.handles).push(v);
85            Handle((*self.handles).len() - 1)
86        }
87    }
88    pub fn get(&self, h: Handle) -> Value {
89        unsafe { (&(*self.handles))[h.0] }
90    }
91    pub fn set(&mut self, h: Handle, v: Value) {
92        unsafe { (&mut (*self.handles))[h.0] = v };
93    }
94    fn handles_len(&self) -> usize {
95        unsafe { (*self.handles).len() }
96    }
97    /// Root values for the GC (called while this thread is stopped).
98    pub fn handle_values(&self) -> &[Value] {
99        unsafe { &*self.handles }
100    }
101    /// Pointer to the handle stack, for root sources that must not borrow the mutator.
102    pub fn handles_ptr(&self) -> *const Vec<Value> {
103        self.handles
104    }
105
106    // ---- allocation ---------------------------------------------------------------------
107
108    #[inline]
109    pub fn alloc_raw(&mut self, size: usize, cls: &'static Class) -> *mut Header {
110        // SAFETY: only this (running) thread touches its heap outside of collections.
111        unsafe { (*self.heap).alloc(size, &cls.gc as *const _) }
112    }
113
114    pub fn alloc_instance(&mut self, cls: &'static Class) -> Value {
115        let n = cls.nfields();
116        let h = self.alloc_raw(InstanceObj::size_for(n), cls);
117        let inst = unsafe { &*(h as *const InstanceObj) };
118        for i in 0..n {
119            inst.set(i, Value::NIL);
120        }
121        Value::from_ptr(h)
122    }
123
124    pub fn alloc_str(&mut self, s: &str) -> Value {
125        let h = self.alloc_raw(StrObj::BASE + s.len(), BUILTINS.string);
126        unsafe {
127            let o = &mut *(h as *mut StrObj);
128            o.len = s.len();
129            std::ptr::copy_nonoverlapping(s.as_ptr(), o.data_ptr(), s.len());
130            (*h).set_state(ObjState::Frozen);
131        }
132        Value::from_ptr(h)
133    }
134
135    /// Allocates a big integer, or returns a small int if it fits.
136    pub fn int_from_big(&mut self, b: BigInt) -> Value {
137        if let Some(i) = b.to_i64() {
138            if fits_small(i) {
139                return Value::small_int(i);
140            }
141        }
142        let h = self.alloc_raw(std::mem::size_of::<BigIntObj>(), BUILTINS.bigint);
143        unsafe {
144            std::ptr::write(&mut (*(h as *mut BigIntObj)).value, b);
145            (*h).set_state(ObjState::Frozen);
146        }
147        Value::from_ptr(h)
148    }
149
150    #[inline]
151    pub fn int(&mut self, i: i64) -> Value {
152        if fits_small(i) {
153            Value::small_int(i)
154        } else {
155            self.int_from_big(BigInt::from(i))
156        }
157    }
158
159    pub fn alloc_list(&mut self, items: Vec<Value>) -> Value {
160        crate::note_external(items.capacity() * 8);
161        let h = self.alloc_raw(std::mem::size_of::<ListObj>(), BUILTINS.list);
162        unsafe { ListObj::init(h as *mut ListObj, items) };
163        Value::from_ptr(h)
164    }
165
166    pub fn alloc_dict(&mut self, table: DictTable) -> Value {
167        self.alloc_table(table, BUILTINS.dict)
168    }
169
170    pub fn alloc_set(&mut self, table: DictTable) -> Value {
171        self.alloc_table(table, BUILTINS.set)
172    }
173
174    fn alloc_table(&mut self, table: DictTable, cls: &'static Class) -> Value {
175        crate::note_external(table.heap_bytes());
176        let h = self.alloc_raw(std::mem::size_of::<DictObj>(), cls);
177        unsafe { std::ptr::write((*(h as *mut DictObj)).table.get(), table) };
178        Value::from_ptr(h)
179    }
180
181    pub fn alloc_tuple(&mut self, items: &[Value]) -> Value {
182        let h = self.alloc_raw(TupleObj::size_for(items.len()), BUILTINS.tuple);
183        unsafe {
184            let t = &mut *(h as *mut TupleObj);
185            t.len = items.len();
186            std::ptr::copy_nonoverlapping(items.as_ptr(), t.elems_mut(), items.len());
187        }
188        Value::from_ptr(h)
189    }
190
191    pub fn alloc_closure(&mut self, func: u32, caps: &[Value]) -> Value {
192        let h = self.alloc_raw(ClosureObj::size_for(caps.len()), BUILTINS.closure);
193        unsafe {
194            let c = &mut *(h as *mut ClosureObj);
195            c.func = func;
196            c.ncaps = caps.len() as u32;
197            std::ptr::copy_nonoverlapping(caps.as_ptr(), c.caps_mut(), caps.len());
198        }
199        Value::from_ptr(h)
200    }
201
202    pub fn alloc_cell(&mut self, v: Value) -> Value {
203        let h = self.alloc_raw(std::mem::size_of::<CellObj>(), BUILTINS.cell);
204        unsafe { (*(h as *mut CellObj)).value = AtomicU64::new(v.0) };
205        Value::from_ptr(h)
206    }
207
208    pub fn alloc_string_builder(&mut self, s: String) -> Value {
209        let h = self.alloc_raw(std::mem::size_of::<StringBuilderObj>(), BUILTINS.string_builder);
210        unsafe { std::ptr::write((*(h as *mut StringBuilderObj)).buf.get(), s) };
211        Value::from_ptr(h)
212    }
213
214    pub fn alloc_mutex(&mut self, v: Value) -> Value {
215        let h = self.alloc_raw(std::mem::size_of::<MutexObj>(), BUILTINS.mutex);
216        unsafe {
217            std::ptr::write(&mut (*(h as *mut MutexObj)).raw, parking_lot::ReentrantMutex::new(()));
218            (*(h as *mut MutexObj)).value = AtomicU64::new(v.0);
219            (*h).set_state(ObjState::Shared);
220        }
221        Value::from_ptr(h)
222    }
223
224    pub fn alloc_atomic(&mut self, v: Value) -> Value {
225        let h = self.alloc_raw(std::mem::size_of::<AtomicObj>(), BUILTINS.atomic);
226        unsafe {
227            (*(h as *mut AtomicObj)).value = AtomicU64::new(v.0);
228            (*h).set_state(ObjState::Shared);
229        }
230        Value::from_ptr(h)
231    }
232
233    pub fn alloc_channel(&mut self, capacity: usize) -> Value {
234        let h = self.alloc_raw(std::mem::size_of::<ChannelObj>(), BUILTINS.channel);
235        let inner = Box::new(ChannelInner {
236            queue: parking_lot::Mutex::new(ChannelState { items: Default::default(), closed: false }),
237            not_empty: parking_lot::Condvar::new(),
238            not_full: parking_lot::Condvar::new(),
239            capacity,
240        });
241        unsafe {
242            std::ptr::write(&mut (*(h as *mut ChannelObj)).inner, inner);
243            (*h).set_state(ObjState::Shared);
244        }
245        Value::from_ptr(h)
246    }
247
248    pub fn alloc_thread(&mut self) -> Value {
249        let h = self.alloc_raw(std::mem::size_of::<ThreadObj>(), BUILTINS.thread);
250        let inner = std::sync::Arc::new(ThreadInner {
251            outcome: parking_lot::Mutex::new(ThreadOutcome::Running),
252            done: parking_lot::Condvar::new(),
253            handle: parking_lot::Mutex::new(None),
254            tid: AtomicU64::new(0),
255        });
256        unsafe {
257            std::ptr::write(&mut (*(h as *mut ThreadObj)).inner, inner);
258            (*h).set_state(ObjState::Shared);
259        }
260        Value::from_ptr(h)
261    }
262
263    pub fn alloc_native(&mut self, data: Box<dyn NativeData>) -> Value {
264        let h = self.alloc_raw(std::mem::size_of::<NativeObj>(), BUILTINS.native);
265        unsafe { std::ptr::write(&mut (*(h as *mut NativeObj)).data, data) };
266        Value::from_ptr(h)
267    }
268
269    pub fn alloc_range(&mut self, first: Value, last: Value, exclusive: bool) -> Value {
270        let v = self.alloc_instance(BUILTINS.range);
271        let r = unsafe { as_instance(v) };
272        r.set(0, first);
273        r.set(1, last);
274        r.set(2, Value::bool(exclusive));
275        unsafe { (*v.as_ptr()).set_state(ObjState::Frozen) };
276        v
277    }
278}
279
280/// Memory held outside GC pages (vector buffers) counts toward the collection budget.
281pub fn note_external(bytes: usize) {
282    if bytes > 0 {
283        catallaxy_gc::stats::note_allocated(bytes);
284    }
285}
286
287/// A region in which native code can root values across calls back into the interpreter (which
288/// may collect). Handles borrow the scope, so the borrow checker keeps them from escaping it;
289/// dropping the scope releases them.
290///
291/// ```ignore
292/// let scope = HandleScope::new(&mut vm.m);
293/// let list = scope.root(make_list(scope.mutator()));
294/// vm_call_that_may_collect();
295/// let still_valid = scope.get(list);
296/// ```
297pub struct HandleScope<'m> {
298    m: *mut Mutator,
299    base: usize,
300    _borrow: std::marker::PhantomData<&'m mut Mutator>,
301}
302
303/// A rooted value, valid for the lifetime of its [`HandleScope`].
304#[derive(Copy, Clone)]
305pub struct Local<'s> {
306    index: usize,
307    _scope: std::marker::PhantomData<&'s ()>,
308}
309
310impl<'m> HandleScope<'m> {
311    pub fn new(m: &'m mut Mutator) -> Self {
312        let base = m.handles_len();
313        HandleScope { m, base, _borrow: std::marker::PhantomData }
314    }
315
316    /// A scope that does not borrow the mutator, so native code can keep calling into the VM
317    /// while it holds handles. Handles still cannot outlive the scope.
318    ///
319    /// # Safety
320    /// `m` must outlive the scope and belong to the current thread.
321    pub unsafe fn detached(m: *mut Mutator) -> HandleScope<'static> {
322        let base = (*m).handles_len();
323        HandleScope { m, base, _borrow: std::marker::PhantomData }
324    }
325
326    /// Roots `v` until the scope ends.
327    pub fn root<'s>(&'s self, v: Value) -> Local<'s> {
328        // SAFETY: the scope holds the mutator's unique borrow; handles are only read by the
329        // collector while this thread is stopped.
330        let hs = unsafe { &mut *(*self.m).handles };
331        hs.push(v);
332        Local { index: hs.len() - 1, _scope: std::marker::PhantomData }
333    }
334
335    pub fn get(&self, h: Local<'_>) -> Value {
336        unsafe { (&(*(*self.m).handles))[h.index] }
337    }
338
339    pub fn set(&self, h: Local<'_>, v: Value) {
340        unsafe { (&mut (*(*self.m).handles))[h.index] = v }
341    }
342
343    /// The mutator, for allocation. Allocation never collects, so handles stay valid.
344    #[allow(clippy::mut_from_ref)]
345    pub fn mutator(&self) -> &mut Mutator {
346        unsafe { &mut *self.m }
347    }
348}
349
350impl Drop for HandleScope<'_> {
351    fn drop(&mut self) {
352        unsafe { (*(*self.m).handles).truncate(self.base) };
353    }
354}