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::{GcHeap, GcPartitionId, GcTrace, GcWeak, weak::GcWeakRawId};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[repr(u8)]
14pub enum GcTriColor {
15    White = 0b00,
16    Gray = 0b01,
17    Black = 0b10,
18}
19
20impl From<GcTriColor> for u32 {
21    fn from(color: GcTriColor) -> Self {
22        color as u32
23    }
24}
25
26impl TryFrom<u32> for GcTriColor {
27    type Error = &'static str;
28
29    fn try_from(value: u32) -> Result<Self, Self::Error> {
30        match value {
31            0b00 => Ok(GcTriColor::White),
32            0b01 => Ok(GcTriColor::Gray),
33            0b10 => Ok(GcTriColor::Black),
34            _ => Err("Invalid value for TriColor"),
35        }
36    }
37}
38
39const COLOR_MASK: u32 = 0b11;
40
41bitflags::bitflags! {
42    #[repr(transparent)]
43    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
44    pub struct GcNodeFlag :u8 {
45        /// is root node
46        const ROOT = 1 << 5;
47
48        /// node is inside a GcContext
49        const LOCAL = 1 << 6;
50
51        /// internal traversal visited flag
52        const TRAVERSE_VISITED = 1 << 7;
53    }
54}
55
56/// GC node info
57pub struct GcHead {
58    /// Attributes of node:
59    /// * bit 24-31: debug sentinel (debug build)
60    /// * bit 16-23: reserved
61    /// * bit 8-15:  gc datatype id
62    /// * bit 5-7:   flags
63    /// * bit 2-4:   reserved
64    /// * bit 0-1:   TriColor state
65    pub(super) attrs: u32,
66
67    /// XRef partition id (16bit) + Partition id (16bit)
68    pub(super) partition: u32,
69
70    pub(super) weak_id: GcWeakRawId,
71
72    /// Pointer to next object (for list traversal)
73    pub(super) next: Option<NonNull<GcHead>>,
74
75    #[cfg(debug_assertions)]
76    pub(crate) dbg_scope_depth: u8,
77    #[cfg(debug_assertions)]
78    pub(crate) dbg_string: std::borrow::Cow<'static, str>,
79}
80
81impl std::fmt::Debug for GcHead {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        let mut s = f.debug_struct("GcNode");
84        s.field("ptr", &(self as *const Self))
85            .field("partition", &self.partition_id())
86            .field("color", &self.color())
87            .field("local", &self.is_local());
88
89        if self.is_root() {
90            s.field("root", &true);
91        }
92        if !self.weak_id.is_null() {
93            let w = self.weak_id;
94            s.field("weakref", &format!("{}#{}", w.index(), w.version()));
95        }
96
97        #[cfg(debug_assertions)]
98        {
99            s.field("scope", &self.dbg_scope_depth)
100                .field("dbg_string", &self.dbg_string);
101        }
102
103        s.finish()
104    }
105}
106
107impl GcHead {
108    /// Get gc node data type id
109    #[inline(always)]
110    pub(crate) fn dtype(&self) -> u8 {
111        ((self.attrs & 0xFF00) >> 8) as u8
112    }
113
114    /// Get the current TriColor state.
115    #[inline(always)]
116    pub(crate) fn color(&self) -> GcTriColor {
117        // This should not fail if the internal state is managed correctly.
118        GcTriColor::try_from(self.attrs & COLOR_MASK).unwrap()
119    }
120
121    /// Set the TriColor state, preserving other flags.
122    #[inline(always)]
123    pub(crate) fn set_color(&mut self, color: GcTriColor) {
124        self.attrs = (self.attrs & !COLOR_MASK) | (color as u32);
125    }
126
127    #[inline(always)]
128    pub(crate) fn flags(&self) -> GcNodeFlag {
129        GcNodeFlag::from_bits_truncate(self.attrs as u8)
130    }
131
132    /// Add a flag.
133    #[inline(always)]
134    pub(crate) fn insert_flag(&mut self, flag: GcNodeFlag) {
135        self.attrs |= flag.bits() as u32;
136    }
137
138    /// Remove a flag.
139    #[inline(always)]
140    pub(crate) fn remove_flag(&mut self, flag: GcNodeFlag) {
141        self.attrs &= !(flag.bits() as u32);
142    }
143
144    /// Check if a flag is present.
145    #[inline(always)]
146    pub(crate) fn contains_flag(&self, flag: GcNodeFlag) -> bool {
147        (self.attrs & flag.bits() as u32) == flag.bits() as u32
148    }
149
150    /// Check if root node
151    #[inline(always)]
152    pub fn is_root(&self) -> bool {
153        self.contains_flag(GcNodeFlag::ROOT)
154    }
155
156    #[inline(always)]
157    pub fn is_local(&self) -> bool {
158        self.contains_flag(GcNodeFlag::LOCAL)
159    }
160
161    #[inline]
162    pub fn is_root_or_local(&self) -> bool {
163        let f = self.flags();
164        f.intersects(GcNodeFlag::ROOT | GcNodeFlag::LOCAL)
165    }
166
167    #[inline(always)]
168    pub(super) fn traverse_visited(&self) -> bool {
169        self.contains_flag(GcNodeFlag::TRAVERSE_VISITED)
170    }
171
172    #[inline(always)]
173    pub(super) fn set_traverse_visited(&mut self, visited: bool) {
174        if visited {
175            self.insert_flag(GcNodeFlag::TRAVERSE_VISITED);
176        } else {
177            self.remove_flag(GcNodeFlag::TRAVERSE_VISITED);
178        }
179    }
180
181    /// Get node's partition id
182    #[inline(always)]
183    pub fn partition_id(&self) -> GcPartitionId {
184        GcPartitionId((self.partition & 0x0000_FFFF) as u16)
185    }
186
187    #[inline(always)]
188    pub(crate) fn set_partition_id(&mut self, id: GcPartitionId) {
189        debug_assert!(self.partition_id().is_null() || self.partition_id() == id);
190        self.partition = (self.partition & 0xFFFF_0000) | id.0 as u32;
191    }
192
193    /// get raw pointer to payload data
194    #[inline(always)]
195    pub fn payload(&self) -> NonNull<u8> {
196        #[cfg(debug_assertions)]
197        self.debug_assert_node_valid_simple();
198
199        unsafe { NonNull::from_ref(self).add(1).cast::<u8>() }
200    }
201}
202
203pub trait GcNode: GcTrace {
204    /// Node data type id
205    const GC_TYPE_ID: u8;
206
207    /// get gc ref
208    fn gc_ref(&self) -> GcRef<Self>
209    where
210        Self: std::marker::Sized;
211
212    /// get gc node head pointer
213    #[inline(always)]
214    fn gc_head_ptr(&self) -> std::ptr::NonNull<GcHead>
215    where
216        Self: std::marker::Sized,
217    {
218        self.gc_ref().node_ptr()
219    }
220
221    /// get gc node head info
222    #[inline(always)]
223    fn gc_head(&self) -> &GcHead
224    where
225        Self: std::marker::Sized,
226    {
227        unsafe { self.gc_head_ptr().as_ref() }
228    }
229
230    /// get gc node head info
231    #[inline(always)]
232    fn gc_head_mut(&mut self) -> &mut GcHead
233    where
234        Self: std::marker::Sized,
235    {
236        unsafe { self.gc_head_ptr().as_mut() }
237    }
238}
239
240/// Garbage collection reference
241#[repr(transparent)]
242pub struct GcRef<T: GcNode> {
243    pub(super) head_ptr: NonNull<GcHead>,
244    pub(super) _marker: PhantomData<T>,
245}
246
247impl<T: GcNode> Deref for GcRef<T> {
248    type Target = T;
249
250    #[inline(always)]
251    fn deref(&self) -> &Self::Target {
252        unsafe { self.head_ptr.as_ref().payload().cast::<T>().as_ref() }
253    }
254}
255
256impl<T: GcNode> DerefMut for GcRef<T> {
257    /// FIXME: DerefMut breaks gc node write barrier. This should be disabled.
258    #[inline(always)]
259    fn deref_mut(&mut self) -> &mut Self::Target {
260        unsafe { self.head_ptr.as_mut().payload().cast::<T>().as_mut() }
261    }
262}
263
264impl<T: GcNode> Clone for GcRef<T> {
265    fn clone(&self) -> Self {
266        *self
267    }
268}
269
270impl<T: GcNode> Copy for GcRef<T> {}
271
272impl<T: GcNode> PartialEq for GcRef<T> {
273    #[inline(always)]
274    fn eq(&self, other: &Self) -> bool {
275        self.head_ptr == other.head_ptr
276    }
277}
278
279impl<T: GcNode> Eq for GcRef<T> {}
280
281impl<T: GcNode> From<GcRef<T>> for NonNull<GcHead> {
282    #[inline(always)]
283    fn from(r: GcRef<T>) -> Self {
284        r.head_ptr
285    }
286}
287
288impl<T: GcNode> From<&GcRef<T>> for NonNull<GcHead> {
289    #[inline(always)]
290    fn from(r: &GcRef<T>) -> Self {
291        r.head_ptr
292    }
293}
294
295impl<T: GcNode> std::fmt::Debug for GcRef<T> {
296    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297        unsafe { write!(f, "GcRef<{:?}>", self.head_ptr.as_ref()) }
298    }
299}
300
301impl<T: GcNode> GcRef<T> {
302    /// Create GcRef<T> from &T reference
303    ///
304    /// This method verifies that the passed reference comes from a valid GC object.
305    /// It ensures safety by checking if the corresponding GcHead is in the GC context.
306    ///
307    /// # Parameters
308    /// - `data_ref`: Reference to convert, must come from valid GcRef object
309    ///
310    /// # Return Value
311    /// - `Some(GcRef<T>)`: If reference comes from valid GC object
312    /// - `None`: If reference is not from GC object or object is invalid
313    ///
314    /// # Safety
315    /// Caller must ensure the passed reference indeed comes from a valid GcRef object.
316    pub fn try_from_ref(heap: &GcHeap, data_ref: &T) -> Option<Self> {
317        let node = unsafe {
318            NonNull::from_ref(data_ref)
319                .cast::<u8>()
320                .sub(std::mem::size_of::<GcHead>())
321                .cast::<GcHead>()
322        };
323
324        if T::GC_TYPE_ID == unsafe { node.as_ref().dtype() } {
325            #[cfg(debug_assertions)]
326            unsafe {
327                node.as_ref().debug_assert_node_valid(heap);
328            }
329
330            Some(Self {
331                head_ptr: node,
332                _marker: PhantomData,
333            })
334        } else {
335            None
336        }
337    }
338
339    /// Unsafe conversion from &T to GcRef<T>, main focus on speed.
340    ///
341    /// # Safety
342    ///
343    /// Caller must ensure &T comes from GcRef<T>, otherwise consequences are unpredictable.
344    #[inline]
345    pub unsafe fn from_ref_unchecked(data_ref: &T) -> Self {
346        let node = unsafe { NonNull::from_ref(data_ref).cast::<GcHead>().sub(1) };
347
348        #[cfg(debug_assertions)]
349        unsafe {
350            node.as_ref().debug_assert_node_valid_simple();
351        }
352
353        Self {
354            head_ptr: node,
355            _marker: PhantomData,
356        }
357    }
358
359    pub fn with_mut<F, R>(&mut self, heap: &mut GcHeap, mutator: F) -> R
360    where
361        F: FnOnce(&mut T) -> R,
362    {
363        let head = unsafe { self.head_ptr.as_mut() };
364        if head.color() == GcTriColor::Black {
365            head.set_color(GcTriColor::Gray);
366            heap.add_gray_node(self.head_ptr);
367        }
368
369        let value = unsafe { head.payload().cast::<T>().as_mut() };
370        mutator(value)
371    }
372
373    #[inline]
374    pub fn as_ptr(&self) -> NonNull<T> {
375        unsafe { self.head_ptr.as_ref().payload().cast::<T>() }
376    }
377
378    #[inline(always)]
379    pub fn downgrade(&self, heap: &mut GcHeap) -> GcWeak<T> {
380        heap.downgrade(self)
381    }
382
383    /// check if this is root object
384    #[inline(always)]
385    pub fn is_root(&self) -> bool {
386        unsafe { self.head_ptr.as_ref().is_root() }
387    }
388
389    /// get node raw pointer
390    #[inline(always)]
391    pub fn node_ptr(&self) -> NonNull<GcHead> {
392        self.head_ptr
393    }
394
395    /// get node info
396    #[inline(always)]
397    pub fn node_info(&self) -> &GcHead {
398        unsafe { self.head_ptr.as_ref() }
399    }
400}
401
402impl GcHeap {
403    /// Establishes a directed reference from `master` to `slave` and performs
404    /// the necessary write barrier for tri-color incremental GC.
405    ///
406    /// # When to use
407    ///
408    /// Call this whenever a GC node (`master`) starts referencing another GC
409    /// node (`slave`) through a pointer write, e.g.:
410    ///
411    /// - Setting an object property to an object/string value
412    /// - Pushing an element into an array
413    /// - Storing a result value into a Promise
414    /// - Updating a closure variable reference
415    ///
416    /// # Write barrier semantics
417    ///
418    /// In tri-color marking, if `master` is already **Black** (fully traced)
419    /// and `slave` is **White** (not yet traced), the slave would be
420    /// incorrectly swept as garbage. This method prevents that by:
421    ///
422    /// 1. Checking the colors of `master` and `slave`.
423    /// 2. If `master` is Black and `slave` is White/Gray, marking `slave`
424    ///    as **Gray** and enqueuing it into the gray list of its partition,
425    ///    ensuring it will be traced in the current GC cycle.
426    /// 3. If `master` and `slave` belong to different GC partitions,
427    ///    recording a cross-partition reference (xref) so that the slave's
428    ///    partition can find it during marking.
429    ///
430    /// # Safety
431    ///
432    /// Both pointers must point to valid, live GC nodes managed by this heap.
433    pub fn bind(&mut self, master: NonNull<GcHead>, mut slave: NonNull<GcHead>) {
434        #[cfg(debug_assertions)]
435        unsafe {
436            master.as_ref().debug_assert_node_valid(self);
437            slave.as_ref().debug_assert_node_valid(self);
438        }
439
440        // tri-color write barrier
441        unsafe {
442            if matches!(
443                (master.as_ref().color(), slave.as_ref().color()),
444                (GcTriColor::Black, GcTriColor::White | GcTriColor::Gray)
445            ) {
446                slave.as_mut().set_color(GcTriColor::Gray);
447
448                if self
449                    .partition(slave.as_ref().partition_id())
450                    .is_some_and(|p| p.is_marking())
451                {
452                    self.add_gray_node(slave);
453                }
454            }
455        }
456
457        // Experimental: cross partition relationship
458        // if unsafe { master.as_ref().partition_id() != slave.as_ref().partition_id() } {
459        //     // update cross scope reference
460        //     let xref = unsafe {
461        //         let x = master.as_ref().xref();
462        //         if x.is_null() {
463        //             master.as_ref().partition_id()
464        //         } else {
465        //             x
466        //         }
467        //     };
468        //     self.set_xref(xref, slave);
469        // }
470    }
471}
472
473#[cfg(debug_assertions)]
474impl GcHead {
475    pub fn debug_set_dbg_string(&mut self, str: std::borrow::Cow<'static, str>) {
476        self.dbg_string = str;
477    }
478
479    pub fn debug_dbg_string(&self) -> &std::borrow::Cow<'static, str> {
480        &self.dbg_string
481    }
482
483    pub fn debug_assert_node_valid_simple(&self) {
484        if !std::thread::panicking() {
485            debug_assert!(
486                ((self.attrs >> 24) & 0xFF) == 0xFF && self.next.is_none_or(|n| n.is_aligned()),
487                "bad node: {self:p}"
488            );
489        }
490    }
491
492    pub fn debug_assert_node_valid(&self, heap: &GcHeap) {
493        if !std::thread::panicking() {
494            debug_assert!(
495                heap.dbg_living_nodes.contains(&NonNull::from_ref(self)),
496                "[O.o] bad node: {self:p}"
497            );
498            self.debug_assert_node_valid_simple();
499        }
500    }
501}