mirror of
https://github.com/imjasonh/nescript
synced 2026-07-12 10:39:31 +00:00
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
This commit is contained in:
parent
1525922faa
commit
f49dbce686
9 changed files with 317 additions and 10 deletions
|
|
@ -312,3 +312,32 @@ fn lower_wait_frame() {
|
|||
.any(|op| matches!(op, IrOp::WaitFrame));
|
||||
assert!(has_wait, "should emit WaitFrame op");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_literal_global_init_is_captured() {
|
||||
// Regression test: `var xs: u8[4] = [1, 2, 3, 4]` used to lose
|
||||
// its initializer because `eval_const` returns None for
|
||||
// `Expr::ArrayLiteral` and `init_value` ended up `None`. The
|
||||
// fix captures the per-element values in a new `init_array`
|
||||
// field so the IR codegen can emit one `LDA #imm; STA base+i`
|
||||
// per byte at startup.
|
||||
let ir = lower_ok(
|
||||
r#"
|
||||
game "Arr" { mapper: NROM }
|
||||
var xs: u8[4] = [1, 2, 3, 4]
|
||||
on frame { wait_frame }
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
let xs = ir
|
||||
.globals
|
||||
.iter()
|
||||
.find(|g| g.name == "xs")
|
||||
.expect("`xs` global should exist");
|
||||
assert_eq!(
|
||||
xs.init_array,
|
||||
vec![1, 2, 3, 4],
|
||||
"array literal initializer should populate init_array: {:?}",
|
||||
xs.init_array
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue