Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

Module scratchpad

Module scratchpad 

Source
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/$k1 and 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 with declare_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). The scratchpad-stack-check feature traps a call made with interrupts enabled and any other handler in the vector.
  • Stack depth. The whole call tree of f must fit in the region minus STACK_OVERHEAD (ScratchpadStack::BUDGET). stack-guard proves it from the linked image after every build: it finds each monomorphised ScratchpadStack entry, 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. The scratchpad-stack-check feature 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. With panic_immediate_abort a panic is a BREAK the exception handler halts on. Otherwise psx-rt’s panic handler moves $sp back 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 f in place, and the guard counts it as part of the outer call tree.

Structs§

Region
A byte range start..end of the scratchpad.
ScratchpadStack
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-check feature 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 regions overlap.
is_on_scratchpad_stack
True while $sp points into the scratchpad. Always false on the host.