pub struct Heap { /* private fields */ }Implementations§
Source§impl Heap
impl Heap
Sourcepub fn new() -> Rc<Self>
pub fn new() -> Rc<Self>
Construct a heap behind Rc — the only ownership shape the guard
and weak-handle machinery accept since issue #252. Everything that
needs &Heap gets it by deref.
Do not store a clone of this Rc inside anything the heap itself
owns (a traced value, a userdata payload): that is a reference cycle
and the heap will never drop. Collector-internal bookkeeping only
ever holds Weak<Heap> (HeapRef) for this reason.
Sourcepub fn unpause(&self)
pub fn unpause(&self)
Enable collection. Until this is called, step is a no-op (so the
runtime can bootstrap without prematurely freeing objects).
pub fn is_paused(&self) -> bool
Sourcepub fn is_closed(&self) -> bool
pub fn is_closed(&self) -> bool
True once drop_all has begun tearing this heap
down. After this returns true, allocate and
allocate_uncollected panic — a
HeapGuard that outlived close() cannot allocate into a
torn-down heap — with one exception: re-entrant allocation from a
destructor running inside the teardown drain itself, which the drain
is designed to accept.
Sourcepub fn begin_bootstrap(&self)
pub fn begin_bootstrap(&self)
Enter bootstrap mode: allocate routes new boxes
through allocate_uncollected instead of
the normal collectable list until the matching
end_bootstrap is called. Prefer the RAII
bootstrap_scope — a manual end_bootstrap
is skipped by early returns and error paths. See the bootstrap_depth
field doc for why this exists separately from paused.
Sourcepub fn end_bootstrap(&self)
pub fn end_bootstrap(&self)
Leave one level of bootstrap mode; once the depth returns to zero,
subsequent allocate calls join the normal collectable head list.
pub fn is_bootstrapping(&self) -> bool
Sourcepub fn bootstrap_scope(&self) -> BootstrapScope
pub fn bootstrap_scope(&self) -> BootstrapScope
RAII form of begin_bootstrap/
end_bootstrap: the returned scope ends the
bootstrap window when dropped, so ?-style early exits from VM
construction cannot leave the heap stuck in bootstrap mode.
Safe to hold past the heap’s death (see BootstrapScope).
Sourcepub fn allocate<T: Trace + 'static>(&self, value: T) -> Gc<T>
pub fn allocate<T: Trace + 'static>(&self, value: T) -> Gc<T>
Allocate a new GcBox<T> and prepend it to the allgc chain. While
is_bootstrapping is true, delegates to
allocate_uncollected instead.
Sourcepub fn allocate_uncollected<T: Trace + 'static>(&self, value: T) -> Gc<T>
pub fn allocate_uncollected<T: Trace + 'static>(&self, value: T) -> Gc<T>
Allocate a GcBox<T> owned by this heap but linked onto neither
head, finobj, nor tobefnz — sweep never visits it, so it is
never collected while the heap is alive (matching Gc::new_uncollected
semantics for permanent roots), but it is linked onto the heap’s
uncollected list, so drop_all frees it when the
heap shuts down instead of leaking it past the heap’s lifetime.
Does not charge bytes/objects — those drive collection pacing and
diagnostics for the collectable set; an object that sweep never visits
would inflate them permanently.
Sourcepub fn bytes_used(&self) -> usize
pub fn bytes_used(&self) -> usize
Bytes currently retained by GC-tracked objects (rough estimate).
Sourcepub fn adjust_bytes(&self, delta: isize)
pub fn adjust_bytes(&self, delta: isize)
Adjust the heap’s pacer byte counter by a signed delta, saturating at
zero. Used by Gc::account_buffer to charge or refund the bytes of
an object’s owned heap buffers (table array/node Vecs) so collections
fire at honest memory pressure rather than only on header sizes.
Sourcepub fn threshold_bytes(&self) -> usize
pub fn threshold_bytes(&self) -> usize
Current collection threshold in bytes. When bytes_used() >= threshold_bytes(),
the next step() will run a full collection (unless paused). Used by
callers that want to short-circuit expensive prep work (e.g. snapshotting
weak tables / pending finalizers) when no collection will actually fire.
Sourcepub fn set_threshold_bytes(&self, threshold: usize)
pub fn set_threshold_bytes(&self, threshold: usize)
Override the next automatic collection threshold.
The VM uses this when Lua-level GC pacing (GCdebt, minor-debt, and
pause-debt calculations) has already computed a byte threshold from the
collector-owned live-byte counter.
Sourcepub fn would_collect(&self) -> bool
pub fn would_collect(&self) -> bool
Cheap predicate: would a step() actually do work? Equivalent to
!paused && bytes_used() >= threshold_bytes(). Callers that build
snapshot state before invoking the heap should gate on this. Always
false once the heap is closed (see collection_inert).
pub fn collections(&self) -> usize
pub fn minor_collections(&self) -> usize
pub fn full_collections(&self) -> usize
pub fn last_mark_stats(&self) -> MarkerStats
pub fn last_sweep_stats(&self) -> SweepStats
pub fn allgc_cohort_stats(&self) -> AllGcCohortStats
pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool
pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool
pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool
Sourcepub fn grayagain_count(&self) -> usize
pub fn grayagain_count(&self) -> usize
Number of objects on the grayagain revisit set. Flag-dedup guarantees
no duplicates, so this equals the intrusive list’s old walk count
(grayagain_links_object_once pins it). Read by lua-cli telemetry.
Sourcepub fn register_v51_udata(&self, probe: Rc<dyn Udata51Probe>)
pub fn register_v51_udata(&self, probe: Rc<dyn Udata51Probe>)
Record a userdata in the Lua 5.1 collect-time finalizability roster.
Called by the VM for every userdata that receives a metatable on 5.1.
The probe holds a weak handle, so the roster never roots the userdata.
No-op for the collector beyond storage; the VM reads metatables and
registers finalizers via scan_v51_finalizable.
Sourcepub fn scan_v51_finalizable(&self) -> Vec<Rc<dyn Udata51Probe>>
pub fn scan_v51_finalizable(&self) -> Vec<Rc<dyn Udata51Probe>>
Drain the Lua 5.1 finalizability roster of dead entries and return the still-live probes for the VM to inspect.
Mirrors the enumeration half of C 5.1 luaC_separateudata: the VM
caller then reads each probe’s live metatable for __gc and registers
the finalizable ones. Dead probes (their userdata already swept) are
dropped here so the roster stays bounded. The returned probes are kept
in the roster too (still live), so an already-finalized userdata that
outlives its __gc is naturally pruned on a later scan once swept.
Sourcepub fn allocation_token(&self, identity: usize) -> Option<usize>
pub fn allocation_token(&self, identity: usize) -> Option<usize>
Return the current heap token for an allocation identity, or None when
the identity was never registered or has since been swept.
Registration is lazy: tokens are minted at weak-handle creation
(register_allocation_token), not at
allocation, so an object that was never weak-referenced reports None
here even while live.
Sourcepub fn register_allocation_token(&self, identity: usize) -> usize
pub fn register_allocation_token(&self, identity: usize) -> usize
Register identity in the weak-handle validation table, returning its
token (get-or-insert against the monotonic counter).
This is the lazy half of weak-handle validation. The hot allocation path
no longer touches allocation_tokens; instead a token is minted the
first time an object is downgraded to a weak handle, which is the only
moment the token is ever consumed.
Correctness: every valid weak handle calls this at creation while holding
a strong reference, so the object is provably live at registration. A
later contains_allocation returning false
for an absent identity therefore means “swept” exactly as the eager
scheme did — sweep removes the entry when it frees the box. The monotonic
counter never reissues a token, so when the allocator reuses an address
(a freed identity re-registered by a fresh object) the new token differs
from any stale handle’s, preventing address reuse from resurrecting a
dead handle. Objects that are never weak-referenced never enter the map.
On a closed heap this refuses to mint: it returns 0 — a value
next_token never issues, so it can never
validate — without touching the map. A downgrade of a stale GcRef
after close would otherwise re-register the freed box’s address and
resurrect it as an upgradable weak target.
Sourcepub fn contains_allocation(&self, identity: usize, token: usize) -> bool
pub fn contains_allocation(&self, identity: usize, token: usize) -> bool
Return true when identity still names the same heap allocation.
The token check prevents allocator address reuse from making a stale
weak handle look live again. Unconditionally false once the heap is
closed: every box is freed (or about to be, mid-drain), so no weak
handle may upgrade regardless of what the token map transiently holds.
Sourcepub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
Forward write barrier: invoked when parent (already-traced black
object) gains a new reference to child. To preserve the tri-color
invariant (“no black points to white”), we mark the child gray
immediately. Cheap: one branch + maybe one queue push.
During incremental mode this prevents the marking phase from missing the new edge. In current stop-the-world mode it’s still correct (a no-op when the collection is idle), so call sites can be wired now and the incremental upgrade is mechanical later.
Sourcepub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
Backward barrier: if a black object receives a reference to a white child, gray the parent so the in-progress cycle will rescan it.
Sourcepub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
Generational forward barrier: if an old object receives a reference to a young object, the child cannot jump directly to OLD because it may still point at younger objects. Lua marks it OLD0 so later young collections advance it through OLD1 to OLD.
Sourcepub fn generational_backward_barrier<P>(&self, parent: Gc<P>)where
P: Trace + 'static,
pub fn generational_backward_barrier<P>(&self, parent: Gc<P>)where
P: Trace + 'static,
Generational backward barrier: an old object that now points to a young
object is revisited by the next young collection. This mirrors
luaC_barrierback_’s age transition to TOUCHED1.
Sourcepub fn step(&self, roots: &dyn Trace)
pub fn step(&self, roots: &dyn Trace)
Possibly run a collection. Trigger: bytes_used > threshold.
Caller passes the root set (the runtime — typically GlobalState
implementing Trace).
Sourcepub fn step_with_post_mark<F: FnMut(&mut Marker)>(
&self,
roots: &dyn Trace,
post_mark: F,
)
pub fn step_with_post_mark<F: FnMut(&mut Marker)>( &self, roots: &dyn Trace, post_mark: F, )
Like [step] but invokes a post_mark hook when a collection
actually fires (threshold reached). Hook is a no-op on the
short-circuit path. The runtime uses this to bridge weak-table
pruning into implicit GC steps fired from inside the VM loop.
Sourcepub fn full_collect(&self, roots: &dyn Trace)
pub fn full_collect(&self, roots: &dyn Trace)
Stop-the-world full collect. Marks every reachable object from
roots, then sweeps white (unreachable) boxes from the heap owner lists.
Sourcepub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>(
&self,
roots: &dyn Trace,
post_mark: F,
)
pub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>( &self, roots: &dyn Trace, post_mark: F, )
Run only the mark/atomic hook portion of a collection, without sweeping.
This is used by runtimes that need an atomic reachability snapshot for weak-table cleanup while they are deliberately avoiding object freeing.
Sourcepub fn promote_all_to_old(&self)
pub fn promote_all_to_old(&self)
Metadata transition used when entering generational mode after a full mark: all currently live objects become old.
Sourcepub fn reset_all_ages(&self)
pub fn reset_all_ages(&self)
Metadata transition used when returning to incremental mode: Lua clears age information and treats all objects as new again.
Sourcepub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>(
&self,
roots: &dyn Trace,
post_mark: F,
)
pub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>( &self, roots: &dyn Trace, post_mark: F, )
Run a complete young-generation collection.
This is the first generational path: it uses the normal root tracer for correctness, then limits sweep/freeing to young objects. Later work can replace the full root traversal with cohort-list traversal without changing the age/sweep contract introduced here.
Sourcepub fn full_collect_with_post_mark<F: FnMut(&mut Marker)>(
&self,
roots: &dyn Trace,
post_mark: F,
)
pub fn full_collect_with_post_mark<F: FnMut(&mut Marker)>( &self, roots: &dyn Trace, post_mark: F, )
Stop-the-world full collect with a post-mark hook.
Internally drives the incremental state machine to completion with
an unbounded budget — equivalent to repeatedly calling
[incremental_step_with_post_mark] until it returns Paused. The
post-mark hook is invoked exactly once, during the atomic transition.
Sourcepub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>(
&self,
roots: &dyn Trace,
budget: StepBudget,
post_mark: F,
) -> StepOutcome
pub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>( &self, roots: &dyn Trace, budget: StepBudget, post_mark: F, ) -> StepOutcome
Run one budgeted step of the incremental collector.
The state machine advances Pause → Propagate → EnterAtomic → Atomic → SweepAllGc → SweepFinObj → SweepToBeFnz → SweepEnd → CallFin → Pause.
Each phase consumes budget; the call returns when the budget runs out
or the cycle reaches Pause. The post_mark
hook is invoked exactly once per cycle, during the Atomic
transition (after the initial gray-queue drain, before sweep starts).
Returns:
StepOutcome::Paused— the cycle completed.StepOutcome::InProgress— budget exhausted before the cycle finished; caller may step again.StepOutcome::SkippedStopped— heap is paused; nothing happened.
Sourcepub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>(
&self,
roots: &dyn Trace,
target: GcState,
max_work: isize,
post_mark: F,
) -> StepOutcome
pub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>( &self, roots: &dyn Trace, target: GcState, max_work: isize, post_mark: F, ) -> StepOutcome
Drive an incremental cycle until target is entered, stopping before any
subsequent phase work. Intended for testC-style inspection of mid-cycle
color/barrier invariants; normal collector pacing uses
Self::incremental_step_with_post_mark.
Sourcepub fn finish_callfin_phase(&self) -> bool
pub fn finish_callfin_phase(&self) -> bool
Finish an idle CallFin phase after the runtime has drained any
pending to-be-finalized objects.
Sourcepub fn allgc_count(&self) -> usize
pub fn allgc_count(&self) -> usize
Approximate number of live GC boxes across all heap owner lists.
Sourcepub fn type_name_count(
&self,
predicate: impl FnMut(&'static str) -> bool,
) -> usize
pub fn type_name_count( &self, predicate: impl FnMut(&'static str) -> bool, ) -> usize
Count live allgc objects whose concrete Rust type name matches
predicate. This is diagnostic/testC telemetry only; collector logic
must not depend on Rust type names.
Sourcepub fn drop_all(&self)
pub fn drop_all(&self)
Drop every allocation, ignoring reachability. Called at shutdown
(Heap::drop, and the VM’s close_state / free_all_objects).
Drain until stable. A single pass over the owner lists is not
enough: a payload Drop may itself allocate — a GcRef::new in a
destructor relinks a fresh box onto head (or onto uncollected
inside a bootstrap window) — and that box would be stranded on a
just-emptied list and leaked outright if the walk were Heap::drop’s
own. So this loops over every owner list until a full pass frees
nothing, then zeroes accounting.
closed is set at the top and never cleared; tearing_down is held
across the loop so those re-entrant allocations are accepted rather
than panicking against the now-closed heap, and re-arms the panic once
the loop settles. Grayagain is emptied and its flags cleared (via
clear_generation_cursors) before any
free, preserving the pre-existing ordering.
Nonconvergence is a panic, in every build. A destructor that
allocates a new box on every pass would spin this loop forever; after
10,000 passes the loop panics naming that cause. An unconditional
panic is deliberate — a silent infinite hang inside close() in a
release build is strictly worse than a loud failure.
Panic safety. tearing_down is restored by an RAII guard, so a
panicking destructor (or the pass-cap panic) leaves the heap closed
with the closed-heap allocation panic correctly re-armed, never stuck
in a state that accepts allocations forever. A destructor that panics
mid-teardown does, however, leak the not-yet-freed remainder of the
list chain being drained: the local cursor into that chain dies with
the unwinding frame. Panicking in Drop is programmer error; the
guarantee kept here is that the heap’s state machine stays sound, not
that a panicking destructor is leak-free.
After this returns, every outstanding Gc<T> is dangling — callers
must ensure no Gc<T> outlives the Heap. Any operation that
dereferences a pre-close Gc/GcRef after this returns — deref,
downgrade header reads, account_buffer — is use-after-free,
exactly as it always was after Heap::drop; close does not widen or
narrow that contract, it only makes the free deterministic. Note the
quarantine caveat: LUA_RS_GC_QUARANTINE=1 catches use-after-sweep
while the heap lives, but this teardown drains the quarantined list
and frees its boxes for real — post-close stale dereferences have no
HDR_FREED tripwire left to hit. The VM’s own close paths
cannot hit this (free_all_objects clears every registry that holds
handles); giving boxes a checkable owner identity at downgrade time
is the #252-follow-up ownership redesign, out of scope here.
Trait Implementations§
Source§impl Drop for Heap
Last-resort teardown when the final Rc<Heap> drops without an explicit
close. Runs the same drain as Heap::drop_all, but with one contract
difference callers must know: no HeapGuard can be active for this
heap here — the Rc is mid-destruction, so there is no sound handle to
push (do not try to smuggle one in). A payload destructor that allocates
via GcRef::new during a plain unguarded Heap::drop therefore panics
loudly with the #253 no-guard message instead of allocating into a dying
heap. The supported deterministic-teardown path for VM heaps is
state-level close (close_state → free_all_objects), which pushes the
guard around the drain so such destructors work.
impl Drop for Heap
Last-resort teardown when the final Rc<Heap> drops without an explicit
close. Runs the same drain as Heap::drop_all, but with one contract
difference callers must know: no HeapGuard can be active for this
heap here — the Rc is mid-destruction, so there is no sound handle to
push (do not try to smuggle one in). A payload destructor that allocates
via GcRef::new during a plain unguarded Heap::drop therefore panics
loudly with the #253 no-guard message instead of allocating into a dying
heap. The supported deterministic-teardown path for VM heaps is
state-level close (close_state → free_all_objects), which pushes the
guard around the drain so such destructors work.