LLVM: exit to dispatcher when mtmsr enables MSR[EE] - #23
Conversation
…verify/state-in-memory
…erify/state-in-memory
External interrupts are only deliverable when the host regains control at a dispatch boundary with MSR[EE] set. Generated code runs whole critical sections between boundaries, and the budget guards that end a burst sit at call sites, which in OS code fall almost entirely inside interrupt-disabled windows. A guest that waits by yield-spinning (enable, check, disable, reschedule) then never shows an enabled window at a boundary and pending interrupts starve. Pokemon Colosseum waits for VI retrace exactly that way and rendered one frame per 3-second timeout (0.25 fps) while the C backend ran at 30 fps. With this exit (and the matching runtime change that delivers a pending external interrupt at an EE=1 boundary) it runs at 28 fps and cold-boots past its THP intros. Mario Kart Double Dash is unaffected (48.7 -> 51.0). mtmsr is a supervisor instruction on OS paths only; the exit fires just on a 0->1 EE transition, mirroring the block-ending JITs.
Backend matrix on Colosseum, now that it runsAll five arms on the pinned Phenac City scene (
Reading the numbers honestly: Colosseum is retrace-locked at 30 fps -- the game's frame loop waits for the VI callback -- so fps here measures saturation, not backend speed, and no arm can beat C's 29.99. The signal is which arms hold the cap:
Two attempted optimizations are documented as not viable as-is: saturating the burst budget instead of exiting (deliver at the next guard), and marking pc+4 a region leader so the resume is native. Both build and both wedge Colosseum deterministically ~10-20 s into the scene, in an audio-handler interrupt loop -- delivery placement interacts with the AI/DSP handler's enable-interrupts-around-callback pattern in a way that needs its own investigation before optimizing this path. The shipped fix is the variant that has survived every run, the control titles, and a cold boot. |
|
Incorporated upstream: |
Summary
Pokemon Colosseum (GC6E01) rendered at 0.25 fps under the LLVM backend against 30 fps under the C backend, and could not cold-boot past its THP intro videos. Root cause: external-interrupt delivery starvation. This PR makes an
mtmsrthat enables MSR[EE] exit to the dispatcher, creating the delivery point the block-ending JITs get for free. The runtime half is ExpansionPak/RecompCore PR (delivers a pending external interrupt when a dispatch boundary shows EE=1); each half is safe alone, both are needed for the fix.The mechanism
OSDisableInterrupts -> SelectThread(yield) -> OSRestoreInterrupts, check callback flag, check a 3-second bus-clock timeout, repeat. The other tested titles block on wait queues; this spin is why only Colosseum failed.mtmsrwas lowered as a plain state write. Dolphin's Jit64/JitArm64 end the block atmtmsrand check external exceptions; this change mirrors that: after the MSR store, a 0->1 transition of EE takes a side exit (sync, store pc+4, return). mtmsr is supervisor-only, so the exit is rare and cheap.Measurements (Windows, pinned savestate, same runtime build)
Interrupt counters on the pinned scene: raised 484 -> 31,777; delivered 462 -> 29,856; pending-but-blocked checks 30% -> 0.5% -- matching the healthy C profile.
Investigation history: #20.