1use std::ptr::NonNull;
5
6use crate::{
7 GcHeap,
8 node::{GcHead, GcTriColor},
9 node_link::{GcNodeLink, NodeLinkIter},
10 partition::GcPartitionId,
11};
12
13impl GcHeap {
14 pub fn add_gray_node(&mut self, node: NonNull<GcHead>) {
15 if unsafe { node.as_ref().color() } != GcTriColor::Black {
16 self.partition_mut(unsafe { node.as_ref().partition_id() })
17 .unwrap()
18 .add_gray_node(node);
19 }
20 }
21
22 pub fn mark_reset(&mut self, partition_id: GcPartitionId) {
23 if let Some(par) = self.partition_mut(partition_id) {
24 par.set_marking(false);
25 par.gray_list.clear();
26 for n in par.nodes_mut() {
27 n.set_color(GcTriColor::White);
28 }
29 }
30 }
31
32 pub fn mark_prepare(&mut self, partition_id: GcPartitionId) {
36 if let Some(par) = self.partitions.get_mut(&partition_id)
37 && !par.is_marking()
38 {
39 debug_assert!(par.gray_list.is_empty());
40
41 par.set_marking(true);
42
43 for mut n in par.nodes.iter() {
44 let node = unsafe { n.as_mut() };
45 if node.is_root_or_local() {
46 node.set_color(GcTriColor::Gray);
47 par.gray_list.push(n);
48 } else {
49 node.set_color(GcTriColor::White);
50 }
51 }
52 }
53 }
54
55 pub fn mark_grays(&mut self, partition_id: GcPartitionId, max_steps: usize) -> bool {
56 if max_steps == 0 {
57 return false;
58 }
59
60 let heap_ptr = self as *mut Self;
61
62 if let Some(par) = self.partitions.get_mut(&partition_id)
63 && !par.gray_list.is_empty()
64 {
65 let mut gcx = unsafe { (*heap_ptr).create_trace_ctx(64) };
66 let mut cnt = 0;
67
68 while let Some(mut node_ptr) = par.gray_list.pop() {
69 let node = unsafe { node_ptr.as_mut() };
70 debug_assert_eq!(node.partition_id(), partition_id);
71
72 if node.color() == GcTriColor::Gray {
73 if cnt >= max_steps {
74 par.gray_list.push(node_ptr);
75 return false;
76 }
77
78 unsafe {
79 (*heap_ptr).trace_node(node_ptr, &mut gcx);
80 }
81
82 while let Some(mut ch) = gcx.traced_nodes.pop() {
83 let child = unsafe { ch.as_mut() };
84
85 #[cfg(debug_assertions)]
86 child.debug_assert_node_valid_simple();
87
88 let pid = child.partition_id();
89 if pid == partition_id {
90 if matches!(child.color(), GcTriColor::White | GcTriColor::Gray) {
91 child.set_color(GcTriColor::Gray);
92 par.gray_list.push(ch);
93 }
94 } else {
95 let p2 = unsafe { (*heap_ptr).partition(pid).unwrap() };
96 if p2.is_marking() {
97 unsafe {
98 (*heap_ptr).add_gray_node(ch);
99 }
100 }
101 }
102 }
103
104 node.set_color(GcTriColor::Black);
106
107 cnt += 1;
108 }
109 }
110 }
111
112 true
113 }
114
115 pub fn mark(&mut self, partition_id: GcPartitionId, max_steps: usize) -> bool {
116 self.mark_prepare(partition_id);
117 if max_steps > 0 {
118 self.mark_grays(partition_id, max_steps)
119 } else {
120 false
121 }
122 }
123
124 pub fn sweep(
126 &mut self,
127 partition_id: GcPartitionId,
128 on_dispose: impl Fn(&GcHeap, &GcHead),
129 ) -> usize {
130 if let Some(link0) = self.partition_mut(partition_id).and_then(|p| {
131 if p.is_marking() && p.gray_list.is_empty() {
132 p.set_marking(false);
133 std::mem::take(&mut p.nodes).into_inner()
134 } else {
135 None }
137 }) {
138 #[cfg(debug_assertions)]
139 for n in NodeLinkIter::new(Some(link0)) {
140 unsafe {
141 debug_assert!(
142 matches!(n.as_ref().color(), GcTriColor::Black | GcTriColor::White),
143 "sweep node must be either black or white: {:?}",
144 n.as_ref()
145 );
146 }
147 }
148
149 let call_on_dispose = !std::ptr::addr_eq(&on_dispose, &Self::DUMMY_DISPOSE_CALLBACK);
150 let mut link1 = Some(link0);
151 let mut freed_bytes = 0;
152
153 for &pass in self.node_dtypes.drop_passes {
154 let mut current = link1;
155 let mut prev: Option<NonNull<GcHead>> = None;
156
157 while let Some(mut this) = current {
158 unsafe {
159 #[cfg(debug_assertions)]
160 this.as_ref().debug_assert_node_valid(self);
161
162 current = this.as_mut().next;
163
164 let drop_pass = self.node_dtypes.type_info_list
165 [this.as_ref().dtype() as usize]
166 .drop_pass;
167
168 if drop_pass == pass
169 && this.as_ref().color() == GcTriColor::White
170 && !this.as_ref().is_root_or_local()
171 {
172 if let Some(mut p) = prev {
173 p.as_mut().next = current;
174 } else {
175 link1 = current;
176 }
177
178 if call_on_dispose {
179 on_dispose(self, this.as_ref());
180 }
181 freed_bytes += self.dispose(this);
182 } else {
183 prev = Some(this);
184 }
185 }
186 }
187
188 if link1.is_none() {
189 break;
190 }
191 }
192
193 debug_assert!(
194 self.partition_mut(partition_id)
195 .unwrap()
196 .gray_list
197 .is_empty()
198 );
199
200 if link1.is_some() {
202 #[cfg(debug_assertions)]
203 for n in NodeLinkIter::new(link1) {
204 unsafe {
205 debug_assert!(
206 n.as_ref().color() == GcTriColor::Black
207 || n.as_ref().is_root_or_local(),
208 "live nodes should be black, root or protected"
209 );
210 }
211 }
212
213 let p = self.partition_mut(partition_id).unwrap();
214 p.nodes = crate::node_link::GcNodeLink::new(link1);
215 }
216
217 if freed_bytes != 0 {
219 self.update_mem_use(partition_id, -(freed_bytes as i32));
220 }
221
222 freed_bytes
223 } else {
224 0
225 }
226 }
227
228 #[inline]
230 pub fn garbage_collect(
231 &mut self,
232 partition_id: GcPartitionId,
233 on_dispose: impl Fn(&GcHeap, &GcHead),
234 ) -> usize {
235 if self.partition(partition_id).is_none() {
236 return 0;
237 }
238
239 while !self.mark(partition_id, 64) {}
241
242 self.sweep(partition_id, on_dispose)
244 }
245
246 pub(crate) fn dispose_all_nodes(
248 &mut self,
249 mut link: GcNodeLink,
250 on_dispose: impl Fn(&GcHeap, &GcHead),
251 ) -> usize {
252 let call_on_dispose = !std::ptr::addr_eq(&on_dispose, &Self::DUMMY_DISPOSE_CALLBACK);
253 let mut freed_bytes = 0;
254
255 let pass_slice = self.node_dtypes.drop_passes;
256 for &pass in pass_slice {
257 log::trace!("[dipose_all] pass {pass}, count={}", link.len());
258
259 let self_ptr: *mut GcHeap = self;
260
261 link.filter_remove_with(
262 |node| {
263 #[cfg(debug_assertions)]
264 unsafe {
265 node.debug_assert_node_valid(&*self_ptr);
266 }
267
268 let dtype = node.dtype() as usize;
269 let info = unsafe { &(*self_ptr).node_dtypes.type_info_list[dtype] };
270 info.drop_pass == pass
271 },
272 |node_ptr| unsafe {
273 if call_on_dispose {
274 on_dispose(&*self_ptr, node_ptr.as_ref());
275 }
276
277 freed_bytes += (&mut *self_ptr).dispose(node_ptr);
278 },
279 );
280
281 if link.head().is_none() {
282 break;
283 }
284 }
285
286 debug_assert!(link.head().is_none());
287 log::trace!("[dipose_all] done, freed {} bytes", freed_bytes);
288
289 freed_bytes
290 }
291}
292
293#[cfg(test)]
294mod sweep_test {
295 use super::*;
296 use crate::GcRef;
297
298 use crate::trace::{GcTrace, GcTraceCtx};
299
300 #[derive(Debug)]
301 struct MyI32(i32);
302
303 impl GcTrace for MyI32 {
304 fn trace(&self, _: &mut GcTraceCtx) {}
305 }
306
307 crate::gc_type_register! {
308 MyI32, drop_pass = 0;
309 }
310
311 fn count_nodes_in_partition(heap: &GcHeap, partition_id: GcPartitionId) -> usize {
313 heap.nodes(partition_id).count()
314 }
315
316 fn get_all_nodes_in_partition(
318 heap: &GcHeap,
319 partition_id: GcPartitionId,
320 ) -> Vec<NonNull<GcHead>> {
321 heap.nodes(partition_id).collect()
322 }
323
324 #[test]
326 fn test_sweep_with_basic() {
327 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
328 let partition_id = heap.create_partition();
329
330 let objects: Vec<GcRef<MyI32>> = (0..5)
331 .map(|i| unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap())
332 .collect();
333
334 for (i, obj) in objects.iter().enumerate() {
335 if i % 2 == 1 {
336 unsafe {
337 let head = obj.head_ptr.as_ptr();
338 let attrs = (*head).attrs | crate::node::GcNodeFlag::ROOT.bits() as u32;
339 std::ptr::write(&mut (*head).attrs, attrs);
340 }
341 }
342 }
343
344 assert_eq!(count_nodes_in_partition(&heap, partition_id), 5);
345
346 while !heap.mark(partition_id, 64) {}
347
348 let removed = heap.sweep(partition_id, |_, _| {});
349 assert!(removed > 0, "Should have freed some bytes");
350
351 assert_eq!(
352 count_nodes_in_partition(&heap, partition_id),
353 2,
354 "Only root nodes should remain"
355 );
356
357 let remaining_nodes = get_all_nodes_in_partition(&heap, partition_id);
358 for node in remaining_nodes {
359 unsafe {
360 let payload_ptr = (node.as_ptr() as *mut u8).add(std::mem::size_of::<GcHead>());
361 let value = *(payload_ptr as *const i32);
362 assert_eq!(value % 2, 1, "Remaining nodes should have odd values");
363 }
364 }
365 }
366
367 #[test]
369 fn test_sweep_with_chain_head_removal() {
370 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
371 let partition_id = heap.create_partition();
372
373 let objects: Vec<GcRef<MyI32>> = (0..5)
374 .map(|i| unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap())
375 .collect();
376
377 let _ = unsafe { heap.alloc_root_raw(partition_id, MyI32(3)) }.unwrap();
378 let _ = unsafe { heap.alloc_root_raw(partition_id, MyI32(4)) }.unwrap();
379
380 while !heap.mark(partition_id, 64) {}
381
382 let removed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
383
384 assert!(removed > 0, "Should have freed some bytes");
385
386 assert_eq!(count_nodes_in_partition(&heap, partition_id), 2);
388
389 let head = heap
391 .partitions
392 .get(&partition_id)
393 .and_then(|p| p.nodes.head());
394 assert!(head.is_some(), "Chain head should exist");
395
396 unsafe {
397 let payload_ptr =
398 (head.unwrap().as_ptr() as *mut u8).add(std::mem::size_of::<GcHead>());
399 let value = (*(payload_ptr as *const MyI32)).0;
400 assert_eq!(
401 value, 4,
402 "Chain head should be value 4 (last allocated, first in chain)"
403 );
404 }
405
406 let nodes = get_all_nodes_in_partition(&heap, partition_id);
408 assert_eq!(nodes.len(), 2);
409
410 unsafe {
411 let payload_ptr1 = nodes[0].as_ref().payload().cast::<MyI32>();
412 let value1 = (*payload_ptr1.as_ptr()).0;
413 assert_eq!(value1, 4);
414
415 let payload_ptr2 = nodes[1].as_ref().payload().cast::<MyI32>();
416 let value2 = (*payload_ptr2.as_ptr()).0;
417 assert_eq!(value2, 3);
418 }
419 }
420
421 #[test]
423 fn test_sweep_with_all_chain_head_removal() {
424 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
425 let partition_id = heap.create_partition();
426
427 let _objects: Vec<GcRef<MyI32>> = (0..3)
428 .map(|i| unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap())
429 .collect();
430
431 while !heap.mark(partition_id, 64) {}
432
433 let removed = heap.sweep(partition_id, |_, n| {
434 println!("dispose {n:?}");
435 });
436
437 assert!(removed > 0, "Should have freed some bytes");
438
439 assert_eq!(count_nodes_in_partition(&heap, partition_id), 0);
441
442 let head = heap
444 .partitions
445 .get(&partition_id)
446 .and_then(|p| p.nodes.head());
447 assert!(
448 head.is_none(),
449 "Chain head should be None after removing all nodes"
450 );
451 }
452
453 #[test]
455 fn test_sweep_with_middle_node_removal() {
456 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
457 let partition_id = heap.create_partition();
458
459 let _objects: Vec<GcRef<MyI32>> = (0..5)
460 .map(|i| {
461 if i != 2 {
462 unsafe { heap.alloc_root_raw(partition_id, MyI32(i)) }.unwrap()
463 } else {
464 unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap()
465 }
466 })
467 .collect();
468
469 while !heap.mark(partition_id, 64) {}
470
471 let removed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
472
473 assert!(removed > 0, "Should have freed some bytes");
474
475 assert_eq!(count_nodes_in_partition(&heap, partition_id), 4);
477
478 let nodes = get_all_nodes_in_partition(&heap, partition_id);
480 assert_eq!(nodes.len(), 4);
481
482 let expected_values = [4, 3, 1, 0];
483 for (i, node) in nodes.iter().enumerate() {
484 unsafe {
485 let payload_ptr = (node.as_ptr() as *mut u8).add(std::mem::size_of::<GcHead>());
486 let value = (*(payload_ptr as *const MyI32)).0;
487 assert_eq!(
488 value, expected_values[i],
489 "Node at position {} should have value {}",
490 i, expected_values[i]
491 );
492 }
493 }
494 }
495
496 #[test]
498 fn test_sweep_with_root_node_removal() {
499 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
500 let partition_id = heap.create_partition();
501
502 let root_obj = unsafe { heap.alloc_raw(partition_id, MyI32(0)) }.unwrap();
503 let _objects: Vec<GcRef<MyI32>> = (1..3)
504 .map(|i| unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap())
505 .collect();
506
507 let _ = unsafe { heap.alloc_root_raw(partition_id, MyI32(1)) }.unwrap();
508
509 let _ = unsafe { heap.alloc_root_raw(partition_id, MyI32(1)) }.unwrap();
510
511 while !heap.mark(partition_id, 64) {}
512
513 let removed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
514
515 assert!(removed > 0, "Should have freed some bytes");
516
517 assert_eq!(count_nodes_in_partition(&heap, partition_id), 2);
519
520 let remaining = get_all_nodes_in_partition(&heap, partition_id);
521 assert!(!remaining.contains(&root_obj.head_ptr));
522 }
523
524 #[test]
526 fn test_sweep_with_empty_partition() {
527 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
528 let partition_id = heap.create_partition();
529
530 while !heap.mark(partition_id, 64) {}
531
532 let removed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
533 assert_eq!(removed, 0, "Should return 0 for empty partition");
534 }
535
536 #[test]
538 fn test_sweep_with_nonexistent_partition() {
539 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
540 let non_existent_partition = GcPartitionId(9999);
541
542 let removed = heap.sweep(non_existent_partition, GcHeap::DUMMY_DISPOSE_CALLBACK);
543 assert_eq!(removed, 0, "Should return 0 for non-existent partition");
544 }
545}