mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
Tests: EE loads and stores against PS2 hardware captures
All 21 load/store opcodes over a fixed byte pattern at three offsets, plus a misaligned +11 for the quad ops. ee_rec_loadstore_tests.cpp covers the same ground as a JIT-vs-interp differential, which cannot see a mistake the two engines share - the likeliest place for one being the eight unaligned ops. The capture prints the full 128-bit destination on every load, so "loads leave bits 127:64 alone" is asserted on all twelve of them (LQ being the one that must not), and the `-> $0` rows load into $0 then read it back, which catches a load that wrote the zero register. One case of 164 diverges: the interpreter's LD writes GPR.r[_Rt_].UD[0] with no !_Rt_ guard, unlike all eleven of its neighbours. Recorded as a row that fails if it is ever fixed, with a DISABLED tripwire showing the end state.
This commit is contained in:
@@ -115,6 +115,7 @@ add_pcsx2_test(recompiler_tests
|
||||
# from the captures and are not edited by hand.
|
||||
ee_mmi_console_conformance_tests.cpp
|
||||
ee_fpu_fcr_console_conformance_tests.cpp
|
||||
ee_lsu_console_conformance_tests.cpp
|
||||
)
|
||||
|
||||
target_include_directories(recompiler_tests PRIVATE
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
// GENERATED from unknownbrackets/ps2autotests tests/cpu/ee/lsu.expected
|
||||
// (real PS2 hardware). Do not edit by hand.
|
||||
#pragma once
|
||||
#include <common/Pcsx2Types.h>
|
||||
|
||||
namespace ps2auto_eelsu {
|
||||
|
||||
// C_PATTERN, 3 x 16 bytes. Loads read it in place (base = +16);
|
||||
// stores get a fresh copy per case.
|
||||
inline constexpr u32 kPattern[12] = {
|
||||
0x45678123u, 0x9ABCDEF0u, 0xDEADBEEFu, 0xC0DE1337u,
|
||||
0x23456789u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu,
|
||||
0x8899AABBu, 0xCCDDEEFFu, 0x00112233u, 0x44556677u,
|
||||
};
|
||||
// C_GARBAGE1, the quad register preset.
|
||||
inline constexpr u32 kGarbage1[4] = {0x00001337u, 0x00001338u, 0x00001339u, 0x0000133Au};
|
||||
// The lui/ori register preset, before sign extension.
|
||||
inline constexpr u32 kImmPreset = 0xABCD4321u;
|
||||
|
||||
// `into_zero` cases load into $0 and read it back through
|
||||
// `ori rt, $0, 0`, so lo must be 0 and hi must survive.
|
||||
struct LoadCase {
|
||||
const char* label; const char* op1; int off1;
|
||||
const char* op2; int off2; // op2 null for single-op cases
|
||||
bool imm_preset; bool into_zero;
|
||||
u64 lo, hi; // expected 128-bit rt
|
||||
};
|
||||
|
||||
inline constexpr LoadCase kLoadCases[] = {
|
||||
{"lb +0 [imm]", "lb", 0, nullptr, 0, true, false, 0xFFFFFFFFFFFFFF89ull, 0x0000133A00001339ull},
|
||||
{"lb +16 [imm]", "lb", 16, nullptr, 0, true, false, 0xFFFFFFFFFFFFFFBBull, 0x0000133A00001339ull},
|
||||
{"lb -16 [imm]", "lb", -16, nullptr, 0, true, false, 0x0000000000000023ull, 0x0000133A00001339ull},
|
||||
{"lb +0 [quad]", "lb", 0, nullptr, 0, false, false, 0xFFFFFFFFFFFFFF89ull, 0x0000133A00001339ull},
|
||||
{"lb +16 [quad]", "lb", 16, nullptr, 0, false, false, 0xFFFFFFFFFFFFFFBBull, 0x0000133A00001339ull},
|
||||
{"lb -16 [quad]", "lb", -16, nullptr, 0, false, false, 0x0000000000000023ull, 0x0000133A00001339ull},
|
||||
{"lb -> $0", "lb", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"lbu +0 [imm]", "lbu", 0, nullptr, 0, true, false, 0x0000000000000089ull, 0x0000133A00001339ull},
|
||||
{"lbu +16 [imm]", "lbu", 16, nullptr, 0, true, false, 0x00000000000000BBull, 0x0000133A00001339ull},
|
||||
{"lbu -16 [imm]", "lbu", -16, nullptr, 0, true, false, 0x0000000000000023ull, 0x0000133A00001339ull},
|
||||
{"lbu +0 [quad]", "lbu", 0, nullptr, 0, false, false, 0x0000000000000089ull, 0x0000133A00001339ull},
|
||||
{"lbu +16 [quad]", "lbu", 16, nullptr, 0, false, false, 0x00000000000000BBull, 0x0000133A00001339ull},
|
||||
{"lbu -16 [quad]", "lbu", -16, nullptr, 0, false, false, 0x0000000000000023ull, 0x0000133A00001339ull},
|
||||
{"lbu -> $0", "lbu", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"ld +0 [imm]", "ld", 0, nullptr, 0, true, false, 0xABCDEF0123456789ull, 0x0000133A00001339ull},
|
||||
{"ld +16 [imm]", "ld", 16, nullptr, 0, true, false, 0xCCDDEEFF8899AABBull, 0x0000133A00001339ull},
|
||||
{"ld -16 [imm]", "ld", -16, nullptr, 0, true, false, 0x9ABCDEF045678123ull, 0x0000133A00001339ull},
|
||||
{"ld +0 [quad]", "ld", 0, nullptr, 0, false, false, 0xABCDEF0123456789ull, 0x0000133A00001339ull},
|
||||
{"ld +16 [quad]", "ld", 16, nullptr, 0, false, false, 0xCCDDEEFF8899AABBull, 0x0000133A00001339ull},
|
||||
{"ld -16 [quad]", "ld", -16, nullptr, 0, false, false, 0x9ABCDEF045678123ull, 0x0000133A00001339ull},
|
||||
{"ld -> $0", "ld", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"ldl +0 [imm]", "ldl", 0, nullptr, 0, true, false, 0x89FFFFFFABCD4321ull, 0x0000133A00001339ull},
|
||||
{"ldr +0 [imm]", "ldr", 0, nullptr, 0, true, false, 0xABCDEF0123456789ull, 0x0000133A00001339ull},
|
||||
{"ldl +1 [imm]", "ldl", 1, nullptr, 0, true, false, 0x6789FFFFABCD4321ull, 0x0000133A00001339ull},
|
||||
{"ldr +1 [imm]", "ldr", 1, nullptr, 0, true, false, 0xFFABCDEF01234567ull, 0x0000133A00001339ull},
|
||||
{"ldl +0/ldr +7 [imm]", "ldl", 0, "ldr", 7, true, false, 0x89FFFFFFABCD43ABull, 0x0000133A00001339ull},
|
||||
{"ldl +0 [quad]", "ldl", 0, nullptr, 0, false, false, 0x8900133800001337ull, 0x0000133A00001339ull},
|
||||
{"ldr +0 [quad]", "ldr", 0, nullptr, 0, false, false, 0xABCDEF0123456789ull, 0x0000133A00001339ull},
|
||||
{"ldl +1 [quad]", "ldl", 1, nullptr, 0, false, false, 0x6789133800001337ull, 0x0000133A00001339ull},
|
||||
{"ldr +1 [quad]", "ldr", 1, nullptr, 0, false, false, 0x00ABCDEF01234567ull, 0x0000133A00001339ull},
|
||||
{"ldl +0/ldr +7 [quad]", "ldl", 0, "ldr", 7, false, false, 0x89001338000013ABull, 0x0000133A00001339ull},
|
||||
{"ldl -> $0", "ldl", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"ldr -> $0", "ldr", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"lh +0 [imm]", "lh", 0, nullptr, 0, true, false, 0x0000000000006789ull, 0x0000133A00001339ull},
|
||||
{"lh +16 [imm]", "lh", 16, nullptr, 0, true, false, 0xFFFFFFFFFFFFAABBull, 0x0000133A00001339ull},
|
||||
{"lh -16 [imm]", "lh", -16, nullptr, 0, true, false, 0xFFFFFFFFFFFF8123ull, 0x0000133A00001339ull},
|
||||
{"lh +0 [quad]", "lh", 0, nullptr, 0, false, false, 0x0000000000006789ull, 0x0000133A00001339ull},
|
||||
{"lh +16 [quad]", "lh", 16, nullptr, 0, false, false, 0xFFFFFFFFFFFFAABBull, 0x0000133A00001339ull},
|
||||
{"lh -16 [quad]", "lh", -16, nullptr, 0, false, false, 0xFFFFFFFFFFFF8123ull, 0x0000133A00001339ull},
|
||||
{"lh -> $0", "lh", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"lhu +0 [imm]", "lhu", 0, nullptr, 0, true, false, 0x0000000000006789ull, 0x0000133A00001339ull},
|
||||
{"lhu +16 [imm]", "lhu", 16, nullptr, 0, true, false, 0x000000000000AABBull, 0x0000133A00001339ull},
|
||||
{"lhu -16 [imm]", "lhu", -16, nullptr, 0, true, false, 0x0000000000008123ull, 0x0000133A00001339ull},
|
||||
{"lhu +0 [quad]", "lhu", 0, nullptr, 0, false, false, 0x0000000000006789ull, 0x0000133A00001339ull},
|
||||
{"lhu +16 [quad]", "lhu", 16, nullptr, 0, false, false, 0x000000000000AABBull, 0x0000133A00001339ull},
|
||||
{"lhu -16 [quad]", "lhu", -16, nullptr, 0, false, false, 0x0000000000008123ull, 0x0000133A00001339ull},
|
||||
{"lhu -> $0", "lhu", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"lw +0 [imm]", "lw", 0, nullptr, 0, true, false, 0x0000000023456789ull, 0x0000133A00001339ull},
|
||||
{"lw +16 [imm]", "lw", 16, nullptr, 0, true, false, 0xFFFFFFFF8899AABBull, 0x0000133A00001339ull},
|
||||
{"lw -16 [imm]", "lw", -16, nullptr, 0, true, false, 0x0000000045678123ull, 0x0000133A00001339ull},
|
||||
{"lw +0 [quad]", "lw", 0, nullptr, 0, false, false, 0x0000000023456789ull, 0x0000133A00001339ull},
|
||||
{"lw +16 [quad]", "lw", 16, nullptr, 0, false, false, 0xFFFFFFFF8899AABBull, 0x0000133A00001339ull},
|
||||
{"lw -16 [quad]", "lw", -16, nullptr, 0, false, false, 0x0000000045678123ull, 0x0000133A00001339ull},
|
||||
{"lw -> $0", "lw", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"lwl +0 [imm]", "lwl", 0, nullptr, 0, true, false, 0xFFFFFFFF89CD4321ull, 0x0000133A00001339ull},
|
||||
{"lwr +0 [imm]", "lwr", 0, nullptr, 0, true, false, 0x0000000023456789ull, 0x0000133A00001339ull},
|
||||
{"lwl +1 [imm]", "lwl", 1, nullptr, 0, true, false, 0x0000000067894321ull, 0x0000133A00001339ull},
|
||||
{"lwr +1 [imm]", "lwr", 1, nullptr, 0, true, false, 0xFFFFFFFFAB234567ull, 0x0000133A00001339ull},
|
||||
{"lwl +0/lwr +3 [imm]", "lwl", 0, "lwr", 3, true, false, 0xFFFFFFFF89CD4323ull, 0x0000133A00001339ull},
|
||||
{"lwl +0 [quad]", "lwl", 0, nullptr, 0, false, false, 0xFFFFFFFF89001337ull, 0x0000133A00001339ull},
|
||||
{"lwr +0 [quad]", "lwr", 0, nullptr, 0, false, false, 0x0000000023456789ull, 0x0000133A00001339ull},
|
||||
{"lwl +1 [quad]", "lwl", 1, nullptr, 0, false, false, 0x0000000067891337ull, 0x0000133A00001339ull},
|
||||
{"lwr +1 [quad]", "lwr", 1, nullptr, 0, false, false, 0x0000133800234567ull, 0x0000133A00001339ull},
|
||||
{"lwl +0/lwr +3 [quad]", "lwl", 0, "lwr", 3, false, false, 0xFFFFFFFF89001323ull, 0x0000133A00001339ull},
|
||||
{"lwl -> $0", "lwl", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"lwr -> $0", "lwr", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"lwu +0 [imm]", "lwu", 0, nullptr, 0, true, false, 0x0000000023456789ull, 0x0000133A00001339ull},
|
||||
{"lwu +16 [imm]", "lwu", 16, nullptr, 0, true, false, 0x000000008899AABBull, 0x0000133A00001339ull},
|
||||
{"lwu -16 [imm]", "lwu", -16, nullptr, 0, true, false, 0x0000000045678123ull, 0x0000133A00001339ull},
|
||||
{"lwu +0 [quad]", "lwu", 0, nullptr, 0, false, false, 0x0000000023456789ull, 0x0000133A00001339ull},
|
||||
{"lwu +16 [quad]", "lwu", 16, nullptr, 0, false, false, 0x000000008899AABBull, 0x0000133A00001339ull},
|
||||
{"lwu -16 [quad]", "lwu", -16, nullptr, 0, false, false, 0x0000000045678123ull, 0x0000133A00001339ull},
|
||||
{"lwu -> $0", "lwu", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
{"lq +0 [imm]", "lq", 0, nullptr, 0, true, false, 0xABCDEF0123456789ull, 0xC0DEC0DEBEEFDEADull},
|
||||
{"lq +16 [imm]", "lq", 16, nullptr, 0, true, false, 0xCCDDEEFF8899AABBull, 0x4455667700112233ull},
|
||||
{"lq -16 [imm]", "lq", -16, nullptr, 0, true, false, 0x9ABCDEF045678123ull, 0xC0DE1337DEADBEEFull},
|
||||
{"lq +11 [imm]", "lq", 11, nullptr, 0, true, false, 0xABCDEF0123456789ull, 0xC0DEC0DEBEEFDEADull},
|
||||
{"lq +0 [quad]", "lq", 0, nullptr, 0, false, false, 0xABCDEF0123456789ull, 0xC0DEC0DEBEEFDEADull},
|
||||
{"lq +16 [quad]", "lq", 16, nullptr, 0, false, false, 0xCCDDEEFF8899AABBull, 0x4455667700112233ull},
|
||||
{"lq -16 [quad]", "lq", -16, nullptr, 0, false, false, 0x9ABCDEF045678123ull, 0xC0DE1337DEADBEEFull},
|
||||
{"lq +11 [quad]", "lq", 11, nullptr, 0, false, false, 0xABCDEF0123456789ull, 0xC0DEC0DEBEEFDEADull},
|
||||
{"lq -> $0", "lq", 0, nullptr, 0, false, true, 0x0000000000000000ull, 0x0000133A00001339ull},
|
||||
};
|
||||
|
||||
// `block` is the byte delta from the base register to the
|
||||
// 16-byte block the capture printed.
|
||||
struct StoreCase {
|
||||
const char* label; const char* op1; int off1;
|
||||
const char* op2; int off2;
|
||||
bool imm_preset; int block;
|
||||
u32 mem[4]; // word0..word3 of that block
|
||||
};
|
||||
|
||||
inline constexpr StoreCase kStoreCases[] = {
|
||||
{"sb +0 [imm]", "sb", 0, nullptr, 0, true, 0, {0x23456721u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sb +16 [imm]", "sb", 16, nullptr, 0, true, 16, {0x8899AA21u, 0xCCDDEEFFu, 0x00112233u, 0x44556677u}},
|
||||
{"sb -16 [imm]", "sb", -16, nullptr, 0, true, -16, {0x45678121u, 0x9ABCDEF0u, 0xDEADBEEFu, 0xC0DE1337u}},
|
||||
{"sb +0 [quad]", "sb", 0, nullptr, 0, false, 0, {0x23456737u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sb +16 [quad]", "sb", 16, nullptr, 0, false, 16, {0x8899AA37u, 0xCCDDEEFFu, 0x00112233u, 0x44556677u}},
|
||||
{"sb -16 [quad]", "sb", -16, nullptr, 0, false, -16, {0x45678137u, 0x9ABCDEF0u, 0xDEADBEEFu, 0xC0DE1337u}},
|
||||
{"sd +0 [imm]", "sd", 0, nullptr, 0, true, 0, {0xABCD4321u, 0xFFFFFFFFu, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sd +16 [imm]", "sd", 16, nullptr, 0, true, 16, {0xABCD4321u, 0xFFFFFFFFu, 0x00112233u, 0x44556677u}},
|
||||
{"sd -16 [imm]", "sd", -16, nullptr, 0, true, -16, {0xABCD4321u, 0xFFFFFFFFu, 0xDEADBEEFu, 0xC0DE1337u}},
|
||||
{"sd +0 [quad]", "sd", 0, nullptr, 0, false, 0, {0x00001337u, 0x00001338u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sd +16 [quad]", "sd", 16, nullptr, 0, false, 16, {0x00001337u, 0x00001338u, 0x00112233u, 0x44556677u}},
|
||||
{"sd -16 [quad]", "sd", -16, nullptr, 0, false, -16, {0x00001337u, 0x00001338u, 0xDEADBEEFu, 0xC0DE1337u}},
|
||||
{"sdl +0 [imm]", "sdl", 0, nullptr, 0, true, 0, {0x234567FFu, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sdr +0 [imm]", "sdr", 0, nullptr, 0, true, 0, {0xABCD4321u, 0xFFFFFFFFu, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sdl +1 [imm]", "sdl", 1, nullptr, 0, true, 0, {0x2345FFFFu, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sdr +1 [imm]", "sdr", 1, nullptr, 0, true, 0, {0xCD432189u, 0xFFFFFFABu, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sdl +0/sdr +7 [imm]", "sdl", 0, "sdr", 7, true, 0, {0x234567FFu, 0x21CDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sdl +0 [quad]", "sdl", 0, nullptr, 0, false, 0, {0x23456700u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sdr +0 [quad]", "sdr", 0, nullptr, 0, false, 0, {0x00001337u, 0x00001338u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sdl +1 [quad]", "sdl", 1, nullptr, 0, false, 0, {0x23450000u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sdr +1 [quad]", "sdr", 1, nullptr, 0, false, 0, {0x00133789u, 0x00133800u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sdl +0/sdr +7 [quad]", "sdl", 0, "sdr", 7, false, 0, {0x23456700u, 0x37CDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sh +0 [imm]", "sh", 0, nullptr, 0, true, 0, {0x23454321u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sh +16 [imm]", "sh", 16, nullptr, 0, true, 16, {0x88994321u, 0xCCDDEEFFu, 0x00112233u, 0x44556677u}},
|
||||
{"sh -16 [imm]", "sh", -16, nullptr, 0, true, -16, {0x45674321u, 0x9ABCDEF0u, 0xDEADBEEFu, 0xC0DE1337u}},
|
||||
{"sh +0 [quad]", "sh", 0, nullptr, 0, false, 0, {0x23451337u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sh +16 [quad]", "sh", 16, nullptr, 0, false, 16, {0x88991337u, 0xCCDDEEFFu, 0x00112233u, 0x44556677u}},
|
||||
{"sh -16 [quad]", "sh", -16, nullptr, 0, false, -16, {0x45671337u, 0x9ABCDEF0u, 0xDEADBEEFu, 0xC0DE1337u}},
|
||||
{"sw +0 [imm]", "sw", 0, nullptr, 0, true, 0, {0xABCD4321u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sw +16 [imm]", "sw", 16, nullptr, 0, true, 16, {0xABCD4321u, 0xCCDDEEFFu, 0x00112233u, 0x44556677u}},
|
||||
{"sw -16 [imm]", "sw", -16, nullptr, 0, true, -16, {0xABCD4321u, 0x9ABCDEF0u, 0xDEADBEEFu, 0xC0DE1337u}},
|
||||
{"sw +0 [quad]", "sw", 0, nullptr, 0, false, 0, {0x00001337u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sw +16 [quad]", "sw", 16, nullptr, 0, false, 16, {0x00001337u, 0xCCDDEEFFu, 0x00112233u, 0x44556677u}},
|
||||
{"sw -16 [quad]", "sw", -16, nullptr, 0, false, -16, {0x00001337u, 0x9ABCDEF0u, 0xDEADBEEFu, 0xC0DE1337u}},
|
||||
{"swl +0 [imm]", "swl", 0, nullptr, 0, true, 0, {0x234567ABu, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"swr +0 [imm]", "swr", 0, nullptr, 0, true, 0, {0xABCD4321u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"swl +1 [imm]", "swl", 1, nullptr, 0, true, 0, {0x2345ABCDu, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"swr +1 [imm]", "swr", 1, nullptr, 0, true, 0, {0xCD432189u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"swl +0/swr +3 [imm]", "swl", 0, "swr", 3, true, 0, {0x214567ABu, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"swl +0 [quad]", "swl", 0, nullptr, 0, false, 0, {0x23456700u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"swr +0 [quad]", "swr", 0, nullptr, 0, false, 0, {0x00001337u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"swl +1 [quad]", "swl", 1, nullptr, 0, false, 0, {0x23450000u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"swr +1 [quad]", "swr", 1, nullptr, 0, false, 0, {0x00133789u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"swl +0/swr +3 [quad]", "swl", 0, "swr", 3, false, 0, {0x37456700u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu}},
|
||||
{"sq +0 [imm]", "sq", 0, nullptr, 0, true, 0, {0xABCD4321u, 0xFFFFFFFFu, 0x00001339u, 0x0000133Au}},
|
||||
{"sq +16 [imm]", "sq", 16, nullptr, 0, true, 16, {0xABCD4321u, 0xFFFFFFFFu, 0x00001339u, 0x0000133Au}},
|
||||
{"sq -16 [imm]", "sq", -16, nullptr, 0, true, -16, {0xABCD4321u, 0xFFFFFFFFu, 0x00001339u, 0x0000133Au}},
|
||||
{"sq +11 [imm]", "sq", 11, nullptr, 0, true, 0, {0xABCD4321u, 0xFFFFFFFFu, 0x00001339u, 0x0000133Au}},
|
||||
{"sq +0 [quad]", "sq", 0, nullptr, 0, false, 0, {0x00001337u, 0x00001338u, 0x00001339u, 0x0000133Au}},
|
||||
{"sq +16 [quad]", "sq", 16, nullptr, 0, false, 16, {0x00001337u, 0x00001338u, 0x00001339u, 0x0000133Au}},
|
||||
{"sq -16 [quad]", "sq", -16, nullptr, 0, false, -16, {0x00001337u, 0x00001338u, 0x00001339u, 0x0000133Au}},
|
||||
{"sq +11 [quad]", "sq", 11, nullptr, 0, false, 0, {0x00001337u, 0x00001338u, 0x00001339u, 0x0000133Au}},
|
||||
};
|
||||
|
||||
inline constexpr int kLoadCaseCount = 82;
|
||||
inline constexpr int kStoreCaseCount = 52;
|
||||
|
||||
} // namespace ps2auto_eelsu
|
||||
@@ -0,0 +1,313 @@
|
||||
// SPDX-FileCopyrightText: 2026 yaps2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
// EE loads and stores against real PS2 hardware.
|
||||
//
|
||||
// autocases_eelsu.h is generated from unknownbrackets/ps2autotests
|
||||
// tests/cpu/ee/lsu.expected: every EE load and store — 21 opcodes — against a
|
||||
// fixed byte pattern at three offsets, plus a deliberately misaligned +11 for
|
||||
// the quad ops. 134 cases.
|
||||
//
|
||||
// ee_rec_loadstore_tests.cpp already covers this ground as a JIT-vs-interp
|
||||
// differential, and that is its limitation: the two engines share an idea of
|
||||
// what LWL at offset 1 means, and a differential cannot see a shared mistake.
|
||||
// The eight unaligned ops (LDL/LDR/LWL/LWR, SDL/SDR/SWL/SWR) are the classic
|
||||
// place for one — their byte/shift tables are fiddly enough that the same
|
||||
// off-by-one gets written twice.
|
||||
//
|
||||
// Two things this capture pins that a 64-bit test cannot:
|
||||
//
|
||||
// * It prints the FULL 128-bit destination on every load, so "loads leave
|
||||
// bits 127:64 alone" is asserted on all twelve of them — and LQ is the
|
||||
// one that must not.
|
||||
// * The `-> $0` cases load into $0 and read $0 back through `ori rt, $0, 0`,
|
||||
// so a load that wrongly wrote the zero register shows up immediately.
|
||||
//
|
||||
// The register presets are executed rather than injected: where the capture
|
||||
// builds rt with `lui/ori`, so does this, which re-pins that neither touches
|
||||
// bits 127:64.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "harness/EeRecTestHarness.h"
|
||||
#include "harness/MipsEncode.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "autocases_eelsu.h"
|
||||
|
||||
using namespace ps2auto_eelsu;
|
||||
|
||||
namespace recompiler_tests
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using namespace mips;
|
||||
using namespace mips::ee; // LD/LDL/LDR/SD/SDL/SDR/LWU/LQ/SQ live here
|
||||
|
||||
constexpr u32 kScratch = RecompilerTestEnvironment::kScratchAddr;
|
||||
constexpr u32 kBase = 8; // $t0 — points at C_PATTERN[1], i.e. pattern + 16
|
||||
constexpr u32 kRt = 9; // $t1 — the loaded/stored register
|
||||
|
||||
// C_GARBAGE1 as a 128-bit register value. Both presets start from this; the
|
||||
// [imm] cases then overwrite the low 64 with lui/ori, which is why every
|
||||
// expected `hi` in the table is this same word.
|
||||
constexpr u64 kGarbageLo =
|
||||
(static_cast<u64>(kGarbage1[1]) << 32) | kGarbage1[0];
|
||||
constexpr u64 kGarbageHi =
|
||||
(static_cast<u64>(kGarbage1[3]) << 32) | kGarbage1[2];
|
||||
|
||||
u32 EncodeLoad(const std::string& op, u32 rt, s16 off, u32 base)
|
||||
{
|
||||
if (op == "lb") return LB(rt, off, base);
|
||||
if (op == "lbu") return LBU(rt, off, base);
|
||||
if (op == "lh") return LH(rt, off, base);
|
||||
if (op == "lhu") return LHU(rt, off, base);
|
||||
if (op == "lw") return LW(rt, off, base);
|
||||
if (op == "lwu") return LWU(rt, off, base);
|
||||
if (op == "lwl") return LWL(rt, off, base);
|
||||
if (op == "lwr") return LWR(rt, off, base);
|
||||
if (op == "ld") return LD(rt, off, base);
|
||||
if (op == "ldl") return LDL(rt, off, base);
|
||||
if (op == "ldr") return LDR(rt, off, base);
|
||||
if (op == "lq") return LQ(rt, off, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 EncodeStore(const std::string& op, u32 rt, s16 off, u32 base)
|
||||
{
|
||||
if (op == "sb") return SB(rt, off, base);
|
||||
if (op == "sh") return SH(rt, off, base);
|
||||
if (op == "sw") return SW(rt, off, base);
|
||||
if (op == "swl") return SWL(rt, off, base);
|
||||
if (op == "swr") return SWR(rt, off, base);
|
||||
if (op == "sd") return SD(rt, off, base);
|
||||
if (op == "sdl") return SDL(rt, off, base);
|
||||
if (op == "sdr") return SDR(rt, off, base);
|
||||
if (op == "sq") return SQ(rt, off, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The capture's `SET_U32<0xABCD4321>`: lui then ori, executed, so the low 64
|
||||
// bits get there by sign extension and 127:64 keep C_GARBAGE1's half.
|
||||
void AppendImmPreset(std::vector<u32>& prog)
|
||||
{
|
||||
prog.push_back(LUI(kRt, static_cast<u16>(kImmPreset >> 16)));
|
||||
prog.push_back(ORI(kRt, kRt, static_cast<u16>(kImmPreset & 0xFFFF)));
|
||||
}
|
||||
|
||||
void Prepare(EeRecTestHarness& h)
|
||||
{
|
||||
h.WriteBytes(kScratch, kPattern, sizeof(kPattern));
|
||||
h.SetGpr64(kBase, kScratch + 16);
|
||||
h.SetGpr128(kRt, kGarbageLo, kGarbageHi);
|
||||
}
|
||||
|
||||
// Recorded from a real run, per engine — never derived from a rule.
|
||||
struct Divergence { const char* label; bool bad_interp, bad_jit; };
|
||||
constexpr Divergence kLoadDivergences[] = {
|
||||
// The interpreter's LD (R5900OpcodeImpl.cpp) writes
|
||||
// cpuRegs.GPR.r[_Rt_].UD[0] with no `!_Rt_` guard, so the following
|
||||
// `ori rt, $0, 0` reads back the loaded quad instead of zero. All eleven
|
||||
// of its neighbours guard: LB/LBU/LH/LHU/LW/LWU/LWL/LWR/LDL/LDR early-out
|
||||
// on !_Rt_ and LQ routes through gpr_GetWritePtr, whose reason for
|
||||
// existing is the dummy zero slot. The recompiler gets it right.
|
||||
{"ld -> $0", true, false},
|
||||
};
|
||||
|
||||
bool IsKnownBad(const char* label, bool jit)
|
||||
{
|
||||
for (const Divergence& d : kLoadDivergences)
|
||||
{
|
||||
if (std::string(label) == d.label)
|
||||
return jit ? d.bad_jit : d.bad_interp;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Builds and runs one load case on one engine. Returns false if an opcode has
|
||||
// no encoder, so the caller can fail with a useful name.
|
||||
bool RunLoadCase(const LoadCase& c, bool jit, u64& lo, u64& hi)
|
||||
{
|
||||
const u32 w1 = EncodeLoad(c.op1, c.into_zero ? 0u : kRt,
|
||||
static_cast<s16>(c.off1), kBase);
|
||||
if (w1 == 0u)
|
||||
return false;
|
||||
|
||||
std::vector<u32> prog;
|
||||
if (c.imm_preset)
|
||||
AppendImmPreset(prog);
|
||||
prog.push_back(w1);
|
||||
if (c.op2)
|
||||
{
|
||||
const u32 w2 = EncodeLoad(c.op2, kRt, static_cast<s16>(c.off2), kBase);
|
||||
if (w2 == 0u)
|
||||
return false;
|
||||
prog.push_back(w2);
|
||||
}
|
||||
if (c.into_zero)
|
||||
prog.push_back(ORI(kRt, 0, 0)); // read $0 back, as the capture does
|
||||
|
||||
EeRecTestHarness h;
|
||||
Prepare(h);
|
||||
h.LoadProgram(prog);
|
||||
if (jit)
|
||||
h.RunJitNoDiff();
|
||||
else
|
||||
h.RunInterpOnly();
|
||||
|
||||
lo = jit ? h.GetGpr64Jit(kRt) : h.GetGpr64Interp(kRt);
|
||||
hi = jit ? h.GetGprUpper64Jit(kRt) : h.GetGprUpper64Interp(kRt);
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// The engines legitimately might disagree here, so each is scored against the
|
||||
// console independently rather than against the other.
|
||||
TEST(EeLsuConsoleConformance, LoadsMatchConsole)
|
||||
{
|
||||
int diverged = 0;
|
||||
for (int i = 0; i < kLoadCaseCount; ++i)
|
||||
{
|
||||
const LoadCase& c = kLoadCases[i];
|
||||
SCOPED_TRACE(::testing::Message() << c.label);
|
||||
|
||||
for (int jit = 0; jit < 2; ++jit)
|
||||
{
|
||||
SCOPED_TRACE(jit ? "[jit]" : "[interp]");
|
||||
u64 lo = 0, hi = 0;
|
||||
ASSERT_TRUE(RunLoadCase(c, jit != 0, lo, hi));
|
||||
|
||||
if (!IsKnownBad(c.label, jit != 0))
|
||||
{
|
||||
EXPECT_EQ(lo, c.lo) << "bits 63:0";
|
||||
EXPECT_EQ(hi, c.hi) << "bits 127:64";
|
||||
}
|
||||
else
|
||||
{
|
||||
++diverged;
|
||||
EXPECT_FALSE(lo == c.lo && hi == c.hi)
|
||||
<< c.label << (jit ? " [jit]" : " [interp]")
|
||||
<< " now MATCHES silicon. If LD was given the `!_Rt_` "
|
||||
"guard its eleven neighbours have, drop it from "
|
||||
"kLoadDivergences and delete the DISABLED tripwire.";
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(diverged, 1) << "exactly one recorded divergence";
|
||||
}
|
||||
|
||||
// What passing looks like once LD stops writing the zero register.
|
||||
TEST(EeLsuConsoleConformance, DISABLED_AllLoadsMatchConsole)
|
||||
{
|
||||
for (int i = 0; i < kLoadCaseCount; ++i)
|
||||
{
|
||||
const LoadCase& c = kLoadCases[i];
|
||||
for (int jit = 0; jit < 2; ++jit)
|
||||
{
|
||||
SCOPED_TRACE(::testing::Message()
|
||||
<< c.label << (jit ? " [jit]" : " [interp]"));
|
||||
u64 lo = 0, hi = 0;
|
||||
ASSERT_TRUE(RunLoadCase(c, jit != 0, lo, hi));
|
||||
EXPECT_EQ(lo, c.lo) << "bits 63:0";
|
||||
EXPECT_EQ(hi, c.hi) << "bits 127:64";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EeLsuConsoleConformance, StoresMatchConsole)
|
||||
{
|
||||
for (int i = 0; i < kStoreCaseCount; ++i)
|
||||
{
|
||||
const StoreCase& c = kStoreCases[i];
|
||||
SCOPED_TRACE(::testing::Message() << c.label);
|
||||
|
||||
const u32 w1 = EncodeStore(c.op1, kRt, static_cast<s16>(c.off1),
|
||||
kBase);
|
||||
ASSERT_NE(w1, 0u) << "no encoder for " << c.op1;
|
||||
|
||||
std::vector<u32> prog;
|
||||
if (c.imm_preset)
|
||||
AppendImmPreset(prog);
|
||||
prog.push_back(w1);
|
||||
if (c.op2)
|
||||
{
|
||||
const u32 w2 = EncodeStore(c.op2, kRt, static_cast<s16>(c.off2),
|
||||
kBase);
|
||||
ASSERT_NE(w2, 0u) << "no encoder for " << c.op2;
|
||||
prog.push_back(w2);
|
||||
}
|
||||
|
||||
for (int jit = 0; jit < 2; ++jit)
|
||||
{
|
||||
SCOPED_TRACE(jit ? "[jit]" : "[interp]");
|
||||
EeRecTestHarness h;
|
||||
Prepare(h);
|
||||
h.TrackMemWindow(kScratch, sizeof(kPattern));
|
||||
h.LoadProgram(prog);
|
||||
if (jit)
|
||||
h.RunJitNoDiff();
|
||||
else
|
||||
h.RunInterpOnly();
|
||||
|
||||
const u32 blk = kScratch + 16 + static_cast<u32>(c.block);
|
||||
for (int w = 0; w < 4; ++w)
|
||||
{
|
||||
EXPECT_EQ(h.ReadU32(blk + 4u * static_cast<u32>(w)), c.mem[w])
|
||||
<< "word " << w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Two invariants the capture states outright, pulled out so a failure names
|
||||
// the rule rather than a row of the table.
|
||||
|
||||
// LQ/SQ mask the address down to 16 bytes rather than faulting or reading
|
||||
// unaligned: the console's `lq +11` row equals its own `lq +0` row.
|
||||
TEST(EeLsuConsoleConformance, QuadOpsMaskTheAddressTo16Bytes)
|
||||
{
|
||||
int checked = 0;
|
||||
for (int i = 0; i < kLoadCaseCount; ++i)
|
||||
{
|
||||
const LoadCase& c = kLoadCases[i];
|
||||
if (std::string(c.op1) != "lq" || c.off1 != 11)
|
||||
continue;
|
||||
for (int j = 0; j < kLoadCaseCount; ++j)
|
||||
{
|
||||
const LoadCase& z = kLoadCases[j];
|
||||
if (std::string(z.op1) != "lq" || z.off1 != 0 || z.into_zero ||
|
||||
z.imm_preset != c.imm_preset)
|
||||
continue;
|
||||
EXPECT_EQ(c.lo, z.lo) << c.label << " vs " << z.label;
|
||||
EXPECT_EQ(c.hi, z.hi) << c.label << " vs " << z.label;
|
||||
++checked;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(checked, 2) << "expected the +11 row of both presets";
|
||||
}
|
||||
|
||||
TEST(EeLsuConsoleConformance, LoadsPreserveTheUpper64BitsExceptLq)
|
||||
{
|
||||
int preserved = 0, overwritten = 0;
|
||||
for (int i = 0; i < kLoadCaseCount; ++i)
|
||||
{
|
||||
const LoadCase& c = kLoadCases[i];
|
||||
// LQ writes all 128 bits, so only its `-> $0` row (which never lands)
|
||||
// keeps the preset half.
|
||||
if (std::string(c.op1) == "lq" && !c.into_zero)
|
||||
{
|
||||
EXPECT_NE(c.hi, kGarbageHi) << c.label;
|
||||
++overwritten;
|
||||
continue;
|
||||
}
|
||||
EXPECT_EQ(c.hi, kGarbageHi) << c.label;
|
||||
++preserved;
|
||||
}
|
||||
EXPECT_EQ(overwritten, 8);
|
||||
EXPECT_EQ(preserved, kLoadCaseCount - 8);
|
||||
}
|
||||
|
||||
} // namespace recompiler_tests
|
||||
Reference in New Issue
Block a user