StaticRecomp: deliver pending external interrupts at EE-enabled dispatch boundaries - #18
StaticRecomp: deliver pending external interrupts at EE-enabled dispatch boundaries#18dougchansan wants to merge 14 commits into
Conversation
Refactor target_include_directories for MSVC and non-MSVC.
The burst loop only broke for hook-raised synchronous exceptions; external interrupts waited for the next timing slice. The interpreter effectively offers a delivery point at every block boundary between an interrupt enable and the following disable, but native bursts surface at budget-guard exits, which sit at call sites and land almost entirely inside interrupt-disabled OS windows. A guest waiting in a yield-spin loop then starves: Pokemon Colosseum held a pending VI interrupt with MSR[EE]=0 at 30% of delivery checks and rendered one frame per 3-second timeout. Break the burst and deliver as soon as a boundary shows a pending external interrupt with MSR[EE] set. Paired with the DolRecomp change that exits to the dispatcher when mtmsr enables EE, Colosseum goes from 0.25 to 28 fps against 30 for the C backend, and cold-boots past its THP intros. Mario Kart Double Dash is unaffected (48.7 -> 51.0).
STATICRECOMP_LOCKSTEP_FILTER=<substr> reports only divergences whose diff contains the substring, and STATICRECOMP_LOCKSTEP_NODEDUP=1 re-checks entry PCs instead of once each. By default the checked-PC set capped output at one report per diverging entry, which read as "74 divergences" when it meant 74 distinct PCs; filtering to msr and disabling the dedup is what exposed the EE starvation pattern on Colosseum.
Delivering at every EE-enabled dispatch boundary preempts handlers that run their callback with interrupts enabled (the AX audio frame callback) in the middle of their work, and re-entering them at each boundary can consume the guest instead of letting the callback finish. The block-ending JITs deliver only where the guest executed mtmsr; match that by checking the instruction before the boundary PC. On Colosseum this removes a probabilistic stall that could catch every backend at scene transitions: the C backend went from one crawl in two uncapped runs to three clean passes at 53.6-56.8 fps, and the AOT arm stays clean. The mtmsr delivery point keeps the retrace-wait fix intact (the capped scene still renders at 25-29 fps against 30 for C).
|
Withdrawing this. The evidence it was built on does not hold up. The Colosseum symptom this PR fixed was an artifact of a modified DOL. The extracted game we were benchmarking, Re-measured against a clean DOL (
Locked to 60 fps, frames advancing, no starvation and no hang. The Two things worth keeping out of this:
Apologies for the noise. The lesson we are adopting: benchmark only from cleanly extracted, unmodified DOLs, and verify |
Summary
Runtime half of the Pokemon Colosseum interrupt-starvation fix (backend half now upstream in DolRecomp
1bec355asemitStateWrite's EE exit; investigation in ExpansionPak/DolRecomp#20). Without this, the mtmsr side exit creates a dispatch boundary but a pending external interrupt still waits for the next timing slice -- and native bursts surface almost exclusively inside interrupt-disabled OS windows, so Colosseum's retrace wait starves and renders one frame per 3-second timeout (0.25 fps).Delivery semantics, final form: the burst loop delivers a pending external interrupt only at boundaries whose preceding guest instruction is
mtmsr-- the same delivery points the block-ending JITs use (they end the block at mtmsr and check there). Two earlier iterations are worth recording:A second commit adds two lockstep options used in the diagnosis:
STATICRECOMP_LOCKSTEP_FILTER=<substr>(report only divergences containing the substring) andSTATICRECOMP_LOCKSTEP_NODEDUP=1(re-check entry PCs; the checked-PC dedup otherwise caps output at one report per diverging entry).