diff --git a/defects/sameboy-0001/patch/sameboy-0001.patch b/defects/sameboy-0001/patch/sameboy-0001.patch new file mode 100644 index 000000000..73811f38f --- /dev/null +++ b/defects/sameboy-0001/patch/sameboy-0001.patch @@ -0,0 +1,60 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/Core/debugger.c ++++ b/Core/debugger.c +@@ -2401,13 +2401,26 @@ static void test_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t flags, uint + static void test_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t flags, uint8_t value) + { + if (unlikely(gb->backstep_instructions)) return; ++ /* O(1) fast-path: bail immediately if no watchpoint covers this address+flags */ ++ if (!(gb->watchpoint_address_flags[addr] & flags)) return; + uint16_t bank = bank_for_addr(gb, addr); + for (unsigned i = 0; i < gb->n_watchpoints; i++) { + struct GB_watchpoint_s *watchpoint = &gb->watchpoints[i]; + if (watchpoint->bank != (uint16_t)-1) { + if (watchpoint->bank != bank) continue; + } + if (!(watchpoint->flags & flags)) continue; + if (addr < watchpoint->addr) continue; + if (addr > (uint32_t)watchpoint->addr + watchpoint->length + watchpoint->inclusive) continue; + if (!watchpoint->condition) { + condition_ok: + GB_debugger_break(gb); + +--- a/Core/gb.h ++++ b/Core/gb.h +@@ -768,6 +768,8 @@ struct GB_gameboy_internal_s { + uint16_t n_watchpoints; + struct GB_watchpoint_s *watchpoints; ++ /* Per-address flags OR'd across all watchpoints covering that address (READ|WRITE). ++ Allows O(1) early-exit in GB_read_memory / GB_write_memory hot path. */ ++ uint8_t watchpoint_address_flags[0x10000]; + +--- a/Core/debugger.c (watchpoint add) ++++ b/Core/debugger.c (watchpoint add) +@@ -1290,6 +1290,12 @@ static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu + gb->watchpoints[gb->n_watchpoints++] = (struct GB_watchpoint_s){ + .id = id, + .key = key, + .condition = condition? strdup(condition) : NULL, + .flags = flags, + .length = length, + .inclusive = inclusive, + }; ++ /* Update O(1) lookup table for addresses covered by this watchpoint */ ++ for (uint32_t a = result.value; a <= (uint32_t)result.value + length + inclusive; a++) { ++ gb->watchpoint_address_flags[(uint16_t)a] |= flags; ++ } + +--- a/Core/debugger.c (watchpoint delete/rebuild) ++++ b/Core/debugger.c (watchpoint delete/rebuild) +@@ rebuild watchpoint_address_flags after any removal ++static void rebuild_watchpoint_flags(GB_gameboy_t *gb) ++{ ++ memset(gb->watchpoint_address_flags, 0, sizeof(gb->watchpoint_address_flags)); ++ for (unsigned i = 0; i < gb->n_watchpoints; i++) { ++ struct GB_watchpoint_s *wp = &gb->watchpoints[i]; ++ for (uint32_t a = wp->addr; a <= (uint32_t)wp->addr + wp->length + wp->inclusive; a++) { ++ gb->watchpoint_address_flags[(uint16_t)a] |= wp->flags; ++ } ++ } ++} diff --git a/defects/sameboy-0001/test/test_sameboy_0001.c b/defects/sameboy-0001/test/test_sameboy_0001.c new file mode 100644 index 000000000..61b4efd20 --- /dev/null +++ b/defects/sameboy-0001/test/test_sameboy_0001.c @@ -0,0 +1,137 @@ +/* + * sameboy-0001: test — watchpoint linear scan O(W) per memory access + * + * Simulates the test_watchpoint() hot path. With W watchpoints, every + * GB_read_memory / GB_write_memory call scans all W watchpoints. + * Fix: watchpoint_address_flags[addr] lookup table gives O(1) early exit. + * + * Test verifies: + * 1. O(W) baseline: op count grows linearly with W + * 2. O(1) fixed: op count is constant regardless of W + * 3. Speedup >= 5x at W=64 + */ + +#include +#include +#include +#include +#include +#include + +#define WATCHPOINT_READ 1 +#define WATCHPOINT_WRITE 2 +#define ADDR_SPACE 0x10000 +#define MEM_ACCESSES 100000 + +/* Simulate the watchpoint struct */ +typedef struct { + unsigned id; + uint16_t addr; + uint16_t bank; + uint8_t flags; + uint16_t length; + int inclusive; + char *condition; +} watchpoint_t; + +/* ---------- baseline: O(W) linear scan ---------- */ +static long long baseline_ops; + +static void test_watchpoint_linear(watchpoint_t *wps, int n, uint16_t addr, uint8_t flags) +{ + for (int i = 0; i < n; i++) { + baseline_ops++; + watchpoint_t *wp = &wps[i]; + if (wp->bank != (uint16_t)-1) continue; /* any-bank */ + if (!(wp->flags & flags)) continue; + if (addr < wp->addr) continue; + if (addr > (uint32_t)wp->addr + wp->length + wp->inclusive) continue; + /* would fire, but we only measure cost */ + break; + } +} + +/* ---------- fixed: O(1) address-flag table ---------- */ +static long long fixed_ops; +static uint8_t watchpoint_address_flags[ADDR_SPACE]; + +static void rebuild_flags(watchpoint_t *wps, int n) +{ + memset(watchpoint_address_flags, 0, sizeof(watchpoint_address_flags)); + for (int i = 0; i < n; i++) { + watchpoint_t *wp = &wps[i]; + for (uint32_t a = wp->addr; a <= (uint32_t)wp->addr + wp->length + wp->inclusive; a++) { + watchpoint_address_flags[(uint16_t)a] |= wp->flags; + } + } +} + +static void test_watchpoint_fixed(watchpoint_t *wps, int n, uint16_t addr, uint8_t flags) +{ + fixed_ops++; + if (!(watchpoint_address_flags[addr] & flags)) return; /* O(1) exit */ + /* full scan only when address is actually covered */ + for (int i = 0; i < n; i++) { + fixed_ops++; + watchpoint_t *wp = &wps[i]; + if (wp->bank != (uint16_t)-1) continue; + if (!(wp->flags & flags)) continue; + if (addr < wp->addr) continue; + if (addr > (uint32_t)wp->addr + wp->length + wp->inclusive) continue; + break; + } +} + +static void run_scenario(int n_watchpoints, long long *out_baseline, long long *out_fixed) +{ + watchpoint_t *wps = calloc(n_watchpoints, sizeof(watchpoint_t)); + /* Place all watchpoints at high addresses so accesses to 0x8000 never hit */ + for (int i = 0; i < n_watchpoints; i++) { + wps[i].id = i + 1; + wps[i].addr = 0xFF00 + (i % 0x100); + wps[i].bank = (uint16_t)-1; /* any */ + wps[i].flags = WATCHPOINT_READ | WATCHPOINT_WRITE; + wps[i].length = 0; + wps[i].inclusive = 0; + wps[i].condition = NULL; + } + rebuild_flags(wps, n_watchpoints); + + baseline_ops = 0; + fixed_ops = 0; + + /* Simulate MEM_ACCESSES to address 0x8000 (not covered by any watchpoint) */ + for (int a = 0; a < MEM_ACCESSES; a++) { + test_watchpoint_linear(wps, n_watchpoints, 0x8000, WATCHPOINT_READ); + } + *out_baseline = baseline_ops; + + for (int a = 0; a < MEM_ACCESSES; a++) { + test_watchpoint_fixed(wps, n_watchpoints, 0x8000, WATCHPOINT_READ); + } + *out_fixed = fixed_ops; + + free(wps); +} + +int main(void) +{ + printf("sameboy-0001: watchpoint O(W) scan per memory access\n"); + printf("%-12s %14s %10s %10s\n", "watchpoints", "baseline_ops", "fixed_ops", "speedup"); + + int sizes[] = {4, 16, 32, 64, 128}; + double last_speedup = 0; + for (int s = 0; s < (int)(sizeof(sizes)/sizeof(sizes[0])); s++) { + int w = sizes[s]; + long long bl, fx; + run_scenario(w, &bl, &fx); + double speedup = (double)bl / (double)fx; + printf("%-12d %14lld %10lld %9.1fx\n", w, bl, fx, speedup); + last_speedup = speedup; + } + + /* At W=64, speedup should be >= 5x */ + assert(last_speedup >= 5.0 && "expected >= 5x speedup at W=128"); + printf("PASS: sameboy-0001\n"); + return 0; +} diff --git a/defects/sameboy-0002/patch/sameboy-0002.patch b/defects/sameboy-0002/patch/sameboy-0002.patch new file mode 100644 index 000000000..2c4006264 --- /dev/null +++ b/defects/sameboy-0002/patch/sameboy-0002.patch @@ -0,0 +1,44 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/Core/debugger.c ++++ b/Core/debugger.c +@@ -1421,6 +1421,8 @@ static unsigned should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to) + static unsigned should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to) + { + if (unlikely(gb->backstep_instructions)) return false; ++ /* O(1) fast-path: no breakpoint registered at this address */ ++ if (!gb->breakpoint_address_set[addr]) return 0; + uint16_t bank = bank_for_addr(gb, addr); + for (unsigned i = 0; i < gb->n_breakpoints; i++) { + struct GB_breakpoint_s *breakpoint = &gb->breakpoints[i]; +@@ -1093,6 +1093,10 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const + gb->breakpoints[gb->n_breakpoints++] = (struct GB_breakpoint_s){ + .id = id, .key = key, .condition = condition? strdup(condition) : NULL, + .is_jump_to = is_jump_to, .length = length, .inclusive = inclusive, + }; ++ /* Mark all addresses covered by this breakpoint range in O(1) lookup */ ++ for (uint32_t a = result.value; a <= (uint32_t)result.value + length + inclusive; a++) { ++ gb->breakpoint_address_set[(uint16_t)a] = true; ++ } + +--- a/Core/gb.h ++++ b/Core/gb.h +@@ -749,6 +749,8 @@ struct GB_gameboy_internal_s { + uint16_t n_breakpoints; + struct GB_breakpoint_s *breakpoints; + bool has_jump_to_breakpoints, has_software_breakpoints; ++ /* Boolean lookup table: true if any breakpoint covers this 16-bit address. ++ Provides O(1) per-instruction bail-out in should_break() hot path. */ ++ bool breakpoint_address_set[0x10000]; + +--- a/Core/debugger.c (rebuild helper) ++++ b/Core/debugger.c (rebuild helper) ++static void rebuild_breakpoint_address_set(GB_gameboy_t *gb) ++{ ++ memset(gb->breakpoint_address_set, 0, sizeof(gb->breakpoint_address_set)); ++ for (unsigned i = 0; i < gb->n_breakpoints; i++) { ++ struct GB_breakpoint_s *bp = &gb->breakpoints[i]; ++ for (uint32_t a = bp->addr; a <= (uint32_t)bp->addr + bp->length + bp->inclusive; a++) { ++ gb->breakpoint_address_set[(uint16_t)a] = true; ++ } ++ } ++} diff --git a/defects/sameboy-0002/test/test_sameboy_0002.c b/defects/sameboy-0002/test/test_sameboy_0002.c new file mode 100644 index 000000000..5b949ff39 --- /dev/null +++ b/defects/sameboy-0002/test/test_sameboy_0002.c @@ -0,0 +1,135 @@ +/* + * sameboy-0002: test — breakpoint linear scan O(B) per CPU instruction + * + * should_break() scans gb->breakpoints[0..n] on every instruction fetch + * when debug_active is set. With B breakpoints all at high addresses, + * an instruction fetch at 0x0100 (ROM entry point) incurs O(B) scan. + * + * Fix: breakpoint_address_set[addr] boolean lookup gives O(1) early exit. + * + * Test verifies: + * 1. O(B) baseline: op count grows linearly with B + * 2. O(1) fixed: op count is constant regardless of B + * 3. Speedup >= 5x at B=64 + */ + +#include +#include +#include +#include +#include +#include + +#define ADDR_SPACE 0x10000 +#define INSTRUCTIONS 100000 + +typedef struct { + unsigned id; + uint16_t addr; + uint16_t bank; + char *condition; + bool is_jump_to; + uint16_t length; + bool inclusive; +} breakpoint_t; + +/* ---------- baseline: O(B) linear scan ---------- */ +static long long baseline_ops; + +static unsigned should_break_linear(breakpoint_t *bps, int n, uint16_t addr) +{ + for (int i = 0; i < n; i++) { + baseline_ops++; + breakpoint_t *bp = &bps[i]; + if (bp->bank != (uint16_t)-1) { + /* bank check omitted for simplicity — bank always any */ + } + if (addr < bp->addr) continue; + if (addr > (uint32_t)bp->addr + bp->length + bp->inclusive) continue; + if (!bp->condition) return bp->id; + } + return 0; +} + +/* ---------- fixed: O(1) address-set table ---------- */ +static long long fixed_ops; +static bool breakpoint_address_set[ADDR_SPACE]; + +static void rebuild_address_set(breakpoint_t *bps, int n) +{ + memset(breakpoint_address_set, 0, sizeof(breakpoint_address_set)); + for (int i = 0; i < n; i++) { + breakpoint_t *bp = &bps[i]; + for (uint32_t a = bp->addr; a <= (uint32_t)bp->addr + bp->length + bp->inclusive; a++) { + breakpoint_address_set[(uint16_t)a] = true; + } + } +} + +static unsigned should_break_fixed(breakpoint_t *bps, int n, uint16_t addr) +{ + fixed_ops++; + if (!breakpoint_address_set[addr]) return 0; /* O(1) exit */ + /* full scan only when address is registered */ + for (int i = 0; i < n; i++) { + fixed_ops++; + breakpoint_t *bp = &bps[i]; + if (addr < bp->addr) continue; + if (addr > (uint32_t)bp->addr + bp->length + bp->inclusive) continue; + if (!bp->condition) return bp->id; + } + return 0; +} + +static void run_scenario(int n_bp, long long *out_baseline, long long *out_fixed) +{ + breakpoint_t *bps = calloc(n_bp, sizeof(breakpoint_t)); + /* Place all breakpoints at high addresses so PC=0x0100 never hits */ + for (int i = 0; i < n_bp; i++) { + bps[i].id = i + 1; + bps[i].addr = 0xFF00 + (i % 0x100); + bps[i].bank = (uint16_t)-1; + bps[i].length = 0; + bps[i].inclusive = false; + bps[i].is_jump_to = false; + bps[i].condition = NULL; + } + rebuild_address_set(bps, n_bp); + + baseline_ops = 0; + fixed_ops = 0; + + for (int i = 0; i < INSTRUCTIONS; i++) { + should_break_linear(bps, n_bp, 0x0100); + } + *out_baseline = baseline_ops; + + for (int i = 0; i < INSTRUCTIONS; i++) { + should_break_fixed(bps, n_bp, 0x0100); + } + *out_fixed = fixed_ops; + + free(bps); +} + +int main(void) +{ + printf("sameboy-0002: breakpoint O(B) scan per CPU instruction\n"); + printf("%-12s %14s %10s %10s\n", "breakpoints", "baseline_ops", "fixed_ops", "speedup"); + + int sizes[] = {4, 16, 32, 64, 128}; + double last_speedup = 0; + for (int s = 0; s < (int)(sizeof(sizes)/sizeof(sizes[0])); s++) { + int b = sizes[s]; + long long bl, fx; + run_scenario(b, &bl, &fx); + double speedup = (double)bl / (double)fx; + printf("%-12d %14lld %10lld %9.1fx\n", b, bl, fx, speedup); + last_speedup = speedup; + } + + /* At B=128, speedup should be >= 5x */ + assert(last_speedup >= 5.0 && "expected >= 5x speedup at B=128"); + printf("PASS: sameboy-0002\n"); + return 0; +}