diff --git a/ISSUES.md b/ISSUES.md index 4fc6b824..3179caa3 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -559,3 +559,70 @@ Tooling: `tools/crop_launcher_assets.ps1` (crop + knockout), `tools/shot_launcher.ps1` (screenshot the GL launcher window). Mockup source: `C:\Users\Matthew\Desktop\ef772e04-a7db-4ecd-98bb-eb75a01de0a6.png` (1448×1086). Pure-cosmetic; does not block Phase 4/5 wiring. + +--- + +## Issue #7 — sljit live execution is unvalidated (pure-live save-load wedge) + +**Status:** open, root-caused — fix in progress (branch `feat/sljit-backend`) +**Date opened:** 2026-06-15 +**Area:** overlay Tier-2 sljit backend (`runtime/src/overlay_loader.c`, +`overlay_sljit.c`, `code_provider.c`, `overlay_sljit.c` resolution) + +### Symptom + +With `PSX_OVERLAY_SLJIT_LIVE=1` (the prototype toolchain-less production path), +save/load **wedges the guest**: `reason=atexit, pc=0`, dispatch looping a +BIOS/kernel address (`0xB0`/`0x650`). Reproduces only in live mode — the +dev differential path (`overlay_diff_on`) is clean (0 divergences / 395 shadow +calls). Flagged in commit `81cf21b` as Known Bug #1. + +### Root cause (NOT the emitter) + +The MIPS→sljit emitter + block-local register allocator are **proven correct** +this session: 66/66 emitter unit, 22/22 `$sp`-balance probe, and byte-identical to +the pre-regalloc emitter across 313 overlay functions +(`runtime/tests/sljit_{emit_test,sp_probe,offline_diff}.c`). The crash is in the +**live-execution wiring**, which bypasses the validation harness: + +- `run_shadow_diff` (the device-touch detector + diff gate) is gated on + `s_diff_mode`. Live mode sets `s_sljit_live`, not `s_diff_mode`, so in live mode + **the diff never runs.** Therefore: + - `device_touch` is **never computed** for a live shard → a device-touching + function runs its shard and **double-executes** its SIO/memory-card/DMA I/O + against real hardware state, corrupting the kernel byte-handler path → the + `atexit, pc=0`, kernel-`0xB0/0x650` livelock. *(Known Bug #3 — leading cause.)* + - Shards run with `diff_passes == 0`, i.e. **completely unvalidated**. + *(Known Bug #2.)* +- Separately, the verify budget counts **cumulative** (not consecutive) clean + passes — `diff_passes` never resets on a divergence — so even in dev mode an + intermittently-wrong shard could reach budget on lucky passes. + +The historical "`$sp` off by 0x18 at 0x12E478" crash was a **different, already-fixed +bug** (diff over-scoping the call tree, fixed by per-function isolation in +`81cf21b`) — not this one, and not a codegen bug. + +### Fix (SLJIT.md §11 Phase A) — runtime-only, no regen + +Collapse blind-live into validated-live: route live-mode shards through the diff +gate (`(s_diff_mode || s_sljit_live)` at the dispatch gate), so `device_touch` is +computed and a shard runs native live ONLY after a clean verify budget; and reset +`diff_passes` to 0 on any divergence (consecutive-clean = "0 divergences"). After +this there is no blind path: device + diverging shards stay on the interpreter. + +### Related gaps (same area, tracked in SLJIT.md §11 Phases B–C) + +- **gcc>sljit precedence not enforced** (LIFO chain order can put an sljit shard + ahead of a gcc shard for the same region). → Phase B. +- **Non-dev machine never triggers sljit generation in normal play** (on-miss hook + gated behind a dead env-only flag; `auto` resolves to gcc on every machine because + `autocompile_configured()` tests for a config *string*, not a real toolchain). → + Phase C. + +### End-to-end success criteria (per DEBUG.md end-to-end rule) + +`PSX_OVERLAY_SLJIT_LIVE=1` (or `auto`→sljit on a toolchain-less box): play through +save-load, FMV, menus, and combat with **0 divergences, 0 wedges**, shards +validated-then-promoted, device functions correctly pinned to the interpreter. +"Diff is clean" or "shard validated" is NOT success until the full save-load loop +closes live. diff --git a/runtime/include/code_provider.h b/runtime/include/code_provider.h index 13dac831..bde721d7 100644 --- a/runtime/include/code_provider.h +++ b/runtime/include/code_provider.h @@ -15,8 +15,9 @@ * rescan on the emu thread. Seconds of latency; many fragments per run. * - SYNC / ON-MISS (compile_fragment): the sljit model. JIT a single fragment * in-process at a dispatch miss, sub-ms, return a native fn the caller - * registers immediately. (Wired into the dispatch path in SLJIT.md step 5; - * defined here so the abstraction is honest about both modes, not gcc-only.) + * registers immediately. Wired into the dispatch path (overlay_loader.c + * try_sljit_region); the sljit shard is then VALIDATED via the same-state + * diff before it runs live (see SLJIT.md §11 / overlay_loader_dispatch). * * A provider sets the hooks it supports and leaves the rest NULL; callers * null-check before invoking. The gcc provider's batch hooks are thin pass- diff --git a/runtime/src/code_provider.c b/runtime/src/code_provider.c index 3071af50..7705b3fb 100644 --- a/runtime/src/code_provider.c +++ b/runtime/src/code_provider.c @@ -28,9 +28,10 @@ static const CodeProvider s_gcc = { /* ---- sljit provider: in-process JIT (sync on-miss only, v1) ---------------- */ /* No batch/async path: sljit produces synchronously at a dispatch miss, so - * request/busy/poll_main are inert. compile_fragment delegates to the emitter, - * which currently declines every fragment (the emitter is SLJIT.md step 4) — - * safe by construction (precision over recall). */ + * request/busy/poll_main are inert. compile_fragment delegates to the MIPS->sljit + * emitter (overlay_sljit_try_compile), which compiles the supported instruction + * classes and declines the rest (fn=NULL) — safe by construction (precision over + * recall). The returned shard is validated via the same-state diff before live. */ static int sljit_request(void) { return 0; } static int sljit_busy(void) { return 0; } static void sljit_poll_main(void) { } diff --git a/runtime/src/overlay_sljit.c b/runtime/src/overlay_sljit.c index 4092922c..f72e427a 100644 --- a/runtime/src/overlay_sljit.c +++ b/runtime/src/overlay_sljit.c @@ -1,9 +1,12 @@ /* overlay_sljit.c — Tier-2 self-contained in-process JIT backend (sljit). * See overlay_sljit.h for the tier model and the precision-over-recall SAFETY - * CONTRACT. This file currently provides: backend-selection policy, sljit - * availability + a real codegen smoke test, and the try_compile entry point - * that (until the MIPS->sljit emitter lands) safely declines every fragment to - * the interpreter. The validated gcc path is untouched. */ + * CONTRACT. Provides: backend-selection policy, sljit availability + a real + * codegen smoke test, and the MIPS->sljit emitter (overlay_sljit_try_compile) + * that parallels dirty_ram_interp.c — leaf+multi-block functions with branches, + * mult/div, jal/jalr calls, jr-tables, GTE/COP2, unaligned mem, and block-local + * GPR register allocation. Unsupported instructions/shapes abort the WHOLE + * fragment (return fn=NULL → caller runs the interpreter), so the emitter can + * decline but never mis-compile. The validated gcc path is untouched. */ #include "overlay_sljit.h"