diff --git a/examples/arrays_and_functions.nes b/examples/arrays_and_functions.nes index ab94d15..1e2fe49 100644 Binary files a/examples/arrays_and_functions.nes and b/examples/arrays_and_functions.nes differ diff --git a/examples/audio_demo.nes b/examples/audio_demo.nes index 1041513..ecb936e 100644 Binary files a/examples/audio_demo.nes and b/examples/audio_demo.nes differ diff --git a/examples/auto_chr_background.nes b/examples/auto_chr_background.nes index e80d644..0db0b90 100644 Binary files a/examples/auto_chr_background.nes and b/examples/auto_chr_background.nes differ diff --git a/examples/bitwise_ops.nes b/examples/bitwise_ops.nes index 7921e14..d649e82 100644 Binary files a/examples/bitwise_ops.nes and b/examples/bitwise_ops.nes differ diff --git a/examples/bouncing_ball.nes b/examples/bouncing_ball.nes index b769195..1685d42 100644 Binary files a/examples/bouncing_ball.nes and b/examples/bouncing_ball.nes differ diff --git a/examples/coin_cavern.nes b/examples/coin_cavern.nes index c80a9f3..fe283cb 100644 Binary files a/examples/coin_cavern.nes and b/examples/coin_cavern.nes differ diff --git a/examples/comparisons.nes b/examples/comparisons.nes index 887c722..d5566be 100644 Binary files a/examples/comparisons.nes and b/examples/comparisons.nes differ diff --git a/examples/friendly_assets.nes b/examples/friendly_assets.nes index 25a0ebb..e609fb2 100644 Binary files a/examples/friendly_assets.nes and b/examples/friendly_assets.nes differ diff --git a/examples/function_chain.nes b/examples/function_chain.nes index aa6a2d2..8a0119e 100644 Binary files a/examples/function_chain.nes and b/examples/function_chain.nes differ diff --git a/examples/hello_sprite.nes b/examples/hello_sprite.nes index d6f2d25..6a128eb 100644 Binary files a/examples/hello_sprite.nes and b/examples/hello_sprite.nes differ diff --git a/examples/inline_asm_demo.nes b/examples/inline_asm_demo.nes index 2b8a9cb..75f57bd 100644 Binary files a/examples/inline_asm_demo.nes and b/examples/inline_asm_demo.nes differ diff --git a/examples/logic_ops.nes b/examples/logic_ops.nes index 9055961..308f9a9 100644 Binary files a/examples/logic_ops.nes and b/examples/logic_ops.nes differ diff --git a/examples/loop_break_continue.nes b/examples/loop_break_continue.nes index 1f28ccc..b70c5da 100644 Binary files a/examples/loop_break_continue.nes and b/examples/loop_break_continue.nes differ diff --git a/examples/match_demo.nes b/examples/match_demo.nes index 741a551..57bb37a 100644 Binary files a/examples/match_demo.nes and b/examples/match_demo.nes differ diff --git a/examples/metasprite_demo.nes b/examples/metasprite_demo.nes index 0baf3d4..12ae32b 100644 Binary files a/examples/metasprite_demo.nes and b/examples/metasprite_demo.nes differ diff --git a/examples/mmc1_banked.nes b/examples/mmc1_banked.nes index 5cfe88a..94ac587 100644 Binary files a/examples/mmc1_banked.nes and b/examples/mmc1_banked.nes differ diff --git a/examples/mmc3_per_state_split.nes b/examples/mmc3_per_state_split.nes index a776605..beaa53d 100644 Binary files a/examples/mmc3_per_state_split.nes and b/examples/mmc3_per_state_split.nes differ diff --git a/examples/nested_structs.nes b/examples/nested_structs.nes index c63f0c4..6bd7cba 100644 Binary files a/examples/nested_structs.nes and b/examples/nested_structs.nes differ diff --git a/examples/noise_triangle_sfx.nes b/examples/noise_triangle_sfx.nes index e7b884b..a48e47e 100644 Binary files a/examples/noise_triangle_sfx.nes and b/examples/noise_triangle_sfx.nes differ diff --git a/examples/palette_and_background.nes b/examples/palette_and_background.nes index df8defb..af97467 100644 Binary files a/examples/palette_and_background.nes and b/examples/palette_and_background.nes differ diff --git a/examples/platformer.nes b/examples/platformer.nes index d26e6a2..1d39d40 100644 Binary files a/examples/platformer.nes and b/examples/platformer.nes differ diff --git a/examples/scanline_split.nes b/examples/scanline_split.nes index 03a34f2..f077d78 100644 Binary files a/examples/scanline_split.nes and b/examples/scanline_split.nes differ diff --git a/examples/sfx_pitch_envelope.nes b/examples/sfx_pitch_envelope.nes index 652d4e4..1facd3e 100644 Binary files a/examples/sfx_pitch_envelope.nes and b/examples/sfx_pitch_envelope.nes differ diff --git a/examples/sprite_flicker_demo.nes b/examples/sprite_flicker_demo.nes index 51954f3..d1d32c8 100644 Binary files a/examples/sprite_flicker_demo.nes and b/examples/sprite_flicker_demo.nes differ diff --git a/examples/sprites_and_palettes.nes b/examples/sprites_and_palettes.nes index da2669b..c0ca2ec 100644 Binary files a/examples/sprites_and_palettes.nes and b/examples/sprites_and_palettes.nes differ diff --git a/examples/state_machine.nes b/examples/state_machine.nes index b8cbf27..405149e 100644 Binary files a/examples/state_machine.nes and b/examples/state_machine.nes differ diff --git a/examples/structs_enums_for.nes b/examples/structs_enums_for.nes index 155d8f3..e5afb78 100644 Binary files a/examples/structs_enums_for.nes and b/examples/structs_enums_for.nes differ diff --git a/examples/uxrom_banked.nes b/examples/uxrom_banked.nes index f6178bc..a41e9c9 100644 Binary files a/examples/uxrom_banked.nes and b/examples/uxrom_banked.nes differ diff --git a/examples/uxrom_banked_to_banked.nes b/examples/uxrom_banked_to_banked.nes index 7fb8de8..e33c3b8 100644 Binary files a/examples/uxrom_banked_to_banked.nes and b/examples/uxrom_banked_to_banked.nes differ diff --git a/examples/uxrom_user_banked.nes b/examples/uxrom_user_banked.nes index 43efe01..9370ffb 100644 Binary files a/examples/uxrom_user_banked.nes and b/examples/uxrom_user_banked.nes differ diff --git a/examples/war.nes b/examples/war.nes index 2e58168..2416716 100644 Binary files a/examples/war.nes and b/examples/war.nes differ diff --git a/src/codegen/ir_codegen.rs b/src/codegen/ir_codegen.rs index 7ea7136..6b982fc 100644 --- a/src/codegen/ir_codegen.rs +++ b/src/codegen/ir_codegen.rs @@ -196,6 +196,18 @@ pub struct IrCodeGen<'a> { /// `false` and reclaim the 16 CHR bytes of tile 0 as a blank /// background tile. default_sprite_used: bool, + /// Set to true the first time we lower an `IrOp::ReadInput` + /// targeting player 1. Drives the `__p1_input_used` marker + /// label — the runtime's NMI skips the three instructions that + /// shift `$4016` into `ZP_INPUT_P1` when nobody reads the + /// player-1 byte. + p1_input_used: bool, + /// Set to true the first time we lower an `IrOp::ReadInput` + /// targeting player 2. Drives the `__p2_input_used` marker + /// label — single-player programs skip the `$4017` read inside + /// the NMI's shift loop, saving ~6 bytes of code and ~30 cycles + /// per frame. + p2_input_used: bool, /// Source-location markers produced from [`IrOp::SourceLoc`]. /// Each entry is a `(label_name, span)` pair — the codegen /// emits a unique label-definition pseudo-op at the current @@ -377,6 +389,8 @@ impl<'a> IrCodeGen<'a> { div_used: false, oam_used: false, default_sprite_used: false, + p1_input_used: false, + p2_input_used: false, source_locs: Vec::new(), next_source_loc: 0, emit_source_locs: false, @@ -1490,6 +1504,16 @@ impl<'a> IrCodeGen<'a> { self.emit(INC, AM::ZeroPage(ZP_OAM_CURSOR)); } IrOp::ReadInput(dest, player) => { + // Drop the per-player input marker so the linker + // can decide whether to keep that port's shift + // block inside NMI. IR uses `player_index` 0 = P1 + // and 1 = P2; the ZP bytes the NMI populates match + // the constants at the top of `runtime/mod.rs`. + if *player == 1 { + self.emit_p2_input_marker(); + } else { + self.emit_p1_input_marker(); + } // $01 = P1 input byte, $08 = P2 input byte let addr = if *player == 1 { 0x08 } else { 0x01 }; self.emit(LDA, AM::ZeroPage(addr)); @@ -2141,6 +2165,28 @@ impl<'a> IrCodeGen<'a> { } } + /// Emit the `__p1_input_used` marker label at most once per + /// program. Triggered by `IrOp::ReadInput` with `player == 0`. + /// Programs that never read controller 1 skip the three NMI + /// instructions that shift `$4016` into `ZP_INPUT_P1`. + fn emit_p1_input_marker(&mut self) { + if !self.p1_input_used { + self.emit_label("__p1_input_used"); + self.p1_input_used = true; + } + } + + /// Emit the `__p2_input_used` marker label at most once per + /// program. Triggered by `IrOp::ReadInput` with `player == 1`. + /// Single-player programs skip the three NMI instructions that + /// shift `$4017` into `ZP_INPUT_P2`. + fn emit_p2_input_marker(&mut self) { + if !self.p2_input_used { + self.emit_label("__p2_input_used"); + self.p2_input_used = true; + } + } + /// Emit the MMC3 `__irq_user` handler that dispatches on the /// `(current_state, scanline_step)` pair. Supports multiple /// `on scanline(N)` handlers per state — they fire in ascending diff --git a/src/linker/mod.rs b/src/linker/mod.rs index 11848ec..bc7cb1b 100644 --- a/src/linker/mod.rs +++ b/src/linker/mod.rs @@ -662,12 +662,14 @@ impl Linker { // every frame" hardware symptom into visible flicker that // the eye reconstructs across frames. let has_sprite_cycle = has_label(user_code, "__sprite_cycle_used"); + let has_p2_input = has_label(user_code, "__p2_input_used"); all_instructions.extend(runtime::gen_nmi(runtime::NmiOptions { has_ppu_updates, has_audio, debug_mode, has_sprite_cycle, has_oam, + has_p2_input, })); // IRQ handler diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index d54329b..dee4df8 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -386,6 +386,12 @@ pub struct NmiOptions { /// so the DMA is ~520 wasted cycles per NMI. Skipping it saves /// those cycles plus 9 bytes of NMI code. pub has_oam: bool, + /// When false, drop the three instructions that shift `$4017` + /// (JOY2) into `ZP_INPUT_P2` from the NMI's 8-iteration input + /// loop. Single-player programs save ~6 bytes of code and ~30 + /// cycles per frame (a `LDA abs`, an `LSR A`, and a `ROL zp` + /// running 8 times). + pub has_p2_input: bool, } #[must_use] @@ -396,6 +402,7 @@ pub fn gen_nmi(opts: NmiOptions) -> Vec { debug_mode, has_sprite_cycle, has_oam, + has_p2_input, } = opts; let mut out = Vec::new(); @@ -506,16 +513,20 @@ pub fn gen_nmi(opts: NmiOptions) -> Vec { out.push(Instruction::new(STA, AM::Absolute(JOY1))); // Read 8 button bits from controller 1 ($4016) into ZP_INPUT_P1 - // and 8 button bits from controller 2 ($4017) into ZP_INPUT_P2 - // simultaneously — shift each port's carry into its ZP byte. + // — and, when `has_p2_input` is set, 8 bits from controller 2 + // ($4017) into ZP_INPUT_P2 in the same loop. Single-player + // programs drop the three P2 instructions (LDA abs, LSR A, ROL + // zp) and shave ~6 bytes plus ~30 cycles/frame off the NMI. out.push(Instruction::new(LDX, AM::Immediate(0x08))); out.push(Instruction::new(NOP, AM::Label("__read_input".into()))); out.push(Instruction::new(LDA, AM::Absolute(JOY1))); out.push(Instruction::new(LSR, AM::Accumulator)); out.push(Instruction::new(ROL, AM::ZeroPage(ZP_INPUT_P1))); - out.push(Instruction::new(LDA, AM::Absolute(0x4017))); // JOY2 - out.push(Instruction::new(LSR, AM::Accumulator)); - out.push(Instruction::new(ROL, AM::ZeroPage(ZP_INPUT_P2))); + if has_p2_input { + out.push(Instruction::new(LDA, AM::Absolute(0x4017))); // JOY2 + out.push(Instruction::new(LSR, AM::Accumulator)); + out.push(Instruction::new(ROL, AM::ZeroPage(ZP_INPUT_P2))); + } out.push(Instruction::implied(DEX)); out.push(Instruction::new( BNE, diff --git a/tests/emulator/goldens/audio_demo.audio.hash b/tests/emulator/goldens/audio_demo.audio.hash index 9ed40e1..e2a54aa 100644 --- a/tests/emulator/goldens/audio_demo.audio.hash +++ b/tests/emulator/goldens/audio_demo.audio.hash @@ -1 +1 @@ -dceef796 132084 +b52a0b1e 132084 diff --git a/tests/emulator/goldens/friendly_assets.audio.hash b/tests/emulator/goldens/friendly_assets.audio.hash index c671279..5d7538e 100644 --- a/tests/emulator/goldens/friendly_assets.audio.hash +++ b/tests/emulator/goldens/friendly_assets.audio.hash @@ -1 +1 @@ -a9a829f7 132084 +9387feb3 132084 diff --git a/tests/emulator/goldens/noise_triangle_sfx.audio.hash b/tests/emulator/goldens/noise_triangle_sfx.audio.hash index 435c42c..e9c3f96 100644 --- a/tests/emulator/goldens/noise_triangle_sfx.audio.hash +++ b/tests/emulator/goldens/noise_triangle_sfx.audio.hash @@ -1 +1 @@ -be86c2a2 132084 +752f9eee 132084 diff --git a/tests/emulator/goldens/platformer.audio.hash b/tests/emulator/goldens/platformer.audio.hash index 4bbfe8e..7755f4b 100644 --- a/tests/emulator/goldens/platformer.audio.hash +++ b/tests/emulator/goldens/platformer.audio.hash @@ -1 +1 @@ -c9c2333d 132084 +83c0eee4 132084 diff --git a/tests/emulator/goldens/sfx_pitch_envelope.audio.hash b/tests/emulator/goldens/sfx_pitch_envelope.audio.hash index 025c719..20f5682 100644 --- a/tests/emulator/goldens/sfx_pitch_envelope.audio.hash +++ b/tests/emulator/goldens/sfx_pitch_envelope.audio.hash @@ -1 +1 @@ -7be9372c 132084 +fd72f4a8 132084 diff --git a/tests/emulator/goldens/war.audio.hash b/tests/emulator/goldens/war.audio.hash index 03cc9de..35b351f 100644 --- a/tests/emulator/goldens/war.audio.hash +++ b/tests/emulator/goldens/war.audio.hash @@ -1 +1 @@ -b7b5b9a0 132084 +51b560a7 132084