From 3007266edf30a025921be77783d54d39d38acc46 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Apr 2026 10:46:01 +0000 Subject: [PATCH] Make IR codegen the default, fall back to AST via --use-ast The IR-based codegen now matches all features of the AST codegen (state dispatch, multi-OAM, P1/P2 input, scroll, debug.log/assert), so flip the default. The legacy AST codegen is still available via --use-ast for comparison and fallback during validation. https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3 --- docs/future-work.md | 37 +++++++++++++++++++++---------------- src/main.rs | 30 +++++++++++++++--------------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/docs/future-work.md b/docs/future-work.md index 6a6d72a..0d45c8f 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -371,13 +371,20 @@ These items were documented as future work but have since been implemented: `--debug` is set, stripped in release mode - **--debug CLI flag wired** — threads through `CodeGen::with_debug` - **IR-based codegen** — `src/codegen/ir_codegen.rs` walks `IrProgram` and - emits 6502, handling all IR ops (load/store, arithmetic, comparisons, - arrays, calls, draws, input, control flow). Enabled via `--use-ir` flag; - all 7 examples compile through it. Default is still AST codegen while - IR path is experimental. + emits 6502 for every IR op: load/store, arithmetic, comparisons, arrays, + calls, draws, input (P1 and P2), scroll, debug.log/assert, state + dispatch, multi-OAM slot allocation, transitions + on_enter handlers. + Now the default; `--use-ast` falls back to the legacy AST-based codegen. - **IR lowering bug fixes** — `ReadInput` now has a destination temp, `ButtonRead` uses the proper input temp, logical AND/OR use a new `emit_move` helper instead of the buggy raw VarId temp storage +- **IR Player 2 controller** — `ReadInput(temp, player)` selects $01 + or $08 based on player index +- **IR scroll support** — `scroll(x, y)` lowers to `IrOp::Scroll(x, y)` + which emits two PPU $2005 writes in IR codegen +- **IR debug.log / debug.assert** — new `IrOp::DebugLog(temps)` and + `IrOp::DebugAssert(cond)` variants, emitted as $4800 writes in debug + mode and stripped in release (same behavior as AST codegen) - **Asset pipeline @binary / @chr loading** — `resolve_sprites()` reads raw binary files and converts PNGs via `png_to_chr()`. Missing files are silently skipped (documentation-friendly) @@ -386,16 +393,14 @@ These items were documented as future work but have since been implemented: For someone picking up this codebase, the recommended order of work: -1. **Make IR codegen the default** — currently behind `--use-ir`. Once - it matches AST codegen for all code paths (state dispatch, multi-OAM, - debug statements), switch over and delete AST codegen. -2. **IR codegen: state dispatch** — currently no main loop / dispatch - table; IR codegen only generates function bodies. Need to emit the - state machine dispatch like the AST codegen does. -3. **IR codegen: multi-OAM** — currently all draws use slot 0 -4. **Audio** — SFX/music driver -5. **on_scanline for MMC3** — scanline IRQ handlers -6. **Language features** — structs, enums, fixed-point -7. **Register allocator** — proper A/X/Y allocation to replace +1. **Delete AST codegen** — IR codegen is now the default and matches + all AST codegen features. Once confidence is high (e.g. a few weeks + of game-writing), remove `--use-ast` and `src/codegen/mod.rs`'s + AST-specific code. Keep the shared constants (`DEBUG_PORT`, ZP + layout) in a common module. +2. **Audio** — SFX/music driver +3. **on_scanline for MMC3** — scanline IRQ handlers +4. **Language features** — structs, enums, fixed-point +5. **Register allocator** — proper A/X/Y allocation to replace zero-page spills used by the current IR codegen -8. **Inline assembly** — `asm { }` blocks +6. **Inline assembly** — `asm { }` blocks diff --git a/src/main.rs b/src/main.rs index f32d07e..3f5bca9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use nescript::analyzer; use nescript::assets; -use nescript::codegen::CodeGen; +use nescript::codegen::{CodeGen, IrCodeGen}; use nescript::errors::render_diagnostics; use nescript::ir; use nescript::linker::Linker; @@ -29,10 +29,10 @@ enum Cli { #[arg(long)] asm_dump: bool, - /// Use the experimental IR-based codegen instead of the - /// AST-based codegen. Enables optimizer passes on the output. + /// Use the legacy AST-based codegen. The default is the IR-based + /// codegen, which runs the optimizer passes before emitting 6502. #[arg(long)] - use_ir: bool, + use_ast: bool, }, /// Type-check a source file without building Check { @@ -50,10 +50,10 @@ fn main() { output, debug, asm_dump, - use_ir, + use_ast, } => { let output = output.unwrap_or_else(|| input.with_extension("nes")); - match compile(&input, debug, asm_dump, use_ir) { + match compile(&input, debug, asm_dump, use_ast) { Ok(rom) => { std::fs::write(&output, rom).unwrap_or_else(|e| { eprintln!("error: failed to write {}: {e}", output.display()); @@ -86,7 +86,7 @@ fn dump_asm(instructions: &[nescript::asm::Instruction]) { } } -fn compile(input: &PathBuf, debug: bool, asm_dump: bool, use_ir: bool) -> Result, ()> { +fn compile(input: &PathBuf, debug: bool, asm_dump: bool, use_ast: bool) -> Result, ()> { let raw_source = std::fs::read_to_string(input).map_err(|e| { eprintln!("error: failed to read {}: {e}", input.display()); })?; @@ -135,18 +135,18 @@ fn compile(input: &PathBuf, debug: bool, asm_dump: bool, use_ir: bool) -> Result eprintln!("error: {e}"); })?; - // Code generation: choose between IR-based and AST-based codegen. - let instructions = if use_ir { - use nescript::codegen::IrCodeGen; - IrCodeGen::new(&analysis.var_allocations, &ir_program) - .with_sprites(&sprites) - .with_debug(debug) - .generate(&ir_program) - } else { + // Code generation: IR-based is the default. `--use-ast` switches to + // the legacy AST-based codegen for comparison and fallback. + let instructions = if use_ast { CodeGen::new(&analysis.var_allocations, &program.constants) .with_sprites(&sprites) .with_debug(debug) .generate(&program) + } else { + IrCodeGen::new(&analysis.var_allocations, &ir_program) + .with_sprites(&sprites) + .with_debug(debug) + .generate(&ir_program) }; if asm_dump {