Skip to main content

gc_lite/
weak.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 John Ray <996351336@qq.com>
3
4use std::{cell::Cell, marker::PhantomData};
5
6use crate::{Gc, GcNode, GcRef, heap::GcHeap};
7
8/// bit 16-31: slot index in weak_list
9/// bit 0-15:  version
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(transparent)]
12pub struct GcWeakRawId(u32);
13
14impl GcWeakRawId {
15    pub(crate) const NULL: Self = Self(0);
16
17    pub const fn index(&self) -> u16 {
18        (self.0 >> 16) as u16
19    }
20
21    pub const fn version(&self) -> u16 {
22        self.0 as u16
23    }
24
25    pub const fn is_null(&self) -> bool {
26        self.version() == 0
27    }
28}
29
30/// Weak reference
31#[derive(PartialEq)]
32#[repr(transparent)]
33pub struct GcWeak<T: GcNode> {
34    /// bit 16-31: slot index in weak_list
35    /// bit 0-15:  version
36    pub(crate) weak_id: GcWeakRawId,
37
38    pub(crate) _marker: PhantomData<T>,
39}
40
41impl<T: GcNode> Clone for GcWeak<T> {
42    #[inline(always)]
43    fn clone(&self) -> Self {
44        *self
45    }
46}
47
48impl<T: GcNode> Copy for GcWeak<T> {}
49
50impl<T: GcNode> Default for GcWeak<T> {
51    /// Creates a null/empty weak reference. `upgrade()` will always return `None`.
52    fn default() -> Self {
53        Self {
54            weak_id: GcWeakRawId::NULL,
55            _marker: PhantomData,
56        }
57    }
58}
59
60impl<T: GcNode> std::fmt::Debug for GcWeak<T> {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(f, "GcWeak({}#{})", self.index(), self.version())
63    }
64}
65
66impl<T: GcNode> GcWeak<T> {
67    pub(crate) fn new(index: u16, version: u16) -> Self {
68        debug_assert!(version > 0, "GcWeak version must be non-zero");
69        Self {
70            weak_id: GcWeakRawId(((index as u32) << 16) | (version as u32)),
71            _marker: PhantomData,
72        }
73    }
74
75    pub(crate) fn from_id(weak_id: GcWeakRawId) -> Self {
76        Self {
77            weak_id,
78            _marker: PhantomData,
79        }
80    }
81
82    /// get weakref index
83    #[inline(always)]
84    pub fn index(&self) -> u16 {
85        self.weak_id.index()
86    }
87
88    #[inline(always)]
89    pub fn version(&self) -> u16 {
90        self.weak_id.version()
91    }
92
93    /// Upgrade weak reference to strong reference
94    #[inline(always)]
95    pub fn upgrade<'a>(&self, heap: &'a GcHeap) -> Option<Gc<'a, T>> {
96        heap.upgrade(self)
97    }
98}
99
100impl GcHeap {
101    /// Create weak reference.
102    pub fn downgrade<T: GcNode>(&mut self, gc_ref: &GcRef<T>) -> GcWeak<T> {
103        let node = unsafe {
104            let mut h = gc_ref.head_ptr;
105            h.as_mut()
106        };
107
108        if !node.weak_id.is_null() {
109            // Weakref already exists, reuse it
110            #[cfg(debug_assertions)]
111            {
112                debug_assert!((node.weak_id.index() as usize) < self.weak_slots.len());
113                let slot = &self.weak_slots[node.weak_id.index() as usize];
114                let ver = slot.0;
115                let ptr = slot.1.get();
116                debug_assert!(ver == node.weak_id.version() && ptr == Some(gc_ref.head_ptr));
117            }
118            GcWeak::from_id(node.weak_id)
119        } else {
120            if self.weak_slots.len() == u16::MAX as usize {
121                panic!("too may weakrefs");
122            }
123
124            // Get free slot
125            let i = self
126                .weak_slots
127                .iter()
128                .position(|(_, slot)| slot.get().is_none())
129                .unwrap_or_else(|| {
130                    let n = self.weak_slots.len();
131                    self.weak_slots.push((0, Cell::new(None)));
132                    n
133                });
134
135            unsafe {
136                // Set slot `i` with node pointer and new version number
137                let curr_ver = self.weak_slots.get_unchecked(i).0;
138                let version = if curr_ver == u16::MAX {
139                    1
140                } else {
141                    curr_ver + 1
142                };
143
144                let weak = GcWeak::new(i as _, version);
145                let slot = self.weak_slots.get_unchecked_mut(i);
146                slot.0 = version;
147                slot.1.set(Some(gc_ref.head_ptr));
148                node.weak_id = weak.weak_id;
149
150                weak
151            }
152        }
153    }
154
155    /// Upgrade weak reference
156    pub fn upgrade<'a, T: GcNode>(&'a self, weak_ref: &GcWeak<T>) -> Option<Gc<'a, T>> {
157        if !weak_ref.weak_id.is_null() {
158            self.weak_slots
159                .get(weak_ref.index() as usize)
160                .and_then(|(version, node)| {
161                    if *version == weak_ref.version() {
162                        node.get()
163                    } else {
164                        None
165                    }
166                })
167                .map(|ptr| {
168                    // SAFETY: The upgrade result is valid for 'a because
169                    // GC requires &mut GcHeap and we hold &'a GcHeap.
170                    unsafe {
171                        Gc::from_raw(GcRef {
172                            head_ptr: ptr,
173                            _marker: PhantomData,
174                        })
175                    }
176                })
177        } else {
178            None
179        }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use crate::{GcTrace, GcTraceCtx, gc_type_register};
186
187    use super::*;
188
189    #[derive(Debug, Default, PartialEq, Eq)]
190    struct TestData1;
191
192    impl GcTrace for TestData1 {
193        fn trace(&self, _: &mut GcTraceCtx) {}
194    }
195
196    #[derive(Debug, Default, PartialEq, Eq)]
197    struct TestData2;
198
199    impl GcTrace for TestData2 {
200        fn trace(&self, _: &mut GcTraceCtx) {}
201    }
202
203    gc_type_register! {
204        TestData1;
205        TestData2;
206    }
207
208    /// Test WeakRef's Clone and Copy traits
209    #[test]
210    fn test_weak_ref_clone_and_copy() {
211        let weak1 = GcWeak::<TestData1>::new(10, 1);
212
213        // Test Clone
214        let weak2 = weak1;
215        assert_eq!(weak1.index(), weak2.index());
216
217        // Test Copy
218        let weak3 = weak1;
219        let weak4 = weak1; // Can be copied multiple times
220        assert_eq!(weak3.index(), weak4.index());
221    }
222
223    /// Test WeakRef's equality
224    #[test]
225    fn test_weak_ref_equality() {
226        let weak1 = GcWeak::<TestData1>::new(5, 1);
227        let weak2 = GcWeak::<TestData1>::new(5, 1);
228        let weak3 = GcWeak::<TestData1>::new(10, 1);
229
230        // WeakRef with same slot should be equal
231        assert_eq!(weak1, weak2);
232
233        // WeakRef with different slots should not be equal
234        assert_ne!(weak1, weak3);
235    }
236
237    /// Test WeakRef's upgrade method (interface test)
238    #[test]
239    fn test_weak_ref_upgrade_interface() {
240        let heap = GcHeap::new(&GC_TYPE_REGISTRY);
241        let weak_ref = GcWeak::<TestData1>::new(0, 1);
242
243        // Test upgrade interface
244        let result = weak_ref.upgrade(&heap);
245
246        // Since there's no corresponding object, upgrade should return None
247        debug_assert!(result.is_none());
248    }
249
250    /// Test WeakRef's type safety
251    #[test]
252    fn test_weak_ref_type_safety() {
253        // Create WeakRef of different types
254        let weak_i32 = GcWeak::<TestData1>::new(1, 1);
255        let weak_string = GcWeak::<TestData2>::new(1, 1);
256
257        // Different type WeakRef with same slot should be equal (because only slot is compared)
258        debug_assert_eq!(weak_i32.index(), weak_string.index());
259        // But they are different types
260        debug_assert_eq!(
261            std::any::TypeId::of::<GcWeak<TestData1>>(),
262            std::any::TypeId::of::<GcWeak<TestData1>>()
263        );
264        debug_assert_ne!(
265            std::any::TypeId::of::<GcWeak<TestData1>>(),
266            std::any::TypeId::of::<GcWeak<TestData2>>()
267        );
268    }
269
270    /// Test WeakRef's edge cases
271    #[test]
272    fn test_weak_ref_edge_cases() {
273        // Test maximum slot value
274        let weak_max = GcWeak::<TestData1>::new(u16::MAX, 1);
275
276        debug_assert_eq!(weak_max.index(), u16::MAX);
277
278        // Test minimum slot value
279        let weak_min = GcWeak::<TestData1>::new(0, 1);
280        debug_assert_eq!(weak_min.index(), 0);
281
282        // Test WeakRef comparison with same slot but different types
283        let weak_i32 = GcWeak::<TestData1>::new(5, 1);
284        let weak_string = GcWeak::<TestData2>::new(5, 1);
285
286        // Although types are different, slots are the same, should be equal
287        debug_assert_eq!(weak_i32.index(), weak_string.index());
288    }
289
290    /// Test WeakRef's serialization compatibility
291    #[test]
292    fn test_weak_ref_serialization_compatibility() {
293        // Test WeakRef can be safely serialized and deserialized
294        // This mainly tests structural layout stability
295
296        let _weak_ref = GcWeak::<TestData1>::new(42, 1);
297
298        // Ensure struct size is fixed
299        assert_eq!(
300            std::mem::size_of::<GcWeak<TestData1>>(),
301            std::mem::size_of::<u32>()
302        );
303
304        // Ensure alignment is reasonable
305        assert_eq!(
306            std::mem::align_of::<GcWeak<TestData1>>(),
307            std::mem::align_of::<u32>()
308        );
309    }
310}