1#![allow(clippy::missing_safety_doc)]
4#![allow(private_interfaces)]
5
6use std::ptr::NonNull;
7
8pub mod region;
9pub mod stats;
10
11#[cfg(not(feature = "no-gc"))]
12pub mod cancellation;
13#[cfg(not(feature = "no-gc"))]
14pub mod config;
15
16#[cfg(feature = "no-gc")]
17pub mod alloc_ctx;
18#[cfg(feature = "no-gc")]
19pub mod static_arena;
20
21pub use stats::{CLJRS_GC_STATS_ENV, GC_STATS, GcStats, GcStatsSnapshot, dump_stats_from_env};
22
23#[cfg(not(feature = "no-gc"))]
26pub use cancellation::{
27 CancellableGuard, MutatorGuard, StwGuard, begin_stw, check_cancellation, gc_requested,
28 park_thread, register_mutator, registered_threads, request_gc, safepoint, take_gc_request,
29 unpark_thread, wait_for_threads_to_park,
30};
31#[cfg(not(feature = "no-gc"))]
32pub use config::{GC_CANCELLATION as CONFIG_CANCELLATION, GcConfig, GcParked};
33
34#[cfg(not(feature = "no-gc"))]
35pub use gc_full::{
36 AllocRootGuard, GcHeap, HEAP, HeapProxy, push_alloc_frame, trace_thread_alloc_roots,
37};
38#[cfg(feature = "no-gc")]
39pub use nogc_stubs::{
40 AllocRootGuard, CONFIG_CANCELLATION, CancellableGuard, GcConfig, GcHeap, GcParked, HEAP,
41 MutatorGuard, StwGuard, begin_stw, check_cancellation, gc_requested, park_thread,
42 push_alloc_frame, register_mutator, registered_threads, request_gc, safepoint, take_gc_request,
43 unpark_thread, wait_for_threads_to_park,
44};
45
46#[cfg(all(feature = "no-gc", debug_assertions))]
51pub fn is_static_addr(addr: usize) -> bool {
52 static_arena::is_static_addr(addr)
53}
54
55pub trait Trace {
69 fn trace(&self, visitor: &mut MarkVisitor);
70
71 fn gc_size_extra(&self) -> usize {
72 0
73 }
74}
75
76impl Trace for String {
79 fn trace(&self, _: &mut MarkVisitor) {}
80
81 fn gc_size_extra(&self) -> usize {
82 self.capacity()
83 }
84}
85impl Trace for i64 {
86 fn trace(&self, _: &mut MarkVisitor) {}
87}
88impl Trace for f64 {
89 fn trace(&self, _: &mut MarkVisitor) {}
90}
91impl Trace for bool {
92 fn trace(&self, _: &mut MarkVisitor) {}
93}
94impl Trace for num_bigint::BigInt {
95 fn trace(&self, _: &mut MarkVisitor) {}
96}
97impl Trace for bigdecimal::BigDecimal {
98 fn trace(&self, _: &mut MarkVisitor) {}
99}
100impl Trace for num_rational::Ratio<num_bigint::BigInt> {
101 fn trace(&self, _: &mut MarkVisitor) {}
102}
103impl Trace for regex::Regex {
104 fn trace(&self, _: &mut MarkVisitor) {}
105}
106macro_rules! impl_trace_prim_array {
107 ($t:ty) => {
108 impl Trace for std::sync::Mutex<Vec<$t>> {
109 fn trace(&self, _: &mut MarkVisitor) {}
110 fn gc_size_extra(&self) -> usize {
111 self.lock().unwrap().capacity() * std::mem::size_of::<$t>()
112 }
113 }
114 };
115}
116impl_trace_prim_array!(i32);
117impl_trace_prim_array!(i64);
118impl_trace_prim_array!(i16);
119impl_trace_prim_array!(i8);
120impl_trace_prim_array!(char);
121impl_trace_prim_array!(f64);
122impl_trace_prim_array!(f32);
123impl_trace_prim_array!(bool);
124
125pub trait GcVisitor {
128 fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>);
129}
130
131#[cfg(not(feature = "no-gc"))]
136pub use self::gc_header::{GcBox, GcBoxHeader};
137
138#[cfg(not(feature = "no-gc"))]
139mod gc_header {
140 use crate::{MarkVisitor, Trace};
141 use std::cell::Cell;
142
143 pub(crate) const GC_INITIAL_LIVES: u8 = 2;
151
152 #[cfg(debug_assertions)]
153 pub(crate) const GC_MAGIC_ALIVE: u64 = 0xCAFE_BABE_DEAD_BEEF;
154 #[cfg(debug_assertions)]
155 pub(crate) const GC_MAGIC_FREED: u64 = 0xDEAD_DEAD_DEAD_DEAD;
156
157 #[repr(C)]
158 pub struct GcBoxHeader {
159 #[cfg(debug_assertions)]
160 pub(crate) magic: Cell<u64>,
161 pub(crate) size: usize,
163 pub(crate) lives: Cell<u8>,
164 pub(crate) next: Cell<*mut GcBoxHeader>,
165 pub(crate) trace_fn: unsafe fn(*const GcBoxHeader, &mut MarkVisitor),
166 pub(crate) drop_fn: unsafe fn(*mut GcBoxHeader),
167 }
168
169 impl GcBoxHeader {
170 pub(crate) fn new<T: Trace + 'static>(heap_extra: usize) -> Self {
171 Self {
172 #[cfg(debug_assertions)]
173 magic: Cell::new(GC_MAGIC_ALIVE),
174 size: std::mem::size_of::<GcBox<T>>() + heap_extra,
175 lives: Cell::new(GC_INITIAL_LIVES - 1),
176 next: Cell::new(std::ptr::null_mut()),
177 trace_fn: trace_gc_box::<T>,
178 drop_fn: drop_gc_box::<T>,
179 }
180 }
181 }
182
183 unsafe impl Send for GcBoxHeader {}
184 unsafe impl Sync for GcBoxHeader {}
185
186 #[repr(C)]
187 pub struct GcBox<T: Trace + 'static> {
188 pub(crate) header: GcBoxHeader,
189 pub value: T,
190 }
191
192 pub(crate) unsafe fn trace_gc_box<T: Trace + 'static>(
193 header: *const GcBoxHeader,
194 visitor: &mut MarkVisitor,
195 ) {
196 unsafe {
197 let gc_box = header as *const GcBox<T>;
198 (*gc_box).value.trace(visitor);
199 }
200 }
201
202 pub(crate) unsafe fn drop_gc_box<T: Trace + 'static>(header: *mut GcBoxHeader) {
203 unsafe {
204 #[cfg(debug_assertions)]
205 {
206 (*header).magic.set(GC_MAGIC_FREED);
207 }
208 let gc_box = header as *mut GcBox<T>;
209 drop(Box::from_raw(gc_box));
210 }
211 }
212}
213
214#[cfg(feature = "no-gc")]
219pub use self::nogc_box::GcBox;
220
221#[cfg(feature = "no-gc")]
222mod nogc_box {
223 use crate::Trace;
224
225 pub struct GcBox<T: Trace + 'static> {
226 pub value: T,
227 }
228}
229
230#[cfg(not(feature = "no-gc"))]
235#[derive(Default)]
236pub struct MarkVisitor {
237 pub(crate) grey: Vec<*mut GcBoxHeader>,
238}
239
240#[cfg(feature = "no-gc")]
241pub struct MarkVisitor;
242
243#[cfg(not(feature = "no-gc"))]
244impl MarkVisitor {
245 pub fn new() -> Self {
246 Self::default()
247 }
248
249 pub fn grey_len(&self) -> usize {
250 self.grey.len()
251 }
252
253 pub unsafe fn mark_header(&mut self, header: *mut GcBoxHeader) {
254 use gc_header::GC_INITIAL_LIVES;
255 let h = unsafe { &*header };
256 if h.lives.get() < GC_INITIAL_LIVES {
257 h.lives.set(GC_INITIAL_LIVES);
258 self.grey.push(header);
259 }
260 }
261
262 pub(crate) fn drain(&mut self) {
263 let mut visited = 0usize;
264 while let Some(header) = self.grey.pop() {
265 visited += 1;
266 let h = unsafe { &*header };
267 unsafe { (h.trace_fn)(header as *const GcBoxHeader, self) };
268 }
269 cljrs_logging::feat_debug!("gc", "drain visited {} objects", visited);
270 }
271}
272
273#[cfg(not(feature = "no-gc"))]
274impl GcVisitor for MarkVisitor {
275 fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>) {
276 use gc_header::GC_INITIAL_LIVES;
277 let header = unsafe { &(*ptr.0.as_ptr()).header };
278 if header.lives.get() < GC_INITIAL_LIVES {
279 header.lives.set(GC_INITIAL_LIVES);
280 self.grey.push(ptr.0.as_ptr() as *mut GcBoxHeader);
281 }
282 }
283}
284
285#[cfg(feature = "no-gc")]
286impl MarkVisitor {
287 pub fn grey_len(&self) -> usize {
288 0
289 }
290 pub unsafe fn mark_header(&mut self, _: *mut u8) {}
291}
292
293#[cfg(feature = "no-gc")]
294impl GcVisitor for MarkVisitor {
295 fn visit<T: Trace + 'static>(&mut self, _: &GcPtr<T>) {}
296}
297
298pub struct GcPtr<T: Trace + 'static>(NonNull<GcBox<T>>);
303
304impl<T: Trace + 'static> GcPtr<T> {
305 #[cfg(not(feature = "no-gc"))]
306 pub fn new(value: T) -> Self {
307 gc_full::HEAP.alloc(value)
308 }
309
310 #[cfg(feature = "no-gc")]
311 pub fn new(value: T) -> Self {
312 alloc_ctx::alloc_in_ctx(value)
313 }
314
315 pub fn get(&self) -> &T {
316 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
317 {
318 use gc_header::GC_MAGIC_ALIVE;
319 let header = unsafe { &(*self.0.as_ptr()).header };
320 assert_eq!(
321 header.magic.get(),
322 GC_MAGIC_ALIVE,
323 "GcPtr::get() on freed object! magic={:#x}",
324 header.magic.get(),
325 );
326 }
327 unsafe { &(*self.0.as_ptr()).value }
328 }
329
330 pub fn get_mut(&mut self) -> &mut T {
331 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
332 {
333 use gc_header::GC_MAGIC_ALIVE;
334 let header = unsafe { &(*self.0.as_ptr()).header };
335 assert_eq!(
336 header.magic.get(),
337 GC_MAGIC_ALIVE,
338 "GcPtr::get_mut() on freed object! magic={:#x}",
339 header.magic.get(),
340 );
341 }
342 unsafe { &mut (*self.0.as_ptr()).value }
343 }
344
345 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
346 a.0 == b.0
347 }
348
349 #[cfg(all(feature = "no-gc", debug_assertions))]
355 pub fn is_static_alloc(&self) -> bool {
356 static_arena::is_static_addr(self.0.as_ptr() as usize)
357 }
358}
359
360impl<T: Trace + 'static> Clone for GcPtr<T> {
361 fn clone(&self) -> Self {
362 GcPtr(self.0)
363 }
364}
365
366impl<T: Trace + 'static + std::fmt::Debug> std::fmt::Debug for GcPtr<T> {
367 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368 unsafe { (*self.0.as_ptr()).value.fmt(f) }
369 }
370}
371
372impl<T: Trace + 'static> Drop for GcPtr<T> {
373 fn drop(&mut self) {}
374}
375
376pub struct StaticGcPtr<T: 'static>(NonNull<T>);
390
391unsafe impl<T: 'static> Send for StaticGcPtr<T> {}
395unsafe impl<T: 'static> Sync for StaticGcPtr<T> {}
396
397impl<T: 'static> StaticGcPtr<T> {
398 pub fn get(&self) -> &T {
400 unsafe { self.0.as_ref() }
402 }
403
404 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
407 a.0 == b.0
408 }
409}
410
411impl<T: 'static> Clone for StaticGcPtr<T> {
412 fn clone(&self) -> Self {
413 StaticGcPtr(self.0)
414 }
415}
416
417impl<T: 'static + std::fmt::Debug> std::fmt::Debug for StaticGcPtr<T> {
418 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
419 unsafe { self.0.as_ref().fmt(f) }
420 }
421}
422
423pub fn static_alloc<T: 'static>(value: T) -> StaticGcPtr<T> {
429 #[cfg(feature = "no-gc")]
430 {
431 static_arena::static_alloc_val(value)
432 }
433 #[cfg(not(feature = "no-gc"))]
434 {
435 StaticGcPtr(NonNull::from(Box::leak(Box::new(value))))
436 }
437}
438
439#[cfg(not(feature = "no-gc"))]
444mod gc_full {
445 use std::cell::RefCell;
446 use std::ptr::NonNull;
447 use std::sync::atomic::{AtomicUsize, Ordering};
448 use std::sync::{Arc, Mutex};
449
450 use crate::config::GcConfig;
451 use crate::gc_header::GC_INITIAL_LIVES;
452 use crate::{GcBox, GcBoxHeader, GcPtr, MarkVisitor, Trace};
453
454 type RootTracer = Box<dyn Fn(&mut MarkVisitor)>;
455
456 pub struct GcHeap {
457 inner: Mutex<GcHeapInner>,
458 config: Mutex<Option<Arc<GcConfig>>>,
459 memory_in_use: AtomicUsize,
460 total_allocated_bytes: AtomicUsize,
461 root_tracers: Mutex<Vec<RootTracer>>,
462 gc_suppressed: std::sync::atomic::AtomicBool,
463 suppressed_threshold: AtomicUsize,
470 zero_yield_headroom: AtomicUsize,
472 }
473
474 struct GcHeapInner {
475 head: *mut GcBoxHeader,
476 count: usize,
477 total_allocated: usize,
478 total_freed: usize,
479 }
480
481 unsafe impl Send for GcHeapInner {}
482
483 impl GcHeapInner {
484 const fn new() -> Self {
485 Self {
486 head: std::ptr::null_mut(),
487 count: 0,
488 total_allocated: 0,
489 total_freed: 0,
490 }
491 }
492 }
493
494 unsafe impl Sync for GcHeap {}
495
496 impl Default for GcHeap {
497 fn default() -> Self {
498 Self::new()
499 }
500 }
501
502 impl GcHeap {
503 pub const fn new() -> Self {
504 Self {
505 inner: Mutex::new(GcHeapInner::new()),
506 config: Mutex::new(None),
507 memory_in_use: AtomicUsize::new(0),
508 total_allocated_bytes: AtomicUsize::new(0),
509 root_tracers: Mutex::new(Vec::new()),
510 gc_suppressed: std::sync::atomic::AtomicBool::new(false),
511 suppressed_threshold: AtomicUsize::new(0),
512 zero_yield_headroom: AtomicUsize::new(0),
513 }
514 }
515
516 pub fn set_config(&self, config: Arc<GcConfig>) {
517 *self.config.lock().unwrap() = Some(config);
518 }
519
520 pub fn set_config_from_env(&self) {
521 #[cfg(not(target_arch = "wasm32"))]
522 let default_soft_limit: usize = (system_memory::total() / 3) as usize;
523 #[cfg(target_arch = "wasm32")]
524 let default_soft_limit: usize = 64 * 1024 * 1024;
525
526 let soft_limit_mb: usize = match std::env::var("CLJRS_GC_SOFT_LIMIT_MB").ok() {
527 Some(s) => s.parse::<usize>().unwrap() * (1024 * 1024),
528 None => default_soft_limit,
529 };
530 let hard_limit_mb: usize = match std::env::var("CLJRS_GC_HARD_LIMIT_MB").ok() {
531 Some(s) => s.parse::<usize>().unwrap() * (1024 * 1024),
532 None => soft_limit_mb,
533 };
534 self.set_config(Arc::new(GcConfig::with_limits(
535 soft_limit_mb,
536 hard_limit_mb,
537 )));
538 }
539
540 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
541 self.root_tracers.lock().unwrap().push(Box::new(tracer));
542 }
543
544 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
545 let tracers = self.root_tracers.lock().unwrap();
546 for tracer in tracers.iter() {
547 tracer(visitor);
548 }
549 }
550
551 pub fn memory_in_use(&self) -> usize {
552 self.memory_in_use.load(Ordering::Relaxed)
553 }
554
555 #[cfg(test)]
556 pub fn set_memory_in_use(&self, bytes: usize) {
557 self.memory_in_use.store(bytes, Ordering::Relaxed);
558 }
559
560 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
561 crate::cancellation::safepoint();
562 let heap_extra = value.gc_size_extra();
563 let gc_box = Box::new(GcBox {
564 header: GcBoxHeader::new::<T>(heap_extra),
565 value,
566 });
567 let obj_size = gc_box.header.size; let raw: *mut GcBox<T> = Box::into_raw(gc_box);
569 {
570 let mut inner = self.inner.lock().unwrap();
571 unsafe {
572 (*raw).header.next.set(inner.head);
573 inner.head = raw as *mut GcBoxHeader;
574 }
575 inner.count += 1;
576 inner.total_allocated += 1;
577 }
578 self.total_allocated_bytes
579 .fetch_add(obj_size, Ordering::Relaxed);
580 crate::stats::GC_STATS.record_gc_alloc(obj_size);
581 let current_usage =
582 self.memory_in_use.fetch_add(obj_size, Ordering::Relaxed) + obj_size;
583
584 if let Some(config) = self.config.lock().unwrap().as_ref()
585 && config.soft_limit_exceeded(current_usage)
586 {
587 if self.gc_suppressed.load(Ordering::Relaxed) {
588 let threshold = self.suppressed_threshold.load(Ordering::Relaxed);
592 if current_usage > threshold {
593 self.gc_suppressed.store(false, Ordering::Relaxed);
594 crate::cancellation::request_gc();
595 }
596 } else {
597 crate::cancellation::request_gc();
598 }
599 }
600
601 register_alloc(raw as *mut GcBoxHeader);
602 GcPtr(unsafe { NonNull::new_unchecked(raw) })
603 }
604
605 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
606 let pre_count = self.inner.lock().unwrap().count;
607 let pre_memory = self.memory_in_use.load(Ordering::Relaxed);
608 cljrs_logging::feat_debug!(
609 "gc",
610 "starting collection: {} objects, ~{} bytes in use",
611 pre_count,
612 pre_memory
613 );
614 let mark_start = std::time::Instant::now();
615 let mut visitor = MarkVisitor::new();
616 trace_roots(&mut visitor);
617 cljrs_logging::feat_debug!(
618 "gc",
619 "starting drain with {} grey objects",
620 visitor.grey.len()
621 );
622 visitor.drain();
623 let mark_elapsed = mark_start.elapsed();
624
625 let sweep_start = std::time::Instant::now();
626 let mut inner = self.inner.lock().unwrap();
627 let mut live: Vec<*mut GcBoxHeader> = Vec::with_capacity(inner.count);
628 let mut dead: Vec<*mut GcBoxHeader> = Vec::new();
629 let mut freed_bytes: usize = 0;
631 let mut current = inner.head;
632 while !current.is_null() {
633 let header = unsafe { &*current };
634 let next = header.next.get();
635 let lives = header.lives.get();
636 let obj_size = header.size;
637 if lives >= GC_INITIAL_LIVES {
638 header.lives.set(GC_INITIAL_LIVES - 1);
640 live.push(current);
641 } else if lives > 0 {
642 header.lives.set(lives - 1);
644 live.push(current);
645 } else {
646 freed_bytes += obj_size;
648 dead.push(current);
649 }
650 current = next;
651 }
652 let freed_count = dead.len();
653 for ptr in dead {
654 let header = unsafe { &*ptr };
655 unsafe { (header.drop_fn)(ptr) };
656 inner.count -= 1;
657 inner.total_freed += 1;
658 }
659 inner.head = std::ptr::null_mut();
660 for ptr in live {
661 let header = unsafe { &*ptr };
662 header.next.set(inner.head);
663 inner.head = ptr;
664 }
665 self.memory_in_use.fetch_sub(freed_bytes, Ordering::Relaxed);
670 let sweep_elapsed = sweep_start.elapsed();
671 crate::stats::GC_STATS.record_gc_pause(
672 mark_elapsed + sweep_elapsed,
673 freed_count as u64,
674 freed_bytes as u64,
675 );
676 let post_memory = self.memory_in_use.load(Ordering::Relaxed);
677 cljrs_logging::feat_debug!(
678 "gc",
679 "collection complete: freed {} (~{} bytes), {} remaining (~{} bytes), mark={:.2?} sweep={:.2?}",
680 freed_count,
681 freed_bytes,
682 inner.count,
683 post_memory,
684 mark_elapsed,
685 sweep_elapsed
686 );
687 if freed_count == 0 {
688 let soft_limit = self
699 .config
700 .lock()
701 .unwrap()
702 .as_ref()
703 .map(|c| c.soft_limit())
704 .unwrap_or(64 * 1024 * 1024);
705 let base_headroom = (soft_limit / 10).max(1);
706 let max_headroom = (soft_limit / 4).max(base_headroom);
707 let prev_headroom = self.zero_yield_headroom.load(Ordering::Relaxed);
708 let headroom = if prev_headroom == 0 {
709 base_headroom
710 } else {
711 prev_headroom.saturating_mul(2).min(max_headroom)
712 };
713 self.zero_yield_headroom.store(headroom, Ordering::Relaxed);
714 self.suppressed_threshold
715 .store(post_memory + headroom, Ordering::Relaxed);
716 self.gc_suppressed.store(true, Ordering::Relaxed);
717 } else {
718 self.zero_yield_headroom.store(0, Ordering::Relaxed);
720 self.gc_suppressed.store(false, Ordering::Relaxed);
721 }
722 }
723
724 pub fn count(&self) -> usize {
725 self.inner.lock().unwrap().count
726 }
727 pub fn total_allocated(&self) -> usize {
728 self.inner.lock().unwrap().total_allocated
729 }
730 pub fn total_freed(&self) -> usize {
731 self.inner.lock().unwrap().total_freed
732 }
733
734 pub fn collect_auto(&self) -> bool {
735 cljrs_logging::feat_debug!("gc", "automatic collection requested");
736 let Some(_stw_guard) = crate::cancellation::begin_stw() else {
737 cljrs_logging::feat_debug!("gc", "automatic collection skipped");
738 return false;
739 };
740 self.collect(|visitor| self.trace_registered_roots(visitor));
741 true
742 }
743 }
744
745 thread_local! {
746 static ISOLATE_HEAP: GcHeap = const { GcHeap::new() };
747 }
748
749 pub struct HeapProxy;
755
756 impl HeapProxy {
757 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
758 ISOLATE_HEAP.with(|h| h.alloc(value))
759 }
760
761 pub fn set_config(&self, config: Arc<GcConfig>) {
762 ISOLATE_HEAP.with(|h| h.set_config(config));
763 }
764
765 pub fn set_config_from_env(&self) {
766 ISOLATE_HEAP.with(|h| h.set_config_from_env());
767 }
768
769 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
770 ISOLATE_HEAP.with(|h| h.register_root_tracer(tracer));
771 }
772
773 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
774 ISOLATE_HEAP.with(|h| h.trace_registered_roots(visitor));
775 }
776
777 pub fn memory_in_use(&self) -> usize {
778 ISOLATE_HEAP.with(|h| h.memory_in_use())
779 }
780
781 pub fn count(&self) -> usize {
782 ISOLATE_HEAP.with(|h| h.count())
783 }
784
785 pub fn total_allocated(&self) -> usize {
786 ISOLATE_HEAP.with(|h| h.total_allocated())
787 }
788
789 pub fn total_freed(&self) -> usize {
790 ISOLATE_HEAP.with(|h| h.total_freed())
791 }
792
793 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
794 ISOLATE_HEAP.with(|h| h.collect(trace_roots));
795 }
796
797 pub fn collect_auto(&self) -> bool {
798 ISOLATE_HEAP.with(|h| h.collect_auto())
799 }
800
801 #[cfg(test)]
802 pub fn set_memory_in_use(&self, bytes: usize) {
803 ISOLATE_HEAP.with(|h| h.set_memory_in_use(bytes));
804 }
805 }
806
807 unsafe impl Sync for HeapProxy {}
810
811 pub static HEAP: HeapProxy = HeapProxy;
812
813 thread_local! {
814 pub(crate) static ALLOC_ROOTS: RefCell<Vec<*mut GcBoxHeader>> = const { RefCell::new(Vec::new()) };
815 }
816
817 pub struct AllocRootGuard {
818 saved_len: usize,
819 }
820
821 impl Drop for AllocRootGuard {
822 fn drop(&mut self) {
823 ALLOC_ROOTS.with(|roots| roots.borrow_mut().truncate(self.saved_len));
824 }
825 }
826
827 pub fn push_alloc_frame() -> AllocRootGuard {
828 let saved_len = ALLOC_ROOTS.with(|roots| roots.borrow().len());
829 AllocRootGuard { saved_len }
830 }
831
832 fn register_alloc(header: *mut GcBoxHeader) {
833 ALLOC_ROOTS.with(|roots| roots.borrow_mut().push(header));
834 }
835
836 pub fn trace_thread_alloc_roots(visitor: &mut MarkVisitor) {
837 ALLOC_ROOTS.with(|roots| {
838 let roots = roots.borrow();
839 for &header in roots.iter() {
840 unsafe { visitor.mark_header(header) };
841 }
842 });
843 }
844}
845
846#[cfg(feature = "no-gc")]
851mod nogc_stubs {
852 use crate::MarkVisitor;
853 use std::sync::Arc;
854
855 #[derive(Debug, Clone)]
856 pub struct GcConfig;
857 impl GcConfig {
858 pub fn new() -> Self {
859 Self
860 }
861 pub fn with_hard_limit(_: usize) -> Self {
862 Self
863 }
864 pub fn with_limits(_: usize, _: usize) -> Self {
865 Self
866 }
867 }
868 impl Default for GcConfig {
869 fn default() -> Self {
870 Self::new()
871 }
872 }
873
874 pub struct GcHeap;
875 impl Default for GcHeap {
876 fn default() -> Self {
877 Self::new()
878 }
879 }
880 impl GcHeap {
881 pub const fn new() -> Self {
882 Self
883 }
884 pub fn set_config(&self, _: Arc<GcConfig>) {}
885 pub fn register_root_tracer(&self, _: impl Fn(&mut MarkVisitor) + 'static) {}
886 pub fn trace_registered_roots(&self, _: &mut MarkVisitor) {}
887 pub fn memory_in_use(&self) -> usize {
888 0
889 }
890 pub fn count(&self) -> usize {
891 0
892 }
893 pub fn total_allocated(&self) -> usize {
894 0
895 }
896 pub fn total_freed(&self) -> usize {
897 0
898 }
899 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, _: F) {}
900 pub fn collect_auto(&self) -> bool {
901 false
902 }
903 }
904 unsafe impl Sync for GcHeap {}
905 pub static HEAP: GcHeap = GcHeap::new();
906
907 pub struct MutatorGuard;
908 impl Drop for MutatorGuard {
909 fn drop(&mut self) {}
910 }
911 pub struct StwGuard;
912 impl Drop for StwGuard {
913 fn drop(&mut self) {}
914 }
915 pub struct GcParked;
916 pub struct CancellableGuard;
917
918 pub struct GcCancellationStub;
919 impl GcCancellationStub {
920 pub const fn new() -> Self {
921 Self
922 }
923 pub fn in_progress(&self) -> bool {
924 false
925 }
926 }
927 pub static CONFIG_CANCELLATION: GcCancellationStub = GcCancellationStub::new();
928
929 pub fn safepoint() {}
930 pub fn gc_requested() -> bool {
931 false
932 }
933 pub fn take_gc_request() -> bool {
934 false
935 }
936 pub fn begin_stw() -> Option<StwGuard> {
937 None
938 }
939 pub fn register_mutator() -> MutatorGuard {
940 MutatorGuard
941 }
942 pub fn registered_threads() -> usize {
943 0
944 }
945 pub fn request_gc() {}
946 pub fn check_cancellation() -> Result<(), GcParked> {
947 Ok(())
948 }
949 pub fn park_thread() {}
950 pub fn unpark_thread() {}
951 pub fn wait_for_threads_to_park() {}
952
953 pub struct AllocRootGuard;
954 impl Drop for AllocRootGuard {
955 fn drop(&mut self) {}
956 }
957 pub fn push_alloc_frame() -> AllocRootGuard {
958 AllocRootGuard
959 }
960}
961
962#[cfg(all(test, not(feature = "no-gc")))]
967mod tests {
968 use super::*;
969 use std::sync::{Arc, Mutex};
970
971 #[derive(Debug)]
972 #[allow(dead_code)]
973 struct Tracked {
974 value: i32,
975 dropped: Arc<Mutex<bool>>,
976 }
977 impl Drop for Tracked {
978 fn drop(&mut self) {
979 *self.dropped.lock().unwrap() = true;
980 }
981 }
982 impl Trace for Tracked {
983 fn trace(&self, _: &mut MarkVisitor) {}
984 }
985
986 #[derive(Debug)]
987 #[allow(dead_code)]
988 struct Parent {
989 child: GcPtr<Tracked>,
990 }
991 impl Trace for Parent {
992 fn trace(&self, visitor: &mut MarkVisitor) {
993 visitor.visit(&self.child);
994 }
995 }
996
997 fn fresh_heap() -> gc_full::GcHeap {
998 let heap = gc_full::GcHeap::new();
999 heap.set_config(Arc::new(GcConfig::with_limits(10000, 50000)));
1000 heap
1001 }
1002
1003 #[test]
1004 fn alloc_and_get() {
1005 let heap = fresh_heap();
1006 let p = heap.alloc(42i64);
1007 assert_eq!(*p.get(), 42);
1008 assert_eq!(heap.count(), 1);
1009 }
1010
1011 #[test]
1012 fn clone_is_same_ptr() {
1013 let heap = fresh_heap();
1014 let p = heap.alloc(99i64);
1015 let q = p.clone();
1016 assert!(GcPtr::ptr_eq(&p, &q));
1017 }
1018
1019 #[test]
1020 fn collect_keeps_reachable() {
1021 let heap = fresh_heap();
1022 let dropped = Arc::new(Mutex::new(false));
1023 let p = heap.alloc(Tracked {
1024 value: 2,
1025 dropped: dropped.clone(),
1026 });
1027 heap.collect(|vis| vis.visit(&p));
1028 assert_eq!(heap.count(), 1);
1029 assert!(!*dropped.lock().unwrap());
1030 }
1031
1032 #[test]
1033 fn b1_two_isolates_independent_heaps() {
1034 use std::sync::{Arc, Barrier};
1035 let barrier = Arc::new(Barrier::new(2));
1038 let b1 = barrier.clone();
1039 let h1 = std::thread::Builder::new()
1040 .name("isolate-1".into())
1041 .spawn(move || {
1042 let _mutator = crate::register_mutator();
1043 let _ptrs: Vec<_> = (0_i64..100)
1045 .map(|i| crate::gc_full::HEAP.alloc(i))
1046 .collect();
1047 b1.wait(); assert_eq!(
1050 crate::gc_full::HEAP.count(),
1051 100,
1052 "isolate-1 heap count should be 100"
1053 );
1054 })
1055 .unwrap();
1056
1057 let b2 = barrier.clone();
1058 let h2 = std::thread::Builder::new()
1059 .name("isolate-2".into())
1060 .spawn(move || {
1061 let _mutator = crate::register_mutator();
1062 let _ptrs: Vec<_> = (0_i64..200)
1064 .map(|i| crate::gc_full::HEAP.alloc(i))
1065 .collect();
1066 b2.wait();
1067 assert_eq!(
1069 crate::gc_full::HEAP.count(),
1070 200,
1071 "isolate-2 heap count should be 200"
1072 );
1073 })
1074 .unwrap();
1075
1076 h1.join().expect("isolate-1 panicked");
1077 h2.join().expect("isolate-2 panicked");
1078 }
1079
1080 #[test]
1081 fn b1_two_isolates_gc_independently() {
1082 let h1 = std::thread::Builder::new()
1084 .name("gc-isolate-1".into())
1085 .spawn(|| {
1086 let _mutator = crate::register_mutator();
1087 let heap = &crate::gc_full::HEAP;
1088 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1089 for _ in 0..5 {
1091 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1092 heap.collect(|_| {});
1094 heap.collect(|_| {}); }
1096 })
1100 .unwrap();
1101
1102 let h2 = std::thread::Builder::new()
1103 .name("gc-isolate-2".into())
1104 .spawn(|| {
1105 let _mutator = crate::register_mutator();
1106 let heap = &crate::gc_full::HEAP;
1107 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1108 for _ in 0..5 {
1109 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1110 heap.collect(|_| {});
1111 heap.collect(|_| {});
1112 }
1113 })
1114 .unwrap();
1115
1116 h1.join().expect("gc-isolate-1 panicked");
1117 h2.join().expect("gc-isolate-2 panicked");
1118 }
1119}
1120
1121#[cfg(all(test, feature = "no-gc"))]
1122mod nogc_tests {
1123 use super::*;
1124 use alloc_ctx::{ScratchGuard, StaticCtxGuard};
1125
1126 #[test]
1127 fn alloc_in_static_context() {
1128 let _g = StaticCtxGuard::new();
1129 let p = GcPtr::new(42i64);
1130 assert_eq!(*p.get(), 42);
1131 }
1132
1133 #[test]
1134 fn alloc_in_scratch_region() {
1135 let mut scratch = ScratchGuard::new();
1136 let p = GcPtr::new(99i64);
1137 assert_eq!(*p.get(), 99);
1138 scratch.pop_for_return();
1139 assert_eq!(*p.get(), 99);
1140 }
1142
1143 #[test]
1144 fn ptr_eq() {
1145 let _g = StaticCtxGuard::new();
1146 let p = GcPtr::new(1i64);
1147 let q = p.clone();
1148 assert!(GcPtr::ptr_eq(&p, &q));
1149 }
1150}