Skip to main content

RuntimeContext

Struct RuntimeContext 

Source
#[repr(C)]
pub struct RuntimeContext {
Show 18 fields pub heap: *mut Heap, pub pending_fault: *mut Fault, pub debug_frames: *mut DebugFrameStackHeader, pub shadow: *mut ShadowStackHeader, pub input_source: GcRef, pub unit_ref: GcRef, pub current_generation: u64, pub stack_left: u32, pub parse_detail: *mut ParseDetail, pub crash_snapshot: *mut SnapshotSlot, pub native_roots: *mut NativeRootStore, pub true_ref: GcRef, pub false_ref: GcRef, pub fault_message: *mut FaultMessage, pub small_ints: *const GcRef, pub debug_values: *mut DebugValueStackHeader, pub small_chars: *const GcRef, pub descriptors: [*const TypeDescriptor; 22],
}
Expand description

The hidden first argument to every generated function.

Matches the sketch in Appendix B. Fields are raw pointers because generated Cranelift code reads them at a fixed offset with a fixed calling convention; Rust borrows would not survive across the ABI boundary.

Fields§

§heap: *mut Heap§pending_fault: *mut Fault

The runtime’s one fault slot. Non-null in every context generated code is ever handedRuntime::context is its only producer and wires it to the runtime’s own Fault; the sole null-wiring constructor, RuntimeContext::placeholder, is unsafe and test-only.

That invariant is load-bearing (ADR-102): an Inst::CheckFault is a load of this pointer and a load of the Fault::KIND_OFFSET word behind it, with no null test. A host that hand-built a context with a null here and called generated code would fault the process rather than silently never observing a Praxis fault. a_wired_context_has_a_fault_slot is the gate.

§debug_frames: *mut DebugFrameStackHeader

The header of the runtime’s one crash-debugger frame stack (§9.3, ADR-021, ADR-104). Generated code claims one DebugFrameEntry in the prologue and restores the top in the epilogue; crate::crash_snapshot::praxis_snapshot_debug_chain reads [base, top) innermost-first to build the frames the crash REPL renders.

§11.6’s discipline in this struct is append at the end, never reorder: a field generated code reads that comes to point at something else keeps its position and bumps RUNTIME_ABI_VERSION, because deleting it and appending a replacement would shift every field below.

§shadow: *mut ShadowStackHeader

The header of the runtime’s one compiler-managed shadow stack (§12.3, ADR-019, ADR-101). Generated code claims a run of slots in the prologue by bumping the header’s top, spills live GcRefs into that run at safepoints, and restores top in the epilogue. The collector scans [base, top) via [RootSet].

§input_source: GcRef§unit_ref: GcRef

The cached immortal Unit — the “defined dummy” returned on fault paths (§10.4). Separate from input_source (which holds the read-in buffer when present), so fault sentinels are stable regardless of input.

§current_generation: u64§stack_left: u32

How much of the native-stack budget the live Praxis frames have not yet spent, in bytes (§9.2, §17.4, ADR-105). A prologue subtracts its own frame_cost; its epilogue stores back the value it found. A prologue guard faults with FaultKind::StackOverflow when what is left will not cover this frame, so deep recursion faults cleanly instead of overflowing the native stack and aborting the host (SIGABRT). Read by generated Cranelift code at a fixed offset, like the other #[repr(C)] fields above.

It counts down, and the direction is the design. Counting up needs the limit in generated code, which fixes it at compile time for every host. Counting down puts the limit in this field, so Runtime::context — the one producer every caller of generated code goes through — is the single place a stack size enters the system, and the backend never learns it. It also makes zero mean exhausted, which is the right thing for RuntimeContext::placeholder to say.

§parse_detail: *mut ParseDetail

Host-managed pointer to the runtime’s crate::ParseDetail slot (§7.11). The parser interpreter writes the richest parse mismatch into it on ParseFailed; the host (CLI / crash debugger) reads it after the fault. Generated code never touches this field — it is appended at the end of RuntimeContext so the offsets of all generated-code-read fields above are unchanged (§11.6 ABI stability).

§crash_snapshot: *mut SnapshotSlot

Host-managed pointer to the runtime’s crate::SnapshotSlot (§9.3). The first fault epilogue deep-copies the debug-frame chain into it before unwinding; the host reads the snapshot after the fault. Like parse_detail, generated code only passes it to praxis_snapshot_debug_chain — it is appended at the end of RuntimeContext for ABI stability.

§native_roots: *mut NativeRootStore

The runtime’s one native root store (ADR-114): what the runtime’s own Rust code holds live across an allocation, in one contiguous array.

Claimed and released by crate::roots::NativeScope, never by generated code — which is why it, like parse_detail and crash_snapshot, is appended at the end of the struct. It is the fifth arm of crate::roots::RuntimeRoots, which scans [0, len).

It has no reader outside praxis-runtime at all, so changing what it points at is not the ABI-version event it would be for shadow or debug_frames, which every generated prologue bump-allocates from. See ADR-114.

§true_ref: GcRef

The cached immortal true, alongside Self::unit_ref (§4.3). There are exactly two Bool values; the runtime allocates them once, so no comparison in a loop consumes arena storage.

§false_ref: GcRef

The cached immortal false. See Self::true_ref.

§fault_message: *mut FaultMessage

Host-managed pointer to the runtime’s FaultMessage slot (§9.1). praxis_panic and praxis_assert write the message the program gave; the host reads it when it renders the fault. Like parse_detail and crash_snapshot, generated code never touches it — it is appended at the end of the struct so every generated-code-read offset above is unchanged (§11.6 ABI stability).

§small_ints: *const GcRef

The base of the interned small-Int table (Immortals::small_ints), alongside Self::true_ref and Self::unit_ref (§4.3, crate::small_int).

Unlike those, this is a pointer to the objects rather than one of them: there are crate::SMALL_INT_COUNT of them, so generated code takes two loads — the base from here, then the element at a byte offset it computed at compile time from the literal’s value. That is what Inst::ConstGc emits, and it is why an in-range Int literal in a loop body is not a call, an allocation and a shadow-frame spill per iteration (docs/handovers/21-where-the-time-goes.md §3.5).

Generated code does read this one, so it would be a compatibility break if it moved — but it is appended like fault_message and its neighbours, so every offset above is unchanged.

§debug_values: *mut DebugValueStackHeader

The header of the runtime’s one crash-debugger value stack (§9.3, ADR-104). Generated code claims one slot per Gc local in the prologue, stores each local’s value there at the instruction that defines it, and restores the top in the epilogue. Each DebugFrameEntry in debug_frames names the base of its own call’s run.

The collector never traces this — it is the weak arm of crate::roots::RuntimeRoots (ADR-106), not a strong one. The slot type is Option<GcRef> rather than the shadow stack’s *mut GcHeader precisely so that impl RootSet for SlotStackHeader<*mut GcHeader> cannot reach it: the debug set is over-approximate and never cleared (MIR-16), and rooting it would undo MIR-01’s clears.

It is scanned, once per collection, immediately after the sweep: every slot naming storage that sweep just reclaimed becomes None. That is what makes a debug value always a live object or an absence, and never a reference to a block the allocator has since reissued as something else — which would render as a well-formed value of another type under the dead local’s own name, sharper than a dangling read.

Appended after small_ints, so every offset above is unchanged.

§small_chars: *const GcRef

The base of the interned ASCII-Char table (Immortals::small_chars), alongside small_ints (§4.3, crate::small_char, ADR-107).

Generated code never reads this one, which is what separates it from small_ints: the language has no character literal, so there is no GcConst::Char and nothing lowers to a load of this base. Its readers are the runtime’s own — abi.rs’s char_ref and the parser interpreter’s Rt::alloc_char, both of which reach the runtime only through a *mut RuntimeContext. It is therefore the native_roots/fault_message class of field, and it is appended at the end of the struct for their reason: every generated-code-read offset above stays where it was (§11.6 ABI stability).

§descriptors: [*const TypeDescriptor; 22]

Every built-in descriptor’s address, indexed by BuiltinTypeId (ADR-116). ADR-102’s inline type proof loads one slot of this and compares the object header’s descriptor word against it.

By value, and that is the decision. A pointer to crate::descriptor::BUILTINS would make the proof two dependent loads; the array makes it one load at a displacement the backend folds from RuntimeContext::descriptor_offset. Baking the address in as an iconst instead would be no load at all, but on aarch64 it costs movz+movk+movk (a static in this binary lives above 2³²) and it would make the compiler name a descriptor address, which it otherwise never does (docs/handovers/25-two-mallocs-per-runtime-call.md §3 F-4).

Filled by crate::descriptor::builtin_descriptor_addresses, which derives it from BUILTINS — so “slot i holds the descriptor whose id is i” has no second place it could be written wrong, and builtins_are_indexed_by_their_id stays the one gate on it.

Appended after small_chars, so every offset above is unchanged (§11.6 ABI stability). Generated code does read this one.

Implementations§

Source§

impl RuntimeContext

Source

pub const fn descriptor_offset(id: BuiltinTypeId) -> usize

The byte displacement of id’s slot in Self::descriptors, from the base of a RuntimeContext.

The one authority for the address ADR-102’s proof compares against, and the reason the backend holds no descriptor address at all: it folds this displacement, loads whatever the runtime put there, and compares. Which descriptor that is is the runtime’s answer rather than a pointer the compiler carried across the ABI — so the two cannot disagree about the address Int’s descriptor has, only about which slot it is in, and that is the enum discriminant.

Minted here rather than reached for with offset_of! from the backend for Fault::KIND_OFFSET’s reason one step further on: the element stride is part of the answer, and a backend that multiplied by its own size_of::<*const _>() would be a second statement of this layout.

Source

pub unsafe fn placeholder(input_source: GcRef) -> RuntimeContext

Construct a context with all pointers null and the input source set to the canonical placeholder. Real runtime setup (rooting the heap, installing a fault sink) is done via Runtime::context.

Generated code must never be run against a placeholder. The prologue is inline (ADR-101): it dereferences shadow unconditionally and without a null check, because the check cost every call in the language and Runtime::context is the only producer of a context generated code is ever handed.

§Safety

input_source must be a valid GcRef (or the caller must ensure no generated code dereferences it before the runtime is fully initialized).

Source

pub fn has_pending_fault(&self) -> bool

True iff a fault is currently pending on this context. Generated code checks this at safepoints after potentially-faulting operations (§10.4).

pending_fault is non-null once the context is wired to a runtime; a fault is pending when the pointed-at Fault slot says so.

Auto Trait Implementations§

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.