pub struct GcHeader { /* private fields */ }Expand description
Header prepended to every GC allocation (§12.2).
Layout is #[repr(C)] and the payload follows this header in the same
allocation, at GcHeader::payload_offset_for bytes from the header’s
address — not necessarily at size_of::<GcHeader>(), because an
over-aligned payload is padded forward. The header is addressable as
*mut GcHeader and the payload is reached via GcHeader::payload.
The fields are private: the allocator (Heap::alloc_raw) is
the only constructor, so an initialized header is the only kind that exists,
and payload_offset cannot disagree with the address the allocator handed
to the payload initializer.
Sixteen bytes, and every field in them has a reader on a hot path
(ADR-109). This prefixes every allocation in the language, so a field here is
a tax on every object a program makes — and once #[repr(C)] padding is
counted, a four-byte field costs eight. Adding a field here is not a local
decision — it moves page::MIN_BLOCK, the whole size-class ladder, and the
immediate generated code folds to reach a payload, so it owes an ABI bump
and an ADR.
Implementations§
Source§impl GcHeader
impl GcHeader
Sourcepub const DESCRIPTOR_OFFSET: usize
pub const DESCRIPTOR_OFFSET: usize
Where the descriptor pointer sits, relative to the header’s address.
Generated code reads it: an Inst::ExtractScalar proves the object’s
type inline (ADR-102) — one load from here, one compare against the
scalar descriptor’s address — rather than calling praxis_int_load and
letting the wrapper prove it. The check is what makes the folded payload
offset below the offset the allocator actually used, and it is what keeps
a praxis check-clean program extracting an Int from a Unit a
refusal rather than an out-of-bounds read.
Exported from here, derived with offset_of!, because ADR-039 decision 1
made the fields private to this module: the backend cannot reach for
the offset itself, and the alternative — writing 0 in the backend —
is exactly the re-derived literal that decision exists to prevent.
payload_offset_for is the same idea one
step further along.
Sourcepub const PAYLOAD_OFFSET_FIELD_OFFSET: usize
pub const PAYLOAD_OFFSET_FIELD_OFFSET: usize
Where the recorded payload displacement sits, relative to the header’s address (ADR-119).
Read by nothing in generated code and written by one thing: the
inline claim sequence, which lays out a header itself. It is a u16, and
what it must be handed is what GcHeader::payload_offset_for answered
for the descriptor being stored beside it — the same value
Heap::occupy writes, from the same call. ADR-039 decision 1 is still
the authority; this is a second transcription of its answer, which is why
InlineClaimSite carries the offset and the
value together rather than letting a caller pair them.
Sourcepub const HEAP_ID_OFFSET: usize
pub const HEAP_ID_OFFSET: usize
Where the owning HeapId sits, relative to the header’s address
(ADR-119).
The provenance word ADR-039 decision 2 made the mark phase’s first read.
Generated code writes it — with the id it loaded out of the live Heap
it claimed the block from, never a compile-time constant: there is no
heap at compile time, and a debugger session replaces its Jit while
keeping its Runtime ([crate::GcConst]’s reason, one field along).
Sourcepub const fn payload_offset_for(payload_align: usize) -> usize
pub const fn payload_offset_for(payload_align: usize) -> usize
Where the payload begins, relative to the header’s address, for a payload with the given alignment.
This is the object-layout calculation: Heap::alloc_raw uses it to
place the payload, payload_offset records what it returned, and
generated code calls it to reach a payload directly. const so codegen
can fold it into an immediate.
§Panics
Panics if payload_align is not a power of two.
Sourcepub fn descriptor(&self) -> &'static TypeDescriptor
pub fn descriptor(&self) -> &'static TypeDescriptor
The descriptor describing this object’s payload (§11.4).
Descriptors are always 'static (built-in constants or compiler-emitted
statics), so the returned lifetime is unconstrained.
§Panics
Panics if the header has been poisoned by the sweep. Callers that may
hold a stale reference must check GcHeader::is_poisoned first; the
collector does this via GcHeader::heap_id.
Sourcepub fn payload<T>(&self) -> *mut T
pub fn payload<T>(&self) -> *mut T
Pointer to this header’s payload bytes.
The caller is responsible for knowing the payload type (via the descriptor); this is the low-level escape hatch used by descriptor callbacks and typed accessors.
Sourcepub fn heap_id(&self) -> Option<HeapId>
pub fn heap_id(&self) -> Option<HeapId>
The heap that owns this allocation, or None if the header is poisoned.
Sourcepub fn is_poisoned(&self) -> bool
pub fn is_poisoned(&self) -> bool
Whether this header’s storage has been swept.
A poisoned header is not an object: its payload has been finalized and its bytes may be reused. Reading anything but this predicate off it is a bug.
“May be reused” is why this predicate has a shelf life. It answers
“has this block been reclaimed” only until the allocator reissues the
block and writes a fresh header over the poison. The collector’s weak
arm (crate::debug::DebugFrameStackHeader::clear_reclaimed, ADR-106)
is the one caller that depends on that, and it runs inside the
collection — after the sweep and before any allocation — for exactly
this reason.