1use std::ptr::NonNull;
5
6use crate::{
7 GcHeap,
8 node::{GcHead, GcNode, GcRef, GcTriColor},
9 node_link::{GcNodeLink, NodeLinkIter},
10 partition::GcPartitionId,
11 trace::GcTraceCtx,
12};
13
14#[cfg(feature = "gc_arena")]
15use crate::node::GcNodeFlag;
16
17impl GcHeap {
18 pub fn add_gray_node(&mut self, node: NonNull<GcHead>) {
19 if unsafe { node.as_ref().color() } != GcTriColor::Black {
20 let pid = unsafe { node.as_ref().partition_id() };
21 self.partitions[pid.0 as usize].add_gray_node(node);
22 }
23 }
24
25 #[inline]
27 pub(crate) unsafe fn is_node_partition_marking<T: GcNode>(&self, node: GcRef<T>) -> bool {
28 let pid = unsafe { node.head_ptr.as_ref().partition_id() };
29 self.partitions[pid.0 as usize].is_marking()
30 }
31
32 pub fn mark_reset(&mut self, partition_id: GcPartitionId) {
33 let par = &mut self.partitions[partition_id.0 as usize];
34 par.set_marking(false);
35
36 for mut n in par.gray_list.drain(..) {
39 unsafe {
40 n.as_mut().set_gray_listed(false);
41 }
42 }
43
44 for n in par.nodes_mut() {
45 n.set_color(GcTriColor::White);
46 }
47 }
48
49 pub fn mark_prepare(&mut self, partition_id: GcPartitionId) {
50 if (partition_id.0 as usize) >= self.partitions.len() {
51 return;
52 }
53
54 if self.partitions[partition_id.0 as usize].is_marking() {
57 return;
58 }
59
60 for par in &mut self.partitions {
63 par.set_marking(true);
64 }
65
66 for i in 0..self.partitions.len() {
68 let has_pending_grays = !self.partitions[i].gray_list.is_empty();
69 let par = &mut self.partitions[i];
70
71 for mut n in par.nodes.iter() {
72 let node = unsafe { n.as_mut() };
73 if node.is_root_or_local() {
74 node.set_color(GcTriColor::Gray);
75 if !has_pending_grays {
76 par.gray_list.push(n);
77 }
78 } else if !has_pending_grays {
79 node.set_color(GcTriColor::White);
80 }
81 }
82
83 if has_pending_grays {
84 for mut n in par.nodes.iter() {
87 let node = unsafe { n.as_mut() };
88 if node.is_root_or_local() {
89 node.set_color(GcTriColor::Gray);
90 if !node.is_gray_listed() {
91 node.set_gray_listed(true);
92 par.gray_list.push(n);
93 }
94 }
95 }
96 }
97 }
98 }
99
100 pub fn mark_grays(&mut self, partition_id: GcPartitionId, max_steps: usize) -> bool {
101 if max_steps == 0 {
102 return false;
103 }
104
105 let opaque = self.opaque();
108 let node_dtypes: *const crate::gctype::GcTypeRegistry = self.node_dtypes;
109
110 if !self.partitions[partition_id.0 as usize]
111 .gray_list
112 .is_empty()
113 {
114 let mut gcx = GcTraceCtx {
115 traced_nodes: Vec::with_capacity(64),
116 opaque,
117 _mark: std::marker::PhantomData,
118 };
119
120 let mut cross_buffer: Vec<(u16, NonNull<GcHead>)> = Vec::with_capacity(8);
124 let mut cnt = 0;
125
126 while let Some(mut node_ptr) = self.partitions[partition_id.0 as usize].gray_list.pop()
127 {
128 let node = unsafe { node_ptr.as_mut() };
129 node.set_gray_listed(false);
131
132 if node.color() == GcTriColor::Gray {
133 if cnt >= max_steps {
134 self.partitions[partition_id.0 as usize]
135 .gray_list
136 .push(node_ptr);
137 return false;
138 }
139
140 debug_assert!(
143 gcx.traced_nodes.is_empty(),
144 "trace context should be empty before tracing a new node"
145 );
146 gcx.traced_nodes.clear();
147
148 unsafe {
149 let dtype = node_ptr.as_ref().dtype() as usize;
150 let info = &(*node_dtypes).type_info_list[dtype];
151 (info.trace_fn)(node_ptr, &mut gcx);
152 }
153
154 while let Some(mut ch) = gcx.traced_nodes.pop() {
155 let child = unsafe { ch.as_mut() };
156
157 #[cfg(debug_assertions)]
158 child.debug_assert_node_valid_simple();
159
160 if matches!(child.color(), GcTriColor::White | GcTriColor::Gray) {
161 child.set_color(GcTriColor::Gray);
162 child.set_gray_listed(true);
163
164 let child_pid = child.partition_id();
165 if child_pid == partition_id {
166 self.partitions[partition_id.0 as usize].gray_list.push(ch);
168 } else {
169 cross_buffer.push((child_pid.0, ch));
173 }
174 }
175 }
176
177 for (cpid, node) in cross_buffer.drain(..) {
179 self.partitions[cpid as usize].gray_list.push(node);
180 }
181
182 node.set_color(GcTriColor::Black);
184
185 cnt += 1;
186 }
187 }
188 }
189
190 true
191 }
192
193 pub fn mark(&mut self, partition_id: GcPartitionId, max_steps: usize) -> bool {
194 self.mark_prepare(partition_id);
195 if max_steps > 0 {
196 self.mark_grays(partition_id, max_steps)
197 } else {
198 false
199 }
200 }
201
202 pub fn sweep(
205 &mut self,
206 partition_id: GcPartitionId,
207 on_dispose: impl Fn(&GcHeap, &GcHead),
208 ) -> usize {
209 if (partition_id.0 as usize) >= self.partitions.len() {
210 return 0;
211 }
212 if let Some(link0) = {
213 let par = &mut self.partitions[partition_id.0 as usize];
214 if par.is_marking() && par.gray_list.is_empty() {
215 par.set_marking(false);
216 std::mem::take(&mut par.nodes).into_inner()
217 } else {
218 None }
220 } {
221 #[cfg(debug_assertions)]
222 for n in NodeLinkIter::new(Some(link0)) {
223 unsafe {
224 debug_assert!(
225 matches!(n.as_ref().color(), GcTriColor::Black | GcTriColor::White),
226 "sweep node must be either black or white: {:?}",
227 n.as_ref()
228 );
229 }
230 }
231
232 let call_on_dispose = !std::ptr::addr_eq(&on_dispose, &Self::DUMMY_DISPOSE_CALLBACK);
233 let mut link1 = Some(link0);
234 let mut freed_bytes = 0;
235
236 #[cfg(feature = "gc_arena")]
237 let mut holes: Vec<crate::arena::Hole> = Vec::new();
238
239 for &pass in self.node_dtypes.drop_passes {
240 let mut current = link1;
241 let mut prev: Option<NonNull<GcHead>> = None;
242
243 while let Some(mut this) = current {
244 unsafe {
245 #[cfg(debug_assertions)]
246 this.as_ref().debug_assert_node_valid(self);
247
248 current = this.as_mut().next;
249
250 let drop_pass = self.node_dtypes.type_info_list
251 [this.as_ref().dtype() as usize]
252 .drop_pass;
253
254 if drop_pass == pass
255 && this.as_ref().color() == GcTriColor::White
256 && !this.as_ref().is_root_or_local()
257 {
258 if let Some(mut p) = prev {
259 p.as_mut().next = current;
260 } else {
261 link1 = current;
262 }
263
264 if call_on_dispose {
265 on_dispose(self, this.as_ref());
266 }
267
268 #[cfg(feature = "gc_arena")]
270 let arena_info = {
271 let hd = this.as_ref();
272 let is_arena = hd.contains_flag(GcNodeFlag::ARENA_ALLOC);
273 let dtype = hd.dtype() as usize;
274 let info = &self.node_dtypes.type_info_list[dtype];
275 (is_arena, info.layout().size())
276 };
277
278 freed_bytes += self.dispose(this);
279
280 #[cfg(feature = "gc_arena")]
282 if arena_info.0 {
283 self.partitions[partition_id.0 as usize]
286 .arena
287 .as_ref()
288 .unwrap()
289 .collect_hole(
290 &mut holes,
291 this.cast::<u8>(),
292 arena_info.1,
293 );
294 }
295 } else {
296 prev = Some(this);
297 }
298 }
299 }
300
301 if link1.is_none() {
302 break;
303 }
304 }
305
306 #[cfg(feature = "gc_arena")]
307 if let Some(ref arena) = self.partitions[partition_id.0 as usize].arena {
308 arena.finish_sweep(&mut holes);
309 }
310
311 debug_assert!(
312 self.partitions[partition_id.0 as usize]
313 .gray_list
314 .is_empty()
315 );
316
317 if link1.is_some() {
319 #[cfg(debug_assertions)]
320 for n in NodeLinkIter::new(link1) {
321 unsafe {
322 debug_assert!(
323 n.as_ref().color() == GcTriColor::Black
324 || n.as_ref().is_root_or_local(),
325 "live nodes should be black, root or protected"
326 );
327 }
328 }
329
330 self.partitions[partition_id.0 as usize].nodes =
331 crate::node_link::GcNodeLink::new(link1);
332 }
333
334 freed_bytes
338 } else {
339 0
340 }
341 }
342
343 #[inline]
345 pub fn garbage_collect(
346 &mut self,
347 partition_id: GcPartitionId,
348 on_dispose: impl Fn(&GcHeap, &GcHead),
349 ) -> usize {
350 if self.partition(partition_id).is_none() {
351 return 0;
352 }
353
354 while !self.mark(partition_id, 64) {}
356
357 self.sweep(partition_id, on_dispose)
359 }
360
361 pub(crate) fn dispose_all_nodes(
363 &mut self,
364 mut link: GcNodeLink,
365 on_dispose: impl Fn(&GcHeap, &GcHead),
366 ) -> usize {
367 let call_on_dispose = !std::ptr::addr_eq(&on_dispose, &Self::DUMMY_DISPOSE_CALLBACK);
368 let mut freed_bytes = 0;
369
370 let pass_slice = self.node_dtypes.drop_passes;
371 for &pass in pass_slice {
372 log::trace!("[dipose_all] pass {pass}, count={}", link.len());
373
374 let self_ptr: *mut GcHeap = self;
375
376 link.filter_remove_with(
377 |node| {
378 #[cfg(debug_assertions)]
379 unsafe {
380 node.debug_assert_node_valid(&*self_ptr);
381 }
382
383 let dtype = node.dtype() as usize;
384 let info = unsafe { &(*self_ptr).node_dtypes.type_info_list[dtype] };
385 info.drop_pass == pass
386 },
387 |node_ptr| unsafe {
388 if call_on_dispose {
389 on_dispose(&*self_ptr, node_ptr.as_ref());
390 }
391
392 freed_bytes += (&mut *self_ptr).dispose(node_ptr);
393 },
394 );
395
396 if link.head().is_none() {
397 break;
398 }
399 }
400
401 debug_assert!(
402 link.head().is_none(),
403 "dispose_all_nodes: link still has nodes after disposal",
404 );
405 log::trace!("[dipose_all] done, freed {} bytes", freed_bytes);
406
407 freed_bytes
408 }
409}
410
411#[cfg(test)]
412mod sweep_test {
413 use super::*;
414 use crate::{GcNode, GcRef};
415
416 use crate::trace::{GcTrace, GcTraceCtx};
417
418 fn create_default_partition(heap: &mut crate::GcHeap) -> GcPartitionId {
420 heap.create_partition(
421 crate::arena::ARENA_CAPACITY,
422 crate::arena::MAX_ARENA_ALLOC,
423 )
424 }
425
426 #[derive(Debug)]
427 struct MyI32(i32);
428
429 impl GcTrace for MyI32 {
430 fn trace(&self, _: &mut GcTraceCtx) {}
431 }
432
433 crate::gc_type_register! {
434 MyI32, drop_pass = 0;
435 }
436
437 fn count_nodes_in_partition(heap: &GcHeap, partition_id: GcPartitionId) -> usize {
439 heap.nodes(partition_id).count()
440 }
441
442 fn get_all_nodes_in_partition(
444 heap: &GcHeap,
445 partition_id: GcPartitionId,
446 ) -> Vec<NonNull<GcHead>> {
447 heap.nodes(partition_id).collect()
448 }
449
450 #[test]
452 fn test_sweep_with_basic() {
453 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
454 let partition_id = create_default_partition(&mut heap);
455
456 let objects: Vec<GcRef<MyI32>> = (0..5)
457 .map(|i| unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap())
458 .collect();
459
460 for (i, obj) in objects.iter().enumerate() {
461 if i % 2 == 1 {
462 unsafe {
463 let head = obj.head_ptr.as_ptr();
464 let attrs = (*head).attrs | crate::node::GcNodeFlag::ROOT.bits() as u32;
465 std::ptr::write(&mut (*head).attrs, attrs);
466 }
467 }
468 }
469
470 assert_eq!(count_nodes_in_partition(&heap, partition_id), 5);
471
472 while !heap.mark(partition_id, 64) {}
473
474 let removed = heap.sweep(partition_id, |_, _| {});
475 assert!(removed > 0, "Should have freed some bytes");
476
477 assert_eq!(
478 count_nodes_in_partition(&heap, partition_id),
479 2,
480 "Only root nodes should remain"
481 );
482
483 let remaining_nodes = get_all_nodes_in_partition(&heap, partition_id);
484 let info = &GC_TYPE_REGISTRY.type_info_list[MyI32::GC_TYPE_ID as usize];
485 for node in remaining_nodes {
486 unsafe {
487 let payload_ptr = info.payload_ptr(node);
488 let value = *(payload_ptr.as_ptr() as *const i32);
489 assert_eq!(value % 2, 1, "Remaining nodes should have odd values");
490 }
491 }
492 }
493
494 #[test]
496 fn test_sweep_with_chain_head_removal() {
497 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
498 let partition_id = create_default_partition(&mut heap);
499
500 let objects: Vec<GcRef<MyI32>> = (0..5)
501 .map(|i| unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap())
502 .collect();
503
504 let _ = unsafe { heap.alloc_root_raw(partition_id, MyI32(3)) }.unwrap();
505 let _ = unsafe { heap.alloc_root_raw(partition_id, MyI32(4)) }.unwrap();
506
507 while !heap.mark(partition_id, 64) {}
508
509 let removed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
510
511 assert!(removed > 0, "Should have freed some bytes");
512
513 assert_eq!(count_nodes_in_partition(&heap, partition_id), 2);
515
516 let head = heap.partitions[partition_id.0 as usize].nodes.head();
518 assert!(head.is_some(), "Chain head should exist");
519
520 unsafe {
521 let info = &GC_TYPE_REGISTRY.type_info_list[MyI32::GC_TYPE_ID as usize];
522 let payload_ptr = info.payload_ptr(head.unwrap());
523 let value = (*(payload_ptr.as_ptr() as *const MyI32)).0;
524 assert_eq!(
525 value, 4,
526 "Chain head should be value 4 (last allocated, first in chain)"
527 );
528 }
529
530 let nodes = get_all_nodes_in_partition(&heap, partition_id);
532 assert_eq!(nodes.len(), 2);
533
534 unsafe {
535 let info = &GC_TYPE_REGISTRY.type_info_list[MyI32::GC_TYPE_ID as usize];
536 let payload_ptr1 = info.payload_ptr(nodes[0]).cast::<MyI32>();
537 let value1 = (*payload_ptr1.as_ptr()).0;
538 assert_eq!(value1, 4);
539
540 let payload_ptr2 = info.payload_ptr(nodes[1]).cast::<MyI32>();
541 let value2 = (*payload_ptr2.as_ptr()).0;
542 assert_eq!(value2, 3);
543 }
544 }
545
546 #[test]
548 fn test_sweep_with_all_chain_head_removal() {
549 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
550 let partition_id = create_default_partition(&mut heap);
551
552 let _objects: Vec<GcRef<MyI32>> = (0..3)
553 .map(|i| unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap())
554 .collect();
555
556 while !heap.mark(partition_id, 64) {}
557
558 let removed = heap.sweep(partition_id, |_, n| {
559 println!("dispose {n:?}");
560 });
561
562 assert!(removed > 0, "Should have freed some bytes");
563
564 assert_eq!(count_nodes_in_partition(&heap, partition_id), 0);
566
567 let head = heap.partitions[partition_id.0 as usize].nodes.head();
569 assert!(
570 head.is_none(),
571 "Chain head should be None after removing all nodes"
572 );
573 }
574
575 #[test]
577 fn test_sweep_with_middle_node_removal() {
578 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
579 let partition_id = create_default_partition(&mut heap);
580
581 let _objects: Vec<GcRef<MyI32>> = (0..5)
582 .map(|i| {
583 if i != 2 {
584 unsafe { heap.alloc_root_raw(partition_id, MyI32(i)) }.unwrap()
585 } else {
586 unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap()
587 }
588 })
589 .collect();
590
591 while !heap.mark(partition_id, 64) {}
592
593 let removed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
594
595 assert!(removed > 0, "Should have freed some bytes");
596
597 assert_eq!(count_nodes_in_partition(&heap, partition_id), 4);
599
600 let nodes = get_all_nodes_in_partition(&heap, partition_id);
602 assert_eq!(nodes.len(), 4);
603
604 let expected_values = [4, 3, 1, 0];
605 let info = &GC_TYPE_REGISTRY.type_info_list[MyI32::GC_TYPE_ID as usize];
606 for (i, node) in nodes.iter().enumerate() {
607 unsafe {
608 let payload_ptr = info.payload_ptr(*node);
609 let value = (*(payload_ptr.as_ptr() as *const MyI32)).0;
610 assert_eq!(
611 value, expected_values[i],
612 "Node at position {} should have value {}",
613 i, expected_values[i]
614 );
615 }
616 }
617 }
618
619 #[test]
621 fn test_sweep_with_root_node_removal() {
622 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
623 let partition_id = create_default_partition(&mut heap);
624
625 let root_obj = unsafe { heap.alloc_raw(partition_id, MyI32(0)) }.unwrap();
626 let _objects: Vec<GcRef<MyI32>> = (1..3)
627 .map(|i| unsafe { heap.alloc_raw(partition_id, MyI32(i)) }.unwrap())
628 .collect();
629
630 let _ = unsafe { heap.alloc_root_raw(partition_id, MyI32(1)) }.unwrap();
631
632 let _ = unsafe { heap.alloc_root_raw(partition_id, MyI32(1)) }.unwrap();
633
634 while !heap.mark(partition_id, 64) {}
635
636 let removed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
637
638 assert!(removed > 0, "Should have freed some bytes");
639
640 assert_eq!(count_nodes_in_partition(&heap, partition_id), 2);
642
643 let remaining = get_all_nodes_in_partition(&heap, partition_id);
644 assert!(!remaining.contains(&root_obj.head_ptr));
645 }
646
647 #[test]
649 fn test_sweep_with_empty_partition() {
650 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
651 let partition_id = create_default_partition(&mut heap);
652
653 while !heap.mark(partition_id, 64) {}
654
655 let removed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
656 assert_eq!(removed, 0, "Should return 0 for empty partition");
657 }
658
659 #[test]
661 fn test_sweep_with_nonexistent_partition() {
662 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
663 let non_existent_partition = GcPartitionId(9999);
664
665 let removed = heap.sweep(non_existent_partition, GcHeap::DUMMY_DISPOSE_CALLBACK);
666 assert_eq!(removed, 0, "Should return 0 for non-existent partition");
667 }
668}