import java.util.Arrays; /** * KronosBreakpointLookupTest — kronos-0001 (CWE-407) * * Defect: SH2HandleBreakpoints() in * yabause/src/sys/sh2/include/sh2core.h:565 * scans codebreakpoint[0..numcodebreakpoints-1] linearly on EVERY instruction * fetch in the Kronos/Yabause SH2 debug interpreter. * * The Saturn SH2 runs at ~28.6 MHz. At 1 breakpoint lookup per instruction * and MAX_BREAKPOINTS=10, that is up to 286 M extra comparisons per emulated * second in debug sessions. * * Fix: maintain sorted_bp_addrs[] (insertion-sort on add/del, max 10 * elements) and replace the linear scan with binary search — O(log N), * worst-case 4 comparisons at N=10 instead of 10. * * This test simulates both strategies and confirms: * 1. Correctness: both return the same hit/miss result. * 2. Performance: binary search does at most log2(N+1) comparisons. * 3. Op-count ratio: binary is at least 2x fewer comparisons overall. */ public class KronosBreakpointLookupTest { static final int MAX_BREAKPOINTS = 10; // ----------------------------------------------------------------------- // Minimal model of the breakpoint table // ----------------------------------------------------------------------- static class BreakpointTable { final long[] addrs = new long[MAX_BREAKPOINTS]; int count = 0; void add(long addr) { if (count < MAX_BREAKPOINTS) addrs[count++] = addr; } /** * Defective: linear scan O(N) per instruction. * Returns [hit(0/1), comparison_count]. */ int[] linearLookup(long pc) { int cmp = 0; for (int i = 0; i < count; i++) { cmp++; if (addrs[i] == pc) return new int[]{1, cmp}; } return new int[]{0, cmp}; } /** * Patched: binary search O(log N). * Mirrors SH2RebuildSortedBreakpoints (insertion-sort) + bsearch in * SH2HandleBreakpoints. * Returns [hit(0/1), comparison_count]. */ int[] binaryLookup(long pc) { // Rebuild sorted copy (as SH2RebuildSortedBreakpoints does on add/del) long[] sorted = Arrays.copyOf(addrs, count); Arrays.sort(sorted); int cmp = 0, lo = 0, hi = count - 1; while (lo <= hi) { cmp++; int mid = (lo + hi) >>> 1; if (sorted[mid] == pc) return new int[]{1, cmp}; else if (pc < sorted[mid]) hi = mid - 1; else lo = mid + 1; } return new int[]{0, cmp}; } } // ----------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------- static void check(boolean cond, String msg) { if (!cond) throw new AssertionError("FAIL: " + msg); } // ----------------------------------------------------------------------- // Test cases // ----------------------------------------------------------------------- static void testMissEmptyTable() { BreakpointTable t = new BreakpointTable(); int[] lin = t.linearLookup(0x06000000L); int[] bin = t.binaryLookup(0x06000000L); check(lin[0] == 0, "linear: empty table = miss"); check(bin[0] == 0, "binary: empty table = miss"); check(lin[1] == 0, "linear: zero comparisons on empty table"); check(bin[1] == 0, "binary: zero comparisons on empty table"); System.out.println("PASS testMissEmptyTable"); } static void testHitSingleBreakpoint() { BreakpointTable t = new BreakpointTable(); t.add(0x06001234L); int[] lin = t.linearLookup(0x06001234L); int[] bin = t.binaryLookup(0x06001234L); check(lin[0] == 1, "linear: single BP hit"); check(bin[0] == 1, "binary: single BP hit"); System.out.println("PASS testHitSingleBreakpoint"); } static void testMissSingleBreakpoint() { BreakpointTable t = new BreakpointTable(); t.add(0x06001234L); int[] lin = t.linearLookup(0x06009999L); int[] bin = t.binaryLookup(0x06009999L); check(lin[0] == 0, "linear: single BP miss"); check(bin[0] == 0, "binary: single BP miss"); System.out.println("PASS testMissSingleBreakpoint"); } static void testHitLastElementFullTable() { BreakpointTable t = new BreakpointTable(); long[] bps = new long[MAX_BREAKPOINTS]; for (int i = 0; i < MAX_BREAKPOINTS; i++) { bps[i] = 0x06000100L + i * 0x100L; t.add(bps[i]); } // Hit the last element: worst case for linear (scans all 10) long hitPC = bps[MAX_BREAKPOINTS - 1]; int[] lin = t.linearLookup(hitPC); int[] bin = t.binaryLookup(hitPC); check(lin[0] == 1, "linear: last element hit"); check(bin[0] == 1, "binary: last element hit"); check(lin[1] == MAX_BREAKPOINTS, "linear: scans all " + MAX_BREAKPOINTS + " on last element (was " + lin[1] + ")"); check(bin[1] <= 4, "binary: at most 4 comparisons at N=10 (was " + bin[1] + ")"); System.out.println("PASS testHitLastElementFullTable: linear=" + lin[1] + " binary=" + bin[1]); } static void testMissFullTable() { BreakpointTable t = new BreakpointTable(); for (int i = 0; i < MAX_BREAKPOINTS; i++) { t.add(0x06000100L + i * 0x100L); } long missPC = 0x07000000L; int[] lin = t.linearLookup(missPC); int[] bin = t.binaryLookup(missPC); check(lin[0] == 0, "linear: full table miss"); check(bin[0] == 0, "binary: full table miss"); check(lin[1] == MAX_BREAKPOINTS, "linear: exhausts all slots on miss"); check(bin[1] <= 4, "binary: at most ceil(log2(11))=4 comparisons at N=10 (was " + bin[1] + ")"); System.out.println("PASS testMissFullTable: linear=" + lin[1] + " binary=" + bin[1]); } static void testAllAddressesHit() { BreakpointTable t = new BreakpointTable(); long[] bps = new long[MAX_BREAKPOINTS]; for (int i = 0; i < MAX_BREAKPOINTS; i++) { bps[i] = 0x06000100L + i * 0x100L; t.add(bps[i]); } for (long bp : bps) { check(t.linearLookup(bp)[0] == 1, "linear hit: 0x" + Long.toHexString(bp)); check(t.binaryLookup(bp)[0] == 1, "binary hit: 0x" + Long.toHexString(bp)); } System.out.println("PASS testAllAddressesHit: all 10 breakpoints hit by both strategies"); } static void testOpCountRatioOverManyInstructions() { BreakpointTable t = new BreakpointTable(); for (int i = 0; i < MAX_BREAKPOINTS; i++) { t.add(0x06000000L + i * 0x1000L); } long linTotal = 0, binTotal = 0; int N = 1_000_000; for (int i = 0; i < N; i++) { // Mostly misses (random-ish PC distribution), occasional hits long pc = 0x06000000L + (long)(i % 0x20000) * 2; linTotal += t.linearLookup(pc)[1]; binTotal += t.binaryLookup(pc)[1]; } double ratio = (double) linTotal / binTotal; System.out.printf("BENCH kronos-0001: linear=%d binary=%d ratio=%.2fx%n", linTotal, binTotal, ratio); check(ratio >= 2.0, "expected op-count ratio >= 2x, got " + String.format("%.2f", ratio) + "x"); System.out.println("PASS testOpCountRatioOverManyInstructions: " + String.format("%.2f", ratio) + "x fewer comparisons"); } // ----------------------------------------------------------------------- // Entry point // ----------------------------------------------------------------------- public static void main(String[] args) { testMissEmptyTable(); testHitSingleBreakpoint(); testMissSingleBreakpoint(); testHitLastElementFullTable(); testMissFullTable(); testAllAddressesHit(); testOpCountRatioOverManyInstructions(); System.out.println("ALL TESTS PASSED"); } }