1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-11 10:10:49 +00:00
Commit graph

2 commits

Author SHA1 Message Date
Claude
f49dbce686
Fix three compiler bugs exposed by array-using examples
Landing bug A from the previous writeup plus two adjacent bugs
that the fix exposed. All three miscompile anything that uses a
u8[N] global with a literal initializer.

1. Array-literal globals are now actually initialized.
   `lower_program` only expanded `Expr::StructLiteral` into per-
   field synthetic globals — `Expr::ArrayLiteral` hit
   `eval_const`, returned `None`, and the array boot-cleared to
   zero. `IrGlobal` now carries an `init_array: Vec<u8>`
   populated by lowering, and the IR codegen startup loop emits
   one `LDA #byte; STA base+i` pair per element.

2. Local variables no longer overlap array globals.
   `IrCodeGen::new` advanced `local_ram_next` past
   `max_global_base + 1` — for an array at `$0300-$0303` it
   placed the first handler-local at `$0301`, inside the array.
   The frame handler's stores through the local then corrupted
   the array mid-frame. The allocator now walks the analyzer's
   `VarAllocation` list and advances past `address + size` for
   every RAM global, not just the base.

3. Peephole `remove_redundant_loads` honors indexed LDAs.
   The pass tracked `LDA Immediate/ZeroPage/Absolute` but let
   `LDA AbsoluteX/AbsoluteY/ZeroPageX/IndirectX/IndirectY` fall
   through the match, leaving the A-equivalence tracker
   unchanged. A later `LDA #v` that happened to match a stale
   entry from BEFORE the indexed load would then be dropped as
   "already in A" — a silent miscompile that turned every
   `draw Sprite at: (arr[i], arr[j])` pattern into garbage
   (the second array index would be computed from `arr[i]`'s
   value, reading way out of bounds). Indexed LDAs now clear
   the tracker.

Regression tests:
- `src/codegen/peephole.rs`: a synthetic
  `LDA #0; TAX; LDA AbsX(arr1); STA temp; LDA #0; TAX;
   LDA AbsX(arr2); ...` sequence asserts both `LDA #0`s survive.
- `src/ir/tests.rs`: verifies `var xs: u8[4] = [1,2,3,4]`
  populates `IrGlobal::init_array` with `[1,2,3,4]`.
- `tests/integration_test.rs`: two IR-codegen tests — one checks
  the startup instructions contain `LDA #v; STA base+i` for
  every element, the other compiles a handler-local var
  alongside an array global and asserts no post-init stores
  land inside the array.

Smoke test impact (14/14 still passing, now more visible):
- arrays_and_functions:  56 -> 104 nonBlack, now animated
- loop_break_continue:   52 -> 208 (player + 3 hazards visible)
- structs_enums_for:     52 -> 104 (player + enemy visible)

Existing examples unchanged; no remaining work for bug B
(static OAM slot allocation in loops) — that's the next PR.

https://claude.ai/code/session_014Z5y3Q9krLcAxYpZQJhZ5V
2026-04-12 19:32:22 +00:00
Claude
1525922faa
examples: add 5 new programs covering match/loop/logic/bitwise/scanline
Fills the biggest feature-coverage gaps in the existing example set:

- match_demo.ne          — match statement over a Screen enum,
                           driving a title / playing / paused /
                           game-over flow with a debounced controller.
- loop_break_continue.ne — `loop { ... }` with `break` and `continue`,
                           scanning an enemy array for the first hit.
- logic_ops.ne           — keyword-based `and` / `or` / `not` gating
                           movement and scoring on alive/paused flags.
- bitwise_ops.ne         — packed status-byte flags with `&` / `|` /
                           `^` / `>>` plus a health-bar render loop.
- scanline_split.ne      — MMC3 `on scanline(120)` handler rewriting
                           the scroll register mid-frame for a
                           classic status-bar split.

All 14 examples (9 existing + 5 new) pass the jsnes smoke test
(`14/14 ROMs rendered successfully`) and still pass `cargo fmt`,
`cargo clippy -D warnings`, and `cargo test`.

Known limitations surfaced while authoring these examples, to be
fixed in follow-up commits:

1. Array-literal global initializers (`var xs: u8[4] = [1,2,3,4]`)
   are silently dropped by `lower_program` — `eval_const` returns
   None for `Expr::ArrayLiteral` and no synthetic per-element
   init code is emitted. Affects `arrays_and_functions`,
   `structs_enums_for`, `loop_break_continue`, and any future
   array-using example. Arrays effectively boot at all-zero.

2. `draw` inside a loop body reuses one static OAM slot —
   `next_oam_slot` increments at IR-codegen time rather than at
   runtime, so N iterations all write to the same 4-byte OAM
   entry. Affects `arrays_and_functions`, `structs_enums_for`,
   `bitwise_ops` (health pips), and any loop that wants to
   render per-iteration sprites.

Both bugs are latent and didn't surface until I tried to write
examples that exercise the relevant features — the existing
integration tests only check iNES header structure, and the
jsnes smoke test's "at least one sprite rendered" bar is
satisfied by one sprite even when several were intended.

https://claude.ai/code/session_014Z5y3Q9krLcAxYpZQJhZ5V
2026-04-12 19:05:18 +00:00