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::{AllocRootGuard, GcHeap, HEAP, push_alloc_frame, trace_thread_alloc_roots};
36#[cfg(feature = "no-gc")]
37pub use nogc_stubs::{
38 AllocRootGuard, CONFIG_CANCELLATION, CancellableGuard, GcConfig, GcHeap, GcParked, HEAP,
39 MutatorGuard, StwGuard, begin_stw, check_cancellation, gc_requested, park_thread,
40 push_alloc_frame, register_mutator, registered_threads, request_gc, safepoint, take_gc_request,
41 unpark_thread, wait_for_threads_to_park,
42};
43
44#[cfg(all(feature = "no-gc", debug_assertions))]
49pub fn is_static_addr(addr: usize) -> bool {
50 static_arena::is_static_addr(addr)
51}
52
53pub trait Trace: Send + Sync {
67 fn trace(&self, visitor: &mut MarkVisitor);
68
69 fn gc_size_extra(&self) -> usize {
70 0
71 }
72}
73
74impl Trace for String {
77 fn trace(&self, _: &mut MarkVisitor) {}
78
79 fn gc_size_extra(&self) -> usize {
80 self.capacity()
81 }
82}
83impl Trace for i64 {
84 fn trace(&self, _: &mut MarkVisitor) {}
85}
86impl Trace for f64 {
87 fn trace(&self, _: &mut MarkVisitor) {}
88}
89impl Trace for bool {
90 fn trace(&self, _: &mut MarkVisitor) {}
91}
92impl Trace for num_bigint::BigInt {
93 fn trace(&self, _: &mut MarkVisitor) {}
94}
95impl Trace for bigdecimal::BigDecimal {
96 fn trace(&self, _: &mut MarkVisitor) {}
97}
98impl Trace for num_rational::Ratio<num_bigint::BigInt> {
99 fn trace(&self, _: &mut MarkVisitor) {}
100}
101impl Trace for regex::Regex {
102 fn trace(&self, _: &mut MarkVisitor) {}
103}
104macro_rules! impl_trace_prim_array {
105 ($t:ty) => {
106 impl Trace for std::sync::Mutex<Vec<$t>> {
107 fn trace(&self, _: &mut MarkVisitor) {}
108 fn gc_size_extra(&self) -> usize {
109 self.lock().unwrap().capacity() * std::mem::size_of::<$t>()
110 }
111 }
112 };
113}
114impl_trace_prim_array!(i32);
115impl_trace_prim_array!(i64);
116impl_trace_prim_array!(i16);
117impl_trace_prim_array!(i8);
118impl_trace_prim_array!(char);
119impl_trace_prim_array!(f64);
120impl_trace_prim_array!(f32);
121impl_trace_prim_array!(bool);
122
123pub trait GcVisitor {
126 fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>);
127}
128
129#[cfg(not(feature = "no-gc"))]
134pub use self::gc_header::{GcBox, GcBoxHeader};
135
136#[cfg(not(feature = "no-gc"))]
137mod gc_header {
138 use crate::{MarkVisitor, Trace};
139 use std::cell::Cell;
140
141 pub(crate) const GC_INITIAL_LIVES: u8 = 2;
149
150 #[cfg(debug_assertions)]
151 pub(crate) const GC_MAGIC_ALIVE: u64 = 0xCAFE_BABE_DEAD_BEEF;
152 #[cfg(debug_assertions)]
153 pub(crate) const GC_MAGIC_FREED: u64 = 0xDEAD_DEAD_DEAD_DEAD;
154
155 #[repr(C)]
156 pub struct GcBoxHeader {
157 #[cfg(debug_assertions)]
158 pub(crate) magic: Cell<u64>,
159 pub(crate) size: usize,
161 pub(crate) lives: Cell<u8>,
162 pub(crate) next: Cell<*mut GcBoxHeader>,
163 pub(crate) trace_fn: unsafe fn(*const GcBoxHeader, &mut MarkVisitor),
164 pub(crate) drop_fn: unsafe fn(*mut GcBoxHeader),
165 }
166
167 impl GcBoxHeader {
168 pub(crate) fn new<T: Trace + 'static>(heap_extra: usize) -> Self {
169 Self {
170 #[cfg(debug_assertions)]
171 magic: Cell::new(GC_MAGIC_ALIVE),
172 size: std::mem::size_of::<GcBox<T>>() + heap_extra,
173 lives: Cell::new(GC_INITIAL_LIVES - 1),
174 next: Cell::new(std::ptr::null_mut()),
175 trace_fn: trace_gc_box::<T>,
176 drop_fn: drop_gc_box::<T>,
177 }
178 }
179 }
180
181 unsafe impl Send for GcBoxHeader {}
182 unsafe impl Sync for GcBoxHeader {}
183
184 #[repr(C)]
185 pub struct GcBox<T: Trace + 'static> {
186 pub(crate) header: GcBoxHeader,
187 pub value: T,
188 }
189
190 pub(crate) unsafe fn trace_gc_box<T: Trace + 'static>(
191 header: *const GcBoxHeader,
192 visitor: &mut MarkVisitor,
193 ) {
194 unsafe {
195 let gc_box = header as *const GcBox<T>;
196 (*gc_box).value.trace(visitor);
197 }
198 }
199
200 pub(crate) unsafe fn drop_gc_box<T: Trace + 'static>(header: *mut GcBoxHeader) {
201 unsafe {
202 #[cfg(debug_assertions)]
203 {
204 (*header).magic.set(GC_MAGIC_FREED);
205 }
206 let gc_box = header as *mut GcBox<T>;
207 drop(Box::from_raw(gc_box));
208 }
209 }
210}
211
212#[cfg(feature = "no-gc")]
217pub use self::nogc_box::GcBox;
218
219#[cfg(feature = "no-gc")]
220mod nogc_box {
221 use crate::Trace;
222
223 pub struct GcBox<T: Trace + 'static> {
224 pub value: T,
225 }
226}
227
228#[cfg(not(feature = "no-gc"))]
233#[derive(Default)]
234pub struct MarkVisitor {
235 pub(crate) grey: Vec<*mut GcBoxHeader>,
236}
237
238#[cfg(feature = "no-gc")]
239pub struct MarkVisitor;
240
241#[cfg(not(feature = "no-gc"))]
242impl MarkVisitor {
243 pub fn new() -> Self {
244 Self::default()
245 }
246
247 pub fn grey_len(&self) -> usize {
248 self.grey.len()
249 }
250
251 pub unsafe fn mark_header(&mut self, header: *mut GcBoxHeader) {
252 use gc_header::GC_INITIAL_LIVES;
253 let h = unsafe { &*header };
254 if h.lives.get() < GC_INITIAL_LIVES {
255 h.lives.set(GC_INITIAL_LIVES);
256 self.grey.push(header);
257 }
258 }
259
260 pub(crate) fn drain(&mut self) {
261 let mut visited = 0usize;
262 while let Some(header) = self.grey.pop() {
263 visited += 1;
264 let h = unsafe { &*header };
265 unsafe { (h.trace_fn)(header as *const GcBoxHeader, self) };
266 }
267 cljrs_logging::feat_debug!("gc", "drain visited {} objects", visited);
268 }
269}
270
271#[cfg(not(feature = "no-gc"))]
272impl GcVisitor for MarkVisitor {
273 fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>) {
274 use gc_header::GC_INITIAL_LIVES;
275 let header = unsafe { &(*ptr.0.as_ptr()).header };
276 if header.lives.get() < GC_INITIAL_LIVES {
277 header.lives.set(GC_INITIAL_LIVES);
278 self.grey.push(ptr.0.as_ptr() as *mut GcBoxHeader);
279 }
280 }
281}
282
283#[cfg(feature = "no-gc")]
284impl MarkVisitor {
285 pub fn grey_len(&self) -> usize {
286 0
287 }
288 pub unsafe fn mark_header(&mut self, _: *mut u8) {}
289}
290
291#[cfg(feature = "no-gc")]
292impl GcVisitor for MarkVisitor {
293 fn visit<T: Trace + 'static>(&mut self, _: &GcPtr<T>) {}
294}
295
296pub struct GcPtr<T: Trace + 'static>(NonNull<GcBox<T>>);
301
302unsafe impl<T: Trace + 'static> Send for GcPtr<T> {}
303unsafe impl<T: Trace + 'static> Sync for GcPtr<T> {}
304
305impl<T: Trace + 'static> GcPtr<T> {
306 #[cfg(not(feature = "no-gc"))]
307 pub fn new(value: T) -> Self {
308 gc_full::HEAP.alloc(value)
309 }
310
311 #[cfg(feature = "no-gc")]
312 pub fn new(value: T) -> Self {
313 alloc_ctx::alloc_in_ctx(value)
314 }
315
316 pub fn get(&self) -> &T {
317 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
318 {
319 use gc_header::GC_MAGIC_ALIVE;
320 let header = unsafe { &(*self.0.as_ptr()).header };
321 assert_eq!(
322 header.magic.get(),
323 GC_MAGIC_ALIVE,
324 "GcPtr::get() on freed object! magic={:#x}",
325 header.magic.get(),
326 );
327 }
328 unsafe { &(*self.0.as_ptr()).value }
329 }
330
331 pub fn get_mut(&mut self) -> &mut T {
332 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
333 {
334 use gc_header::GC_MAGIC_ALIVE;
335 let header = unsafe { &(*self.0.as_ptr()).header };
336 assert_eq!(
337 header.magic.get(),
338 GC_MAGIC_ALIVE,
339 "GcPtr::get_mut() on freed object! magic={:#x}",
340 header.magic.get(),
341 );
342 }
343 unsafe { &mut (*self.0.as_ptr()).value }
344 }
345
346 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
347 a.0 == b.0
348 }
349
350 #[cfg(all(feature = "no-gc", debug_assertions))]
356 pub fn is_static_alloc(&self) -> bool {
357 static_arena::is_static_addr(self.0.as_ptr() as usize)
358 }
359}
360
361impl<T: Trace + 'static> Clone for GcPtr<T> {
362 fn clone(&self) -> Self {
363 GcPtr(self.0)
364 }
365}
366
367impl<T: Trace + 'static + std::fmt::Debug> std::fmt::Debug for GcPtr<T> {
368 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369 unsafe { (*self.0.as_ptr()).value.fmt(f) }
370 }
371}
372
373impl<T: Trace + 'static> Drop for GcPtr<T> {
374 fn drop(&mut self) {}
375}
376
377#[cfg(not(feature = "no-gc"))]
382mod gc_full {
383 use std::cell::RefCell;
384 use std::ptr::NonNull;
385 use std::sync::atomic::{AtomicUsize, Ordering};
386 use std::sync::{Arc, Mutex};
387
388 use crate::config::GcConfig;
389 use crate::gc_header::GC_INITIAL_LIVES;
390 use crate::{GcBox, GcBoxHeader, GcPtr, MarkVisitor, Trace};
391
392 type RootTracer = Box<dyn Fn(&mut MarkVisitor) + Send + Sync>;
393
394 pub struct GcHeap {
395 inner: Mutex<GcHeapInner>,
396 config: Mutex<Option<Arc<GcConfig>>>,
397 memory_in_use: AtomicUsize,
398 total_allocated_bytes: AtomicUsize,
399 root_tracers: Mutex<Vec<RootTracer>>,
400 gc_suppressed: std::sync::atomic::AtomicBool,
401 suppressed_threshold: AtomicUsize,
408 zero_yield_headroom: AtomicUsize,
410 }
411
412 struct GcHeapInner {
413 head: *mut GcBoxHeader,
414 count: usize,
415 total_allocated: usize,
416 total_freed: usize,
417 }
418
419 unsafe impl Send for GcHeapInner {}
420
421 impl GcHeapInner {
422 const fn new() -> Self {
423 Self {
424 head: std::ptr::null_mut(),
425 count: 0,
426 total_allocated: 0,
427 total_freed: 0,
428 }
429 }
430 }
431
432 unsafe impl Sync for GcHeap {}
433
434 impl Default for GcHeap {
435 fn default() -> Self {
436 Self::new()
437 }
438 }
439
440 impl GcHeap {
441 pub const fn new() -> Self {
442 Self {
443 inner: Mutex::new(GcHeapInner::new()),
444 config: Mutex::new(None),
445 memory_in_use: AtomicUsize::new(0),
446 total_allocated_bytes: AtomicUsize::new(0),
447 root_tracers: Mutex::new(Vec::new()),
448 gc_suppressed: std::sync::atomic::AtomicBool::new(false),
449 suppressed_threshold: AtomicUsize::new(0),
450 zero_yield_headroom: AtomicUsize::new(0),
451 }
452 }
453
454 pub fn set_config(&self, config: Arc<GcConfig>) {
455 *self.config.lock().unwrap() = Some(config);
456 }
457
458 pub fn set_config_from_env(&self) {
459 #[cfg(not(target_arch = "wasm32"))]
460 let default_soft_limit: usize = (system_memory::total() / 3) as usize;
461 #[cfg(target_arch = "wasm32")]
462 let default_soft_limit: usize = 64 * 1024 * 1024;
463
464 let soft_limit_mb: usize = match std::env::var("CLJRS_GC_SOFT_LIMIT_MB").ok() {
465 Some(s) => s.parse::<usize>().unwrap() * (1024 * 1024),
466 None => default_soft_limit,
467 };
468 let hard_limit_mb: usize = match std::env::var("CLJRS_GC_HARD_LIMIT_MB").ok() {
469 Some(s) => s.parse::<usize>().unwrap() * (1024 * 1024),
470 None => soft_limit_mb,
471 };
472 self.set_config(Arc::new(GcConfig::with_limits(
473 soft_limit_mb,
474 hard_limit_mb,
475 )));
476 }
477
478 pub fn register_root_tracer(
479 &self,
480 tracer: impl Fn(&mut MarkVisitor) + Send + Sync + 'static,
481 ) {
482 self.root_tracers.lock().unwrap().push(Box::new(tracer));
483 }
484
485 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
486 let tracers = self.root_tracers.lock().unwrap();
487 for tracer in tracers.iter() {
488 tracer(visitor);
489 }
490 }
491
492 pub fn memory_in_use(&self) -> usize {
493 self.memory_in_use.load(Ordering::Relaxed)
494 }
495
496 #[cfg(test)]
497 pub fn set_memory_in_use(&self, bytes: usize) {
498 self.memory_in_use.store(bytes, Ordering::Relaxed);
499 }
500
501 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
502 crate::cancellation::safepoint();
503 let heap_extra = value.gc_size_extra();
504 let gc_box = Box::new(GcBox {
505 header: GcBoxHeader::new::<T>(heap_extra),
506 value,
507 });
508 let obj_size = gc_box.header.size; let raw: *mut GcBox<T> = Box::into_raw(gc_box);
510 {
511 let mut inner = self.inner.lock().unwrap();
512 unsafe {
513 (*raw).header.next.set(inner.head);
514 inner.head = raw as *mut GcBoxHeader;
515 }
516 inner.count += 1;
517 inner.total_allocated += 1;
518 }
519 self.total_allocated_bytes
520 .fetch_add(obj_size, Ordering::Relaxed);
521 crate::stats::GC_STATS.record_gc_alloc(obj_size);
522 let current_usage =
523 self.memory_in_use.fetch_add(obj_size, Ordering::Relaxed) + obj_size;
524
525 if let Some(config) = self.config.lock().unwrap().as_ref()
526 && config.soft_limit_exceeded(current_usage)
527 {
528 if self.gc_suppressed.load(Ordering::Relaxed) {
529 let threshold = self.suppressed_threshold.load(Ordering::Relaxed);
533 if current_usage > threshold {
534 self.gc_suppressed.store(false, Ordering::Relaxed);
535 crate::cancellation::request_gc();
536 }
537 } else {
538 crate::cancellation::request_gc();
539 }
540 }
541
542 register_alloc(raw as *mut GcBoxHeader);
543 GcPtr(unsafe { NonNull::new_unchecked(raw) })
544 }
545
546 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
547 let pre_count = self.inner.lock().unwrap().count;
548 let pre_memory = self.memory_in_use.load(Ordering::Relaxed);
549 cljrs_logging::feat_debug!(
550 "gc",
551 "starting collection: {} objects, ~{} bytes in use",
552 pre_count,
553 pre_memory
554 );
555 let mark_start = std::time::Instant::now();
556 let mut visitor = MarkVisitor::new();
557 trace_roots(&mut visitor);
558 cljrs_logging::feat_debug!(
559 "gc",
560 "starting drain with {} grey objects",
561 visitor.grey.len()
562 );
563 visitor.drain();
564 let mark_elapsed = mark_start.elapsed();
565
566 let sweep_start = std::time::Instant::now();
567 let mut inner = self.inner.lock().unwrap();
568 let mut live: Vec<*mut GcBoxHeader> = Vec::with_capacity(inner.count);
569 let mut dead: Vec<*mut GcBoxHeader> = Vec::new();
570 let mut freed_bytes: usize = 0;
572 let mut current = inner.head;
573 while !current.is_null() {
574 let header = unsafe { &*current };
575 let next = header.next.get();
576 let lives = header.lives.get();
577 let obj_size = header.size;
578 if lives >= GC_INITIAL_LIVES {
579 header.lives.set(GC_INITIAL_LIVES - 1);
581 live.push(current);
582 } else if lives > 0 {
583 header.lives.set(lives - 1);
585 live.push(current);
586 } else {
587 freed_bytes += obj_size;
589 dead.push(current);
590 }
591 current = next;
592 }
593 let freed_count = dead.len();
594 for ptr in dead {
595 let header = unsafe { &*ptr };
596 unsafe { (header.drop_fn)(ptr) };
597 inner.count -= 1;
598 inner.total_freed += 1;
599 }
600 inner.head = std::ptr::null_mut();
601 for ptr in live {
602 let header = unsafe { &*ptr };
603 header.next.set(inner.head);
604 inner.head = ptr;
605 }
606 self.memory_in_use.fetch_sub(freed_bytes, Ordering::Relaxed);
611 let sweep_elapsed = sweep_start.elapsed();
612 crate::stats::GC_STATS.record_gc_pause(
613 mark_elapsed + sweep_elapsed,
614 freed_count as u64,
615 freed_bytes as u64,
616 );
617 let post_memory = self.memory_in_use.load(Ordering::Relaxed);
618 cljrs_logging::feat_debug!(
619 "gc",
620 "collection complete: freed {} (~{} bytes), {} remaining (~{} bytes), mark={:.2?} sweep={:.2?}",
621 freed_count,
622 freed_bytes,
623 inner.count,
624 post_memory,
625 mark_elapsed,
626 sweep_elapsed
627 );
628 if freed_count == 0 {
629 let soft_limit = self
640 .config
641 .lock()
642 .unwrap()
643 .as_ref()
644 .map(|c| c.soft_limit())
645 .unwrap_or(64 * 1024 * 1024);
646 let base_headroom = (soft_limit / 10).max(1);
647 let max_headroom = (soft_limit / 4).max(base_headroom);
648 let prev_headroom = self.zero_yield_headroom.load(Ordering::Relaxed);
649 let headroom = if prev_headroom == 0 {
650 base_headroom
651 } else {
652 prev_headroom.saturating_mul(2).min(max_headroom)
653 };
654 self.zero_yield_headroom.store(headroom, Ordering::Relaxed);
655 self.suppressed_threshold
656 .store(post_memory + headroom, Ordering::Relaxed);
657 self.gc_suppressed.store(true, Ordering::Relaxed);
658 } else {
659 self.zero_yield_headroom.store(0, Ordering::Relaxed);
661 self.gc_suppressed.store(false, Ordering::Relaxed);
662 }
663 }
664
665 pub fn count(&self) -> usize {
666 self.inner.lock().unwrap().count
667 }
668 pub fn total_allocated(&self) -> usize {
669 self.inner.lock().unwrap().total_allocated
670 }
671 pub fn total_freed(&self) -> usize {
672 self.inner.lock().unwrap().total_freed
673 }
674
675 pub fn collect_auto(&self) -> bool {
676 cljrs_logging::feat_debug!("gc", "automatic collection requested");
677 let Some(_stw_guard) = crate::cancellation::begin_stw() else {
678 cljrs_logging::feat_debug!("gc", "automatic collection skipped");
679 return false;
680 };
681 self.collect(|visitor| self.trace_registered_roots(visitor));
682 true
683 }
684 }
685
686 pub static HEAP: GcHeap = GcHeap::new();
687
688 thread_local! {
689 pub(crate) static ALLOC_ROOTS: RefCell<Vec<*mut GcBoxHeader>> = const { RefCell::new(Vec::new()) };
690 }
691
692 pub struct AllocRootGuard {
693 saved_len: usize,
694 }
695
696 impl Drop for AllocRootGuard {
697 fn drop(&mut self) {
698 ALLOC_ROOTS.with(|roots| roots.borrow_mut().truncate(self.saved_len));
699 }
700 }
701
702 pub fn push_alloc_frame() -> AllocRootGuard {
703 let saved_len = ALLOC_ROOTS.with(|roots| roots.borrow().len());
704 AllocRootGuard { saved_len }
705 }
706
707 fn register_alloc(header: *mut GcBoxHeader) {
708 ALLOC_ROOTS.with(|roots| roots.borrow_mut().push(header));
709 }
710
711 pub fn trace_thread_alloc_roots(visitor: &mut MarkVisitor) {
712 ALLOC_ROOTS.with(|roots| {
713 let roots = roots.borrow();
714 for &header in roots.iter() {
715 unsafe { visitor.mark_header(header) };
716 }
717 });
718 }
719}
720
721#[cfg(feature = "no-gc")]
726mod nogc_stubs {
727 use crate::MarkVisitor;
728 use std::sync::Arc;
729
730 #[derive(Debug, Clone)]
731 pub struct GcConfig;
732 impl GcConfig {
733 pub fn new() -> Self {
734 Self
735 }
736 pub fn with_hard_limit(_: usize) -> Self {
737 Self
738 }
739 pub fn with_limits(_: usize, _: usize) -> Self {
740 Self
741 }
742 }
743 impl Default for GcConfig {
744 fn default() -> Self {
745 Self::new()
746 }
747 }
748
749 pub struct GcHeap;
750 impl Default for GcHeap {
751 fn default() -> Self {
752 Self::new()
753 }
754 }
755 impl GcHeap {
756 pub const fn new() -> Self {
757 Self
758 }
759 pub fn set_config(&self, _: Arc<GcConfig>) {}
760 pub fn register_root_tracer(&self, _: impl Fn(&mut MarkVisitor) + Send + Sync + 'static) {}
761 pub fn trace_registered_roots(&self, _: &mut MarkVisitor) {}
762 pub fn memory_in_use(&self) -> usize {
763 0
764 }
765 pub fn count(&self) -> usize {
766 0
767 }
768 pub fn total_allocated(&self) -> usize {
769 0
770 }
771 pub fn total_freed(&self) -> usize {
772 0
773 }
774 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, _: F) {}
775 pub fn collect_auto(&self) -> bool {
776 false
777 }
778 }
779 unsafe impl Sync for GcHeap {}
780 pub static HEAP: GcHeap = GcHeap::new();
781
782 pub struct MutatorGuard;
783 impl Drop for MutatorGuard {
784 fn drop(&mut self) {}
785 }
786 pub struct StwGuard;
787 impl Drop for StwGuard {
788 fn drop(&mut self) {}
789 }
790 pub struct GcParked;
791 pub struct CancellableGuard;
792
793 pub struct GcCancellationStub;
794 impl GcCancellationStub {
795 pub const fn new() -> Self {
796 Self
797 }
798 pub fn in_progress(&self) -> bool {
799 false
800 }
801 }
802 pub static CONFIG_CANCELLATION: GcCancellationStub = GcCancellationStub::new();
803
804 pub fn safepoint() {}
805 pub fn gc_requested() -> bool {
806 false
807 }
808 pub fn take_gc_request() -> bool {
809 false
810 }
811 pub fn begin_stw() -> Option<StwGuard> {
812 None
813 }
814 pub fn register_mutator() -> MutatorGuard {
815 MutatorGuard
816 }
817 pub fn registered_threads() -> usize {
818 0
819 }
820 pub fn request_gc() {}
821 pub fn check_cancellation() -> Result<(), GcParked> {
822 Ok(())
823 }
824 pub fn park_thread() {}
825 pub fn unpark_thread() {}
826 pub fn wait_for_threads_to_park() {}
827
828 pub struct AllocRootGuard;
829 impl Drop for AllocRootGuard {
830 fn drop(&mut self) {}
831 }
832 pub fn push_alloc_frame() -> AllocRootGuard {
833 AllocRootGuard
834 }
835}
836
837#[cfg(all(test, not(feature = "no-gc")))]
842mod tests {
843 use super::*;
844 use std::sync::{Arc, Mutex};
845
846 #[derive(Debug)]
847 #[allow(dead_code)]
848 struct Tracked {
849 value: i32,
850 dropped: Arc<Mutex<bool>>,
851 }
852 impl Drop for Tracked {
853 fn drop(&mut self) {
854 *self.dropped.lock().unwrap() = true;
855 }
856 }
857 impl Trace for Tracked {
858 fn trace(&self, _: &mut MarkVisitor) {}
859 }
860
861 #[derive(Debug)]
862 #[allow(dead_code)]
863 struct Parent {
864 child: GcPtr<Tracked>,
865 }
866 impl Trace for Parent {
867 fn trace(&self, visitor: &mut MarkVisitor) {
868 visitor.visit(&self.child);
869 }
870 }
871
872 fn fresh_heap() -> gc_full::GcHeap {
873 let heap = gc_full::GcHeap::new();
874 heap.set_config(Arc::new(GcConfig::with_limits(10000, 50000)));
875 heap
876 }
877
878 #[test]
879 fn alloc_and_get() {
880 let heap = fresh_heap();
881 let p = heap.alloc(42i64);
882 assert_eq!(*p.get(), 42);
883 assert_eq!(heap.count(), 1);
884 }
885
886 #[test]
887 fn clone_is_same_ptr() {
888 let heap = fresh_heap();
889 let p = heap.alloc(99i64);
890 let q = p.clone();
891 assert!(GcPtr::ptr_eq(&p, &q));
892 }
893
894 #[test]
895 fn collect_keeps_reachable() {
896 let heap = fresh_heap();
897 let dropped = Arc::new(Mutex::new(false));
898 let p = heap.alloc(Tracked {
899 value: 2,
900 dropped: dropped.clone(),
901 });
902 heap.collect(|vis| vis.visit(&p));
903 assert_eq!(heap.count(), 1);
904 assert!(!*dropped.lock().unwrap());
905 }
906}
907
908#[cfg(all(test, feature = "no-gc"))]
909mod nogc_tests {
910 use super::*;
911 use alloc_ctx::{ScratchGuard, StaticCtxGuard};
912
913 #[test]
914 fn alloc_in_static_context() {
915 let _g = StaticCtxGuard::new();
916 let p = GcPtr::new(42i64);
917 assert_eq!(*p.get(), 42);
918 }
919
920 #[test]
921 fn alloc_in_scratch_region() {
922 let mut scratch = ScratchGuard::new();
923 let p = GcPtr::new(99i64);
924 assert_eq!(*p.get(), 99);
925 scratch.pop_for_return();
926 assert_eq!(*p.get(), 99);
927 }
929
930 #[test]
931 fn ptr_eq() {
932 let _g = StaticCtxGuard::new();
933 let p = GcPtr::new(1i64);
934 let q = p.clone();
935 assert!(GcPtr::ptr_eq(&p, &q));
936 }
937}