Skip to content

Mirror DolRecomp's cycle_budget, and bump the CPU ABI to 4 - #21

Open
dougchansan wants to merge 1 commit into
ExpansionPak:moderngekko-vendorfrom
dougchansan:fix/cpu-abi-cycle-budget
Open

Mirror DolRecomp's cycle_budget, and bump the CPU ABI to 4#21
dougchansan wants to merge 1 commit into
ExpansionPak:moderngekko-vendorfrom
dougchansan:fix/cpu-abi-cycle-budget

Conversation

@dougchansan

@dougchansan dougchansan commented Sep 5, 2026

Copy link
Copy Markdown

The break

DolRecomp added s64 cycle_budget immediately after downcount in struct CPUState ([src/cpu/cpu.h], commit f0a86be "rewrite llvm backend", Aug 20 2026). ModernGekko tracked that change in 0fbc214 ("update recomp runtime") — its include/moderngekko/cpu_state.h carries cycle_budget and declares MODERNGEKKO_CPU_ABI_VERSION 4u.

GXRuntime's core/cpu.h, which mirrors the same generated-code prefix by contract, never picked it up. It is still at ABI 3 with no cycle_budget.

The header states the rule that was broken:

Do not insert fields into the mirrored prefix without coordinating a generated-code ABI bump with DolRecomp.

Symptom

ModernGekko master pins vendor/dolphin at 55c7b023 — this branch's tip, at ABI 3 — while its own runtime requires ABI 4. So a freshly recompiled module is rejected at load:

initialization failed: native module was rejected: CPU ABI mismatch

That reproduces on current upstream for anyone building a title from scratch. Trees cut before f0a86be are unaffected, which is why this went unnoticed.

The change

  • Add s64 cycle_budget; directly after downcount, matching DolRecomp's offset.
  • Bump GXRUNTIME_CPU_ABI_VERSION 3 → 4, and document v4 alongside the existing v2 note.
  • Update the ABI static asserts in GXRuntime/tests/runtime_tests.c (the == 3u assert would otherwise fail to compile), and add offset/size asserts pinning cycle_budget behind downcount.

Verification

Field order and names now match DolRecomp main exactly; the only textual differences are mem2/mem2_size, which are union aliases of exram/exram_size at identical offsets. This also brings GXRuntime into agreement with ModernGekko's cpu_state.h, which the two headers require since they share the include guard DOLRECOMP_CPU_H and whichever is included first silently wins.

Built and tested on a Raspberry Pi 4 (aarch64, gcc, Linux 6.18):

  • GXRuntime/tests/runtime_tests.c with the updated asserts compiles clean.
  • The companion conformance test in ModernGekko compiles clean against this branch's header with -Wall -Wextra, and fails to compile against the pre-fix header at 55c7b023, leading with the ABI version assertion. The drift is now a build error rather than a load-time rejection.

A full ModernGekko configure against this branch was not possible on that machine — this repo's own nested submodules were not populated (Externals/cpp-ipc alias failure, vendor/dolphin/DolRecomp missing its CMakeLists.txt) and SDL3 is absent, both unrelated to this change.

Companion

ModernGekko needs no source change; it is already at ABI 4. Once this merges, its vendor/dolphin pin moves from 55c7b023 to this commit. That bump is filed separately and is stacked on this PR.

DolRecomp added `s64 cycle_budget` immediately after `downcount` in its
generated-code CPUState in f0a86be ("rewrite llvm backend"). GXRuntime's
header mirrors that prefix by contract, but was never updated, so every
field from `exram` onward sat eight bytes off between generated code and
the chassis.

The header already warns not to touch the mirrored prefix without a
coordinated ABI bump, so take the bump here: GXRUNTIME_CPU_ABI_VERSION
goes to 4. That makes the mismatch a clean load-time rejection instead of
silent corruption, and it re-keys consumers that fold the CPU ABI into
their module cache identity.

Field order and offsets now match DolRecomp main exactly.
@dougchansan
dougchansan force-pushed the fix/cpu-abi-cycle-budget branch from f33a2ca to 52c6b46 Compare September 5, 2026 00:05
@dougchansan

Copy link
Copy Markdown
Author

Verification, since the description notes CI was the only thing available.

A local ModernGekko tree here already carries this change substantively — GXRUNTIME_CPU_ABI_VERSION 4u with s64 cycle_budget directly after downcount — reached independently through a vendor rebase rather than from this branch. It builds under MSVC 14.50 and clang-cl 20.1.8 on Windows, Apple clang 17 on arm64 macOS, and GCC 13.3 on Linux, and has been running recompiled modules for four titles across both backends all week. So the substantive half of this PR is exercised, not just compiled.

The three headers agree byte-exactly. Measured rather than eyeballed, compiled against each header in turn:

ABI downcount cycle_budget sizeof(CPUState)
GXRuntime/include/core/cpu.h 4 3480 3488 3536
moderngekko/cpu_state.h 4 3480 3488 3536
DolRecomp/src/cpu/cpu.h line 111 line 112

Delta is 8, so the fields are contiguous with no padding inserted, and the mirrored prefix lines up on both sides of the generated-code boundary. That is the invariant the header comment asks for.

The two new static asserts hold. Compiled standalone against core/cpu.h:

_Static_assert(GXRUNTIME_CPU_ABI_VERSION == 4u, ...);
_Static_assert(offsetof(CPUState, cycle_budget) > offsetof(CPUState, downcount), ...);
_Static_assert(sizeof(((CPUState*)0)->cycle_budget) == 8u, ...);

All three compile clean with clang 20.1.8 -std=c11.

On the symptom: initialization failed: native module was rejected: CPU ABI mismatch comes from dolphin_runtime.cpp:589, and it is fatal only because allow_interpreter is false. With --allow-interpreter the same mismatch clears config.module and boots anyway — at interpreter speed, silently. Worth flagging in case anyone hits this and reaches for that flag to make the error go away: it turns a loud failure into a quiet 30x slowdown, which is an easy way to benchmark the interpreter by accident.

One difference from this branch worth noting: the tree here has the cycle_budget field but not the two asserts this PR adds, so those were only verifiable standalone as above — CI on this branch is still the thing that proves them in place.

@dougchansan

Copy link
Copy Markdown
Author

Rebuilt a module both ways to check the fix end to end, since the header change alone does not show whether a real module round-trips.

Controlled A/B: same game (GC6E01), same recompiler, same moderngekko-port invocation, same output hash directory (…-bc5fd54029dea0e3). The only difference between the two builds is GXRUNTIME_CPU_ABI_VERSION in GXRuntime/include/core/cpu.h.

ABI 3 — reproduces the reported failure

$ moderngekko-module-info gGC6E01_recomp.dll
module load failed: 4 (CPU ABI mismatch)

$ moderngekko-run --game … --module gGC6E01_recomp.dll
initialization failed: native module was rejected: CPU ABI mismatch
EXIT=1

Byte-identical to the message in the description. Note the module builds and links fine — the break is only visible at load, which is why it survives CI that stops at compiling.

ABI 4 — accepted and running natively

$ moderngekko-module-info gGC6E01_recomp.dll
game_id=GC6E01
module_abi=3
cpu_abi=4
cpu_state_size=3536
entry_point=0x80003154
code_ranges=1
smc_ranges=105
chunk_ranges=154

Loaded, booted, and ran a savestate for 25 s:

frames advanced 1281 in 25 s (~51 fps)
speed 1.594
native instructions 2,818,856,332
fallback 0
SMC hash mismatches 0

fallback=0 is the part worth reading: the module is not merely accepted, it is executing natively throughout, so the cycle_budget field is at an offset the generated code agrees with. A layout disagreement here would show up as corruption or a fallback storm rather than a clean run.

Prior state, for completeness

Every module previously built in this tree already reports cpu_abi=4 — six checked across both backends and three output directories — because the tree reached ABI 4 through a vendor rebase. The ABI 3 build above was produced by reverting the constant specifically to reproduce the failure, then restored.

So: fix confirmed, and the failure it fixes is confirmed to be exactly the one reported.

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