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}
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 tracing::debug!(target: "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 if ptr.is_region_alloc() {
285 return;
286 }
287 let raw = ptr.raw();
288 let header = unsafe { &(*raw).header };
289 if header.lives.get() < GC_INITIAL_LIVES {
290 header.lives.set(GC_INITIAL_LIVES);
291 self.grey.push(raw as *mut GcBoxHeader);
292 }
293 }
294}
295
296#[cfg(feature = "no-gc")]
297impl MarkVisitor {
298 pub fn grey_len(&self) -> usize {
299 0
300 }
301 pub unsafe fn mark_header(&mut self, _: *mut u8) {}
302}
303
304#[cfg(feature = "no-gc")]
305impl GcVisitor for MarkVisitor {
306 fn visit<T: Trace + 'static>(&mut self, _: &GcPtr<T>) {}
307}
308
309pub struct GcPtr<T: Trace + 'static>(NonNull<GcBox<T>>);
314
315#[cfg(not(feature = "no-gc"))]
324pub(crate) const REGION_PTR_TAG: usize = 1;
325
326impl<T: Trace + 'static> GcPtr<T> {
327 #[cfg(not(feature = "no-gc"))]
328 pub fn new(value: T) -> Self {
329 gc_full::HEAP.alloc(value)
330 }
331
332 #[cfg(feature = "no-gc")]
333 pub fn new(value: T) -> Self {
334 alloc_ctx::alloc_in_ctx(value)
335 }
336
337 #[inline]
340 fn raw(&self) -> *mut GcBox<T> {
341 #[cfg(not(feature = "no-gc"))]
342 {
343 (self.0.as_ptr() as usize & !REGION_PTR_TAG) as *mut GcBox<T>
344 }
345 #[cfg(feature = "no-gc")]
346 {
347 self.0.as_ptr()
348 }
349 }
350
351 #[cfg(not(feature = "no-gc"))]
354 #[inline]
355 pub fn is_region_alloc(&self) -> bool {
356 (self.0.as_ptr() as usize & REGION_PTR_TAG) != 0
357 }
358
359 #[cfg(not(feature = "no-gc"))]
366 #[inline]
367 pub(crate) unsafe fn from_region_raw(raw: *mut GcBox<T>) -> Self {
368 GcPtr(unsafe { NonNull::new_unchecked((raw as usize | REGION_PTR_TAG) as *mut GcBox<T>) })
369 }
370
371 pub fn get(&self) -> &T {
372 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
373 {
374 use gc_header::GC_MAGIC_ALIVE;
375 let header = unsafe { &(*self.raw()).header };
376 assert_eq!(
377 header.magic.get(),
378 GC_MAGIC_ALIVE,
379 "GcPtr::get() on freed object! magic={:#x}",
380 header.magic.get(),
381 );
382 }
383 unsafe { &(*self.raw()).value }
384 }
385
386 pub fn get_mut(&mut self) -> &mut T {
387 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
388 {
389 use gc_header::GC_MAGIC_ALIVE;
390 let header = unsafe { &(*self.raw()).header };
391 assert_eq!(
392 header.magic.get(),
393 GC_MAGIC_ALIVE,
394 "GcPtr::get_mut() on freed object! magic={:#x}",
395 header.magic.get(),
396 );
397 }
398 unsafe { &mut (*self.raw()).value }
399 }
400
401 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
402 a.0 == b.0
403 }
404
405 #[cfg(all(feature = "no-gc", debug_assertions))]
411 pub fn is_static_alloc(&self) -> bool {
412 alloc_ctx::invocation_is_active() || static_arena::is_static_addr(self.0.as_ptr() as usize)
413 }
414}
415
416impl<T: Trace + 'static> Clone for GcPtr<T> {
417 fn clone(&self) -> Self {
418 GcPtr(self.0)
419 }
420}
421
422impl<T: Trace + 'static + std::fmt::Debug> std::fmt::Debug for GcPtr<T> {
423 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424 unsafe { (*self.raw()).value.fmt(f) }
425 }
426}
427
428impl<T: Trace + 'static> Drop for GcPtr<T> {
429 fn drop(&mut self) {}
430}
431
432pub struct StaticGcPtr<T: 'static>(NonNull<T>);
446
447unsafe impl<T: 'static> Send for StaticGcPtr<T> {}
451unsafe impl<T: 'static> Sync for StaticGcPtr<T> {}
452
453impl<T: 'static> StaticGcPtr<T> {
454 pub fn get(&self) -> &T {
456 unsafe { self.0.as_ref() }
458 }
459
460 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
463 a.0 == b.0
464 }
465}
466
467impl<T: 'static> Clone for StaticGcPtr<T> {
468 fn clone(&self) -> Self {
469 StaticGcPtr(self.0)
470 }
471}
472
473impl<T: 'static + std::fmt::Debug> std::fmt::Debug for StaticGcPtr<T> {
474 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475 unsafe { self.0.as_ref().fmt(f) }
476 }
477}
478
479pub fn static_alloc<T: 'static>(value: T) -> StaticGcPtr<T> {
485 #[cfg(feature = "no-gc")]
486 {
487 static_arena::static_alloc_val(value)
488 }
489 #[cfg(not(feature = "no-gc"))]
490 {
491 StaticGcPtr(NonNull::from(Box::leak(Box::new(value))))
492 }
493}
494
495#[cfg(not(feature = "no-gc"))]
500mod gc_full {
501 use std::cell::RefCell;
502 use std::ptr::NonNull;
503 use std::sync::atomic::{AtomicUsize, Ordering};
504 use std::sync::{Arc, Mutex};
505
506 use crate::config::GcConfig;
507 use crate::gc_header::GC_INITIAL_LIVES;
508 use crate::{GcBox, GcBoxHeader, GcPtr, MarkVisitor, Trace};
509
510 type RootTracer = Box<dyn Fn(&mut MarkVisitor)>;
511
512 pub struct GcHeap {
513 inner: Mutex<GcHeapInner>,
514 config: Mutex<Option<Arc<GcConfig>>>,
515 memory_in_use: AtomicUsize,
516 total_allocated_bytes: AtomicUsize,
517 root_tracers: Mutex<Vec<RootTracer>>,
518 gc_suppressed: std::sync::atomic::AtomicBool,
519 suppressed_threshold: AtomicUsize,
526 zero_yield_headroom: AtomicUsize,
528 }
529
530 struct GcHeapInner {
531 head: *mut GcBoxHeader,
532 count: usize,
533 total_allocated: usize,
534 total_freed: usize,
535 }
536
537 unsafe impl Send for GcHeapInner {}
538
539 impl GcHeapInner {
540 const fn new() -> Self {
541 Self {
542 head: std::ptr::null_mut(),
543 count: 0,
544 total_allocated: 0,
545 total_freed: 0,
546 }
547 }
548 }
549
550 unsafe impl Sync for GcHeap {}
551
552 impl Default for GcHeap {
553 fn default() -> Self {
554 Self::new()
555 }
556 }
557
558 pub(crate) fn parse_limit_mb(var: &str, value: Option<&str>, default: usize) -> usize {
563 match value {
564 Some(s) => match s.trim().parse::<usize>() {
565 Ok(mb) => mb.saturating_mul(1024 * 1024),
566 Err(_) => {
567 eprintln!(
568 "[gc] warning: ignoring invalid {var}={s:?} (expected a number of megabytes)"
569 );
570 default
571 }
572 },
573 None => default,
574 }
575 }
576
577 impl GcHeap {
578 pub const fn new() -> Self {
579 Self {
580 inner: Mutex::new(GcHeapInner::new()),
581 config: Mutex::new(None),
582 memory_in_use: AtomicUsize::new(0),
583 total_allocated_bytes: AtomicUsize::new(0),
584 root_tracers: Mutex::new(Vec::new()),
585 gc_suppressed: std::sync::atomic::AtomicBool::new(false),
586 suppressed_threshold: AtomicUsize::new(0),
587 zero_yield_headroom: AtomicUsize::new(0),
588 }
589 }
590
591 pub fn set_config(&self, config: Arc<GcConfig>) {
592 *self.config.lock().unwrap() = Some(config);
593 }
594
595 pub fn set_config_from_env(&self) {
596 #[cfg(not(target_arch = "wasm32"))]
597 let default_soft_limit: usize = (system_memory::total() / 3) as usize;
598 #[cfg(target_arch = "wasm32")]
599 let default_soft_limit: usize = 64 * 1024 * 1024;
600
601 let soft_limit_mb = parse_limit_mb(
602 "CLJRS_GC_SOFT_LIMIT_MB",
603 std::env::var("CLJRS_GC_SOFT_LIMIT_MB").ok().as_deref(),
604 default_soft_limit,
605 );
606 let hard_limit_mb = parse_limit_mb(
607 "CLJRS_GC_HARD_LIMIT_MB",
608 std::env::var("CLJRS_GC_HARD_LIMIT_MB").ok().as_deref(),
609 soft_limit_mb,
610 );
611 self.set_config(Arc::new(GcConfig::with_limits(
612 soft_limit_mb,
613 hard_limit_mb,
614 )));
615 }
616
617 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
618 self.root_tracers.lock().unwrap().push(Box::new(tracer));
619 }
620
621 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
622 let tracers = self.root_tracers.lock().unwrap();
623 for tracer in tracers.iter() {
624 tracer(visitor);
625 }
626 }
627
628 pub fn memory_in_use(&self) -> usize {
629 self.memory_in_use.load(Ordering::Relaxed)
630 }
631
632 #[cfg(test)]
633 pub fn set_memory_in_use(&self, bytes: usize) {
634 self.memory_in_use.store(bytes, Ordering::Relaxed);
635 }
636
637 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
638 crate::cancellation::safepoint();
639 let heap_extra = value.gc_size_extra();
640 let gc_box = Box::new(GcBox {
641 header: GcBoxHeader::new::<T>(heap_extra),
642 value,
643 });
644 let obj_size = gc_box.header.size; let raw: *mut GcBox<T> = Box::into_raw(gc_box);
646 {
647 let mut inner = self.inner.lock().unwrap();
648 unsafe {
649 (*raw).header.next.set(inner.head);
650 inner.head = raw as *mut GcBoxHeader;
651 }
652 inner.count += 1;
653 inner.total_allocated += 1;
654 }
655 self.total_allocated_bytes
656 .fetch_add(obj_size, Ordering::Relaxed);
657 crate::stats::GC_STATS.record_gc_alloc(obj_size);
658 let current_usage =
659 self.memory_in_use.fetch_add(obj_size, Ordering::Relaxed) + obj_size;
660
661 if let Some(config) = self.config.lock().unwrap().as_ref()
662 && config.soft_limit_exceeded(current_usage)
663 {
664 if self.gc_suppressed.load(Ordering::Relaxed) {
665 let threshold = self.suppressed_threshold.load(Ordering::Relaxed);
669 if current_usage > threshold {
670 self.gc_suppressed.store(false, Ordering::Relaxed);
671 crate::cancellation::request_gc();
672 }
673 } else {
674 crate::cancellation::request_gc();
675 }
676 }
677
678 register_alloc(raw as *mut GcBoxHeader);
679 GcPtr(unsafe { NonNull::new_unchecked(raw) })
680 }
681
682 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
683 let pre_count = self.inner.lock().unwrap().count;
684 let pre_memory = self.memory_in_use.load(Ordering::Relaxed);
685 tracing::debug!(
686 target: "gc",
687 "starting collection: {} objects, ~{} bytes in use",
688 pre_count,
689 pre_memory
690 );
691 let mark_start = std::time::Instant::now();
692 let mut visitor = MarkVisitor::new();
693 trace_roots(&mut visitor);
694 crate::region::trace_active_regions(&mut visitor);
700 crate::region::trace_retired_regions(&mut visitor);
703 tracing::debug!(
704 target: "gc",
705 "starting drain with {} grey objects",
706 visitor.grey.len()
707 );
708 visitor.drain();
709 let mark_elapsed = mark_start.elapsed();
710
711 let sweep_start = std::time::Instant::now();
712 let mut inner = self.inner.lock().unwrap();
713 let mut live: Vec<*mut GcBoxHeader> = Vec::with_capacity(inner.count);
714 let mut dead: Vec<*mut GcBoxHeader> = Vec::new();
715 let mut freed_bytes: usize = 0;
717 let mut current = inner.head;
718 while !current.is_null() {
719 let header = unsafe { &*current };
720 let next = header.next.get();
721 let lives = header.lives.get();
722 let obj_size = header.size;
723 if lives >= GC_INITIAL_LIVES {
724 header.lives.set(GC_INITIAL_LIVES - 1);
726 live.push(current);
727 } else if lives > 0 {
728 header.lives.set(lives - 1);
730 live.push(current);
731 } else {
732 freed_bytes += obj_size;
734 dead.push(current);
735 }
736 current = next;
737 }
738 let freed_count = dead.len();
739 for ptr in dead {
740 let header = unsafe { &*ptr };
741 unsafe { (header.drop_fn)(ptr) };
742 inner.count -= 1;
743 inner.total_freed += 1;
744 }
745 inner.head = std::ptr::null_mut();
746 for ptr in live {
747 let header = unsafe { &*ptr };
748 header.next.set(inner.head);
749 inner.head = ptr;
750 }
751 self.memory_in_use.fetch_sub(freed_bytes, Ordering::Relaxed);
756 let sweep_elapsed = sweep_start.elapsed();
757 crate::stats::GC_STATS.record_gc_pause(
758 mark_elapsed + sweep_elapsed,
759 freed_count as u64,
760 freed_bytes as u64,
761 );
762 let post_memory = self.memory_in_use.load(Ordering::Relaxed);
763 tracing::debug!(
764 target: "gc",
765 "collection complete: freed {} (~{} bytes), {} remaining (~{} bytes), mark={:.2?} sweep={:.2?}",
766 freed_count,
767 freed_bytes,
768 inner.count,
769 post_memory,
770 mark_elapsed,
771 sweep_elapsed
772 );
773 if freed_count == 0 {
774 let soft_limit = self
785 .config
786 .lock()
787 .unwrap()
788 .as_ref()
789 .map(|c| c.soft_limit())
790 .unwrap_or(64 * 1024 * 1024);
791 let base_headroom = (soft_limit / 10).max(1);
792 let max_headroom = (soft_limit / 4).max(base_headroom);
793 let prev_headroom = self.zero_yield_headroom.load(Ordering::Relaxed);
794 let headroom = if prev_headroom == 0 {
795 base_headroom
796 } else {
797 prev_headroom.saturating_mul(2).min(max_headroom)
798 };
799 self.zero_yield_headroom.store(headroom, Ordering::Relaxed);
800 self.suppressed_threshold
801 .store(post_memory + headroom, Ordering::Relaxed);
802 self.gc_suppressed.store(true, Ordering::Relaxed);
803 } else {
804 self.zero_yield_headroom.store(0, Ordering::Relaxed);
806 self.gc_suppressed.store(false, Ordering::Relaxed);
807 }
808 }
809
810 pub fn count(&self) -> usize {
811 self.inner.lock().unwrap().count
812 }
813 pub fn total_allocated(&self) -> usize {
814 self.inner.lock().unwrap().total_allocated
815 }
816 pub fn total_freed(&self) -> usize {
817 self.inner.lock().unwrap().total_freed
818 }
819
820 pub fn collect_auto(&self) -> bool {
821 tracing::debug!(target: "gc", "automatic collection requested");
822 let Some(_stw_guard) = crate::cancellation::begin_stw() else {
823 tracing::debug!(target: "gc", "automatic collection skipped");
824 return false;
825 };
826 self.collect(|visitor| self.trace_registered_roots(visitor));
827 true
828 }
829 }
830
831 thread_local! {
832 static ISOLATE_HEAP: GcHeap = const { GcHeap::new() };
833 }
834
835 pub struct HeapProxy;
841
842 impl HeapProxy {
843 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
844 ISOLATE_HEAP.with(|h| h.alloc(value))
845 }
846
847 pub fn set_config(&self, config: Arc<GcConfig>) {
848 ISOLATE_HEAP.with(|h| h.set_config(config));
849 }
850
851 pub fn set_config_from_env(&self) {
852 ISOLATE_HEAP.with(|h| h.set_config_from_env());
853 }
854
855 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
856 ISOLATE_HEAP.with(|h| h.register_root_tracer(tracer));
857 }
858
859 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
860 ISOLATE_HEAP.with(|h| h.trace_registered_roots(visitor));
861 }
862
863 pub fn memory_in_use(&self) -> usize {
864 ISOLATE_HEAP.with(|h| h.memory_in_use())
865 }
866
867 pub fn count(&self) -> usize {
868 ISOLATE_HEAP.with(|h| h.count())
869 }
870
871 pub fn total_allocated(&self) -> usize {
872 ISOLATE_HEAP.with(|h| h.total_allocated())
873 }
874
875 pub fn total_freed(&self) -> usize {
876 ISOLATE_HEAP.with(|h| h.total_freed())
877 }
878
879 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
880 ISOLATE_HEAP.with(|h| h.collect(trace_roots));
881 }
882
883 pub fn collect_auto(&self) -> bool {
884 ISOLATE_HEAP.with(|h| h.collect_auto())
885 }
886
887 #[cfg(test)]
888 pub fn set_memory_in_use(&self, bytes: usize) {
889 ISOLATE_HEAP.with(|h| h.set_memory_in_use(bytes));
890 }
891 }
892
893 unsafe impl Sync for HeapProxy {}
896
897 pub static HEAP: HeapProxy = HeapProxy;
898
899 thread_local! {
900 pub(crate) static ALLOC_ROOTS: RefCell<Vec<*mut GcBoxHeader>> = const { RefCell::new(Vec::new()) };
901 }
902
903 pub struct AllocRootGuard {
904 saved_len: usize,
905 }
906
907 impl Drop for AllocRootGuard {
908 fn drop(&mut self) {
909 ALLOC_ROOTS.with(|roots| roots.borrow_mut().truncate(self.saved_len));
910 }
911 }
912
913 pub fn push_alloc_frame() -> AllocRootGuard {
914 let saved_len = ALLOC_ROOTS.with(|roots| roots.borrow().len());
915 AllocRootGuard { saved_len }
916 }
917
918 fn register_alloc(header: *mut GcBoxHeader) {
919 ALLOC_ROOTS.with(|roots| roots.borrow_mut().push(header));
920 }
921
922 pub fn trace_thread_alloc_roots(visitor: &mut MarkVisitor) {
923 ALLOC_ROOTS.with(|roots| {
924 let roots = roots.borrow();
925 for &header in roots.iter() {
926 unsafe { visitor.mark_header(header) };
927 }
928 });
929 }
930}
931
932#[cfg(feature = "no-gc")]
937mod nogc_stubs {
938 use crate::MarkVisitor;
939 use std::sync::Arc;
940
941 #[derive(Debug, Clone)]
942 pub struct GcConfig;
943 impl GcConfig {
944 pub fn new() -> Self {
945 Self
946 }
947 pub fn with_hard_limit(_: usize) -> Self {
948 Self
949 }
950 pub fn with_limits(_: usize, _: usize) -> Self {
951 Self
952 }
953 }
954 impl Default for GcConfig {
955 fn default() -> Self {
956 Self::new()
957 }
958 }
959
960 pub struct GcHeap;
961 impl Default for GcHeap {
962 fn default() -> Self {
963 Self::new()
964 }
965 }
966 impl GcHeap {
967 pub const fn new() -> Self {
968 Self
969 }
970 pub fn set_config(&self, _: Arc<GcConfig>) {}
971 pub fn set_config_from_env(&self) {}
976 pub fn register_root_tracer(&self, _: impl Fn(&mut MarkVisitor) + 'static) {}
977 pub fn trace_registered_roots(&self, _: &mut MarkVisitor) {}
978 pub fn memory_in_use(&self) -> usize {
979 0
980 }
981 pub fn count(&self) -> usize {
982 0
983 }
984 pub fn total_allocated(&self) -> usize {
985 0
986 }
987 pub fn total_freed(&self) -> usize {
988 0
989 }
990 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, _: F) {}
991 pub fn collect_auto(&self) -> bool {
992 false
993 }
994 }
995 unsafe impl Sync for GcHeap {}
996 pub static HEAP: GcHeap = GcHeap::new();
997
998 pub struct MutatorGuard;
999 impl Drop for MutatorGuard {
1000 fn drop(&mut self) {}
1001 }
1002 pub struct StwGuard;
1003 impl Drop for StwGuard {
1004 fn drop(&mut self) {}
1005 }
1006 pub struct GcParked;
1007
1008 pub struct GcCancellationStub;
1009 impl GcCancellationStub {
1010 pub const fn new() -> Self {
1011 Self
1012 }
1013 pub fn in_progress(&self) -> bool {
1014 false
1015 }
1016 }
1017 pub static CONFIG_CANCELLATION: GcCancellationStub = GcCancellationStub::new();
1018
1019 pub fn safepoint() {}
1020 pub fn gc_requested() -> bool {
1021 false
1022 }
1023 pub fn take_gc_request() -> bool {
1024 false
1025 }
1026 pub fn begin_stw() -> Option<StwGuard> {
1027 None
1028 }
1029 pub fn register_mutator() -> MutatorGuard {
1030 MutatorGuard
1031 }
1032 pub fn registered_threads() -> usize {
1033 0
1034 }
1035 pub fn request_gc() {}
1036 pub fn check_cancellation() -> Result<(), GcParked> {
1037 Ok(())
1038 }
1039 pub fn park_thread() {}
1040 pub fn unpark_thread() {}
1041 pub fn wait_for_threads_to_park() {}
1042
1043 pub struct AllocRootGuard;
1044 impl Drop for AllocRootGuard {
1045 fn drop(&mut self) {}
1046 }
1047 pub fn push_alloc_frame() -> AllocRootGuard {
1048 AllocRootGuard
1049 }
1050}
1051
1052#[cfg(all(test, not(feature = "no-gc")))]
1057mod tests {
1058 use super::*;
1059 use std::sync::{Arc, Mutex};
1060
1061 #[derive(Debug)]
1062 #[allow(dead_code)]
1063 struct Tracked {
1064 value: i32,
1065 dropped: Arc<Mutex<bool>>,
1066 }
1067 impl Drop for Tracked {
1068 fn drop(&mut self) {
1069 *self.dropped.lock().unwrap() = true;
1070 }
1071 }
1072 impl Trace for Tracked {
1073 fn trace(&self, _: &mut MarkVisitor) {}
1074 }
1075
1076 #[derive(Debug)]
1077 #[allow(dead_code)]
1078 struct Parent {
1079 child: GcPtr<Tracked>,
1080 }
1081 impl Trace for Parent {
1082 fn trace(&self, visitor: &mut MarkVisitor) {
1083 visitor.visit(&self.child);
1084 }
1085 }
1086
1087 fn fresh_heap() -> gc_full::GcHeap {
1088 let heap = gc_full::GcHeap::new();
1089 heap.set_config(Arc::new(GcConfig::with_limits(10000, 50000)));
1090 heap
1091 }
1092
1093 #[test]
1094 fn alloc_and_get() {
1095 let heap = fresh_heap();
1096 let p = heap.alloc(42i64);
1097 assert_eq!(*p.get(), 42);
1098 assert_eq!(heap.count(), 1);
1099 }
1100
1101 #[test]
1102 fn clone_is_same_ptr() {
1103 let heap = fresh_heap();
1104 let p = heap.alloc(99i64);
1105 let q = p.clone();
1106 assert!(GcPtr::ptr_eq(&p, &q));
1107 }
1108
1109 #[test]
1110 fn parse_limit_mb_handles_valid_unset_and_malformed() {
1111 assert_eq!(gc_full::parse_limit_mb("X", Some("4"), 7), 4 * 1024 * 1024);
1113 assert_eq!(
1115 gc_full::parse_limit_mb("X", Some(" 4 "), 7),
1116 4 * 1024 * 1024
1117 );
1118 assert_eq!(gc_full::parse_limit_mb("X", None, 7), 7);
1120 assert_eq!(gc_full::parse_limit_mb("X", Some("foo"), 7), 7);
1122 assert_eq!(gc_full::parse_limit_mb("X", Some(""), 7), 7);
1123 assert_eq!(gc_full::parse_limit_mb("X", Some("-1"), 7), 7);
1124 assert_eq!(
1126 gc_full::parse_limit_mb("X", Some(&usize::MAX.to_string()), 7),
1127 usize::MAX
1128 );
1129 }
1130
1131 #[test]
1132 fn collect_keeps_reachable() {
1133 let heap = fresh_heap();
1134 let dropped = Arc::new(Mutex::new(false));
1135 let p = heap.alloc(Tracked {
1136 value: 2,
1137 dropped: dropped.clone(),
1138 });
1139 heap.collect(|vis| vis.visit(&p));
1140 assert_eq!(heap.count(), 1);
1141 assert!(!*dropped.lock().unwrap());
1142 }
1143
1144 #[test]
1145 fn b1_two_isolates_independent_heaps() {
1146 use std::sync::{Arc, Barrier};
1147 let barrier = Arc::new(Barrier::new(2));
1150 let b1 = barrier.clone();
1151 let h1 = std::thread::Builder::new()
1152 .name("isolate-1".into())
1153 .spawn(move || {
1154 let _mutator = crate::register_mutator();
1155 let _ptrs: Vec<_> = (0_i64..100)
1157 .map(|i| crate::gc_full::HEAP.alloc(i))
1158 .collect();
1159 b1.wait(); assert_eq!(
1162 crate::gc_full::HEAP.count(),
1163 100,
1164 "isolate-1 heap count should be 100"
1165 );
1166 })
1167 .unwrap();
1168
1169 let b2 = barrier.clone();
1170 let h2 = std::thread::Builder::new()
1171 .name("isolate-2".into())
1172 .spawn(move || {
1173 let _mutator = crate::register_mutator();
1174 let _ptrs: Vec<_> = (0_i64..200)
1176 .map(|i| crate::gc_full::HEAP.alloc(i))
1177 .collect();
1178 b2.wait();
1179 assert_eq!(
1181 crate::gc_full::HEAP.count(),
1182 200,
1183 "isolate-2 heap count should be 200"
1184 );
1185 })
1186 .unwrap();
1187
1188 h1.join().expect("isolate-1 panicked");
1189 h2.join().expect("isolate-2 panicked");
1190 }
1191
1192 #[test]
1193 fn b1_two_isolates_gc_independently() {
1194 let h1 = std::thread::Builder::new()
1196 .name("gc-isolate-1".into())
1197 .spawn(|| {
1198 let _mutator = crate::register_mutator();
1199 let heap = &crate::gc_full::HEAP;
1200 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1201 for _ in 0..5 {
1203 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1204 heap.collect(|_| {});
1206 heap.collect(|_| {}); }
1208 })
1212 .unwrap();
1213
1214 let h2 = std::thread::Builder::new()
1215 .name("gc-isolate-2".into())
1216 .spawn(|| {
1217 let _mutator = crate::register_mutator();
1218 let heap = &crate::gc_full::HEAP;
1219 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1220 for _ in 0..5 {
1221 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1222 heap.collect(|_| {});
1223 heap.collect(|_| {});
1224 }
1225 })
1226 .unwrap();
1227
1228 h1.join().expect("gc-isolate-1 panicked");
1229 h2.join().expect("gc-isolate-2 panicked");
1230 }
1231}
1232
1233#[cfg(all(test, feature = "no-gc"))]
1234mod nogc_tests {
1235 use super::*;
1236 use alloc_ctx::{ScratchGuard, StaticCtxGuard};
1237
1238 #[test]
1239 fn alloc_in_static_context() {
1240 let _g = StaticCtxGuard::new();
1241 let p = GcPtr::new(42i64);
1242 assert_eq!(*p.get(), 42);
1243 }
1244
1245 #[test]
1246 fn alloc_in_scratch_region() {
1247 let mut scratch = ScratchGuard::new();
1248 let p = GcPtr::new(99i64);
1249 assert_eq!(*p.get(), 99);
1250 scratch.pop_for_return();
1251 assert_eq!(*p.get(), 99);
1252 }
1254
1255 #[test]
1256 fn ptr_eq() {
1257 let _g = StaticCtxGuard::new();
1258 let p = GcPtr::new(1i64);
1259 let q = p.clone();
1260 assert!(GcPtr::ptr_eq(&p, &q));
1261 }
1262}