Skip to main content

Heap

Struct Heap 

Source
pub struct Heap { /* private fields */ }

Implementations§

Source§

impl Heap

Source

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.

Source

pub fn unpause(&self)

Enable collection. Until this is called, step is a no-op (so the runtime can bootstrap without prematurely freeing objects).

Source

pub fn is_paused(&self) -> bool

Source

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.

Source

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.

Source

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.

Source

pub fn is_bootstrapping(&self) -> bool

Source

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).

Source

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.

Source

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.

Source

pub fn bytes_used(&self) -> usize

Bytes currently retained by GC-tracked objects (rough estimate).

Source

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.

Source

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.

Source

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.

Source

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).

Source

pub fn collections(&self) -> usize

Source

pub fn minor_collections(&self) -> usize

Source

pub fn full_collections(&self) -> usize

Source

pub fn last_mark_stats(&self) -> MarkerStats

Source

pub fn last_sweep_stats(&self) -> SweepStats

Source

pub fn allgc_cohort_stats(&self) -> AllGcCohortStats

Source

pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool

Source

pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool

Source

pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
where P: Trace + 'static, C: Trace + 'static,

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.

Source

pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
where P: Trace + 'static, C: Trace + 'static,

Backward barrier: if a black object receives a reference to a white child, gray the parent so the in-progress cycle will rescan it.

Source

pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
where P: Trace + 'static, C: Trace + 'static,

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

pub fn promote_all_to_old(&self)

Metadata transition used when entering generational mode after a full mark: all currently live objects become old.

Source

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.

Source

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.

Source

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.

Source

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:

Source

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.

Source

pub fn finish_callfin_phase(&self) -> bool

Finish an idle CallFin phase after the runtime has drained any pending to-be-finalized objects.

Source

pub fn gc_state(&self) -> GcState

Returns the current state of the incremental collector.

Source

pub fn allgc_count(&self) -> usize

Approximate number of live GC boxes across all heap owner lists.

Source

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.

Source

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_statefree_all_objects), which pushes the guard around the drain so such destructors work.

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

§

impl !Freeze for Heap

§

impl !RefUnwindSafe for Heap

§

impl !Send for Heap

§

impl !Sync for Heap

§

impl !UnwindSafe for Heap

§

impl Unpin for Heap

§

impl UnsafeUnpin for Heap

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.