Skip to main content

Safepoint

Struct Safepoint 

Source
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_rawclaim_blockoccupy does, in that order, without entering this module. Three parts carry that:

  1. 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.
  2. 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 a praxis_* 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.
  3. 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.

Auto Trait Implementations§

§

impl<'a> !RefUnwindSafe for Safepoint<'a>

§

impl<'a> !Send for Safepoint<'a>

§

impl<'a> !Sync for Safepoint<'a>

§

impl<'a> !UnwindSafe for Safepoint<'a>

§

impl<'a> Freeze for Safepoint<'a>

§

impl<'a> Unpin for Safepoint<'a>

§

impl<'a> UnsafeUnpin for Safepoint<'a>

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 = !

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.