The IR frontend compiles on past a conditional branch that isn't taken,
so the IR cores don't check downcount there, while the interpreter
ended its block at every branch. Events then landed on different
instructions, and a game's trace left the interpreter's within a few
hundred syscalls.
The interpreter now applies the same rule at run time: not likely, the
operands not decided by their registers, no branch in the delay slot,
no prefix pending after the delay slot, and the same instruction and
branch limits, counted the same way. It also goes on past a beql/bnel
over a break, which the IR frontend drops. The limits move to IRInst.h
so both use them.
With it, the interpreter, IR interpreter and ARM64 JIT produce the same
trace of syscalls and thread switches over 5 emulated seconds of God of
War, Tekken 6 and Daxter.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The cores charged cycles differently and checked downcount at different
places, so interrupts and thread switches landed on different
instructions, and a game could take a different path on each core.
- IR: j, jal, bc1t and bc1f now charge their delay slot.
- IR: a syscall sees every earlier instruction charged, including a
branch it's the delay slot of, and is charged once it returns. It used
to charge itself first, or, in a delay slot, never.
- Interpreter: a skipped likely delay slot costs what the instruction
would, as in the IR cores, instead of 1.
- Interpreter: both run loops go on to the end of what the IR cores run
as a block before checking downcount, and charge each instruction as
it runs.
The CPUEquivalence fuzzer now compares the cycles up to its terminating
syscall. With continueBranches off, the three cores now produce the same
trace of syscalls and thread switches, ticks included, over several
emulated seconds of commercial games. With it on, the IR cores leave the
interpreter within a few hundred syscalls, as it moves block ends.
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>
As on hardware (cpu/cpu_alu/branch_link_ra), with ra as rs the branch
sees its old value, not the return address. Every core linked first.
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>
* 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