Skip to content

Carve EE invocation stacks from kernel-reserved RAM instead of the top of game RAM - #413

Open
0xLeathery wants to merge 4 commits into
ran-j:mainfrom
0xLeathery:feat/ee-stacks-kernel-area
Open

0xLeathery wants to merge 4 commits into
ran-j:mainfrom
0xLeathery:feat/ee-stacks-kernel-area

Conversation

@0xLeathery

Copy link
Copy Markdown

Problem

PS2Runtime::reserveAsyncCallbackStack hands out the 16 KB guest stacks that EeScheduler::invocationStackTop gives to interrupt handlers, RPC/stream callbacks, HLE calls and exit handlers. It carves them downward from the top of the 32 MB EE RAM (m_asyncCallbackStackTop = PS2_RAM_SIZE, floor 0x1F00000). That memory belongs to the game:

  • The first stack, 0x1FFC000–0x2000000, overlaps the default main-thread stack. SetupThread with stack -1 puts that stack at the top of RAM.
  • Sly 2 (SCUS-97316) keeps per-frame GS DMA packet chains at 0x1FFC000 and 0x1FD9280–0x1FF5500. Those are exactly the addresses the first runtime stacks occupy. They only survive while callback stack use stays shallow.

On real hardware, the EE kernel owns the first 1 MB of RAM and games load at 0x00100000 or above. The runtime already uses 0x0–0x80000 for kernel-mirrored state (the syscall table mirror). That leaves 0x00080000–0x00100000 free.

Change

  • Kernel-area stacks. Invocation stacks now come from [0x00080000, min(0x00100000, lowest PT_LOAD start)). loadELF works out the ceiling from the lowest physical segment start, so KSEG0 ELFs work. The defaults (0x80000/0x100000) apply when no ELF is loaded.
  • Fallback. If the low region can't fit a stack (an ELF loads at or below 0x80000, or more than 32 stacks are needed), reservation falls back to the previous top-of-RAM carving. It logs one line per loaded ELF when that happens.
  • Unchanged: the per-(thread, depth) caching in invocationStackTop, the stack size, and the mutex.

Testing

  • New tests in ps2_runtime_interrupt_tests.cpp, each written to fail first. They go through the real loadELF with a small synthetic ELF.
    • The first stack ends at or below 0x100000.
    • 24 stacks stay inside [0x80000, 0x100000) without overlapping.
    • An ELF loaded at 0x000C0000 keeps the stacks below it.
    • The 33rd stack falls back to top of RAM.
  • One existing GS test assertion (ps2_gs_tests.cpp, "callback invocation should use the reserved async stack pool") expected stacks at or above 0x01F00000. It now expects the kernel-area range.
  • ps2x_tests: 450/450.
  • Probes, not committed:
    • A handler wrote and read its whole 16 KB stack. The 0x0–0x80000 mirror stayed byte-identical, and memory at 0x100000 was untouched.
    • KSEG0 ELFs, a segment below 0x80000, mixed segment orders and back-to-back ELF loads all set the bounds correctly.
    • Exhaustion falls back at exactly the 33rd stack, with one log line.

Known limits, not changed here

  • EeScheduler::reset doesn't clear its stack cache, while loadELF resets the cursors. A second ELF load in one session could reuse cached stacks. This predates the change.
  • Stacks are never freed, and thread ids cycle through 2–255. A game that creates and destroys many threads could use up the 32 kernel-area slots and fall back to top of RAM.

Built and tested on macOS arm64 (Apple clang).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant