Skip to main content

Module shadow_stack

Module shadow_stack 

Source
Expand description

The compiler-managed shadow stack (§12.3, ADR-019, ADR-101).

§12.3 offers “compiler-managed shadow-stack frames or explicit root frames,” and the runtime has both: explicit root frames (RootScope, ADR-012) for the host, and this compiler-managed shadow stack that JIT-generated code spills into. At every GC safepoint (allocation / call that may allocate), the Cranelift backend stores the live GcRef locals into this function’s slots before the safepoint and reloads them after.

A frame is not an object. The runtime owns one contiguous region of slots for the whole program (SlotStack); a function’s frame is the run of slots between the top it found on entry and the top it left behind. Generated code claims a run by bump-allocating inline — load top, zero slot_count slots, store top + slot_count*8 back — and reclaims it by storing the saved base into top again. No allocation, no free, no call (ADR-101).

Slots are raw *mut GcHeader (not GcRef) because a slot is null until the backend writes a value into it: a local may be live across a safepoint (so it must be in the root set) before it has ever been assigned at runtime. GcRef is NonNull by construction, so it cannot represent that state; the raw pointer can, and push_roots skips nulls.

The collector reaches the whole stack through one RootSet impl on SlotStackHeader, which scans [base, top) in a single linear pass. That is exactly every live frame’s roots, because each frame occupies exactly its own slot run and the runs partition [base, top).

The header is #[repr(C)] and publishes SlotStackHeader::TOP_OFFSET so the backend emits a compile-time-derived displacement rather than a literal (Appendix B).

Structs§

DebugSlotCount
A count of debug value slots the debug value stack can actually hold: a u32 proven <= MAX_DEBUG_VALUE_SLOTS at construction (ADR-128 decision 3).
ShadowFrameGuard
A frame claimed from Rust, released when dropped.
SlotCount
A frame width the shadow stack can actually hold: a u32 proven <= MAX_SHADOW_SLOTS at construction.
SlotStack
The owner of one slot reservation and its header.
SlotStackHeader
The three-word header generated code bump-allocates against.

Constants§

MAX_DEBUG_VALUE_SLOTS
The maximum Gc locals a single JIT’d function may have — the bound on the dense index space the crash debugger reads (ADR-128 decision 3).
MAX_SHADOW_SLOTS
The maximum Gc roots a single JIT’d function may spill. The backend rejects (at compile time) any function exceeding this, through SlotCount; real Praxis functions have small root sets.
SHADOW_STACK_SLOTS
The size of the one shadow-stack reservation, in slots.

Functions§

push_frame
Claim count zeroed slots on ctx’s shadow stack, the way a generated prologue does.

Type Aliases§

ShadowStack
The whole shadow stack, as the runtime owns it.
ShadowStackHeader
The header generated code bump-allocates against.