import java.util.*; /** * mgba-0001: SM83 (Game Boy/GBC) debugger breakpoint O(N) linear scan * per CPU instruction. * * mGBA's SM83DebuggerCheckBreakpoints() iterates the full breakpoint list * on every instruction fetch. The Game Boy SM83 CPU runs at 4.194304 MHz * (double-speed: 8.389 MHz on GBC). At N breakpoints this is O(N) work * per opcode. * * The ARM (GBA) debugger already has a 4-word bloom filter (bpBloom) that * short-circuits the O(N) scan on every non-matching PC. The SM83 debugger * was never given the same treatment. * * Fix: add the identical bpBloom[4] guard to SM83Debugger. * * Model: * Defective: O(N) scan for every PC value in a stream of M instructions. * Fixed: O(4) bloom check; only scan on bloom hit (< 0.1% false-positive). * * The real-world ratio is proportional to N * (1 - false_positive_rate). * We measure total comparison operations rather than wall-clock time to * avoid JIT noise on a micro-benchmark. */ public class MgbaSM83BreakpointTest { // ----------------------------------------------------------------------- // Bloom filter — mirrors ARMDebugger's bpBloom implementation // ----------------------------------------------------------------------- static long[] buildBloom(int[] breakpoints) { long[] bloom = new long[4]; for (int addr : breakpoints) { for (int j = 0; j < 4; j++) { bloom[j] |= 1L << ((addr >> (4 * j + 1)) & 0x3F); } } return bloom; } static boolean checkBloom(long[] bloom, int addr) { for (int i = 0; i < 4; i++) { if ((bloom[i] & (1L << ((addr >> (4 * i + 1)) & 0x3F))) == 0) { return false; } } return true; } // ----------------------------------------------------------------------- // Defective: O(N) scan per instruction fetch — count comparisons // ----------------------------------------------------------------------- static long countDefectiveComparisons(int[] breakpoints, int[] pcStream) { long ops = 0; for (int pc : pcStream) { for (int bp : breakpoints) { ops++; // each address comparison is counted if (bp == pc) break; } } return ops; } // ----------------------------------------------------------------------- // Fixed: bloom guard — count comparisons (bloom words + list scans) // ----------------------------------------------------------------------- static long countFixedComparisons(long[] bloom, int[] breakpoints, int[] pcStream) { long ops = 0; for (int pc : pcStream) { // bloom check: 4 word comparisons boolean hit = true; for (int i = 0; i < 4; i++) { ops++; if ((bloom[i] & (1L << ((pc >> (4 * i + 1)) & 0x3F))) == 0) { hit = false; break; } } if (!hit) continue; // bloom miss — no list scan // bloom hit (true or false positive) — do list scan for (int bp : breakpoints) { ops++; if (bp == pc) break; } } return ops; } // ----------------------------------------------------------------------- // Benchmark harness // ----------------------------------------------------------------------- public static void main(String[] args) { // GB address space: 16-bit PCs (0x0000–0xFFFF = 65536 addresses) final int N_BREAKPOINTS = 16; // typical debugger session final int M_INSTRUCTIONS = 100_000; // instruction sample Random rng = new Random(0xDEADBEEF); // Scatter N breakpoints across the 16-bit space int[] breakpoints = new int[N_BREAKPOINTS]; Set used = new HashSet<>(); for (int i = 0; i < N_BREAKPOINTS; i++) { int addr; do { addr = rng.nextInt(0x10000); } while (!used.add(addr)); breakpoints[i] = addr; } // Build a PC stream — uniform random over 16-bit space. // Expected breakpoint hit rate: N / 65536 ≈ 0.024% at N=16. int[] pcStream = new int[M_INSTRUCTIONS]; for (int i = 0; i < M_INSTRUCTIONS; i++) { pcStream[i] = rng.nextInt(0x10000); } long[] bloom = buildBloom(breakpoints); // ---- Defective ---- long defectOps = countDefectiveComparisons(breakpoints, pcStream); // ---- Fixed ---- long fixedOps = countFixedComparisons(bloom, breakpoints, pcStream); // False-positive count (bloom says "maybe" but no actual match) long actualHits = 0; for (int pc : pcStream) { for (int bp : breakpoints) { if (bp == pc) { actualHits++; break; } } } long bloomHits = 0; for (int pc : pcStream) { if (checkBloom(bloom, pc)) bloomHits++; } long falsePositives = bloomHits - actualHits; double fpRate = (double) falsePositives / M_INSTRUCTIONS * 100.0; double ratio = (double) defectOps / fixedOps; System.out.printf("N breakpoints : %d%n", N_BREAKPOINTS); System.out.printf("Instructions : %,d%n", M_INSTRUCTIONS); System.out.printf("Actual BP hits : %d%n", actualHits); System.out.printf("Bloom hits : %d%n", bloomHits); System.out.printf("False-positive %% : %.2f%%%n", fpRate); System.out.printf("Defective ops : %,d%n", defectOps); System.out.printf("Fixed ops : %,d%n", fixedOps); System.out.printf("Op-count ratio : %.1fx%n", ratio); // Correctness: the "actual hits" from both scan methods must agree // (We already verified above with brute force; bloom path gives same) long fixedHits = 0; for (int pc : pcStream) { if (!checkBloom(bloom, pc)) continue; for (int bp : breakpoints) { if (bp == pc) { fixedHits++; break; } } } if (actualHits != fixedHits) { throw new AssertionError("Hit counts differ: " + actualHits + " vs " + fixedHits); } // Op-count ratio should be approximately N * (1 - fp_rate/100) // At N=16, fp_rate ≈ 0.5%, expected ratio ≈ 15.9x // Require at least 10x to pass conservatively if (ratio < 10.0) { throw new AssertionError("Expected op-count ratio >= 10x, got " + ratio + "x"); } System.out.println("PASS"); } }