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 MutatorGuard, StwGuard, begin_stw, check_cancellation, gc_requested, park_thread,
28 register_mutator, registered_threads, request_gc, safepoint, take_gc_request, unpark_thread,
29 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, GcConfig, GcHeap, GcParked, HEAP, MutatorGuard, StwGuard,
41 begin_stw, check_cancellation, gc_requested, park_thread, push_alloc_frame, register_mutator,
42 registered_threads, request_gc, safepoint, take_gc_request, unpark_thread,
43 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}
103macro_rules! impl_trace_prim_array {
104 ($t:ty) => {
105 impl Trace for std::sync::Mutex<Vec<$t>> {
106 fn trace(&self, _: &mut MarkVisitor) {}
107 fn gc_size_extra(&self) -> usize {
108 self.lock().unwrap().capacity() * std::mem::size_of::<$t>()
109 }
110 }
111 };
112}
113impl_trace_prim_array!(i32);
114impl_trace_prim_array!(i64);
115impl_trace_prim_array!(i16);
116impl_trace_prim_array!(i8);
117impl_trace_prim_array!(char);
118impl_trace_prim_array!(f64);
119impl_trace_prim_array!(f32);
120impl_trace_prim_array!(bool);
121
122pub trait GcVisitor {
125 fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>);
126}
127
128#[cfg(not(feature = "no-gc"))]
133pub use self::gc_header::{GcBox, GcBoxHeader};
134
135#[cfg(not(feature = "no-gc"))]
136mod gc_header {
137 use crate::{MarkVisitor, Trace};
138 use std::cell::Cell;
139
140 pub(crate) const GC_INITIAL_LIVES: u8 = 2;
148
149 #[cfg(debug_assertions)]
150 pub(crate) const GC_MAGIC_ALIVE: u64 = 0xCAFE_BABE_DEAD_BEEF;
151 #[cfg(debug_assertions)]
152 pub(crate) const GC_MAGIC_FREED: u64 = 0xDEAD_DEAD_DEAD_DEAD;
153
154 #[repr(C)]
155 pub struct GcBoxHeader {
156 #[cfg(debug_assertions)]
157 pub(crate) magic: Cell<u64>,
158 pub(crate) size: usize,
160 pub(crate) lives: Cell<u8>,
161 pub(crate) next: Cell<*mut GcBoxHeader>,
162 pub(crate) trace_fn: unsafe fn(*const GcBoxHeader, &mut MarkVisitor),
163 pub(crate) drop_fn: unsafe fn(*mut GcBoxHeader),
164 }
165
166 impl GcBoxHeader {
167 pub(crate) fn new<T: Trace + 'static>(heap_extra: usize) -> Self {
168 Self {
169 #[cfg(debug_assertions)]
170 magic: Cell::new(GC_MAGIC_ALIVE),
171 size: std::mem::size_of::<GcBox<T>>() + heap_extra,
172 lives: Cell::new(GC_INITIAL_LIVES - 1),
173 next: Cell::new(std::ptr::null_mut()),
174 trace_fn: trace_gc_box::<T>,
175 drop_fn: drop_gc_box::<T>,
176 }
177 }
178 }
179
180 unsafe impl Send for GcBoxHeader {}
181 unsafe impl Sync for GcBoxHeader {}
182
183 #[repr(C)]
184 pub struct GcBox<T: Trace + 'static> {
185 pub(crate) header: GcBoxHeader,
186 pub value: T,
187 }
188
189 pub(crate) unsafe fn trace_gc_box<T: Trace + 'static>(
190 header: *const GcBoxHeader,
191 visitor: &mut MarkVisitor,
192 ) {
193 unsafe {
194 let gc_box = header as *const GcBox<T>;
195 (*gc_box).value.trace(visitor);
196 }
197 }
198
199 pub(crate) unsafe fn drop_gc_box<T: Trace + 'static>(header: *mut GcBoxHeader) {
200 unsafe {
201 #[cfg(debug_assertions)]
202 {
203 (*header).magic.set(GC_MAGIC_FREED);
204 }
205 let gc_box = header as *mut GcBox<T>;
206 drop(Box::from_raw(gc_box));
207 }
208 }
209}
210
211#[cfg(feature = "no-gc")]
216pub use self::nogc_box::GcBox;
217
218#[cfg(feature = "no-gc")]
219mod nogc_box {
220 use crate::Trace;
221
222 pub struct GcBox<T: Trace + 'static> {
223 pub value: T,
224 }
225}
226
227#[cfg(not(feature = "no-gc"))]
232#[derive(Default)]
233pub struct MarkVisitor {
234 pub(crate) grey: Vec<*mut GcBoxHeader>,
235}
236
237#[cfg(feature = "no-gc")]
238pub struct MarkVisitor;
239
240#[cfg(not(feature = "no-gc"))]
241impl MarkVisitor {
242 pub fn new() -> Self {
243 Self::default()
244 }
245
246 pub fn grey_len(&self) -> usize {
247 self.grey.len()
248 }
249
250 pub unsafe fn mark_header(&mut self, header: *mut GcBoxHeader) {
251 use gc_header::GC_INITIAL_LIVES;
252 let h = unsafe { &*header };
253 if h.lives.get() < GC_INITIAL_LIVES {
254 h.lives.set(GC_INITIAL_LIVES);
255 self.grey.push(header);
256 }
257 }
258
259 pub(crate) fn drain(&mut self) {
260 let mut visited = 0usize;
261 while let Some(header) = self.grey.pop() {
262 visited += 1;
263 let h = unsafe { &*header };
264 unsafe { (h.trace_fn)(header as *const GcBoxHeader, self) };
265 }
266 tracing::debug!(target: "gc", "drain visited {} objects", visited);
267 }
268}
269
270#[cfg(not(feature = "no-gc"))]
271impl GcVisitor for MarkVisitor {
272 fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>) {
273 use gc_header::GC_INITIAL_LIVES;
274 if ptr.is_region_alloc() {
282 return;
283 }
284 let raw = ptr.raw();
285 let header = unsafe { &(*raw).header };
286 if header.lives.get() < GC_INITIAL_LIVES {
287 header.lives.set(GC_INITIAL_LIVES);
288 self.grey.push(raw as *mut GcBoxHeader);
289 }
290 }
291}
292
293#[cfg(feature = "no-gc")]
294impl MarkVisitor {
295 pub fn grey_len(&self) -> usize {
296 0
297 }
298 pub unsafe fn mark_header(&mut self, _: *mut u8) {}
299}
300
301#[cfg(feature = "no-gc")]
302impl GcVisitor for MarkVisitor {
303 fn visit<T: Trace + 'static>(&mut self, _: &GcPtr<T>) {}
304}
305
306pub struct GcPtr<T: Trace + 'static>(NonNull<GcBox<T>>);
311
312#[cfg(not(feature = "no-gc"))]
321pub(crate) const REGION_PTR_TAG: usize = 1;
322
323impl<T: Trace + 'static> GcPtr<T> {
324 #[cfg(not(feature = "no-gc"))]
325 pub fn new(value: T) -> Self {
326 gc_full::HEAP.alloc(value)
327 }
328
329 #[cfg(feature = "no-gc")]
330 pub fn new(value: T) -> Self {
331 alloc_ctx::alloc_in_ctx(value)
332 }
333
334 #[inline]
337 fn raw(&self) -> *mut GcBox<T> {
338 #[cfg(not(feature = "no-gc"))]
339 {
340 (self.0.as_ptr() as usize & !REGION_PTR_TAG) as *mut GcBox<T>
341 }
342 #[cfg(feature = "no-gc")]
343 {
344 self.0.as_ptr()
345 }
346 }
347
348 #[cfg(not(feature = "no-gc"))]
351 #[inline]
352 pub fn is_region_alloc(&self) -> bool {
353 (self.0.as_ptr() as usize & REGION_PTR_TAG) != 0
354 }
355
356 #[cfg(not(feature = "no-gc"))]
363 #[inline]
364 pub(crate) unsafe fn from_region_raw(raw: *mut GcBox<T>) -> Self {
365 GcPtr(unsafe { NonNull::new_unchecked((raw as usize | REGION_PTR_TAG) as *mut GcBox<T>) })
366 }
367
368 pub fn get(&self) -> &T {
369 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
370 {
371 use gc_header::GC_MAGIC_ALIVE;
372 let header = unsafe { &(*self.raw()).header };
373 assert_eq!(
374 header.magic.get(),
375 GC_MAGIC_ALIVE,
376 "GcPtr::get() on freed object! magic={:#x}",
377 header.magic.get(),
378 );
379 }
380 unsafe { &(*self.raw()).value }
381 }
382
383 pub fn get_mut(&mut self) -> &mut T {
384 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
385 {
386 use gc_header::GC_MAGIC_ALIVE;
387 let header = unsafe { &(*self.raw()).header };
388 assert_eq!(
389 header.magic.get(),
390 GC_MAGIC_ALIVE,
391 "GcPtr::get_mut() on freed object! magic={:#x}",
392 header.magic.get(),
393 );
394 }
395 unsafe { &mut (*self.raw()).value }
396 }
397
398 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
399 a.0 == b.0
400 }
401
402 #[cfg(all(feature = "no-gc", debug_assertions))]
408 pub fn is_static_alloc(&self) -> bool {
409 alloc_ctx::invocation_is_active() || static_arena::is_static_addr(self.0.as_ptr() as usize)
410 }
411}
412
413impl<T: Trace + 'static> Clone for GcPtr<T> {
414 fn clone(&self) -> Self {
415 GcPtr(self.0)
416 }
417}
418
419impl<T: Trace + 'static + std::fmt::Debug> std::fmt::Debug for GcPtr<T> {
420 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
421 unsafe { (*self.raw()).value.fmt(f) }
422 }
423}
424
425impl<T: Trace + 'static> Drop for GcPtr<T> {
426 fn drop(&mut self) {}
427}
428
429pub struct StaticGcPtr<T: 'static>(NonNull<T>);
443
444unsafe impl<T: 'static> Send for StaticGcPtr<T> {}
448unsafe impl<T: 'static> Sync for StaticGcPtr<T> {}
449
450impl<T: 'static> StaticGcPtr<T> {
451 pub fn get(&self) -> &T {
453 unsafe { self.0.as_ref() }
455 }
456
457 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
460 a.0 == b.0
461 }
462}
463
464impl<T: 'static> Clone for StaticGcPtr<T> {
465 fn clone(&self) -> Self {
466 StaticGcPtr(self.0)
467 }
468}
469
470impl<T: 'static + std::fmt::Debug> std::fmt::Debug for StaticGcPtr<T> {
471 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472 unsafe { self.0.as_ref().fmt(f) }
473 }
474}
475
476pub fn static_alloc<T: 'static>(value: T) -> StaticGcPtr<T> {
482 #[cfg(feature = "no-gc")]
483 {
484 static_arena::static_alloc_val(value)
485 }
486 #[cfg(not(feature = "no-gc"))]
487 {
488 StaticGcPtr(NonNull::from(Box::leak(Box::new(value))))
489 }
490}
491
492#[cfg(not(feature = "no-gc"))]
497mod gc_full {
498 use std::cell::RefCell;
499 use std::ptr::NonNull;
500 use std::sync::atomic::{AtomicUsize, Ordering};
501 use std::sync::{Arc, Mutex};
502
503 use crate::config::GcConfig;
504 use crate::gc_header::GC_INITIAL_LIVES;
505 use crate::{GcBox, GcBoxHeader, GcPtr, MarkVisitor, Trace};
506
507 type RootTracer = Box<dyn Fn(&mut MarkVisitor)>;
508
509 pub struct GcHeap {
510 inner: Mutex<GcHeapInner>,
511 config: Mutex<Option<Arc<GcConfig>>>,
512 memory_in_use: AtomicUsize,
513 total_allocated_bytes: AtomicUsize,
514 root_tracers: Mutex<Vec<RootTracer>>,
515 gc_suppressed: std::sync::atomic::AtomicBool,
516 suppressed_threshold: AtomicUsize,
523 zero_yield_headroom: AtomicUsize,
525 }
526
527 struct GcHeapInner {
528 head: *mut GcBoxHeader,
529 count: usize,
530 total_allocated: usize,
531 total_freed: usize,
532 }
533
534 unsafe impl Send for GcHeapInner {}
535
536 impl GcHeapInner {
537 const fn new() -> Self {
538 Self {
539 head: std::ptr::null_mut(),
540 count: 0,
541 total_allocated: 0,
542 total_freed: 0,
543 }
544 }
545 }
546
547 unsafe impl Sync for GcHeap {}
548
549 impl Default for GcHeap {
550 fn default() -> Self {
551 Self::new()
552 }
553 }
554
555 pub(crate) fn parse_limit_mb(var: &str, value: Option<&str>, default: usize) -> usize {
560 match value {
561 Some(s) => match s.trim().parse::<usize>() {
562 Ok(mb) => mb.saturating_mul(1024 * 1024),
563 Err(_) => {
564 eprintln!(
565 "[gc] warning: ignoring invalid {var}={s:?} (expected a number of megabytes)"
566 );
567 default
568 }
569 },
570 None => default,
571 }
572 }
573
574 impl GcHeap {
575 pub const fn new() -> Self {
576 Self {
577 inner: Mutex::new(GcHeapInner::new()),
578 config: Mutex::new(None),
579 memory_in_use: AtomicUsize::new(0),
580 total_allocated_bytes: AtomicUsize::new(0),
581 root_tracers: Mutex::new(Vec::new()),
582 gc_suppressed: std::sync::atomic::AtomicBool::new(false),
583 suppressed_threshold: AtomicUsize::new(0),
584 zero_yield_headroom: AtomicUsize::new(0),
585 }
586 }
587
588 pub fn set_config(&self, config: Arc<GcConfig>) {
589 *self.config.lock().unwrap() = Some(config);
590 }
591
592 pub fn set_config_from_env(&self) {
593 #[cfg(not(target_arch = "wasm32"))]
594 let default_soft_limit: usize = (system_memory::total() / 3) as usize;
595 #[cfg(target_arch = "wasm32")]
596 let default_soft_limit: usize = 64 * 1024 * 1024;
597
598 let soft_limit_mb = parse_limit_mb(
599 "CLJRS_GC_SOFT_LIMIT_MB",
600 std::env::var("CLJRS_GC_SOFT_LIMIT_MB").ok().as_deref(),
601 default_soft_limit,
602 );
603 let hard_limit_mb = parse_limit_mb(
604 "CLJRS_GC_HARD_LIMIT_MB",
605 std::env::var("CLJRS_GC_HARD_LIMIT_MB").ok().as_deref(),
606 soft_limit_mb,
607 );
608 self.set_config(Arc::new(GcConfig::with_limits(
609 soft_limit_mb,
610 hard_limit_mb,
611 )));
612 }
613
614 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
615 self.root_tracers.lock().unwrap().push(Box::new(tracer));
616 }
617
618 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
619 let tracers = self.root_tracers.lock().unwrap();
620 for tracer in tracers.iter() {
621 tracer(visitor);
622 }
623 }
624
625 pub fn memory_in_use(&self) -> usize {
626 self.memory_in_use.load(Ordering::Relaxed)
627 }
628
629 #[cfg(test)]
630 pub fn set_memory_in_use(&self, bytes: usize) {
631 self.memory_in_use.store(bytes, Ordering::Relaxed);
632 }
633
634 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
635 crate::cancellation::safepoint();
636 let heap_extra = value.gc_size_extra();
637 let gc_box = Box::new(GcBox {
638 header: GcBoxHeader::new::<T>(heap_extra),
639 value,
640 });
641 let obj_size = gc_box.header.size; let raw: *mut GcBox<T> = Box::into_raw(gc_box);
643 {
644 let mut inner = self.inner.lock().unwrap();
645 unsafe {
646 (*raw).header.next.set(inner.head);
647 inner.head = raw as *mut GcBoxHeader;
648 }
649 inner.count += 1;
650 inner.total_allocated += 1;
651 }
652 self.total_allocated_bytes
653 .fetch_add(obj_size, Ordering::Relaxed);
654 crate::stats::GC_STATS.record_gc_alloc(obj_size);
655 let current_usage =
656 self.memory_in_use.fetch_add(obj_size, Ordering::Relaxed) + obj_size;
657
658 if let Some(config) = self.config.lock().unwrap().as_ref()
659 && config.soft_limit_exceeded(current_usage)
660 {
661 if self.gc_suppressed.load(Ordering::Relaxed) {
662 let threshold = self.suppressed_threshold.load(Ordering::Relaxed);
666 if current_usage > threshold {
667 self.gc_suppressed.store(false, Ordering::Relaxed);
668 crate::cancellation::request_gc();
669 }
670 } else {
671 crate::cancellation::request_gc();
672 }
673 }
674
675 register_alloc(raw as *mut GcBoxHeader);
676 GcPtr(unsafe { NonNull::new_unchecked(raw) })
677 }
678
679 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
680 let pre_count = self.inner.lock().unwrap().count;
681 let pre_memory = self.memory_in_use.load(Ordering::Relaxed);
682 tracing::debug!(
683 target: "gc",
684 "starting collection: {} objects, ~{} bytes in use",
685 pre_count,
686 pre_memory
687 );
688 let mark_start = std::time::Instant::now();
689 let mut visitor = MarkVisitor::new();
690 trace_roots(&mut visitor);
691 crate::region::trace_active_regions(&mut visitor);
697 crate::region::trace_retired_regions(&mut visitor);
700 tracing::debug!(
701 target: "gc",
702 "starting drain with {} grey objects",
703 visitor.grey.len()
704 );
705 visitor.drain();
706 let mark_elapsed = mark_start.elapsed();
707
708 let sweep_start = std::time::Instant::now();
709 let mut inner = self.inner.lock().unwrap();
710 let mut live: Vec<*mut GcBoxHeader> = Vec::with_capacity(inner.count);
711 let mut dead: Vec<*mut GcBoxHeader> = Vec::new();
712 let mut freed_bytes: usize = 0;
714 let mut current = inner.head;
715 while !current.is_null() {
716 let header = unsafe { &*current };
717 let next = header.next.get();
718 let lives = header.lives.get();
719 let obj_size = header.size;
720 if lives >= GC_INITIAL_LIVES {
721 header.lives.set(GC_INITIAL_LIVES - 1);
723 live.push(current);
724 } else if lives > 0 {
725 header.lives.set(lives - 1);
727 live.push(current);
728 } else {
729 freed_bytes += obj_size;
731 dead.push(current);
732 }
733 current = next;
734 }
735 let freed_count = dead.len();
736 for ptr in dead {
737 let header = unsafe { &*ptr };
738 unsafe { (header.drop_fn)(ptr) };
739 inner.count -= 1;
740 inner.total_freed += 1;
741 }
742 inner.head = std::ptr::null_mut();
743 for ptr in live {
744 let header = unsafe { &*ptr };
745 header.next.set(inner.head);
746 inner.head = ptr;
747 }
748 self.memory_in_use.fetch_sub(freed_bytes, Ordering::Relaxed);
753 let sweep_elapsed = sweep_start.elapsed();
754 crate::stats::GC_STATS.record_gc_pause(
755 mark_elapsed + sweep_elapsed,
756 freed_count as u64,
757 freed_bytes as u64,
758 );
759 let post_memory = self.memory_in_use.load(Ordering::Relaxed);
760 tracing::debug!(
761 target: "gc",
762 "collection complete: freed {} (~{} bytes), {} remaining (~{} bytes), mark={:.2?} sweep={:.2?}",
763 freed_count,
764 freed_bytes,
765 inner.count,
766 post_memory,
767 mark_elapsed,
768 sweep_elapsed
769 );
770 if freed_count == 0 {
771 let soft_limit = self
782 .config
783 .lock()
784 .unwrap()
785 .as_ref()
786 .map(|c| c.soft_limit())
787 .unwrap_or(64 * 1024 * 1024);
788 let base_headroom = (soft_limit / 10).max(1);
789 let max_headroom = (soft_limit / 4).max(base_headroom);
790 let prev_headroom = self.zero_yield_headroom.load(Ordering::Relaxed);
791 let headroom = if prev_headroom == 0 {
792 base_headroom
793 } else {
794 prev_headroom.saturating_mul(2).min(max_headroom)
795 };
796 self.zero_yield_headroom.store(headroom, Ordering::Relaxed);
797 self.suppressed_threshold
798 .store(post_memory + headroom, Ordering::Relaxed);
799 self.gc_suppressed.store(true, Ordering::Relaxed);
800 } else {
801 self.zero_yield_headroom.store(0, Ordering::Relaxed);
803 self.gc_suppressed.store(false, Ordering::Relaxed);
804 }
805 }
806
807 pub fn count(&self) -> usize {
808 self.inner.lock().unwrap().count
809 }
810 pub fn total_allocated(&self) -> usize {
811 self.inner.lock().unwrap().total_allocated
812 }
813 pub fn total_freed(&self) -> usize {
814 self.inner.lock().unwrap().total_freed
815 }
816
817 pub fn collect_auto(&self) -> bool {
818 tracing::debug!(target: "gc", "automatic collection requested");
819 let Some(_stw_guard) = crate::cancellation::begin_stw() else {
820 tracing::debug!(target: "gc", "automatic collection skipped");
821 return false;
822 };
823 self.collect(|visitor| self.trace_registered_roots(visitor));
824 true
825 }
826 }
827
828 thread_local! {
829 static ISOLATE_HEAP: GcHeap = const { GcHeap::new() };
830 }
831
832 pub struct HeapProxy;
838
839 impl HeapProxy {
840 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
841 ISOLATE_HEAP.with(|h| h.alloc(value))
842 }
843
844 pub fn set_config(&self, config: Arc<GcConfig>) {
845 ISOLATE_HEAP.with(|h| h.set_config(config));
846 }
847
848 pub fn set_config_from_env(&self) {
849 ISOLATE_HEAP.with(|h| h.set_config_from_env());
850 }
851
852 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
853 ISOLATE_HEAP.with(|h| h.register_root_tracer(tracer));
854 }
855
856 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
857 ISOLATE_HEAP.with(|h| h.trace_registered_roots(visitor));
858 }
859
860 pub fn memory_in_use(&self) -> usize {
861 ISOLATE_HEAP.with(|h| h.memory_in_use())
862 }
863
864 pub fn count(&self) -> usize {
865 ISOLATE_HEAP.with(|h| h.count())
866 }
867
868 pub fn total_allocated(&self) -> usize {
869 ISOLATE_HEAP.with(|h| h.total_allocated())
870 }
871
872 pub fn total_freed(&self) -> usize {
873 ISOLATE_HEAP.with(|h| h.total_freed())
874 }
875
876 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
877 ISOLATE_HEAP.with(|h| h.collect(trace_roots));
878 }
879
880 pub fn collect_auto(&self) -> bool {
881 ISOLATE_HEAP.with(|h| h.collect_auto())
882 }
883
884 #[cfg(test)]
885 pub fn set_memory_in_use(&self, bytes: usize) {
886 ISOLATE_HEAP.with(|h| h.set_memory_in_use(bytes));
887 }
888 }
889
890 unsafe impl Sync for HeapProxy {}
893
894 pub static HEAP: HeapProxy = HeapProxy;
895
896 thread_local! {
897 pub(crate) static ALLOC_ROOTS: RefCell<Vec<*mut GcBoxHeader>> = const { RefCell::new(Vec::new()) };
898 }
899
900 pub struct AllocRootGuard {
901 saved_len: usize,
902 }
903
904 impl Drop for AllocRootGuard {
905 fn drop(&mut self) {
906 ALLOC_ROOTS.with(|roots| roots.borrow_mut().truncate(self.saved_len));
907 }
908 }
909
910 pub fn push_alloc_frame() -> AllocRootGuard {
911 let saved_len = ALLOC_ROOTS.with(|roots| roots.borrow().len());
912 AllocRootGuard { saved_len }
913 }
914
915 fn register_alloc(header: *mut GcBoxHeader) {
916 ALLOC_ROOTS.with(|roots| roots.borrow_mut().push(header));
917 }
918
919 pub fn trace_thread_alloc_roots(visitor: &mut MarkVisitor) {
920 ALLOC_ROOTS.with(|roots| {
921 let roots = roots.borrow();
922 for &header in roots.iter() {
923 unsafe { visitor.mark_header(header) };
924 }
925 });
926 }
927}
928
929#[cfg(feature = "no-gc")]
934mod nogc_stubs {
935 use crate::MarkVisitor;
936 use std::sync::Arc;
937
938 #[derive(Debug, Clone)]
939 pub struct GcConfig;
940 impl GcConfig {
941 pub fn new() -> Self {
942 Self
943 }
944 pub fn with_hard_limit(_: usize) -> Self {
945 Self
946 }
947 pub fn with_limits(_: usize, _: usize) -> Self {
948 Self
949 }
950 }
951 impl Default for GcConfig {
952 fn default() -> Self {
953 Self::new()
954 }
955 }
956
957 pub struct GcHeap;
958 impl Default for GcHeap {
959 fn default() -> Self {
960 Self::new()
961 }
962 }
963 impl GcHeap {
964 pub const fn new() -> Self {
965 Self
966 }
967 pub fn set_config(&self, _: Arc<GcConfig>) {}
968 pub fn set_config_from_env(&self) {}
973 pub fn register_root_tracer(&self, _: impl Fn(&mut MarkVisitor) + 'static) {}
974 pub fn trace_registered_roots(&self, _: &mut MarkVisitor) {}
975 pub fn memory_in_use(&self) -> usize {
976 0
977 }
978 pub fn count(&self) -> usize {
979 0
980 }
981 pub fn total_allocated(&self) -> usize {
982 0
983 }
984 pub fn total_freed(&self) -> usize {
985 0
986 }
987 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, _: F) {}
988 pub fn collect_auto(&self) -> bool {
989 false
990 }
991 }
992 unsafe impl Sync for GcHeap {}
993 pub static HEAP: GcHeap = GcHeap::new();
994
995 pub struct MutatorGuard;
996 impl Drop for MutatorGuard {
997 fn drop(&mut self) {}
998 }
999 pub struct StwGuard;
1000 impl Drop for StwGuard {
1001 fn drop(&mut self) {}
1002 }
1003 pub struct GcParked;
1004
1005 pub struct GcCancellationStub;
1006 impl GcCancellationStub {
1007 pub const fn new() -> Self {
1008 Self
1009 }
1010 pub fn in_progress(&self) -> bool {
1011 false
1012 }
1013 }
1014 pub static CONFIG_CANCELLATION: GcCancellationStub = GcCancellationStub::new();
1015
1016 pub fn safepoint() {}
1017 pub fn gc_requested() -> bool {
1018 false
1019 }
1020 pub fn take_gc_request() -> bool {
1021 false
1022 }
1023 pub fn begin_stw() -> Option<StwGuard> {
1024 None
1025 }
1026 pub fn register_mutator() -> MutatorGuard {
1027 MutatorGuard
1028 }
1029 pub fn registered_threads() -> usize {
1030 0
1031 }
1032 pub fn request_gc() {}
1033 pub fn check_cancellation() -> Result<(), GcParked> {
1034 Ok(())
1035 }
1036 pub fn park_thread() {}
1037 pub fn unpark_thread() {}
1038 pub fn wait_for_threads_to_park() {}
1039
1040 pub struct AllocRootGuard;
1041 impl Drop for AllocRootGuard {
1042 fn drop(&mut self) {}
1043 }
1044 pub fn push_alloc_frame() -> AllocRootGuard {
1045 AllocRootGuard
1046 }
1047}
1048
1049#[cfg(all(test, not(feature = "no-gc")))]
1054mod tests {
1055 use super::*;
1056 use std::sync::{Arc, Mutex};
1057
1058 #[derive(Debug)]
1059 #[allow(dead_code)]
1060 struct Tracked {
1061 value: i32,
1062 dropped: Arc<Mutex<bool>>,
1063 }
1064 impl Drop for Tracked {
1065 fn drop(&mut self) {
1066 *self.dropped.lock().unwrap() = true;
1067 }
1068 }
1069 impl Trace for Tracked {
1070 fn trace(&self, _: &mut MarkVisitor) {}
1071 }
1072
1073 #[derive(Debug)]
1074 #[allow(dead_code)]
1075 struct Parent {
1076 child: GcPtr<Tracked>,
1077 }
1078 impl Trace for Parent {
1079 fn trace(&self, visitor: &mut MarkVisitor) {
1080 visitor.visit(&self.child);
1081 }
1082 }
1083
1084 fn fresh_heap() -> gc_full::GcHeap {
1085 let heap = gc_full::GcHeap::new();
1086 heap.set_config(Arc::new(GcConfig::with_limits(10000, 50000)));
1087 heap
1088 }
1089
1090 #[test]
1091 fn alloc_and_get() {
1092 let heap = fresh_heap();
1093 let p = heap.alloc(42i64);
1094 assert_eq!(*p.get(), 42);
1095 assert_eq!(heap.count(), 1);
1096 }
1097
1098 #[test]
1099 fn clone_is_same_ptr() {
1100 let heap = fresh_heap();
1101 let p = heap.alloc(99i64);
1102 let q = p.clone();
1103 assert!(GcPtr::ptr_eq(&p, &q));
1104 }
1105
1106 #[test]
1107 fn parse_limit_mb_handles_valid_unset_and_malformed() {
1108 assert_eq!(gc_full::parse_limit_mb("X", Some("4"), 7), 4 * 1024 * 1024);
1110 assert_eq!(
1112 gc_full::parse_limit_mb("X", Some(" 4 "), 7),
1113 4 * 1024 * 1024
1114 );
1115 assert_eq!(gc_full::parse_limit_mb("X", None, 7), 7);
1117 assert_eq!(gc_full::parse_limit_mb("X", Some("foo"), 7), 7);
1119 assert_eq!(gc_full::parse_limit_mb("X", Some(""), 7), 7);
1120 assert_eq!(gc_full::parse_limit_mb("X", Some("-1"), 7), 7);
1121 assert_eq!(
1123 gc_full::parse_limit_mb("X", Some(&usize::MAX.to_string()), 7),
1124 usize::MAX
1125 );
1126 }
1127
1128 #[test]
1129 fn collect_keeps_reachable() {
1130 let heap = fresh_heap();
1131 let dropped = Arc::new(Mutex::new(false));
1132 let p = heap.alloc(Tracked {
1133 value: 2,
1134 dropped: dropped.clone(),
1135 });
1136 heap.collect(|vis| vis.visit(&p));
1137 assert_eq!(heap.count(), 1);
1138 assert!(!*dropped.lock().unwrap());
1139 }
1140
1141 #[test]
1142 fn b1_two_isolates_independent_heaps() {
1143 use std::sync::{Arc, Barrier};
1144 let barrier = Arc::new(Barrier::new(2));
1147 let b1 = barrier.clone();
1148 let h1 = std::thread::Builder::new()
1149 .name("isolate-1".into())
1150 .spawn(move || {
1151 let _mutator = crate::register_mutator();
1152 let _ptrs: Vec<_> = (0_i64..100)
1154 .map(|i| crate::gc_full::HEAP.alloc(i))
1155 .collect();
1156 b1.wait(); assert_eq!(
1159 crate::gc_full::HEAP.count(),
1160 100,
1161 "isolate-1 heap count should be 100"
1162 );
1163 })
1164 .unwrap();
1165
1166 let b2 = barrier.clone();
1167 let h2 = std::thread::Builder::new()
1168 .name("isolate-2".into())
1169 .spawn(move || {
1170 let _mutator = crate::register_mutator();
1171 let _ptrs: Vec<_> = (0_i64..200)
1173 .map(|i| crate::gc_full::HEAP.alloc(i))
1174 .collect();
1175 b2.wait();
1176 assert_eq!(
1178 crate::gc_full::HEAP.count(),
1179 200,
1180 "isolate-2 heap count should be 200"
1181 );
1182 })
1183 .unwrap();
1184
1185 h1.join().expect("isolate-1 panicked");
1186 h2.join().expect("isolate-2 panicked");
1187 }
1188
1189 #[test]
1190 fn b1_two_isolates_gc_independently() {
1191 let h1 = std::thread::Builder::new()
1193 .name("gc-isolate-1".into())
1194 .spawn(|| {
1195 let _mutator = crate::register_mutator();
1196 let heap = &crate::gc_full::HEAP;
1197 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1198 for _ in 0..5 {
1200 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1201 heap.collect(|_| {});
1203 heap.collect(|_| {}); }
1205 })
1209 .unwrap();
1210
1211 let h2 = std::thread::Builder::new()
1212 .name("gc-isolate-2".into())
1213 .spawn(|| {
1214 let _mutator = crate::register_mutator();
1215 let heap = &crate::gc_full::HEAP;
1216 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1217 for _ in 0..5 {
1218 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1219 heap.collect(|_| {});
1220 heap.collect(|_| {});
1221 }
1222 })
1223 .unwrap();
1224
1225 h1.join().expect("gc-isolate-1 panicked");
1226 h2.join().expect("gc-isolate-2 panicked");
1227 }
1228}
1229
1230#[cfg(all(test, feature = "no-gc"))]
1231mod nogc_tests {
1232 use super::*;
1233 use alloc_ctx::{ScratchGuard, StaticCtxGuard};
1234
1235 #[test]
1236 fn alloc_in_static_context() {
1237 let _g = StaticCtxGuard::new();
1238 let p = GcPtr::new(42i64);
1239 assert_eq!(*p.get(), 42);
1240 }
1241
1242 #[test]
1243 fn alloc_in_scratch_region() {
1244 let mut scratch = ScratchGuard::new();
1245 let p = GcPtr::new(99i64);
1246 assert_eq!(*p.get(), 99);
1247 scratch.pop_for_return();
1248 assert_eq!(*p.get(), 99);
1249 }
1251
1252 #[test]
1253 fn ptr_eq() {
1254 let _g = StaticCtxGuard::new();
1255 let p = GcPtr::new(1i64);
1256 let q = p.clone();
1257 assert!(GcPtr::ptr_eq(&p, &q));
1258 }
1259}