1use std::ptr::NonNull;
5
6use crate::{GcHead, GcHeap, node::GcTriColor, node_link::GcNodeLink};
7
8#[cfg(feature = "gc_arena")]
9use crate::arena::GcArena;
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash)]
16#[repr(transparent)]
17pub struct GcPartitionId(pub u16);
18
19impl std::fmt::Debug for GcPartitionId {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(f, "{}", self.0)
22 }
23}
24
25#[derive(Debug)]
26pub struct GcPartition {
27 pub(crate) nodes: GcNodeLink,
29 pub(crate) gray_list: Vec<NonNull<GcHead>>,
31 marking: bool,
33
34 pub(crate) memory_used: usize,
35
36 #[cfg(feature = "gc_arena")]
38 pub(crate) arena: Option<GcArena>,
39 #[cfg(feature = "gc_arena")]
41 arena_capacity: usize,
42 #[cfg(feature = "gc_arena")]
44 pub(crate) arena_max_alloc: usize,
45}
46
47impl GcPartition {
48 #[allow(unused_variables)]
57 pub fn new(capacity: usize, max_alloc: usize) -> Self {
58 #[cfg(feature = "gc_arena")]
59 let (arena, arena_max_alloc) = if capacity > 0 {
60 (
61 Some(GcArena::new(capacity).expect("arena alloc failed")),
62 max_alloc,
63 )
64 } else {
65 (None, 0)
66 };
67
68 Self {
69 memory_used: 0,
70 nodes: GcNodeLink::default(),
71 gray_list: Vec::new(),
72 marking: false,
73
74 #[cfg(feature = "gc_arena")]
75 arena,
76 #[cfg(feature = "gc_arena")]
77 arena_capacity: capacity,
78 #[cfg(feature = "gc_arena")]
79 arena_max_alloc,
80 }
81 }
82
83 #[cfg(feature = "gc_arena")]
86 pub(super) fn reset_arena(&mut self) {
87 self.arena = if self.arena_capacity > 0 {
88 Some(GcArena::new(self.arena_capacity).expect("arena alloc failed"))
89 } else {
90 None
91 };
92 }
93
94 #[inline(always)]
95 pub fn memory_used(&self) -> usize {
96 self.memory_used
97 }
98
99 #[inline(always)]
100 pub const fn is_marking(&self) -> bool {
101 self.marking
102 }
103
104 #[inline(always)]
105 pub(crate) const fn set_marking(&mut self, marking: bool) {
106 self.marking = marking;
107 }
108
109 pub(crate) fn add_gray_node(&mut self, mut node: NonNull<GcHead>) {
110 debug_assert!(
111 self.is_marking(),
112 "add_gray_node called when partition is not marking"
113 );
114
115 match unsafe { node.as_ref().color() } {
116 GcTriColor::White => unsafe {
117 node.as_mut().set_color(GcTriColor::Gray);
118 },
119 GcTriColor::Gray => {}
120 GcTriColor::Black => {
121 return;
122 }
123 }
124
125 unsafe {
126 if !node.as_ref().is_gray_listed() {
127 node.as_mut().set_gray_listed(true);
128 self.gray_list.push(node);
129 }
130 }
131 }
132}
133
134impl GcHeap {
135 pub fn create_partition(
142 &mut self,
143 arena_capacity: usize,
144 arena_max_alloc: usize,
145 ) -> GcPartitionId {
146 let id = GcPartitionId(self.partitions.len() as u16);
147 self.partitions
148 .push(GcPartition::new(arena_capacity, arena_max_alloc));
149 id
150 }
151
152 pub fn finalize_partition(&self, partition_id: GcPartitionId) -> Option<GcNodeLink> {
176 self.partitions.get(partition_id.0 as usize)?;
178
179 for stack in &self.scope_stacks {
183 if stack.partition == Some(partition_id) {
184 for s in &stack.list {
185 s.clear();
186 }
187 }
188 }
189
190 log::trace!("[finalize_partition] {partition_id:?}");
191
192 let link = self.partitions[partition_id.0 as usize].nodes.clone();
194
195 for node in link.iter() {
201 let hd = unsafe { node.as_ref() };
202 if !hd.weak_id.is_null() {
203 self.weak_slots[hd.weak_id.index() as usize].1.set(None);
204 }
205 }
206
207 for &pass in self.node_dtypes.drop_passes {
210 for node in link.iter() {
211 let dtype = unsafe { node.as_ref().dtype() } as usize;
212 let info = &self.node_dtypes.type_info_list[dtype];
213 if info.drop_pass == pass {
214 self.drop_node_payload_without_dealloc(node);
215 }
216 }
217 }
218
219 Some(link)
220 }
221
222 pub fn dealloc_partition(&mut self, partition_id: GcPartitionId, link: GcNodeLink) -> usize {
230 let partition_mem = self.partitions[partition_id.0 as usize].memory_used;
231 let mut freed_bytes = 0;
232
233 for node in link.iter() {
235 unsafe {
237 if !node.as_ref().weak_id.is_null() {
238 let widx = node.as_ref().weak_id.index();
239 debug_assert!(
240 (widx as usize) < self.weak_slots.len(),
241 "dealloc_partition: weak slot index {} out of bounds (len {})",
242 widx,
243 self.weak_slots.len(),
244 );
245 self.weak_slots.get_unchecked_mut(widx as usize).1.set(None);
246 }
247 }
248
249 let dtype = unsafe { node.as_ref().dtype() } as usize;
250 let info = &self.node_dtypes.type_info_list[dtype];
251 let layout = info.layout();
252 let gross_size = layout.size();
253
254 #[cfg(debug_assertions)]
255 unsafe {
256 (*node.as_ptr()).attrs = 0xDEAD_BEEF;
258 (*node.as_ptr()).next = None;
259 }
260
261 #[cfg(feature = "gc_arena")]
262 {
263 let head = unsafe { node.as_ref() };
264 if head.contains_flag(crate::node::GcNodeFlag::ARENA_ALLOC) {
265 #[cfg(debug_assertions)]
269 {
270 self.dbg_living_nodes.remove(&node.cast());
271 }
272
273 freed_bytes += gross_size;
274 continue;
275 }
276 }
277
278 self.mem_dealloc(node.cast::<u8>(), layout);
279 freed_bytes += gross_size;
280 }
281
282 debug_assert!(
283 self.total_memory_used >= partition_mem,
284 "dealloc_partition: global memory underflow ({} < {})",
285 self.total_memory_used,
286 partition_mem,
287 );
288 self.total_memory_used -= partition_mem;
289
290 #[cfg(feature = "gc_arena")]
293 self.partitions[partition_id.0 as usize].reset_arena();
294 self.partitions[partition_id.0 as usize].memory_used = 0;
295 self.partitions[partition_id.0 as usize].nodes = GcNodeLink::default();
296 self.partitions[partition_id.0 as usize].gray_list.clear();
297 self.partitions[partition_id.0 as usize].marking = false;
298
299 log::trace!("[dealloc_partition] freed {freed_bytes} bytes");
300
301 freed_bytes
302 }
303
304 #[deprecated(
327 note = "unsound — use finalize_partition(&self) + dealloc_partition(&mut self) instead"
328 )]
329 pub fn remove_partition(
330 &mut self,
331 partition_id: GcPartitionId,
332 _on_dispose: impl Fn(&GcHeap, &GcHead),
333 ) -> usize {
334 let link = self.finalize_partition(partition_id);
335 link.map_or(0, |link| {
336 #[cfg(debug_assertions)]
337 {
338 self.dbg_dropping_root_partition = Some(partition_id);
339 }
340 let result = self.dealloc_partition(partition_id, link);
341 #[cfg(debug_assertions)]
342 {
343 self.dbg_dropping_root_partition = None;
344 }
345 result
346 })
347 }
348
349 #[inline(always)]
351 pub fn partition(&self, partition_id: GcPartitionId) -> Option<&GcPartition> {
352 self.partitions.get(partition_id.0 as usize)
353 }
354
355 #[inline(always)]
357 pub fn partition_mut(&mut self, partition_id: GcPartitionId) -> Option<&mut GcPartition> {
358 self.partitions.get_mut(partition_id.0 as usize)
359 }
360
361 pub fn partition_ids(&self) -> Vec<GcPartitionId> {
363 (0..self.partitions.len())
364 .map(|i| GcPartitionId(i as u16))
365 .collect()
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372 use crate::arena::{ARENA_CAPACITY, MAX_ARENA_ALLOC};
373
374 #[derive(Debug)]
375 struct DummyType;
376 impl crate::trace::GcTrace for DummyType {
377 fn trace(&self, _: &mut crate::trace::GcTraceCtx) {}
378 }
379
380 crate::gc_type_register! {
381 DummyType, drop_pass = 0;
382 }
383
384 #[test]
385 fn test_partition_creation() {
386 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
387 let id = heap.create_partition(ARENA_CAPACITY, MAX_ARENA_ALLOC);
388
389 let partition = heap.partition(id).unwrap();
390 assert_eq!(partition.memory_used(), 0);
391
392 let link = heap.finalize_partition(id).unwrap();
394 heap.dealloc_partition(id, link);
395 assert_eq!(heap.partition(id).unwrap().memory_used(), 0);
397 }
398
399 #[test]
400 fn test_gc_threshold() {
401 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
402 heap.set_memory_limit(1024);
403
404 assert_eq!(heap.gc_threshold(), 0);
405
406 heap.set_gc_threshold(512);
407 assert_eq!(heap.gc_threshold(), 512);
408
409 heap.set_gc_threshold(2048);
410 assert_eq!(heap.gc_threshold(), 819);
411
412 heap.set_gc_threshold(0);
413 assert_eq!(heap.gc_threshold(), 0);
414 }
415
416 #[test]
417 fn test_memory_limit() {
418 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
419 let id = heap.create_partition(0, 0);
420
421 assert_eq!(heap.memory_limit(), 0);
422
423 heap.update_mem_use(id, 100);
425 assert_eq!(heap.memory_limit(), 0);
426
427 let applied = heap.set_memory_limit(512);
429 assert_eq!(applied, 512);
430 assert_eq!(heap.memory_limit(), 512);
431
432 let applied_small = heap.set_memory_limit(80);
434 assert_eq!(applied_small, 100);
435 assert_eq!(heap.memory_limit(), 100);
436
437 let applied_zero = heap.set_memory_limit(0);
439 assert_eq!(applied_zero, 0);
440 assert_eq!(heap.memory_limit(), 0);
441 }
442
443 #[test]
444 fn test_is_ancestor_of() {
445 }
447
448 #[test]
449 fn test_common_parent() {
450 }
452
453 #[test]
454 fn test_update_mem_use() {
455 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
456 let id = heap.create_partition(0, 0);
457
458 heap.update_mem_use(id, 100);
459 assert_eq!(heap.partition(id).unwrap().memory_used(), 100);
460 assert_eq!(heap.memory_used(), 100);
461
462 heap.update_mem_use(id, -20);
463 assert_eq!(heap.partition(id).unwrap().memory_used(), 80);
464 assert_eq!(heap.memory_used(), 80);
465
466 let link = heap.finalize_partition(id).unwrap();
468 heap.dealloc_partition(id, link);
469 }
470
471 #[test]
472 fn test_partition_id_serial_and_range() {
473 let id = GcPartitionId(10);
474 assert_eq!(id.0, 10);
475 }
476
477 #[test]
478 fn test_remove_partition_reclaims_memory_stats() {
479 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
480 let id = heap.create_partition(ARENA_CAPACITY, MAX_ARENA_ALLOC);
481
482 let _n1 = unsafe { heap.alloc_raw(id, DummyType) }.unwrap();
484 let _n2 = unsafe { heap.alloc_raw(id, DummyType) }.unwrap();
485
486 let used_before = heap.memory_used();
487 assert!(
488 used_before > 0,
489 "memory_used should be > 0 after allocations"
490 );
491
492 let p_used = heap.partition(id).unwrap().memory_used();
493 assert!(p_used > 0, "partition memory_used should be > 0");
494
495 let link = heap.finalize_partition(id).unwrap();
497 let freed = heap.dealloc_partition(id, link);
498 assert!(freed > 0, "should free some bytes");
499
500 assert_eq!(heap.partition(id).unwrap().memory_used(), 0);
502 assert_eq!(heap.memory_used(), 0, "all memory should be reclaimed");
503 }
504}