Skip to main content

cljrs_gc/
lib.rs

1//! Garbage collector (default) or region-based allocator (`no-gc` feature) for clojurust.
2
3#![allow(clippy::missing_safety_doc)]
4#![allow(private_interfaces)]
5
6use std::ptr::NonNull;
7
8pub mod governor;
9pub mod region;
10pub mod stats;
11
12#[cfg(not(feature = "no-gc"))]
13pub mod cancellation;
14#[cfg(not(feature = "no-gc"))]
15pub mod config;
16
17#[cfg(feature = "no-gc")]
18pub mod alloc_ctx;
19#[cfg(feature = "no-gc")]
20pub mod static_arena;
21
22pub use governor::{
23    IsolateAccount, IsolateControl, MemoryCharge, MemoryClass, MemoryConfig, MemoryConfigError,
24    MemoryLimitExceeded, MemorySnapshot, PressureLevel, ProcessMemoryGovernor, governor,
25    register_current_isolate,
26};
27pub use stats::{
28    CLJRS_GC_STATS_ENV, GC_STATS, GcStats, GcStatsSnapshot, dump_stats_from_env,
29    report as stats_report,
30};
31
32// ── Re-exports from active implementation ─────────────────────────────────────
33
34#[cfg(not(feature = "no-gc"))]
35pub use cancellation::{
36    MutatorGuard, StwGuard, begin_stw, check_cancellation, gc_requested, park_thread,
37    register_mutator, registered_threads, request_gc, safepoint, take_gc_request, unpark_thread,
38    wait_for_threads_to_park,
39};
40#[cfg(not(feature = "no-gc"))]
41pub use config::{GC_CANCELLATION as CONFIG_CANCELLATION, GcConfig, GcParked};
42
43#[cfg(not(feature = "no-gc"))]
44pub use gc_full::{
45    AllocRootGuard, GcHeap, HEAP, HeapProxy, push_alloc_frame, trace_thread_alloc_roots,
46};
47#[cfg(feature = "no-gc")]
48pub use nogc_stubs::{
49    AllocRootGuard, CONFIG_CANCELLATION, GcConfig, GcHeap, GcParked, HEAP, MutatorGuard, StwGuard,
50    begin_stw, check_cancellation, gc_requested, park_thread, push_alloc_frame, register_mutator,
51    registered_threads, request_gc, safepoint, take_gc_request, unpark_thread,
52    wait_for_threads_to_park,
53};
54
55/// Return `true` if `addr` was allocated by the global `StaticArena`.
56///
57/// Available only in `no-gc` debug builds.  Downstream crates (`cljrs-value`)
58/// use this to implement write-site provenance assertions.
59#[cfg(all(feature = "no-gc", debug_assertions))]
60pub fn is_static_addr(addr: usize) -> bool {
61    static_arena::is_static_addr(addr)
62}
63
64// ── Trace trait ───────────────────────────────────────────────────────────────
65
66/// Implemented by every type that can be stored behind a [`GcPtr`].
67///
68/// The `gc_size_extra` method accounts for heap bytes owned by the value that
69/// are NOT captured by `size_of::<GcBox<T>>()` (e.g. `Vec` buffers, `String`
70/// capacity, `Form` AST trees stored inline).  The default returns 0, which is
71/// correct for primitives and types with no out-of-line heap.
72///
73/// Rules for implementors of `gc_size_extra`:
74/// - Count only bytes THIS value owns and will free when dropped.
75/// - Do NOT cross `GcPtr` boundaries — each pointed-to box is counted
76///   separately when it is allocated.
77pub trait Trace {
78    fn trace(&self, visitor: &mut MarkVisitor);
79
80    fn gc_size_extra(&self) -> usize {
81        0
82    }
83}
84
85// ── Leaf Trace impls ──────────────────────────────────────────────────────────
86
87impl Trace for String {
88    fn trace(&self, _: &mut MarkVisitor) {}
89
90    fn gc_size_extra(&self) -> usize {
91        self.capacity()
92    }
93}
94impl Trace for i64 {
95    fn trace(&self, _: &mut MarkVisitor) {}
96}
97impl Trace for f64 {
98    fn trace(&self, _: &mut MarkVisitor) {}
99}
100impl Trace for bool {
101    fn trace(&self, _: &mut MarkVisitor) {}
102}
103impl Trace for num_bigint::BigInt {
104    fn trace(&self, _: &mut MarkVisitor) {}
105}
106impl Trace for bigdecimal::BigDecimal {
107    fn trace(&self, _: &mut MarkVisitor) {}
108}
109impl Trace for num_rational::Ratio<num_bigint::BigInt> {
110    fn trace(&self, _: &mut MarkVisitor) {}
111}
112macro_rules! impl_trace_prim_array {
113    ($t:ty) => {
114        impl Trace for std::sync::Mutex<Vec<$t>> {
115            fn trace(&self, _: &mut MarkVisitor) {}
116            fn gc_size_extra(&self) -> usize {
117                self.lock().unwrap().capacity() * std::mem::size_of::<$t>()
118            }
119        }
120    };
121}
122impl_trace_prim_array!(i32);
123impl_trace_prim_array!(i64);
124impl_trace_prim_array!(i16);
125impl_trace_prim_array!(i8);
126impl_trace_prim_array!(char);
127impl_trace_prim_array!(f64);
128impl_trace_prim_array!(f32);
129impl_trace_prim_array!(bool);
130
131// ── GcVisitor ─────────────────────────────────────────────────────────────────
132
133pub trait GcVisitor {
134    fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>);
135}
136
137// =============================================================================
138// GC build: GcBox with header, MarkVisitor with grey stack
139// =============================================================================
140
141#[cfg(not(feature = "no-gc"))]
142pub use self::gc_header::{GcBox, GcBoxHeader};
143
144#[cfg(not(feature = "no-gc"))]
145mod gc_header {
146    use crate::{MarkVisitor, Trace};
147    use std::cell::Cell;
148
149    // Objects start at lives = GC_INITIAL_LIVES - 1.  The mark phase sets
150    // lives = GC_INITIAL_LIVES for reachable objects; sweep frees objects
151    // whose lives reach 0.  A value of 2 gives exactly one cycle of grace:
152    // enough to cover the window between an alloc frame dropping and the
153    // next GC safepoint (where VALUE_ROOTS or a new alloc frame re-roots it).
154    // 10 was chosen conservatively but keeps 9× more garbage in RAM than
155    // necessary, worsening OOM pressure under test suites with many forms.
156    pub(crate) const GC_INITIAL_LIVES: u8 = 2;
157
158    #[cfg(debug_assertions)]
159    pub(crate) const GC_MAGIC_ALIVE: u64 = 0xCAFE_BABE_DEAD_BEEF;
160    #[cfg(debug_assertions)]
161    pub(crate) const GC_MAGIC_FREED: u64 = 0xDEAD_DEAD_DEAD_DEAD;
162
163    #[repr(C)]
164    pub struct GcBoxHeader {
165        #[cfg(debug_assertions)]
166        pub(crate) magic: Cell<u64>,
167        /// Exact size of the GcBox<T> allocation in bytes.
168        pub(crate) size: usize,
169        pub(crate) lives: Cell<u8>,
170        pub(crate) next: Cell<*mut GcBoxHeader>,
171        pub(crate) trace_fn: unsafe fn(*const GcBoxHeader, &mut MarkVisitor),
172        pub(crate) drop_fn: unsafe fn(*mut GcBoxHeader),
173    }
174
175    impl GcBoxHeader {
176        pub(crate) fn new<T: Trace + 'static>(heap_extra: usize) -> Self {
177            Self {
178                #[cfg(debug_assertions)]
179                magic: Cell::new(GC_MAGIC_ALIVE),
180                size: std::mem::size_of::<GcBox<T>>() + heap_extra,
181                lives: Cell::new(GC_INITIAL_LIVES - 1),
182                next: Cell::new(std::ptr::null_mut()),
183                trace_fn: trace_gc_box::<T>,
184                drop_fn: drop_gc_box::<T>,
185            }
186        }
187    }
188
189    unsafe impl Send for GcBoxHeader {}
190    unsafe impl Sync for GcBoxHeader {}
191
192    #[repr(C)]
193    pub struct GcBox<T: Trace + 'static> {
194        pub(crate) header: GcBoxHeader,
195        pub value: T,
196    }
197
198    pub(crate) unsafe fn trace_gc_box<T: Trace + 'static>(
199        header: *const GcBoxHeader,
200        visitor: &mut MarkVisitor,
201    ) {
202        unsafe {
203            let gc_box = header as *const GcBox<T>;
204            (*gc_box).value.trace(visitor);
205        }
206    }
207
208    pub(crate) unsafe fn drop_gc_box<T: Trace + 'static>(header: *mut GcBoxHeader) {
209        unsafe {
210            #[cfg(debug_assertions)]
211            {
212                (*header).magic.set(GC_MAGIC_FREED);
213            }
214            let gc_box = header as *mut GcBox<T>;
215            drop(Box::from_raw(gc_box));
216        }
217    }
218}
219
220// =============================================================================
221// no-gc build: GcBox without header
222// =============================================================================
223
224#[cfg(feature = "no-gc")]
225pub use self::nogc_box::GcBox;
226
227#[cfg(feature = "no-gc")]
228mod nogc_box {
229    use crate::Trace;
230
231    pub struct GcBox<T: Trace + 'static> {
232        pub value: T,
233    }
234}
235
236// =============================================================================
237// MarkVisitor: full under GC, stub under no-gc
238// =============================================================================
239
240#[cfg(not(feature = "no-gc"))]
241#[derive(Default)]
242pub struct MarkVisitor {
243    pub(crate) grey: Vec<*mut GcBoxHeader>,
244}
245
246#[cfg(feature = "no-gc")]
247pub struct MarkVisitor;
248
249#[cfg(not(feature = "no-gc"))]
250impl MarkVisitor {
251    pub fn new() -> Self {
252        Self::default()
253    }
254
255    pub fn grey_len(&self) -> usize {
256        self.grey.len()
257    }
258
259    pub unsafe fn mark_header(&mut self, header: *mut GcBoxHeader) {
260        use gc_header::GC_INITIAL_LIVES;
261        let h = unsafe { &*header };
262        if h.lives.get() < GC_INITIAL_LIVES {
263            h.lives.set(GC_INITIAL_LIVES);
264            self.grey.push(header);
265        }
266    }
267
268    pub(crate) fn drain(&mut self) {
269        let mut visited = 0usize;
270        while let Some(header) = self.grey.pop() {
271            visited += 1;
272            let h = unsafe { &*header };
273            unsafe { (h.trace_fn)(header as *const GcBoxHeader, self) };
274        }
275        tracing::debug!(target: "gc", "drain visited {} objects", visited);
276    }
277}
278
279#[cfg(not(feature = "no-gc"))]
280impl GcVisitor for MarkVisitor {
281    fn visit<T: Trace + 'static>(&mut self, ptr: &GcPtr<T>) {
282        use gc_header::GC_INITIAL_LIVES;
283        // Region-allocated objects are not on the GC heap; their lifetime is
284        // bounded by the enclosing `Region`, not the collector.  Never
285        // dereference one here: once the region's scope ends its memory is
286        // freed/reused, so the header would be garbage and we'd follow a
287        // dangling `trace_fn`.  Live regions are traced as roots separately
288        // (see `region::trace_active_regions`), so a region's heap-allocated
289        // children are still kept alive.
290        if ptr.is_region_alloc() {
291            return;
292        }
293        let raw = ptr.raw();
294        let header = unsafe { &(*raw).header };
295        if header.lives.get() < GC_INITIAL_LIVES {
296            header.lives.set(GC_INITIAL_LIVES);
297            self.grey.push(raw as *mut GcBoxHeader);
298        }
299    }
300}
301
302#[cfg(feature = "no-gc")]
303impl MarkVisitor {
304    pub fn grey_len(&self) -> usize {
305        0
306    }
307    pub unsafe fn mark_header(&mut self, _: *mut u8) {}
308}
309
310#[cfg(feature = "no-gc")]
311impl GcVisitor for MarkVisitor {
312    fn visit<T: Trace + 'static>(&mut self, _: &GcPtr<T>) {}
313}
314
315// =============================================================================
316// GcPtr — always present
317// =============================================================================
318
319pub struct GcPtr<T: Trace + 'static>(NonNull<GcBox<T>>);
320
321/// Low pointer bit reserved to mark region-allocated `GcPtr`s in GC builds.
322///
323/// `GcBox<T>` (a `GcBoxHeader` followed by the value) is always ≥8-byte
324/// aligned in GC builds, so bit 0 is free.  Region-allocated pointers set it;
325/// GC-heap pointers leave it clear.  This lets the mark phase distinguish a
326/// region object from a heap object **without dereferencing it** — essential,
327/// because a region whose scope has ended leaves dangling pointers whose
328/// headers point at freed (or reused) memory.
329#[cfg(not(feature = "no-gc"))]
330pub(crate) const REGION_PTR_TAG: usize = 1;
331
332impl<T: Trace + 'static> GcPtr<T> {
333    #[cfg(not(feature = "no-gc"))]
334    pub fn new(value: T) -> Self {
335        gc_full::HEAP.alloc(value)
336    }
337
338    #[cfg(feature = "no-gc")]
339    pub fn new(value: T) -> Self {
340        alloc_ctx::alloc_in_ctx(value)
341    }
342
343    /// The untagged `GcBox<T>` address.  In GC builds this masks off the
344    /// region-provenance tag bit; in no-gc builds pointers are never tagged.
345    #[inline]
346    fn raw(&self) -> *mut GcBox<T> {
347        #[cfg(not(feature = "no-gc"))]
348        {
349            (self.0.as_ptr() as usize & !REGION_PTR_TAG) as *mut GcBox<T>
350        }
351        #[cfg(feature = "no-gc")]
352        {
353            self.0.as_ptr()
354        }
355    }
356
357    /// `true` if this pointer was bump-allocated in a [`region::Region`]
358    /// rather than the GC heap.  Region objects are not GC-managed.
359    #[cfg(not(feature = "no-gc"))]
360    #[inline]
361    pub fn is_region_alloc(&self) -> bool {
362        (self.0.as_ptr() as usize & REGION_PTR_TAG) != 0
363    }
364
365    /// Construct a region-tagged pointer from a raw `GcBox<T>` allocated in a
366    /// bump region.
367    ///
368    /// # Safety
369    /// `raw` must be a valid, non-null, ≥8-aligned `GcBox<T>` whose header was
370    /// initialised by [`region::Region::alloc`].
371    #[cfg(not(feature = "no-gc"))]
372    #[inline]
373    pub(crate) unsafe fn from_region_raw(raw: *mut GcBox<T>) -> Self {
374        GcPtr(unsafe { NonNull::new_unchecked((raw as usize | REGION_PTR_TAG) as *mut GcBox<T>) })
375    }
376
377    pub fn get(&self) -> &T {
378        #[cfg(all(debug_assertions, not(feature = "no-gc")))]
379        {
380            use gc_header::GC_MAGIC_ALIVE;
381            let header = unsafe { &(*self.raw()).header };
382            assert_eq!(
383                header.magic.get(),
384                GC_MAGIC_ALIVE,
385                "GcPtr::get() on freed object! magic={:#x}",
386                header.magic.get(),
387            );
388        }
389        unsafe { &(*self.raw()).value }
390    }
391
392    pub fn get_mut(&mut self) -> &mut T {
393        #[cfg(all(debug_assertions, not(feature = "no-gc")))]
394        {
395            use gc_header::GC_MAGIC_ALIVE;
396            let header = unsafe { &(*self.raw()).header };
397            assert_eq!(
398                header.magic.get(),
399                GC_MAGIC_ALIVE,
400                "GcPtr::get_mut() on freed object! magic={:#x}",
401                header.magic.get(),
402            );
403        }
404        unsafe { &mut (*self.raw()).value }
405    }
406
407    pub fn ptr_eq(a: &Self, b: &Self) -> bool {
408        a.0 == b.0
409    }
410
411    /// Return `true` if this pointer was allocated by the global `StaticArena`.
412    ///
413    /// Only meaningful (and only compiled) in `no-gc` debug builds.  Used by
414    /// write-site assertions in `Atom::reset` / `Var::bind` to catch
415    /// region-local values being stored in program-lifetime containers.
416    #[cfg(all(feature = "no-gc", debug_assertions))]
417    pub fn is_static_alloc(&self) -> bool {
418        alloc_ctx::invocation_is_active() || static_arena::is_static_addr(self.0.as_ptr() as usize)
419    }
420}
421
422impl<T: Trace + 'static> Clone for GcPtr<T> {
423    fn clone(&self) -> Self {
424        GcPtr(self.0)
425    }
426}
427
428impl<T: Trace + 'static + std::fmt::Debug> std::fmt::Debug for GcPtr<T> {
429    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
430        unsafe { (*self.raw()).value.fmt(f) }
431    }
432}
433
434impl<T: Trace + 'static> Drop for GcPtr<T> {
435    fn drop(&mut self) {}
436}
437
438// =============================================================================
439// StaticGcPtr — Send+Sync pointer to program-lifetime data
440// =============================================================================
441
442/// A raw pointer to a value that lives for the entire program lifetime.
443///
444/// Backed by the global `StaticArena` (in `no-gc` builds) or by `Box::leak`
445/// (in GC builds).  Either way the pointee is never freed and never moved, so
446/// it is safe to share across isolate threads.
447///
448/// `StaticGcPtr<T>` wraps `*const T` — it does **not** involve a `GcBox`
449/// header — so it is independent of the GC build mode and carries no GC
450/// overhead.
451pub struct StaticGcPtr<T: 'static>(NonNull<T>);
452
453// SAFETY: program-lifetime allocations are never moved, freed, or mutated
454// after the initial write.  The stored types (Keyword, Symbol, …) are
455// themselves `Sync` (no unsynchronised interior mutability).
456unsafe impl<T: 'static> Send for StaticGcPtr<T> {}
457unsafe impl<T: 'static> Sync for StaticGcPtr<T> {}
458
459impl<T: 'static> StaticGcPtr<T> {
460    /// Borrow the contained value.
461    pub fn get(&self) -> &T {
462        // SAFETY: pointer is program-lifetime, always valid.
463        unsafe { self.0.as_ref() }
464    }
465
466    /// Pointer equality: `true` iff both `StaticGcPtr`s point to the exact
467    /// same allocation (i.e. the same interned entry).
468    pub fn ptr_eq(a: &Self, b: &Self) -> bool {
469        a.0 == b.0
470    }
471}
472
473impl<T: 'static> Clone for StaticGcPtr<T> {
474    fn clone(&self) -> Self {
475        StaticGcPtr(self.0)
476    }
477}
478
479impl<T: 'static + std::fmt::Debug> std::fmt::Debug for StaticGcPtr<T> {
480    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481        unsafe { self.0.as_ref().fmt(f) }
482    }
483}
484
485/// Allocate `value` as program-lifetime memory and return a [`StaticGcPtr`].
486///
487/// In `no-gc` builds the allocation comes from the global bump-allocated
488/// `StaticArena` (never freed, no GC header overhead).  In GC builds
489/// `Box::leak` is used instead — the memory lives until the process exits.
490pub fn static_alloc<T: 'static>(value: T) -> StaticGcPtr<T> {
491    #[cfg(feature = "no-gc")]
492    {
493        static_arena::static_alloc_val(value)
494    }
495    #[cfg(not(feature = "no-gc"))]
496    {
497        StaticGcPtr(NonNull::from(Box::leak(Box::new(value))))
498    }
499}
500
501// =============================================================================
502// Full GC implementation (default build)
503// =============================================================================
504
505#[cfg(not(feature = "no-gc"))]
506mod gc_full {
507    use std::cell::RefCell;
508    use std::ptr::NonNull;
509    use std::sync::atomic::{AtomicUsize, Ordering};
510    use std::sync::{Arc, Mutex};
511
512    use crate::config::GcConfig;
513    use crate::gc_header::GC_INITIAL_LIVES;
514    use crate::{GcBox, GcBoxHeader, GcPtr, MarkVisitor, Trace};
515
516    type RootTracer = Box<dyn Fn(&mut MarkVisitor)>;
517
518    pub struct GcHeap {
519        inner: Mutex<GcHeapInner>,
520        config: Mutex<Option<Arc<GcConfig>>>,
521        memory_in_use: AtomicUsize,
522        total_allocated_bytes: AtomicUsize,
523        root_tracers: Mutex<Vec<RootTracer>>,
524        gc_suppressed: std::sync::atomic::AtomicBool,
525        /// memory_in_use threshold above which GC is re-enabled after a
526        /// zero-yield collection.  The headroom doubles on each consecutive
527        /// zero-yield cycle (exponential backoff, capped at soft_limit) so a
528        /// long computation where all objects are live doesn't spin in a
529        /// constant GC storm of O(N) sweeps.  Resets to the base headroom
530        /// (soft_limit / 10) once GC actually frees something.
531        suppressed_threshold: AtomicUsize,
532        /// Current headroom used for exponential backoff after zero-yield cycles.
533        zero_yield_headroom: AtomicUsize,
534    }
535
536    struct GcHeapInner {
537        head: *mut GcBoxHeader,
538        count: usize,
539        total_allocated: usize,
540        total_freed: usize,
541    }
542
543    unsafe impl Send for GcHeapInner {}
544
545    impl GcHeapInner {
546        const fn new() -> Self {
547            Self {
548                head: std::ptr::null_mut(),
549                count: 0,
550                total_allocated: 0,
551                total_freed: 0,
552            }
553        }
554    }
555
556    unsafe impl Sync for GcHeap {}
557
558    impl Default for GcHeap {
559        fn default() -> Self {
560            Self::new()
561        }
562    }
563
564    /// Parse a megabyte limit from a (raw) environment value into a byte count,
565    /// falling back to `default` (and warning) on malformed input rather than
566    /// panicking on user misconfiguration. `value` is `None` when the variable
567    /// is unset. Saturates instead of overflowing on absurdly large values.
568    pub(crate) fn parse_limit_mb(var: &str, value: Option<&str>, default: usize) -> usize {
569        match value {
570            Some(s) => match s.trim().parse::<usize>() {
571                Ok(mb) => mb.saturating_mul(1024 * 1024),
572                Err(_) => {
573                    eprintln!(
574                        "[gc] warning: ignoring invalid {var}={s:?} (expected a number of megabytes)"
575                    );
576                    default
577                }
578            },
579            None => default,
580        }
581    }
582
583    impl GcHeap {
584        pub const fn new() -> Self {
585            Self {
586                inner: Mutex::new(GcHeapInner::new()),
587                config: Mutex::new(None),
588                memory_in_use: AtomicUsize::new(0),
589                total_allocated_bytes: AtomicUsize::new(0),
590                root_tracers: Mutex::new(Vec::new()),
591                gc_suppressed: std::sync::atomic::AtomicBool::new(false),
592                suppressed_threshold: AtomicUsize::new(0),
593                zero_yield_headroom: AtomicUsize::new(0),
594            }
595        }
596
597        pub fn set_config(&self, config: Arc<GcConfig>) {
598            *self.config.lock().unwrap() = Some(config);
599        }
600
601        pub fn set_config_from_env(&self) {
602            #[cfg(not(target_arch = "wasm32"))]
603            let default_soft_limit: usize = (system_memory::total() / 3) as usize;
604            #[cfg(target_arch = "wasm32")]
605            let default_soft_limit: usize = 64 * 1024 * 1024;
606
607            let soft_limit_mb = parse_limit_mb(
608                "CLJRS_GC_SOFT_LIMIT_MB",
609                std::env::var("CLJRS_GC_SOFT_LIMIT_MB").ok().as_deref(),
610                default_soft_limit,
611            );
612            let mut hard_limit_mb = parse_limit_mb(
613                "CLJRS_GC_HARD_LIMIT_MB",
614                std::env::var("CLJRS_GC_HARD_LIMIT_MB").ok().as_deref(),
615                soft_limit_mb,
616            );
617            if hard_limit_mb < soft_limit_mb {
618                eprintln!(
619                    "[gc] warning: CLJRS_GC_HARD_LIMIT_MB is below CLJRS_GC_SOFT_LIMIT_MB; using the soft limit for both"
620                );
621                hard_limit_mb = soft_limit_mb;
622            }
623            self.set_config(Arc::new(GcConfig::with_limits(
624                soft_limit_mb,
625                hard_limit_mb,
626            )));
627        }
628
629        pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
630            self.root_tracers.lock().unwrap().push(Box::new(tracer));
631        }
632
633        pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
634            let tracers = self.root_tracers.lock().unwrap();
635            for tracer in tracers.iter() {
636                tracer(visitor);
637            }
638        }
639
640        pub fn memory_in_use(&self) -> usize {
641            self.memory_in_use.load(Ordering::Relaxed)
642        }
643
644        #[cfg(test)]
645        pub fn set_memory_in_use(&self, bytes: usize) {
646            self.memory_in_use.store(bytes, Ordering::Relaxed);
647        }
648
649        pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
650            crate::cancellation::safepoint();
651            let heap_extra = value.gc_size_extra();
652            let gc_box = Box::new(GcBox {
653                header: GcBoxHeader::new::<T>(heap_extra),
654                value,
655            });
656            let obj_size = gc_box.header.size; // GcBox<T> size + gc_size_extra()
657            let raw: *mut GcBox<T> = Box::into_raw(gc_box);
658            {
659                let mut inner = self.inner.lock().unwrap();
660                unsafe {
661                    (*raw).header.next.set(inner.head);
662                    inner.head = raw as *mut GcBoxHeader;
663                }
664                inner.count += 1;
665                inner.total_allocated += 1;
666            }
667            self.total_allocated_bytes
668                .fetch_add(obj_size, Ordering::Relaxed);
669            // The account batches the allocation statistics; record them
670            // directly only during thread-local teardown.
671            if crate::governor::with_current_account(|a| a.charge(obj_size)).is_none() {
672                crate::stats::GC_STATS.record_gc_alloc(obj_size);
673            }
674            let current_usage =
675                self.memory_in_use.fetch_add(obj_size, Ordering::Relaxed) + obj_size;
676
677            if let Some(config) = self.config.lock().unwrap().as_ref()
678                && config.soft_limit_exceeded(current_usage)
679            {
680                if self.gc_suppressed.load(Ordering::Relaxed) {
681                    // Suppression active: only re-enable GC once memory has
682                    // grown past the threshold set by the last zero-yield
683                    // collection (current_memory + soft_limit/10).
684                    let threshold = self.suppressed_threshold.load(Ordering::Relaxed);
685                    if current_usage > threshold {
686                        self.gc_suppressed.store(false, Ordering::Relaxed);
687                        crate::cancellation::request_gc();
688                    }
689                } else {
690                    crate::cancellation::request_gc();
691                }
692            }
693
694            register_alloc(raw as *mut GcBoxHeader);
695            GcPtr(unsafe { NonNull::new_unchecked(raw) })
696        }
697
698        pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
699            let pre_count = self.inner.lock().unwrap().count;
700            let pre_memory = self.memory_in_use.load(Ordering::Relaxed);
701            tracing::debug!(
702                target: "gc",
703                "starting collection: {} objects, ~{} bytes in use",
704                pre_count,
705                pre_memory
706            );
707            let mark_start = std::time::Instant::now();
708            let mut visitor = MarkVisitor::new();
709            trace_roots(&mut visitor);
710            // Active bump regions are additional roots: a live region object
711            // may hold `GcPtr`s into the heap, and those heap objects must not
712            // be collected while the region can still reach them.  The mark
713            // phase skips region objects themselves (they are not heap-managed),
714            // so we trace their children here instead.
715            crate::region::trace_active_regions(&mut visitor);
716            // Retired (poisoned) regions are immortal roots: their objects may
717            // still be referenced and may hold `GcPtr`s into the heap.
718            crate::region::trace_retired_regions(&mut visitor);
719            tracing::debug!(
720                target: "gc",
721                "starting drain with {} grey objects",
722                visitor.grey.len()
723            );
724            visitor.drain();
725            let mark_elapsed = mark_start.elapsed();
726
727            let sweep_start = std::time::Instant::now();
728            let mut inner = self.inner.lock().unwrap();
729            let mut live: Vec<*mut GcBoxHeader> = Vec::with_capacity(inner.count);
730            let mut dead: Vec<*mut GcBoxHeader> = Vec::new();
731            // Bytes of objects with lives==0 that will be freed now.
732            let mut freed_bytes: usize = 0;
733            let mut current = inner.head;
734            while !current.is_null() {
735                let header = unsafe { &*current };
736                let next = header.next.get();
737                let lives = header.lives.get();
738                let obj_size = header.size;
739                if lives >= GC_INITIAL_LIVES {
740                    // Marked reachable this cycle — reset grace counter.
741                    header.lives.set(GC_INITIAL_LIVES - 1);
742                    live.push(current);
743                } else if lives > 0 {
744                    // In grace period (unreachable but not yet freed).
745                    header.lives.set(lives - 1);
746                    live.push(current);
747                } else {
748                    // Grace period exhausted — collect now.
749                    freed_bytes += obj_size;
750                    dead.push(current);
751                }
752                current = next;
753            }
754            let freed_count = dead.len();
755            for ptr in dead {
756                let header = unsafe { &*ptr };
757                unsafe { (header.drop_fn)(ptr) };
758                inner.count -= 1;
759                inner.total_freed += 1;
760            }
761            inner.head = std::ptr::null_mut();
762            for ptr in live {
763                let header = unsafe { &*ptr };
764                header.next.set(inner.head);
765                inner.head = ptr;
766            }
767            // Decrement memory_in_use by the bytes actually freed.  All heap
768            // objects (live + grace-period) remain counted; only physically
769            // freed objects are subtracted.  This keeps memory pressure
770            // accurate so GC fires again when the heap genuinely grows.
771            self.memory_in_use.fetch_sub(freed_bytes, Ordering::Relaxed);
772            let sweep_elapsed = sweep_start.elapsed();
773            let post_memory = self.memory_in_use.load(Ordering::Relaxed);
774            crate::governor::with_current_account(|a| {
775                a.release(freed_bytes);
776                a.record_collection(crate::governor::CollectionReport {
777                    bytes_before: pre_memory,
778                    bytes_after: post_memory,
779                    bytes_returned: freed_bytes,
780                    duration: mark_elapsed + sweep_elapsed,
781                });
782            });
783            crate::stats::GC_STATS.record_gc_pause(
784                mark_elapsed + sweep_elapsed,
785                freed_count as u64,
786                freed_bytes as u64,
787            );
788            tracing::debug!(
789                target: "gc",
790                "collection complete: freed {} (~{} bytes), {} remaining (~{} bytes), mark={:.2?} sweep={:.2?}",
791                freed_count,
792                freed_bytes,
793                inner.count,
794                post_memory,
795                mark_elapsed,
796                sweep_elapsed
797            );
798            if freed_count == 0 {
799                // Zero-yield collection: exponential-backoff suppression.
800                // Each consecutive zero-yield cycle doubles the headroom before
801                // the next GC attempt (capped at soft_limit/4).  This prevents a
802                // GC storm during deep recursion where all objects are live —
803                // without backoff, GC fires every soft_limit/10 bytes, tracing
804                // the entire live set O(N) times to no benefit.
805                // The headroom resets to soft_limit/10 when GC frees something.
806                // Cap at soft_limit/4 (not soft_limit) so that GC still fires
807                // frequently enough to catch short-lived test allocations after
808                // a long namespace-loading phase of zero-yield cycles.
809                let soft_limit = self
810                    .config
811                    .lock()
812                    .unwrap()
813                    .as_ref()
814                    .map(|c| c.soft_limit())
815                    .unwrap_or(64 * 1024 * 1024);
816                let base_headroom = (soft_limit / 10).max(1);
817                let max_headroom = (soft_limit / 4).max(base_headroom);
818                let prev_headroom = self.zero_yield_headroom.load(Ordering::Relaxed);
819                let headroom = if prev_headroom == 0 {
820                    base_headroom
821                } else {
822                    prev_headroom.saturating_mul(2).min(max_headroom)
823                };
824                self.zero_yield_headroom.store(headroom, Ordering::Relaxed);
825                self.suppressed_threshold
826                    .store(post_memory + headroom, Ordering::Relaxed);
827                self.gc_suppressed.store(true, Ordering::Relaxed);
828            } else {
829                // GC freed something: reset exponential backoff.
830                self.zero_yield_headroom.store(0, Ordering::Relaxed);
831                self.gc_suppressed.store(false, Ordering::Relaxed);
832            }
833        }
834
835        pub fn count(&self) -> usize {
836            self.inner.lock().unwrap().count
837        }
838        pub fn total_allocated(&self) -> usize {
839            self.inner.lock().unwrap().total_allocated
840        }
841        pub fn total_freed(&self) -> usize {
842            self.inner.lock().unwrap().total_freed
843        }
844
845        pub fn collect_auto(&self) -> bool {
846            tracing::debug!(target: "gc", "automatic collection requested");
847            let Some(_stw_guard) = crate::cancellation::begin_stw() else {
848                tracing::debug!(target: "gc", "automatic collection skipped");
849                return false;
850            };
851            self.collect(|visitor| self.trace_registered_roots(visitor));
852            true
853        }
854    }
855
856    thread_local! {
857        static ISOLATE_HEAP: GcHeap = const { GcHeap::new() };
858    }
859
860    /// Zero-sized proxy that dispatches all heap operations to the calling
861    /// thread's [`GcHeap`] via the `ISOLATE_HEAP` thread-local.
862    ///
863    /// This means every isolate (OS thread) owns an independent heap; GC runs
864    /// fully in parallel on different threads with no cross-isolate coordination.
865    pub struct HeapProxy;
866
867    impl HeapProxy {
868        pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
869            ISOLATE_HEAP.with(|h| h.alloc(value))
870        }
871
872        pub fn set_config(&self, config: Arc<GcConfig>) {
873            ISOLATE_HEAP.with(|h| h.set_config(config));
874        }
875
876        pub fn set_config_from_env(&self) {
877            ISOLATE_HEAP.with(|h| h.set_config_from_env());
878        }
879
880        pub fn register_root_tracer(&self, tracer: impl Fn(&mut MarkVisitor) + 'static) {
881            ISOLATE_HEAP.with(|h| h.register_root_tracer(tracer));
882        }
883
884        pub fn trace_registered_roots(&self, visitor: &mut MarkVisitor) {
885            ISOLATE_HEAP.with(|h| h.trace_registered_roots(visitor));
886        }
887
888        pub fn memory_in_use(&self) -> usize {
889            ISOLATE_HEAP.with(|h| h.memory_in_use())
890        }
891
892        pub fn count(&self) -> usize {
893            ISOLATE_HEAP.with(|h| h.count())
894        }
895
896        pub fn total_allocated(&self) -> usize {
897            ISOLATE_HEAP.with(|h| h.total_allocated())
898        }
899
900        pub fn total_freed(&self) -> usize {
901            ISOLATE_HEAP.with(|h| h.total_freed())
902        }
903
904        pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, trace_roots: F) {
905            ISOLATE_HEAP.with(|h| h.collect(trace_roots));
906        }
907
908        pub fn collect_auto(&self) -> bool {
909            ISOLATE_HEAP.with(|h| h.collect_auto())
910        }
911
912        #[cfg(test)]
913        pub fn set_memory_in_use(&self, bytes: usize) {
914            ISOLATE_HEAP.with(|h| h.set_memory_in_use(bytes));
915        }
916    }
917
918    // SAFETY: HeapProxy is zero-sized; all state lives in a thread-local GcHeap.
919    // The Send + Sync impls are needed so `pub static HEAP: HeapProxy` is valid.
920    unsafe impl Sync for HeapProxy {}
921
922    pub static HEAP: HeapProxy = HeapProxy;
923
924    thread_local! {
925        pub(crate) static ALLOC_ROOTS: RefCell<Vec<*mut GcBoxHeader>> = const { RefCell::new(Vec::new()) };
926    }
927
928    pub struct AllocRootGuard {
929        saved_len: usize,
930    }
931
932    impl Drop for AllocRootGuard {
933        fn drop(&mut self) {
934            ALLOC_ROOTS.with(|roots| roots.borrow_mut().truncate(self.saved_len));
935        }
936    }
937
938    pub fn push_alloc_frame() -> AllocRootGuard {
939        let saved_len = ALLOC_ROOTS.with(|roots| roots.borrow().len());
940        AllocRootGuard { saved_len }
941    }
942
943    fn register_alloc(header: *mut GcBoxHeader) {
944        ALLOC_ROOTS.with(|roots| roots.borrow_mut().push(header));
945    }
946
947    pub fn trace_thread_alloc_roots(visitor: &mut MarkVisitor) {
948        ALLOC_ROOTS.with(|roots| {
949            let roots = roots.borrow();
950            for &header in roots.iter() {
951                unsafe { visitor.mark_header(header) };
952            }
953        });
954    }
955}
956
957// =============================================================================
958// no-gc stubs
959// =============================================================================
960
961#[cfg(feature = "no-gc")]
962mod nogc_stubs {
963    use crate::MarkVisitor;
964    use std::sync::Arc;
965
966    #[derive(Debug, Clone)]
967    pub struct GcConfig;
968    impl GcConfig {
969        pub fn new() -> Self {
970            Self
971        }
972        pub fn with_hard_limit(_: usize) -> Self {
973            Self
974        }
975        pub fn with_limits(_: usize, _: usize) -> Self {
976            Self
977        }
978        pub fn with_soft_limit(_: usize) -> Self {
979            Self
980        }
981        pub fn try_with_limits(
982            soft_limit: usize,
983            hard_limit: usize,
984        ) -> Result<Self, crate::governor::MemoryConfigError> {
985            crate::governor::MemoryConfig::with_limits(soft_limit, hard_limit).map(|_| Self)
986        }
987        pub fn validate(&self) -> Result<(), crate::governor::MemoryConfigError> {
988            Ok(())
989        }
990    }
991    impl Default for GcConfig {
992        fn default() -> Self {
993            Self::new()
994        }
995    }
996
997    pub struct GcHeap;
998    impl Default for GcHeap {
999        fn default() -> Self {
1000            Self::new()
1001        }
1002    }
1003    impl GcHeap {
1004        pub const fn new() -> Self {
1005            Self
1006        }
1007        pub fn set_config(&self, _: Arc<GcConfig>) {}
1008        /// No-op counterpart of the GC build's env-driven configuration.
1009        /// Region allocation has no soft limit to configure, but the runtime
1010        /// builder calls this unconditionally, so the method must exist in
1011        /// both builds.
1012        pub fn set_config_from_env(&self) {}
1013        pub fn register_root_tracer(&self, _: impl Fn(&mut MarkVisitor) + 'static) {}
1014        pub fn trace_registered_roots(&self, _: &mut MarkVisitor) {}
1015        pub fn memory_in_use(&self) -> usize {
1016            0
1017        }
1018        pub fn count(&self) -> usize {
1019            0
1020        }
1021        pub fn total_allocated(&self) -> usize {
1022            0
1023        }
1024        pub fn total_freed(&self) -> usize {
1025            0
1026        }
1027        pub fn collect<F: FnOnce(&mut MarkVisitor)>(&self, _: F) {}
1028        pub fn collect_auto(&self) -> bool {
1029            false
1030        }
1031    }
1032    unsafe impl Sync for GcHeap {}
1033    pub static HEAP: GcHeap = GcHeap::new();
1034
1035    pub struct MutatorGuard;
1036    impl Drop for MutatorGuard {
1037        fn drop(&mut self) {}
1038    }
1039    pub struct StwGuard;
1040    impl Drop for StwGuard {
1041        fn drop(&mut self) {}
1042    }
1043    pub struct GcParked;
1044
1045    pub struct GcCancellationStub;
1046    impl GcCancellationStub {
1047        pub const fn new() -> Self {
1048            Self
1049        }
1050        pub fn in_progress(&self) -> bool {
1051            false
1052        }
1053    }
1054    pub static CONFIG_CANCELLATION: GcCancellationStub = GcCancellationStub::new();
1055
1056    pub fn safepoint() {}
1057    pub fn gc_requested() -> bool {
1058        false
1059    }
1060    pub fn take_gc_request() -> bool {
1061        false
1062    }
1063    pub fn begin_stw() -> Option<StwGuard> {
1064        None
1065    }
1066    pub fn register_mutator() -> MutatorGuard {
1067        MutatorGuard
1068    }
1069    pub fn registered_threads() -> usize {
1070        0
1071    }
1072    pub fn request_gc() {}
1073    pub fn check_cancellation() -> Result<(), GcParked> {
1074        Ok(())
1075    }
1076    pub fn park_thread() {}
1077    pub fn unpark_thread() {}
1078    pub fn wait_for_threads_to_park() {}
1079
1080    pub struct AllocRootGuard;
1081    impl Drop for AllocRootGuard {
1082        fn drop(&mut self) {}
1083    }
1084    pub fn push_alloc_frame() -> AllocRootGuard {
1085        AllocRootGuard
1086    }
1087}
1088
1089// =============================================================================
1090// Tests
1091// =============================================================================
1092
1093#[cfg(all(test, not(feature = "no-gc")))]
1094mod tests {
1095    use super::*;
1096    use std::sync::{Arc, Mutex};
1097
1098    #[derive(Debug)]
1099    #[allow(dead_code)]
1100    struct Tracked {
1101        value: i32,
1102        dropped: Arc<Mutex<bool>>,
1103    }
1104    impl Drop for Tracked {
1105        fn drop(&mut self) {
1106            *self.dropped.lock().unwrap() = true;
1107        }
1108    }
1109    impl Trace for Tracked {
1110        fn trace(&self, _: &mut MarkVisitor) {}
1111    }
1112
1113    #[derive(Debug)]
1114    #[allow(dead_code)]
1115    struct Parent {
1116        child: GcPtr<Tracked>,
1117    }
1118    impl Trace for Parent {
1119        fn trace(&self, visitor: &mut MarkVisitor) {
1120            visitor.visit(&self.child);
1121        }
1122    }
1123
1124    fn fresh_heap() -> gc_full::GcHeap {
1125        let heap = gc_full::GcHeap::new();
1126        heap.set_config(Arc::new(GcConfig::with_limits(10000, 50000)));
1127        heap
1128    }
1129
1130    #[test]
1131    fn alloc_and_get() {
1132        let heap = fresh_heap();
1133        let p = heap.alloc(42i64);
1134        assert_eq!(*p.get(), 42);
1135        assert_eq!(heap.count(), 1);
1136    }
1137
1138    #[test]
1139    fn clone_is_same_ptr() {
1140        let heap = fresh_heap();
1141        let p = heap.alloc(99i64);
1142        let q = p.clone();
1143        assert!(GcPtr::ptr_eq(&p, &q));
1144    }
1145
1146    #[test]
1147    fn parse_limit_mb_handles_valid_unset_and_malformed() {
1148        // Valid: converts megabytes to bytes.
1149        assert_eq!(gc_full::parse_limit_mb("X", Some("4"), 7), 4 * 1024 * 1024);
1150        // Surrounding whitespace is tolerated.
1151        assert_eq!(
1152            gc_full::parse_limit_mb("X", Some(" 4 "), 7),
1153            4 * 1024 * 1024
1154        );
1155        // Unset: falls back to the default.
1156        assert_eq!(gc_full::parse_limit_mb("X", None, 7), 7);
1157        // Malformed must NOT panic — it falls back to the default.
1158        assert_eq!(gc_full::parse_limit_mb("X", Some("foo"), 7), 7);
1159        assert_eq!(gc_full::parse_limit_mb("X", Some(""), 7), 7);
1160        assert_eq!(gc_full::parse_limit_mb("X", Some("-1"), 7), 7);
1161        // Absurdly large value saturates rather than overflowing.
1162        assert_eq!(
1163            gc_full::parse_limit_mb("X", Some(&usize::MAX.to_string()), 7),
1164            usize::MAX
1165        );
1166    }
1167
1168    #[test]
1169    fn collect_keeps_reachable() {
1170        let heap = fresh_heap();
1171        let dropped = Arc::new(Mutex::new(false));
1172        let p = heap.alloc(Tracked {
1173            value: 2,
1174            dropped: dropped.clone(),
1175        });
1176        heap.collect(|vis| vis.visit(&p));
1177        assert_eq!(heap.count(), 1);
1178        assert!(!*dropped.lock().unwrap());
1179    }
1180
1181    #[test]
1182    fn b1_two_isolates_independent_heaps() {
1183        use std::sync::{Arc, Barrier};
1184        // Each thread has its own ISOLATE_HEAP; allocations on one do not appear
1185        // in the other.
1186        let barrier = Arc::new(Barrier::new(2));
1187        let b1 = barrier.clone();
1188        let h1 = std::thread::Builder::new()
1189            .name("isolate-1".into())
1190            .spawn(move || {
1191                let _mutator = crate::register_mutator();
1192                // Allocate 100 objects on this isolate's heap
1193                let _ptrs: Vec<_> = (0_i64..100)
1194                    .map(|i| crate::gc_full::HEAP.alloc(i))
1195                    .collect();
1196                b1.wait(); // both threads are now at peak allocation
1197                // This isolate has exactly 100 live objects
1198                assert_eq!(
1199                    crate::gc_full::HEAP.count(),
1200                    100,
1201                    "isolate-1 heap count should be 100"
1202                );
1203            })
1204            .unwrap();
1205
1206        let b2 = barrier.clone();
1207        let h2 = std::thread::Builder::new()
1208            .name("isolate-2".into())
1209            .spawn(move || {
1210                let _mutator = crate::register_mutator();
1211                // Allocate 200 objects on this isolate's heap
1212                let _ptrs: Vec<_> = (0_i64..200)
1213                    .map(|i| crate::gc_full::HEAP.alloc(i))
1214                    .collect();
1215                b2.wait();
1216                // This isolate has exactly 200 live objects, unaffected by isolate-1
1217                assert_eq!(
1218                    crate::gc_full::HEAP.count(),
1219                    200,
1220                    "isolate-2 heap count should be 200"
1221                );
1222            })
1223            .unwrap();
1224
1225        h1.join().expect("isolate-1 panicked");
1226        h2.join().expect("isolate-2 panicked");
1227    }
1228
1229    #[test]
1230    fn b1_two_isolates_gc_independently() {
1231        // Two threads run allocation-heavy loops and GC their own heaps independently.
1232        let h1 = std::thread::Builder::new()
1233            .name("gc-isolate-1".into())
1234            .spawn(|| {
1235                let _mutator = crate::register_mutator();
1236                let heap = &crate::gc_full::HEAP;
1237                heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1238                // Allocate in batches and collect; each collection touches only this heap
1239                for _ in 0..5 {
1240                    let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1241                    // drive a manual collect with no roots so objects are freed
1242                    heap.collect(|_| {});
1243                    heap.collect(|_| {}); // second pass clears grace-period objects
1244                }
1245                // After all collections the heap should be empty (or close to it).
1246                // We don't assert an exact count because alloc_frame roots may keep
1247                // some alive; just assert we can collect without panicking.
1248            })
1249            .unwrap();
1250
1251        let h2 = std::thread::Builder::new()
1252            .name("gc-isolate-2".into())
1253            .spawn(|| {
1254                let _mutator = crate::register_mutator();
1255                let heap = &crate::gc_full::HEAP;
1256                heap.set_config(Arc::new(GcConfig::with_limits(16_384, 65_536)));
1257                for _ in 0..5 {
1258                    let _ptrs: Vec<_> = (0_i64..50).map(|i| heap.alloc(i)).collect();
1259                    heap.collect(|_| {});
1260                    heap.collect(|_| {});
1261                }
1262            })
1263            .unwrap();
1264
1265        h1.join().expect("gc-isolate-1 panicked");
1266        h2.join().expect("gc-isolate-2 panicked");
1267    }
1268}
1269
1270#[cfg(all(test, feature = "no-gc"))]
1271mod nogc_tests {
1272    use super::*;
1273    use alloc_ctx::{ScratchGuard, StaticCtxGuard};
1274
1275    #[test]
1276    fn alloc_in_static_context() {
1277        let _g = StaticCtxGuard::new();
1278        let p = GcPtr::new(42i64);
1279        assert_eq!(*p.get(), 42);
1280    }
1281
1282    #[test]
1283    fn alloc_in_scratch_region() {
1284        let mut scratch = ScratchGuard::new();
1285        let p = GcPtr::new(99i64);
1286        assert_eq!(*p.get(), 99);
1287        scratch.pop_for_return();
1288        assert_eq!(*p.get(), 99);
1289        // scratch drops here, resets the region
1290    }
1291
1292    #[test]
1293    fn ptr_eq() {
1294        let _g = StaticCtxGuard::new();
1295        let p = GcPtr::new(1i64);
1296        let q = p.clone();
1297        assert!(GcPtr::ptr_eq(&p, &q));
1298    }
1299}