SaveState: Keep JIT blocks in module code a load doesn't change

Loading a state started the JIT over, so every block the game runs had to
be compiled again, which stutters. Now the loaded modules' code is
compared with the state's as RAM is loaded (a chunk that already matches
isn't copied either), and blocks entirely inside unchanged code stay.
Everything else is invalidated as before, and without a JIT, or with no
unchanged code, the JIT still starts over.

The JIT throws its blocks away if the loaded state needs code they weren't
compiled for (rounding mode switches, or no default prefixes at block
start). The other way round it keeps them, along with the flags they were
compiled under: before, a loaded hasSetRounding of false, or the reset of
lastSetRounding, had the next compile rebuild everything anyway.

In the three seconds after loading a state, Ridge Racer compiles 28 blocks
instead of 4823, Force Unleashed 31 instead of 7727. Frames rendered after
the load are identical either way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-19 19:47:05 +02:00
1 parent 3ac908345c
commit 79d340f4fe
12 files changed
+206 -30

No files matched your search

+10 -1
View File
@@ -53,6 +53,7 @@
#include "Core/HLE/ReplaceTables.h"
#include "Core/HLE/sceDisplay.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelModule.h"
#include "Core/HLE/sceUtility.h"
#include "Core/HLE/sceSas.h"
#include "Core/HLE/sceIo.h"
@@ -60,6 +61,7 @@
#include "GPU/GPU.h"
#include "GPU/GPUCommon.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/RetroAchievements.h"
#include "HW/MemoryStick.h"
@@ -233,13 +235,20 @@ int g_screenshotFailures;
// The state may hold different code. KernelModuleDoState hooks the functions again.
std::vector<std::pair<u32, u32>> hookedOps;
// The loaded modules' code is compared with the state's as it's loaded. Where it's the same,
// the JIT keeps its blocks, so they don't all have to be compiled again.
Memory::RamCompare codeCompare;
if (p.mode == p.MODE_READ) {
hookedOps = Replacement_GetHookedOps();
RestoreReplacedInstructions(0, 0xFFFFFFFF);
if (MIPSComp::jit) {
codeCompare.ranges = __KernelModuleCodeRanges();
}
}
Memory::DoState(p);
Memory::DoState(p, &codeCompare);
if (p.mode == p.MODE_READ) {
Replacement_RepairStaleHookOps(hookedOps);
currentMIPS->KeepJitBlocksOnNextLoad(std::move(codeCompare.unchanged));
}
if (s >= 3) {