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 let soft_limit_mb: usize = match std::env::var("CLJRS_GC_SOFT_LIMIT_MB").ok() {
460 Some(s) => s.parse::<usize>().unwrap() * (1024 * 1024),
461 None => (system_memory::total() / 3) as usize,
462 };
463 let hard_limit_mb: usize = match std::env::var("CLJRS_GC_HARD_LIMIT_MB").ok() {
464 Some(s) => s.parse::<usize>().unwrap() * (1024 * 1024),
465 None => soft_limit_mb,
466 };
467 self.set_config(Arc::new(GcConfig::with_limits(
468 soft_limit_mb,
469 hard_limit_mb,
470 )));
471 }
472
473 pub fn register_root_tracer(
474 &self,
475 tracer: impl Fn(&mut MarkVisitor) + Send + Sync + 'static,
476 ) {
477 self.root_tracers.lock().unwrap().push(Box::new(tracer));
478 }
479
480 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
481 let tracers = self.root_tracers.lock().unwrap();
482 for tracer in tracers.iter() {
483 tracer(visitor);
484 }
485 }
486
487 pub fn memory_in_use(&self) -> usize {
488 self.memory_in_use.load(Ordering::Relaxed)
489 }
490
491 #[cfg(test)]
492 pub fn set_memory_in_use(&self, bytes: usize) {
493 self.memory_in_use.store(bytes, Ordering::Relaxed);
494 }
495
496 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
497 crate::cancellation::safepoint();
498 let heap_extra = value.gc_size_extra();
499 let gc_box = Box::new(GcBox {
500 header: GcBoxHeader::new::<T>(heap_extra),
501 value,
502 });
503 let obj_size = gc_box.header.size; let raw: *mut GcBox<T> = Box::into_raw(gc_box);
505 {
506 let mut inner = self.inner.lock().unwrap();
507 unsafe {
508 (*raw).header.next.set(inner.head);
509 inner.head = raw as *mut GcBoxHeader;
510 }
511 inner.count += 1;
512 inner.total_allocated += 1;
513 }
514 self.total_allocated_bytes
515 .fetch_add(obj_size, Ordering::Relaxed);
516 crate::stats::GC_STATS.record_gc_alloc(obj_size);
517 let current_usage =
518 self.memory_in_use.fetch_add(obj_size, Ordering::Relaxed) + obj_size;
519
520 if let Some(config) = self.config.lock().unwrap().as_ref()
521 && config.soft_limit_exceeded(current_usage)
522 {
523 if self.gc_suppressed.load(Ordering::Relaxed) {
524 let threshold = self.suppressed_threshold.load(Ordering::Relaxed);
528 if current_usage > threshold {
529 self.gc_suppressed.store(false, Ordering::Relaxed);
530 crate::cancellation::request_gc();
531 }
532 } else {
533 crate::cancellation::request_gc();
534 }
535 }
536
537 register_alloc(raw as *mut GcBoxHeader);
538 GcPtr(unsafe { NonNull::new_unchecked(raw) })
539 }
540
541 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
542 let pre_count = self.inner.lock().unwrap().count;
543 let pre_memory = self.memory_in_use.load(Ordering::Relaxed);
544 cljrs_logging::feat_debug!(
545 "gc",
546 "starting collection: {} objects, ~{} bytes in use",
547 pre_count,
548 pre_memory
549 );
550 let mark_start = std::time::Instant::now();
551 let mut visitor = MarkVisitor::new();
552 trace_roots(&mut visitor);
553 cljrs_logging::feat_debug!(
554 "gc",
555 "starting drain with {} grey objects",
556 visitor.grey.len()
557 );
558 visitor.drain();
559 let mark_elapsed = mark_start.elapsed();
560
561 let sweep_start = std::time::Instant::now();
562 let mut inner = self.inner.lock().unwrap();
563 let mut live: Vec<*mut GcBoxHeader> = Vec::with_capacity(inner.count);
564 let mut dead: Vec<*mut GcBoxHeader> = Vec::new();
565 let mut freed_bytes: usize = 0;
567 let mut current = inner.head;
568 while !current.is_null() {
569 let header = unsafe { &*current };
570 let next = header.next.get();
571 let lives = header.lives.get();
572 let obj_size = header.size;
573 if lives >= GC_INITIAL_LIVES {
574 header.lives.set(GC_INITIAL_LIVES - 1);
576 live.push(current);
577 } else if lives > 0 {
578 header.lives.set(lives - 1);
580 live.push(current);
581 } else {
582 freed_bytes += obj_size;
584 dead.push(current);
585 }
586 current = next;
587 }
588 let freed_count = dead.len();
589 for ptr in dead {
590 let header = unsafe { &*ptr };
591 unsafe { (header.drop_fn)(ptr) };
592 inner.count -= 1;
593 inner.total_freed += 1;
594 }
595 inner.head = std::ptr::null_mut();
596 for ptr in live {
597 let header = unsafe { &*ptr };
598 header.next.set(inner.head);
599 inner.head = ptr;
600 }
601 self.memory_in_use.fetch_sub(freed_bytes, Ordering::Relaxed);
606 let sweep_elapsed = sweep_start.elapsed();
607 crate::stats::GC_STATS.record_gc_pause(
608 mark_elapsed + sweep_elapsed,
609 freed_count as u64,
610 freed_bytes as u64,
611 );
612 let post_memory = self.memory_in_use.load(Ordering::Relaxed);
613 cljrs_logging::feat_debug!(
614 "gc",
615 "collection complete: freed {} (~{} bytes), {} remaining (~{} bytes), mark={:.2?} sweep={:.2?}",
616 freed_count,
617 freed_bytes,
618 inner.count,
619 post_memory,
620 mark_elapsed,
621 sweep_elapsed
622 );
623 if freed_count == 0 {
624 let soft_limit = self
635 .config
636 .lock()
637 .unwrap()
638 .as_ref()
639 .map(|c| c.soft_limit())
640 .unwrap_or(64 * 1024 * 1024);
641 let base_headroom = (soft_limit / 10).max(1);
642 let max_headroom = (soft_limit / 4).max(base_headroom);
643 let prev_headroom = self.zero_yield_headroom.load(Ordering::Relaxed);
644 let headroom = if prev_headroom == 0 {
645 base_headroom
646 } else {
647 prev_headroom.saturating_mul(2).min(max_headroom)
648 };
649 self.zero_yield_headroom.store(headroom, Ordering::Relaxed);
650 self.suppressed_threshold
651 .store(post_memory + headroom, Ordering::Relaxed);
652 self.gc_suppressed.store(true, Ordering::Relaxed);
653 } else {
654 self.zero_yield_headroom.store(0, Ordering::Relaxed);
656 self.gc_suppressed.store(false, Ordering::Relaxed);
657 }
658 }
659
660 pub fn count(&self) -> usize {
661 self.inner.lock().unwrap().count
662 }
663 pub fn total_allocated(&self) -> usize {
664 self.inner.lock().unwrap().total_allocated
665 }
666 pub fn total_freed(&self) -> usize {
667 self.inner.lock().unwrap().total_freed
668 }
669
670 pub fn collect_auto(&self) -> bool {
671 cljrs_logging::feat_debug!("gc", "automatic collection requested");
672 let Some(_stw_guard) = crate::cancellation::begin_stw() else {
673 cljrs_logging::feat_debug!("gc", "automatic collection skipped");
674 return false;
675 };
676 self.collect(|visitor| self.trace_registered_roots(visitor));
677 true
678 }
679 }
680
681 pub static HEAP: GcHeap = GcHeap::new();
682
683 thread_local! {
684 pub(crate) static ALLOC_ROOTS: RefCell<Vec<*mut GcBoxHeader>> = const { RefCell::new(Vec::new()) };
685 }
686
687 pub struct AllocRootGuard {
688 saved_len: usize,
689 }
690
691 impl Drop for AllocRootGuard {
692 fn drop(&mut self) {
693 ALLOC_ROOTS.with(|roots| roots.borrow_mut().truncate(self.saved_len));
694 }
695 }
696
697 pub fn push_alloc_frame() -> AllocRootGuard {
698 let saved_len = ALLOC_ROOTS.with(|roots| roots.borrow().len());
699 AllocRootGuard { saved_len }
700 }
701
702 fn register_alloc(header: *mut GcBoxHeader) {
703 ALLOC_ROOTS.with(|roots| roots.borrow_mut().push(header));
704 }
705
706 pub fn trace_thread_alloc_roots(visitor: &mut MarkVisitor) {
707 ALLOC_ROOTS.with(|roots| {
708 let roots = roots.borrow();
709 for &header in roots.iter() {
710 unsafe { visitor.mark_header(header) };
711 }
712 });
713 }
714}
715
716#[cfg(feature = "no-gc")]
721mod nogc_stubs {
722 use crate::MarkVisitor;
723 use std::sync::Arc;
724
725 #[derive(Debug, Clone)]
726 pub struct GcConfig;
727 impl GcConfig {
728 pub fn new() -> Self {
729 Self
730 }
731 pub fn with_hard_limit(_: usize) -> Self {
732 Self
733 }
734 pub fn with_limits(_: usize, _: usize) -> Self {
735 Self
736 }
737 }
738 impl Default for GcConfig {
739 fn default() -> Self {
740 Self::new()
741 }
742 }
743
744 pub struct GcHeap;
745 impl Default for GcHeap {
746 fn default() -> Self {
747 Self::new()
748 }
749 }
750 impl GcHeap {
751 pub const fn new() -> Self {
752 Self
753 }
754 pub fn set_config(&self, _: Arc<GcConfig>) {}
755 pub fn register_root_tracer(&self, _: impl Fn(&mut MarkVisitor) + Send + Sync + 'static) {}
756 pub fn trace_registered_roots(&self, _: &mut MarkVisitor) {}
757 pub fn memory_in_use(&self) -> usize {
758 0
759 }
760 pub fn count(&self) -> usize {
761 0
762 }
763 pub fn total_allocated(&self) -> usize {
764 0
765 }
766 pub fn total_freed(&self) -> usize {
767 0
768 }
769 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, _: F) {}
770 pub fn collect_auto(&self) -> bool {
771 false
772 }
773 }
774 unsafe impl Sync for GcHeap {}
775 pub static HEAP: GcHeap = GcHeap::new();
776
777 pub struct MutatorGuard;
778 impl Drop for MutatorGuard {
779 fn drop(&mut self) {}
780 }
781 pub struct StwGuard;
782 impl Drop for StwGuard {
783 fn drop(&mut self) {}
784 }
785 pub struct GcParked;
786 pub struct CancellableGuard;
787
788 pub struct GcCancellationStub;
789 impl GcCancellationStub {
790 pub const fn new() -> Self {
791 Self
792 }
793 pub fn in_progress(&self) -> bool {
794 false
795 }
796 }
797 pub static CONFIG_CANCELLATION: GcCancellationStub = GcCancellationStub::new();
798
799 pub fn safepoint() {}
800 pub fn gc_requested() -> bool {
801 false
802 }
803 pub fn take_gc_request() -> bool {
804 false
805 }
806 pub fn begin_stw() -> Option<StwGuard> {
807 None
808 }
809 pub fn register_mutator() -> MutatorGuard {
810 MutatorGuard
811 }
812 pub fn registered_threads() -> usize {
813 0
814 }
815 pub fn request_gc() {}
816 pub fn check_cancellation() -> Result<(), GcParked> {
817 Ok(())
818 }
819 pub fn park_thread() {}
820 pub fn unpark_thread() {}
821 pub fn wait_for_threads_to_park() {}
822
823 pub struct AllocRootGuard;
824 impl Drop for AllocRootGuard {
825 fn drop(&mut self) {}
826 }
827 pub fn push_alloc_frame() -> AllocRootGuard {
828 AllocRootGuard
829 }
830}
831
832#[cfg(all(test, not(feature = "no-gc")))]
837mod tests {
838 use super::*;
839 use std::sync::{Arc, Mutex};
840
841 #[derive(Debug)]
842 #[allow(dead_code)]
843 struct Tracked {
844 value: i32,
845 dropped: Arc<Mutex<bool>>,
846 }
847 impl Drop for Tracked {
848 fn drop(&mut self) {
849 *self.dropped.lock().unwrap() = true;
850 }
851 }
852 impl Trace for Tracked {
853 fn trace(&self, _: &mut MarkVisitor) {}
854 }
855
856 #[derive(Debug)]
857 #[allow(dead_code)]
858 struct Parent {
859 child: GcPtr<Tracked>,
860 }
861 impl Trace for Parent {
862 fn trace(&self, visitor: &mut MarkVisitor) {
863 visitor.visit(&self.child);
864 }
865 }
866
867 fn fresh_heap() -> gc_full::GcHeap {
868 let heap = gc_full::GcHeap::new();
869 heap.set_config(Arc::new(GcConfig::with_limits(10000, 50000)));
870 heap
871 }
872
873 #[test]
874 fn alloc_and_get() {
875 let heap = fresh_heap();
876 let p = heap.alloc(42i64);
877 assert_eq!(*p.get(), 42);
878 assert_eq!(heap.count(), 1);
879 }
880
881 #[test]
882 fn clone_is_same_ptr() {
883 let heap = fresh_heap();
884 let p = heap.alloc(99i64);
885 let q = p.clone();
886 assert!(GcPtr::ptr_eq(&p, &q));
887 }
888
889 #[test]
890 fn collect_keeps_reachable() {
891 let heap = fresh_heap();
892 let dropped = Arc::new(Mutex::new(false));
893 let p = heap.alloc(Tracked {
894 value: 2,
895 dropped: dropped.clone(),
896 });
897 heap.collect(|vis| vis.visit(&p));
898 assert_eq!(heap.count(), 1);
899 assert!(!*dropped.lock().unwrap());
900 }
901}
902
903#[cfg(all(test, feature = "no-gc"))]
904mod nogc_tests {
905 use super::*;
906 use alloc_ctx::{ScratchGuard, StaticCtxGuard};
907
908 #[test]
909 fn alloc_in_static_context() {
910 let _g = StaticCtxGuard::new();
911 let p = GcPtr::new(42i64);
912 assert_eq!(*p.get(), 42);
913 }
914
915 #[test]
916 fn alloc_in_scratch_region() {
917 let mut scratch = ScratchGuard::new();
918 let p = GcPtr::new(99i64);
919 assert_eq!(*p.get(), 99);
920 scratch.pop_for_return();
921 assert_eq!(*p.get(), 99);
922 }
924
925 #[test]
926 fn ptr_eq() {
927 let _g = StaticCtxGuard::new();
928 let p = GcPtr::new(1i64);
929 let q = p.clone();
930 assert!(GcPtr::ptr_eq(&p, &q));
931 }
932}