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
376#[cfg(not(feature = "no-gc"))]
381mod gc_full {
382 use std::cell::RefCell;
383 use std::ptr::NonNull;
384 use std::sync::atomic::{AtomicUsize, Ordering};
385 use std::sync::{Arc, Mutex};
386
387 use crate::config::GcConfig;
388 use crate::gc_header::GC_INITIAL_LIVES;
389 use crate::{GcBox, GcBoxHeader, GcPtr, MarkVisitor, Trace};
390
391 type RootTracer = Box<dyn Fn(&mut MarkVisitor)>;
392
393 pub struct GcHeap {
394 inner: Mutex<GcHeapInner>,
395 config: Mutex<Option<Arc<GcConfig>>>,
396 memory_in_use: AtomicUsize,
397 total_allocated_bytes: AtomicUsize,
398 root_tracers: Mutex<Vec<RootTracer>>,
399 gc_suppressed: std::sync::atomic::AtomicBool,
400 suppressed_threshold: AtomicUsize,
407 zero_yield_headroom: AtomicUsize,
409 }
410
411 struct GcHeapInner {
412 head: *mut GcBoxHeader,
413 count: usize,
414 total_allocated: usize,
415 total_freed: usize,
416 }
417
418 unsafe impl Send for GcHeapInner {}
419
420 impl GcHeapInner {
421 const fn new() -> Self {
422 Self {
423 head: std::ptr::null_mut(),
424 count: 0,
425 total_allocated: 0,
426 total_freed: 0,
427 }
428 }
429 }
430
431 unsafe impl Sync for GcHeap {}
432
433 impl Default for GcHeap {
434 fn default() -> Self {
435 Self::new()
436 }
437 }
438
439 impl GcHeap {
440 pub const fn new() -> Self {
441 Self {
442 inner: Mutex::new(GcHeapInner::new()),
443 config: Mutex::new(None),
444 memory_in_use: AtomicUsize::new(0),
445 total_allocated_bytes: AtomicUsize::new(0),
446 root_tracers: Mutex::new(Vec::new()),
447 gc_suppressed: std::sync::atomic::AtomicBool::new(false),
448 suppressed_threshold: AtomicUsize::new(0),
449 zero_yield_headroom: AtomicUsize::new(0),
450 }
451 }
452
453 pub fn set_config(&self, config: Arc<GcConfig>) {
454 *self.config.lock().unwrap() = Some(config);
455 }
456
457 pub fn set_config_from_env(&self) {
458 #[cfg(not(target_arch = "wasm32"))]
459 let default_soft_limit: usize = (system_memory::total() / 3) as usize;
460 #[cfg(target_arch = "wasm32")]
461 let default_soft_limit: usize = 64 * 1024 * 1024;
462
463 let soft_limit_mb: usize = match std::env::var("CLJRS_GC_SOFT_LIMIT_MB").ok() {
464 Some(s) => s.parse::<usize>().unwrap() * (1024 * 1024),
465 None => default_soft_limit,
466 };
467 let hard_limit_mb: usize = match std::env::var("CLJRS_GC_HARD_LIMIT_MB").ok() {
468 Some(s) => s.parse::<usize>().unwrap() * (1024 * 1024),
469 None => soft_limit_mb,
470 };
471 self.set_config(Arc::new(GcConfig::with_limits(
472 soft_limit_mb,
473 hard_limit_mb,
474 )));
475 }
476
477 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
478 self.root_tracers.lock().unwrap().push(Box::new(tracer));
479 }
480
481 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
482 let tracers = self.root_tracers.lock().unwrap();
483 for tracer in tracers.iter() {
484 tracer(visitor);
485 }
486 }
487
488 pub fn memory_in_use(&self) -> usize {
489 self.memory_in_use.load(Ordering::Relaxed)
490 }
491
492 #[cfg(test)]
493 pub fn set_memory_in_use(&self, bytes: usize) {
494 self.memory_in_use.store(bytes, Ordering::Relaxed);
495 }
496
497 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
498 crate::cancellation::safepoint();
499 let heap_extra = value.gc_size_extra();
500 let gc_box = Box::new(GcBox {
501 header: GcBoxHeader::new::<T>(heap_extra),
502 value,
503 });
504 let obj_size = gc_box.header.size; let raw: *mut GcBox<T> = Box::into_raw(gc_box);
506 {
507 let mut inner = self.inner.lock().unwrap();
508 unsafe {
509 (*raw).header.next.set(inner.head);
510 inner.head = raw as *mut GcBoxHeader;
511 }
512 inner.count += 1;
513 inner.total_allocated += 1;
514 }
515 self.total_allocated_bytes
516 .fetch_add(obj_size, Ordering::Relaxed);
517 crate::stats::GC_STATS.record_gc_alloc(obj_size);
518 let current_usage =
519 self.memory_in_use.fetch_add(obj_size, Ordering::Relaxed) + obj_size;
520
521 if let Some(config) = self.config.lock().unwrap().as_ref()
522 && config.soft_limit_exceeded(current_usage)
523 {
524 if self.gc_suppressed.load(Ordering::Relaxed) {
525 let threshold = self.suppressed_threshold.load(Ordering::Relaxed);
529 if current_usage > threshold {
530 self.gc_suppressed.store(false, Ordering::Relaxed);
531 crate::cancellation::request_gc();
532 }
533 } else {
534 crate::cancellation::request_gc();
535 }
536 }
537
538 register_alloc(raw as *mut GcBoxHeader);
539 GcPtr(unsafe { NonNull::new_unchecked(raw) })
540 }
541
542 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
543 let pre_count = self.inner.lock().unwrap().count;
544 let pre_memory = self.memory_in_use.load(Ordering::Relaxed);
545 cljrs_logging::feat_debug!(
546 "gc",
547 "starting collection: {} objects, ~{} bytes in use",
548 pre_count,
549 pre_memory
550 );
551 let mark_start = std::time::Instant::now();
552 let mut visitor = MarkVisitor::new();
553 trace_roots(&mut visitor);
554 cljrs_logging::feat_debug!(
555 "gc",
556 "starting drain with {} grey objects",
557 visitor.grey.len()
558 );
559 visitor.drain();
560 let mark_elapsed = mark_start.elapsed();
561
562 let sweep_start = std::time::Instant::now();
563 let mut inner = self.inner.lock().unwrap();
564 let mut live: Vec<*mut GcBoxHeader> = Vec::with_capacity(inner.count);
565 let mut dead: Vec<*mut GcBoxHeader> = Vec::new();
566 let mut freed_bytes: usize = 0;
568 let mut current = inner.head;
569 while !current.is_null() {
570 let header = unsafe { &*current };
571 let next = header.next.get();
572 let lives = header.lives.get();
573 let obj_size = header.size;
574 if lives >= GC_INITIAL_LIVES {
575 header.lives.set(GC_INITIAL_LIVES - 1);
577 live.push(current);
578 } else if lives > 0 {
579 header.lives.set(lives - 1);
581 live.push(current);
582 } else {
583 freed_bytes += obj_size;
585 dead.push(current);
586 }
587 current = next;
588 }
589 let freed_count = dead.len();
590 for ptr in dead {
591 let header = unsafe { &*ptr };
592 unsafe { (header.drop_fn)(ptr) };
593 inner.count -= 1;
594 inner.total_freed += 1;
595 }
596 inner.head = std::ptr::null_mut();
597 for ptr in live {
598 let header = unsafe { &*ptr };
599 header.next.set(inner.head);
600 inner.head = ptr;
601 }
602 self.memory_in_use.fetch_sub(freed_bytes, Ordering::Relaxed);
607 let sweep_elapsed = sweep_start.elapsed();
608 crate::stats::GC_STATS.record_gc_pause(
609 mark_elapsed + sweep_elapsed,
610 freed_count as u64,
611 freed_bytes as u64,
612 );
613 let post_memory = self.memory_in_use.load(Ordering::Relaxed);
614 cljrs_logging::feat_debug!(
615 "gc",
616 "collection complete: freed {} (~{} bytes), {} remaining (~{} bytes), mark={:.2?} sweep={:.2?}",
617 freed_count,
618 freed_bytes,
619 inner.count,
620 post_memory,
621 mark_elapsed,
622 sweep_elapsed
623 );
624 if freed_count == 0 {
625 let soft_limit = self
636 .config
637 .lock()
638 .unwrap()
639 .as_ref()
640 .map(|c| c.soft_limit())
641 .unwrap_or(64 * 1024 * 1024);
642 let base_headroom = (soft_limit / 10).max(1);
643 let max_headroom = (soft_limit / 4).max(base_headroom);
644 let prev_headroom = self.zero_yield_headroom.load(Ordering::Relaxed);
645 let headroom = if prev_headroom == 0 {
646 base_headroom
647 } else {
648 prev_headroom.saturating_mul(2).min(max_headroom)
649 };
650 self.zero_yield_headroom.store(headroom, Ordering::Relaxed);
651 self.suppressed_threshold
652 .store(post_memory + headroom, Ordering::Relaxed);
653 self.gc_suppressed.store(true, Ordering::Relaxed);
654 } else {
655 self.zero_yield_headroom.store(0, Ordering::Relaxed);
657 self.gc_suppressed.store(false, Ordering::Relaxed);
658 }
659 }
660
661 pub fn count(&self) -> usize {
662 self.inner.lock().unwrap().count
663 }
664 pub fn total_allocated(&self) -> usize {
665 self.inner.lock().unwrap().total_allocated
666 }
667 pub fn total_freed(&self) -> usize {
668 self.inner.lock().unwrap().total_freed
669 }
670
671 pub fn collect_auto(&self) -> bool {
672 cljrs_logging::feat_debug!("gc", "automatic collection requested");
673 let Some(_stw_guard) = crate::cancellation::begin_stw() else {
674 cljrs_logging::feat_debug!("gc", "automatic collection skipped");
675 return false;
676 };
677 self.collect(|visitor| self.trace_registered_roots(visitor));
678 true
679 }
680 }
681
682 thread_local! {
683 static ISOLATE_HEAP: GcHeap = const { GcHeap::new() };
684 }
685
686 pub struct HeapProxy;
692
693 impl HeapProxy {
694 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
695 ISOLATE_HEAP.with(|h| h.alloc(value))
696 }
697
698 pub fn set_config(&self, config: Arc<GcConfig>) {
699 ISOLATE_HEAP.with(|h| h.set_config(config));
700 }
701
702 pub fn set_config_from_env(&self) {
703 ISOLATE_HEAP.with(|h| h.set_config_from_env());
704 }
705
706 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
707 ISOLATE_HEAP.with(|h| h.register_root_tracer(tracer));
708 }
709
710 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
711 ISOLATE_HEAP.with(|h| h.trace_registered_roots(visitor));
712 }
713
714 pub fn memory_in_use(&self) -> usize {
715 ISOLATE_HEAP.with(|h| h.memory_in_use())
716 }
717
718 pub fn count(&self) -> usize {
719 ISOLATE_HEAP.with(|h| h.count())
720 }
721
722 pub fn total_allocated(&self) -> usize {
723 ISOLATE_HEAP.with(|h| h.total_allocated())
724 }
725
726 pub fn total_freed(&self) -> usize {
727 ISOLATE_HEAP.with(|h| h.total_freed())
728 }
729
730 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
731 ISOLATE_HEAP.with(|h| h.collect(trace_roots));
732 }
733
734 pub fn collect_auto(&self) -> bool {
735 ISOLATE_HEAP.with(|h| h.collect_auto())
736 }
737
738 #[cfg(test)]
739 pub fn set_memory_in_use(&self, bytes: usize) {
740 ISOLATE_HEAP.with(|h| h.set_memory_in_use(bytes));
741 }
742 }
743
744 unsafe impl Sync for HeapProxy {}
747
748 pub static HEAP: HeapProxy = HeapProxy;
749
750 thread_local! {
751 pub(crate) static ALLOC_ROOTS: RefCell<Vec<*mut GcBoxHeader>> = const { RefCell::new(Vec::new()) };
752 }
753
754 pub struct AllocRootGuard {
755 saved_len: usize,
756 }
757
758 impl Drop for AllocRootGuard {
759 fn drop(&mut self) {
760 ALLOC_ROOTS.with(|roots| roots.borrow_mut().truncate(self.saved_len));
761 }
762 }
763
764 pub fn push_alloc_frame() -> AllocRootGuard {
765 let saved_len = ALLOC_ROOTS.with(|roots| roots.borrow().len());
766 AllocRootGuard { saved_len }
767 }
768
769 fn register_alloc(header: *mut GcBoxHeader) {
770 ALLOC_ROOTS.with(|roots| roots.borrow_mut().push(header));
771 }
772
773 pub fn trace_thread_alloc_roots(visitor: &mut MarkVisitor) {
774 ALLOC_ROOTS.with(|roots| {
775 let roots = roots.borrow();
776 for &header in roots.iter() {
777 unsafe { visitor.mark_header(header) };
778 }
779 });
780 }
781}
782
783#[cfg(feature = "no-gc")]
788mod nogc_stubs {
789 use crate::MarkVisitor;
790 use std::sync::Arc;
791
792 #[derive(Debug, Clone)]
793 pub struct GcConfig;
794 impl GcConfig {
795 pub fn new() -> Self {
796 Self
797 }
798 pub fn with_hard_limit(_: usize) -> Self {
799 Self
800 }
801 pub fn with_limits(_: usize, _: usize) -> Self {
802 Self
803 }
804 }
805 impl Default for GcConfig {
806 fn default() -> Self {
807 Self::new()
808 }
809 }
810
811 pub struct GcHeap;
812 impl Default for GcHeap {
813 fn default() -> Self {
814 Self::new()
815 }
816 }
817 impl GcHeap {
818 pub const fn new() -> Self {
819 Self
820 }
821 pub fn set_config(&self, _: Arc<GcConfig>) {}
822 pub fn register_root_tracer(&self, _: impl Fn(&mut MarkVisitor) + 'static) {}
823 pub fn trace_registered_roots(&self, _: &mut MarkVisitor) {}
824 pub fn memory_in_use(&self) -> usize {
825 0
826 }
827 pub fn count(&self) -> usize {
828 0
829 }
830 pub fn total_allocated(&self) -> usize {
831 0
832 }
833 pub fn total_freed(&self) -> usize {
834 0
835 }
836 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, _: F) {}
837 pub fn collect_auto(&self) -> bool {
838 false
839 }
840 }
841 unsafe impl Sync for GcHeap {}
842 pub static HEAP: GcHeap = GcHeap::new();
843
844 pub struct MutatorGuard;
845 impl Drop for MutatorGuard {
846 fn drop(&mut self) {}
847 }
848 pub struct StwGuard;
849 impl Drop for StwGuard {
850 fn drop(&mut self) {}
851 }
852 pub struct GcParked;
853 pub struct CancellableGuard;
854
855 pub struct GcCancellationStub;
856 impl GcCancellationStub {
857 pub const fn new() -> Self {
858 Self
859 }
860 pub fn in_progress(&self) -> bool {
861 false
862 }
863 }
864 pub static CONFIG_CANCELLATION: GcCancellationStub = GcCancellationStub::new();
865
866 pub fn safepoint() {}
867 pub fn gc_requested() -> bool {
868 false
869 }
870 pub fn take_gc_request() -> bool {
871 false
872 }
873 pub fn begin_stw() -> Option<StwGuard> {
874 None
875 }
876 pub fn register_mutator() -> MutatorGuard {
877 MutatorGuard
878 }
879 pub fn registered_threads() -> usize {
880 0
881 }
882 pub fn request_gc() {}
883 pub fn check_cancellation() -> Result<(), GcParked> {
884 Ok(())
885 }
886 pub fn park_thread() {}
887 pub fn unpark_thread() {}
888 pub fn wait_for_threads_to_park() {}
889
890 pub struct AllocRootGuard;
891 impl Drop for AllocRootGuard {
892 fn drop(&mut self) {}
893 }
894 pub fn push_alloc_frame() -> AllocRootGuard {
895 AllocRootGuard
896 }
897}
898
899#[cfg(all(test, not(feature = "no-gc")))]
904mod tests {
905 use super::*;
906 use std::sync::{Arc, Mutex};
907
908 #[derive(Debug)]
909 #[allow(dead_code)]
910 struct Tracked {
911 value: i32,
912 dropped: Arc<Mutex<bool>>,
913 }
914 impl Drop for Tracked {
915 fn drop(&mut self) {
916 *self.dropped.lock().unwrap() = true;
917 }
918 }
919 impl Trace for Tracked {
920 fn trace(&self, _: &mut MarkVisitor) {}
921 }
922
923 #[derive(Debug)]
924 #[allow(dead_code)]
925 struct Parent {
926 child: GcPtr<Tracked>,
927 }
928 impl Trace for Parent {
929 fn trace(&self, visitor: &mut MarkVisitor) {
930 visitor.visit(&self.child);
931 }
932 }
933
934 fn fresh_heap() -> gc_full::GcHeap {
935 let heap = gc_full::GcHeap::new();
936 heap.set_config(Arc::new(GcConfig::with_limits(10000, 50000)));
937 heap
938 }
939
940 #[test]
941 fn alloc_and_get() {
942 let heap = fresh_heap();
943 let p = heap.alloc(42i64);
944 assert_eq!(*p.get(), 42);
945 assert_eq!(heap.count(), 1);
946 }
947
948 #[test]
949 fn clone_is_same_ptr() {
950 let heap = fresh_heap();
951 let p = heap.alloc(99i64);
952 let q = p.clone();
953 assert!(GcPtr::ptr_eq(&p, &q));
954 }
955
956 #[test]
957 fn collect_keeps_reachable() {
958 let heap = fresh_heap();
959 let dropped = Arc::new(Mutex::new(false));
960 let p = heap.alloc(Tracked {
961 value: 2,
962 dropped: dropped.clone(),
963 });
964 heap.collect(|vis| vis.visit(&p));
965 assert_eq!(heap.count(), 1);
966 assert!(!*dropped.lock().unwrap());
967 }
968
969 #[test]
970 fn b1_two_isolates_independent_heaps() {
971 use std::sync::{Arc, Barrier};
972 let barrier = Arc::new(Barrier::new(2));
975 let b1 = barrier.clone();
976 let h1 = std::thread::Builder::new()
977 .name("isolate-1".into())
978 .spawn(move || {
979 let _mutator = crate::register_mutator();
980 let _ptrs: Vec<_> = (0_i64..100)
982 .map(|i| crate::gc_full::HEAP.alloc(i))
983 .collect();
984 b1.wait(); assert_eq!(
987 crate::gc_full::HEAP.count(),
988 100,
989 "isolate-1 heap count should be 100"
990 );
991 })
992 .unwrap();
993
994 let b2 = barrier.clone();
995 let h2 = std::thread::Builder::new()
996 .name("isolate-2".into())
997 .spawn(move || {
998 let _mutator = crate::register_mutator();
999 let _ptrs: Vec<_> = (0_i64..200)
1001 .map(|i| crate::gc_full::HEAP.alloc(i))
1002 .collect();
1003 b2.wait();
1004 assert_eq!(
1006 crate::gc_full::HEAP.count(),
1007 200,
1008 "isolate-2 heap count should be 200"
1009 );
1010 })
1011 .unwrap();
1012
1013 h1.join().expect("isolate-1 panicked");
1014 h2.join().expect("isolate-2 panicked");
1015 }
1016
1017 #[test]
1018 fn b1_two_isolates_gc_independently() {
1019 let h1 = std::thread::Builder::new()
1021 .name("gc-isolate-1".into())
1022 .spawn(|| {
1023 let _mutator = crate::register_mutator();
1024 let heap = &crate::gc_full::HEAP;
1025 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1026 for _ in 0..5 {
1028 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1029 heap.collect(|_| {});
1031 heap.collect(|_| {}); }
1033 })
1037 .unwrap();
1038
1039 let h2 = std::thread::Builder::new()
1040 .name("gc-isolate-2".into())
1041 .spawn(|| {
1042 let _mutator = crate::register_mutator();
1043 let heap = &crate::gc_full::HEAP;
1044 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1045 for _ in 0..5 {
1046 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1047 heap.collect(|_| {});
1048 heap.collect(|_| {});
1049 }
1050 })
1051 .unwrap();
1052
1053 h1.join().expect("gc-isolate-1 panicked");
1054 h2.join().expect("gc-isolate-2 panicked");
1055 }
1056}
1057
1058#[cfg(all(test, feature = "no-gc"))]
1059mod nogc_tests {
1060 use super::*;
1061 use alloc_ctx::{ScratchGuard, StaticCtxGuard};
1062
1063 #[test]
1064 fn alloc_in_static_context() {
1065 let _g = StaticCtxGuard::new();
1066 let p = GcPtr::new(42i64);
1067 assert_eq!(*p.get(), 42);
1068 }
1069
1070 #[test]
1071 fn alloc_in_scratch_region() {
1072 let mut scratch = ScratchGuard::new();
1073 let p = GcPtr::new(99i64);
1074 assert_eq!(*p.get(), 99);
1075 scratch.pop_for_return();
1076 assert_eq!(*p.get(), 99);
1077 }
1079
1080 #[test]
1081 fn ptr_eq() {
1082 let _g = StaticCtxGuard::new();
1083 let p = GcPtr::new(1i64);
1084 let q = p.clone();
1085 assert!(GcPtr::ptr_eq(&p, &q));
1086 }
1087}