Compile pristine SMB disassembly with static control-flow and stack analysis - #8
Merged
Merged
Conversation
nathsou
marked this pull request as ready for review
September 19, 2026 21:27
- Replace the JumpEngine-shaped dispatcher interpreter with a general 6502 evaluator over partially known state. The index may be in A, X or Y; the helper may exit through JMP (ind) or an RTS-based jump; branches on the proven path become runtime guards. Table extent comes from the inline data after the call (up to the next label/instruction), not from .dw contiguity. - A call that returns into inline data without a proven helper is now a clear error instead of decoding the table as code. RTS jumps through data pushed by the routine report an unsupported computed jump. - Reject loops that can repeat without changing memory, I/O, stack or any consumed register: under the frame-based NMI model they would hang. - Emit JSRs as CALL(routine, return_address). - Extend the NROM-128 fixture with an X-indexed, byte-table, RTS-based dispatcher executed natively. SMB output is unchanged apart from CALL and the dispatch default; all 18 dispatch sites recover the same targets and the replay hash is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Promote the labelled head of a tail shared by several routines to its own function when the tail call cannot recurse. Extra label copies in SMB drop from 214 to 40 (remaining ones are shared loops); gotos 696 -> 607. - Emit comments that precede a routine's entry label above its C function, and the source's leading comments as the code.c file header. - CALL(routine, return_address) becomes a plain C call with -DNATIVE_CALLS (EXTRA_CFLAGS in the Makefile). The suite checks the replay hash and the NROM fixture in both modes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Compiles the unmodified SMB disassembly (
src/smb.asmis byte-identical to the original file). No routine markers, rewritten control flow or name-based special cases remain. The recovered structure comes from static analysis.What changed
.db $2cBIT tricks) and code inside data directives. A constant-propagation pass prunes branches that can never be taken.jsris proven by evaluating it as ordinary 6502 code over partially known state. It doesn't rely on the helper's instruction shape: the index can be in A, X or Y, the table can be.dbor.dw, and the exit can beJMP (ind)or an RTS jump. The table ends where the inline data ends. Indices outside the proven set trap at runtime.if (a < 0x8)-style conditions.goto.Fails closed on unsupported code
These produce a compile error rather than guessed code:
Wait: lda flag / beq Wait). NMI is delivered only at ajmp *idle loop, so these would otherwise hang.Generated C (vs
2143a91)gotodo/whileloopsOriginal names and comments are kept. Routine comments sit above each function, the disassembly header is the file header, and inverted branches are marked
Original branch:.Calls read as
CALL(GameRoutines, 0xaef5);. By default this keeps the original return address on the guest stack and checks it on return.-DNATIVE_CALLS(make … EXTRA_CFLAGS=-DNATIVE_CALLS) turns it into a plain C call.Validation
python3 tests/check_codegen.py --romruns:1633679932, unchanged).Limits
jmp *main loop are supported.docs/recompiler-migration.md: resumable foreground execution, split-table/pointer analysis, mirrored code views, and ROM input with optional metadata.🤖 Generated with Claude Code