diff --git a/examples/arrays_and_functions.nes b/examples/arrays_and_functions.nes index 9faa196..481c3da 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 a8dee48..cff3bea 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 c921938..3459fd3 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 9f298a8..50571da 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 9959992..74adae0 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 8fcf88a..230f497 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 84044f6..99c527b 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 803da81..fe80777 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 287a713..b68a2ac 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 97eaccf..09872a2 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 97e2b5f..2c19839 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 ccdfff4..460671b 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 8e4e158..0d66288 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 9343b05..aa3dd91 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 50f684d..a42abea 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 7f250d2..c647061 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 cd48257..43a0a22 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 df29244..0ae7f4e 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 8bfe217..619fda3 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 88751f6..cd24ddc 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 911e40a..ab75555 100644 Binary files a/examples/platformer.nes and b/examples/platformer.nes differ diff --git a/examples/pong.nes b/examples/pong.nes index aae3928..24e4069 100644 Binary files a/examples/pong.nes and b/examples/pong.nes differ diff --git a/examples/scanline_split.nes b/examples/scanline_split.nes index 93ba691..24096ea 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 032cd18..7075ebd 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 b128bd7..6b0f146 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 9cf96e7..3f0fe1d 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 97bbefa..c02ee94 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 9585948..a336c34 100644 Binary files a/examples/structs_enums_for.nes and b/examples/structs_enums_for.nes differ diff --git a/examples/two_player.nes b/examples/two_player.nes index cd07add..c5a75b3 100644 Binary files a/examples/two_player.nes and b/examples/two_player.nes differ diff --git a/examples/uxrom_banked.nes b/examples/uxrom_banked.nes index a5e6030..a0b7aa8 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 a8e6eb7..f675664 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 a71fe7e..b9fb319 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 e438709..5020e68 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 61b9e45..516ca0c 100644 --- a/src/codegen/ir_codegen.rs +++ b/src/codegen/ir_codegen.rs @@ -161,6 +161,23 @@ pub struct IrCodeGen<'a> { /// resulting `__ppu_update_used` marker label to decide whether /// to splice the in-NMI palette/nametable update helper. ppu_update_used: bool, + /// Set to true the first time we lower an `IrOp::Mul`. Drives + /// the `__mul_used` marker label so the linker can skip + /// `gen_multiply` (~50 bytes) for programs that never multiply. + /// The optimizer's strength reduction has already turned + /// multiplies by constant powers of two into shifts by the time + /// codegen runs, so this flag only gets set for real runtime + /// multiplications. + mul_used: bool, + /// Set to true the first time we lower an `IrOp::Div` or + /// `IrOp::Mod` (modulo reuses the divide routine). Drives the + /// `__div_used` marker label so the linker can skip `gen_divide` + /// (~70 bytes) for programs that never divide. Divide by + /// constant powers of two has been strength-reduced to shifts + /// by the optimizer, and modulo by constant powers of two to + /// masks; runtime divide only survives for non-constant or + /// non-power-of-two divisors. + div_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 @@ -338,6 +355,8 @@ impl<'a> IrCodeGen<'a> { noise_used: false, triangle_used: false, ppu_update_used: false, + mul_used: false, + div_used: false, source_locs: Vec::new(), next_source_loc: 0, emit_source_locs: false, @@ -1175,7 +1194,11 @@ impl<'a> IrCodeGen<'a> { self.store_temp(*d); } IrOp::Mul(d, a, b) => { - // Software multiply: multiplicand in A, multiplier in $02 + // Software multiply: multiplicand in A, multiplier in $02. + // Drop the `__mul_used` marker so the linker knows to + // link the `__multiply` subroutine in — programs that + // don't multiply skip ~50 bytes of runtime. + self.emit_mul_marker(); self.load_temp(*a); self.emit(PHA, AM::Implied); // Save for __multiply contract let b_addr = self.temp_addr(*b); @@ -1222,7 +1245,10 @@ impl<'a> IrCodeGen<'a> { IrOp::Div(d, a, b) => { // Software divide: dividend in A, divisor in $02. // `__divide` returns quotient in A and leaves - // remainder in ZP $03. + // remainder in ZP $03. Emit the `__div_used` marker + // so the linker links the `__divide` subroutine in; + // programs that don't divide skip ~70 bytes. + self.emit_div_marker(); self.load_temp(*a); self.emit(PHA, AM::Implied); let b_addr = self.temp_addr(*b); @@ -1234,7 +1260,10 @@ impl<'a> IrCodeGen<'a> { } IrOp::Mod(d, a, b) => { // Modulo reuses __divide and reads the remainder out - // of ZP $03 afterwards. + // of ZP $03 afterwards. Same `__div_used` marker as + // `IrOp::Div` — modulo doesn't have a separate + // runtime routine. + self.emit_div_marker(); self.load_temp(*a); self.emit(PHA, AM::Implied); let b_addr = self.temp_addr(*b); @@ -2018,6 +2047,32 @@ impl<'a> IrCodeGen<'a> { } } + /// Emit the `__mul_used` marker label at most once per program. + /// The linker uses this to decide whether to link in the + /// `gen_multiply` subroutine. Programs that never multiply (or + /// that only multiply by constant powers of two — those are + /// strength-reduced to shifts) skip the runtime entirely. + fn emit_mul_marker(&mut self) { + if !self.mul_used { + self.emit_label("__mul_used"); + self.mul_used = true; + } + } + + /// Emit the `__div_used` marker label at most once per program. + /// The linker uses this to decide whether to link in the + /// `gen_divide` subroutine. Both `IrOp::Div` and `IrOp::Mod` + /// trigger this marker because modulo reuses the divide + /// routine. Programs whose divide/modulo operations all use + /// constant power-of-two divisors have already been rewritten + /// to shifts / masks by the optimizer and never reach here. + fn emit_div_marker(&mut self) { + if !self.div_used { + self.emit_label("__div_used"); + self.div_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 94ec068..3f9927a 100644 --- a/src/linker/mod.rs +++ b/src/linker/mod.rs @@ -471,9 +471,22 @@ impl Linker { } } - // Math runtime routines (included always for simplicity) - all_instructions.extend(runtime::gen_multiply()); - all_instructions.extend(runtime::gen_divide()); + // Math runtime routines. Gated on the `__mul_used` / + // `__div_used` marker labels that the IR codegen drops at + // the first `IrOp::Mul` / `IrOp::Div` / `IrOp::Mod`. The + // optimizer rewrites multiplies and divides by constant + // powers of two into shifts (and modulo by constant powers + // of two into masks) before codegen runs, so these markers + // only fire for genuinely runtime-costly math. Programs + // without any surviving mul or div pay zero bytes here. + let has_mul = has_label(user_code, "__mul_used"); + let has_div = has_label(user_code, "__div_used"); + if has_mul { + all_instructions.extend(runtime::gen_multiply()); + } + if has_div { + all_instructions.extend(runtime::gen_divide()); + } // Audio subsystem — linked in whenever user code touched // audio (detected via the `__audio_used` marker emitted by diff --git a/tests/emulator/goldens/audio_demo.audio.hash b/tests/emulator/goldens/audio_demo.audio.hash index 220a846..aaa85d4 100644 --- a/tests/emulator/goldens/audio_demo.audio.hash +++ b/tests/emulator/goldens/audio_demo.audio.hash @@ -1 +1 @@ -7b16a9db 132084 +49c411ce 132084 diff --git a/tests/emulator/goldens/friendly_assets.audio.hash b/tests/emulator/goldens/friendly_assets.audio.hash index d5069db..0dca2ac 100644 --- a/tests/emulator/goldens/friendly_assets.audio.hash +++ b/tests/emulator/goldens/friendly_assets.audio.hash @@ -1 +1 @@ -e73a6a73 132084 +68089bdb 132084 diff --git a/tests/emulator/goldens/noise_triangle_sfx.audio.hash b/tests/emulator/goldens/noise_triangle_sfx.audio.hash index 7efa628..e0af6eb 100644 --- a/tests/emulator/goldens/noise_triangle_sfx.audio.hash +++ b/tests/emulator/goldens/noise_triangle_sfx.audio.hash @@ -1 +1 @@ -ea38044c 132084 +5146a336 132084 diff --git a/tests/emulator/goldens/platformer.audio.hash b/tests/emulator/goldens/platformer.audio.hash index e30b406..359b2c8 100644 --- a/tests/emulator/goldens/platformer.audio.hash +++ b/tests/emulator/goldens/platformer.audio.hash @@ -1 +1 @@ -dded3a6a 132084 +5cc6d0f5 132084 diff --git a/tests/emulator/goldens/pong.audio.hash b/tests/emulator/goldens/pong.audio.hash index da68c82..778c04f 100644 --- a/tests/emulator/goldens/pong.audio.hash +++ b/tests/emulator/goldens/pong.audio.hash @@ -1 +1 @@ -f2dbfbf3 132084 +714f679e 132084 diff --git a/tests/emulator/goldens/war.audio.hash b/tests/emulator/goldens/war.audio.hash index b37a532..7dc1df6 100644 --- a/tests/emulator/goldens/war.audio.hash +++ b/tests/emulator/goldens/war.audio.hash @@ -1 +1 @@ -4e8bdb33 132084 +575adcb6 132084