diff --git a/defects/fceux-0001/patch/fceux-0001.patch b/defects/fceux-0001/patch/fceux-0001.patch new file mode 100644 index 000000000..9906ab683 --- /dev/null +++ b/defects/fceux-0001/patch/fceux-0001.patch @@ -0,0 +1,84 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/cheat.cpp ++++ b/src/cheat.cpp +@@ -67,6 +67,10 @@ CHEATF_SUBFAST SubCheats[256]; + uint32 numsubcheats = 0; + int globalCheatDisabled = 0; + int disableAutoLSCheats = 0; + bool disableShowGG = 0; + static _8BYTECHEATMAP* cheatMap = NULL; + struct CHEATF *cheats = 0, *cheatsl = 0; ++ ++// O(1) direct-address lookup replacing the O(C) SubCheats[] linear scan. ++// Index -1 means no cheat at this address. ++static int cheat_idx[0x10000]; + + + #define CHEATC_NONE 0x8000 +@@ -79,16 +83,16 @@ static uint16 *CheatComp = 0; + + static DECLFR(SubCheatsRead) + { +- CHEATF_SUBFAST *s = SubCheats; +- int x=numsubcheats; +- +- do +- { +- if(s->addr==A) +- { +- if(s->compare>=0) +- { +- uint8 pv=s->PrevRead(A); +- +- if(pv==s->compare) +- return(s->val); +- else return(pv); +- } +- else return(s->val); +- } +- s++; +- } while(--x); +- return(0); /* We should never get here. */ ++ // O(1): look up this address directly instead of scanning all active cheats. ++ int idx = cheat_idx[A]; ++ if(idx < 0) return(0); /* should never happen if installed correctly */ ++ CHEATF_SUBFAST *s = &SubCheats[idx]; ++ if(s->compare>=0) ++ { ++ uint8 pv=s->PrevRead(A); ++ if(pv==s->compare) ++ return(s->val); ++ else return(pv); ++ } ++ return(s->val); + } + + void RebuildSubCheats(void) +@@ -96,6 +100,8 @@ void RebuildSubCheats(void) + uint32 x; + struct CHEATF *c = cheats; ++ // Clear the direct-lookup table for addresses we're about to remove. + for (x = 0; x < numsubcheats; x++) + { ++ cheat_idx[SubCheats[x].addr] = -1; + SetReadHandler(SubCheats[x].addr, SubCheats[x].addr, SubCheats[x].PrevRead); + if (cheatMap) + FCEUI_SetCheatMapByte(SubCheats[x].addr, false); +@@ -113,6 +119,7 @@ void RebuildSubCheats(void) + SubCheats[numsubcheats].val = c->val; + SubCheats[numsubcheats].compare = c->compare; + SetReadHandler(c->addr, c->addr, SubCheatsRead); ++ cheat_idx[c->addr] = (int)numsubcheats; + if (cheatMap) + FCEUI_SetCheatMapByte(SubCheats[numsubcheats].addr, true); + numsubcheats++; +@@ -123,6 +130,14 @@ void RebuildSubCheats(void) + FrozenAddressCount = numsubcheats; + } + ++// Call once at startup to initialise the direct-lookup table. ++void FCEU_CheatIdxInit(void) ++{ ++ for(int i = 0; i < 0x10000; i++) ++ cheat_idx[i] = -1; ++} diff --git a/defects/fceux-0001/test/test_fceux_0001.cpp b/defects/fceux-0001/test/test_fceux_0001.cpp new file mode 100644 index 000000000..16e82725f --- /dev/null +++ b/defects/fceux-0001/test/test_fceux_0001.cpp @@ -0,0 +1,144 @@ +// test_fceux_0001.cpp +// Unit test for fceux-0001: SubCheatsRead O(C) linear scan -> O(1) direct lookup +// +// Reproduces the defect in isolation, verifies the fix gives correct results, +// and benchmarks the operation ratio at C=256 cheats. + +#include +#include +#include +#include +#include + +// ---- Reproduction of the defect ---- + +struct CHEATF_SUBFAST { + uint16_t addr; + uint8_t val; + int compare; // -1 means no compare +}; + +static const int MAX_CHEATS = 256; + +// ---- DEFECT: O(C) linear scan per read ---- + +static CHEATF_SUBFAST SubCheats_defect[MAX_CHEATS]; +static int numsubcheats_defect = 0; + +static uint8_t defect_SubCheatsRead(uint16_t A) +{ + CHEATF_SUBFAST *s = SubCheats_defect; + int x = numsubcheats_defect; + do { + if (s->addr == A) { + return s->val; + } + s++; + } while (--x); + return 0; // should not reach +} + +// ---- FIX: O(1) direct index lookup ---- + +static CHEATF_SUBFAST SubCheats_fixed[MAX_CHEATS]; +static int numsubcheats_fixed = 0; +static int cheat_idx[0x10000]; + +static void fixed_init() +{ + memset(cheat_idx, -1, sizeof(cheat_idx)); +} + +static void fixed_add(uint16_t addr, uint8_t val, int compare) +{ + int idx = numsubcheats_fixed; + SubCheats_fixed[idx] = { addr, val, compare }; + cheat_idx[addr] = idx; + numsubcheats_fixed++; +} + +static uint8_t fixed_SubCheatsRead(uint16_t A) +{ + int idx = cheat_idx[A]; + if (idx < 0) return 0; + return SubCheats_fixed[idx].val; +} + +// ---- Helpers ---- + +static long clock_ns() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (long)ts.tv_sec * 1000000000L + ts.tv_nsec; +} + +static void build_defect(int n_cheats) +{ + numsubcheats_defect = 0; + for (int i = 0; i < n_cheats; i++) { + uint16_t addr = (uint16_t)(0x0300 + i); + SubCheats_defect[numsubcheats_defect++] = { addr, (uint8_t)(i & 0xFF), -1 }; + } +} + +static void build_fixed(int n_cheats) +{ + fixed_init(); + numsubcheats_fixed = 0; + for (int i = 0; i < n_cheats; i++) { + uint16_t addr = (uint16_t)(0x0300 + i); + fixed_add(addr, (uint8_t)(i & 0xFF), -1); + } +} + +int main() +{ + const int N = MAX_CHEATS; // 256 active cheats + const int REPS = 10000000; + + // ---- Correctness: both implementations must return identical values ---- + build_defect(N); + build_fixed(N); + + for (int i = 0; i < N; i++) { + uint16_t addr = (uint16_t)(0x0300 + i); + uint8_t expected = (uint8_t)(i & 0xFF); + uint8_t got_defect = defect_SubCheatsRead(addr); + uint8_t got_fixed = fixed_SubCheatsRead(addr); + assert(got_defect == expected && "defect implementation returned wrong value"); + assert(got_fixed == expected && "fixed implementation returned wrong value"); + } + printf("CORRECTNESS: PASS (both return identical values for %d cheats)\n", N); + + // ---- Performance: O(1) must be substantially faster than O(C) ---- + // Simulate reading from all cheat addresses REPS times (worst-case: last address each read) + uint16_t worst_addr = (uint16_t)(0x0300 + N - 1); // last cheat = max scan in defect + + volatile uint8_t sink = 0; + + long t0 = clock_ns(); + for (int r = 0; r < REPS; r++) { + sink ^= defect_SubCheatsRead(worst_addr); + } + long defect_ns = clock_ns() - t0; + + long t1 = clock_ns(); + for (int r = 0; r < REPS; r++) { + sink ^= fixed_SubCheatsRead(worst_addr); + } + long fixed_ns = clock_ns() - t1; + + (void)sink; + + double ratio = (double)defect_ns / (double)fixed_ns; + printf("BENCHMARK: defect=%ldns fixed=%ldns ratio=%.1fx (C=%d cheats)\n", + defect_ns, fixed_ns, ratio, N); + + // Expect at least 5x improvement with 256 cheats + assert(ratio >= 5.0 && "expected at least 5x speedup with O(1) lookup vs O(256) scan"); + printf("PERFORMANCE: PASS (%.1fx >= 5.0x required)\n", ratio); + + printf("ALL TESTS PASSED\n"); + return 0; +}