Expand description
The CPU scratchpad, and running code with its stack there.
The R3000A has no data cache. Its 1 KiB scratchpad (BASE) is
the only data memory that loads from in one cycle; a main-RAM load stalls
for about six. Code whose frames spill a lot (long loops with more live
values than callee-saved registers, explicit work stacks in a frame) pays
that stall on every stack reload. ScratchpadStack::run moves $sp
into a scratchpad region for one call, so the same instructions run with
their frames in fast memory. Nothing about the code changes, only where
its frames live, so results are identical by construction. On Cortex,
running face selection, collision traces and the model face walker this
way cut gameplay work 3.9% in the emulator (2026-09-22). No console has
run it yet.
§Sharing the scratchpad
Games and the engine already keep working sets in the scratchpad (clip
planes, batch workspaces, bucket heads), usually in phases that reuse the
same bytes. There is deliberately no allocator. Instead each user names
its bytes as a const Region, and every set of regions that is live at
the same time is checked once at compile time with assert_disjoint:
use psx_rt::scratchpad::{assert_disjoint, Region, ScratchpadStack};
// Persistent across the whole frame.
const BUCKET_HEADS: Region = Region::new(0, 128);
// Face pass only: clip planes, then the batch workspace.
const CLIP_PLANES: Region = Region::new(128, 224);
const BATCH: Region = Region::new(224, 1024);
// Selection runs before the face pass claims its bytes.
type SelectionStack = ScratchpadStack<128, 1024>;
const _: () = assert_disjoint(&[BUCKET_HEADS, CLIP_PLANES, BATCH]);
const _: () = assert_disjoint(&[BUCKET_HEADS, SelectionStack::REGION]);A stack region may overlap a phase’s region only when that phase is not
live around the call, and the second assert_disjoint is the written
record of which regions are.
§What makes it safe
- Interrupts. psx-rt’s exception handler (installed by
interrupts::install_vblank_counter) uses only$k0/$k1and never touches$sp, so an IRQ taken on a scratchpad stack writes nothing there and returns to the same$sp. A game that runs with a different handler installed must check that it does not push onto the interrupted stack, and then says so withdeclare_stack_safe_handler(hk-psx’s CD wrapper saves to its own context, runs on its own stack and chains to psx-rt’s handler). Thescratchpad-stack-checkfeature traps a call made with interrupts enabled and any other handler in the vector. - Stack depth. The whole call tree of
fmust fit in the region minusSTACK_OVERHEAD(ScratchpadStack::BUDGET).stack-guardproves it from the linked image after every build: it finds each monomorphisedScratchpadStackentry, sums frame sizes down the static call graph, and fails on overflow, recursion, calls through a register and BIOS calls, none of which it can bound. Thescratchpad-stack-checkfeature adds a canary word at the bottom of the region, checked after every call. - Panics. SDK guests build with
panic = "abort", so nothing unwinds through the switch. Withpanic_immediate_aborta panic is a BREAK the exception handler halts on. Otherwise psx-rt’s panic handler moves$spback to the RAM stack before it prints, so reporting never runs on (or overflows) the scratchpad. - Nesting. A call made while already on a scratchpad stack runs
fin place, and the guard counts it as part of the outer call tree.
Structs§
- Region
- A byte range
start..endof the scratchpad. - Scratchpad
Stack - A call stack in scratchpad bytes
START..END.
Constants§
- BASE
- Address of the first scratchpad byte (KUSEG/KSEG0; there is no KSEG1 alias).
- CANARY
- The value the
scratchpad-stack-checkfeature keeps in a stack region’s bottom word. - SIZE
- Scratchpad capacity in bytes.
- STACK_
OVERHEAD - Bytes of a stack region a call tree cannot use: the 16-byte o32 argument home area above the first frame, and the canary word at the bottom of the region (reserved whether or not the check is enabled, so the budget does not depend on a feature).
Functions§
- assert_
disjoint - Fail const evaluation when any two of
regionsoverlap. - is_
on_ scratchpad_ stack - True while
$sppoints into the scratchpad. Always false on the host.