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. Lists do the same with their elements and swap whole
5//! buffers on growth, so their reads never lock. Dicts and other containers keep Rust-side
6//! buffers; the ownership rules (and per-object locks for shared objects) guarantee those are
7//! never mutated while another thread reads them.
8
9use std::alloc::Layout;
10use std::cell::UnsafeCell;
11use std::sync::atomic::{AtomicPtr, AtomicU64, AtomicUsize, Ordering};
12
13use catallaxy_gc::{GcVTable, Header, Tracer, HEADER_SIZE};
14use num_bigint::BigInt;
15
16use crate::class::{heap_class, ObjKind};
17use crate::dict::DictTable;
18use crate::ownership::Access;
19use crate::value::Value;
20
21#[inline(always)]
22pub fn mark_value(t: &mut Tracer, v: Value) {
23    if v.is_ptr() {
24        t.mark(v.as_ptr());
25    }
26}
27
28#[repr(C)]
29pub struct InstanceObj {
30    pub header: Header,
31    fields: [AtomicU64; 0],
32}
33
34impl InstanceObj {
35    #[inline(always)]
36    pub fn field(&self, i: usize) -> &AtomicU64 {
37        // SAFETY: instances are allocated with `nfields` trailing words and callers index within it.
38        unsafe { &*self.fields.as_ptr().add(i) }
39    }
40    #[inline(always)]
41    pub fn get(&self, i: usize) -> Value {
42        Value(self.field(i).load(Ordering::Relaxed))
43    }
44    #[inline(always)]
45    pub fn get_acquire(&self, i: usize) -> Value {
46        Value(self.field(i).load(Ordering::Acquire))
47    }
48    #[inline(always)]
49    pub fn set(&self, i: usize, v: Value) {
50        self.field(i).store(v.0, Ordering::Relaxed)
51    }
52    #[inline(always)]
53    pub fn set_release(&self, i: usize, v: Value) {
54        self.field(i).store(v.0, Ordering::Release)
55    }
56    pub fn nfields(&self) -> usize {
57        unsafe { heap_class(&self.header).nfields() }
58    }
59    pub fn size_for(nfields: usize) -> usize {
60        HEADER_SIZE + 8 * nfields
61    }
62}
63
64#[repr(C)]
65pub struct StrObj {
66    pub header: Header,
67    pub len: usize,
68    /// Cached hash; 0 means not yet computed.
69    pub hash: AtomicU64,
70    /// Cached char count plus one; 0 means not yet computed.
71    pub chars: AtomicU64,
72    data: [u8; 0],
73}
74
75impl StrObj {
76    pub const BASE: usize = std::mem::size_of::<StrObj>();
77    pub fn as_str(&self) -> &str {
78        // SAFETY: strings are created from valid UTF-8 and never mutated.
79        unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.data.as_ptr(), self.len)) }
80    }
81    pub(crate) fn data_ptr(&mut self) -> *mut u8 {
82        self.data.as_mut_ptr()
83    }
84    pub fn char_count(&self) -> usize {
85        let c = self.chars.load(Ordering::Relaxed);
86        if c != 0 {
87            return (c - 1) as usize;
88        }
89        let s = self.as_str();
90        let n = if s.is_ascii() { s.len() } else { s.chars().count() };
91        self.chars.store(n as u64 + 1, Ordering::Relaxed);
92        n
93    }
94    pub fn hash_code(&self) -> u64 {
95        let h = self.hash.load(Ordering::Relaxed);
96        if h != 0 {
97            return h;
98        }
99        let mut h = crate::ops::hash_bytes(self.as_str().as_bytes());
100        if h == 0 {
101            h = 1;
102        }
103        self.hash.store(h, Ordering::Relaxed);
104        h
105    }
106}
107
108#[repr(C)]
109pub struct BigIntObj {
110    pub header: Header,
111    pub value: BigInt,
112}
113
114/// A list's backing array: a capacity word followed by `cap` slots. Slots at or past the list's
115/// length always hold `nil`, so a racing reader that sees a stale length never sees a stale
116/// pointer.
117#[repr(C)]
118pub struct ListBuf {
119    cap: usize,
120    slots: [AtomicU64; 0],
121}
122
123/// The buffer of every list with no capacity. Never written, never freed.
124static EMPTY_LIST_BUF: ListBuf = ListBuf { cap: 0, slots: [] };
125
126impl ListBuf {
127    fn layout(cap: usize) -> Layout {
128        Layout::from_size_align(8 * (cap + 1), 8).expect("list too large")
129    }
130
131    fn alloc(cap: usize) -> *mut ListBuf {
132        if cap == 0 {
133            return &EMPTY_LIST_BUF as *const ListBuf as *mut ListBuf;
134        }
135        unsafe {
136            let p = std::alloc::alloc(Self::layout(cap)) as *mut ListBuf;
137            if p.is_null() {
138                std::alloc::handle_alloc_error(Self::layout(cap));
139            }
140            std::ptr::addr_of_mut!((*p).cap).write(cap);
141            let slots = Self::slots_ptr(p);
142            for i in 0..cap {
143                slots.add(i).write(AtomicU64::new(Value::NIL.0));
144            }
145            p
146        }
147    }
148
149    /// # Safety
150    /// `p` must come from [`ListBuf::alloc`] and be unreachable by any reader.
151    unsafe fn free(p: *mut u8) {
152        let b = p as *mut ListBuf;
153        let cap = (*b).cap;
154        if cap > 0 {
155            std::alloc::dealloc(p, Self::layout(cap));
156        }
157    }
158
159    #[inline(always)]
160    fn slots_ptr(p: *const ListBuf) -> *mut AtomicU64 {
161        unsafe { (p as *mut u8).add(8) as *mut AtomicU64 }
162    }
163
164    /// # Safety
165    /// `p` must be a live buffer; the slice must not outlive the next safepoint.
166    #[inline(always)]
167    unsafe fn slots<'a>(p: *const ListBuf) -> &'a [AtomicU64] {
168        std::slice::from_raw_parts(Self::slots_ptr(p), (*p).cap)
169    }
170}
171
172/// A list: header, length and a pointer to a backing array.
173///
174/// Reads never lock, even on shared lists. The length and buffer pointer are atomic words, and a
175/// reader bounds-checks against the buffer it loaded, so it sees some consistent-enough state and
176/// never reads out of bounds. A writer (the owner, or a thread holding a shared list's lock) that
177/// outgrows the buffer copies it into a new one and swaps the pointer; the old buffer is retired
178/// to the collector, which frees it only once the world is stopped and no reader can hold it.
179#[repr(C)]
180pub struct ListObj {
181    pub header: Header,
182    len: AtomicUsize,
183    buf: AtomicPtr<ListBuf>,
184}
185
186impl ListObj {
187    /// # Safety
188    /// `p` must point to freshly allocated, unpublished list memory.
189    pub(crate) unsafe fn init(p: *mut ListObj, items: Vec<Value>) {
190        let buf = ListBuf::alloc(items.capacity());
191        let slots = ListBuf::slots_ptr(buf);
192        for (i, v) in items.iter().enumerate() {
193            (*slots.add(i)).store(v.0, Ordering::Relaxed);
194        }
195        std::ptr::addr_of_mut!((*p).len).write(AtomicUsize::new(items.len()));
196        std::ptr::addr_of_mut!((*p).buf).write(AtomicPtr::new(buf));
197    }
198
199    #[inline(always)]
200    pub fn len(&self) -> usize {
201        self.len.load(Ordering::Acquire)
202    }
203
204    #[inline(always)]
205    pub fn is_empty(&self) -> bool {
206        self.len() == 0
207    }
208
209    #[inline(always)]
210    fn slots(&self) -> &[AtomicU64] {
211        unsafe { ListBuf::slots(self.buf.load(Ordering::Acquire)) }
212    }
213
214    /// The item at `i`, if in bounds. Safe against concurrent writers; the caller must still have
215    /// passed the ownership read check.
216    #[inline(always)]
217    pub fn get(&self, i: usize) -> Option<Value> {
218        if i >= self.len() {
219            return None;
220        }
221        self.slots().get(i).map(|x| Value(x.load(Ordering::Acquire)))
222    }
223
224    /// As [`ListObj::get`] for a list owned by this thread or frozen, where nothing races.
225    #[inline(always)]
226    pub fn get_local(&self, i: usize) -> Option<Value> {
227        if i >= self.len.load(Ordering::Relaxed) {
228            return None;
229        }
230        let slots = unsafe { ListBuf::slots(self.buf.load(Ordering::Relaxed)) };
231        slots.get(i).map(|x| Value(x.load(Ordering::Relaxed)))
232    }
233
234    /// A snapshot of the items.
235    pub fn to_vec(&self) -> Vec<Value> {
236        let n = self.len();
237        let slots = self.slots();
238        slots[..n.min(slots.len())].iter().map(|x| Value(x.load(Ordering::Acquire))).collect()
239    }
240
241    pub fn capacity(&self) -> usize {
242        unsafe { (*self.buf.load(Ordering::Acquire)).cap }
243    }
244
245    // Writers. Each requires write access per the ownership rules: the owning thread, or the
246    // holder of a shared list's lock. `access` says which, so replaced buffers are freed at once
247    // when no other thread can be reading them.
248
249    /// # Safety
250    /// See above.
251    pub unsafe fn set(&self, i: usize, v: Value) -> bool {
252        if i >= self.len.load(Ordering::Relaxed) {
253            return false;
254        }
255        self.slots()[i].store(v.0, Ordering::Release);
256        true
257    }
258
259    /// # Safety
260    /// See above.
261    pub unsafe fn push(&self, v: Value, access: Access) {
262        let n = self.len.load(Ordering::Relaxed);
263        if n == self.capacity() {
264            self.grow(n + 1, access);
265        }
266        self.slots()[n].store(v.0, Ordering::Release);
267        self.len.store(n + 1, Ordering::Release);
268    }
269
270    /// # Safety
271    /// See above.
272    pub unsafe fn pop(&self) -> Option<Value> {
273        let n = self.len.load(Ordering::Relaxed);
274        if n == 0 {
275            return None;
276        }
277        let slot = &self.slots()[n - 1];
278        let v = Value(slot.load(Ordering::Relaxed));
279        self.len.store(n - 1, Ordering::Release);
280        slot.store(Value::NIL.0, Ordering::Release);
281        Some(v)
282    }
283
284    /// Replaces the contents with `items`, reusing the buffer when it is large enough.
285    ///
286    /// # Safety
287    /// See above.
288    pub unsafe fn replace(&self, items: &[Value], access: Access) {
289        let old_len = self.len.load(Ordering::Relaxed);
290        let n = items.len();
291        if n > self.capacity() {
292            let buf = ListBuf::alloc(n.max(4));
293            let slots = ListBuf::slots(buf);
294            for (s, v) in slots.iter().zip(items) {
295                s.store(v.0, Ordering::Relaxed);
296            }
297            self.swap_buf(buf, access);
298            self.len.store(n, Ordering::Release);
299            return;
300        }
301        let slots = self.slots();
302        if n < old_len {
303            self.len.store(n, Ordering::Release);
304        }
305        for (s, v) in slots.iter().zip(items) {
306            s.store(v.0, Ordering::Release);
307        }
308        for s in &slots[n..old_len.max(n)] {
309            s.store(Value::NIL.0, Ordering::Release);
310        }
311        if n >= old_len {
312            self.len.store(n, Ordering::Release);
313        }
314    }
315
316    /// Runs `f` on a copy of the items and writes the result back.
317    ///
318    /// # Safety
319    /// See above.
320    pub unsafe fn modify<R>(&self, access: Access, f: impl FnOnce(&mut Vec<Value>) -> R) -> R {
321        let mut items = self.to_vec();
322        let r = f(&mut items);
323        self.replace(&items, access);
324        r
325    }
326
327    unsafe fn grow(&self, need: usize, access: Access) {
328        let n = self.len.load(Ordering::Relaxed);
329        let cap = need.max(self.capacity() * 2).max(4);
330        let buf = ListBuf::alloc(cap);
331        let new = ListBuf::slots(buf);
332        for (d, s) in new.iter().zip(&self.slots()[..n]) {
333            d.store(s.load(Ordering::Relaxed), Ordering::Relaxed);
334        }
335        self.swap_buf(buf, access);
336    }
337
338    unsafe fn swap_buf(&self, buf: *mut ListBuf, access: Access) {
339        crate::note_external((*buf).cap * 8);
340        let old = self.buf.swap(buf, Ordering::AcqRel);
341        if (*old).cap == 0 {
342            return;
343        }
344        if access == Access::Local && !crate::ownership::PROMOTE_ON_ACCESS.load(Ordering::Relaxed) {
345            ListBuf::free(old as *mut u8);
346        } else {
347            catallaxy_gc::retire(old as *mut u8, ListBuf::free);
348        }
349    }
350}
351
352#[repr(C)]
353pub struct DictObj {
354    pub header: Header,
355    pub table: UnsafeCell<DictTable>,
356}
357
358impl DictObj {
359    /// # Safety
360    /// As for [`ListObj::items`].
361    #[inline(always)]
362    pub unsafe fn table(&self) -> &DictTable {
363        &*self.table.get()
364    }
365    /// # Safety
366    /// As for [`ListObj::items_mut`].
367    #[inline(always)]
368    #[allow(clippy::mut_from_ref)]
369    pub unsafe fn table_mut(&self) -> &mut DictTable {
370        &mut *self.table.get()
371    }
372}
373
374#[repr(C)]
375pub struct TupleObj {
376    pub header: Header,
377    pub len: usize,
378    elems: [Value; 0],
379}
380
381impl TupleObj {
382    pub fn elems(&self) -> &[Value] {
383        unsafe { std::slice::from_raw_parts(self.elems.as_ptr(), self.len) }
384    }
385    pub(crate) fn elems_mut(&mut self) -> *mut Value {
386        self.elems.as_mut_ptr()
387    }
388    pub fn size_for(n: usize) -> usize {
389        std::mem::size_of::<TupleObj>() + 8 * n
390    }
391}
392
393#[repr(C)]
394pub struct ClosureObj {
395    pub header: Header,
396    pub func: u32,
397    pub ncaps: u32,
398    caps: [Value; 0],
399}
400
401impl ClosureObj {
402    pub fn caps(&self) -> &[Value] {
403        unsafe { std::slice::from_raw_parts(self.caps.as_ptr(), self.ncaps as usize) }
404    }
405    pub(crate) fn caps_mut(&mut self) -> *mut Value {
406        self.caps.as_mut_ptr()
407    }
408    pub fn size_for(n: usize) -> usize {
409        std::mem::size_of::<ClosureObj>() + 8 * n
410    }
411}
412
413#[repr(C)]
414pub struct CellObj {
415    pub header: Header,
416    pub value: AtomicU64,
417}
418
419#[repr(C)]
420pub struct StringBuilderObj {
421    pub header: Header,
422    pub buf: UnsafeCell<String>,
423}
424
425#[repr(C)]
426pub struct MutexObj {
427    pub header: Header,
428    pub raw: parking_lot::ReentrantMutex<()>,
429    pub value: AtomicU64,
430}
431
432#[repr(C)]
433pub struct AtomicObj {
434    pub header: Header,
435    pub value: AtomicU64,
436}
437
438pub struct ChannelInner {
439    pub queue: parking_lot::Mutex<ChannelState>,
440    pub not_empty: parking_lot::Condvar,
441    pub not_full: parking_lot::Condvar,
442    pub capacity: usize,
443}
444
445pub struct ChannelState {
446    pub items: std::collections::VecDeque<Value>,
447    pub closed: bool,
448}
449
450#[repr(C)]
451pub struct ChannelObj {
452    pub header: Header,
453    pub inner: Box<ChannelInner>,
454}
455
456/// Outcome of a finished thread: its value or its panic message.
457pub enum ThreadOutcome {
458    Running,
459    Done(Value),
460    Panicked(String),
461    Joined,
462}
463
464pub struct ThreadInner {
465    pub outcome: parking_lot::Mutex<ThreadOutcome>,
466    pub done: parking_lot::Condvar,
467    pub handle: parking_lot::Mutex<Option<std::thread::JoinHandle<()>>>,
468    /// Thread id of the running thread (for transferring its result).
469    pub tid: AtomicU64,
470}
471
472#[repr(C)]
473pub struct ThreadObj {
474    pub header: Header,
475    pub inner: std::sync::Arc<ThreadInner>,
476}
477
478pub trait NativeData: std::any::Any + Send + Sync {
479    fn as_any(&self) -> &dyn std::any::Any;
480}
481impl<T: std::any::Any + Send + Sync> NativeData for T {
482    fn as_any(&self) -> &dyn std::any::Any {
483        self
484    }
485}
486
487#[repr(C)]
488pub struct NativeObj {
489    pub header: Header,
490    pub data: Box<dyn NativeData>,
491}
492
493// ---------------------------------------------------------------------------------------------
494// GC hooks per object kind.
495
496unsafe fn trace_nothing(_: *const Header, _: &mut Tracer) {}
497
498unsafe fn trace_instance(h: *const Header, t: &mut Tracer) {
499    let o = &*(h as *const InstanceObj);
500    for i in 0..heap_class(h).nfields() {
501        mark_value(t, o.get(i));
502    }
503}
504unsafe fn size_instance(h: *const Header) -> usize {
505    InstanceObj::size_for(heap_class(h).nfields())
506}
507
508unsafe fn size_str(h: *const Header) -> usize {
509    StrObj::BASE + (*(h as *const StrObj)).len
510}
511
512unsafe fn fin_bigint(h: *mut Header) {
513    std::ptr::drop_in_place(&mut (*(h as *mut BigIntObj)).value);
514}
515unsafe fn size_bigint(h: *const Header) -> usize {
516    std::mem::size_of::<BigIntObj>() + (*(h as *const BigIntObj)).value.bits() as usize / 8
517}
518
519unsafe fn trace_list(h: *const Header, t: &mut Tracer) {
520    // The world is stopped; slots past the length are nil.
521    for s in (*(h as *const ListObj)).slots() {
522        mark_value(t, Value(s.load(Ordering::Relaxed)));
523    }
524}
525unsafe fn fin_list(h: *mut Header) {
526    ListBuf::free(*(*(h as *mut ListObj)).buf.get_mut() as *mut u8);
527}
528unsafe fn size_list(h: *const Header) -> usize {
529    std::mem::size_of::<ListObj>() + 8 * (*(h as *const ListObj)).capacity()
530}
531
532unsafe fn trace_dict(h: *const Header, t: &mut Tracer) {
533    (*(*(h as *const DictObj)).table.get()).trace(t);
534}
535unsafe fn fin_dict(h: *mut Header) {
536    std::ptr::drop_in_place((*(h as *mut DictObj)).table.get());
537}
538unsafe fn size_dict(h: *const Header) -> usize {
539    std::mem::size_of::<DictObj>() + (*(*(h as *const DictObj)).table.get()).heap_bytes()
540}
541
542unsafe fn trace_tuple(h: *const Header, t: &mut Tracer) {
543    for &v in (*(h as *const TupleObj)).elems() {
544        mark_value(t, v);
545    }
546}
547unsafe fn size_tuple(h: *const Header) -> usize {
548    TupleObj::size_for((*(h as *const TupleObj)).len)
549}
550
551unsafe fn trace_closure(h: *const Header, t: &mut Tracer) {
552    for &v in (*(h as *const ClosureObj)).caps() {
553        mark_value(t, v);
554    }
555}
556unsafe fn size_closure(h: *const Header) -> usize {
557    ClosureObj::size_for((*(h as *const ClosureObj)).ncaps as usize)
558}
559
560unsafe fn trace_cell(h: *const Header, t: &mut Tracer) {
561    mark_value(t, Value((*(h as *const CellObj)).value.load(Ordering::Relaxed)));
562}
563unsafe fn size_cell(_: *const Header) -> usize {
564    std::mem::size_of::<CellObj>()
565}
566
567unsafe fn fin_sb(h: *mut Header) {
568    std::ptr::drop_in_place((*(h as *mut StringBuilderObj)).buf.get());
569}
570unsafe fn size_sb(h: *const Header) -> usize {
571    std::mem::size_of::<StringBuilderObj>() + (*(*(h as *const StringBuilderObj)).buf.get()).capacity()
572}
573
574unsafe fn trace_mutex(h: *const Header, t: &mut Tracer) {
575    mark_value(t, Value((*(h as *const MutexObj)).value.load(Ordering::Relaxed)));
576}
577unsafe fn fin_mutex(h: *mut Header) {
578    std::ptr::drop_in_place(&mut (*(h as *mut MutexObj)).raw);
579}
580unsafe fn size_mutex(_: *const Header) -> usize {
581    std::mem::size_of::<MutexObj>()
582}
583
584unsafe fn trace_atomic(h: *const Header, t: &mut Tracer) {
585    mark_value(t, Value((*(h as *const AtomicObj)).value.load(Ordering::Relaxed)));
586}
587unsafe fn size_atomic(_: *const Header) -> usize {
588    std::mem::size_of::<AtomicObj>()
589}
590
591unsafe fn trace_channel(h: *const Header, t: &mut Tracer) {
592    // The world is stopped; no thread holds the queue lock across a safepoint.
593    let inner = &(*(h as *const ChannelObj)).inner;
594    let q = inner.queue.data_ptr();
595    for &v in (*q).items.iter() {
596        mark_value(t, v);
597    }
598}
599unsafe fn fin_channel(h: *mut Header) {
600    std::ptr::drop_in_place(&mut (*(h as *mut ChannelObj)).inner);
601}
602unsafe fn size_channel(h: *const Header) -> usize {
603    std::mem::size_of::<ChannelObj>() + 8 * (*(*(h as *const ChannelObj)).inner.queue.data_ptr()).items.capacity()
604}
605
606unsafe fn trace_thread(h: *const Header, t: &mut Tracer) {
607    let inner = &(*(h as *const ThreadObj)).inner;
608    if let ThreadOutcome::Done(v) = &*inner.outcome.data_ptr() {
609        mark_value(t, *v);
610    }
611}
612unsafe fn fin_thread(h: *mut Header) {
613    std::ptr::drop_in_place(&mut (*(h as *mut ThreadObj)).inner);
614}
615unsafe fn size_thread(_: *const Header) -> usize {
616    std::mem::size_of::<ThreadObj>() + 128
617}
618
619unsafe fn fin_native(h: *mut Header) {
620    std::ptr::drop_in_place(&mut (*(h as *mut NativeObj)).data);
621}
622unsafe fn size_native(_: *const Header) -> usize {
623    std::mem::size_of::<NativeObj>()
624}
625
626unsafe fn size_zero(_: *const Header) -> usize {
627    0
628}
629
630#[allow(clippy::type_complexity)]
631pub(crate) fn vtable_for(kind: ObjKind) -> GcVTable {
632    let (trace, finalize, size): (
633        unsafe fn(*const Header, &mut Tracer),
634        Option<unsafe fn(*mut Header)>,
635        unsafe fn(*const Header) -> usize,
636    ) = match kind {
637        ObjKind::Immediate => (trace_nothing, None, size_zero),
638        ObjKind::Instance => (trace_instance, None, size_instance),
639        ObjKind::Str => (trace_nothing, None, size_str),
640        ObjKind::BigInt => (trace_nothing, Some(fin_bigint), size_bigint),
641        ObjKind::List => (trace_list, Some(fin_list), size_list),
642        ObjKind::Dict | ObjKind::Set => (trace_dict, Some(fin_dict), size_dict),
643        ObjKind::Tuple => (trace_tuple, None, size_tuple),
644        ObjKind::Closure => (trace_closure, None, size_closure),
645        ObjKind::Cell => (trace_cell, None, size_cell),
646        ObjKind::StringBuilder => (trace_nothing, Some(fin_sb), size_sb),
647        ObjKind::Mutex => (trace_mutex, Some(fin_mutex), size_mutex),
648        ObjKind::Atomic => (trace_atomic, None, size_atomic),
649        ObjKind::Channel => (trace_channel, Some(fin_channel), size_channel),
650        ObjKind::Thread => (trace_thread, Some(fin_thread), size_thread),
651        ObjKind::Native => (trace_nothing, Some(fin_native), size_native),
652    };
653    GcVTable { trace, finalize, size }
654}
655
656// ---------------------------------------------------------------------------------------------
657// Typed views. Callers must have checked the kind (usually statically, by the type checker).
658
659macro_rules! view {
660    ($fn:ident, $ty:ty) => {
661        /// # Safety
662        /// `v` must be a pointer to an object of this kind.
663        #[inline(always)]
664        pub unsafe fn $fn<'a>(v: Value) -> &'a $ty {
665            &*(v.as_ptr() as *const $ty)
666        }
667    };
668}
669view!(as_instance, InstanceObj);
670view!(as_str_obj, StrObj);
671view!(as_bigint, BigIntObj);
672view!(as_list, ListObj);
673view!(as_dict, DictObj);
674view!(as_tuple, TupleObj);
675view!(as_closure, ClosureObj);
676view!(as_cell, CellObj);
677view!(as_sb, StringBuilderObj);
678view!(as_mutex, MutexObj);
679view!(as_atomic, AtomicObj);
680view!(as_channel, ChannelObj);
681view!(as_thread, ThreadObj);
682view!(as_native, NativeObj);
683
684/// The object kind of a value (`Immediate` for non-pointers).
685#[inline]
686pub fn kind_of(v: Value) -> ObjKind {
687    if v.is_ptr() {
688        unsafe { heap_class(v.as_ptr()).kind }
689    } else {
690        ObjKind::Immediate
691    }
692}
693
694/// Returns the string slice if `v` is a string.
695#[inline]
696pub fn str_of<'a>(v: Value) -> Option<&'a str> {
697    if kind_of(v) == ObjKind::Str {
698        Some(unsafe { as_str_obj(v) }.as_str())
699    } else {
700        None
701    }
702}