1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 00:45:38 +00:00
nescript/docs/future-work.md

179 lines
8.1 KiB
Markdown
Raw Normal View History

# Future Work
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
This document tracks the gaps between what NEScript currently compiles and
what the spec describes. Items are grouped by area. Anything implemented and
tested is omitted — `git log` is the authoritative record of what shipped.
---
## PNG-sourced assets
palette/background: first-class declarations with reset-time load and runtime swaps Re-adds `palette Name { colors: [...] }` and `background Name { tiles: [...], attributes: [...] }` as first-class declarations, plus `set_palette Name` and `load_background Name` statements for runtime swaps. Unlike the previous iteration that quietly no-op'd, this one is fully wired through the pipeline and its behavior is pinned by both unit tests and an emulator golden. Pipeline: - Lexer: re-adds `palette`, `background`, `set_palette`, `load_background` keywords and tokenizes them. - AST: `PaletteDecl` (name + 1..=32 colour bytes) and `BackgroundDecl` (name + 0..=960 tile bytes + 0..=64 attribute bytes) live in `Program`. `Statement::SetPalette` and `Statement::LoadBackground` name-reference these declarations. - Parser: `palette Name { colors: [...] }` / `background Name { tiles: [...], attributes: [...] }` blocks and their statement forms parse via the existing byte-array helper. - Analyzer: validates colour indices ($00-$3F), palette length (<=32), nametable length (<=960), attribute length (<=64), and duplicate decl names. `set_palette` / `load_background` targets must reference a declared name (E0502 otherwise). When a program declares palette or background, the analyzer bumps the user zero-page allocator's starting address from `$10` to `$18` to reserve `$11-$17` for the runtime update handshake — programs that don't use the feature keep the old layout so their emulator goldens stay byte-exact. - Assets: `PaletteData` and `BackgroundData` resolve declarations into zero-padded fixed-size blobs (32 / 960 / 64 bytes) and expose `label()` / `tiles_label()` / `attrs_label()` for codegen to reference. - IR: new `IrOp::SetPalette(String)` and `IrOp::LoadBackground(String)`; lowering forwards the names verbatim. - Codegen: `gen_set_palette` writes the palette label pointer into ZP `$12/$13` and ORs bit 0 into the update flags at `$11`; `gen_load_background` does the same for tile/attribute pointers at `$14/$15/$16/$17` with bit 1. Both emit a `__ppu_update_used` marker so the linker splices in the NMI apply helper only when the feature is actually used. - Runtime: `gen_initial_palette_load` and `gen_initial_background_load` write the first declared palette/background at reset time (before rendering is enabled, where PPU writes are safe). `gen_nmi(has_ppu_updates)` takes a new flag; when true it splices `gen_ppu_update_apply` at the top of the NMI body, which checks the `$11` flags byte and copies pending palette / nametable data to `$3F00` / `$2000` inside vblank. All helpers use only ZP $02/$03 as scratch at reset time and never clobber ZP slots live across NMI. - Linker: new `link_banked_with_ppu` takes slice of `PaletteData` / `BackgroundData`; splices each blob as a labelled data block in PRG ROM, picks the first-declared as the reset-time load target, enables background rendering automatically when a background is declared, and threads `has_ppu_updates` into `gen_nmi`. Old `link_banked` remains as a thin wrapper for callers without palette/background data so existing tests don't shift. Tests: - Lexer: tokenization of the 4 new keywords (single added test case). - Parser: 5 new tests for `palette` / `background` decls with and without attributes, plus `set_palette` / `load_background` statements. - Analyzer: 9 new tests covering acceptance of declared palettes/backgrounds, E0502 for unknown names, E0201 for out-of-range NES colors and oversized blobs, E0501 for duplicate names, and the zero-page-layout guard (palette/bg decls bump ZP start; no decls keeps it at $10). - Resolver: 3 new tests for zero-padding, truncation of oversized decls, and label derivation. - IR: 2 new lowering tests for `set_palette` and `load_background`. - Integration: 5 new tests — blob contents spliced verbatim into PRG, `STA $12` / `STA $14` emitted by set_palette / load_background codegen, and a regression guard that programs without palette/background still land user vars at $10. - Emulator: new `examples/palette_and_background.ne` driven by a frame counter that toggles between `CoolBlues` / `WarmReds` and `TitleScreen` / `StageOne` every 90 frames. Golden PNG and audio hash checked in under `tests/emulator/goldens/` and verified via `node run_examples.mjs` — rendered image shows the blue `CoolBlues` palette with the nametable populated from `TitleScreen`. Docs: - `README.md` adds the feature to the headline list and the example table. - `docs/language-guide.md` restores the palette/background sections with the full 32-byte layout table and `set_palette` / `load_background` statement references. - `docs/future-work.md` replaces the "removed as dead code" entry with the remaining gaps (PNG-sourced palette and nametable assets, cross-vblank large background updates, memory-map reporting). - `spec.md` restores the grammar productions and usage examples. - `examples/README.md` lists the new demo. All 497 unit + integration tests pass. Clippy clean. All 21 emulator goldens match after the update pass. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 11:11:33 +00:00
**What ships today.** `palette Name { colors: [...] }` and
`background Name { tiles: [...], attributes: [...] }` declarations with
inline byte arrays, plus `palette Name @palette("file.png")` and
`background Name @nametable("file.png")` for PNG-sourced variants.
The palette path maps each pixel to its nearest NES master-palette index
(via `nearest_nes_color()` in `src/assets/palette.rs`), deduplicates, and
emits the 32-byte blob; the nametable path slices a 256×240 PNG into the
32×30 tile grid, deduplicates (max 256 unique tiles), and emits the 960+64
byte nametable/attribute blobs. `--memory-map` reports per-blob PRG ROM
addresses and a running total alongside the variable layout.
palette/background: first-class declarations with reset-time load and runtime swaps Re-adds `palette Name { colors: [...] }` and `background Name { tiles: [...], attributes: [...] }` as first-class declarations, plus `set_palette Name` and `load_background Name` statements for runtime swaps. Unlike the previous iteration that quietly no-op'd, this one is fully wired through the pipeline and its behavior is pinned by both unit tests and an emulator golden. Pipeline: - Lexer: re-adds `palette`, `background`, `set_palette`, `load_background` keywords and tokenizes them. - AST: `PaletteDecl` (name + 1..=32 colour bytes) and `BackgroundDecl` (name + 0..=960 tile bytes + 0..=64 attribute bytes) live in `Program`. `Statement::SetPalette` and `Statement::LoadBackground` name-reference these declarations. - Parser: `palette Name { colors: [...] }` / `background Name { tiles: [...], attributes: [...] }` blocks and their statement forms parse via the existing byte-array helper. - Analyzer: validates colour indices ($00-$3F), palette length (<=32), nametable length (<=960), attribute length (<=64), and duplicate decl names. `set_palette` / `load_background` targets must reference a declared name (E0502 otherwise). When a program declares palette or background, the analyzer bumps the user zero-page allocator's starting address from `$10` to `$18` to reserve `$11-$17` for the runtime update handshake — programs that don't use the feature keep the old layout so their emulator goldens stay byte-exact. - Assets: `PaletteData` and `BackgroundData` resolve declarations into zero-padded fixed-size blobs (32 / 960 / 64 bytes) and expose `label()` / `tiles_label()` / `attrs_label()` for codegen to reference. - IR: new `IrOp::SetPalette(String)` and `IrOp::LoadBackground(String)`; lowering forwards the names verbatim. - Codegen: `gen_set_palette` writes the palette label pointer into ZP `$12/$13` and ORs bit 0 into the update flags at `$11`; `gen_load_background` does the same for tile/attribute pointers at `$14/$15/$16/$17` with bit 1. Both emit a `__ppu_update_used` marker so the linker splices in the NMI apply helper only when the feature is actually used. - Runtime: `gen_initial_palette_load` and `gen_initial_background_load` write the first declared palette/background at reset time (before rendering is enabled, where PPU writes are safe). `gen_nmi(has_ppu_updates)` takes a new flag; when true it splices `gen_ppu_update_apply` at the top of the NMI body, which checks the `$11` flags byte and copies pending palette / nametable data to `$3F00` / `$2000` inside vblank. All helpers use only ZP $02/$03 as scratch at reset time and never clobber ZP slots live across NMI. - Linker: new `link_banked_with_ppu` takes slice of `PaletteData` / `BackgroundData`; splices each blob as a labelled data block in PRG ROM, picks the first-declared as the reset-time load target, enables background rendering automatically when a background is declared, and threads `has_ppu_updates` into `gen_nmi`. Old `link_banked` remains as a thin wrapper for callers without palette/background data so existing tests don't shift. Tests: - Lexer: tokenization of the 4 new keywords (single added test case). - Parser: 5 new tests for `palette` / `background` decls with and without attributes, plus `set_palette` / `load_background` statements. - Analyzer: 9 new tests covering acceptance of declared palettes/backgrounds, E0502 for unknown names, E0201 for out-of-range NES colors and oversized blobs, E0501 for duplicate names, and the zero-page-layout guard (palette/bg decls bump ZP start; no decls keeps it at $10). - Resolver: 3 new tests for zero-padding, truncation of oversized decls, and label derivation. - IR: 2 new lowering tests for `set_palette` and `load_background`. - Integration: 5 new tests — blob contents spliced verbatim into PRG, `STA $12` / `STA $14` emitted by set_palette / load_background codegen, and a regression guard that programs without palette/background still land user vars at $10. - Emulator: new `examples/palette_and_background.ne` driven by a frame counter that toggles between `CoolBlues` / `WarmReds` and `TitleScreen` / `StageOne` every 90 frames. Golden PNG and audio hash checked in under `tests/emulator/goldens/` and verified via `node run_examples.mjs` — rendered image shows the blue `CoolBlues` palette with the nametable populated from `TitleScreen`. Docs: - `README.md` adds the feature to the headline list and the example table. - `docs/language-guide.md` restores the palette/background sections with the full 32-byte layout table and `set_palette` / `load_background` statement references. - `docs/future-work.md` replaces the "removed as dead code" entry with the remaining gaps (PNG-sourced palette and nametable assets, cross-vblank large background updates, memory-map reporting). - `spec.md` restores the grammar productions and usage examples. - `examples/README.md` lists the new demo. All 497 unit + integration tests pass. Clippy clean. All 21 emulator goldens match after the update pass. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 11:11:33 +00:00
**Still TODO.**
- **Automatic CHR generation from `@nametable` PNGs** — the nametable
resolver currently produces tile indices 0..N but does not write matching
CHR data, so users still need to supply CHR via `@chr(...)` with the same
tile ordering. Closing this gap requires coordinating the PNG tile
dedupe with the CHR allocator so both pipelines agree on indices.
- **Per-state background rendering control** — programs currently load a
single nametable at reset. Per-state swaps work but are limited by the
NMI-time write budget (~2273 cycles, enough for a palette but not a
full 1024-byte nametable).
---
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
## User code distribution across switchable banks
**What ships today.** `bank Foo { fun bar() { ... } }` nesting places user
functions into a specific switchable bank. The codegen emits per-bank
instruction streams; the linker runs a two-pass assembly (discover labels
per-bank, then resolve with the merged label table) so banked code can
still reference fixed-bank symbols. Fixed → banked calls are rewritten to
`JSR __tramp_<name>`, where each trampoline is a per-function stub in the
fixed bank that saves the current bank, switches, calls the target,
restores, and returns. `runtime/mod.rs::gen_bank_trampoline` is the
per-mapper emitter. See `examples/uxrom_user_banked.ne`.
**Still TODO.**
- **Banked → banked cross-bank calls.** The codegen panics if a function
in bank A tries to call a function in bank B. The fix is to generalize
the trampoline registry so the caller's bank restore logic works for
arbitrary target banks, not just calls originating in the fixed bank.
- **Greedy size-packing.** Placement is explicit-only today — there is no
pass that takes a program with too much fixed-bank code and
automatically spills the biggest leaf functions to declared empty banks.
- **MMC3 per-state-handler split** — the `mmc3_per_state_split.ne`
example still uses the legacy fixed-bank placement for its handlers.
Extending the banked-fun syntax to state handlers (plus trampoline
emission on handler dispatch) would unify the two paths.
---
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
## Language feature gaps (post-v0.1)
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
From the spec's "Reserved for Future Versions" section:
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
| Feature | Description |
|--------------------|-----------------------------------------------------------------------|
| **Fixed-point** | `fixed8.8` type for sub-pixel movement with operator support. |
| **Text / HUD** | Font sheet declarations + layout system for scores, health, menus. |
| **Metasprites** | Multi-tile sprite groups with relative positioning. |
| **Tilemaps** | Declarative level data with built-in collision queries. |
| **SRAM / saves** | Persistent storage declarations for battery-backed save data. |
NES 2.0 headers are now supported via `game Foo { header: nes2 }` — see
`src/rom/mod.rs`.
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
### Struct / array field widths
`u16` struct fields now compile. Nested struct fields and array fields are
still rejected with `E0201`; the field-layout accumulator handles variable
sizes correctly, but the IR-lowering side needs extending to recurse for
nested structs and to multiply by element size for array fields.
---
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
## Audio pipeline
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
**What ships today.** Frame-walking pulse driver with `sfx Name { duty, pitch,
volume }` and `music Name { duty, volume, repeat, notes }` blocks; builtin
effects and tracks; a 60-entry period table; `__audio_used` marker that
elides the whole subsystem when no program statement references it. **Plus**
`channel: triangle` and `channel: noise` on `sfx` blocks, which splice in
per-channel slots that write to $4008-$400B (triangle) or $400C-$400F
(noise) when a program declares them. Pulse-only programs still produce
byte-identical driver code. See `examples/noise_triangle_sfx.ne`.
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
**Still TODO for richer audio.**
- **DMC channel** — delta-modulation sample playback is not wired yet.
- **Multi-channel tracker playback** — one `notes` list per channel on
`music` blocks (the triangle/noise SFX are one-shot envelopes, not a
tracker).
- **`@sfx("file.nsf")` / `@music("file.ftm")`** — neither the NSF nor the
FamiTracker format is parsed yet.
- **Per-note pitch changes within a sfx** — `pitch` latches once at
trigger time.
---
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
## Debug instrumentation
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
**What ships today.** `debug.log(...)` and `debug.assert(...)` lower to $4800
writes when `--debug` is passed, and are stripped entirely in release builds.
`--symbols <path>` writes a Mesen-compatible `.mlb` file listing function,
state-handler, and variable addresses (with PRG ROM offsets for code and
CPU addresses for RAM). `--source-map <path>` consumes the `SourceLoc` IR
op and writes a plain-text map of `<rom_offset> <file_id> <line> <col>`
entries for every lowered statement. Debug builds emit array bounds checks
(CMP against size, BCC past a `JMP __debug_halt` wedge) and bump an
overrun counter at `$07FF` in the NMI handler when the main loop didn't
reach `wait_frame` before the next vblank.
**Still TODO.**
- **`debug.overlay(x, y, text)`** — needs the text/HUD subsystem (see
Language feature gaps).
- **Richer frame overrun telemetry** — today a single counter is bumped.
A `debug.frame_overrun_count()` builtin that exposes the counter to user
code, plus a per-frame "did this frame overrun" bit for
`debug.assert!(no_overrun)`-style guards, would make the data more
actionable.
---
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
## Code quality / tooling
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
### Register allocator
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
All IR temps currently spill to a recycled zero-page slot (`$80-$FF`). The
peephole pass mops up the most obvious waste, but a real CFG-aware allocator
that holds short-lived temps in `A`/`X`/`Y` would cut a noticeable number of
LDA/STA pairs.
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
### Cross-block temp live-range analysis
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
The slot recycler is function-local per-block. Temps that flow across block
boundaries get a dedicated slot for the entire function, even if a later
block could reuse the slot.
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
### WASM build target
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
To build a browser IDE we would need to route file I/O through a trait so the
core pipeline works on `&str → Vec<u8>` without touching `std::fs`. Today the
parser's preprocess pass and the asset resolver read files directly.
---
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
## Error message polish
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
### Unused error codes
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
`ErrorCode` only defines codes that are actually emitted. Previously there
were placeholder variants (`E0202` invalid cast, `E0403` unreachable state)
marked `#[allow(dead_code)]`; those were removed during cleanup. If those
semantics come back, add the codes at that point.
---
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
## Open design questions
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
1. **Inline asm label syntax.** `.label:` (ca65 style) vs `label:` (generic)?
Today the inline-asm parser accepts `label:` but not `.label`; migrating
would be cheap but would invalidate any copy-pasted ca65 fragments.
2. **Debug port address.** $4800 is conventional but not universal. Should
we support multiple debug output methods?
3. **OAM allocation strategy.** Sequential allocation vs priority-based with
automatic sprite cycling for the 8-per-scanline limit?
cleanup: fix silent miscompiles and delete dead code exposed by code review Two correctness bugs were silently producing wrong ROMs: - `x << n` / `x >> n` always shifted by 1, regardless of `n`, because the IR lowering for `BinOp::ShiftLeft`/`ShiftRight` hardcoded the count. Now eval_const the RHS into a compile-time count; fall back to a new `IrOp::ShiftLeftVar` / `ShiftRightVar` (runtime loop) when the amount isn't constant. Strength reduction folds the variable form back to a fixed count once the optimizer knows the value. - `x / n` / `x % n` always returned 0, because the lowering emitted `LoadImm(t, 0)` for `BinOp::Div`/`Mod` with a comment saying the runtime call was "TODO for now". Added real `IrOp::Div` and `IrOp::Mod`, wired them through use-counting and DCE, gave codegen `__divide`-based implementations, and taught strength reduction to rewrite power-of-two divisors into shifts and modulo-by-2ⁿ into AND masks. Constant folding now handles `Mul`/`Div`/`Mod`/shifts too, which were previously left for the codegen to emit inefficient software calls. Dead code removed (no backward-compat shims kept): - `src/debug/` entirely. `DebugSymbols`, `SourceMap`, and the Mesen/.sym emitters had no callers outside their own tests; `main.rs` never wrote a symbol file. Documented the intent in `docs/future-work.md` so it comes back intentionally if needed. - `ErrorCode::E0202` (invalid cast) and `E0403` (unreachable state): defined, formatted, and marked `#[allow(dead_code)]` but never emitted. W0104 now carries the unreachable-state semantics too. - `Level::Info`: never constructed. - `load_background` / `set_palette` statements and their `BackgroundDecl` / `PaletteDecl` parser support: parsed and silently dropped by IR lowering (`// TODO: implement in asset pipeline`). Removed keywords, AST variants, parser paths, analyzer arms, and tests. `docs/future-work.md` documents the runtime palette/nametable design for when it comes back. Doc cleanup: - `docs/architecture.md` was describing files that don't exist (`analyzer/types.rs`, `optimizer/const_fold.rs`, `codegen/regalloc.rs`, `rom/header.rs`, `debug/symbols.rs`, …). Rewrote it to match the real flat `mod.rs` + `tests.rs` layout and the real pipeline order. - `docs/future-work.md` was a hybrid of open work and "recently completed" entries that duplicated the active stubs at the top of the file. Collapsed to just the gaps that are actually still open. - `README.md` claimed Mesen symbol export and 210 tests; updated both. - `docs/language-guide.md` and `spec.md` described `palette` decls, `set_palette` / `load_background`, `debug.overlay`, and error codes that were never emitted. Trimmed. - Stale comments on `Statement::Play`/`StartMusic`/`StopMusic` claimed the audio subsystem was "a no-op at codegen time". Tests: - Regression tests for every fix above (`lower_shift_left_with_literal _count_uses_that_count`, `lower_shift_right_with_variable_count _uses_runtime_variant`, `lower_divide_emits_div_op_not_load_imm _zero`, `lower_modulo_emits_mod_op_not_load_imm_zero`, `strength_reduce_div_by_power_of_two`, `strength_reduce_mod_by _power_of_two`, `strength_reduce_shift_var_with_constant_amount`). - Renamed the `program_with_sprites_and_palette` integration test (which was exercising the now-removed `load_background`/`set_palette`) to `program_with_inline_sprite_chr`. `examples/sprites_and_palettes.ne` lost its `palette`/`set_palette` usage. Nothing in the emulator test presses A, so the headless jsnes render shouldn't move, but the golden may need regeneration via `UPDATE_GOLDENS=1` if it does. https://claude.ai/code/session_012fKB251HvEUQwG3tizFyqt
2026-04-13 02:47:37 +00:00
4. **Error recovery granularity.** How aggressively should the parser
recover? More recovery means more errors per compile but also risks
cascading false errors.