PPU: Hardware exact VMX estimates

vrefp, vrsqrtefp, vexptefp and vlogefp return the Cell's estimates instead
of exact results, matched bit for bit against a real PS3 with VSCR[NJ] set
and clear.
This commit is contained in:
izcarti
2026-10-10 22:06:19 +03:00
committed by Elad
parent 68e0d6021c
commit cb450d7e8f
5 changed files with 204 additions and 29 deletions
+151
View File
@@ -199,3 +199,154 @@ inline struct ppu_frsqrte_lut_t
}
}
} ppu_frqrte_lut;
constexpr u32 ppu_vrefp_segments[32][2] =
{
{0x7ff800, 0x3e1}, {0x783800, 0x3a7}, {0x70ea00, 0x371}, {0x6a0800, 0x340},
{0x638800, 0x313}, {0x5d6200, 0x2ea}, {0x579000, 0x2c4}, {0x520800, 0x2a0},
{0x4cc800, 0x27f}, {0x47ca00, 0x261}, {0x430800, 0x245}, {0x3e8000, 0x22a},
{0x3a2c00, 0x212}, {0x360800, 0x1fb}, {0x321400, 0x1e5}, {0x2e4a00, 0x1d1},
{0x2aa800, 0x1be}, {0x272c00, 0x1ac}, {0x23d600, 0x19b}, {0x209e00, 0x18b},
{0x1d8800, 0x17c}, {0x1a9000, 0x16e}, {0x17ae00, 0x15b}, {0x14f800, 0x15b},
{0x124400, 0x143}, {0x0fbe00, 0x143}, {0x0d3800, 0x12d}, {0x0ade00, 0x12d},
{0x088400, 0x11a}, {0x065000, 0x11a}, {0x041c00, 0x108}, {0x020c00, 0x106},
};
constexpr u32 ppu_vrsqrtefp_segments[32][3] =
{
{0x34fd00, 0x568, 0x00}, {0x2f9700, 0x4f3, 0x26}, {0x2aa500, 0x48d, 0xc8}, {0x261800, 0x435, 0xc8},
{0x21e400, 0x3e7, 0x26}, {0x1dfe00, 0x3a2, 0x88}, {0x1a5c00, 0x365, 0xc8}, {0x16f800, 0x32e, 0x22},
{0x13ca00, 0x2fc, 0x00}, {0x10ce00, 0x2d0, 0x00}, {0x0dfe00, 0x2a8, 0x00}, {0x0b5700, 0x283, 0x26},
{0x08d400, 0x261, 0xc8}, {0x067300, 0x243, 0x26}, {0x043100, 0x226, 0x22}, {0x020b00, 0x20b, 0x26},
{0x7ff400, 0x7a4, 0x00}, {0x785200, 0x700, 0x00}, {0x715400, 0x670, 0x00}, {0x6ae400, 0x5f2, 0x88},
{0x64f200, 0x584, 0x00}, {0x5f6e00, 0x524, 0x00}, {0x5a4c00, 0x4cc, 0x00}, {0x558000, 0x47e, 0x22},
{0x510200, 0x43a, 0x88}, {0x4cca00, 0x3fa, 0x88}, {0x48d000, 0x3c2, 0x88}, {0x450e00, 0x38e, 0x22},
{0x418200, 0x35e, 0x22}, {0x3e2400, 0x332, 0x88}, {0x3af200, 0x30a, 0x88}, {0x37e800, 0x2e6, 0x22},
};
constexpr u16 ppu_vlogefp_segments[16][2] =
{
{0x0000, 12}, {0x0600, 10}, {0x0b00, 10}, {0x1000, 9},
{0x1480, 9}, {0x1900, 9}, {0x1d80, 8}, {0x2180, 8},
{0x2580, 8}, {0x297f, 7}, {0x2cff, 7}, {0x307f, 7},
{0x33fe, 6}, {0x36fe, 6}, {0x39fe, 6}, {0x3cfe, 6},
};
constexpr u16 ppu_vexptefp_segments[16][2] =
{
{0x0006, 6}, {0x0306, 6}, {0x0606, 6}, {0x0906, 6},
{0x0c07, 7}, {0x0f87, 7}, {0x1307, 7}, {0x1680, 8},
{0x1a80, 8}, {0x1e80, 8}, {0x2288, 9}, {0x2708, 9},
{0x2b88, 9}, {0x3008, 10}, {0x3508, 10}, {0x3a08, 12},
};
constexpr void ppu_vnormalize(s32& e, u32& m)
{
const u32 shift = std::countl_zero(m) - 8;
m = (m << shift) & 0x7fffff;
e = 1 - static_cast<s32>(shift);
}
constexpr u32 ppu_vrefp(u32 x, bool nj)
{
const u32 s = x & 0x80000000;
s32 e = (x >> 23) & 0xff;
u32 m = x & 0x7fffff;
if (e == 0xff)
return m ? x | 0x400000 : s;
if (e == 0 && (nj || m == 0))
return s | 0x7f800000;
if (e == 0)
ppu_vnormalize(e, m);
const auto& seg = ppu_vrefp_segments[m >> 18];
const u32 mant = seg[0] - seg[1] * ((m >> 9) & 0x1ff);
const s32 exp = 253 - e;
if (exp >= 0xff)
return s | 0x7f800000;
if (exp > 0)
return s | exp << 23 | mant;
if (nj)
return s;
const u32 full = mant | 0x800000, shift = 1 - exp, half = 1u << (shift - 1);
const u32 rest = full & ((half << 1) - 1);
return s | ((full >> shift) + (rest > half || (rest == half && (full >> shift) & 1)));
}
constexpr u32 ppu_vrsqrtefp(u32 x, bool nj)
{
const u32 s = x & 0x80000000;
s32 e = (x >> 23) & 0xff;
u32 m = x & 0x7fffff;
if (e == 0xff)
return m ? x | 0x400000 : s ? 0x7fc00000 : 0;
if (e == 0 && (nj || m == 0))
return s | 0x7f800000;
if (s)
return 0x7fc00000;
if (e == 0)
ppu_vnormalize(e, m);
const auto& seg = ppu_vrsqrtefp_segments[(e & 1) << 4 | m >> 19];
const u32 key = (m >> 9) & 0x3ff;
return (380 - e) >> 1 << 23 | (seg[0] - ((seg[1] * key) >> 2) - ((seg[2] >> (key & 7)) & 1));
}
constexpr u32 ppu_vlogefp(u32 x, bool nj)
{
const u32 s = x & 0x80000000;
s32 e = (x >> 23) & 0xff;
u32 m = x & 0x7fffff;
if (e == 0xff)
return m ? x | 0x400000 : s ? 0x7fc00000 : x;
if (e == 0 && (nj || m == 0))
return 0xff800000;
if (s)
return 0x7fc00000;
if (e == 0)
ppu_vnormalize(e, m);
const u32 key = m >> 12;
const auto& seg = ppu_vlogefp_segments[key >> 7];
const s32 fixed = (e - 127) * 2048 + ((seg[0] + seg[1] * (key & 0x7f)) >> 3);
return std::bit_cast<u32>(static_cast<f32>(fixed) / 2048);
}
constexpr u32 ppu_vexptefp(u32 x, bool)
{
const u32 s = x & 0x80000000, e = (x >> 23) & 0xff, m = x & 0x7fffff;
if (e == 0xff)
return m ? x | 0x400000 : s ? 0 : x;
if (e > 150)
return s ? 0 : 0x7f800000;
const s64 mag = e >= 135 ? s64{m | 0x800000} << (e - 135) : e > 111 ? (m | 0x800000) >> (135 - e) : 0;
const s64 fixed = (s ? -mag : mag) >> 4;
const s64 exp = 127 + (fixed >> 11);
if (exp >= 0xff)
return 0x7f800000;
if (exp <= 0)
return 0;
const auto& seg = ppu_vexptefp_segments[(fixed >> 7) & 0xf];
return static_cast<u32>(exp) << 23 | ((seg[0] + seg[1] * (fixed & 0x7f)) >> 3) << 12;
}
+28 -22
View File
@@ -1325,30 +1325,34 @@ template <u32 Build, ppu_exec_bit... Flags>
auto VEXPTEFP()
{
if constexpr (Build == 0xf1a6)
return ppu_exec_select<Flags...>::template select<fix_vnan>();
return ppu_exec_select<Flags...>::template select<>();
static const auto exec = [](auto&& d, auto&& b)
static const auto exec = [](auto&& d, auto&& b, auto&& nj)
{
// for (u32 i = 0; i < 4; i++) d._f[i] = std::exp2f(b._f[i]);
d = ppu_set_vnan<Flags...>(gv_exp2_approxfs(std::move(b)));
for (u32 i = 0; i < 4; i++)
{
d._u32[i] = ppu_vexptefp(b._u32[i], nj);
}
};
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb]);
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.nj);
}
template <u32 Build, ppu_exec_bit... Flags>
auto VLOGEFP()
{
if constexpr (Build == 0xf1a6)
return ppu_exec_select<Flags...>::template select<fix_vnan>();
return ppu_exec_select<Flags...>::template select<>();
static const auto exec = [](auto&& d, auto&& b)
static const auto exec = [](auto&& d, auto&& b, auto&& nj)
{
// for (u32 i = 0; i < 4; i++) d._f[i] = std::log2f(b._f[i]);
d = ppu_set_vnan<Flags...>(gv_log2_approxfs(std::move(b)));
for (u32 i = 0; i < 4; i++)
{
d._u32[i] = ppu_vlogefp(b._u32[i], nj);
}
};
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb]);
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.nj);
}
template <u32 Build, ppu_exec_bit... Flags>
@@ -2173,16 +2177,17 @@ template <u32 Build, ppu_exec_bit... Flags>
auto VREFP()
{
if constexpr (Build == 0xf1a6)
return ppu_exec_select<Flags...>::template select<use_nj, fix_nj, set_vnan, fix_vnan>();
return ppu_exec_select<Flags...>::template select<>();
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask)
static const auto exec = [](auto&& d, auto&& b, auto&& nj)
{
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), b), b));
for (u32 i = 0; i < 4; i++)
{
d._u32[i] = ppu_vrefp(b._u32[i], nj);
}
};
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask);
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.nj);
}
template <u32 Build, ppu_exec_bit... Flags>
@@ -2293,16 +2298,17 @@ template <u32 Build, ppu_exec_bit... Flags>
auto VRSQRTEFP()
{
if constexpr (Build == 0xf1a6)
return ppu_exec_select<Flags...>::template select<use_nj, fix_nj, set_vnan, fix_vnan>();
return ppu_exec_select<Flags...>::template select<>();
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask)
static const auto exec = [](auto&& d, auto&& b, auto&& nj)
{
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), gv_sqrtfs(b)), b));
for (u32 i = 0; i < 4; i++)
{
d._u32[i] = ppu_vrsqrtefp(b._u32[i], nj);
}
};
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask);
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.nj);
}
template <u32 Build, ppu_exec_bit... Flags>
+6 -1
View File
@@ -20,6 +20,7 @@
#include "PPUAnalyser.h"
#include "PPUModule.h"
#include "PPUDisAsm.h"
#include "Common.h"
#include "SPURecompiler.h"
#include "timers.hpp"
#include "lv2/sys_sync.h"
@@ -4967,6 +4968,10 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
{ "__escape", reinterpret_cast<u64>(+ppu_escape) },
{ "__read_maybe_mmio32", reinterpret_cast<u64>(+ppu_read_mmio_aware_u32) },
{ "__write_maybe_mmio32", reinterpret_cast<u64>(+ppu_write_mmio_aware_u32) },
{ "__vrefp", reinterpret_cast<u64>(+[](u32 x, u32 nj) { return ppu_vrefp(x, nj != 0); }) },
{ "__vrsqrtefp", reinterpret_cast<u64>(+[](u32 x, u32 nj) { return ppu_vrsqrtefp(x, nj != 0); }) },
{ "__vexptefp", reinterpret_cast<u64>(+[](u32 x, u32 nj) { return ppu_vexptefp(x, nj != 0); }) },
{ "__vlogefp", reinterpret_cast<u64>(+[](u32 x, u32 nj) { return ppu_vlogefp(x, nj != 0); }) },
};
for (u64 index = 0; index < 1024; index++)
@@ -5587,7 +5592,7 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
settings += ppu_settings::daz_and_ftz;
// Write version, hash, CPU, settings
fmt::append(obj_name, "v8-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string()));
fmt::append(obj_name, "v9-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string()));
}
if (cpu ? cpu->state.all_of(cpu_flag::exit) : Emu.IsStopped())
+18 -6
View File
@@ -489,6 +489,20 @@ Value* PPUTranslator::VecHandleResult(Value* val, bool flush_denormals_manually)
return val;
}
Value* PPUTranslator::VecEstimate(StringRef name, u32 vr)
{
const auto b = GetVr(vr, VrType::vi32);
const auto nj = ZExt(RegLoad(m_nj), GetType<u32>());
Value* result = GetUndef<u32[4]>();
for (u32 i = 0; i < 4; i++)
{
result = m_ir->CreateInsertElement(result, Call(GetType<u32>(), m_pure_attr, name, m_ir->CreateExtractElement(b, i), nj), i);
}
return result;
}
Value* PPUTranslator::GetAddr(u64 _add)
{
if (m_reloc)
@@ -1293,14 +1307,12 @@ void PPUTranslator::VCTUXS(ppu_opcode_t op)
void PPUTranslator::VEXPTEFP(ppu_opcode_t op)
{
const auto b = get_vr<f32[4]>(op.vb);
set_vr(op.vd, vec_handle_result(llvm_calli<f32[4], decltype(b)>{"llvm.exp2.v4f32", {b}}, true));
SetVr(op.vd, VecEstimate("__vexptefp", op.vb));
}
void PPUTranslator::VLOGEFP(ppu_opcode_t op)
{
const auto b = get_vr<f32[4]>(op.vb);
set_vr(op.vd, vec_handle_result(llvm_calli<f32[4], decltype(b)>{"llvm.log2.v4f32", {b}}, true));
SetVr(op.vd, VecEstimate("__vlogefp", op.vb));
}
void PPUTranslator::VMADDFP(ppu_opcode_t op)
@@ -1772,7 +1784,7 @@ void PPUTranslator::VPKUWUS(ppu_opcode_t op)
void PPUTranslator::VREFP(ppu_opcode_t op)
{
set_vr(op.vd, vec_handle_result(fsplat<f32[4]>(1.0) / get_vr<f32[4]>(op.vb)));
SetVr(op.vd, VecEstimate("__vrefp", op.vb));
}
void PPUTranslator::VRFIM(ppu_opcode_t op)
@@ -1815,7 +1827,7 @@ void PPUTranslator::VRLW(ppu_opcode_t op)
void PPUTranslator::VRSQRTEFP(ppu_opcode_t op)
{
set_vr(op.vd, vec_handle_result(fsplat<f32[4]>(1.0) / callf<f32[4]>(get_intrinsic<f32[4]>(Intrinsic::sqrt), get_vr<f32[4]>(op.vb))));
SetVr(op.vd, VecEstimate("__vrsqrtefp", op.vb));
}
void PPUTranslator::VSEL(ppu_opcode_t op)
+1
View File
@@ -117,6 +117,7 @@ public:
llvm::Value* VecHandleNan(llvm::Value* val);
llvm::Value* VecHandleDenormal(llvm::Value* val);
llvm::Value* VecHandleResult(llvm::Value* val, bool flush_denormals_manually = false);
llvm::Value* VecEstimate(llvm::StringRef name, u32 vr);
template <typename T>
auto vec_handle_result(T&& expr, bool flush_denormals_manually = false)