diff --git a/examples/arrays_and_functions.nes b/examples/arrays_and_functions.nes index 0db9cab..9faa196 100644 Binary files a/examples/arrays_and_functions.nes and b/examples/arrays_and_functions.nes differ diff --git a/examples/bitwise_ops.nes b/examples/bitwise_ops.nes index 00b2609..9f298a8 100644 Binary files a/examples/bitwise_ops.nes and b/examples/bitwise_ops.nes differ diff --git a/examples/logic_ops.nes b/examples/logic_ops.nes index 5a859af..ccdfff4 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 f2d50c6..8e4e158 100644 Binary files a/examples/loop_break_continue.nes and b/examples/loop_break_continue.nes differ diff --git a/examples/pong.nes b/examples/pong.nes index c8b76a0..aae3928 100644 Binary files a/examples/pong.nes and b/examples/pong.nes differ diff --git a/examples/sha256.nes b/examples/sha256.nes index 89e4d45..e53088b 100644 Binary files a/examples/sha256.nes and b/examples/sha256.nes differ diff --git a/examples/structs_enums_for.nes b/examples/structs_enums_for.nes index 5ba4263..9585948 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 455be48..cd07add 100644 Binary files a/examples/two_player.nes and b/examples/two_player.nes differ diff --git a/examples/war.nes b/examples/war.nes index b2e5323..e438709 100644 Binary files a/examples/war.nes and b/examples/war.nes differ diff --git a/src/codegen/peephole.rs b/src/codegen/peephole.rs index f1a4476..2507222 100644 --- a/src/codegen/peephole.rs +++ b/src/codegen/peephole.rs @@ -231,11 +231,22 @@ fn invert_branch(op: Opcode) -> Option { /// Remove `LDA …` instructions whose value is never read — the next /// instruction overwrites A without using the current value. /// -/// The heuristic looks one instruction ahead. If the next instruction -/// is an A-clobbering load (`LDA`, `LDX`, `LDY`, `PLA`, `TXA`, `TYA`), -/// the preceding `LDA` is dead. Shifts and arithmetic ops read A, so -/// they don't qualify. A label or branch in between stops the scan -/// (we can't prove A's dead across control flow). +/// The heuristic looks forward, stepping over instructions that +/// don't touch A (memory `INC`/`DEC`/`STX`/`STY`, labels). If the +/// first instruction that *does* touch A overwrites it without +/// reading it (`LDA`, `PLA`, `TXA`, `TYA`), the preceding `LDA` is +/// dead. Shifts and arithmetic ops read A, so they end the scan +/// without marking dead. +/// +/// One unconditional `JMP` is followed: we look up its target label +/// and resume scanning from the first instruction after it. This +/// catches `LDA #imm; DEC zp; JMP loop_cond; loop_cond: LDA loop_var` +/// patterns that the IR codegen leaves behind for `i -= 1`-style +/// loops, where the `LDA #1` was the constant operand of a `Sub` +/// the optimizer already strength-reduced into the `DEC`. Conditional +/// branches and `JSR` still end the scan — JSR could land on a +/// runtime helper that reads A, and a branch's not-taken path is +/// unconstrained. fn remove_dead_loads(instructions: &mut Vec) { let mut keep = vec![true; instructions.len()]; for i in 0..instructions.len() { @@ -243,19 +254,9 @@ fn remove_dead_loads(instructions: &mut Vec) { if inst.opcode != Opcode::LDA { continue; } - // Walk forward looking for the next instruction that either - // reads A or overwrites it. If the first such instruction - // overwrites A without reading it, our LDA is dead. - // - // We can safely step across instructions that neither read - // nor write A — INC, DEC, STX, STY on memory, and label - // definitions — because they don't observe A and don't - // clobber it either. We must NOT step over any branch or - // jump: control flow at that point can reach code that - // reads A via a different path, and our local analysis - // can't see it. let mut j = i + 1; let mut dead = false; + let mut followed_jmp = false; while j < instructions.len() { let next = &instructions[j]; // Labels are passive markers. @@ -263,8 +264,7 @@ fn remove_dead_loads(instructions: &mut Vec) { j += 1; continue; } - // Instructions that don't touch A — skip over them. - // INC/DEC on memory, STX/STY — all leave A alone. + // Memory INC/DEC/STX/STY don't touch A. if matches!( next.opcode, Opcode::INC | Opcode::DEC | Opcode::STX | Opcode::STY @@ -273,6 +273,24 @@ fn remove_dead_loads(instructions: &mut Vec) { j += 1; continue; } + // Cross one unconditional `JMP