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.
174 lines
6.6 KiB
Java
174 lines
6.6 KiB
Java
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<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");
|
||
}
|
||
}
|