Skip to content

Reproduce game weak data with a PCH - #130

Open
KakarottoCake wants to merge 3 commits into
doldecomp:mainfrom
KakarottoCake:pch-j3djoint
Open

Reproduce game weak data with a PCH#130
KakarottoCake wants to merge 3 commits into
doldecomp:mainfrom
KakarottoCake:pch-j3djoint

Conversation

@KakarottoCake

@KakarottoCake KakarottoCake commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

This reproduces the MWCC precompiled-header behavior responsible for the J3DJoint weak Vec literals appearing in .data in retail game objects.

  • add an SMS PCH modeled on the equivalent zeldaret/tww fix
  • apply it selectively to 40 evidence-backed game translation units through per-object extra_cflags=["-prefix SMS.mch"]
  • keep -opt all,nostrength and -inline deferred centralized in cflags_game
  • leave the other 347 game units, JSystem, and SDK compilation without the PCH
  • replace hand-written Vec/table stand-ins in the completed-enough units
  • fix TMapObjGeneral vtable ownership, which removes the last stand-ins in MapObjInit.cpp
  • quote the generated PCH output path so Windows workspaces containing spaces build correctly
  • restore the retail 0x1C size of TMBindShadowBody
  • retain the small source/layout corrections found during the project sweep

TWW references:

Results

Measured against main at the current rebase point, by toggling only the PCH:

with PCH without PCH
matched code 37.6561% 37.0842%
matched data 55.1132% 50.3807%
matched functions 8090 8063

So the PCH is worth +0.57pp code, +4.73pp data, +27 functions.

build/GMSJ01/mario.dol: OK.

Largest per-unit contributions: NPC/NpcInitData +10280 data, Player/MarioInit +10016 code, MoveBG/Item +5088 data, Enemy/hinokuri2 +3000 code / +1976 data, Enemy/gatekeeper +2584 data, Strategic/liveinterp +2140 code, MarioUtil/MathUtil +2096 data.

Per-unit A/B across the 40: 23 improve, 17 no measurable effect, 0 regress. The 17 inert units are kept because they are premature rather than wrong — seven of them (bombhei, bosswanwan, feetinv, killer, tinkoopa, ModelGate, MarNameRefGen_BossEnemy) are still at 0% code and 0% data, so there is nothing yet for the PCH to act on, and two (MapEventDolpic, MapEventSink) are already at 100% data.

Selective PCH audit

The selection was re-verified on the current base with a three-way controlled experiment, changing only whether the PCH is applied:

  1. selective — the 40 units as submitted
  2. off — PCH disabled everywhere
  3. global — PCH applied to every game unit via cflags_game

Global is worse than selective (code 37.6024%, data 54.7896%, 8088 functions), which is what justifies keeping the list narrow rather than turning the PCH on project-wide.

Across the 696 units that do not receive the PCH, none would gain from it, and exactly three regress under the global build:

Unit Effect of PCH
MarioUtil/RumbleData data −2072
Strategic/spcinterp code −1308, −1 function
MarioUtil/gd-reinit-gx code −620, −1 function

These are the same three no-PCH cases identified when the list was first assembled, so the boundary is unchanged.

No padding or source fakematch is used for any of these results.

MapObjGeneral vtable ownership

Retail's MapObjInit.o owns __vt__14TMapObjGeneral, the getFlushTime weak body, and the @32@__dt__14TMapObjGeneral adjustor thunk. We were emitting all three in MapObjGeneral.o, and that missing .data is what the hand-written stand-ins in MapObjInit.cpp were compensating for.

MWCC selects the owning TU by key function: the first non-inline virtual in declaration order. Ours was loadAfter (MapObjGeneral.cpp); retail's must have been initMapObj (MapObjInit.cpp). Because initMapObj overrides a TMapObjBase virtual, its slot is fixed by the base, so promoting its declaration cannot change the vtable layout — and does not, the table is still exactly 0x1dc. All three symbols relocate together.

getFlushTime also had an empty body { } (4 bytes, and the source of a return value expected warning). Retail is 8 bytes and disassembles to lwz r3, mNormalFlushTime@sda21; blr; that static already existed, so the real body mirrors getLivingTime() above it and matches 100%.

With real vtable data present, dummy, dummy2, dummy3 and dummy4 are removed. MapObjInit data goes 0.389% → 44.203%; the remainder is genuine unreconstructed content, not padding.

A sweep of the rest of the codebase for the same bug — resolving every __vt__ owner in mario.MAP against the TU defining that class's first non-inline virtual — found no other mismatches (624 classes resolved of 1508 vtables).

Validation

  • generated metadata confirms exactly 40/387 game units use -prefix SMS.mch
  • full ninja build and retail DOL checksum
  • three-way controlled A/B/global experiment, per-unit, described above
  • tools/check-changed-symbol-order.py passes on all 10 changed .cpp files
  • clang-format clean on all changed sources
  • no manual volatile padding

@decomp-dev

decomp-dev Bot commented Jul 26, 2026

Copy link
Copy Markdown

Report for GMSJ01 (9b62e09 - aecc701)

📈 Matched code: 37.73% (+0.57%, +20624 bytes)
📈 Matched data: 55.11% (+6.02%, +38576 bytes)

✅ 46 new matches
Unit Item Bytes Before After
mario/MoveBG/MapObjInit .rodata +1727 92.48% 100.00%
mario/Player/MarioInit .data +85 85.29% 100.00%
mario/Player/MarioRun .data +49 72.73% 100.00%
mario/System/EmitterViewObj .data +47 82.46% 100.00%
mario/GC2D/Option .data +43 82.76% 100.00%
mario/Enemy/gesso .data +42 98.02% 100.00%
mario/MSound/MSHandle .data +41 94.90% 100.00%
mario/MoveBG/Item .data +40 99.21% 100.00%
mario/System/RenderModeObj .data +26 75.90% 100.00%
mario/Enemy/gatekeeper .data +21 98.37% 100.00%
mario/Enemy/hinokuri2 .rodata +14 99.32% 100.00%
mario/M3DUtil/M3UJoint .data +12 85.00% 100.00%
mario/M3DUtil/SampleCtrlNode .sdata +8 0.00% 100.00%
mario/MoveBG/MapObjInit @32@__dt__14TMapObjGeneralFv +8 0.00% 100.00%
mario/MoveBG/MapObjInit TMapObjGeneral::getFlushTime() const +8 0.00% 100.00%
mario/Enemy/namekuri .data +8 99.55% 100.00%
mario/Player/MarioParticle .data +7 94.00% 100.00%
mario/Enemy/gatekeeper .rodata +7 99.47% 100.00%
mario/Player/MarioInit TMario::TDeParams::TDeParams() +5 99.83% 100.00%
mario/Player/MarioInit TMario::TJumpParams::TJumpParams() +4 99.83% 100.00%
mario/Player/MarioInit TMario::TSwimParams::TSwimParams() +3 99.85% 100.00%
mario/Enemy/hinokuri2 THino2Params::THino2Params(const char*) +3 99.83% 100.00%
mario/Player/MarioInit TMario::TRunParams::TRunParams() +2 99.84% 100.00%
mario/Player/MarioInit TMario::TWireParams::TWireParams() +2 99.84% 100.00%
mario/Player/MarioInit TMario::TGraffitoParams::TGraffitoParams() +2 99.83% 100.00%
mario/Player/MarioInit TMario::TDirtyParams::TDirtyParams() +2 99.83% 100.00%
mario/Enemy/gesso TGessoSaveLoadParams::TGessoSaveLoadParams(const char*) +2 99.85% 100.00%
mario/Enemy/bgtentacle TBGTentacle::TTentacleParams::TTentacleParams(const char*) +2 99.84% 100.00%
mario/System/EmitterViewObj __sinit_EmitterViewObj_cpp +1 99.67% 100.00%
mario/System/RenderModeObj SMSSetupTitleRenderMode(_GXRenderModeObj*) +1 99.96% 100.00%

...and 16 more new matches

📈 42 improvements in unmatched items
Unit Item Bytes Before After
mario/MoveBG/MapObjInit .data +3789 81.14% 96.41%
mario/MoveBG/MapObjInit .sdata2 +841 80.87% 99.85%
mario/Enemy/enemyMario .data +415 81.18% 97.95%
mario/Enemy/bgtentacle .data +106 88.90% 98.58%
mario/Enemy/hinokuri2 .data +101 90.76% 99.01%
mario/Player/MarioDraw .data +89 90.39% 92.33%
mario/GC2D/SelectMenu .data +43 84.42% 91.67%
mario/Enemy/bosseel .data +41 98.32% 99.66%
mario/Player/MarioInit TMario::TMario() +27 99.10% 99.23%
mario/System/RenderModeObj SMSSetupTitleRenderingInfo(JDrama::TDisplay*) +6 97.61% 99.92%
mario/MSound/MSHandle MSHandle::MSACos(float) +6 84.17% 91.22%
mario/Enemy/hinokuri2 .sdata +4 25.00% 50.00%
mario/System/RenderModeObj SMSSetupGameRenderingInfo(JDrama::TDisplay*, bool) +1 99.44% 99.94%
mario/Strategic/liveinterp linGetSRT(TSpcTypedInterp<TLiveActor>*, unsigned long) +1 96.03% 96.06%
mario/Player/Yoshi TYoshi::init(TMario*) +1 99.52% 99.57%
mario/Player/MarioInit TMario::TSurfingParams::TSurfingParams(const char*) +1 96.51% 96.62%
mario/GC2D/Option TOptionSoundUnit::adjustView() +1 99.64% 99.78%
mario/Enemy/enemyMario TEnemyMario::initModel() +1 99.87% 99.91%
mario/Enemy/enemyMario TEnemyMario::initEnemyValues() +1 94.70% 94.72%
mario/Enemy/gatekeeper TBiancoGateKeeper::launchNamekuri() +1 97.36% 97.38%
mario/Enemy/bgtentacle TBGTentacle::TBGTentacle(TBossGesso*, int, int) +1 97.91% 97.94%
mario/Enemy/bgtentacle TBGTentacle::calcAttackGuideAnm() +1 75.77% 75.78%
mario/System/MarNameRefGen_BossEnemy TMarNameRefGen::getNameRef_BossEnemy(const char*) const 0 16.32% 16.33%
mario/Player/MarioDraw TMario::setAnimation(int, float) 0 98.07% 98.09%
mario/Player/MarioDraw TMario::initModel() 0 95.46% 95.46%
mario/Player/MarioRun TMario::moveMain() 0 99.32% 99.33%
mario/Player/Yoshi TYoshi::movement() 0 99.38% 99.38%
mario/Player/MarioInit .rodata 0 99.98% 99.99%
mario/Player/MarioInit TMario::initValues() 0 99.94% 99.95%
mario/MoveBG/MapObjSirena TRoulette::initMapObj() 0 99.94% 99.96%

...and 12 more improvements in unmatched items

📉 4 regressions in unmatched items
Unit Item Bytes Before After
mario/GC2D/SelectMenu TSelectMenu::perform(unsigned long, JDrama::TGraphics*) -20 91.05% 90.74%
mario/System/MarNameRefGen_BossEnemy .rodata -15 27.72% 26.84%
mario/Player/MarioDraw .rodata -13 97.98% 97.57%
mario/Enemy/gatekeeper .sdata -1 50.00% 40.00%

@KakarottoCake KakarottoCake changed the title WIP: Use a PCH for J3DJoint weak data WIP: Reproduce game weak data with a PCH Jul 26, 2026
@KakarottoCake
KakarottoCake force-pushed the pch-j3djoint branch 2 times, most recently from 2002949 to a927788 Compare July 26, 2026 16:01
Comment thread src/Camera/CameraDemo.cpp Outdated
@KakarottoCake
KakarottoCake force-pushed the pch-j3djoint branch 4 times, most recently from 533a056 to f8aa717 Compare July 26, 2026 17:36
@KakarottoCake

Copy link
Copy Markdown
Contributor Author

Follow-up commit a7e5756 centralizes the common game compiler profile in cflags_game: -opt all,nostrength, -inline deferred, and -prefix SMS.mch. The PCH build and the three evidence-backed no-PCH object exceptions retain explicit flag lists; THPPlayer remains on its separate profile. Verified with a full build (mario.dol: OK), ninja baseline/changes_all (no regressions), Python syntax validation, and git diff --check.

@KakarottoCake
KakarottoCake force-pushed the pch-j3djoint branch 2 times, most recently from 6854611 to 9b9140b Compare July 27, 2026 19:37
@KakarottoCake

KakarottoCake commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Edited: this comment predated the rebase. The commit is now ca3368df, the figures below were measured against the old base, and the JKRArchive follow-up described here turned out to be unnecessary. Corrected inline; the full write-up now lives in the PR description under "MapObjGeneral vtable ownership".

Resolves the "Known edge case" that was in the description — MapObjInit no longer needs its stand-in literals.

Root cause. Retail's MapObjInit.o owns __vt__14TMapObjGeneral, the getFlushTime weak body, and the @32@__dt__14TMapObjGeneral adjustor thunk. We were emitting all three in MapObjGeneral.o. They travel together, and the missing .data is exactly what dummy/dummy2/dummy3/dummy4 were standing in for.

MWCC picks the owning TU by key function: the first non-inline virtual in declaration order. Ours was loadAfter (MapObjGeneral.cpp); retail's must have been initMapObj (MapObjInit.cpp). initMapObj overrides a TMapObjBase virtual, so its slot is fixed by the base and promoting its declaration cannot change the vtable layout — and doesn't, the table is still exactly 0x1dc.

Second fix. getFlushTime had an empty body { } — 4 bytes, and the source of the return value expected warning in the build log. Retail is 8 bytes:

lwz r3, TMapObjGeneral::mNormalFlushTime@sda21
blr

mNormalFlushTime already existed as a static, so the real body mirrors getLivingTime() directly above it. 100% match.

MapObjInit data goes 0.389% → 44.203%; the remainder is genuine unreconstructed content, not padding. For the whole-PR figures, see the table in the description — the ones originally quoted here were against a base that has since been superseded by the rebase.

Codebase sweep. I checked for the same bug elsewhere by resolving every __vt__ owner in mario.MAP against the TU defining that class's first non-inline virtual. 624 classes resolved of 1508 vtables, and TMapObjGeneral is the only mismatch. An earlier version of this comment reported JKRArchive as a second one and said I would send it separately; that was wrong — it had already been fixed upstream in c6a3188, which even documents the same technique:

// NOTE: has to be defined not first to get the vtable
// to emit to the correct TU
virtual ~JKRArchive();

My sweep had been reading the pre-sweep header on this branch. No follow-up PR is coming, and none is needed.

@KakarottoCake KakarottoCake changed the title WIP: Reproduce game weak data with a PCH Reproduce game weak data with a PCH Aug 22, 2026
@KakarottoCake

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and refreshed the description. Dropping the WIP prefix — this is ready for review.

Three things changed since the last look:

CI. The Validate symbol order failure was not from this PR. The workflow picks its file list with a two-dot git diff "$base" HEAD, so a branch that is behind main gets every file main has touched added to the list and then validated in its stale copy — 105 units checked, 31 failed, none of them files this PR modifies. Rebasing alone took it to 10 units, all passing. Filed separately as #151 with a suggested git merge-base fix, since it will keep biting other PRs.

Description. The Results table was measured against a superseded baseline and the "Known edge case" section described the MapObjInit stand-ins, which ca3368df fixed. Both rewritten against the current base.

Re-audited the PCH selection. main has changed 101 source files since the original audit, and 11 of the 40 PCH units were among them, so the evidence behind the list needed redoing. Three-way controlled experiment, toggling only the PCH:

code data functions
selective (40 units) 37.6561% 55.1132% 8090
PCH off 37.0842% 50.3807% 8063
PCH global 37.6024% 54.7896% 8088

Per-unit across the 40: 23 improve, 17 no effect, 0 regress. Across the 696 non-PCH units: none would gain, and exactly three regress under the global build — RumbleData, spcinterp, gd-reinit-gx, the same three the original audit excluded. So the boundary is unchanged and the selection needs no edits.

The 17 inert units are kept deliberately: seven are still at 0% code and 0% data, so there is nothing for the PCH to act on yet, and two are already at 100% data. Happy to trim them to 23 if you would rather the list carry only units with present-day evidence — it makes no difference to the build either way.

KakarottoCake and others added 3 commits August 23, 2026 03:36
Retail puts __vt__14TMapObjGeneral, the getFlushTime weak body and the
@32@ adjustor thunk in MapObjInit.o. We were emitting all three in
MapObjGeneral.o, which is why MapObjInit.cpp still carried hand-written
stand-in literals to make up the missing data.

MWCC picks the owning TU by key function: the first non-inline virtual in
declaration order. Ours was loadAfter (MapObjGeneral.cpp); retail's must
have been initMapObj (MapObjInit.cpp). initMapObj is an override of a
TMapObjBase virtual, so moving its declaration to the front of the list
cannot change the vtable layout -- and it doesn't, the table is still
0x1dc. All three symbols move to MapObjInit.o together.

getFlushTime also had an empty body, which is 4 bytes; retail is 8 and
disassembles to `lwz r3, mNormalFlushTime@sda21; blr`, so give it the
real body. That mirrors getLivingTime directly above it, matches 100%,
and silences the "return value expected" warning.

With the data now coming from the real vtable, dummy/dummy2/dummy3/dummy4
are no longer needed and are removed -- no fakematch left in this file.

  matched data      50.0071% -> 53.5928%
  matched functions 7921 -> 7923
  MapObjInit data      0.389% -> 44.203%

mario.dol: OK; check-changed-symbol-order.py passes on both units.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@KakarottoCake
KakarottoCake marked this pull request as ready for review August 23, 2026 15:24
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.

2 participants