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