diff --git a/src/xenia/cpu/backend/a64/a64_sequences.cc b/src/xenia/cpu/backend/a64/a64_sequences.cc index ead271af3..4ca256b05 100644 --- a/src/xenia/cpu/backend/a64/a64_sequences.cc +++ b/src/xenia/cpu/backend/a64/a64_sequences.cc @@ -4393,10 +4393,7 @@ EMITTER_OPCODE_TABLE(OPCODE_SET_ROUNDING_MODE, SET_ROUNDING_MODE); // PPC frsqrte lookup table implementation (PowerISA Table E-5). // Matches the x64 backend's EmitFrsqrteHelper. -static uint64_t PpcFrsqrte(void* raw_context) { - auto* ctx = reinterpret_cast(raw_context); - uint64_t bits = ctx->scratch; - +static uint64_t PpcFrsqrte(uint64_t bits) { uint32_t sign = (uint32_t)(bits >> 63); uint32_t exp = (uint32_t)((bits >> 52) & 0x7FF); uint64_t mantissa = bits & 0x000FFFFFFFFFFFFFULL; @@ -4473,7 +4470,7 @@ struct RSQRT_F32 : Sequence> { struct RSQRT_F64 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { // PPC frsqrte uses a specific lookup table, not a high-precision estimate. - // Store source bits to PPCContext::scratch, call helper, load result. + // Call PpcFrsqrte directly (pure integer math, no FPCR impact). DReg src = i.src1.is_constant ? e.d0 : DReg(i.src1.reg().getIdx()); if (i.src1.is_constant) { union { @@ -4485,10 +4482,8 @@ struct RSQRT_F64 : Sequence> { e.fmov(e.d0, e.x0); } e.fmov(e.x0, src); - e.str(e.x0, Xbyak_aarch64::ptr( - e.GetContextReg(), - static_cast(offsetof(ppc::PPCContext, scratch)))); - e.CallNative(reinterpret_cast(PpcFrsqrte)); + e.mov(e.x9, reinterpret_cast(PpcFrsqrte)); + e.blr(e.x9); e.fmov(i.dest, e.x0); } }; @@ -4577,34 +4572,27 @@ static uint32_t PpcVrsqrtefpLane(uint32_t bits) { return result; } -static uint64_t PpcVrsqrtefpHelper(void* raw_context) { - auto* ctx = reinterpret_cast(raw_context); - return (uint64_t)PpcVrsqrtefpLane((uint32_t)ctx->scratch); -} - struct RSQRT_V128 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { - // No hardware FP emitted — the C++ helper does all math. - // GuestToHostThunk restores FPCR after the native call. - // Save source to stack scratch (survives CallNative). + // Call PpcVrsqrtefpLane directly per lane (pure integer math). + // Save source to stack scratch, accumulate results there, load at end. int src_idx = SrcVReg(e, i.src1, 0); e.str(QReg(src_idx), Xbyak_aarch64::ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); - int dest_idx = i.dest.reg().getIdx(); for (int lane = 0; lane < 4; lane++) { - // Load float32 bits from saved source. e.ldr(e.w0, Xbyak_aarch64::ptr( e.sp, static_cast(StackLayout::GUEST_SCRATCH) + lane * 4)); - // Store to PPCContext::scratch for helper. - e.str(e.x0, Xbyak_aarch64::ptr(e.GetContextReg(), - static_cast( - offsetof(ppc::PPCContext, scratch)))); - e.CallNative(reinterpret_cast(PpcVrsqrtefpHelper)); - // Insert result lane into dest (allocated reg, survives CallNative). - e.ins(VReg(dest_idx).s4[lane], e.w0); + e.mov(e.x9, reinterpret_cast(PpcVrsqrtefpLane)); + e.blr(e.x9); + e.str(e.w0, Xbyak_aarch64::ptr( + e.sp, static_cast(StackLayout::GUEST_SCRATCH) + + lane * 4)); } + e.ldr(QReg(i.dest.reg().getIdx()), + Xbyak_aarch64::ptr(e.sp, + static_cast(StackLayout::GUEST_SCRATCH))); } }; EMITTER_OPCODE_TABLE(OPCODE_RSQRT, RSQRT_F32, RSQRT_F64, RSQRT_V128); diff --git a/src/xenia/cpu/testing/misc_opcode_test.cc b/src/xenia/cpu/testing/misc_opcode_test.cc index 73416ebeb..780d55998 100644 --- a/src/xenia/cpu/testing/misc_opcode_test.cc +++ b/src/xenia/cpu/testing/misc_opcode_test.cc @@ -290,6 +290,90 @@ TEST_CASE("RSQRT_F32", "[arithmetic]") { }); } +// ============================================================================ +// RSQRT F64 — PPC frsqrte (lookup table estimate) +// ============================================================================ +TEST_CASE("RSQRT_F64", "[arithmetic]") { + TestFunction test([](HIRBuilder& b) { + auto rsqrt = b.RSqrt(LoadFPR(b, 4)); + StoreFPR(b, 3, rsqrt); + b.Return(); + }); + // rsqrt(1.0) — table gives an estimate near 1.0 + test.Run([](PPCContext* ctx) { ctx->f[4] = 1.0; }, + [](PPCContext* ctx) { + // PPC frsqrte returns a table-based estimate, not exact 1.0. + // Verify it's in the right ballpark (within ~3% of 1.0). + REQUIRE(ctx->f[3] > 0.96); + REQUIRE(ctx->f[3] < 1.04); + }); + // rsqrt(4.0) — estimate near 0.5 + test.Run([](PPCContext* ctx) { ctx->f[4] = 4.0; }, + [](PPCContext* ctx) { + REQUIRE(ctx->f[3] > 0.48); + REQUIRE(ctx->f[3] < 0.52); + }); + // rsqrt(+0.0) → +Inf + test.Run([](PPCContext* ctx) { ctx->f[4] = 0.0; }, + [](PPCContext* ctx) { + REQUIRE(std::isinf(ctx->f[3])); + REQUIRE(ctx->f[3] > 0); + }); + // rsqrt(negative) → QNaN + test.Run([](PPCContext* ctx) { ctx->f[4] = -1.0; }, + [](PPCContext* ctx) { REQUIRE(std::isnan(ctx->f[3])); }); +} + +// ============================================================================ +// RSQRT V128 — PPC vrsqrtefp (per-lane lookup table estimate) +// ============================================================================ +TEST_CASE("RSQRT_V128", "[vector]") { + TestFunction test([](HIRBuilder& b) { + StoreVR(b, 3, b.RSqrt(LoadVR(b, 4))); + b.Return(); + }); + // Normal positive values: estimates should be in the right ballpark. + test.Run( + [](PPCContext* ctx) { ctx->v[4] = vec128f(1.0f, 4.0f, 16.0f, 100.0f); }, + [](PPCContext* ctx) { + auto r = ctx->v[3]; + float r0, r1, r2, r3; + std::memcpy(&r0, &r.u32[0], 4); + std::memcpy(&r1, &r.u32[1], 4); + std::memcpy(&r2, &r.u32[2], 4); + std::memcpy(&r3, &r.u32[3], 4); + // rsqrt(1) ≈ 1.0, rsqrt(4) ≈ 0.5, rsqrt(16) ≈ 0.25, rsqrt(100) ≈ 0.1 + REQUIRE(r0 > 0.9f); + REQUIRE(r0 < 1.1f); + REQUIRE(r1 > 0.45f); + REQUIRE(r1 < 0.55f); + REQUIRE(r2 > 0.22f); + REQUIRE(r2 < 0.28f); + REQUIRE(r3 > 0.08f); + REQUIRE(r3 < 0.12f); + }); + // +0 → +Inf + test.Run([](PPCContext* ctx) { ctx->v[4] = vec128f(0.0f, 0.0f, 0.0f, 0.0f); }, + [](PPCContext* ctx) { + auto r = ctx->v[3]; + float r0; + std::memcpy(&r0, &r.u32[0], 4); + REQUIRE(std::isinf(r0)); + REQUIRE(r0 > 0); + }); + // Negative → QNaN + test.Run( + [](PPCContext* ctx) { + ctx->v[4] = vec128f(-1.0f, -4.0f, -16.0f, -100.0f); + }, + [](PPCContext* ctx) { + auto r = ctx->v[3]; + float r0; + std::memcpy(&r0, &r.u32[0], 4); + REQUIRE(std::isnan(r0)); + }); +} + // ============================================================================ // VECTOR_AVERAGE I8 — rounding halving add: (a+b+1)>>1 // ============================================================================