java-topology/defects/mgba-0001/unit/MgbaSM83BreakpointTest.java
russell@unturf.com 52bf9f213e snes9x+mgba: 5-MOAD scan; 1 defect (mgba-0001) / snes9x CLEAN
mgba-0001: SM83DebuggerCheckBreakpoints() O(N) linear scan per GB/GBC
CPU instruction — no bloom filter guard, unlike ARMDebugger which already
has bpBloom[4].  Fix: add identical bloom guard to SM83Debugger.
Op-count ratio 10.4x at N=16 breakpoints (PASS).

snes9x: CLEAN across all 5 MOADs.  Breakpoint array is fixed size-6 (O(1)).
Cheat apply is per-frame O(G*C), not per-instruction.  No credential logging.
2026-03-31 21:48:05 -04:00

174 lines
6.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 (0x00000xFFFF = 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<Integer> 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");
}
}