1use std::{cell::Cell, ptr::NonNull};
5
6use crate::{GcHead, GcHeap, node::GcTriColor, node_link::GcNodeLink};
7
8#[derive(Clone, Copy, PartialEq, Eq, Hash)]
10pub struct GcPartitionId(pub u16);
11
12impl GcPartitionId {
13 pub const NONE: Self = Self(0);
15
16 #[inline(always)]
17 pub const fn is_null(&self) -> bool {
18 self.0 == 0
19 }
20}
21
22impl std::fmt::Debug for GcPartitionId {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(f, "{}", self.0)
25 }
26}
27
28#[derive(Debug)]
29pub struct GcPartition {
30 pub(crate) nodes: GcNodeLink,
32 pub(crate) gray_list: Vec<NonNull<GcHead>>,
34 marking: bool,
36
37 pub(crate) memory_used: usize,
38}
39
40impl GcPartition {
41 fn new() -> Self {
42 Self {
43 memory_used: 0,
44 nodes: GcNodeLink::default(),
45 gray_list: Vec::new(),
46 marking: false,
47 }
48 }
49
50 #[inline(always)]
51 pub fn memory_used(&self) -> usize {
52 self.memory_used
53 }
54
55 #[inline(always)]
56 pub const fn is_marking(&self) -> bool {
57 self.marking
58 }
59
60 #[inline(always)]
61 pub(crate) const fn set_marking(&mut self, marking: bool) {
62 self.marking = marking;
63 }
64
65 pub(crate) fn add_gray_node(&mut self, mut node: NonNull<GcHead>) {
66 debug_assert!(self.is_marking());
67
68 match unsafe { node.as_ref().color() } {
69 GcTriColor::White => unsafe {
70 node.as_mut().set_color(GcTriColor::Gray);
71 },
72 GcTriColor::Gray => {}
73 GcTriColor::Black => {
74 return;
75 }
76 }
77
78 if !self.gray_list.contains(&node) {
79 self.gray_list.push(node);
80 }
81 }
82}
83
84impl GcHeap {
85 pub fn create_partition(&mut self) -> GcPartitionId {
87 thread_local! {
88 static NEXT_PARTITION_ID: Cell<u16> = const { Cell::new(1) };
89 }
90
91 let id = NEXT_PARTITION_ID.with(|next_id| {
92 let mut serial = next_id.get();
93 if serial == 0 {
94 serial = 1;
95 }
96 let start = serial;
97
98 loop {
99 let candidate = GcPartitionId(serial);
100 let conflict = self.partitions.contains_key(&candidate);
101 if !conflict {
102 let next = if serial == u16::MAX { 1 } else { serial + 1 };
103 next_id.set(next);
104 return candidate;
105 }
106
107 serial = if serial == u16::MAX { 1 } else { serial + 1 };
108 if serial == start {
109 panic!("too many active partitions");
110 }
111 }
112 });
113
114 let partition = GcPartition::new();
115 self.partitions.insert(id, partition);
116
117 log::trace!("[new_scope] {id:?}");
118
119 id
120 }
121
122 pub fn remove_partition(
125 &mut self,
126 partition_id: GcPartitionId,
127 on_dispose: impl Fn(&GcHeap, &GcHead),
128 ) -> usize {
129 if partition_id.is_null() {
130 return 0;
131 }
132
133 #[cfg(debug_assertions)]
134 {
135 self.dbg_dropping_root_partition = Some(partition_id);
136 }
137
138 log::trace!("[close_scope] {partition_id:?}");
139
140 let mut freed_bytes = 0;
141
142 if let Some(mut par) = self.partitions.remove(&partition_id) {
143 let link = std::mem::take(&mut par.nodes);
144 freed_bytes += self.dispose_all_nodes(link, &on_dispose);
145 }
146
147 log::trace!("[close_scope_done] {partition_id:?}");
148
149 #[cfg(debug_assertions)]
150 {
151 self.dbg_dropping_root_partition = None;
152 }
153
154 freed_bytes
155 }
156
157 #[inline(always)]
159 pub fn partition(&self, partition_id: GcPartitionId) -> Option<&GcPartition> {
160 self.partitions.get(&partition_id)
161 }
162
163 #[inline(always)]
165 pub fn partition_mut(&mut self, partition_id: GcPartitionId) -> Option<&mut GcPartition> {
166 self.partitions.get_mut(&partition_id)
167 }
168
169 pub fn partition_ids(&self) -> Vec<GcPartitionId> {
171 self.partitions.keys().copied().collect()
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 struct DummyType;
180 impl crate::trace::GcTrace for DummyType {
181 fn trace(&self, _: &mut crate::trace::GcTraceCtx) {}
182 }
183
184 crate::gc_type_register! {
185 DummyType, drop_pass = 0;
186 }
187
188 #[test]
189 fn test_partition_creation() {
190 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
191 let id = heap.create_partition();
192
193 let partition = heap.partition(id).unwrap();
194 assert_eq!(partition.memory_used(), 0);
195
196 heap.remove_partition(id, |_, n| {
198 println!("dispose: {n:?}");
199 });
200 assert!(heap.partition(id).is_none());
201 }
202
203 #[test]
204 fn test_gc_threshold() {
205 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
206 heap.set_memory_limit(1024);
207
208 assert_eq!(heap.gc_threshold(), 0);
209
210 heap.set_gc_threshold(512);
211 assert_eq!(heap.gc_threshold(), 512);
212
213 heap.set_gc_threshold(2048);
214 assert_eq!(heap.gc_threshold(), 819);
215
216 heap.set_gc_threshold(0);
217 assert_eq!(heap.gc_threshold(), 0);
218 }
219
220 #[test]
221 fn test_memory_limit() {
222 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
223 let id = heap.create_partition();
224
225 assert_eq!(heap.memory_limit(), 0);
226
227 heap.update_mem_use(id, 100);
229 assert_eq!(heap.memory_limit(), 0);
230
231 let applied = heap.set_memory_limit(512);
233 assert_eq!(applied, 512);
234 assert_eq!(heap.memory_limit(), 512);
235
236 let applied_small = heap.set_memory_limit(80);
238 assert_eq!(applied_small, 100);
239 assert_eq!(heap.memory_limit(), 100);
240
241 let applied_zero = heap.set_memory_limit(0);
243 assert_eq!(applied_zero, 0);
244 assert_eq!(heap.memory_limit(), 0);
245 }
246
247 #[test]
248 fn test_is_ancestor_of() {
249 }
251
252 #[test]
253 fn test_common_parent() {
254 }
256
257 #[test]
258 fn test_update_mem_use() {
259 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
260 let p1 = heap.create_partition();
261 let p2 = heap.create_partition();
262
263 heap.update_mem_use(p1, 100);
264 assert_eq!(heap.partition(p1).unwrap().memory_used(), 100);
265 assert_eq!(heap.partition(p2).unwrap().memory_used(), 0);
266
267 heap.update_mem_use(p2, 50);
268 assert_eq!(heap.partition(p1).unwrap().memory_used(), 100);
269 assert_eq!(heap.partition(p2).unwrap().memory_used(), 50);
270
271 heap.update_mem_use(p1, -20);
272 assert_eq!(heap.partition(p1).unwrap().memory_used(), 80);
273 assert_eq!(heap.partition(p2).unwrap().memory_used(), 50);
274
275 heap.remove_partition(p1, GcHeap::DUMMY_DISPOSE_CALLBACK);
277 }
278
279 #[test]
280 fn test_partition_id_serial_and_range() {
281 let id = GcPartitionId(10);
282 assert_eq!(id.0, 10);
283 assert!(!id.is_null());
284 assert!(GcPartitionId::NONE.is_null());
285 }
286}