1#![allow(clippy::missing_safety_doc)]
4#![allow(private_interfaces)]
5
6use std::ptr::NonNull;
7
8pub mod fiber;
9pub mod governor;
10pub mod mutator;
11pub mod region;
12pub mod stats;
13
14#[cfg(not(feature = "no-gc"))]
15pub mod cancellation;
16#[cfg(not(feature = "no-gc"))]
17pub mod config;
18
19#[cfg(feature = "no-gc")]
20pub mod alloc_ctx;
21#[cfg(feature = "no-gc")]
22pub mod static_arena;
23
24pub use governor::{
25 Admission, CriticalSection, IsolateAccount, IsolateControl, MemoryCharge, MemoryClass,
26 MemoryConfig, MemoryConfigError, MemoryLimitExceeded, MemorySnapshot, PressureLevel,
27 ProcessMemoryGovernor, ShedHookId, enter_critical, governor, register_current_isolate,
28 resolve_runtime_fault, with_critical,
29};
30pub use stats::{
31 CLJRS_GC_STATS_ENV, GC_STATS, GcStats, GcStatsSnapshot, dump_stats_from_env,
32 report as stats_report,
33};
34
35#[cfg(not(feature = "no-gc"))]
38pub use cancellation::{
39 MutatorGuard, StwGuard, begin_stw, check_cancellation, gc_requested, park_thread,
40 register_mutator, registered_threads, request_gc, safepoint, take_gc_request, unpark_thread,
41 wait_for_threads_to_park,
42};
43#[cfg(not(feature = "no-gc"))]
44pub use config::{GC_CANCELLATION as CONFIG_CANCELLATION, GcConfig, GcParked};
45
46#[cfg(not(feature = "no-gc"))]
47pub use gc_full::{
48 AllocRootGuard, GcHeap, HEAP, HeapProxy, push_alloc_frame, trace_thread_alloc_roots,
49};
50#[cfg(feature = "no-gc")]
51pub use nogc_stubs::{
52 AllocRootGuard, CONFIG_CANCELLATION, GcConfig, GcHeap, GcParked, HEAP, MutatorGuard, StwGuard,
53 begin_stw, check_cancellation, gc_requested, park_thread, push_alloc_frame, register_mutator,
54 registered_threads, request_gc, safepoint, take_gc_request, unpark_thread,
55 wait_for_threads_to_park,
56};
57
58#[cfg(all(feature = "no-gc", debug_assertions))]
63pub fn is_static_addr(addr: usize) -> bool {
64 static_arena::is_static_addr(addr)
65}
66
67pub trait Trace {
81 #[cfg(feature = "no-gc")]
83 fn init_ownership(&self) {}
84
85 #[cfg(feature = "no-gc")]
88 fn immutable_children(&self) -> bool {
89 false
90 }
91
92 fn trace(&self, visitor: &mut MarkVisitor);
93
94 fn gc_size_extra(&self) -> usize {
95 0
96 }
97}
98
99impl Trace for String {
102 fn trace(&self, _: &mut MarkVisitor) {}
103
104 fn gc_size_extra(&self) -> usize {
105 self.capacity()
106 }
107}
108impl Trace for i64 {
109 fn trace(&self, _: &mut MarkVisitor) {}
110}
111impl Trace for f64 {
112 fn trace(&self, _: &mut MarkVisitor) {}
113}
114impl Trace for bool {
115 fn trace(&self, _: &mut MarkVisitor) {}
116}
117impl Trace for num_bigint::BigInt {
118 fn trace(&self, _: &mut MarkVisitor) {}
119}
120impl Trace for bigdecimal::BigDecimal {
121 fn trace(&self, _: &mut MarkVisitor) {}
122}
123impl Trace for num_rational::Ratio<num_bigint::BigInt> {
124 fn trace(&self, _: &mut MarkVisitor) {}
125}
126macro_rules! impl_trace_prim_array {
127 ($t:ty) => {
128 impl Trace for std::sync::Mutex<Vec<$t>> {
129 fn trace(&self, _: &mut MarkVisitor) {}
130 fn gc_size_extra(&self) -> usize {
131 self.lock().unwrap().capacity() * std::mem::size_of::<$t>()
132 }
133 }
134 };
135}
136impl_trace_prim_array!(i32);
137impl_trace_prim_array!(i64);
138impl_trace_prim_array!(i16);
139impl_trace_prim_array!(i8);
140impl_trace_prim_array!(char);
141impl_trace_prim_array!(f64);
142impl_trace_prim_array!(f32);
143impl_trace_prim_array!(bool);
144
145pub trait GcVisitor {
148 fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>);
149}
150
151#[cfg(not(feature = "no-gc"))]
156pub use self::gc_header::{GcBox, GcBoxHeader};
157
158#[cfg(not(feature = "no-gc"))]
159mod gc_header {
160 use crate::{MarkVisitor, Trace};
161 use std::cell::Cell;
162
163 pub(crate) const GC_INITIAL_LIVES: u8 = 2;
171
172 #[cfg(debug_assertions)]
173 pub(crate) const GC_MAGIC_ALIVE: u64 = 0xCAFE_BABE_DEAD_BEEF;
174 #[cfg(debug_assertions)]
175 pub(crate) const GC_MAGIC_FREED: u64 = 0xDEAD_DEAD_DEAD_DEAD;
176
177 #[repr(C)]
178 pub struct GcBoxHeader {
179 #[cfg(debug_assertions)]
180 pub(crate) magic: Cell<u64>,
181 pub(crate) size: usize,
183 pub(crate) lives: Cell<u8>,
184 pub(crate) next: Cell<*mut GcBoxHeader>,
185 pub(crate) trace_fn: unsafe fn(*const GcBoxHeader, &mut MarkVisitor),
186 pub(crate) drop_fn: unsafe fn(*mut GcBoxHeader),
187 }
188
189 impl GcBoxHeader {
190 pub(crate) fn new<T: Trace + 'static>(heap_extra: usize) -> Self {
191 Self {
192 #[cfg(debug_assertions)]
193 magic: Cell::new(GC_MAGIC_ALIVE),
194 size: std::mem::size_of::<GcBox<T>>() + heap_extra,
195 lives: Cell::new(GC_INITIAL_LIVES - 1),
196 next: Cell::new(std::ptr::null_mut()),
197 trace_fn: trace_gc_box::<T>,
198 drop_fn: drop_gc_box::<T>,
199 }
200 }
201 }
202
203 unsafe impl Send for GcBoxHeader {}
204 unsafe impl Sync for GcBoxHeader {}
205
206 #[repr(C)]
207 pub struct GcBox<T: Trace + 'static> {
208 pub(crate) header: GcBoxHeader,
209 pub value: T,
210 }
211
212 pub(crate) unsafe fn trace_gc_box<T: Trace + 'static>(
213 header: *const GcBoxHeader,
214 visitor: &mut MarkVisitor,
215 ) {
216 unsafe {
217 let gc_box = header as *const GcBox<T>;
218 (*gc_box).value.trace(visitor);
219 }
220 }
221
222 pub(crate) unsafe fn drop_gc_box<T: Trace + 'static>(header: *mut GcBoxHeader) {
223 unsafe {
224 #[cfg(debug_assertions)]
225 {
226 (*header).magic.set(GC_MAGIC_FREED);
227 }
228 let gc_box = header as *mut GcBox<T>;
229 drop(Box::from_raw(gc_box));
230 }
231 }
232}
233
234#[cfg(feature = "no-gc")]
239pub use self::nogc_box::GcBox;
240
241#[cfg(feature = "no-gc")]
242mod nogc_box {
243 use crate::Trace;
244
245 pub(crate) const RELOCATED: u64 = 1 << 63;
246 pub(crate) const OWNED: u64 = 1 << 62;
247 pub(crate) const ID_MASK: u64 = OWNED - 1;
248 static NEXT_IDENTITY: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
249 pub struct GcBox<T: Trace + 'static> {
250 pub value: T,
251 pub(crate) identity: std::sync::atomic::AtomicU64,
252 }
253 impl<T: Trace + 'static> GcBox<T> {
254 pub(crate) fn new(value: T) -> Self {
255 Self {
256 value,
257 identity: std::sync::atomic::AtomicU64::new(
258 NEXT_IDENTITY
259 .fetch_update(
260 std::sync::atomic::Ordering::Relaxed,
261 std::sync::atomic::Ordering::Relaxed,
262 |id| (id < ID_MASK).then_some(id + 1),
263 )
264 .expect("managed identity space exhausted"),
265 ),
266 }
267 }
268 }
269}
270
271#[cfg(not(feature = "no-gc"))]
276#[derive(Default)]
277pub struct MarkVisitor {
278 pub(crate) grey: Vec<*mut GcBoxHeader>,
279}
280
281#[cfg(feature = "no-gc")]
282#[derive(Default)]
283pub struct MarkVisitor {
284 retaining: Option<std::collections::HashSet<usize>>,
285 collecting: Option<region::RegionLease>,
286 pending: Vec<PendingTrace>,
287 destination: Option<region::AllocationOwner>,
288 completed: Vec<*const std::sync::atomic::AtomicU64>,
289}
290
291#[cfg(feature = "no-gc")]
293pub fn retain_regions_for(value: &impl Trace) {
294 let mut visitor = MarkVisitor {
295 retaining: Some(Default::default()),
296 ..Default::default()
297 };
298 value.trace(&mut visitor);
299 visitor.drain_owned();
300}
301
302#[cfg(feature = "no-gc")]
303fn leases_for(value: &impl Trace) -> region::RegionLease {
304 let mut visitor = MarkVisitor {
305 retaining: Some(Default::default()),
306 collecting: Some(Default::default()),
307 ..Default::default()
308 };
309 value.trace(&mut visitor);
310 visitor.drain_owned();
311 visitor.collecting.take().unwrap()
312}
313
314#[cfg(not(feature = "no-gc"))]
315impl MarkVisitor {
316 pub fn new() -> Self {
317 Self::default()
318 }
319
320 pub fn grey_len(&self) -> usize {
321 self.grey.len()
322 }
323
324 pub unsafe fn mark_header(&mut self, header: *mut GcBoxHeader) {
325 use gc_header::GC_INITIAL_LIVES;
326 let h = unsafe { &*header };
327 if h.lives.get() < GC_INITIAL_LIVES {
328 h.lives.set(GC_INITIAL_LIVES);
329 self.grey.push(header);
330 }
331 }
332
333 pub(crate) fn drain(&mut self) {
334 let mut visited = 0usize;
335 while let Some(header) = self.grey.pop() {
336 visited += 1;
337 let h = unsafe { &*header };
338 unsafe { (h.trace_fn)(header as *const GcBoxHeader, self) };
339 }
340 tracing::debug!(target: "gc", "drain visited {} objects", visited);
341 }
342}
343
344#[cfg(not(feature = "no-gc"))]
345impl GcVisitor for MarkVisitor {
346 fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>) {
347 use gc_header::GC_INITIAL_LIVES;
348 if ptr.is_region_alloc() {
356 return;
357 }
358 let raw = ptr.raw();
359 let header = unsafe { &(*raw).header };
360 if header.lives.get() < GC_INITIAL_LIVES {
361 header.lives.set(GC_INITIAL_LIVES);
362 self.grey.push(raw as *mut GcBoxHeader);
363 }
364 }
365}
366
367#[cfg(feature = "no-gc")]
368impl MarkVisitor {
369 pub fn grey_len(&self) -> usize {
370 0
371 }
372 pub unsafe fn mark_header(&mut self, _: *mut u8) {}
373}
374
375#[cfg(feature = "no-gc")]
376struct PendingTrace {
377 pointer: *const u8,
378 trace: unsafe fn(*const u8, &mut MarkVisitor),
379 destination: Option<region::AllocationOwner>,
380}
381#[cfg(feature = "no-gc")]
382impl MarkVisitor {
383 fn drain_owned(&mut self) {
384 while let Some(pending) = self.pending.pop() {
385 self.destination = pending.destination;
386 unsafe { (pending.trace)(pending.pointer, self) };
388 }
389 for identity in self.completed.drain(..) {
390 unsafe { &*identity }.fetch_or(nogc_box::OWNED, std::sync::atomic::Ordering::Release);
392 }
393 }
394}
395#[cfg(feature = "no-gc")]
396impl GcVisitor for MarkVisitor {
397 fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>) {
398 if self.retaining.is_none() {
399 return;
400 }
401 let address = ptr.0.as_ptr() as usize;
402 let source = region::RegionLease::for_address(address);
403 if let Some(destination) = &self.destination {
404 destination.adopt(&source);
405 }
406 if let Some(lease) = &mut self.collecting {
407 lease.extend(&source);
408 } else {
409 region::retain_containing(address);
410 }
411 if !self.retaining.as_mut().unwrap().insert(address) {
412 return;
413 }
414 let immutable = ptr.get().immutable_children();
415 if immutable
416 && unsafe {
417 (*ptr.raw())
418 .identity
419 .load(std::sync::atomic::Ordering::Acquire)
420 & nogc_box::OWNED
421 != 0
422 }
423 {
424 return;
425 }
426 unsafe fn trace<T: Trace + 'static>(raw: *const u8, visitor: &mut MarkVisitor) {
427 unsafe { &*(raw as *const T) }.trace(visitor);
428 }
429 let destination = if immutable {
430 self.completed.push(unsafe { &(*ptr.raw()).identity });
431 Some(region::AllocationOwner::for_address(address))
432 } else {
433 self.destination.clone()
434 };
435 self.pending.push(PendingTrace {
436 pointer: ptr.get() as *const T as *const u8,
437 trace: trace::<T>,
438 destination,
439 });
440 }
441}
442
443pub struct GcPtr<T: Trace + 'static>(NonNull<GcBox<T>>);
448
449#[cfg(not(feature = "no-gc"))]
458pub(crate) const REGION_PTR_TAG: usize = 1;
459
460pub struct AllocError<T> {
463 value: T,
464 error: MemoryLimitExceeded,
465}
466
467impl<T> AllocError<T> {
468 pub fn error(&self) -> MemoryLimitExceeded {
470 self.error
471 }
472
473 pub fn into_inner(self) -> T {
475 self.value
476 }
477
478 pub fn into_parts(self) -> (T, MemoryLimitExceeded) {
480 (self.value, self.error)
481 }
482}
483
484impl<T> std::fmt::Debug for AllocError<T> {
485 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
486 f.debug_struct("AllocError")
487 .field("error", &self.error)
488 .finish_non_exhaustive()
489 }
490}
491
492impl<T> std::fmt::Display for AllocError<T> {
493 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
494 self.error.fmt(f)
495 }
496}
497
498impl<T> std::error::Error for AllocError<T> {}
499
500impl<T> From<AllocError<T>> for MemoryLimitExceeded {
501 fn from(e: AllocError<T>) -> Self {
502 e.error
503 }
504}
505
506impl<T: Trace + 'static> GcPtr<T> {
507 #[cfg(not(feature = "no-gc"))]
512 pub fn new(value: T) -> Self {
513 gc_full::HEAP.alloc(value)
514 }
515
516 #[cfg(feature = "no-gc")]
517 pub fn new(value: T) -> Self {
518 alloc_ctx::alloc_in_ctx(value)
519 }
520
521 pub fn try_new(value: T) -> Result<Self, AllocError<T>> {
530 #[cfg(not(feature = "no-gc"))]
531 {
532 let bytes = std::mem::size_of::<GcBox<T>>() + value.gc_size_extra();
533 match governor::try_reserve_current(bytes) {
534 Ok(()) => Ok(Self::new(value)),
535 Err(error) => Err(AllocError { value, error }),
536 }
537 }
538 #[cfg(feature = "no-gc")]
539 {
540 alloc_ctx::try_alloc_in_ctx(value)
541 }
542 }
543
544 pub fn new_critical(value: T) -> Self {
548 let _section = governor::enter_critical();
549 Self::new(value)
550 }
551
552 #[inline]
555 fn raw(&self) -> *mut GcBox<T> {
556 #[cfg(not(feature = "no-gc"))]
557 {
558 (self.0.as_ptr() as usize & !REGION_PTR_TAG) as *mut GcBox<T>
559 }
560 #[cfg(feature = "no-gc")]
561 {
562 self.0.as_ptr()
563 }
564 }
565
566 #[cfg(not(feature = "no-gc"))]
569 #[inline]
570 pub fn is_region_alloc(&self) -> bool {
571 (self.0.as_ptr() as usize & REGION_PTR_TAG) != 0
572 }
573
574 #[cfg(not(feature = "no-gc"))]
581 #[inline]
582 pub(crate) unsafe fn from_region_raw(raw: *mut GcBox<T>) -> Self {
583 GcPtr(unsafe { NonNull::new_unchecked((raw as usize | REGION_PTR_TAG) as *mut GcBox<T>) })
584 }
585
586 pub fn get(&self) -> &T {
587 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
588 {
589 use gc_header::GC_MAGIC_ALIVE;
590 let header = unsafe { &(*self.raw()).header };
591 assert_eq!(
592 header.magic.get(),
593 GC_MAGIC_ALIVE,
594 "GcPtr::get() on freed object! magic={:#x}",
595 header.magic.get(),
596 );
597 }
598 unsafe { &(*self.raw()).value }
599 }
600
601 #[cfg(not(feature = "no-gc"))]
602 pub fn get_mut(&mut self) -> &mut T {
603 #[cfg(all(debug_assertions, not(feature = "no-gc")))]
604 {
605 use gc_header::GC_MAGIC_ALIVE;
606 let header = unsafe { &(*self.raw()).header };
607 assert_eq!(
608 header.magic.get(),
609 GC_MAGIC_ALIVE,
610 "GcPtr::get_mut() on freed object! magic={:#x}",
611 header.magic.get(),
612 );
613 }
614 unsafe { &mut (*self.raw()).value }
615 }
616
617 #[cfg(feature = "no-gc")]
619 pub fn get_mut(&mut self) -> GcMut<'_, T> {
620 unsafe { &(*self.raw()).identity }
621 .fetch_and(!nogc_box::OWNED, std::sync::atomic::Ordering::Relaxed);
622 GcMut { pointer: self }
623 }
624
625 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
626 a.0 == b.0
627 }
628
629 pub fn identity_eq(a: &Self, b: &Self) -> bool {
631 #[cfg(feature = "no-gc")]
632 {
633 unsafe {
634 (*a.raw())
635 .identity
636 .load(std::sync::atomic::Ordering::Relaxed)
637 & nogc_box::ID_MASK
638 == (*b.raw())
639 .identity
640 .load(std::sync::atomic::Ordering::Relaxed)
641 & nogc_box::ID_MASK
642 }
643 }
644 #[cfg(not(feature = "no-gc"))]
645 {
646 Self::ptr_eq(a, b)
647 }
648 }
649
650 pub fn new_isolated(value: T) -> Self {
653 #[cfg(feature = "no-gc")]
654 if !alloc_ctx::is_static_context() && !alloc_ctx::invocation_is_active() {
655 let mut scratch =
656 alloc_ctx::ScratchGuard::with_capacity(std::mem::size_of::<GcBox<T>>() + 32);
657 let pointer = Self::new(value);
658 let lease = region::RegionLease::for_address(pointer.raw() as usize);
659 region::AllocationOwner::for_address(pointer.raw() as usize)
660 .adopt(®ion::RegionLease::for_value(pointer.get()));
661 scratch.pop_for_return();
662 lease.protect_current();
663 return pointer;
664 }
665 Self::new(value)
666 }
667
668 #[cfg(feature = "no-gc")]
673 pub unsafe fn relocate_immutable(&self, value: T) -> Self {
674 let copy = Self::new(value);
675 unsafe {
676 (*copy.raw()).identity.store(
677 ((*self.raw())
678 .identity
679 .load(std::sync::atomic::Ordering::Relaxed)
680 & nogc_box::ID_MASK)
681 | nogc_box::RELOCATED,
682 std::sync::atomic::Ordering::Relaxed,
683 );
684 }
685 copy
686 }
687
688 #[cfg(feature = "no-gc")]
690 pub fn is_relocated_immutable(&self) -> bool {
691 unsafe {
692 (*self.raw())
693 .identity
694 .load(std::sync::atomic::Ordering::Relaxed)
695 & nogc_box::RELOCATED
696 != 0
697 }
698 }
699
700 #[cfg(feature = "no-gc")]
706 pub unsafe fn clone_immutable(&self) -> Self
707 where
708 T: Clone,
709 {
710 let copy = Self::new(self.get().clone());
711 unsafe {
712 (*copy.raw()).identity.store(
713 ((*self.raw())
714 .identity
715 .load(std::sync::atomic::Ordering::Relaxed)
716 & nogc_box::ID_MASK)
717 | nogc_box::RELOCATED,
718 std::sync::atomic::Ordering::Relaxed,
719 );
720 }
721 copy
722 }
723
724 #[cfg(feature = "no-gc")]
730 pub fn is_static_alloc(&self) -> bool {
731 alloc_ctx::invocation_is_active()
732 || static_arena::is_static_addr(self.0.as_ptr() as usize)
733 || region::is_permanent_address(self.0.as_ptr() as usize)
734 }
735}
736
737#[cfg(feature = "no-gc")]
739pub struct GcMut<'a, T: Trace + 'static> {
740 pointer: &'a mut GcPtr<T>,
741}
742#[cfg(feature = "no-gc")]
743impl<T: Trace + 'static> std::ops::Deref for GcMut<'_, T> {
744 type Target = T;
745 fn deref(&self) -> &T {
746 self.pointer.get()
747 }
748}
749#[cfg(feature = "no-gc")]
750impl<T: Trace + 'static> std::ops::DerefMut for GcMut<'_, T> {
751 fn deref_mut(&mut self) -> &mut T {
752 unsafe { &mut (*self.pointer.raw()).value }
753 }
754}
755#[cfg(feature = "no-gc")]
756impl<T: Trace + 'static> Drop for GcMut<'_, T> {
757 fn drop(&mut self) {
758 if !alloc_ctx::invocation_is_active() {
759 region::AllocationOwner::for_address(self.pointer.raw() as usize)
760 .adopt(®ion::RegionLease::for_value(self.pointer.get()));
761 }
762 }
763}
764
765impl<T: Trace + 'static> Clone for GcPtr<T> {
766 fn clone(&self) -> Self {
767 GcPtr(self.0)
768 }
769}
770
771impl<T: Trace + 'static + std::fmt::Debug> std::fmt::Debug for GcPtr<T> {
772 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
773 unsafe { (*self.raw()).value.fmt(f) }
774 }
775}
776
777impl<T: Trace + 'static> Drop for GcPtr<T> {
778 fn drop(&mut self) {}
779}
780
781pub struct StaticGcPtr<T: 'static>(NonNull<T>);
795
796unsafe impl<T: 'static> Send for StaticGcPtr<T> {}
800unsafe impl<T: 'static> Sync for StaticGcPtr<T> {}
801
802impl<T: 'static> StaticGcPtr<T> {
803 pub fn get(&self) -> &T {
805 unsafe { self.0.as_ref() }
807 }
808
809 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
812 a.0 == b.0
813 }
814}
815
816impl<T: 'static> Clone for StaticGcPtr<T> {
817 fn clone(&self) -> Self {
818 StaticGcPtr(self.0)
819 }
820}
821
822impl<T: 'static + std::fmt::Debug> std::fmt::Debug for StaticGcPtr<T> {
823 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
824 unsafe { self.0.as_ref().fmt(f) }
825 }
826}
827
828pub fn static_alloc<T: 'static>(value: T) -> StaticGcPtr<T> {
836 static_alloc_inner(value)
837}
838
839pub fn try_static_alloc<T: 'static>(value: T) -> Result<StaticGcPtr<T>, AllocError<T>> {
843 #[cfg(not(feature = "no-gc"))]
844 {
845 match governor::governor()
846 .try_charge_permanent(governor::MemoryClass::Static, std::mem::size_of::<T>())
847 {
848 Ok(()) => Ok(StaticGcPtr(NonNull::from(Box::leak(Box::new(value))))),
849 Err(error) => Err(AllocError { value, error }),
850 }
851 }
852 #[cfg(feature = "no-gc")]
853 {
854 static_arena::static_arena().try_alloc_val(value)
855 }
856}
857
858fn static_alloc_inner<T: 'static>(value: T) -> StaticGcPtr<T> {
859 #[cfg(feature = "no-gc")]
860 {
861 static_arena::static_alloc_val(value)
862 }
863 #[cfg(not(feature = "no-gc"))]
864 {
865 governor::governor()
866 .charge_permanent(governor::MemoryClass::Static, std::mem::size_of::<T>());
867 StaticGcPtr(NonNull::from(Box::leak(Box::new(value))))
868 }
869}
870
871#[cfg(not(feature = "no-gc"))]
876mod gc_full {
877 use std::cell::RefCell;
878 use std::ptr::NonNull;
879 use std::sync::atomic::{AtomicUsize, Ordering};
880 use std::sync::{Arc, Mutex};
881
882 use crate::config::GcConfig;
883 use crate::gc_header::GC_INITIAL_LIVES;
884 use crate::{GcBox, GcBoxHeader, GcPtr, MarkVisitor, Trace};
885
886 type RootTracer = Box<dyn Fn(&mut MarkVisitor)>;
887
888 pub struct GcHeap {
889 inner: Mutex<GcHeapInner>,
890 config: Mutex<Option<Arc<GcConfig>>>,
891 memory_in_use: AtomicUsize,
892 total_allocated_bytes: AtomicUsize,
893 root_tracers: Mutex<Vec<RootTracer>>,
894 gc_suppressed: std::sync::atomic::AtomicBool,
895 suppressed_threshold: AtomicUsize,
902 zero_yield_headroom: AtomicUsize,
904 }
905
906 struct GcHeapInner {
907 head: *mut GcBoxHeader,
908 count: usize,
909 total_allocated: usize,
910 total_freed: usize,
911 }
912
913 unsafe impl Send for GcHeapInner {}
914
915 impl GcHeapInner {
916 const fn new() -> Self {
917 Self {
918 head: std::ptr::null_mut(),
919 count: 0,
920 total_allocated: 0,
921 total_freed: 0,
922 }
923 }
924 }
925
926 unsafe impl Sync for GcHeap {}
927
928 impl Default for GcHeap {
929 fn default() -> Self {
930 Self::new()
931 }
932 }
933
934 pub(crate) fn parse_limit_mb(var: &str, value: Option<&str>, default: usize) -> usize {
939 match value {
940 Some(s) => match s.trim().parse::<usize>() {
941 Ok(mb) => mb.saturating_mul(1024 * 1024),
942 Err(_) => {
943 eprintln!(
944 "[gc] warning: ignoring invalid {var}={s:?} (expected a number of megabytes)"
945 );
946 default
947 }
948 },
949 None => default,
950 }
951 }
952
953 impl GcHeap {
954 pub const fn new() -> Self {
955 Self {
956 inner: Mutex::new(GcHeapInner::new()),
957 config: Mutex::new(None),
958 memory_in_use: AtomicUsize::new(0),
959 total_allocated_bytes: AtomicUsize::new(0),
960 root_tracers: Mutex::new(Vec::new()),
961 gc_suppressed: std::sync::atomic::AtomicBool::new(false),
962 suppressed_threshold: AtomicUsize::new(0),
963 zero_yield_headroom: AtomicUsize::new(0),
964 }
965 }
966
967 pub fn set_config(&self, config: Arc<GcConfig>) {
968 *self.config.lock().unwrap() = Some(config);
969 }
970
971 pub fn set_config_from_env(&self) {
972 #[cfg(not(target_arch = "wasm32"))]
973 let default_soft_limit: usize = (system_memory::total() / 3) as usize;
974 #[cfg(target_arch = "wasm32")]
975 let default_soft_limit: usize = 64 * 1024 * 1024;
976
977 let soft_limit_mb = parse_limit_mb(
978 "CLJRS_GC_SOFT_LIMIT_MB",
979 std::env::var("CLJRS_GC_SOFT_LIMIT_MB").ok().as_deref(),
980 default_soft_limit,
981 );
982 let mut hard_limit_mb = parse_limit_mb(
983 "CLJRS_GC_HARD_LIMIT_MB",
984 std::env::var("CLJRS_GC_HARD_LIMIT_MB").ok().as_deref(),
985 soft_limit_mb,
986 );
987 if hard_limit_mb < soft_limit_mb {
988 eprintln!(
989 "[gc] warning: CLJRS_GC_HARD_LIMIT_MB is below CLJRS_GC_SOFT_LIMIT_MB; using the soft limit for both"
990 );
991 hard_limit_mb = soft_limit_mb;
992 }
993 self.set_config(Arc::new(GcConfig::with_limits(
994 soft_limit_mb,
995 hard_limit_mb,
996 )));
997 }
998
999 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
1000 self.root_tracers.lock().unwrap().push(Box::new(tracer));
1001 }
1002
1003 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
1004 let tracers = self.root_tracers.lock().unwrap();
1005 for tracer in tracers.iter() {
1006 tracer(visitor);
1007 }
1008 }
1009
1010 pub fn memory_in_use(&self) -> usize {
1011 self.memory_in_use.load(Ordering::Relaxed)
1012 }
1013
1014 #[cfg(test)]
1015 pub fn set_memory_in_use(&self, bytes: usize) {
1016 self.memory_in_use.store(bytes, Ordering::Relaxed);
1017 }
1018
1019 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
1020 crate::cancellation::safepoint();
1021 let heap_extra = value.gc_size_extra();
1022 let gc_box = Box::new(GcBox {
1023 header: GcBoxHeader::new::<T>(heap_extra),
1024 value,
1025 });
1026 let obj_size = gc_box.header.size; let raw: *mut GcBox<T> = Box::into_raw(gc_box);
1028 {
1029 let mut inner = self.inner.lock().unwrap();
1030 unsafe {
1031 (*raw).header.next.set(inner.head);
1032 inner.head = raw as *mut GcBoxHeader;
1033 }
1034 inner.count += 1;
1035 inner.total_allocated += 1;
1036 }
1037 self.total_allocated_bytes
1038 .fetch_add(obj_size, Ordering::Relaxed);
1039 if crate::governor::with_current_account(|a| a.charge(obj_size)).is_none() {
1042 crate::stats::GC_STATS.record_gc_alloc(obj_size);
1043 }
1044 let current_usage =
1045 self.memory_in_use.fetch_add(obj_size, Ordering::Relaxed) + obj_size;
1046
1047 if let Some(config) = self.config.lock().unwrap().as_ref()
1048 && config.soft_limit_exceeded(current_usage)
1049 {
1050 if self.gc_suppressed.load(Ordering::Relaxed) {
1051 let threshold = self.suppressed_threshold.load(Ordering::Relaxed);
1055 if current_usage > threshold {
1056 self.gc_suppressed.store(false, Ordering::Relaxed);
1057 crate::cancellation::request_gc();
1058 }
1059 } else {
1060 crate::cancellation::request_gc();
1061 }
1062 }
1063
1064 register_alloc(raw as *mut GcBoxHeader);
1065 GcPtr(unsafe { NonNull::new_unchecked(raw) })
1066 }
1067
1068 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
1069 let _pause = crate::fiber::gc_pause();
1070 let pre_count = self.inner.lock().unwrap().count;
1071 let pre_memory = self.memory_in_use.load(Ordering::Relaxed);
1072 tracing::debug!(
1073 target: "gc",
1074 "starting collection: {} objects, ~{} bytes in use",
1075 pre_count,
1076 pre_memory
1077 );
1078 let mark_start = std::time::Instant::now();
1079 let mut visitor = MarkVisitor::new();
1080 trace_roots(&mut visitor);
1081 crate::mutator::trace_suspended(&mut visitor);
1082 crate::region::trace_active_regions(&mut visitor);
1088 crate::region::trace_retired_regions(&mut visitor);
1091 tracing::debug!(
1092 target: "gc",
1093 "starting drain with {} grey objects",
1094 visitor.grey.len()
1095 );
1096 visitor.drain();
1097 let mark_elapsed = mark_start.elapsed();
1098
1099 let sweep_start = std::time::Instant::now();
1100 let mut inner = self.inner.lock().unwrap();
1101 let mut live: Vec<*mut GcBoxHeader> = Vec::with_capacity(inner.count);
1102 let mut dead: Vec<*mut GcBoxHeader> = Vec::new();
1103 let mut freed_bytes: usize = 0;
1105 let mut pending_bytes: usize = 0;
1108 let mut current = inner.head;
1109 while !current.is_null() {
1110 let header = unsafe { &*current };
1111 let next = header.next.get();
1112 let lives = header.lives.get();
1113 let obj_size = header.size;
1114 if lives >= GC_INITIAL_LIVES {
1115 header.lives.set(GC_INITIAL_LIVES - 1);
1117 live.push(current);
1118 } else if lives > 0 {
1119 header.lives.set(lives - 1);
1121 if lives == 1 {
1122 pending_bytes += obj_size;
1123 }
1124 live.push(current);
1125 } else {
1126 freed_bytes += obj_size;
1128 dead.push(current);
1129 }
1130 current = next;
1131 }
1132 let freed_count = dead.len();
1133 for ptr in dead {
1134 let header = unsafe { &*ptr };
1135 unsafe { (header.drop_fn)(ptr) };
1136 inner.count -= 1;
1137 inner.total_freed += 1;
1138 }
1139 inner.head = std::ptr::null_mut();
1140 for ptr in live {
1141 let header = unsafe { &*ptr };
1142 header.next.set(inner.head);
1143 inner.head = ptr;
1144 }
1145 self.memory_in_use.fetch_sub(freed_bytes, Ordering::Relaxed);
1150 let sweep_elapsed = sweep_start.elapsed();
1151 let post_memory = self.memory_in_use.load(Ordering::Relaxed);
1152 crate::governor::with_current_account(|a| {
1153 a.release(freed_bytes);
1154 a.record_collection(crate::governor::CollectionReport {
1155 bytes_before: pre_memory,
1156 bytes_after: post_memory,
1157 bytes_returned: freed_bytes,
1158 bytes_pending: pending_bytes,
1159 duration: mark_elapsed + sweep_elapsed,
1160 });
1161 });
1162 crate::stats::GC_STATS.record_gc_pause(
1163 mark_elapsed + sweep_elapsed,
1164 freed_count as u64,
1165 freed_bytes as u64,
1166 );
1167 tracing::debug!(
1168 target: "gc",
1169 "collection complete: freed {} (~{} bytes), {} remaining (~{} bytes, ~{} pending), mark={:.2?} sweep={:.2?}",
1170 freed_count,
1171 freed_bytes,
1172 inner.count,
1173 post_memory,
1174 pending_bytes,
1175 mark_elapsed,
1176 sweep_elapsed
1177 );
1178 if freed_count == 0 && pending_bytes == 0 {
1179 let soft_limit = self
1194 .config
1195 .lock()
1196 .unwrap()
1197 .as_ref()
1198 .map(|c| c.soft_limit())
1199 .unwrap_or(64 * 1024 * 1024);
1200 let base_headroom = (soft_limit / 10).max(1);
1201 let max_headroom = (soft_limit / 4).max(base_headroom);
1202 let prev_headroom = self.zero_yield_headroom.load(Ordering::Relaxed);
1203 let headroom = if prev_headroom == 0 {
1204 base_headroom
1205 } else {
1206 prev_headroom.saturating_mul(2).min(max_headroom)
1207 };
1208 self.zero_yield_headroom.store(headroom, Ordering::Relaxed);
1209 self.suppressed_threshold
1210 .store(post_memory + headroom, Ordering::Relaxed);
1211 self.gc_suppressed.store(true, Ordering::Relaxed);
1212 } else {
1213 self.zero_yield_headroom.store(0, Ordering::Relaxed);
1215 self.gc_suppressed.store(false, Ordering::Relaxed);
1216 }
1217 }
1218
1219 pub fn count(&self) -> usize {
1220 self.inner.lock().unwrap().count
1221 }
1222 pub fn total_allocated(&self) -> usize {
1223 self.inner.lock().unwrap().total_allocated
1224 }
1225 pub fn total_freed(&self) -> usize {
1226 self.inner.lock().unwrap().total_freed
1227 }
1228
1229 pub fn collect_auto(&self) -> bool {
1230 tracing::debug!(target: "gc", "automatic collection requested");
1231 let Some(_stw_guard) = crate::cancellation::begin_stw() else {
1232 tracing::debug!(target: "gc", "automatic collection skipped");
1233 return false;
1234 };
1235 self.collect(|visitor| self.trace_registered_roots(visitor));
1236 true
1237 }
1238 }
1239
1240 thread_local! {
1241 static ISOLATE_HEAP: GcHeap = const { GcHeap::new() };
1242 }
1243
1244 pub struct HeapProxy;
1250
1251 impl HeapProxy {
1252 pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
1253 ISOLATE_HEAP.with(|h| h.alloc(value))
1254 }
1255
1256 pub fn set_config(&self, config: Arc<GcConfig>) {
1257 ISOLATE_HEAP.with(|h| h.set_config(config));
1258 }
1259
1260 pub fn set_config_from_env(&self) {
1261 ISOLATE_HEAP.with(|h| h.set_config_from_env());
1262 }
1263
1264 pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
1265 ISOLATE_HEAP.with(|h| h.register_root_tracer(tracer));
1266 }
1267
1268 pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
1269 ISOLATE_HEAP.with(|h| h.trace_registered_roots(visitor));
1270 }
1271
1272 pub fn memory_in_use(&self) -> usize {
1273 ISOLATE_HEAP.with(|h| h.memory_in_use())
1274 }
1275
1276 pub fn count(&self) -> usize {
1277 ISOLATE_HEAP.with(|h| h.count())
1278 }
1279
1280 pub fn total_allocated(&self) -> usize {
1281 ISOLATE_HEAP.with(|h| h.total_allocated())
1282 }
1283
1284 pub fn total_freed(&self) -> usize {
1285 ISOLATE_HEAP.with(|h| h.total_freed())
1286 }
1287
1288 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
1289 ISOLATE_HEAP.with(|h| h.collect(trace_roots));
1290 }
1291
1292 pub fn collect_auto(&self) -> bool {
1293 ISOLATE_HEAP.with(|h| h.collect_auto())
1294 }
1295
1296 #[cfg(test)]
1297 pub fn set_memory_in_use(&self, bytes: usize) {
1298 ISOLATE_HEAP.with(|h| h.set_memory_in_use(bytes));
1299 }
1300 }
1301
1302 unsafe impl Sync for HeapProxy {}
1305
1306 pub static HEAP: HeapProxy = HeapProxy;
1307
1308 thread_local! {
1309 pub(crate) static ALLOC_ROOTS: RefCell<Vec<*mut GcBoxHeader>> = const { RefCell::new(Vec::new()) };
1310 }
1311 crate::mutator_slot!("cljrs-gc/ALLOC_ROOTS", refcell ALLOC_ROOTS: Vec<*mut GcBoxHeader>, trace trace_saved_alloc_roots);
1312
1313 fn trace_saved_alloc_roots(roots: &Vec<*mut GcBoxHeader>, visitor: &mut MarkVisitor) {
1314 for &header in roots {
1315 unsafe { visitor.mark_header(header) };
1317 }
1318 }
1319
1320 pub struct AllocRootGuard {
1321 saved_len: usize,
1322 }
1323
1324 impl AllocRootGuard {
1325 pub fn release(&self) {
1329 ALLOC_ROOTS.with(|roots| roots.borrow_mut().truncate(self.saved_len));
1330 }
1331 }
1332
1333 impl Drop for AllocRootGuard {
1334 fn drop(&mut self) {
1335 self.release();
1336 }
1337 }
1338
1339 pub fn push_alloc_frame() -> AllocRootGuard {
1340 let saved_len = ALLOC_ROOTS.with(|roots| roots.borrow().len());
1341 AllocRootGuard { saved_len }
1342 }
1343
1344 fn register_alloc(header: *mut GcBoxHeader) {
1345 ALLOC_ROOTS.with(|roots| roots.borrow_mut().push(header));
1346 }
1347
1348 pub fn trace_thread_alloc_roots(visitor: &mut MarkVisitor) {
1349 ALLOC_ROOTS.with(|roots| {
1350 let roots = roots.borrow();
1351 for &header in roots.iter() {
1352 unsafe { visitor.mark_header(header) };
1353 }
1354 });
1355 }
1356}
1357
1358#[cfg(feature = "no-gc")]
1363mod nogc_stubs {
1364 use crate::MarkVisitor;
1365 use std::sync::Arc;
1366
1367 #[derive(Debug, Clone)]
1368 pub struct GcConfig;
1369 impl GcConfig {
1370 pub fn new() -> Self {
1371 Self
1372 }
1373 pub fn with_hard_limit(_: usize) -> Self {
1374 Self
1375 }
1376 pub fn with_limits(_: usize, _: usize) -> Self {
1377 Self
1378 }
1379 pub fn with_soft_limit(_: usize) -> Self {
1380 Self
1381 }
1382 pub fn try_with_limits(
1383 soft_limit: usize,
1384 hard_limit: usize,
1385 ) -> Result<Self, crate::governor::MemoryConfigError> {
1386 crate::governor::MemoryConfig::with_limits(soft_limit, hard_limit).map(|_| Self)
1387 }
1388 pub fn validate(&self) -> Result<(), crate::governor::MemoryConfigError> {
1389 Ok(())
1390 }
1391 }
1392 impl Default for GcConfig {
1393 fn default() -> Self {
1394 Self::new()
1395 }
1396 }
1397
1398 pub struct GcHeap;
1399 impl Default for GcHeap {
1400 fn default() -> Self {
1401 Self::new()
1402 }
1403 }
1404 impl GcHeap {
1405 pub const fn new() -> Self {
1406 Self
1407 }
1408 pub fn set_config(&self, _: Arc<GcConfig>) {}
1409 pub fn set_config_from_env(&self) {}
1414 pub fn register_root_tracer(&self, _: impl Fn(&mut MarkVisitor) + 'static) {}
1415 pub fn trace_registered_roots(&self, _: &mut MarkVisitor) {}
1416 pub fn memory_in_use(&self) -> usize {
1417 0
1418 }
1419 pub fn count(&self) -> usize {
1420 0
1421 }
1422 pub fn total_allocated(&self) -> usize {
1423 0
1424 }
1425 pub fn total_freed(&self) -> usize {
1426 0
1427 }
1428 pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, _: F) {}
1429 pub fn collect_auto(&self) -> bool {
1430 false
1431 }
1432 }
1433 unsafe impl Sync for GcHeap {}
1434 pub static HEAP: GcHeap = GcHeap::new();
1435
1436 pub struct MutatorGuard;
1437 impl Drop for MutatorGuard {
1438 fn drop(&mut self) {}
1439 }
1440 pub struct StwGuard;
1441 impl Drop for StwGuard {
1442 fn drop(&mut self) {}
1443 }
1444 pub struct GcParked;
1445
1446 pub struct GcCancellationStub;
1447 impl GcCancellationStub {
1448 pub const fn new() -> Self {
1449 Self
1450 }
1451 pub fn in_progress(&self) -> bool {
1452 false
1453 }
1454 }
1455 pub static CONFIG_CANCELLATION: GcCancellationStub = GcCancellationStub::new();
1456
1457 pub fn safepoint() {}
1458 pub fn gc_requested() -> bool {
1459 false
1460 }
1461 pub fn take_gc_request() -> bool {
1462 false
1463 }
1464 pub fn begin_stw() -> Option<StwGuard> {
1465 None
1466 }
1467 pub fn register_mutator() -> MutatorGuard {
1468 MutatorGuard
1469 }
1470 pub fn registered_threads() -> usize {
1471 0
1472 }
1473 pub fn request_gc() {}
1474 pub fn check_cancellation() -> Result<(), GcParked> {
1475 Ok(())
1476 }
1477 pub fn park_thread() {}
1478 pub fn unpark_thread() {}
1479 pub fn wait_for_threads_to_park() {}
1480
1481 pub struct AllocRootGuard;
1482 impl AllocRootGuard {
1483 pub fn release(&self) {}
1484 }
1485 impl Drop for AllocRootGuard {
1486 fn drop(&mut self) {}
1487 }
1488 pub fn push_alloc_frame() -> AllocRootGuard {
1489 AllocRootGuard
1490 }
1491}
1492
1493#[cfg(all(test, not(feature = "no-gc")))]
1498mod tests {
1499 use super::*;
1500 use std::sync::{Arc, Mutex};
1501
1502 #[derive(Debug)]
1503 #[allow(dead_code)]
1504 struct Tracked {
1505 value: i32,
1506 dropped: Arc<Mutex<bool>>,
1507 }
1508 impl Drop for Tracked {
1509 fn drop(&mut self) {
1510 *self.dropped.lock().unwrap() = true;
1511 }
1512 }
1513 impl Trace for Tracked {
1514 fn trace(&self, _: &mut MarkVisitor) {}
1515 }
1516
1517 #[derive(Debug)]
1518 #[allow(dead_code)]
1519 struct Parent {
1520 child: GcPtr<Tracked>,
1521 }
1522 impl Trace for Parent {
1523 fn trace(&self, visitor: &mut MarkVisitor) {
1524 visitor.visit(&self.child);
1525 }
1526 }
1527
1528 fn fresh_heap() -> gc_full::GcHeap {
1529 let heap = gc_full::GcHeap::new();
1530 heap.set_config(Arc::new(GcConfig::with_limits(10000, 50000)));
1531 heap
1532 }
1533
1534 #[test]
1535 fn alloc_and_get() {
1536 let heap = fresh_heap();
1537 let p = heap.alloc(42i64);
1538 assert_eq!(*p.get(), 42);
1539 assert_eq!(heap.count(), 1);
1540 }
1541
1542 #[test]
1543 fn clone_is_same_ptr() {
1544 let heap = fresh_heap();
1545 let p = heap.alloc(99i64);
1546 let q = p.clone();
1547 assert!(GcPtr::ptr_eq(&p, &q));
1548 }
1549
1550 #[test]
1551 fn parse_limit_mb_handles_valid_unset_and_malformed() {
1552 assert_eq!(gc_full::parse_limit_mb("X", Some("4"), 7), 4 * 1024 * 1024);
1554 assert_eq!(
1556 gc_full::parse_limit_mb("X", Some(" 4 "), 7),
1557 4 * 1024 * 1024
1558 );
1559 assert_eq!(gc_full::parse_limit_mb("X", None, 7), 7);
1561 assert_eq!(gc_full::parse_limit_mb("X", Some("foo"), 7), 7);
1563 assert_eq!(gc_full::parse_limit_mb("X", Some(""), 7), 7);
1564 assert_eq!(gc_full::parse_limit_mb("X", Some("-1"), 7), 7);
1565 assert_eq!(
1567 gc_full::parse_limit_mb("X", Some(&usize::MAX.to_string()), 7),
1568 usize::MAX
1569 );
1570 }
1571
1572 #[test]
1573 fn collect_keeps_reachable() {
1574 let heap = fresh_heap();
1575 let dropped = Arc::new(Mutex::new(false));
1576 let p = heap.alloc(Tracked {
1577 value: 2,
1578 dropped: dropped.clone(),
1579 });
1580 heap.collect(|vis| vis.visit(&p));
1581 assert_eq!(heap.count(), 1);
1582 assert!(!*dropped.lock().unwrap());
1583 }
1584
1585 #[test]
1586 fn b1_two_isolates_independent_heaps() {
1587 use std::sync::{Arc, Barrier};
1588 let barrier = Arc::new(Barrier::new(2));
1591 let b1 = barrier.clone();
1592 let h1 = std::thread::Builder::new()
1593 .name("isolate-1".into())
1594 .spawn(move || {
1595 let _mutator = crate::register_mutator();
1596 let _ptrs: Vec<_> = (0_i64..100)
1598 .map(|i| crate::gc_full::HEAP.alloc(i))
1599 .collect();
1600 b1.wait(); assert_eq!(
1603 crate::gc_full::HEAP.count(),
1604 100,
1605 "isolate-1 heap count should be 100"
1606 );
1607 })
1608 .unwrap();
1609
1610 let b2 = barrier.clone();
1611 let h2 = std::thread::Builder::new()
1612 .name("isolate-2".into())
1613 .spawn(move || {
1614 let _mutator = crate::register_mutator();
1615 let _ptrs: Vec<_> = (0_i64..200)
1617 .map(|i| crate::gc_full::HEAP.alloc(i))
1618 .collect();
1619 b2.wait();
1620 assert_eq!(
1622 crate::gc_full::HEAP.count(),
1623 200,
1624 "isolate-2 heap count should be 200"
1625 );
1626 })
1627 .unwrap();
1628
1629 h1.join().expect("isolate-1 panicked");
1630 h2.join().expect("isolate-2 panicked");
1631 }
1632
1633 #[test]
1634 fn b1_two_isolates_gc_independently() {
1635 let h1 = std::thread::Builder::new()
1637 .name("gc-isolate-1".into())
1638 .spawn(|| {
1639 let _mutator = crate::register_mutator();
1640 let heap = &crate::gc_full::HEAP;
1641 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1642 for _ in 0..5 {
1644 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1645 heap.collect(|_| {});
1647 heap.collect(|_| {}); }
1649 })
1653 .unwrap();
1654
1655 let h2 = std::thread::Builder::new()
1656 .name("gc-isolate-2".into())
1657 .spawn(|| {
1658 let _mutator = crate::register_mutator();
1659 let heap = &crate::gc_full::HEAP;
1660 heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1661 for _ in 0..5 {
1662 let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1663 heap.collect(|_| {});
1664 heap.collect(|_| {});
1665 }
1666 })
1667 .unwrap();
1668
1669 h1.join().expect("gc-isolate-1 panicked");
1670 h2.join().expect("gc-isolate-2 panicked");
1671 }
1672}
1673
1674#[cfg(all(test, feature = "no-gc"))]
1675mod nogc_tests {
1676 use super::*;
1677 use alloc_ctx::{ScratchGuard, StaticCtxGuard};
1678
1679 #[test]
1680 fn cached_parent_survives_a_child_mutation_into_another_scope() {
1681 struct Node {
1682 child: Option<GcPtr<Node>>,
1683 label: String,
1684 }
1685 impl Trace for Node {
1686 fn immutable_children(&self) -> bool {
1687 true
1688 }
1689 fn trace(&self, visitor: &mut MarkVisitor) {
1690 if let Some(child) = &self.child {
1691 visitor.visit(child);
1692 }
1693 }
1694 }
1695 struct Root(GcPtr<Node>);
1696 impl Trace for Root {
1697 fn trace(&self, visitor: &mut MarkVisitor) {
1698 visitor.visit(&self.0);
1699 }
1700 }
1701 let (parent, lease) = {
1702 let _source = ScratchGuard::new();
1703 let mut child = GcPtr::new(Node {
1704 child: None,
1705 label: "middle".into(),
1706 });
1707 let parent = GcPtr::new(Node {
1708 child: Some(child.clone()),
1709 label: "parent".into(),
1710 });
1711 let lease = region::RegionLease::for_value(&Root(parent.clone()));
1712 {
1713 let _new_source = ScratchGuard::new();
1714 child.get_mut().child = Some(GcPtr::new(Node {
1715 child: None,
1716 label: "new".into(),
1717 }));
1718 }
1719 let cached = region::RegionLease::for_value(&Root(parent.clone()));
1722 drop(lease);
1723 (parent, cached)
1724 };
1725 assert_eq!(
1726 parent
1727 .get()
1728 .child
1729 .as_ref()
1730 .unwrap()
1731 .get()
1732 .child
1733 .as_ref()
1734 .unwrap()
1735 .get()
1736 .label,
1737 "new"
1738 );
1739 drop(lease);
1740 }
1741
1742 #[test]
1743 fn invocation_caches_follow_their_mutator_and_expire_before_reuse() {
1744 let first = alloc_ctx::InvocationGuard::new(4096);
1745 alloc_ctx::invocation_cache_insert("test", "same".into(), 1);
1746 let mut other = mutator::MutatorState::fresh();
1747 other.swap_with_current();
1748 {
1749 let _second = alloc_ctx::InvocationGuard::new(4096);
1750 assert!(alloc_ctx::invocation_cache_get("test", "same").is_none());
1751 alloc_ctx::invocation_cache_insert("test", "same".into(), 2);
1752 }
1753 other.swap_with_current();
1754 assert_eq!(alloc_ctx::invocation_cache_get("test", "same"), Some(1));
1755 drop(first);
1756 let _next = alloc_ctx::InvocationGuard::new(4096);
1757 assert!(alloc_ctx::invocation_cache_get("test", "same").is_none());
1758 }
1759
1760 #[test]
1761 fn immutable_flags_do_not_grow_boxes_or_change_identity() {
1762 assert_eq!(std::mem::size_of::<GcBox<i64>>(), 16);
1763 let _source = ScratchGuard::new();
1764 let original = GcPtr::new(String::from("compact"));
1765 let copy = unsafe { original.clone_immutable() };
1766 assert!(copy.is_relocated_immutable());
1767 assert!(!original.is_relocated_immutable());
1768 assert!(GcPtr::identity_eq(&original, ©));
1769 }
1770
1771 #[test]
1772 fn alloc_in_static_context() {
1773 let _g = StaticCtxGuard::new();
1774 let p = GcPtr::new(42i64);
1775 assert_eq!(*p.get(), 42);
1776 }
1777
1778 #[test]
1779 fn alloc_in_scratch_region() {
1780 let mut scratch = ScratchGuard::new();
1781 let p = GcPtr::new(99i64);
1782 assert_eq!(*p.get(), 99);
1783 scratch.pop_for_return();
1784 assert_eq!(*p.get(), 99);
1785 }
1787
1788 #[test]
1789 fn ptr_eq() {
1790 let _g = StaticCtxGuard::new();
1791 let p = GcPtr::new(1i64);
1792 let q = p.clone();
1793 assert!(GcPtr::ptr_eq(&p, &q));
1794 }
1795}