The native dispatcher generated for the ME leaves when its downcount
runs out instead of calling CoreTiming::Advance, and IRNativeJit gets
RunDowncount. Pointerify is decided from the CPU's own memory base. ARM32,
RISC-V and LoongArch64 still give the ME the IR interpreter.
LoadKernel/StoreKernel are now compiled on ARM64 and x64 rather than
going through the IR interpreter one op at a time: memory directly, or a
call to fixed code (which saves the caller-saved registers guest values
live in) for 0xBC000000-0xBFFFFFFF. ARM64's block size estimate counts
them as such. The 16-bit ones stay generic while the software renderer's
depth mirrors are active, as Load16/Store16 do. That matters for the main
CPU's kernel code too, and for all code while the ME is emulated, which
runs in kernel mode everywhere. mcidclan's me-soft-interrupt went from
476 billion to 6.4 billion host instructions for 3 emulated seconds,
ending with the same ME counter as the IR interpreter.
The ME unit tests run on the native JIT too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Unless the interpreter is selected, the ME gets its own IRInterpreterJit,
with its own block shadow, and runs it without CoreTiming
(RunDowncount, split out of the dispatcher loop). Its MIPSState never
touches the main CPU's replacement hook.
The IR frontend compiles through the CPU's address space: instructions
read as they are in the ME's memory (no main CPU replacements or hooks),
kernel mode always, jump targets and block bookkeeping checked against
its rules, syscall left to the interpreter (which traps it). An
interpreted instruction that ends a block now gets its own pc first, so
halt goes on from the right place.
The main CPU's icache invalidations are passed on to the ME, which runs
code from the same RAM.
The unit tests run the ME on both cores. mcidclan's me-soft-interrupt
runs about four times as fast.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
IRJit is now the base the IR JITs share: the frontend, the table of guest
blocks, and compiling and invalidating them. IRInterpreterJit and
IRNativeJit are the two kinds. Only IRInterpreterJit keeps IR, in its arena,
along with its dispatch loop and per-block profile.
The native JITs no longer keep the IR they compiled from. The debugger and
the --jit-profile report compile a native block's IR again when they want it,
on a copy of the frontend's state, which gives what ran unless the code or
that state has changed since.
IRBlockCache and IRBlock become JitBlockTable and JitBlock: where each block
came from, the page lookup for invalidation, and the block shadow entry (an
arena or native code offset, depending on the JIT). One IRJitDebugInterface
serves both JITs, taking the MIPS code from the table and the IR and host code
from the JIT, in place of the native JIT's own debug interface.
The native backends don't see the table at all. They compile a span of IR for
a block number and start PC, and keep what they need about their own blocks
(start PC, code offset, which block starts where, for linking) next to the
exits they already tracked. The register cache takes the instructions
directly.
The tracer's block index now goes into the IR before the block is stored, so
native blocks get it too. The unused FindByCookie goes. TestJit checks that
the native JIT keeps no IR, and can still show it. The JIT stats overlay no
longer shows IR for the native JITs. MIPSState can't be copied, now that it
owns its JIT.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The global MIPSComp::jit becomes MIPSState::jit, created and deleted by the
state it runs. JitAt() compiles with the JIT of the state it's handed, so
the dispatchers no longer go through a global. Code that is about the main
CPU by nature (the UI, the crash handler, the disassembler's symbol lookup,
the JIT harness) uses mipsr4k.jit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
Shows the number of blocks, how full the native code space and the IR
arena are, and how many times each filling up cleared the cache.
The "Ran out of block numbers" message was logged for both of those, and
there is no block number limit. It now says which one filled up.
Also hide the GPU profile and allocator overlays by value on backends
that lack them. Cutting the last two off the list had been hiding the
framebuffer list since it was added after them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both now list their routines (GetFixedCodeLabels), so the disassembly
names jump targets and --jit-profile charges routine samples to the
calling block. The profiler samples them on Linux, and its report
disassembles their host code per instruction (RISC-V's in 2- and 4-byte
steps).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A non-likely conditional branch now exits to its target when taken, and
the block goes on with the not-taken path, up to 8 branches and 96
instructions. Loops written as a few blocks become one with one exit per
iteration, and code around branches gets optimized together. Branches
decided by their operands, like b, still end the block.
Not with a prefix pending, as blocks assume default prefixes on entry
and only the end of a block checks that it leaves them so.
10 emulated seconds, Vulkan, ARM64: cycles -4.6% in God of War, -3.5%
Killzone, -3.0% God Eater, -2.8% Daxter, -2.3% Tekken 6, -2.2% GTA,
-2.1% ATV, about -1% in the rest.
docs/cpu-equivalence.md gets the fuzzer's new classes and what it found
on this branch and in #22576.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The native IR JITs get the emulator thread's host PC sampled every 50 us (a
signal on macOS and Linux, SuspendThread on Windows), mapped to blocks, the
routines they call and host functions. The report has each hot block's MIPS,
IR and host code with the samples per host instruction. The IR interpreter's
block timing, previously behind IR_PROFILING, now runs on the same switch.
Both cost nothing when off.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FakeJit was the native JIT for architectures without one, but the config
already forces those onto the IR interpreter, so CreateNativeJit() now
returns that instead. The vertex decoder fallback no longer needs
FakeEmitter.
JitBlockCache was the block cache of the pre-IR JITs. Its header keeps
the debug interface types the IR block caches use. Also removes the
JitInterface link hooks only it called, and the JitState fields tied to
it. This fixes a null dereference in the ImDebugger memory section,
which read GetBlockCache(), always null with the IR JITs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The prefix fields were decoded by hand in the interpreter, the IR frontend,
JitState, MIPSState and the disassembler, with three copies of the constant
table. They now share small decoders and one table, in MIPS.h. The backends'
Vec4Shuffle lane decoding gets a helper of its own in IRInst.h.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backend follows the ARM64 one: a dispatcher, a register cache on top of the IR one, and
native exits, syscalls, breakpoints and transfers. Everything else goes to the IR interpreter
for now. Floats are mapped one lane per S register (no SIMD mapping yet), and addresses are
masked, as on every 32-bit build.
The JIT core now uses it on ARM32 in place of the old ArmJit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Their IR-based replacements stay. The JIT core now creates the IR JIT on both, so "JIT using
IR" is the same as "JIT" on every architecture (ARM32 still uses its legacy JIT for both). The
settings no longer offer it, a saved JIT_IR loads as JIT, and the JIT harness and CPUEquivalence
fuzzer drop the duplicate run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The old disarm handled the integer instructions but only a fraction of VFP
and NEON, and got several of those wrong. The new one decodes ARMv7-A ARM
state with VFPv4 and NEON, printing UAL with upper case mnemonics.
The new ArmDisasm test runs every ARM emitter function through it, two
examples each. Tools/arm_disasm_check.py compares the test's output with
llvm-mc; they agree on all of it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Config.h is edited about 100 times a year and each edit recompiled 198
files; now 172. SmallDataConvert.h pulls in arm_neon.h and was included by
headers that don't use it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Memory no longer holds the hooks, so the disassembly fetches the op the CPU runs, which is the
CallRepl pseudo-op at a hooked address. That lookup can come from the Win32 debugger's UI thread,
so the CPU thread now changes the hook map under a lock (and reads it without, being the only
writer).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The JITs used to find a block by overwriting its first instruction with an "emuhack" holding
the offset into the code space, and replacement functions installed their own emuhack the same
way. Anything that read code (savestates, the debugger, memory dumps, cheats, the function
hasher, games reading their own code) had to translate them back.
Blocks now live in a block shadow: one u32 per guest instruction, indexed by the PC masked with
BLOCK_SHADOW_MASK (1GB on 64-bit, folding the kernel and uncached mirrors; 256MB on 32-bit, iOS
and Switch, where address space is short), with the low bits cleared so a misaligned PC can't
read across two entries. Only the ranges under memory views are committed, so a bad PC still
faults in the dispatcher, and the shadow is reserved before the memory map so it can't land in
a gap of the 4GB window. Entries hold full 32-bit offsets, lifting the 16MB limit. The
dispatchers on every backend, and the IR interpreter's loop, look blocks up there.
A hooked instruction without a compiled block gets the JIT's hook value in its entry: the offset
of the dispatcher's compile path (1 for the interpreters, the IR arena keeping that offset
empty), so dispatching there compiles a block, and the frontends substitute the hook. Compiling
replaces the entry with the block's, and destroying the block puts the hook value back. The hook
value is rewritten when the JIT changes or regenerates its dispatcher.
Hooks live in a map keyed by the masked address, and are dropped when the game overwrites the
hooked instruction, or when an icache invalidation finds the function's hash (immediates masked,
as when matching it) changed. Those checks run when queued invalidations are processed, since
cheats and the debugger queue them before writing. Installing and removing hooks invalidates
deferred, as it happens in HLE calls that can run inside a block. The cache instruction goes
through MIPSState, so it drops changed hooks too.
With clean memory, the save/clear/restore around savestates, memory reads and dumps goes away,
as do the source-range invalidations in the memcpy replacements, sceDmac and the cheats.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An interpreter state always claimed an uneaten VFPU prefix, which made a
JIT loading it run in unknown-prefix mode for the rest of the session.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
InvalidateICache widened its search by MAX_BLOCK_INSTRUCTIONS, but
block_map_ is keyed by byte addresses - a block can span four times that
many bytes. A block longer than 0x4000 bytes starting just below the end
of the invalidated range could therefore be missed and left behind after
the code under it changed. Multiply by 4.
DestroyBlock's "invalid original address" safety check returned after
setting b->invalid but before UnlinkBlock() and the checkedEntry
poisoning, so other blocks kept jumping directly into a block the cache
had already written off. The check is there to guard the memory access
that restores the original opcode, so only guard that.
Also: codeSize is a byte count assigned from a full pointer difference,
so a block over 64KB truncated it (affecting GetAddressFromBlockPtr and
the disassembly view); widen it to u32. The RAMTOP range check missed a
block sitting exactly on the user-memory midpoint, since the RAMBOTTOM
check next to it is strict. And GetBlockDebugInfo disassembled one
instruction past the end of the block.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Rename LogType to Log
* Explicitly use the Log:: enum when logging. Allows for autocomplete when editing.
* Mac/ARM64 buildfix
* Do the same with the hle result log macros
* Rename the log names to mixed case while at it.
* iOS buildfix
* Qt buildfix attempt, ARM32 buildfix