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 toolchain encodes vrnds's operand in vs and leaves vd zero, so we
always seeded from S000. Resistance: Retribution reseeds per particle
from S620, which made every particle in its smoke and explosions take
new random values each frame. They flickered, and some never drew.
Hardware test: cpu/vfpu/vrnd (vs and vd fields).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CTXREG points at mips->f[0] in the x86-64 IR JIT, 128 bytes into MIPSState,
but the dot routine's inf and NaN path stored its operands at
offsetof(MIPSState, vfpuDotArgs) from it. It read them back from the same
place, so the results were right, but it wrote over invalidateAll_ and 16
bytes past the end of mipsr4k. On Alpine that crashed the unit tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since the sequential copies heuristic went (50696c5b1f), every partial readback
waits for the GPU. Motorstorm reads parts of the screen about 18 times a frame,
and ran about 75% slower than 1.20.4: 5403 blocking readbacks in 10 emulated
seconds, against 156.
With the flag, the first partial color readback of a buffer reads all of it into
a copy outside PSP memory, and the pieces until the next render to the buffer
are copied from there. Unlike the old heuristic, only what the game asked for is
written to PSP memory. Motorstorm is back to 153 blocking readbacks and 1.20.4's
speed, with identical output. Grand Knights History, which the heuristic was
added for, gets the flag too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the PSP, lanes past the vector size where both the S and T prefixes are
constants add their product to vdot, and an S constant there is added in by vfad
and vavg (cpu/vfpu/prefix_lanes). Without AccurateVdot, the IR leaves those to
the interpreter; the accurate dot products now include them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The operands go in xmm0 and xmm1 and the result comes back in edi. The routine
saves nothing: the call site flushes whatever guest state is in the registers it
clobbers, the xmms allocated last and rax, rcx, rdx, rsi and rdi, the last two
never allocated outside Windows. 32-bit x86 keeps going through the IR
interpreter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After clang's ARMv7 output for vfpu_dot_neon(), in Q8-Q15, which the register
cache leaves alone. The end is done in 32-bit integers instead of 64-bit ones,
checked bit-exact against vfpu_dot_finish(). The call site gathers the lanes into
Q8 and Q9 and flushes whatever guest state is in R2, R3, R12 and LR, usually
nothing. Inf and NaN go through a routine that calls vfpu_dot_reference().
Without NEON, Vec4VfpuDot still goes through the IR interpreter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The games that needed vmmul's accurate dot products (Tekken 6, Warriors Orochi,
Tomb Raider Anniversary) now get AccurateVdot for every dot product, even with
the setting turned off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The accurate dot products cost a few percent at most in the IR JIT, apart
from GTA (about 20% on a game that's cheap to run anyway). The fuzzer follows
the default too, unless PPSSPP_CPUFUZZ_ACCURATE_VDOT=0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The operands go in q0 and q1 and the result comes back in w1. The routine saves
nothing: the call site flushes whatever guest state is in the few registers it
clobbers (q4-q6, w0-w3), which come late in the allocation order, so usually
there's nothing to do. That's about 20 bytes a call, with nothing going through
memory, and as fast as having the whole thing inline, which made a vmmul.q about
5 KB of code. Inf and NaN go through a routine that calls vfpu_dot_reference().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It was integer code, about four times the work. It also clobbers q0-q3 and
x16/x17 now, which both ARM64 JITs keep free, instead of saving them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The disassembler now decodes the across-lanes and three-different groups,
which it only printed as raw words. The encodings match clang's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GenerateX64VfpuDot() is the x86-64 counterpart of GenerateArm64VfpuDot(): the
same integer version of vfpu_dot(), preserving every register but the flags,
with inf and NaN operands going to vfpu_dot_reference(). A dest lane inside a
source Vec4 still takes the generic fallback, like Vec4Dot does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GenerateArm64VfpuDot() generates vfpu_dot() as integer code, once per JIT, to
be called with BL. The operands and result go through mips->vfpuDotArgs and
vfpuDotResult, and the routine preserves every register but x30, so a call
site is two stores, the BL and a load, with nothing flushed. Inf and NaN
operands go to vfpu_dot_reference(), with the caller-saved registers saved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frontend lowers vdot, vhdp, vfad, vavg, vdet, vcrsp/vqmul, vtfm/vhtfm and
vmmul to it then, with the operands built from the same lanes, constants and
negations the interpreter feeds vfpu_dot(). BuildDotOperands() puts them in
Vec4 regs, using the free temps where they aren't one already, and the ops
fall back to the interpreter when there aren't enough. The IR interpreter runs
it through vfpu_dot(), and the native backends through their generic fallback
for now. vmmul takes the same path for the MoreAccurateVMMUL compat flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vdot, vhdp, vfad, vavg, vdet, vtfm/vhtfm, vcrsp/vqmul and vmmul compute
through vfpu_dot() then, as the PSP does. vmmul still also takes it from the
MoreAccurateVMMUL compat flag. With it, signed_zero matches the PSP, and
fcr31, nan_gen and specials get closer. What's left there is the single-lane
ops' NaNs and the VFPU's rounding.
vcrsp.t no longer zeroes infinities before its last dot: inf * 0 gives the
VFPU's NaN there on the PSP (cpu/vfpu/nan_gen). The NaN and denormal fixups in
vadd, vsub, vmul and vdiv shared the flag but aren't dot products, so they
get their own (still off).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Not wired to anything yet. It will make every core compute the VFPU's dot
products (vdot, vmmul, vtfm and the rest) bit-exactly like the PSP, through
vfpu_dot().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FSat0_1's MAXSS had the known 0 second, so a NaN came out as 0, unlike the
interpreter and the other backends, and the PSP (cpu/vfpu/prefix_sat, nan_gen).
-0.0 is now cleared by a compare and mask rather than by adding 0.0, which would
quiet a signaling NaN.
Co-Authored-By: Claude Opus 5.5 <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>
On the PSP, lanes past the vector size where both the S and T prefixes are
constants add their product to vdot, and an S constant there is added in by vfad
and vavg (cpu/vfpu/prefix_lanes). The interpreter does that; the IR dropped those
lanes. Such prefixes are rare, so the IR leaves them to the interpreter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each next to its .cpp, or with its neighbours in the unconditional list for
its directory. They're only for IDEs, so this changes nothing in the build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Android's armeabi-v7a uses the softfp calling convention, where floats travel in R0/R1, not S0/S1.
The math helper calls (vfpu_sin and the rest) only did the hard float version, so on Android they
got garbage and returned it (broken 3D in Virtua Tennis: World Tour). Like the old ArmJit, check
__ARM_PCS_VFP. With an armel softfp build under qemu, the fuzzer found 41 of 3000 cases where the
JIT differed from the IR interpreter before this, and none after.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#12343
Killzone: Liberation's Chapter 5 DLC is an installer that the PSP turns
into ms0:/PSP/GAME/<id>/KZL.PRX, which the game loads with
sceKernelLoadModuleDNAS. That file is a PGD around a type 3 PRX (decrypt
mode 18), both keyed to the console that installed it. Its Fuse ID
gives everything needed:
- libkirk: the per-console key derivation, and KIRK commands 3, 5 and 8
built on it (KIRK ROM per uofw, key mesh per psdevwiki).
- PrxDecrypter: type 3, which is type 5 with the first step through
KIRK command 3 (JPCSP's PRX.java), and the DNAS PGD unwrap.
- sceKernelLoadModuleDNAS uses the new FuseID setting (or --fuse-id).
Without one, or with the wrong one, the game falls back to its stock
module and an OSD message says why.
- Headless --re-decrypt takes --re-decrypt-key and --fuse-id, to decrypt
such a file offline.
Verified with a patch installed on a real PSP: the v1.20 module loads,
starts and reads CHAPTER5.BIN. Type 5 output is unchanged (an NPJH50521
PBOOT.PBP decrypts byte-identically). How to install one over PSPLink is
in docs/dnas-patches.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
See #12343
Killzone: Liberation's bootloader loads its DLC patch from
ms0:/PSP/GAME/<id>/KZL.PRX through this, and falls back to the disc's
KZL.PRX on any negative result. The stub returned 0, which the game took
as a module ID and crashed starting it.
The installed file is keyed to the PSP that installed it (KIRK command 2,
whose key isn't known), so we can't decrypt one copied from a PSP. Unwrap a
PGD we can open with the caller's key and load a module that's already
decrypted; otherwise fail with an OSD note, and the game runs without the
patch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The IR-based one replaces it. With that, every native JIT is IR-based, which the JIT viewer and
the settings' comments now assume. The ARM emitter test loses its part that drove the old FPU
register cache.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With each lane in its own S register, these are scalar VFP ops per lane, and the packs go
through integer registers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
VFPv3 has no scalar min/max and no conversions with a rounding mode of their own, so saturation
is compares and conditional moves, and round/ceil/floor swap FPSCR's RMode around the conversion
as the old ArmJit did. vmin/vmax compare by vfpu_minmax_key in integer registers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The address is masked into a scratch register and used as an offset from the memory base.
VLDR/VSTR have no register offset form, so float and Vec4 accesses add the base first.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mostly as on ARM64, with HI and LO in separate registers. Register shifts mask the amount to
five bits first, since ARM shifts by the whole bottom byte, and msub is a multiply followed by a
64-bit subtract. Division needs the IDIV extension and goes to the IR interpreter without it.
Co-Authored-By: Claude Opus 5.5 (1M context) <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>
A flush stores pending imms through SCRATCH1, so in a non-constant MemoryCheck, and in
ValidateAddress's bad-address path, the address computed into SCRATCH1 could be replaced by an imm
before IRRunMemCheck or ReportBadAddress saw it. It now waits in SCRATCH2, which flushes don't use.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When floats are mapped one lane at a time (RISC-V and LoongArch), a NOINIT dest was only upgraded
to a loaded one when a source had the same first reg. In Vec2Pack32To16 vf3, vf2,vf3 the dest is
the source's second lane, so it could be mapped without a load and the op read garbage. Found
with an IR-based ARM32 JIT, which failed vi2s.q C002, C000 in cpu/vfpu/overlap because of it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The encodings are reachable from guest code, and the CPUEquivalence fuzzer generates them, which
stopped a debug-build "UnitTest all" at the interpreter's assert. Every core already leaves rt
alone for mfv and writes nothing for mtv there (the IR interprets them), so only the asserts go.
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>
mtvc masks the written control register like the other JITs, vsgn tests the
exponent (so a denormal gives 0, as in the interpreter), and the hardware
half-to-float conversion quiets NaNs, so vh2f goes to the interpreter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ARM32 JIT only checked for unknown prefixes, so a swizzle naming a lane
past the op's size, or a T prefix on vscl, read the wrong register or
asserted in VMOV. It now uses the ARM64 JIT's gates, and sends what they
refuse to the interpreter.
Its vasin was a polynomial approximation, not the bit-exact function the
other cores use (cpu/vfpu/exact failed 364 lines), so it goes to the
interpreter too, like vsin and vcos already did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ins with msb below pos encoded an invalid BFI instead of doing nothing,
as the interpreter and the other JITs do, and ext with pos + size past 32
an invalid UBFX. max and min of two constants compared them unsigned.
Found by CPUEquivalence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
So vcmovt never moves and vcmovf always moves (cpu/vfpu/undefined). The
interpreter did nothing for either, and the JITs took it as imm3 = 6,
one CC bit per lane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the PSP, zeros and denormals all compare equal, a tie returns the
second operand, and the result keeps its bits (cpu/vfpu/minmax_zero,
minmax_tie, fcr31). The interpreter got the ties and NaNs right but
compared denormals as numbers, and each JIT had its own fast path with
host min/max, which returned the first operand on a tie and flushed or
not depending on the host's mode.
All cores now compare by vfpu_minmax_key(), an integer key that is 0
for a zero exponent and orders the rest by sign and magnitude, and
select the operand by it, without branching on NaN.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Their old grid setting is no longer migrated, so everyone gets the new
view there. Recent and Remote still keep theirs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>