From f45f4d5c851bc583d3ce72a944251d7ccce1ba4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Oct 2026 20:18:46 +0200 Subject: [PATCH] SaveState: Only rehash functions in code a load changed Every load hashed all known functions again, for the replacement hooks: 2.4-6 ms, about half the load. Functions in module code the load left as it was keep their hash, so only the rest is hashed now (0.5-1 ms). The code is compared on every load for this, not only with a JIT. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceKernelModule.cpp | 11 ++++++++++- Core/HLE/sceKernelModule.h | 3 +++ Core/MIPS/MIPSAnalyst.cpp | 27 ++++++++++++++++++++++++--- Core/MIPS/MIPSAnalyst.h | 5 ++++- Core/SaveState.cpp | 5 ++--- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 2943ed4443..a48c46ba6a 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -534,6 +534,13 @@ static void __KernelModuleInit() { eventModuleLoadDone = CoreTiming::RegisterEvent("LoadModuleDone", &__KernelLoadModuleDone); } +// Set for the next load by the savestate code: code the load left as it was. +static std::vector> g_unchangedCodeOnLoad; + +void __KernelModuleSetUnchangedCodeOnLoad(std::vector> unchanged) { + g_unchangedCodeOnLoad = std::move(unchanged); +} + std::vector> __KernelModuleCodeRanges() { std::vector> ranges; u32 error; @@ -615,7 +622,9 @@ void __KernelModuleDoState(PointerWrap &p) { } // The functions were found in memory from before the load, where other code may have been // (an overlay module, say.) Hash them from what's there now, so a hook only goes where it matches. - MIPSAnalyst::RehashFunctions(); + // Only what the load may have changed: hashing it all again takes milliseconds. + MIPSAnalyst::RehashFunctions(g_unchangedCodeOnLoad); + g_unchangedCodeOnLoad.clear(); if (g_Config.bFuncReplacements) { MIPSAnalyst::ReplaceFunctions(); } diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h index fd1224b4c9..9df71ded49 100644 --- a/Core/HLE/sceKernelModule.h +++ b/Core/HLE/sceKernelModule.h @@ -231,6 +231,9 @@ KernelObject *__KernelModuleObject(); void __KernelModuleDoState(PointerWrap &p); // The code of the loaded modules, as (address, size). std::vector> __KernelModuleCodeRanges(); +// For the next savestate load: code it leaves as it was, as (address, size). Its functions aren't +// hashed again. +void __KernelModuleSetUnchangedCodeOnLoad(std::vector> unchanged); void __KernelModuleShutdown(); class MIPSState; diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index fe736e5dfd..362c061526 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -909,7 +909,22 @@ namespace MIPSAnalyst { return DetermineRegisterUsage(reg, addr, instrs) == USAGE_CLOBBERED; } - void HashFunctions() { + // Ranges are (address, size), sorted by address and not overlapping. + static bool InsideRanges(const std::vector> &ranges, u32 start, u32 end) { + start &= 0x3FFFFFFF; + end &= 0x3FFFFFFF; + auto iter = std::upper_bound(ranges.begin(), ranges.end(), start, [](u32 address, const std::pair &range) { + return address < (range.first & 0x3FFFFFFF); + }); + if (iter == ranges.begin()) { + return false; + } + --iter; + const u32 rangeStart = iter->first & 0x3FFFFFFF; + return start >= rangeStart && end <= rangeStart + iter->second; + } + + static void HashFunctions(const std::vector> &unchanged = {}) { std::lock_guard guard(functions_lock); std::vector buffer; @@ -918,6 +933,10 @@ namespace MIPSAnalyst { if (!Memory::IsValidRange(f.start, f.end - f.start + 4)) { continue; } + // end is the last instruction. + if (f.hasHash && InsideRanges(unchanged, f.start, f.end + 4)) { + continue; + } // Immediates are masked out, so relocations don't change the hash. buffer.resize((f.end - f.start + 4) / 4); @@ -1311,9 +1330,11 @@ namespace MIPSAnalyst { return false; } - void RehashFunctions() { + void RehashFunctions(const std::vector> &unchanged) { + std::vector> sorted = unchanged; + std::sort(sorted.begin(), sorted.end()); std::lock_guard guard(functions_lock); - HashFunctions(); + HashFunctions(sorted); UpdateHashToFunctionMap(); } diff --git a/Core/MIPS/MIPSAnalyst.h b/Core/MIPS/MIPSAnalyst.h index fda9908b56..bfd9b05071 100644 --- a/Core/MIPS/MIPSAnalyst.h +++ b/Core/MIPS/MIPSAnalyst.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include "Common/CommonTypes.h" @@ -118,7 +119,9 @@ namespace MIPSAnalyst { const char *LookupHash(u64 hash, u32 funcSize); // Hashes the known functions again, from what memory holds now (after a savestate load.) - void RehashFunctions(); + // Functions that already have a hash and lie entirely in one of the (address, size) ranges in + // unchanged keep it: their code is what it was hashed from. + void RehashFunctions(const std::vector> &unchanged = {}); void ReplaceFunctions(); void UpdateHashMap(); diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 91bd706c32..7138d1d7e0 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -235,13 +235,12 @@ int g_screenshotFailures; if (p.mode == p.MODE_READ) { hookedOps = Replacement_GetHookedOps(); RestoreReplacedInstructions(0, 0xFFFFFFFF); - if (MIPSComp::jit) { - codeCompare.ranges = __KernelModuleCodeRanges(); - } + codeCompare.ranges = __KernelModuleCodeRanges(); } Memory::DoState(p, &codeCompare); if (p.mode == p.MODE_READ) { Replacement_RepairStaleHookOps(hookedOps); + __KernelModuleSetUnchangedCodeOnLoad(codeCompare.unchanged); currentMIPS->KeepJitBlocksOnNextLoad(std::move(codeCompare.unchanged)); }