Skip to main content

gc_lite/
mem.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright (c) 2025-2026 John Ray <996351336@qq.com>
3
4use std::{alloc::Layout, marker::PhantomData, ptr::NonNull};
5
6use crate::{GcError, GcHead, GcHeap, GcNode, GcPartitionId, GcRef, unlikely, weak::GcWeakRawId};
7
8impl GcHeap {
9    fn mem_alloc(&mut self, size: usize) -> Option<NonNull<u8>> {
10        debug_assert_ne!(size, 0);
11
12        let layout = Layout::from_size_align(size, std::mem::align_of::<usize>()).ok()?;
13        debug_assert_eq!(layout.size(), size);
14
15        unsafe {
16            let ptr = std::alloc::alloc(layout);
17
18            if !ptr.is_null() {
19                #[cfg(debug_assertions)]
20                {
21                    let n = NonNull::new_unchecked(ptr).cast::<GcHead>();
22                    debug_assert!(
23                        !self.dbg_living_nodes.contains(&n),
24                        "node {ptr:?} already exists"
25                    );
26                    self.dbg_living_nodes.insert(n);
27                }
28
29                Some(NonNull::new_unchecked(ptr))
30            } else {
31                None
32            }
33        }
34    }
35
36    fn mem_dealloc(&mut self, ptr: NonNull<u8>, gross_size: usize) {
37        debug_assert_ne!(gross_size, 0);
38
39        #[cfg(debug_assertions)]
40        debug_assert!(
41            self.dbg_living_nodes.contains(&ptr.cast()),
42            "[O.o][dealloc] bad pointer {ptr:?}"
43        );
44
45        let ly = Layout::from_size_align(gross_size, std::mem::align_of::<usize>());
46
47        #[cfg(debug_assertions)]
48        let layout = ly.unwrap();
49        #[cfg(not(debug_assertions))]
50        let layout = unsafe { ly.unwrap_unchecked() };
51
52        debug_assert_eq!(layout.size(), gross_size);
53
54        unsafe {
55            #[cfg(debug_assertions)]
56            self.dbg_living_nodes.remove(&ptr.cast());
57
58            std::alloc::dealloc(ptr.as_ptr(), layout);
59        }
60    }
61
62    /// Allocate a typed gc node with payload data in given scope
63    unsafe fn alloc_node_mem<T: GcNode>(
64        &mut self,
65        partition_id: GcPartitionId,
66        payload: T,
67    ) -> Result<(NonNull<GcHead>, usize), (GcError, T)> {
68        match self.partition_mut(partition_id) {
69            Some(_) => {
70                let size = std::mem::size_of::<T>();
71                let gross_size = std::mem::size_of::<GcHead>() + size;
72
73                if unlikely(
74                    self.memory_limit > 0
75                        && self.total_memory_used + gross_size > self.memory_limit,
76                ) {
77                    Err((GcError::PartitionFull, payload))
78                } else {
79                    let gc_type = T::GC_TYPE_ID;
80                    let ptr = match self.mem_alloc(gross_size) {
81                        Some(p) => p,
82                        None => {
83                            return Err((GcError::AllocationFailed, payload));
84                        }
85                    };
86
87                    let head = ptr.cast::<GcHead>();
88
89                    // setup node info and data
90                    unsafe {
91                        std::ptr::write(head.add(1).cast::<T>().as_ptr(), payload);
92                    }
93
94                    let node_info = GcHead {
95                        attrs: { 0xFF00_0000 | ((gc_type as u32) << 8) },
96                        partition: 0,
97                        weak_id: GcWeakRawId::NULL,
98                        next: None,
99
100                        #[cfg(debug_assertions)]
101                        dbg_scope_depth: self.scope_max_depth(),
102                        #[cfg(debug_assertions)]
103                        dbg_string: std::any::type_name::<T>().into(),
104                    };
105
106                    unsafe {
107                        std::ptr::write(head.as_ptr(), node_info);
108                    }
109
110                    self.update_mem_use(partition_id, gross_size as i32);
111
112                    Ok((head, gross_size))
113                }
114            }
115            None => Err((GcError::PartitionNotFound, payload)),
116        }
117    }
118
119    /// Allocate a typed gc node with payload data, do not put to any scope, even if the current scope is present.
120    ///
121    /// # SAFETY
122    ///
123    /// This function is unsafe because it directly manipulates raw pointers and memory allocation.
124    /// The caller must ensure that the `partition_id` is valid and that the returned `GcRef` is
125    /// properly managed to avoid memory leaks or use-after-free errors.
126    pub unsafe fn alloc_raw<T: GcNode>(
127        &mut self,
128        partition_id: GcPartitionId,
129        payload: T,
130    ) -> Result<GcRef<T>, (GcError, T)> {
131        match unsafe { self.alloc_node_mem(partition_id, payload) } {
132            Ok((head, _)) => {
133                self.attach_node(partition_id, head);
134
135                log::trace!("[alloc] {:?}", unsafe { head.as_ref() });
136
137                Ok(GcRef {
138                    head_ptr: head,
139                    _marker: PhantomData,
140                })
141            }
142            Err(e) => Err(e),
143        }
144    }
145
146    /// # SAFETY
147    ///
148    /// This function is unsafe because it directly manipulates raw pointers and memory allocation.
149    /// The caller must ensure that the `partition_id` is valid and that the returned `GcRef` is
150    /// properly managed to avoid memory leaks or use-after-free errors.
151    pub unsafe fn alloc_root_raw<T: GcNode>(
152        &mut self,
153        partition_id: GcPartitionId,
154        payload: T,
155    ) -> Result<GcRef<T>, (GcError, T)> {
156        let (mut node, _) = unsafe { self.alloc_node_mem(partition_id, payload)? };
157
158        unsafe { node.as_mut() }.insert_flag(crate::node::GcNodeFlag::ROOT);
159
160        self.attach_node(partition_id, node);
161
162        let par = self.partition_mut(partition_id).unwrap();
163        if par.is_marking() {
164            par.add_gray_node(node);
165        }
166
167        log::trace!("[alloc_root] {:?}", unsafe { node.as_ref() });
168
169        Ok(GcRef {
170            head_ptr: node,
171            _marker: PhantomData,
172        })
173    }
174
175    /// Dispose a node
176    pub(crate) fn dispose(&mut self, node: NonNull<GcHead>) -> usize {
177        let hd = unsafe { node.as_ref() };
178        log::trace!("[dispose] {hd:?}");
179
180        #[cfg(debug_assertions)]
181        hd.debug_assert_node_valid(self);
182
183        if !hd.weak_id.is_null() {
184            // clear weak slot
185            let widx = hd.weak_id.index();
186            debug_assert!((widx as usize) < self.weak_slots.len());
187            unsafe {
188                self.weak_slots.get_unchecked_mut(widx as usize).1.take();
189            }
190        }
191
192        let dtype = hd.dtype() as usize;
193        let info = &self.node_dtypes.type_info_list[dtype];
194        let gross_size = std::mem::size_of::<GcHead>() + info.size as usize;
195
196        #[cfg(debug_assertions)]
197        unsafe {
198            std::ptr::drop_in_place(node.cast::<GcHead>().as_ptr());
199        }
200
201        if let Some(f) = info.drop_fn {
202            unsafe {
203                f(node.as_ref().payload().as_ptr());
204            }
205        }
206
207        self.mem_dealloc(node.cast::<u8>(), gross_size);
208
209        gross_size
210    }
211}