pub struct Safepoint<'a>(/* private fields */);Expand description
Proof that the collector was given a chance to run at this point.
Heap::alloc and Heap::alloc_with demand one, and Heap::pace —
which performs the Heap::maybe_collect — is its only producer. The
field is private to this module, so “allocate on the paced path without
pacing” has no spelling: obtaining the token is the pacing.
pace in turn takes a RuntimeRoots, which is constructible only from a
live RuntimeContext and is exhaustive over the runtime’s owners, so the
collection a token permits can never run against a partial root set.
Deliberately neither Copy nor Clone: one token, one allocation. A
wrapper that allocates twice paces twice.
§Generated code holds no token, and does not need one (ADR-113, ADR-119)
The Cranelift backend reproduces Heap::collection_is_due inline
(ADR-113) and, when it answers false, reads an interned small Int out of
crate::small_int’s table without entering this module at all. That is not
a forged token, and the reason is what this type means: the token is
permission to collect, not permission to allocate. It takes that branch
only where maybe_collect would have returned false, which is the branch
on which pace mints a token having done nothing at all. Where the predicate
answers true the inline path branches to praxis_alloc_int, which paces
through pace.
The inline path also does more than hand back an immortal (ADR-119): on the
branch the predicate leaves open, generated code claims a block out of a
page’s allocated bitmap, writes the header and the payload itself, and
bumps both live counters and the pacing charge — everything alloc_raw →
claim_block → occupy does, in that order, without entering this module.
Three parts carry that:
- Entry. Every store the sequence performs is dominated, in the emitted
Cranelift CFG, by the branch on
Heap::collection_is_due. Asserted with a dominator tree over a function with two claim sites, so it is a dominance claim and not a claim about one lowering’s shape. - Duration, which is the part that carries the weight. Between that
branch and the last store there is no call, and a collection begins
only inside
Heap::collect_inner, which generated code reaches only through apraxis_*wrapper. So not due on entry implies not due throughout: no sweep can observe the block half-written, and a re-entrant claim cannot be handed the same free bit because there is nothing to re-enter through. - State. The heap is left field-for-field as the paced path would have left it, both live counters included — each is only ever decremented elsewhere and never recomputed, so a skipped increment underflows rather than decays.
The store order (header, payload, allocated bit, counters) is a severity
ranking against a collection part 2 says cannot occur — not the safety
argument. All three parts are claims about an instruction stream, so all
three are carried by tests in crates/praxis-codegen-cranelift/src/lower.rs
that read the emitted Cranelift, and the displacements they name are checked
against live objects by the_claim_site_displacements_name_the_fields_they_claim_to
below. InlineInternSite and InlineClaimSite carry the half a type can:
which table may be probed, and which descriptors have a claim sequence at all.