Skip to main content

catallaxy_runtime/
object.rs

1//! Heap object layouts and their GC hooks.
2//!
3//! Every object starts with a [`Header`]. Instances keep their fields inline as atomic words, so a
4//! racing reader can never see a torn value. Containers keep their elements in Rust-side buffers;
5//! the ownership rules (and per-object locks for shared objects) guarantee those buffers are never
6//! mutated while another thread reads them.
7
8use std::cell::UnsafeCell;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11use catallaxy_gc::{GcVTable, Header, Tracer, HEADER_SIZE};
12use num_bigint::BigInt;
13
14use crate::class::{heap_class, ObjKind};
15use crate::dict::DictTable;
16use crate::value::Value;
17
18#[inline(always)]
19pub fn mark_value(t: &mut Tracer, v: Value) {
20    if v.is_ptr() {
21        t.mark(v.as_ptr());
22    }
23}
24
25#[repr(C)]
26pub struct InstanceObj {
27    pub header: Header,
28    fields: [AtomicU64; 0],
29}
30
31impl InstanceObj {
32    #[inline(always)]
33    pub fn field(&self, i: usize) -> &AtomicU64 {
34        // SAFETY: instances are allocated with `nfields` trailing words and callers index within it.
35        unsafe { &*self.fields.as_ptr().add(i) }
36    }
37    #[inline(always)]
38    pub fn get(&self, i: usize) -> Value {
39        Value(self.field(i).load(Ordering::Relaxed))
40    }
41    #[inline(always)]
42    pub fn get_acquire(&self, i: usize) -> Value {
43        Value(self.field(i).load(Ordering::Acquire))
44    }
45    #[inline(always)]
46    pub fn set(&self, i: usize, v: Value) {
47        self.field(i).store(v.0, Ordering::Relaxed)
48    }
49    #[inline(always)]
50    pub fn set_release(&self, i: usize, v: Value) {
51        self.field(i).store(v.0, Ordering::Release)
52    }
53    pub fn nfields(&self) -> usize {
54        unsafe { heap_class(&self.header).nfields() }
55    }
56    pub fn size_for(nfields: usize) -> usize {
57        HEADER_SIZE + 8 * nfields
58    }
59}
60
61#[repr(C)]
62pub struct StrObj {
63    pub header: Header,
64    pub len: usize,
65    /// Cached hash; 0 means not yet computed.
66    pub hash: AtomicU64,
67    /// Cached char count plus one; 0 means not yet computed.
68    pub chars: AtomicU64,
69    data: [u8; 0],
70}
71
72impl StrObj {
73    pub const BASE: usize = std::mem::size_of::<StrObj>();
74    pub fn as_str(&self) -> &str {
75        // SAFETY: strings are created from valid UTF-8 and never mutated.
76        unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.data.as_ptr(), self.len)) }
77    }
78    pub(crate) fn data_ptr(&mut self) -> *mut u8 {
79        self.data.as_mut_ptr()
80    }
81    pub fn char_count(&self) -> usize {
82        let c = self.chars.load(Ordering::Relaxed);
83        if c != 0 {
84            return (c - 1) as usize;
85        }
86        let s = self.as_str();
87        let n = if s.is_ascii() { s.len() } else { s.chars().count() };
88        self.chars.store(n as u64 + 1, Ordering::Relaxed);
89        n
90    }
91    pub fn hash_code(&self) -> u64 {
92        let h = self.hash.load(Ordering::Relaxed);
93        if h != 0 {
94            return h;
95        }
96        let mut h = crate::ops::hash_bytes(self.as_str().as_bytes());
97        if h == 0 {
98            h = 1;
99        }
100        self.hash.store(h, Ordering::Relaxed);
101        h
102    }
103}
104
105#[repr(C)]
106pub struct BigIntObj {
107    pub header: Header,
108    pub value: BigInt,
109}
110
111#[repr(C)]
112pub struct ListObj {
113    pub header: Header,
114    pub vec: UnsafeCell<Vec<Value>>,
115}
116
117impl ListObj {
118    /// # Safety
119    /// The caller must have read access per the ownership rules (see `ownership::with_read`).
120    #[inline(always)]
121    pub unsafe fn items(&self) -> &Vec<Value> {
122        &*self.vec.get()
123    }
124    /// # Safety
125    /// The caller must have write access per the ownership rules, and must not hold another
126    /// reference to the items.
127    #[inline(always)]
128    #[allow(clippy::mut_from_ref)]
129    pub unsafe fn items_mut(&self) -> &mut Vec<Value> {
130        &mut *self.vec.get()
131    }
132}
133
134#[repr(C)]
135pub struct DictObj {
136    pub header: Header,
137    pub table: UnsafeCell<DictTable>,
138}
139
140impl DictObj {
141    /// # Safety
142    /// As for [`ListObj::items`].
143    #[inline(always)]
144    pub unsafe fn table(&self) -> &DictTable {
145        &*self.table.get()
146    }
147    /// # Safety
148    /// As for [`ListObj::items_mut`].
149    #[inline(always)]
150    #[allow(clippy::mut_from_ref)]
151    pub unsafe fn table_mut(&self) -> &mut DictTable {
152        &mut *self.table.get()
153    }
154}
155
156#[repr(C)]
157pub struct TupleObj {
158    pub header: Header,
159    pub len: usize,
160    elems: [Value; 0],
161}
162
163impl TupleObj {
164    pub fn elems(&self) -> &[Value] {
165        unsafe { std::slice::from_raw_parts(self.elems.as_ptr(), self.len) }
166    }
167    pub(crate) fn elems_mut(&mut self) -> *mut Value {
168        self.elems.as_mut_ptr()
169    }
170    pub fn size_for(n: usize) -> usize {
171        std::mem::size_of::<TupleObj>() + 8 * n
172    }
173}
174
175#[repr(C)]
176pub struct ClosureObj {
177    pub header: Header,
178    pub func: u32,
179    pub ncaps: u32,
180    caps: [Value; 0],
181}
182
183impl ClosureObj {
184    pub fn caps(&self) -> &[Value] {
185        unsafe { std::slice::from_raw_parts(self.caps.as_ptr(), self.ncaps as usize) }
186    }
187    pub(crate) fn caps_mut(&mut self) -> *mut Value {
188        self.caps.as_mut_ptr()
189    }
190    pub fn size_for(n: usize) -> usize {
191        std::mem::size_of::<ClosureObj>() + 8 * n
192    }
193}
194
195#[repr(C)]
196pub struct CellObj {
197    pub header: Header,
198    pub value: AtomicU64,
199}
200
201#[repr(C)]
202pub struct StringBuilderObj {
203    pub header: Header,
204    pub buf: UnsafeCell<String>,
205}
206
207#[repr(C)]
208pub struct MutexObj {
209    pub header: Header,
210    pub raw: parking_lot::ReentrantMutex<()>,
211    pub value: AtomicU64,
212}
213
214#[repr(C)]
215pub struct AtomicObj {
216    pub header: Header,
217    pub value: AtomicU64,
218}
219
220pub struct ChannelInner {
221    pub queue: parking_lot::Mutex<ChannelState>,
222    pub not_empty: parking_lot::Condvar,
223    pub not_full: parking_lot::Condvar,
224    pub capacity: usize,
225}
226
227pub struct ChannelState {
228    pub items: std::collections::VecDeque<Value>,
229    pub closed: bool,
230}
231
232#[repr(C)]
233pub struct ChannelObj {
234    pub header: Header,
235    pub inner: Box<ChannelInner>,
236}
237
238/// Outcome of a finished thread: its value or its panic message.
239pub enum ThreadOutcome {
240    Running,
241    Done(Value),
242    Panicked(String),
243    Joined,
244}
245
246pub struct ThreadInner {
247    pub outcome: parking_lot::Mutex<ThreadOutcome>,
248    pub done: parking_lot::Condvar,
249    pub handle: parking_lot::Mutex<Option<std::thread::JoinHandle<()>>>,
250    /// Thread id of the running thread (for transferring its result).
251    pub tid: AtomicU64,
252}
253
254#[repr(C)]
255pub struct ThreadObj {
256    pub header: Header,
257    pub inner: std::sync::Arc<ThreadInner>,
258}
259
260pub trait NativeData: std::any::Any + Send + Sync {
261    fn as_any(&self) -> &dyn std::any::Any;
262}
263impl<T: std::any::Any + Send + Sync> NativeData for T {
264    fn as_any(&self) -> &dyn std::any::Any {
265        self
266    }
267}
268
269#[repr(C)]
270pub struct NativeObj {
271    pub header: Header,
272    pub data: Box<dyn NativeData>,
273}
274
275// ---------------------------------------------------------------------------------------------
276// GC hooks per object kind.
277
278unsafe fn trace_nothing(_: *const Header, _: &mut Tracer) {}
279
280unsafe fn trace_instance(h: *const Header, t: &mut Tracer) {
281    let o = &*(h as *const InstanceObj);
282    for i in 0..heap_class(h).nfields() {
283        mark_value(t, o.get(i));
284    }
285}
286unsafe fn size_instance(h: *const Header) -> usize {
287    InstanceObj::size_for(heap_class(h).nfields())
288}
289
290unsafe fn size_str(h: *const Header) -> usize {
291    StrObj::BASE + (*(h as *const StrObj)).len
292}
293
294unsafe fn fin_bigint(h: *mut Header) {
295    std::ptr::drop_in_place(&mut (*(h as *mut BigIntObj)).value);
296}
297unsafe fn size_bigint(h: *const Header) -> usize {
298    std::mem::size_of::<BigIntObj>() + (*(h as *const BigIntObj)).value.bits() as usize / 8
299}
300
301unsafe fn trace_list(h: *const Header, t: &mut Tracer) {
302    for &v in (*(*(h as *const ListObj)).vec.get()).iter() {
303        mark_value(t, v);
304    }
305}
306unsafe fn fin_list(h: *mut Header) {
307    std::ptr::drop_in_place((*(h as *mut ListObj)).vec.get());
308}
309unsafe fn size_list(h: *const Header) -> usize {
310    std::mem::size_of::<ListObj>() + 8 * (*(*(h as *const ListObj)).vec.get()).capacity()
311}
312
313unsafe fn trace_dict(h: *const Header, t: &mut Tracer) {
314    (*(*(h as *const DictObj)).table.get()).trace(t);
315}
316unsafe fn fin_dict(h: *mut Header) {
317    std::ptr::drop_in_place((*(h as *mut DictObj)).table.get());
318}
319unsafe fn size_dict(h: *const Header) -> usize {
320    std::mem::size_of::<DictObj>() + (*(*(h as *const DictObj)).table.get()).heap_bytes()
321}
322
323unsafe fn trace_tuple(h: *const Header, t: &mut Tracer) {
324    for &v in (*(h as *const TupleObj)).elems() {
325        mark_value(t, v);
326    }
327}
328unsafe fn size_tuple(h: *const Header) -> usize {
329    TupleObj::size_for((*(h as *const TupleObj)).len)
330}
331
332unsafe fn trace_closure(h: *const Header, t: &mut Tracer) {
333    for &v in (*(h as *const ClosureObj)).caps() {
334        mark_value(t, v);
335    }
336}
337unsafe fn size_closure(h: *const Header) -> usize {
338    ClosureObj::size_for((*(h as *const ClosureObj)).ncaps as usize)
339}
340
341unsafe fn trace_cell(h: *const Header, t: &mut Tracer) {
342    mark_value(t, Value((*(h as *const CellObj)).value.load(Ordering::Relaxed)));
343}
344unsafe fn size_cell(_: *const Header) -> usize {
345    std::mem::size_of::<CellObj>()
346}
347
348unsafe fn fin_sb(h: *mut Header) {
349    std::ptr::drop_in_place((*(h as *mut StringBuilderObj)).buf.get());
350}
351unsafe fn size_sb(h: *const Header) -> usize {
352    std::mem::size_of::<StringBuilderObj>() + (*(*(h as *const StringBuilderObj)).buf.get()).capacity()
353}
354
355unsafe fn trace_mutex(h: *const Header, t: &mut Tracer) {
356    mark_value(t, Value((*(h as *const MutexObj)).value.load(Ordering::Relaxed)));
357}
358unsafe fn fin_mutex(h: *mut Header) {
359    std::ptr::drop_in_place(&mut (*(h as *mut MutexObj)).raw);
360}
361unsafe fn size_mutex(_: *const Header) -> usize {
362    std::mem::size_of::<MutexObj>()
363}
364
365unsafe fn trace_atomic(h: *const Header, t: &mut Tracer) {
366    mark_value(t, Value((*(h as *const AtomicObj)).value.load(Ordering::Relaxed)));
367}
368unsafe fn size_atomic(_: *const Header) -> usize {
369    std::mem::size_of::<AtomicObj>()
370}
371
372unsafe fn trace_channel(h: *const Header, t: &mut Tracer) {
373    // The world is stopped; no thread holds the queue lock across a safepoint.
374    let inner = &(*(h as *const ChannelObj)).inner;
375    let q = inner.queue.data_ptr();
376    for &v in (*q).items.iter() {
377        mark_value(t, v);
378    }
379}
380unsafe fn fin_channel(h: *mut Header) {
381    std::ptr::drop_in_place(&mut (*(h as *mut ChannelObj)).inner);
382}
383unsafe fn size_channel(h: *const Header) -> usize {
384    std::mem::size_of::<ChannelObj>() + 8 * (*(*(h as *const ChannelObj)).inner.queue.data_ptr()).items.capacity()
385}
386
387unsafe fn trace_thread(h: *const Header, t: &mut Tracer) {
388    let inner = &(*(h as *const ThreadObj)).inner;
389    if let ThreadOutcome::Done(v) = &*inner.outcome.data_ptr() {
390        mark_value(t, *v);
391    }
392}
393unsafe fn fin_thread(h: *mut Header) {
394    std::ptr::drop_in_place(&mut (*(h as *mut ThreadObj)).inner);
395}
396unsafe fn size_thread(_: *const Header) -> usize {
397    std::mem::size_of::<ThreadObj>() + 128
398}
399
400unsafe fn fin_native(h: *mut Header) {
401    std::ptr::drop_in_place(&mut (*(h as *mut NativeObj)).data);
402}
403unsafe fn size_native(_: *const Header) -> usize {
404    std::mem::size_of::<NativeObj>()
405}
406
407unsafe fn size_zero(_: *const Header) -> usize {
408    0
409}
410
411#[allow(clippy::type_complexity)]
412pub(crate) fn vtable_for(kind: ObjKind) -> GcVTable {
413    let (trace, finalize, size): (
414        unsafe fn(*const Header, &mut Tracer),
415        Option<unsafe fn(*mut Header)>,
416        unsafe fn(*const Header) -> usize,
417    ) = match kind {
418        ObjKind::Immediate => (trace_nothing, None, size_zero),
419        ObjKind::Instance => (trace_instance, None, size_instance),
420        ObjKind::Str => (trace_nothing, None, size_str),
421        ObjKind::BigInt => (trace_nothing, Some(fin_bigint), size_bigint),
422        ObjKind::List => (trace_list, Some(fin_list), size_list),
423        ObjKind::Dict | ObjKind::Set => (trace_dict, Some(fin_dict), size_dict),
424        ObjKind::Tuple => (trace_tuple, None, size_tuple),
425        ObjKind::Closure => (trace_closure, None, size_closure),
426        ObjKind::Cell => (trace_cell, None, size_cell),
427        ObjKind::StringBuilder => (trace_nothing, Some(fin_sb), size_sb),
428        ObjKind::Mutex => (trace_mutex, Some(fin_mutex), size_mutex),
429        ObjKind::Atomic => (trace_atomic, None, size_atomic),
430        ObjKind::Channel => (trace_channel, Some(fin_channel), size_channel),
431        ObjKind::Thread => (trace_thread, Some(fin_thread), size_thread),
432        ObjKind::Native => (trace_nothing, Some(fin_native), size_native),
433    };
434    GcVTable { trace, finalize, size }
435}
436
437// ---------------------------------------------------------------------------------------------
438// Typed views. Callers must have checked the kind (usually statically, by the type checker).
439
440macro_rules! view {
441    ($fn:ident, $ty:ty) => {
442        /// # Safety
443        /// `v` must be a pointer to an object of this kind.
444        #[inline(always)]
445        pub unsafe fn $fn<'a>(v: Value) -> &'a $ty {
446            &*(v.as_ptr() as *const $ty)
447        }
448    };
449}
450view!(as_instance, InstanceObj);
451view!(as_str_obj, StrObj);
452view!(as_bigint, BigIntObj);
453view!(as_list, ListObj);
454view!(as_dict, DictObj);
455view!(as_tuple, TupleObj);
456view!(as_closure, ClosureObj);
457view!(as_cell, CellObj);
458view!(as_sb, StringBuilderObj);
459view!(as_mutex, MutexObj);
460view!(as_atomic, AtomicObj);
461view!(as_channel, ChannelObj);
462view!(as_thread, ThreadObj);
463view!(as_native, NativeObj);
464
465/// The object kind of a value (`Immediate` for non-pointers).
466#[inline]
467pub fn kind_of(v: Value) -> ObjKind {
468    if v.is_ptr() {
469        unsafe { heap_class(v.as_ptr()).kind }
470    } else {
471        ObjKind::Immediate
472    }
473}
474
475/// Returns the string slice if `v` is a string.
476#[inline]
477pub fn str_of<'a>(v: Value) -> Option<&'a str> {
478    if kind_of(v) == ObjKind::Str {
479        Some(unsafe { as_str_obj(v) }.as_str())
480    } else {
481        None
482    }
483}