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    /// Whether new objects start young (see `catallaxy_gc::LocalHeap::set_young_alloc`).
60    pub fn set_young_alloc(&mut self, on: bool) {
61        unsafe { (*self.heap).set_young_alloc(on) }
62    }
63
64    /// A second context for the same thread (a fiber): same heap and thread record, its own
65    /// handle stack. Release it with [`Mutator::free_handles`].
66    pub fn sharing(other: &Mutator) -> Self {
67        Mutator { tid: other.tid, rec: other.rec.clone(), heap: other.heap, handles: Box::into_raw(Box::new(Vec::new())) }
68    }
69
70    /// Frees the handle stack of a context made by [`Mutator::sharing`].
71    ///
72    /// # Safety
73    /// No root source may still reference the handles.
74    pub unsafe fn free_handles(&mut self) {
75        drop(Box::from_raw(self.handles));
76        self.handles = std::ptr::null_mut();
77    }
78
79    /// Raw pointer to the allocator, for registration with the collector.
80    pub fn heap_ptr(&self) -> *mut LocalHeap {
81        self.heap
82    }
83
84    /// Returns the thread's pages to the global pool (on thread exit, in native state).
85    pub fn release_pages(&mut self) {
86        // SAFETY: the heap pointer is valid for the mutator's lifetime; the caller is in native
87        // state so the collector is not using it concurrently... except a collection may be
88        // running, which is why callers hold the thread out of `Running` via unregistration.
89        unsafe { (*self.heap).release_pages() }
90    }
91
92    // ---- handles ------------------------------------------------------------------------
93
94    /// Runs `f` with a handle scope; handles rooted inside are released afterwards.
95    pub fn scope<R>(&mut self, f: impl FnOnce(&mut Mutator) -> R) -> R {
96        let base = self.handles_len();
97        let r = f(self);
98        // SAFETY: only this thread mutates its handles, never during a collection.
99        unsafe { (*self.handles).truncate(base) };
100        r
101    }
102    pub fn root(&mut self, v: Value) -> Handle {
103        unsafe {
104            (*self.handles).push(v);
105            Handle((*self.handles).len() - 1)
106        }
107    }
108    pub fn get(&self, h: Handle) -> Value {
109        unsafe { (&(*self.handles))[h.0] }
110    }
111    pub fn set(&mut self, h: Handle, v: Value) {
112        unsafe { (&mut (*self.handles))[h.0] = v };
113    }
114    fn handles_len(&self) -> usize {
115        unsafe { (*self.handles).len() }
116    }
117    /// The current depth of the handle stack, for [`Mutator::release_handles`]. Lets foreign
118    /// code (the C API) manage scopes explicitly.
119    pub fn handle_mark(&self) -> usize {
120        self.handles_len()
121    }
122    /// Releases every handle rooted since `mark`.
123    pub fn release_handles(&mut self, mark: usize) {
124        unsafe { (*self.handles).truncate(mark) };
125    }
126    /// Root values for the GC (called while this thread is stopped).
127    pub fn handle_values(&self) -> &[Value] {
128        unsafe { &*self.handles }
129    }
130    /// Pointer to the handle stack, for root sources that must not borrow the mutator.
131    pub fn handles_ptr(&self) -> *const Vec<Value> {
132        self.handles
133    }
134
135    // ---- allocation ---------------------------------------------------------------------
136
137    #[inline]
138    pub fn alloc_raw(&mut self, size: usize, cls: &'static Class) -> *mut Header {
139        // SAFETY: only this (running) thread touches its heap outside of collections.
140        unsafe { (*self.heap).alloc(size, &cls.gc as *const _) }
141    }
142
143    pub fn alloc_instance(&mut self, cls: &'static Class) -> Value {
144        let n = cls.nfields();
145        let h = self.alloc_raw(InstanceObj::size_for(n), cls);
146        let inst = unsafe { &*(h as *const InstanceObj) };
147        for i in 0..n {
148            inst.set(i, Value::NIL);
149        }
150        Value::from_ptr(h)
151    }
152
153    pub fn alloc_str(&mut self, s: &str) -> Value {
154        let h = self.alloc_raw(StrObj::BASE + s.len(), BUILTINS.string);
155        unsafe {
156            let o = &mut *(h as *mut StrObj);
157            o.len = s.len();
158            std::ptr::copy_nonoverlapping(s.as_ptr(), o.data_ptr(), s.len());
159            (*h).set_state(ObjState::Frozen);
160        }
161        Value::from_ptr(h)
162    }
163
164    /// Allocates a big integer, or returns a small int if it fits.
165    pub fn int_from_big(&mut self, b: BigInt) -> Value {
166        if let Some(i) = b.to_i64() {
167            if fits_small(i) {
168                return Value::small_int(i);
169            }
170        }
171        let h = self.alloc_raw(std::mem::size_of::<BigIntObj>(), BUILTINS.bigint);
172        unsafe {
173            std::ptr::write(&mut (*(h as *mut BigIntObj)).value, b);
174            (*h).set_state(ObjState::Frozen);
175        }
176        Value::from_ptr(h)
177    }
178
179    #[inline]
180    pub fn int(&mut self, i: i64) -> Value {
181        if fits_small(i) {
182            Value::small_int(i)
183        } else {
184            self.int_from_big(BigInt::from(i))
185        }
186    }
187
188    pub fn alloc_list(&mut self, items: Vec<Value>) -> Value {
189        let h = self.alloc_raw(std::mem::size_of::<ListObj>(), BUILTINS.list);
190        unsafe { ListObj::init(h as *mut ListObj, items) };
191        Value::from_ptr(h)
192    }
193
194    pub fn alloc_dict(&mut self, table: DictTable) -> Value {
195        self.alloc_table(table, BUILTINS.dict)
196    }
197
198    pub fn alloc_set(&mut self, table: DictTable) -> Value {
199        self.alloc_table(table, BUILTINS.set)
200    }
201
202    fn alloc_table(&mut self, table: DictTable, cls: &'static Class) -> Value {
203        let h = self.alloc_raw(std::mem::size_of::<DictObj>(), cls);
204        unsafe { std::ptr::write(std::ptr::addr_of_mut!((*(h as *mut DictObj)).table), table) };
205        Value::from_ptr(h)
206    }
207
208    pub fn alloc_tuple(&mut self, items: &[Value]) -> Value {
209        let h = self.alloc_raw(TupleObj::size_for(items.len()), BUILTINS.tuple);
210        unsafe {
211            let t = &mut *(h as *mut TupleObj);
212            t.len = items.len();
213            std::ptr::copy_nonoverlapping(items.as_ptr(), t.elems_mut(), items.len());
214        }
215        Value::from_ptr(h)
216    }
217
218    pub fn alloc_closure(&mut self, func: u32, caps: &[Value]) -> Value {
219        let h = self.alloc_raw(ClosureObj::size_for(caps.len()), BUILTINS.closure);
220        unsafe {
221            let c = &mut *(h as *mut ClosureObj);
222            c.func = func;
223            c.ncaps = caps.len() as u32;
224            std::ptr::copy_nonoverlapping(caps.as_ptr(), c.caps_mut(), caps.len());
225        }
226        Value::from_ptr(h)
227    }
228
229    pub fn alloc_cell(&mut self, v: Value) -> Value {
230        let h = self.alloc_raw(std::mem::size_of::<CellObj>(), BUILTINS.cell);
231        unsafe { (*(h as *mut CellObj)).value = AtomicU64::new(v.0) };
232        Value::from_ptr(h)
233    }
234
235    pub fn alloc_string_builder(&mut self, s: String) -> Value {
236        let h = self.alloc_raw(std::mem::size_of::<StringBuilderObj>(), BUILTINS.string_builder);
237        unsafe { std::ptr::write((*(h as *mut StringBuilderObj)).buf.get(), s) };
238        Value::from_ptr(h)
239    }
240
241    pub fn alloc_mutex(&mut self, v: Value) -> Value {
242        let h = self.alloc_raw(std::mem::size_of::<MutexObj>(), BUILTINS.mutex);
243        unsafe {
244            std::ptr::write(&mut (*(h as *mut MutexObj)).raw, parking_lot::ReentrantMutex::new(()));
245            (*(h as *mut MutexObj)).value = AtomicU64::new(v.0);
246            (*h).set_state(ObjState::Shared);
247        }
248        Value::from_ptr(h)
249    }
250
251    pub fn alloc_atomic(&mut self, v: Value) -> Value {
252        let h = self.alloc_raw(std::mem::size_of::<AtomicObj>(), BUILTINS.atomic);
253        unsafe {
254            (*(h as *mut AtomicObj)).value = AtomicU64::new(v.0);
255            (*h).set_state(ObjState::Shared);
256        }
257        Value::from_ptr(h)
258    }
259
260    pub fn alloc_channel(&mut self, capacity: usize) -> Value {
261        let h = self.alloc_raw(std::mem::size_of::<ChannelObj>(), BUILTINS.channel);
262        let inner = Box::new(ChannelInner {
263            queue: parking_lot::Mutex::new(ChannelState { items: Default::default(), closed: false }),
264            not_empty: parking_lot::Condvar::new(),
265            not_full: parking_lot::Condvar::new(),
266            capacity,
267            waiters: parking_lot::Mutex::new(Vec::new()),
268        });
269        unsafe {
270            std::ptr::write(&mut (*(h as *mut ChannelObj)).inner, inner);
271            (*h).set_state(ObjState::Shared);
272        }
273        Value::from_ptr(h)
274    }
275
276    pub fn alloc_thread(&mut self) -> Value {
277        let h = self.alloc_raw(std::mem::size_of::<ThreadObj>(), BUILTINS.thread);
278        let inner = std::sync::Arc::new(ThreadInner {
279            outcome: parking_lot::Mutex::new(ThreadOutcome::Running),
280            done: parking_lot::Condvar::new(),
281            handle: parking_lot::Mutex::new(None),
282            tid: AtomicU64::new(0),
283        });
284        unsafe {
285            std::ptr::write(&mut (*(h as *mut ThreadObj)).inner, inner);
286            (*h).set_state(ObjState::Shared);
287        }
288        Value::from_ptr(h)
289    }
290
291    pub fn alloc_native(&mut self, data: Box<dyn NativeData>) -> Value {
292        let h = self.alloc_raw(std::mem::size_of::<NativeObj>(), BUILTINS.native);
293        unsafe { std::ptr::write(&mut (*(h as *mut NativeObj)).data, data) };
294        Value::from_ptr(h)
295    }
296
297    pub fn alloc_range(&mut self, first: Value, last: Value, exclusive: bool) -> Value {
298        let v = self.alloc_instance(BUILTINS.range);
299        let r = unsafe { as_instance(v) };
300        r.set(0, first);
301        r.set(1, last);
302        r.set(2, Value::bool(exclusive));
303        unsafe { (*v.as_ptr()).set_state(ObjState::Frozen) };
304        v
305    }
306}
307
308/// Memory held outside GC pages (vector buffers) counts toward the collection budget.
309pub fn note_external(bytes: usize) {
310    if bytes > 0 {
311        catallaxy_gc::stats::note_allocated(bytes);
312    }
313}
314
315/// A region in which native code can root values across calls back into the interpreter (which
316/// may collect). Handles borrow the scope, so the borrow checker keeps them from escaping it;
317/// dropping the scope releases them.
318///
319/// ```ignore
320/// let scope = HandleScope::new(&mut vm.m);
321/// let list = scope.root(make_list(scope.mutator()));
322/// vm_call_that_may_collect();
323/// let still_valid = scope.get(list);
324/// ```
325pub struct HandleScope<'m> {
326    m: *mut Mutator,
327    base: usize,
328    _borrow: std::marker::PhantomData<&'m mut Mutator>,
329}
330
331/// A rooted value, valid for the lifetime of its [`HandleScope`].
332#[derive(Copy, Clone)]
333pub struct Local<'s> {
334    index: usize,
335    _scope: std::marker::PhantomData<&'s ()>,
336}
337
338impl<'m> HandleScope<'m> {
339    pub fn new(m: &'m mut Mutator) -> Self {
340        let base = m.handles_len();
341        HandleScope { m, base, _borrow: std::marker::PhantomData }
342    }
343
344    /// A scope that does not borrow the mutator, so native code can keep calling into the VM
345    /// while it holds handles. Handles still cannot outlive the scope.
346    ///
347    /// # Safety
348    /// `m` must outlive the scope and belong to the current thread.
349    pub unsafe fn detached(m: *mut Mutator) -> HandleScope<'static> {
350        let base = (*m).handles_len();
351        HandleScope { m, base, _borrow: std::marker::PhantomData }
352    }
353
354    /// Roots `v` until the scope ends.
355    pub fn root<'s>(&'s self, v: Value) -> Local<'s> {
356        // SAFETY: the scope holds the mutator's unique borrow; handles are only read by the
357        // collector while this thread is stopped.
358        let hs = unsafe { &mut *(*self.m).handles };
359        hs.push(v);
360        Local { index: hs.len() - 1, _scope: std::marker::PhantomData }
361    }
362
363    pub fn get(&self, h: Local<'_>) -> Value {
364        unsafe { (&(*(*self.m).handles))[h.index] }
365    }
366
367    pub fn set(&self, h: Local<'_>, v: Value) {
368        unsafe { (&mut (*(*self.m).handles))[h.index] = v }
369    }
370
371    /// The mutator, for allocation. Allocation never collects, so handles stay valid.
372    #[allow(clippy::mut_from_ref)]
373    pub fn mutator(&self) -> &mut Mutator {
374        unsafe { &mut *self.m }
375    }
376}
377
378impl Drop for HandleScope<'_> {
379    fn drop(&mut self) {
380        unsafe { (*(*self.m).handles).truncate(self.base) };
381    }
382}