analysis: don't extend max_reached past last visited code in jtbl handling

discover_function_bounds was extending max_reached to cover the jump
table's bytes themselves (jtbl_end - 4). This was wrong: jtbl entries
are DATA, not code. Including their bytes in the function's words
range made recompile_function try to interpret intervening data words
as MIPS instructions, which usually fails (encountering 'pref',
'INVALID', branches to outside the function, etc.).

The fix: stop extending max_reached for jtbl_end. The recompiler's
analyze_function reads jtbl entries directly from context.rom — they
don't need to be part of func.words. Function bounds = max-reached-
instruction + 4 (delay slot), nothing more.

Effect on Stadium's 0x8FF00000 pattern activation:

  Before this fix: 1 recompile error (`func_8FF00020__rom_FE000020`
  at instr 8243 — 'pref' instruction emitted at vram 0x8FF080B8,
  followed by INVALID instructions). The function had been sized
  large enough to contain unrelated data after the case-arms region.

  After this fix: 0 errors. All 219 pattern-synthesized sections
  recompile cleanly.

Stadium boot impact (pattern activated, 30-second run):
  - 1048 audio hits (15x improvement; was ~70 with static fragment78).
  - 31 fragments registered (3.4x improvement; was ~9).
  - 17 different fragment78-family variants streamed through link
    vram 0x8FF00000, each correctly content-hash-dispatched.
  - Stadium reaches a NEW failure mode much later: `lookup miss
    at 0x810001D0` — a different fragment slot (link bucket 0x10)
    needs similar treatment. Tracked separately.

The pattern is now ACTIVE in Stadium's game.toml. game.toml updated
to document the active configuration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley 2026-04-28 21:52:32 -07:00
parent 392fef7cc0
commit 745046ca43

View File

@ -526,21 +526,22 @@ bool N64Recomp::discover_function_bounds(
cursor, jtbl.vram);
return false;
}
// Add each target to BFS. Also extend max_reached past
// the table itself so we count its bytes as part of
// the function.
// Add each target to BFS so its arm gets walked and
// its instructions count toward max_reached. Do NOT
// extend max_reached to cover the jump table's bytes
// themselves: jtbl entries are DATA, not code, and
// including them in the function's `words` makes
// recompile_function try to interpret intervening
// data words as MIPS instructions (which usually
// fails because the data isn't valid code). The
// recompiler's analyze_function reads jtbl entries
// directly from context.rom — they don't need to
// be inside func.words.
for (size_t t : jtbl_targets) {
if (!visited.contains(t)) {
worklist.push_back(t);
}
}
size_t jtbl_end = (jtbl.vram - vram_base) +
collected * 4;
if (jtbl_end > 0) {
if (jtbl_end - 4 > max_reached) {
max_reached = jtbl_end - 4;
}
}
break; // block ends after the jr's delay slot
}