cljrs-gc
Non-moving, stop-the-world mark-and-sweep garbage collector for clojurust.
Phase: 8.1 (GcVisitor + Trace infrastructure) + 8.2 (GcBox/GcHeap raw-pointer implementation) — implemented.
Purpose
Manages all Clojure runtime values. Rust code owns the root set and triggers
collection explicitly. GcPtr<T> is a raw pointer into the GC heap; clone
is O(1); drop is a no-op. Memory is freed only during GcHeap::collect.
File layout
src/
lib.rs — GcVisitor, Trace, GcBoxHeader, GcBox<T>, GcHeap, MarkVisitor,
HEAP singleton, GcPtr<T>, leaf Trace impls (i64, f64, BigInt, …)
region.rs — Region bump allocator, RegionGuard, thread-local region stack
Public API
GcVisitor
Implemented by [MarkVisitor]. Call visitor.visit(ptr) inside
Trace::trace for every GcPtr field.
Trace
Implemented by every type stored behind a GcPtr. Must call
visitor.visit(ptr) for every GcPtr reachable from self (directly or
through Arc/Mutex/etc.).
Built-in leaf impls: String, i64, f64, bool,
num_bigint::BigInt, bigdecimal::BigDecimal,
num_rational::Ratio<BigInt>.
GcPtr<T: Trace + 'static>
;
GcHeap
collect is stop-the-world: must only be called when no other thread is
creating or dereferencing GcPtr values.
MarkVisitor
Uses a grey stack (avoids recursion stack overflow) and handles cycles via already-marked check.
HEAP
pub static HEAP: GcHeap;
Global singleton; all GcPtr::new calls allocate here.
region::Region
Bump allocator for short-lived objects. ~2.6x faster than GcHeap::alloc
(no mutex, no Box::new). Objects are NOT in the GC heap linked list.
Destructors run on reset() or drop.
region::RegionGuard
RAII guard that pushes a Region onto the thread-local stack. Use with
try_alloc_in_region() for opportunistic region allocation.
Design notes
- Non-moving:
GcPtr<T>stores a stableNonNull<GcBox<T>>address. - Stop-the-world:
collectmust pause all other threads that holdGcPtrs. - Intrusive linked list: all
GcBoxes are linked viaGcBoxHeader::next. - Type erasure:
trace_fn/drop_fnin the header enable type-erased mark and sweep without a vtable pointer per allocation. - Cycle collection: because
GcPtr::dropis a no-op, reference cycles do not prevent collection — any object unreachable from roots is freed.
Deferred to later phases
- Incremental/concurrent collection — Phase 10+
- Write barriers for generational GC — Phase 10+
- Weak references (
WeakGcPtr<T>) — deferred - Safepoint integration with JIT frames — Phase 10+
- Automatic collection trigger (threshold-based) — deferred