Files
ARMSX2/tests/ctest/core/recompilers/autocases_vusat.h
T
pstef 83f2510134 Tests: pin the VU FMAC's range against the console
The VU's largest value is 0x7FFFFFFF, one binade above FLT_MAX, the same
range the EE FPU has. So an exponent-255 word is an ordinary number on
the way in and on the way out, and "overflow" starts above it rather
than above FLT_MAX.

Both engines put the boundary a binade lower. vuDouble() rewrites an
exponent-255 operand as 0x7F7FFFFF and VU_MAC_UPDATE() calls every
exponent-255 result an overflow; the arm64 COP2 macro emitters clamp the
result to +/-FLT_MAX and raise neither O nor U. microVU's per-op operand
clamps approximate the same thing from a list of games rather than a
rule.

68 rows off an SCPH-90000 through VU0 macro mode, scored per engine and
per column, with what each engine cannot yet reproduce recorded per case
so a fix trips the test as loudly as a regression. Nothing is fixed
here.

Two of the rows are structural rather than about range. An overflowed
product does not become 0x7FFFFFFF before the accumulate: an addend of
-0x7FFFFFFF cannot cancel it. An underflowed product does become zero
before it. And the multiplier is the EE's, with the same one-ULP deficit
decided by ft's mantissa alone.

The harness grows two things the rows need: a VADDA encoder, and an
opt-out from Run()'s VU0 JIT-vs-interp auto-diff for tests that score
each engine against a hardware capture instead of against the other
engine.
2026-08-18 08:00:56 -07:00

112 lines
10 KiB
C++

// GENERATED by captures/vusat/gen_autocases.py from a capture taken on an
// SCPH-90000 (hw-run1.out, byte-identical to hw-run2.out). Do not edit by hand.
//
// VU0 macro-mode FMAC at the top and the bottom of its range. Each row seeds
// fs/ft/acc into all four lanes of vf1/vf2/vf3, runs
//
// vadda.xyzw $ACC, $vf3, $vf7 ; vf7 is zero, so ACC = acc
// <op>.xyzw $vf4, $vf1, $vf2
//
// and records vf4 (identical in all four lanes), the MAC flag and the status
// flag. The status flag is cleared before the pair, so it carries both ops.
//
// bad_interp / bad_jit are not from the console: they are what this tree
// currently fails to reproduce, per column, regenerated from the test binary's
// own dump. Clearing a bit is how a fix lands.
#pragma once
#include <common/Pcsx2Types.h>
namespace console_vusat {
enum VuSatOp { VS_ADD, VS_SUB, VS_MUL, VS_MADD, VS_MSUB, VS_MAX, VS_MINI };
// Columns a case can be wrong in, independently.
enum { VSB_VALUE = 1, VSB_MAC = 2, VSB_STAT = 4 };
struct VuSatCase {
u8 op;
u32 fs, ft, acc; // seeded into all four lanes of vf1 / vf2 / vf3
u32 out; // vf4 on the console, all four lanes
u16 mac, stat; // VI17 and VI16 on the console
u8 bad_interp, bad_jit;
const char* what;
};
inline constexpr VuSatCase kVuSatCases[] = {
{VS_ADD, 0x3F800000u, 0x3F800000u, 0x00000000u, 0x40000000u, 0x0000u, 0x0040u, 0, 0, "CONTROL 1.0+1.0, plumbing"},
{VS_ADD, 0x3F800000u, 0xBF800000u, 0x00000000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 0, "CONTROL liveness 1.0-1.0: value and MAC flag must both differ from row 0"},
{VS_MUL, 0x3F000000u, 0x7F800000u, 0x00000000u, 0x7F000000u, 0x0000u, 0x0040u, 1, 1, "A 0.5*2^128 full 7F000000 | operand clamped 7EFFFFFF"},
{VS_MUL, 0x3F000000u, 0x7FFFFFFFu, 0x00000000u, 0x7F7FFFFEu, 0x0000u, 0x0040u, 1, 1, "A 0.5*max full 7F7FFFFF | operand clamped 7EFFFFFF"},
{VS_ADD, 0x7FFFFFFFu, 0xFF800000u, 0x00000000u, 0x7F7FFFFEu, 0x0000u, 0x0040u, 7, 1, "A max + -2^128 full 7F7FFFFE | operand clamped 00000000"},
{VS_SUB, 0x7FFFFFFFu, 0x7F800000u, 0x00000000u, 0x7F7FFFFEu, 0x0000u, 0x0040u, 7, 1, "A max - 2^128 full 7F7FFFFE | operand clamped 00000000"},
{VS_ADD, 0x7F800000u, 0xFF800000u, 0x00000000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 7, "A 2^128 + -2^128 zero under either reading"},
{VS_MUL, 0x7F7FFFFFu, 0x40000000u, 0x00000000u, 0x7FFFFFFFu, 0x0000u, 0x0040u, 1, 1, "B FLT_MAX*2 exactly 7FFFFFFF if the range runs that far"},
{VS_MUL, 0x7F7FFFFFu, 0xC0000000u, 0x00000000u, 0xFFFFFFFFu, 0x00F0u, 0x00C2u, 1, 1, "B FLT_MAX*-2 sign mirror of the row above"},
{VS_ADD, 0x7FFFFFFFu, 0x74000000u, 0x00000000u, 0x7FFFFFFFu, 0xF000u, 0x0248u, 7, 7, "B max + 1ulp first value past the top: saturate or wrap"},
{VS_MUL, 0x7F800000u, 0x40000000u, 0x00000000u, 0x7FFFFFFFu, 0xF000u, 0x0248u, 7, 7, "B 2^128*2 one binade past the top"},
{VS_MUL, 0x7F800000u, 0xC0000000u, 0x00000000u, 0xFFFFFFFFu, 0xF0F0u, 0x02CAu, 7, 7, "B 2^128*-2 sign mirror"},
{VS_ADD, 0x7FFFFFFFu, 0x7FFFFFFFu, 0x00000000u, 0x7FFFFFFFu, 0xF000u, 0x0248u, 7, 7, "B max+max"},
{VS_MUL, 0x7F800000u, 0x7F800000u, 0x00000000u, 0x7FFFFFFFu, 0xF000u, 0x0248u, 7, 7, "B 2^128*2^128 far past the top"},
{VS_SUB, 0xFFFFFFFFu, 0x7FFFFFFFu, 0x00000000u, 0xFFFFFFFFu, 0xF0F0u, 0x02CAu, 7, 7, "B -max-max"},
{VS_MUL, 0x7F000000u, 0x3F800001u, 0x00000000u, 0x7F000001u, 0x0000u, 0x0040u, 0, 0, "B 2^127*(1+2^-23) exp FE, in range under every reading"},
{VS_MUL, 0x7F800000u, 0x3F800001u, 0x00000000u, 0x7F800001u, 0x0000u, 0x0040u, 1, 1, "B 2^128*(1+2^-23) exp FF and representable: does exp FF alone raise O?"},
{VS_ADD, 0x7F800000u, 0x74000000u, 0x00000000u, 0x7F800001u, 0x0000u, 0x0040u, 1, 1, "B 2^128 + 1ulp exp FF and representable, via the adder"},
{VS_MUL, 0x00800000u, 0x3F000000u, 0x00000000u, 0x00000000u, 0x0F0Fu, 0x0145u, 6, 6, "C 2^-126*0.5 first value below the bottom"},
{VS_MUL, 0x80800000u, 0x3F000000u, 0x00000000u, 0x80000000u, 0x0FFFu, 0x01C7u, 6, 6, "C -2^-126*0.5 sign of a flushed result"},
{VS_MUL, 0x00800000u, 0x00800000u, 0x00000000u, 0x00000000u, 0x0F0Fu, 0x0145u, 6, 6, "C 2^-126*2^-126 far below the bottom"},
{VS_ADD, 0x00800000u, 0x80800001u, 0x00000000u, 0x80000000u, 0x0FFFu, 0x01C7u, 6, 6, "C 2^-126 + -(2^-126+1ulp) tiny negative from the adder"},
{VS_MUL, 0x00400000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 0, "C denormal operand *2 flushed 00000000 | kept 00800000"},
{VS_ADD, 0x00400000u, 0x00400000u, 0x00000000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 0, "C denormal operand + itself flushed 00000000 | kept 00800000"},
{VS_MUL, 0x00000000u, 0x7F800000u, 0x00000000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 7, "D 0*2^128"},
{VS_MUL, 0x80000000u, 0x7F800000u, 0x00000000u, 0x80000000u, 0x00FFu, 0x00C3u, 0, 7, "D -0*2^128"},
{VS_MUL, 0x00000000u, 0x7FFFFFFFu, 0x00000000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 7, "D 0*max"},
{VS_MADD, 0x00000000u, 0x7F800000u, 0x3F800000u, 0x3F800000u, 0x0000u, 0x0040u, 4, 5, "D 1.0 + 0*2^128"},
{VS_MADD, 0x7F800000u, 0x40800000u, 0xFFFFFFFFu, 0x7FFFFFFFu, 0xF000u, 0x0288u, 7, 7, "E -max + 2^130 saturated product 00000000 | wide product 7FFFFFFF"},
{VS_MSUB, 0x7F800000u, 0x40800000u, 0x7FFFFFFFu, 0xFFFFFFFFu, 0xF0F0u, 0x028Au, 7, 7, "E max - 2^130 saturated product 00000000 | wide product FFFFFFFF"},
{VS_MADD, 0x7F800000u, 0xC0800000u, 0x7FFFFFFFu, 0xFFFFFFFFu, 0xF0F0u, 0x028Au, 7, 7, "E max + -2^130 saturated product 00000000 | wide product FFFFFFFF"},
{VS_MADD, 0x7F800000u, 0x40800000u, 0x00000000u, 0x7FFFFFFFu, 0xF000u, 0x0248u, 7, 7, "E 0 + 2^130 both readings overflow"},
{VS_MADD, 0x00800000u, 0x3F000000u, 0x00800000u, 0x00800000u, 0x0000u, 0x0140u, 4, 4, "E 2^-126 + 2^-127 flushed product 00800000 | wide product 00C00000"},
{VS_MSUB, 0x00800000u, 0x3F000000u, 0x00800000u, 0x00800000u, 0x0000u, 0x0140u, 4, 4, "E 2^-126 - 2^-127 flushed product 00800000 | wide product 00400000->0"},
{VS_MAX, 0x7F800000u, 0x7FFFFFFFu, 0x00000000u, 0x7FFFFFFFu, 0x000Fu, 0x0041u, 0, 0, "F max(2^128, max)"},
{VS_MAX, 0xFF800000u, 0x7F800000u, 0x00000000u, 0x7F800000u, 0x000Fu, 0x0041u, 0, 0, "F max(-2^128, 2^128)"},
{VS_MINI, 0xFF800000u, 0xFFFFFFFFu, 0x00000000u, 0xFFFFFFFFu, 0x000Fu, 0x0041u, 0, 0, "F mini(-2^128, -max)"},
{VS_MAX, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 0, "F max(+0, -0) integer compare 00000000"},
{VS_MINI, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x000Fu, 0x0041u, 0, 0, "F mini(+0, -0) integer compare 80000000"},
{VS_MAX, 0x00400000u, 0x00000000u, 0x00000000u, 0x00400000u, 0x000Fu, 0x0041u, 0, 0, "F max(denormal, 0) operand flushed 00000000 | kept 00400000"},
{VS_MINI, 0x00400000u, 0x00800000u, 0x00000000u, 0x00400000u, 0x000Fu, 0x0041u, 0, 0, "F mini(denormal, 2^-126)"},
{VS_ADD, 0x7FFFFFFFu, 0x3F800000u, 0x00000000u, 0x7FFFFFFFu, 0x0000u, 0x0040u, 1, 1, "G max + 1.0 far below one ULP: chop"},
{VS_SUB, 0x7F800000u, 0x74000000u, 0x00000000u, 0x7F7FFFFEu, 0x0000u, 0x0040u, 1, 1, "G 2^128 - 1ulp"},
{VS_SUB, 0x3F800000u, 0x3F800000u, 0x00000000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 0, "G 1.0-1.0 sign of an exact zero"},
{VS_ADD, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 0, "G -0 + +0"},
{VS_ADD, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00FFu, 0x00C3u, 0, 0, "G -0 + -0"},
{VS_MUL, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00FFu, 0x00C3u, 0, 0, "G -0 * +0"},
{VS_MAX, 0x00000000u, 0x80000000u, 0x3F800000u, 0x00000000u, 0x0000u, 0x0000u, 0, 0, "H max -> 0, seed 1.0 -> no flag: 000F if MAX writes MAC, 0000 if not"},
{VS_MINI, 0x00000000u, 0x80000000u, 0x3F800000u, 0x80000000u, 0x0000u, 0x0000u, 0, 0, "H mini -> -0, seed 1.0: 00FF if MINI writes MAC, 0000 if not"},
{VS_MAX, 0x3F800000u, 0xBF800000u, 0x00000000u, 0x3F800000u, 0x000Fu, 0x0041u, 0, 0, "H max -> 1.0, seed 0 -> Z: 0000 if MAX writes MAC, 000F if not"},
{VS_MINI, 0x3F800000u, 0xBF800000u, 0x00000000u, 0xBF800000u, 0x000Fu, 0x0041u, 0, 0, "H mini -> -1.0, seed 0: 00F0 if MINI writes MAC, 000F if not"},
{VS_MADD, 0x7F800000u, 0x40000000u, 0xFFFFFFFFu, 0x7FFFFFFFu, 0xF000u, 0x0288u, 7, 7, "I -max + 2^129 saturated product 00000000 | exact wide product 74000000"},
{VS_MADD, 0x7F7FFFFFu, 0x40800000u, 0xFFFFFFFFu, 0x7FFFFFFFu, 0xF000u, 0x0288u, 7, 7, "I -max + 2*max saturated product 00000000 | exact wide product 7FFFFFFF no O"},
{VS_MADD, 0x7F800000u, 0x3F800000u, 0x7F800000u, 0x7FFFFFFFu, 0xF000u, 0x0208u, 7, 7, "I 2^128 + 2^128 in-range product, sum past the top"},
{VS_MUL, 0x7FFFFFFFu, 0x3F000000u, 0x00000000u, 0x7F7FFFFFu, 0x0000u, 0x0040u, 1, 0, "J max*0.5 case 3 swapped"},
{VS_MUL, 0x7FFFFFFFu, 0x3F800000u, 0x00000000u, 0x7FFFFFFFu, 0x0000u, 0x0040u, 1, 1, "J max*1.0"},
{VS_MUL, 0x3F800000u, 0x7FFFFFFFu, 0x00000000u, 0x7FFFFFFEu, 0x0000u, 0x0040u, 1, 1, "J 1.0*max swapped"},
{VS_MUL, 0x3EAAAAABu, 0x40400000u, 0x00000000u, 0x3F800000u, 0x0000u, 0x0040u, 0, 0, "J (1/3)*3 exact 1.00000002980232239"},
{VS_MUL, 0x40400000u, 0x3EAAAAABu, 0x00000000u, 0x3F800000u, 0x0000u, 0x0040u, 0, 0, "J 3*(1/3) swapped"},
{VS_MUL, 0x3F800001u, 0x3F800001u, 0x00000000u, 0x3F800002u, 0x0000u, 0x0040u, 0, 0, "J (1+2^-23)^2 exact 1+2^-22+2^-46"},
{VS_MUL, 0x3FB504F3u, 0x3FB504F3u, 0x00000000u, 0x3FFFFFFFu, 0x0000u, 0x0040u, 0, 0, "J sqrt2^2 exact 1.9999999830"},
{VS_ADD, 0x3F800000u, 0x33C00000u, 0x00000000u, 0x3F800000u, 0x0000u, 0x0040u, 0, 0, "K 1.0 + 1.5*2^-24 chop 3F800000 | nearest 3F800001"},
{VS_ADD, 0xBF800000u, 0xB3C00000u, 0x00000000u, 0xBF800000u, 0x00F0u, 0x00C2u, 0, 0, "K -1.0 - 1.5*2^-24 chop BF800000 | nearest BF800001"},
{VS_ADD, 0x7FFFFFFFu, 0x73FFFFFFu, 0x00000000u, 0x7FFFFFFFu, 0x0000u, 0x0040u, 1, 1, "K max + just under 1ulp chop 7FFFFFFF, no O"},
{VS_ADD, 0xFF800000u, 0xFF800000u, 0x00000000u, 0xFFFFFFFFu, 0xF0F0u, 0x02CAu, 7, 7, "L -2^128 + -2^128"},
{VS_SUB, 0x7FFFFFFFu, 0xFF800000u, 0x00000000u, 0x7FFFFFFFu, 0xF000u, 0x0248u, 7, 7, "L max - -2^128"},
{VS_MSUB, 0x00800000u, 0x3F800000u, 0x00800000u, 0x00000000u, 0x000Fu, 0x0041u, 0, 0, "L 2^-126 - 2^-126 exact zero, Z without U"},
{VS_MUL, 0x00800000u, 0xBF000000u, 0x00000000u, 0x80000000u, 0x0FFFu, 0x01C7u, 6, 6, "L 2^-126 * -0.5 sign of a flushed product"},
};
inline constexpr int kVuSatCaseCount = 68;
// Column-misses, not case-misses: a case wrong in two columns counts twice.
inline constexpr int kVuSatBadInterp = 76;
inline constexpr int kVuSatBadJit = 84;
} // namespace console_vusat