mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
Remove the legacy AST codegen — IR path is canonical now
The `--use-ast` path through `src/codegen/mod.rs` was a strictly
inferior subset of the IR codegen. Building every example with
`--use-ast` through the jsnes harness:
- `arrays_and_functions` — fully black (array init + function
return values + OAM-in-loop all broken)
- `structs_enums_for` — fully black (struct literal is a no-op,
all fields stay at 0)
- `inline_asm_demo` — fully black
- `bitwise_ops`, `loop_break_continue` — below sprite floors
(static `next_oam_slot` bug B)
- `match_demo` — panics at compile time with
`branch offset 153 out of range` (AST's if/else-chain
desugaring of `match` emits short branches that can't reach
the far arms in a multi-arm match)
Six of fourteen examples are non-functional under `--use-ast`.
The other eight happen to fall inside the subset AST handles
(no arrays, no structs, no function return values, no
multi-sprite loops, no long match chains).
`docs/future-work.md` already listed "Once working, delete the
AST-based codegen entirely" as the intended direction. It's
working, so this commit does the deletion.
What's removed:
- The `CodeGen` struct, its impl block, and every helper in
`src/codegen/mod.rs` (the AST codegen body) — ~1150 lines.
The file is now a module header that re-exports `IrCodeGen`.
- `src/codegen/tests.rs` — 15 AST-specific instruction-pattern
tests. Every feature they covered has an equivalent test in
`src/codegen/ir_codegen.rs::{tests,more_tests}` already.
- The `--use-ast` CLI flag and its branch in `src/main.rs`.
- `compile_with_ir_codegen` in `tests/integration_test.rs` —
`compile()` now does what it did, so they merged. All 40
integration tests go through the IR path.
- Outdated sections in `docs/future-work.md` that described the
IR codegen as "not yet implemented" and listed AST codegen
gaps as priority work.
What's kept:
- `src/codegen/ir_codegen.rs` — the real codegen.
- `src/codegen/peephole.rs` — post-codegen cleanup pass, now
run unconditionally from `main.rs`.
Test plan:
- `cargo test --release` — 313 unit + 37 integration tests pass
(was 328 + 37; the 15 dropped are the deleted AST-specific
tests).
- `cargo fmt --check` clean.
- `cargo clippy --release --all-targets -- -D warnings` clean.
- `node tests/emulator/run_examples.mjs` — 14/14 ROMs render
above their per-example nonBlack floors.
- The one tightening: `sprite_resolution_uses_tile_index` was
asserting on the old static-slot encoding
(`A9 01 8D 01 02`). Updated to the cursor-based form
(`A9 01 99 01 02`, i.e. STA AbsoluteY).
Net diff: 1581 deletions, 62 insertions.
https://claude.ai/code/session_014Z5y3Q9krLcAxYpZQJhZ5V
This commit is contained in:
parent
54acb9ee38
commit
a757336681
6 changed files with 61 additions and 1580 deletions
|
|
@ -2,13 +2,17 @@ use std::path::Path;
|
|||
|
||||
use nescript::analyzer;
|
||||
use nescript::assets;
|
||||
use nescript::codegen::CodeGen;
|
||||
use nescript::codegen::IrCodeGen;
|
||||
use nescript::ir;
|
||||
use nescript::linker::Linker;
|
||||
use nescript::optimizer;
|
||||
use nescript::rom;
|
||||
|
||||
/// Compile a `NEScript` source string into a .nes ROM.
|
||||
/// Compile a `NEScript` source string into a .nes ROM. Runs the full
|
||||
/// IR pipeline: parse → analyze → IR lower → optimize → IR codegen
|
||||
/// → peephole → link. This is what the `nescript build` CLI does
|
||||
/// (minus file IO and the dump flags), so these integration tests
|
||||
/// exercise the same path end users hit.
|
||||
fn compile(source: &str) -> Vec<u8> {
|
||||
let (program, diags) = nescript::parser::parse(source);
|
||||
assert!(
|
||||
|
|
@ -24,16 +28,15 @@ fn compile(source: &str) -> Vec<u8> {
|
|||
analysis.diagnostics
|
||||
);
|
||||
|
||||
// Run IR lowering and optimization (validates the pipeline works)
|
||||
let mut ir_program = ir::lower(&program, &analysis);
|
||||
optimizer::optimize(&mut ir_program);
|
||||
|
||||
let sprites = assets::resolve_sprites(&program, Path::new("."))
|
||||
.expect("sprite resolution should succeed");
|
||||
|
||||
let codegen =
|
||||
CodeGen::new(&analysis.var_allocations, &program.constants).with_sprites(&sprites);
|
||||
let instructions = codegen.generate(&program);
|
||||
let codegen = IrCodeGen::new(&analysis.var_allocations, &ir_program).with_sprites(&sprites);
|
||||
let mut instructions = codegen.generate(&ir_program);
|
||||
nescript::codegen::peephole::optimize(&mut instructions);
|
||||
|
||||
let linker = Linker::new(program.game.mirroring);
|
||||
linker.link_with_assets(&instructions, &sprites)
|
||||
|
|
@ -643,9 +646,9 @@ fn compile_with_mapper(source: &str) -> Vec<u8> {
|
|||
let sprites = assets::resolve_sprites(&program, Path::new("."))
|
||||
.expect("sprite resolution should succeed");
|
||||
|
||||
let codegen = nescript::codegen::CodeGen::new(&analysis.var_allocations, &program.constants)
|
||||
.with_sprites(&sprites);
|
||||
let instructions = codegen.generate(&program);
|
||||
let codegen = IrCodeGen::new(&analysis.var_allocations, &ir_program).with_sprites(&sprites);
|
||||
let mut instructions = codegen.generate(&ir_program);
|
||||
nescript::codegen::peephole::optimize(&mut instructions);
|
||||
|
||||
let linker = Linker::with_mapper(program.game.mirroring, program.game.mapper);
|
||||
linker.link_with_assets(&instructions, &sprites)
|
||||
|
|
@ -703,13 +706,15 @@ fn sprite_resolution_uses_tile_index() {
|
|||
"tile 0 should still contain the default smiley",
|
||||
);
|
||||
|
||||
// In PRG ROM, look for `LDA #$01 ; STA $0201` which writes the Player's
|
||||
// tile index (1) into the tile-index byte of the first OAM slot.
|
||||
// In PRG ROM, look for `LDA #$01 ; STA $0201,Y` which writes
|
||||
// the Player's tile index (1) into the tile-index byte of the
|
||||
// current OAM slot (the slot is computed at runtime via the
|
||||
// OAM cursor in Y). The STA AbsoluteY opcode is $99.
|
||||
let prg = &rom_data[16..16 + 16384];
|
||||
let pattern = [0xA9u8, 0x01, 0x8D, 0x01, 0x02];
|
||||
let pattern = [0xA9u8, 0x01, 0x99, 0x01, 0x02];
|
||||
assert!(
|
||||
prg.windows(pattern.len()).any(|w| w == pattern),
|
||||
"PRG ROM should contain LDA #$01 ; STA $0201 for draw Player",
|
||||
"PRG ROM should contain LDA #$01 ; STA $0201,Y for draw Player",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -746,38 +751,11 @@ fn program_with_mmc1() {
|
|||
}
|
||||
|
||||
// ── IR Codegen Tests ──
|
||||
|
||||
/// Compile a program using the IR-based codegen path instead of the
|
||||
/// AST-based codegen. Validates the full IR pipeline produces a valid ROM.
|
||||
fn compile_with_ir_codegen(source: &str) -> Vec<u8> {
|
||||
use nescript::codegen::IrCodeGen;
|
||||
|
||||
let (program, diags) = nescript::parser::parse(source);
|
||||
assert!(
|
||||
diags.is_empty(),
|
||||
"unexpected parse errors: {diags:?}\nsource:\n{source}"
|
||||
);
|
||||
let program = program.expect("parse should succeed");
|
||||
|
||||
let analysis = analyzer::analyze(&program);
|
||||
assert!(
|
||||
analysis.diagnostics.iter().all(|d| !d.is_error()),
|
||||
"unexpected analysis errors: {:?}",
|
||||
analysis.diagnostics
|
||||
);
|
||||
|
||||
// Lower to IR and run the optimizer
|
||||
let mut ir_program = ir::lower(&program, &analysis);
|
||||
optimizer::optimize(&mut ir_program);
|
||||
|
||||
// IR-based codegen
|
||||
let codegen = IrCodeGen::new(&analysis.var_allocations, &ir_program);
|
||||
let instructions = codegen.generate(&ir_program);
|
||||
|
||||
// Link into a ROM
|
||||
let linker = Linker::new(program.game.mirroring);
|
||||
linker.link(&instructions)
|
||||
}
|
||||
//
|
||||
// These tests exercise specific end-to-end IR codegen behavior.
|
||||
// They all use the top-level `compile()` helper now that it runs
|
||||
// the full IR pipeline — there's no longer a separate legacy path
|
||||
// to compare against.
|
||||
|
||||
#[test]
|
||||
fn ir_codegen_minimal_rom() {
|
||||
|
|
@ -787,7 +765,7 @@ fn ir_codegen_minimal_rom() {
|
|||
on frame { wait_frame }
|
||||
start Main
|
||||
"#;
|
||||
let rom_data = compile_with_ir_codegen(source);
|
||||
let rom_data = compile(source);
|
||||
let info = rom::validate_ines(&rom_data).expect("should be valid iNES");
|
||||
assert_eq!(info.mapper, 0);
|
||||
assert_eq!(rom_data.len(), 16 + 16384 + 8192);
|
||||
|
|
@ -807,7 +785,7 @@ fn ir_codegen_full_pipeline() {
|
|||
}
|
||||
start Main
|
||||
"#;
|
||||
let rom_data = compile_with_ir_codegen(source);
|
||||
let rom_data = compile(source);
|
||||
rom::validate_ines(&rom_data).expect("should be valid iNES");
|
||||
}
|
||||
|
||||
|
|
@ -831,7 +809,7 @@ fn ir_codegen_multi_state_dispatch() {
|
|||
}
|
||||
start Title
|
||||
"#;
|
||||
let rom_data = compile_with_ir_codegen(source);
|
||||
let rom_data = compile(source);
|
||||
let info = rom::validate_ines(&rom_data).expect("should be valid iNES");
|
||||
assert_eq!(info.mapper, 0);
|
||||
}
|
||||
|
|
@ -851,7 +829,7 @@ fn ir_codegen_multi_oam() {
|
|||
}
|
||||
start Main
|
||||
"#;
|
||||
let rom_data = compile_with_ir_codegen(source);
|
||||
let rom_data = compile(source);
|
||||
rom::validate_ines(&rom_data).expect("should be valid iNES");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue