JitArm64: implement the StaticRecomp fallback contract - #6
Open
dougchansan wants to merge 1 commit into
Open
Conversation
Static recompilation was inert on Apple Silicon. Jit64 implements a two-part
contract as the StaticRecomp fallback -- disable block linking, and call
StaticRecompShouldYieldAt from the dispatcher -- and JitArm64 implemented
neither. Linked arm64 blocks chained without returning to the dispatcher, so
StaticRecompCore never regained control to ask whether the module covered the
next address. The module was entered once at boot and never again, and every
arm64 "static recomp" measurement was really JitArm64.
Adding the hook alone is not enough, and this is the subtle part. JitArm64
keeps the PC in DISPATCHER_PC (W26) and only spills it to PPCSTATE at
do_timing, which the yield exit bypasses -- so the core resumed from a stale
ppcState.pc and dispatched the module at the wrong address. Jit64's identical
hook is safe only because x86 keeps the PC in memory throughout. Hence the
explicit STR before the exit branch. This is the one place the two JITs are
not interchangeable.
Default on, matching x86-64 where StaticRecomp is already the default core;
MODERNGEKKO_ARM64_STATICRECOMP=0 restores JitArm64. Note this changes default
behaviour for arm64 users of this branch.
Measured on Apple Silicon (M5, macOS 26.1, Apple clang 17). Null backend,
uncapped, 3 interleaved repeats with reverse-order control, from savestates.
Two independent sessions; the ratios reproduce to within ~2%.
game static fps jit ratio
Mario Kart GM4E01 0.9705 58.2 5.7059 5.88 (race)
Luigi's Mansion 1.2252 73.4 5.4671 4.46 (gameplay)
Pokemon Colosseum 1.3756 82.5 3.8508 2.80 (title)
Paper Mario TTYD 1.8102 108.5 3.6882 2.04 (title)
Three of four clear realtime, all four with zero fallback steps -- the modules
run the games rather than leaning on the JIT underneath. Luigi's Mansion,
Colosseum and TTYD needed no per-game work at all; they are one
`moderngekko-port build` each.
FP-unavailable exceptions run 0.0014-0.0023% of dispatches on all four titles,
against 12.4% before this branch's sync and interrupt fixes, so that repair is
title-agnostic rather than MKDD-shaped.
MKDD on its reference savestate reads 0.9273x and 0.9705x in two sessions
against 0.7457x before the rebase -- same scene, same machine, so +24% to +30%
rather than one precise figure. Note the host was never idle (foreign CPU
20-273% across arms); the tight standard deviations, not a quiet machine, are
what make these trustworthy.
Correctness. DolRecomp's suite passes 16/16 including pc_reference, fpscr and
float_semantics. Lockstep on a 150 s race run reports divergences, and they are
NOT introduced here: with MODERNGEKKO_NO_FALLBACK_JIT=1, where this patch is
inert, the count is identical, matching what was recorded on x86-64 before any
of this work. The lockstep-loop-boundary commit in this series explains and
fixes the largest class of them (271 -> 107); the remaining 107 are
undiagnosed, none diverge on pc, and 2 touch MMIO, which a replay cannot
reproduce by construction.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Static recompilation was inert on Apple Silicon.
Jit64implements a two-part contract as the StaticRecomp fallback — disable block linking, and callStaticRecompShouldYieldAtfrom the dispatcher — andJitArm64implemented neither. Linked arm64 blocks chained without returning to the dispatcher, soStaticRecompCorenever regained control to ask whether the module covered the next address. The module was entered once at boot and never again, which means every arm64 "static recomp" measurement was reallyJitArm64.The part that isn't obvious
Adding the hook alone is not enough.
JitArm64keeps the PC inDISPATCHER_PC(W26) and only spills it toPPCSTATEatdo_timing, which the yield exit bypasses — so the core resumed from a staleppcState.pcand dispatched the module at the wrong address.Jit64's identical hook is safe only because x86 keeps the PC in memory throughout. Hence the explicitSTRbefore the exit branch.This is the one place the two JITs are not interchangeable.
Behaviour change
Default on, matching x86-64 where StaticRecomp is already the default core.
MODERNGEKKO_ARM64_STATICRECOMP=0restoresJitArm64. Flagging explicitly: this changes default behaviour for arm64 users of this branch.Results
Apple Silicon (M5, macOS 26.1, Apple clang 17). Null backend, uncapped, 3 interleaved repeats with reverse-order control, from savestates. Two independent sessions; ratios reproduce to within ~2%.
GM4E01GLME01GC6E01G8ME01Three of four clear realtime, all four with zero fallback steps — the modules run the games rather than leaning on the JIT underneath. Luigi's Mansion, Colosseum and TTYD needed no per-game work; each is one
moderngekko-port build.FP-unavailable exceptions run 0.0014–0.0023% of dispatches across all four titles, against 12.4% before this branch's sync and interrupt fixes — so that repair is title-agnostic rather than MKDD-shaped.
MKDD on its reference savestate reads 0.9273x and 0.9705x in two sessions against 0.7457x before the rebase — same scene, same machine, so +24% to +30% rather than one fake-precise figure.
The host was never idle (foreign CPU 20–273% across arms). The tight standard deviations, not a quiet machine, are what make these trustworthy.
Correctness
DolRecomp's suite passes 16/16, including
pc_reference,fpscrandfloat_semantics.Lockstep on a 150 s race run reports divergences, and they are not introduced here: with
MODERNGEKKO_NO_FALLBACK_JIT=1, where this patch is inert, the count is identical and matches what was recorded on x86-64 before any of this work. #4 in this series explains and fixes the largest class (271 → 107). The remaining 107 are undiagnosed; none diverge onpc, and 2 touch MMIO, which a replay cannot reproduce by construction.Depends on #3 to build.
Performance impact, stated plainly
This patch does not make static recompilation faster on arm64 — it makes it run at all. Before it, the module was entered once at boot and every subsequent instruction went through
JitArm64, so the honest comparison is "static recomp executes / does not execute", not a percentage.What it costs against the JIT is real and worth being explicit about. Uncapped, Null backend, MKDD race scene:
JitArm64So enabling static recompilation is a 5.9x slowdown versus the JIT on this host. That is the price of running an AOT-compiled native module instead of a dynamic recompiler, and it is why the flag exists.
With rendering and a real worst case it is tighter than the Null figures suggest:
Anyone adopting this on Apple Silicon should expect roughly 51 fps single-player and 41 fps split-screen, not 60.
The gain that is attributable to this branch as a whole: MKDD on its reference savestate reads 0.9273x and 0.9705x across two sessions against 0.7457x before the RecompCore rebase — +24% to +30% on the same scene and machine. That comes from the branch's sync and interrupt fixes; this patch is what lets them be measured on arm64 at all.