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>
With LSX, the Vec2 pack and unpack ops went to the interpreter, as they
addressed the lanes of a pair one by one. They're now an interleave or a
pick of halfwords in the pair's register.
That makes pairs, so mapping a quad whose halves are mapped pairs (two
vs2i.s feeding a vi2f.q) joins them with VILVL_D rather than storing both
and loading the quad back, like the ARM64 JIT's ZIP1 (3d20d51c70).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ops from 108e66f2d1, so both now get moreVec4Ops. With LSX they're
vector code: vfpu_minmax_key() of each lane picked by VBITSEL, a compare
ANDed with 1.0, and VFFINT with a scale. RISC-V and LoongArch without LSX
do them lane by lane, vslt and vsge as a compare into a GPR rather than
through fpcond.
Vec4VfpuMultiDot stays ARM64-only, so the frontend still doesn't emit it
for these.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Like the ARM64 JIT (e33f38afa6), each jr and jalr does the dispatcher's
lookup itself, for an indirect branch of its own, and goes to the
dispatcher when out of downcount or with no block yet.
A bad target now faults in the block rather than at dispatchFetch, so the
fault handler takes any fault in the block shadow as a bad jump, with the
masked target from the address. That covers ARM64's too, which was handled
as a bad memory access.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As on RISC-V: the accurate dot products called the IR interpreter. Now the JIT calls a routine
generated at startup, vfpu_dot_lsx() with LSX (the operands go in v8 and v9) and vfpu_dot_scalar()
without (through mips->vfpuDotArgs), with inf and NaN going to vfpu_dot_reference(). With LSX, a
result that lands in one of the operands' Vec4s goes in with VEXTRINS.
VFPUDotJit covers both versions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The accurate dot products went to the IR interpreter, flushing everything around each one. Now
the JIT stores the operands to mips->vfpuDotArgs and calls a routine generated at startup, which
clobbers only registers late in the allocation order. Inf and NaN go to vfpu_dot_reference().
The routine is vfpu_dot_scalar() in RV64, using Zbb and Zicond where it has them, or with the V
extension, vfpu_dot_sse2() in RVV. The rounding at the end comes from FCVT.S.LU with a static
round-to-nearest-even, which also gives the exponent, so there's no need for Zbb's CLZ.
VFPUDotJit runs each variant the host can run against the reference on VFPUDot's inputs, the RVV
one with poisoned vector registers. PPSSPP_CPUFUZZ_HOSTEXT=- now turns V off too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Hosts without NEON or SSE2 ran vfpu_dot_reference() for every dot product. vfpu_dot_scalar() is
the same computation without its branches, finished like the SIMD versions, and is what RISC-V
gets. LoongArch builds with LSX (GCC's default la64v1.0 has it) get vfpu_dot_lsx(), the SSE2
version in LSX.
VFPUDot checks vfpu_dot_scalar() on every host, and its inputs come from ForEachVFPUDotTestCase(),
for the JITs' routines to use too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
vsgn takes the sign of s - t, and a NaN's sign counts (cpu/vfpu/specials).
RISC-V's subtraction always makes the positive canonical NaN, so there vsgn
of a negative NaN gave 1 instead of -1, unlike the PSP and the IR cores.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
For a matrix like E020, the register layout checks pass, but transposing
the register names and taking their columns gives unaligned Vec4s. The IR
interpreter crashed on x86 on the aligned store (vmscl.p M000, M702, S301
then vmmov.q E020, E100). Take the Vec4s from the checked layout instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A fused multiply-add rounds once where the IR interpreter rounds twice, so
vdot, vhdp, vtfm and vmmul gave different low bits. Multiply and add
separately, in the IR interpreter's order. (LoongArch's LSX path already
did, in the pairwise order of the x86 and ARM64 JITs.)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FMIN/FMAX return the other operand for a NaN, so the saturating ops clamped
a NaN to the bound. They now compare and select like vfpu_clamp(). vcmp NE
used the ordered not-equal, false for a NaN.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#22578
Since loads became deferred (#22507), the file is read from the event at the
end of the wait, where there is no current thread and so no current
directory. Crash Tag Team Racing loads its PRXs as "prx/sc_sascore.prx" and
so on, got NOCWD for all of them and never started. Make the path absolute
with the caller's current directory before waiting.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#20420
Each shot's light lasts 0.1s of effect time. At 30fps the 303 assault rifle
fires every 0.1s, and the wall goes dark for a frame per shot. At 60fps it
fires every 1/12s, the lights overlap and the wall stays lit. Hook the flash
effect's constructor and shorten the light to 0.075s, which keeps the 30fps
pattern and leaves one dark frame in five at 60fps.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DescribeCodePtr() called everything in the fixed code other than a few
routines "fixedCode". It now uses the routine list the profiler has
(moved to the backend as GetAllFixedCodeLabels()), with an offset when
not at the start, so a block ends in "b dispatcherCheckCoreState".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The debris smoke trails add a node on every update, so at 60fps the smoke was twice as dense
as at 30fps and spawned twice as many trails. Keep the nodes 1/30 of the effect's clock apart
instead. In the angel boss explosion the display list now peaks at 0x483ec bytes without the
overflow guard's help, against 0x637f4 before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#19755
The game adds smoke trail nodes per update but expires them by time, so the faster we run, the
more trails are alive. After the angel boss in Bonn, the debris trails grow the display list
past its 0x5DC40 byte buffer at 60fps. The overflow lands in the music stream's Atrac buffer
(the "Invalid start bit" errors), corrupts textures, and crashes the game. Locking the CPU to
222MHz, the known workaround, slows the frame rate enough to avoid it.
Hook the trail draw and skip a trail when the current list buffer, whose limit the game keeps
next to its write pointer, is nearly full.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A module the game loaded before the state was saved, but that this run never loaded, was never
scanned, so loading such a state left all of its code without function replacements and hooks
(and without functions in the debugger). Scan any loaded module with no analyzed functions in its
text range before rehashing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
vhdp's s, [x y z 1], went lane by lane, splitting the quad apart in the
register cache only to zip it back together for the dot. It's now a
Vec4Init and a Vec4Blend.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mapping a quad whose halves were in registers as pairs (two vs2i.p
feeding a vi2f.q) flushed both and loaded the quad back, a store
forwarding stall.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
New IR ops do them four lanes at a time where the backend has them, the
ARM64 JIT and the IR interpreter so far. vmin and vmax compare by
vfpu_minmax_key() in NEON, which took about twelve instructions a lane
through the integer registers before; vslt and vsge are a compare and an
AND with 1.0.
vi2f becomes one SCVTF with fractional bits.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each jr and jalr now does the dispatcher's lookup itself, so each has its
own indirect branch, which the CPU predicts much better than the one in
the dispatcher that every one of them shared. Out of downcount or with no
block yet, it goes to the dispatcher as before.
Cycles over 10 emulated seconds: -2.3% in ATV, -1.4% Tekken 6 and Ridge
Racer, -1.2% Daxter, -1.1% Killzone, and -0.3% to -0.8% in the rest.
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>
A new IR op, Vec4VfpuMultiDot, does four vfpu_dot()s against one vector,
the other operands being the rows or Vec4s of a 4x4 block of registers.
The frontend emits it for vmmul and vtfm/vhtfm, and for runs of vdot that
fit it, like a matrix times a vector written out.
On ARM64 it calls a routine that has the dots in lanes rather than the
terms, so nothing is horizontal and nothing goes through the integer
registers. The block comes in with LD1, or LD4 for the transposed form,
when none of it is in host registers. The IR interpreter runs it through
vfpu_dot(); the other backends don't get it yet.
The CPU fuzzer gets runs of vdot shaped for it, and finds no difference
between the cores on them.
There's a routine per number of terms, so vdot.t runs don't pay for a
fourth. The vector's significands are worked out once and multiplied by
element, and the rounding at the end is UCVTF's in round-to-nearest,
which is what games run in; the integer version stays for the other
modes. The routine saves nothing, and the call site keeps only those
registers it clobbers that hold guest state.
JitDotBench, ns per dot: vtfm4.q 3.40, vmmul.q 3.12, a run of vdot.t
2.83, against 4.6 for single dots.
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>
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>
With a D prefix the result goes through temps, and a lane the condition
doesn't move kept whatever the temp held. They now start as d. That
exposed OptimizeFPMoves renaming the src3 of FCmovVfpuCC, which is also
its destination, so it no longer replaces a register an op reads as src3
and writes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A store at a negative offset whose end wraps past zero, like 16 bytes at
-16, counted as ending before a load at -12, so the load reused an older
value from before the store.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FlushNativeReg left a register caching MIPS zero mapped, as there's
nothing to store, but its callers flush to clobber the register. After
the dot product routine's call, a store of zero stored its result.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They wrote each lane straight to the destination, so with a destination
that holds a later lane's source (a wrapped row over a column, say) that
lane read the converted value.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Like vmscl, they flipped the transposed bit to get Vec4s, which names
other registers for a matrix that wraps around. Taking the elements in
transposed order does it without.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
OptimizeFPMoves replaced a temp read by vs2i (from a copy of a lane of
its own destination) with the original register, which the ARM64 JIT
then lost by mapping the destination without loading it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They read one lane, the first of the source vector at the size in the
instruction, as the interpreter does, which for the pair and quad
encodings is another register than the single the IR took.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As on hardware (cpu/vfpu/vfim), a denormal half becomes a signed zero,
and inf and NaN keep their mantissa bits in place. The interpreter got
denormals half their value, the IR the IEEE one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both were turned into their untransposed forms to get Vec4s, but for a
matrix whose offset makes it wrap around, flipping the transposed bit
names other registers. Taking the elements of both in transposed order
gets the same Vec4s without that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As on hardware (cpu/vfpu/prefix_load), lv.q, lvl.q and lvr.q write every
lane whatever mask a pending D prefix has, and leave the prefix pending,
like lv.s and mtv already did. The IR cores fall back to the interpreter
for lvl.q and lvr.q, so they get this too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Game runs in headless measured the C++ vertex decoder, which the app only
uses where it can't JIT. pspautotests and the frametests pass with the
JIT decoders. --vertex-jit=false brings back the C++ decoder.
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 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>