[SPU] Calculate FRSQEST exponent using arithmetic (#18905)

The `FRSQEST` instruction uses a lookup-table to calculate both the
exponent and mantissa. The exponent follows a simple linear pattern and
thus can be calculated using integer arithmetic: `(exponent==0)? 0xFF :
190 - (exponent + 1) / 2`. This patch replaces the LUT with this method,
vectorizing its calculating and saving on 1kB.

The mantissa still relies on a LUT, which bottlenecks this instruction's
emulation despite the improvement.
This commit is contained in:
Walter 2026-06-20 01:02:20 +10:00 committed by GitHub
parent c0598f61e7
commit 33123bff04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 28 deletions

View File

@ -2799,32 +2799,35 @@ void spu_recompiler::FREST(spu_opcode_t op)
void spu_recompiler::FRSQEST(spu_opcode_t op)
{
const XmmLink& va = XmmGet(op.ra, XmmType::Float);
const XmmLink& vz = XmmAlloc();
const XmmLink& v1 = XmmAlloc();
const XmmLink& v_fraction = XmmAlloc();
const XmmLink& v_exponent = XmmAlloc();
c->movdqa(v_fraction, va);
c->movdqa(v_exponent, va);
// (exponent==0)? 0xFF : 190 - (exponent + 1) / 2
c->paddd(v_exponent, v_exponent);
c->pxor(vz, vz);
c->pavgb(v_exponent, vz);
c->movdqa(v1, XmmConst(v128::from32p(190 << 24)));
c->psubb(v1, v_exponent);
c->pcmpeqb(v_exponent, vz);
c->por(v_exponent, v1);
c->psrld(v_exponent, 1);
c->pand(v_exponent, XmmConst(v128::from32p(0xFF << 23)));
c->psrld(v_fraction, 18);
c->psrld(v_exponent, 23);
c->andps(v_fraction, XmmConst(v128::from32p(0x3F)));
c->andps(v_exponent, XmmConst(v128::from32p(0xFF)));
c->pand(v_fraction, XmmConst(v128::from32p(0x3F)));
const u64 fraction_lut_addr = reinterpret_cast<u64>(spu_frsqest_fraction_lut);
const u64 exponent_lut_addr = reinterpret_cast<u64>(spu_frsqest_exponent_lut);
c->movabs(*arg0, fraction_lut_addr);
c->movabs(*arg1, exponent_lut_addr);
for (u32 index = 0; index < 4; index++)
{
c->pextrd(*qw0, v_fraction, index);
c->mov(*qw1, asmjit::x86::dword_ptr(*arg0, *qw0, 2));
c->pinsrd(v_fraction, *qw1, index);
c->pextrd(*qw0, v_exponent, index);
c->mov(*qw1, asmjit::x86::dword_ptr(*arg1, *qw0, 2));
c->pinsrd(v_exponent, *qw1, index);
}
c->orps(v_fraction, v_exponent);

View File

@ -128,7 +128,6 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
// Global LUTs
llvm::GlobalVariable* m_spu_frest_fraction_lut{};
llvm::GlobalVariable* m_spu_frsqest_fraction_lut{};
llvm::GlobalVariable* m_spu_frsqest_exponent_lut{};
// Helpers (interpreter)
llvm::GlobalVariable* m_scale_float_to{};
@ -1623,7 +1622,6 @@ public:
// LUTs for some instructions
m_spu_frest_fraction_lut = new llvm::GlobalVariable(*m_module, llvm::ArrayType::get(GetType<u32>(), 32), true, llvm::GlobalValue::PrivateLinkage, llvm::ConstantDataArray::get(m_context, spu_frest_fraction_lut));
m_spu_frsqest_fraction_lut = new llvm::GlobalVariable(*m_module, llvm::ArrayType::get(GetType<u32>(), 64), true, llvm::GlobalValue::PrivateLinkage, llvm::ConstantDataArray::get(m_context, spu_frsqest_fraction_lut));
m_spu_frsqest_exponent_lut = new llvm::GlobalVariable(*m_module, llvm::ArrayType::get(GetType<u32>(), 256), true, llvm::GlobalValue::PrivateLinkage, llvm::ConstantDataArray::get(m_context, spu_frsqest_exponent_lut));
}
virtual spu_function_t compile(spu_program&& _func) override
@ -7529,22 +7527,23 @@ public:
{
const auto a = bitcast<u32[4]>(value<f32[4]>(ci->getOperand(0)));
// (exponent==0)? 0xFF : 190 - (exponent + 1) / 2
const auto a_exponent = a & splat<u32[4]>(0xFF << 23);
const auto h_exponent = (a_exponent + (a_exponent & splat<u32[4]>(1 << 23))) >> splat<u32[4]>(1);
const auto r_exponent = splat<u32[4]>(190 << 23) - h_exponent;
const auto final_exponent = select(a_exponent == 0, splat<u32[4]>(0xFF << 23), r_exponent);
const auto a_fraction = (a >> splat<u32[4]>(18)) & splat<u32[4]>(0x3F);
const auto a_exponent = (a >> splat<u32[4]>(23)) & splat<u32[4]>(0xFF);
value_t<u32[4]> final_result = eval(splat<u32[4]>(0));
value_t<u32[4]> final_fraction = eval(splat<u32[4]>(0));
for (u32 i = 0; i < 4; i++)
{
const auto eval_fraction = eval(extract(a_fraction, i));
const auto eval_exponent = eval(extract(a_exponent, i));
value_t<u32> r_fraction = load_const<u32>(m_spu_frsqest_fraction_lut, eval_fraction);
value_t<u32> r_exponent = load_const<u32>(m_spu_frsqest_exponent_lut, eval_exponent);
final_result = eval(insert(final_result, i, eval(r_fraction | r_exponent)));
final_fraction = eval(insert(final_fraction, i, r_fraction));
}
return bitcast<f32[4]>(final_result);
return bitcast<f32[4]>(final_fraction | final_exponent);
});
set_vr(op.rt, frsqest(get_vr<f32[4]>(op.ra)));
@ -8575,21 +8574,25 @@ public:
register_intrinsic("spu_rsqrte", [&](llvm::CallInst* ci)
{
const auto a = bitcast<u32[4]>(value<f32[4]>(ci->getOperand(0)));
// (exponent==0)? 0xFF : 190 - (exponent + 1) / 2
const auto a_exponent = a & splat<u32[4]>(0xFF << 23);
const auto h_exponent = (a_exponent + (a_exponent & splat<u32[4]>(1 << 23))) >> splat<u32[4]>(1);
const auto r_exponent = splat<u32[4]>(190 << 23) - h_exponent;
const auto final_exponent = select(a_exponent == 0, splat<u32[4]>(0xFF << 23), r_exponent);
const auto a_fraction = (a >> splat<u32[4]>(18)) & splat<u32[4]>(0x3F);
const auto a_exponent = (a >> splat<u32[4]>(23)) & splat<u32[4]>(0xFF);
value_t<u32[4]> b = eval(splat<u32[4]>(0));
value_t<u32[4]> final_fraction = eval(splat<u32[4]>(0));
for (u32 i = 0; i < 4; i++)
{
const auto eval_fraction = eval(extract(a_fraction, i));
const auto eval_exponent = eval(extract(a_exponent, i));
value_t<u32> r_fraction = load_const<u32>(m_spu_frsqest_fraction_lut, eval_fraction);
value_t<u32> r_exponent = load_const<u32>(m_spu_frsqest_exponent_lut, eval_exponent);
b = eval(insert(b, i, eval(r_fraction | r_exponent)));
final_fraction = eval(insert(final_fraction, i, r_fraction));
}
const auto b = eval(final_fraction | final_exponent);
const auto base = (b & 0x007ffc00u) << 9; // Base fraction
const auto ymul = (b & 0x3ff) * (a & 0x7ffff); // Step fraction * Y fraction (fixed point at 2^-32)
const auto comparison = (ymul > base); // Should exponent be adjusted?