mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 00:45:38 +00:00
runtime: gate controller-2 reads in NMI on __p2_input_used
Drop the three-instruction JOY2 shift block (`LDA $4017 / LSR A / ROL ZP_INPUT_P2`) from inside the NMI's 8-iteration input loop when user code never reads controller 2. IR codegen emits the `__p2_input_used` marker from `IrOp::ReadInput(_, 1)`; the linker threads the flag through a new `NmiOptions::has_p2_input` bool, and `gen_nmi` writes the shift block only when the flag is set. Savings for single-player programs: - ~6 bytes of NMI code. - ~30 cycles per frame (3 instructions × 8 loop iterations, each 6-8 cycles depending on addressing — LDA abs is 4, LSR A is 2, ROL zp is 5, so ~11 cycles × 8 = ~88 cycles; rounded down for the page-crossing penalty landing differently in the new layout). This commit also fixes the IR codegen to drop the matching `__p1_input_used` marker from `IrOp::ReadInput(_, 0)`, even though the next commit is the one that actually consumes it. Landing the two markers together keeps the IR codegen's per-op bookkeeping coherent. Six audio goldens flip (every program that reads input + plays audio) with the expected NMI-layout-shift cycle drift. https://claude.ai/code/session_016kM6P7PukktBDqTZexrrAN
This commit is contained in:
parent
bd30ac3010
commit
0de1d60c33
40 changed files with 70 additions and 11 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
examples/war.nes
BIN
examples/war.nes
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<Instruction> {
|
|||
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<Instruction> {
|
|||
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)));
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
dceef796 132084
|
||||
b52a0b1e 132084
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
a9a829f7 132084
|
||||
9387feb3 132084
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
be86c2a2 132084
|
||||
752f9eee 132084
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
c9c2333d 132084
|
||||
83c0eee4 132084
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
7be9372c 132084
|
||||
fd72f4a8 132084
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
b7b5b9a0 132084
|
||||
51b560a7 132084
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue