Skip to main content

gc_lite/
node.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright (c) 2025-2026 John Ray <996351336@qq.com>
3
4use std::{
5    marker::PhantomData,
6    ops::{Deref, DerefMut},
7    ptr::NonNull,
8};
9
10use crate::gctype::{GcTypeRegistry, payload_offset_of};
11use crate::{GcHeap, GcPartitionId, GcTrace, GcWeak, weak::GcWeakRawId};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(u8)]
15pub enum GcTriColor {
16    White = 0b00,
17    Gray = 0b01,
18    Black = 0b10,
19}
20
21impl From<GcTriColor> for u32 {
22    fn from(color: GcTriColor) -> Self {
23        color as u32
24    }
25}
26
27impl TryFrom<u32> for GcTriColor {
28    type Error = &'static str;
29
30    fn try_from(value: u32) -> Result<Self, Self::Error> {
31        match value {
32            0b00 => Ok(GcTriColor::White),
33            0b01 => Ok(GcTriColor::Gray),
34            0b10 => Ok(GcTriColor::Black),
35            _ => Err("Invalid value for TriColor"),
36        }
37    }
38}
39
40const COLOR_MASK: u32 = 0b11;
41
42bitflags::bitflags! {
43    #[repr(transparent)]
44    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
45    pub struct GcNodeFlag :u8 {
46        /// allocated from arena (bump+free-list)
47        const ARENA_ALLOC = 1 << 2;
48        /// node is in gray list (mark phase optimization)
49        const GRAY_LISTED = 1 << 3;
50        /// is root node
51        const ROOT = 1 << 5;
52        /// node is inside a GcContext
53        const LOCAL = 1 << 6;
54        /// internal traversal visited flag
55        const TRAVERSE_VISITED = 1 << 7;
56    }
57}
58
59/// GC node info
60#[repr(C)]
61pub struct GcHead {
62    /// Attributes of node:
63    /// * bit 24-31: debug sentinel (debug build)
64    /// * bit 16-23: reserved
65    /// * bit 8-15:  gc datatype id
66    /// * bit 5-7:   flags
67    /// * bit 2-4:   reserved
68    /// * bit 0-1:   TriColor state
69    pub(super) attrs: u32,
70
71    /// XRef partition id (16bit) + Partition id (16bit)
72    pub(super) partition: u32,
73
74    pub(super) weak_id: GcWeakRawId,
75
76    /// Pointer to next object (for list traversal)
77    pub(super) next: Option<NonNull<GcHead>>,
78
79    #[cfg(debug_assertions)]
80    pub(crate) dbg_string: std::borrow::Cow<'static, str>,
81}
82
83impl std::fmt::Debug for GcHead {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        let mut s = f.debug_struct("GcNode");
86        s.field("ptr", &(self as *const Self))
87            .field("partition", &self.partition_id())
88            .field("color", &self.color())
89            .field("local", &self.is_local());
90
91        if self.is_root() {
92            s.field("root", &true);
93        }
94        if !self.weak_id.is_null() {
95            let w = self.weak_id;
96            s.field("weakref", &format!("{}#{}", w.index(), w.version()));
97        }
98
99        #[cfg(debug_assertions)]
100        {
101            s.field("dbg_string", &self.dbg_string);
102        }
103
104        s.finish()
105    }
106}
107
108impl GcHead {
109    /// Get gc node data type id
110    #[inline(always)]
111    pub(crate) fn dtype(&self) -> u8 {
112        ((self.attrs & 0xFF00) >> 8) as u8
113    }
114
115    /// Get the current TriColor state.
116    #[inline(always)]
117    pub(crate) fn color(&self) -> GcTriColor {
118        // This should not fail if the internal state is managed correctly.
119        GcTriColor::try_from(self.attrs & COLOR_MASK).unwrap()
120    }
121
122    /// Set the TriColor state, preserving other flags.
123    #[inline(always)]
124    pub(crate) fn set_color(&mut self, color: GcTriColor) {
125        self.attrs = (self.attrs & !COLOR_MASK) | (color as u32);
126    }
127
128    #[inline(always)]
129    pub(crate) fn flags(&self) -> GcNodeFlag {
130        GcNodeFlag::from_bits_truncate(self.attrs as u8)
131    }
132
133    /// Add a flag.
134    #[inline(always)]
135    pub(crate) fn insert_flag(&mut self, flag: GcNodeFlag) {
136        self.attrs |= flag.bits() as u32;
137    }
138
139    /// Remove a flag.
140    #[inline(always)]
141    pub(crate) fn remove_flag(&mut self, flag: GcNodeFlag) {
142        self.attrs &= !(flag.bits() as u32);
143    }
144
145    /// Check if a flag is present.
146    #[inline(always)]
147    pub(crate) fn contains_flag(&self, flag: GcNodeFlag) -> bool {
148        (self.attrs & flag.bits() as u32) == flag.bits() as u32
149    }
150
151    /// Check if root node
152    #[inline(always)]
153    pub fn is_root(&self) -> bool {
154        self.contains_flag(GcNodeFlag::ROOT)
155    }
156
157    #[inline(always)]
158    pub fn is_local(&self) -> bool {
159        self.contains_flag(GcNodeFlag::LOCAL)
160    }
161
162    #[inline]
163    pub fn is_root_or_local(&self) -> bool {
164        let f = self.flags();
165        f.intersects(GcNodeFlag::ROOT | GcNodeFlag::LOCAL)
166    }
167
168    #[inline(always)]
169    pub(super) fn traverse_visited(&self) -> bool {
170        self.contains_flag(GcNodeFlag::TRAVERSE_VISITED)
171    }
172
173    #[inline(always)]
174    pub(super) fn set_traverse_visited(&mut self, visited: bool) {
175        if visited {
176            self.insert_flag(GcNodeFlag::TRAVERSE_VISITED);
177        } else {
178            self.remove_flag(GcNodeFlag::TRAVERSE_VISITED);
179        }
180    }
181
182    /// Check if node is already in the gray list (O(1) check)
183    #[inline(always)]
184    pub(super) fn is_gray_listed(&self) -> bool {
185        (self.attrs & (GcNodeFlag::GRAY_LISTED.bits() as u32)) != 0
186    }
187
188    /// Set/clear the gray-listed flag
189    #[inline(always)]
190    pub(super) fn set_gray_listed(&mut self, b: bool) {
191        if b {
192            self.attrs |= GcNodeFlag::GRAY_LISTED.bits() as u32;
193        } else {
194            self.attrs &= !(GcNodeFlag::GRAY_LISTED.bits() as u32);
195        }
196    }
197
198    /// Get node's partition id
199    #[inline(always)]
200    pub fn partition_id(&self) -> GcPartitionId {
201        GcPartitionId((self.partition & 0x0000_FFFF) as u16)
202    }
203
204    /// Get raw pointer to payload data using the heap's type registry.
205    #[inline(always)]
206    pub fn payload(&self, registry: &GcTypeRegistry) -> NonNull<u8> {
207        #[cfg(debug_assertions)]
208        self.debug_assert_node_valid_simple();
209
210        let info = &registry.type_info_list[self.dtype() as usize];
211        info.payload_ptr(NonNull::from_ref(self))
212    }
213
214    #[inline(always)]
215    pub(crate) fn payload_for<T>(&self) -> NonNull<u8> {
216        #[cfg(debug_assertions)]
217        self.debug_assert_node_valid_simple();
218
219        unsafe {
220            NonNull::from_ref(self)
221                .cast::<u8>()
222                .add(payload_offset_of::<T>())
223        }
224    }
225}
226
227pub trait GcNode: GcTrace {
228    /// Node data type id
229    const GC_TYPE_ID: u8;
230
231    /// get gc ref
232    fn gc_ref(&self) -> GcRef<Self>
233    where
234        Self: std::marker::Sized;
235
236    /// get gc node head pointer
237    #[inline(always)]
238    fn gc_head_ptr(&self) -> std::ptr::NonNull<GcHead>
239    where
240        Self: std::marker::Sized,
241    {
242        self.gc_ref().node_ptr()
243    }
244
245    /// get gc node head info
246    #[inline(always)]
247    fn gc_head(&self) -> &GcHead
248    where
249        Self: std::marker::Sized,
250    {
251        unsafe { self.gc_head_ptr().as_ref() }
252    }
253
254    /// get gc node head info
255    #[inline(always)]
256    fn gc_head_mut(&mut self) -> &mut GcHead
257    where
258        Self: std::marker::Sized,
259    {
260        unsafe { self.gc_head_ptr().as_mut() }
261    }
262}
263
264/// Garbage collection reference
265#[repr(transparent)]
266pub struct GcRef<T: GcNode> {
267    pub(super) head_ptr: NonNull<GcHead>,
268    pub(super) _marker: PhantomData<T>,
269}
270
271impl<T: GcNode> Clone for GcRef<T> {
272    #[inline(always)]
273    fn clone(&self) -> Self {
274        *self
275    }
276}
277
278impl<T: GcNode> Copy for GcRef<T> {}
279
280impl<T: GcNode> PartialEq for GcRef<T> {
281    #[inline(always)]
282    fn eq(&self, other: &Self) -> bool {
283        self.head_ptr == other.head_ptr
284    }
285}
286
287impl<T: GcNode> Eq for GcRef<T> {}
288
289impl<T: GcNode> From<GcRef<T>> for NonNull<GcHead> {
290    #[inline(always)]
291    fn from(r: GcRef<T>) -> Self {
292        r.head_ptr
293    }
294}
295
296impl<T: GcNode> From<&GcRef<T>> for NonNull<GcHead> {
297    #[inline(always)]
298    fn from(r: &GcRef<T>) -> Self {
299        r.head_ptr
300    }
301}
302
303impl<T: GcNode> std::fmt::Debug for GcRef<T> {
304    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305        write!(f, "GcRef<{:p}>", self.head_ptr)
306    }
307}
308
309impl<T: GcNode> GcRef<T> {
310    /// Access the underlying GC-managed object as a reference.
311    ///
312    /// # Safety
313    ///
314    /// The caller must ensure the GC object is still alive and has not been
315    /// collected (e.g., it is protected by a root/LOCAL flag, or the GC
316    /// heap is guaranteed not to run a collection cycle).
317    #[inline(always)]
318    pub unsafe fn as_ref(&self) -> &T {
319        unsafe {
320            self.head_ptr
321                .as_ref()
322                .payload_for::<T>()
323                .cast::<T>()
324                .as_ref()
325        }
326    }
327
328    /// Access the underlying GC-managed object as a mutable reference.
329    ///
330    /// # Safety
331    ///
332    /// Same as [`as_ref`](Self::as_ref).
333    #[inline(always)]
334    pub unsafe fn as_mut(&mut self) -> &mut T {
335        unsafe {
336            self.head_ptr
337                .as_mut()
338                .payload_for::<T>()
339                .cast::<T>()
340                .as_mut()
341        }
342    }
343
344    /// Create GcRef<T> from &T reference
345    ///
346    /// # Safety
347    ///
348    /// Caller must ensure the passed reference comes from a valid GC-managed object
349    /// that was allocated via `GcRef<T>`. Passing a reference from any other source
350    /// (stack, heap, etc.) is undefined behavior.
351    #[inline]
352    pub unsafe fn try_from_ref(heap: &GcHeap, data_ref: &T) -> Option<Self> {
353        let node = unsafe {
354            NonNull::from_ref(data_ref)
355                .cast::<u8>()
356                .sub(payload_offset_of::<T>())
357                .cast::<GcHead>()
358        };
359
360        if T::GC_TYPE_ID == unsafe { node.as_ref().dtype() } {
361            #[cfg(debug_assertions)]
362            unsafe {
363                node.as_ref().debug_assert_node_valid(heap);
364            }
365
366            Some(Self {
367                head_ptr: node,
368                _marker: PhantomData,
369            })
370        } else {
371            None
372        }
373    }
374
375    /// Unsafe conversion from &T to GcRef<T>, main focus on speed.
376    ///
377    /// # Safety
378    ///
379    /// Caller must ensure &T comes from GcRef<T>, otherwise consequences are unpredictable.
380    #[inline]
381    pub unsafe fn from_ref_unchecked(data_ref: &T) -> Self {
382        let node = unsafe {
383            NonNull::from_ref(data_ref)
384                .cast::<u8>()
385                .sub(payload_offset_of::<T>())
386                .cast::<GcHead>()
387        };
388
389        #[cfg(debug_assertions)]
390        unsafe {
391            node.as_ref().debug_assert_node_valid_simple();
392        }
393
394        Self {
395            head_ptr: node,
396            _marker: PhantomData,
397        }
398    }
399
400    /// Apply a mutator function to the GC-managed object, with write barrier.
401    ///
402    /// # Safety
403    ///
404    /// The caller must ensure the GC object is still alive and valid.
405    /// If the `mutator` writes new GC references into the object, the write
406    /// barrier will handle tri-color invariant for incremental GC.
407    #[inline]
408    pub unsafe fn with_write_barrier<F, R>(&mut self, heap: &mut GcHeap, mutator: F) -> R
409    where
410        F: FnOnce(&mut T) -> R,
411    {
412        // Only enforce the tri-color invariant when a GC marking cycle
413        // is in progress for this node's partition.
414        if unsafe { heap.is_node_partition_marking(*self) } {
415            // SAFETY: self is alive (caller guarantees it via the unsafe fn contract).
416            let node = unsafe { self.head_ptr.as_mut() };
417            if node.color() == GcTriColor::Black {
418                node.set_color(GcTriColor::Gray);
419                heap.add_gray_node(self.head_ptr);
420            }
421        }
422
423        // SAFETY: self is alive (caller guarantees it via the unsafe fn contract).
424        let value = unsafe {
425            self.head_ptr
426                .as_mut()
427                .payload_for::<T>()
428                .cast::<T>()
429                .as_mut()
430        };
431        mutator(value)
432    }
433
434    /// Get a raw pointer to the GC-managed payload.
435    ///
436    /// # Safety
437    ///
438    /// The caller must ensure the GC object is still alive.
439    #[inline]
440    pub unsafe fn as_ptr(&self) -> NonNull<T> {
441        unsafe { self.head_ptr.as_ref().payload_for::<T>().cast::<T>() }
442    }
443
444    /// Create a weak reference from this GC reference.
445    ///
446    /// # Safety
447    ///
448    /// The caller must ensure the GC object is still alive.
449    #[inline(always)]
450    pub unsafe fn downgrade(&self, heap: &mut GcHeap) -> GcWeak<T> {
451        heap.downgrade(self)
452    }
453
454    /// Check if this is a root object.
455    ///
456    /// # Safety
457    ///
458    /// The caller must ensure the GC object is still alive.
459    #[inline(always)]
460    pub unsafe fn is_root(&self) -> bool {
461        unsafe { self.head_ptr.as_ref().is_root() }
462    }
463
464    /// Get node raw pointer (always safe — just returns the raw pointer value).
465    #[inline(always)]
466    pub fn node_ptr(&self) -> NonNull<GcHead> {
467        self.head_ptr
468    }
469
470    /// Get node info (GC head metadata).
471    ///
472    /// # Safety
473    ///
474    /// The caller must ensure the GC object is still alive.
475    #[inline(always)]
476    pub unsafe fn node_info(&self) -> &GcHead {
477        unsafe { self.head_ptr.as_ref() }
478    }
479}
480
481/// A safe GC reference with a lifetime guarantee.
482///
483/// `Gc<'a, T>` wraps a [`GcRef<T>`] and provides safe `Deref`/`DerefMut`
484/// implementations, where the lifetime `'a` proves that the GC object is
485/// alive and protected (e.g., by a scope or a heap borrow).
486///
487/// # Safety invariant
488///
489/// The lifetime `'a` must be derived from a source that guarantees the
490/// GC object will not be collected during `'a`. Typical sources:
491/// - A [`GcScope`](crate::GcScope) or [`GcScopeState`](crate::GcScopeState)
492///   for scope-local allocations
493/// - A `&GcHeap` borrow for [`GcWeak::upgrade`](crate::GcWeak::upgrade) results
494#[repr(transparent)]
495pub struct Gc<'a, T: GcNode> {
496    inner: GcRef<T>,
497    _marker: PhantomData<&'a T>,
498}
499
500impl<'a, T: GcNode> Deref for Gc<'a, T> {
501    type Target = T;
502
503    #[inline(always)]
504    fn deref(&self) -> &Self::Target {
505        // SAFETY: The lifetime 'a guarantees the object is alive.
506        unsafe { self.inner.as_ref() }
507    }
508}
509
510impl<'a, T: GcNode> DerefMut for Gc<'a, T> {
511    #[inline(always)]
512    fn deref_mut(&mut self) -> &mut Self::Target {
513        // SAFETY: The lifetime 'a guarantees the object is alive.
514        unsafe { self.inner.as_mut() }
515    }
516}
517
518impl<'a, T: GcNode> Clone for Gc<'a, T> {
519    #[inline(always)]
520    fn clone(&self) -> Self {
521        *self
522    }
523}
524
525impl<'a, T: GcNode> Copy for Gc<'a, T> {}
526
527impl<'a, T: GcNode> PartialEq for Gc<'a, T> {
528    #[inline(always)]
529    fn eq(&self, other: &Self) -> bool {
530        self.inner == other.inner
531    }
532}
533
534impl<'a, T: GcNode> Eq for Gc<'a, T> {}
535
536impl<'a, T: GcNode> std::fmt::Debug for Gc<'a, T> {
537    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
538        write!(f, "Gc<{:p}>", self.inner.head_ptr)
539    }
540}
541
542impl<'a, T: GcNode> Gc<'a, T> {
543    /// Create a `Gc` from a raw `GcRef` and a lifetime proof.
544    ///
545    /// # Safety
546    ///
547    /// The caller must ensure that the object referenced by `inner` stays
548    /// alive for the entire lifetime `'a`. This is typically guaranteed
549    /// by tying `'a` to a scope or a heap borrow.
550    #[inline(always)]
551    pub unsafe fn from_raw(inner: GcRef<T>) -> Self {
552        Self {
553            inner,
554            _marker: PhantomData,
555        }
556    }
557
558    /// Convert back to the raw `GcRef` handle.
559    #[inline(always)]
560    pub fn into_raw(self) -> GcRef<T> {
561        self.inner
562    }
563
564    /// Access the raw `GcRef` without consuming self.
565    #[inline(always)]
566    pub fn as_raw(&self) -> &GcRef<T> {
567        &self.inner
568    }
569}
570
571impl GcHeap {
572    /// Establishes a directed reference from `master` to `slave` and performs
573    /// the necessary write barrier for tri-color incremental GC.
574    ///
575    /// # When to use
576    ///
577    /// Call this whenever a GC node (`master`) starts referencing another GC
578    /// node (`slave`) through a pointer write, e.g.:
579    ///
580    /// - Setting an object property to an object/string value
581    /// - Pushing an element into an array
582    /// - Storing a result value into a Promise
583    /// - Updating a closure variable reference
584    ///
585    /// # Write barrier semantics
586    ///
587    /// In tri-color marking, if `master` is already **Black** (fully traced)
588    /// and `slave` is **White** (not yet traced), the slave would be
589    /// incorrectly swept as garbage. This method prevents that by:
590    ///
591    /// 1. Checking the colors of `master` and `slave`.
592    /// 2. If `master` is Black and `slave` is White/Gray, marking `slave`
593    ///    as **Gray** and enqueuing it into the gray list of its partition,
594    ///    ensuring it will be traced in the current GC cycle.
595    ///
596    /// # Safety
597    ///
598    /// Both pointers must point to valid, live GC nodes managed by this heap.
599    pub fn bind(&mut self, master: NonNull<GcHead>, mut slave: NonNull<GcHead>) {
600        #[cfg(debug_assertions)]
601        unsafe {
602            master.as_ref().debug_assert_node_valid(self);
603            slave.as_ref().debug_assert_node_valid(self);
604        }
605
606        // tri-color write barrier
607        unsafe {
608            if matches!(
609                (master.as_ref().color(), slave.as_ref().color()),
610                (GcTriColor::Black, GcTriColor::White | GcTriColor::Gray)
611            ) {
612                slave.as_mut().set_color(GcTriColor::Gray);
613
614                let slave_pid = slave.as_ref().partition_id();
615                if self.partition(slave_pid).is_some_and(|p| p.is_marking()) {
616                    self.add_gray_node(slave);
617                }
618            }
619        }
620
621        // Cross-partition reference tracking.
622        // If master and slave belong to different partitions, ensure the slave
623        // is reachable from the master's partition during marking.
624        unsafe {
625            let master_pid = master.as_ref().partition_id();
626            let slave_pid = slave.as_ref().partition_id();
627
628            if master_pid != slave_pid {
629                let slave_color = slave.as_ref().color();
630
631                if matches!(slave_color, GcTriColor::White | GcTriColor::Gray)
632                    && let Some(slave_par) = self.partition_mut(slave_pid)
633                    && slave_par.is_marking()
634                {
635                    slave.as_mut().set_color(GcTriColor::Gray);
636                    if !slave.as_ref().is_gray_listed() {
637                        slave.as_mut().set_gray_listed(true);
638                        slave_par.gray_list.push(slave);
639                    }
640                }
641            }
642        }
643    }
644}
645
646#[cfg(debug_assertions)]
647impl GcHead {
648    pub fn debug_set_dbg_string(&mut self, str: std::borrow::Cow<'static, str>) {
649        self.dbg_string = str;
650    }
651
652    pub fn debug_dbg_string(&self) -> &std::borrow::Cow<'static, str> {
653        &self.dbg_string
654    }
655
656    pub fn debug_assert_node_valid_simple(&self) {
657        if !std::thread::panicking() {
658            debug_assert!(
659                ((self.attrs >> 24) & 0xFF) == 0xFF && self.next.is_none_or(|n| n.is_aligned()),
660                "bad node: {self:p}"
661            );
662        }
663    }
664
665    pub fn debug_assert_node_valid(&self, heap: &GcHeap) {
666        if !std::thread::panicking() {
667            debug_assert!(
668                heap.dbg_living_nodes.contains(&NonNull::from_ref(self)),
669                "[O.o] bad node: {self:p}"
670            );
671            self.debug_assert_node_valid_simple();
672        }
673    }
674}