From 23da98199ef3c6d2de227835ebaa7bb4c5fca428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 13 Oct 2026 23:37:07 +0200 Subject: [PATCH] CPUEquivalence: Dump every core's end state, not only the interpreter's So a diff between hosts covers the JITs too, without minimizing every case. Co-Authored-By: Claude Opus 5.5 --- docs/cpu-equivalence.md | 7 ++++--- unittest/JitHarness.cpp | 12 +++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/cpu-equivalence.md b/docs/cpu-equivalence.md index e3e42a6e24..876fa35736 100644 --- a/docs/cpu-equivalence.md +++ b/docs/cpu-equivalence.md @@ -30,7 +30,7 @@ Environment variables: | `PPSSPP_CPUFUZZ_SKIP` | cores to skip, by digit: 1 IR interpreter, 2 JIT, 3 JIT IR | | `PPSSPP_CPUFUZZ_REPORTS` | how many differences to minimize and print (default 20) | | `PPSSPP_CPUFUZZ_VERBOSE` | print every case before running it, to find one that crashes | -| `PPSSPP_CPUFUZZ_DUMP` | write the interpreter's end state of every case to a file | +| `PPSSPP_CPUFUZZ_DUMP` | write every core's end state of every case to a file | | `PPSSPP_CPUFUZZ_STRICT` | fail on any difference, not only on a crash | Always set `CI=1` when running it from a script on Windows. Otherwise a JIT assert opens a message @@ -41,8 +41,9 @@ finds it and reduces it to the instructions still needed for the crash. A crash after other cases have run usually means uninitialized or stale state, not the reduced code. To compare hosts: run with the same seed and count and `PPSSPP_CPUFUZZ_DUMP` on each, and diff the -files. A Windows-on-ARM machine can run the x64 build under emulation, which is enough for the -host-dependent float behavior of the interpreter. It isn't for timing, of course. +files. Each line starts with the core and the seed. A Windows-on-ARM machine can run the x64 build +under emulation, which is enough for the host-dependent float behavior of the cores. It isn't for +timing, of course. ## Fixed diff --git a/unittest/JitHarness.cpp b/unittest/JitHarness.cpp index b29357f6e8..bd2e360eb6 100644 --- a/unittest/JitHarness.cpp +++ b/unittest/JitHarness.cpp @@ -376,7 +376,7 @@ bool TestJitReplacementHooks() { // Environment variables: // PPSSPP_CPUFUZZ_COUNT number of cases (default 2000) // PPSSPP_CPUFUZZ_SEED first case's seed (default 1). Case i always uses seed + i. -// PPSSPP_CPUFUZZ_DUMP file to write the interpreter's end state of every case to, for diffing +// PPSSPP_CPUFUZZ_DUMP file to write every core's end state of every case to, for diffing // the results of different host architectures. // PPSSPP_CPUFUZZ_CLASSES instruction classes to generate, as a weighted string of "aifvm" (alu, // imm, fpu, vfpu, memory). @@ -754,10 +754,10 @@ static int DiffFuzzStates(const FuzzState &ref, const FuzzState &other, std::str return diffs; } -static void DumpFuzzState(FILE *f, u64 seed, const FuzzState &s) { +static void DumpFuzzState(FILE *f, const char *core, u64 seed, const FuzzState &s) { // All NaNs print the same, so their payloads don't show up as differences. auto fl = [](u32 v) { return IsNaNBits(v) ? 0x7FFFFFFFU : v; }; - fprintf(f, "%llu r", (unsigned long long)seed); + fprintf(f, "%s %llu r", core, (unsigned long long)seed); for (int i = 0; i < 32; ++i) { fprintf(f, " %08x", s.r[i]); } @@ -942,8 +942,10 @@ bool TestCPUEquivalence() { if (dumpPath) { FILE *f = fopen(dumpPath, "w"); if (f) { - for (int i = 0; i < count; ++i) { - DumpFuzzState(f, firstSeed + i, results[(size_t)i * numCores]); + for (int c = 0; c < numCores; ++c) { + for (int i = 0; i < count; ++i) { + DumpFuzzState(f, CoreName(cores[c]), firstSeed + i, results[(size_t)i * numCores + c]); + } } fclose(f); } else {