[CPU/A64] Add PPC FP state handling

Track PPC scalar FP state in the backend context and restore it at
host/guest boundaries so translated FP code has a stable rounding mode.

Convert remaining VMX sequences to scoped EmitWithVmxFpcr. Remove
unnecessary ChangeFpcrMode from sequences that only call native helpers.

Co-Authored-By: Reality <reality@xenios.jp>
This commit is contained in:
Herman S.
2026-03-23 12:06:28 +09:00
parent 3747f5e282
commit be64fb1b70
2 changed files with 217 additions and 186 deletions

View File

@@ -126,6 +126,12 @@ HostToGuestThunk A64HelperEmitter::EmitHostToGuestThunk() {
// x21 = virtual_membase (loaded from context) // x21 = virtual_membase (loaded from context)
ldr(x21, ptr(x20, static_cast<int32_t>( ldr(x21, ptr(x20, static_cast<int32_t>(
offsetof(ppc::PPCContext, virtual_membase)))); offsetof(ppc::PPCContext, virtual_membase))));
// Restore the guest scalar FPCR on every host->guest entry so host-side
// work done before the call can't leak a stale rounding / non-IEEE mode.
sub(x10, x20, static_cast<uint32_t>(sizeof(A64BackendContext)));
ldr(w11,
ptr(x10, static_cast<uint32_t>(offsetof(A64BackendContext, fpcr_fpu))));
msr(3, 3, 4, 4, 0, x11);
// x0 still holds target, x2 holds return address. // x0 still holds target, x2 holds return address.
// The guest function's prolog stores x0 to GUEST_RET_ADDR on its stack // The guest function's prolog stores x0 to GUEST_RET_ADDR on its stack
// frame. Move the target to a scratch reg and put the guest return // frame. Move the target to a scratch reg and put the guest return
@@ -239,6 +245,13 @@ GuestToHostThunk A64HelperEmitter::EmitGuestToHostThunk() {
// x1, x2, x3 already hold args from the caller. // x1, x2, x3 already hold args from the caller.
blr(x9); blr(x9);
// Host callbacks may change FPCR. Restore the guest scalar FPCR before
// resuming the JIT so later guest ops observe the cached PPC mode.
sub(x10, x20, static_cast<uint32_t>(sizeof(A64BackendContext)));
ldr(w11,
ptr(x10, static_cast<uint32_t>(offsetof(A64BackendContext, fpcr_fpu))));
msr(3, 3, 4, 4, 0, x11);
code_offsets.epilog = getSize(); code_offsets.epilog = getSize();
// Restore. // Restore.
@@ -703,6 +716,10 @@ void A64Backend::InitializeBackendContext(void* ctx) {
a64_ctx->stackpoints = new A64BackendStackpoint[max_stackpoints](); a64_ctx->stackpoints = new A64BackendStackpoint[max_stackpoints]();
} }
} }
// Reset the live host FPCR for a fresh PPC context so one test's rounding
// state does not leak into the next on the shared PPC test runner thread.
SetGuestRoundingMode(ctx, 0);
} }
void A64Backend::DeinitializeBackendContext(void* ctx) { void A64Backend::DeinitializeBackendContext(void* ctx) {
@@ -781,15 +798,18 @@ void A64Backend::SetGuestRoundingMode(void* ctx, unsigned int mode) {
A64BackendContext* bctx = BackendContextForGuestContext(ctx); A64BackendContext* bctx = BackendContextForGuestContext(ctx);
uint32_t control = mode & 7; uint32_t control = mode & 7;
uint32_t fpcr_val = fpcr_table[control]; uint32_t fpcr_val = fpcr_table[control];
#if XE_ARCH_ARM64
#if XE_COMPILER_MSVC #if XE_COMPILER_MSVC
// MSVC ARM64 intrinsic: ARM64_FPCR = register ID 0x5A20. // MSVC ARM64 intrinsic: ARM64_FPCR = register ID 0x5A20.
_WriteStatusReg(0x5A20, static_cast<uint64_t>(fpcr_val)); _WriteStatusReg(0x5A20, static_cast<uint64_t>(fpcr_val));
#else #else
__asm__ volatile("msr fpcr, %0" : : "r"(static_cast<uint64_t>(fpcr_val))); __asm__ volatile("msr fpcr, %0" : : "r"(static_cast<uint64_t>(fpcr_val)));
#endif
#endif #endif
bctx->fpcr_fpu = fpcr_val; bctx->fpcr_fpu = fpcr_val;
if (control & 0b100) {
bctx->flags |= (1u << kA64BackendNonIEEEMode);
} else {
bctx->flags &= ~(1u << kA64BackendNonIEEEMode);
}
auto ppc_context = reinterpret_cast<ppc::PPCContext*>(ctx); auto ppc_context = reinterpret_cast<ppc::PPCContext*>(ctx);
ppc_context->fpscr.bits.rn = control; ppc_context->fpscr.bits.rn = control;
ppc_context->fpscr.bits.ni = control >> 2; ppc_context->fpscr.bits.ni = control >> 2;

View File

@@ -1434,9 +1434,10 @@ struct NEG_F64 : Sequence<NEG_F64, I<OPCODE_NEG, F64Op, F64Op>> {
}; };
struct NEG_V128 : Sequence<NEG_V128, I<OPCODE_NEG, V128Op, V128Op>> { struct NEG_V128 : Sequence<NEG_V128, I<OPCODE_NEG, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
int s = SrcVReg(e, i.src1, 0); int s = SrcVReg(e, i.src1, 0);
e.fneg(VReg(i.dest.reg().getIdx()).s4, VReg(s).s4); e.fneg(VReg(i.dest.reg().getIdx()).s4, VReg(s).s4);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_NEG, NEG_I8, NEG_I16, NEG_I32, NEG_I64, NEG_F32, EMITTER_OPCODE_TABLE(OPCODE_NEG, NEG_I8, NEG_I16, NEG_I32, NEG_I64, NEG_F32,
@@ -1479,9 +1480,10 @@ struct ABS_F64 : Sequence<ABS_F64, I<OPCODE_ABS, F64Op, F64Op>> {
}; };
struct ABS_V128 : Sequence<ABS_V128, I<OPCODE_ABS, V128Op, V128Op>> { struct ABS_V128 : Sequence<ABS_V128, I<OPCODE_ABS, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
int s = SrcVReg(e, i.src1, 0); int s = SrcVReg(e, i.src1, 0);
e.fabs(VReg(i.dest.reg().getIdx()).s4, VReg(s).s4); e.fabs(VReg(i.dest.reg().getIdx()).s4, VReg(s).s4);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_ABS, ABS_F32, ABS_F64, ABS_V128); EMITTER_OPCODE_TABLE(OPCODE_ABS, ABS_F32, ABS_F64, ABS_V128);
@@ -3192,7 +3194,7 @@ struct MAX_F64 : Sequence<MAX_F64, I<OPCODE_MAX, F64Op, F64Op, F64Op>> {
}; };
struct MAX_V128 : Sequence<MAX_V128, I<OPCODE_MAX, V128Op, V128Op, V128Op>> { struct MAX_V128 : Sequence<MAX_V128, I<OPCODE_MAX, V128Op, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
int s1, s2; int s1, s2;
PrepareVmxFpSources(e, i.src1, i.src2, s1, s2); PrepareVmxFpSources(e, i.src1, i.src2, s1, s2);
e.fmax(VReg(2).s4, VReg(s1).s4, VReg(s2).s4); e.fmax(VReg(2).s4, VReg(s1).s4, VReg(s2).s4);
@@ -3200,6 +3202,7 @@ struct MAX_V128 : Sequence<MAX_V128, I<OPCODE_MAX, V128Op, V128Op, V128Op>> {
FixupVmxMaxMinNan(e); FixupVmxMaxMinNan(e);
FlushDenormals_V128(e, 2, 0, 1); FlushDenormals_V128(e, 2, 0, 1);
e.mov(VReg(i.dest.reg().getIdx()).b16, VReg(2).b16); e.mov(VReg(i.dest.reg().getIdx()).b16, VReg(2).b16);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_MAX, MAX_F32, MAX_F64, MAX_V128); EMITTER_OPCODE_TABLE(OPCODE_MAX, MAX_F32, MAX_F64, MAX_V128);
@@ -3328,7 +3331,7 @@ struct MIN_F64 : Sequence<MIN_F64, I<OPCODE_MIN, F64Op, F64Op, F64Op>> {
}; };
struct MIN_V128 : Sequence<MIN_V128, I<OPCODE_MIN, V128Op, V128Op, V128Op>> { struct MIN_V128 : Sequence<MIN_V128, I<OPCODE_MIN, V128Op, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
int s1, s2; int s1, s2;
PrepareVmxFpSources(e, i.src1, i.src2, s1, s2); PrepareVmxFpSources(e, i.src1, i.src2, s1, s2);
e.fmin(VReg(2).s4, VReg(s1).s4, VReg(s2).s4); e.fmin(VReg(2).s4, VReg(s1).s4, VReg(s2).s4);
@@ -3336,6 +3339,7 @@ struct MIN_V128 : Sequence<MIN_V128, I<OPCODE_MIN, V128Op, V128Op, V128Op>> {
FixupVmxMaxMinNan(e); FixupVmxMaxMinNan(e);
FlushDenormals_V128(e, 2, 0, 1); FlushDenormals_V128(e, 2, 0, 1);
e.mov(VReg(i.dest.reg().getIdx()).b16, VReg(2).b16); e.mov(VReg(i.dest.reg().getIdx()).b16, VReg(2).b16);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_MIN, MIN_I8, MIN_I16, MIN_I32, MIN_I64, MIN_F32, EMITTER_OPCODE_TABLE(OPCODE_MIN, MIN_I8, MIN_I16, MIN_I32, MIN_I64, MIN_F32,
@@ -3562,7 +3566,7 @@ struct ROUND_F64 : Sequence<ROUND_F64, I<OPCODE_ROUND, F64Op, F64Op>> {
}; };
struct ROUND_V128 : Sequence<ROUND_V128, I<OPCODE_ROUND, V128Op, V128Op>> { struct ROUND_V128 : Sequence<ROUND_V128, I<OPCODE_ROUND, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
int s = SrcVReg(e, i.src1, 0); int s = SrcVReg(e, i.src1, 0);
auto src = VReg(s).s4; auto src = VReg(s).s4;
auto dst = VReg(i.dest.reg().getIdx()).s4; auto dst = VReg(i.dest.reg().getIdx()).s4;
@@ -3584,6 +3588,7 @@ struct ROUND_V128 : Sequence<ROUND_V128, I<OPCODE_ROUND, V128Op, V128Op>> {
e.frinti(dst, src); e.frinti(dst, src);
break; break;
} }
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_ROUND, ROUND_F32, ROUND_F64, ROUND_V128); EMITTER_OPCODE_TABLE(OPCODE_ROUND, ROUND_F32, ROUND_F64, ROUND_V128);
@@ -3625,9 +3630,10 @@ struct SQRT_F64 : Sequence<SQRT_F64, I<OPCODE_SQRT, F64Op, F64Op>> {
}; };
struct SQRT_V128 : Sequence<SQRT_V128, I<OPCODE_SQRT, V128Op, V128Op>> { struct SQRT_V128 : Sequence<SQRT_V128, I<OPCODE_SQRT, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
int s = SrcVReg(e, i.src1, 0); int s = SrcVReg(e, i.src1, 0);
e.fsqrt(VReg(i.dest.reg().getIdx()).s4, VReg(s).s4); e.fsqrt(VReg(i.dest.reg().getIdx()).s4, VReg(s).s4);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_SQRT, SQRT_F32, SQRT_F64, SQRT_V128); EMITTER_OPCODE_TABLE(OPCODE_SQRT, SQRT_F32, SQRT_F64, SQRT_V128);
@@ -4039,7 +4045,7 @@ struct MUL_ADD_V128
// 1. Flush s3 into v3, save to stack[32]. // 1. Flush s3 into v3, save to stack[32].
// 2. Flush s1/s2 into v0/v1, save to stack[0]/stack[16]. // 2. Flush s1/s2 into v0/v1, save to stack[0]/stack[16].
// 3. Restore s3 into v3, fmla into v2, NaN fixup, flush output. // 3. Restore s3 into v3, fmla into v2, NaN fixup, flush output.
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
int d = i.dest.reg().getIdx(); int d = i.dest.reg().getIdx();
// Flush s3 → v3, save to stack slot 2. // Flush s3 → v3, save to stack slot 2.
@@ -4053,8 +4059,8 @@ struct MUL_ADD_V128
// Flush s1/s2 → v0/v1, save to stack slots 0/1. // Flush s1/s2 → v0/v1, save to stack slots 0/1.
int s1, s2; int s1, s2;
PrepareVmxFpSources(e, i.src1, i.src2, s1, s2); PrepareVmxFpSources(e, i.src1, i.src2, s1, s2);
e.str(QReg(0), Xbyak_aarch64::ptr( e.str(QReg(0), Xbyak_aarch64::ptr(e.sp, static_cast<int32_t>(
e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH))); StackLayout::GUEST_SCRATCH)));
e.str(QReg(1), e.str(QReg(1),
Xbyak_aarch64::ptr( Xbyak_aarch64::ptr(
e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH) + 16)); e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH) + 16));
@@ -4071,6 +4077,7 @@ struct MUL_ADD_V128
// Flush output denormals. // Flush output denormals.
FlushDenormals_V128(e, 2, 0, 1); FlushDenormals_V128(e, 2, 0, 1);
e.mov(VReg(d).b16, VReg(2).b16); e.mov(VReg(d).b16, VReg(2).b16);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_MUL_ADD, MUL_ADD_F32, MUL_ADD_F64, MUL_ADD_V128); EMITTER_OPCODE_TABLE(OPCODE_MUL_ADD, MUL_ADD_F32, MUL_ADD_F64, MUL_ADD_V128);
@@ -4160,7 +4167,7 @@ struct MUL_SUB_V128
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
// dest = s1*s2 - s3 with VMX denormal flushing + PPC NaN propagation. // dest = s1*s2 - s3 with VMX denormal flushing + PPC NaN propagation.
// Same as MUL_ADD but negate s3 before the fmla. // Same as MUL_ADD but negate s3 before the fmla.
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
int d = i.dest.reg().getIdx(); int d = i.dest.reg().getIdx();
// Flush s3 → v3, save un-negated for NaN fixup. // Flush s3 → v3, save un-negated for NaN fixup.
@@ -4174,8 +4181,8 @@ struct MUL_SUB_V128
// Flush s1/s2 → v0/v1, save for NaN fixup. // Flush s1/s2 → v0/v1, save for NaN fixup.
int s1, s2; int s1, s2;
PrepareVmxFpSources(e, i.src1, i.src2, s1, s2); PrepareVmxFpSources(e, i.src1, i.src2, s1, s2);
e.str(QReg(0), Xbyak_aarch64::ptr( e.str(QReg(0), Xbyak_aarch64::ptr(e.sp, static_cast<int32_t>(
e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH))); StackLayout::GUEST_SCRATCH)));
e.str(QReg(1), e.str(QReg(1),
Xbyak_aarch64::ptr( Xbyak_aarch64::ptr(
e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH) + 16)); e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH) + 16));
@@ -4193,6 +4200,7 @@ struct MUL_SUB_V128
// Flush output denormals. // Flush output denormals.
FlushDenormals_V128(e, 2, 0, 1); FlushDenormals_V128(e, 2, 0, 1);
e.mov(VReg(d).b16, VReg(2).b16); e.mov(VReg(d).b16, VReg(2).b16);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_MUL_SUB, MUL_SUB_F32, MUL_SUB_F64, MUL_SUB_V128); EMITTER_OPCODE_TABLE(OPCODE_MUL_SUB, MUL_SUB_F32, MUL_SUB_F64, MUL_SUB_V128);
@@ -4274,7 +4282,8 @@ struct POW2_F64 : Sequence<POW2_F64, I<OPCODE_POW2, F64Op, F64Op>> {
}; };
struct POW2_V128 : Sequence<POW2_V128, I<OPCODE_POW2, V128Op, V128Op>> { struct POW2_V128 : Sequence<POW2_V128, I<OPCODE_POW2, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); // No hardware FP emitted — the C++ helper does all math.
// GuestToHostThunk restores FPCR after the native call.
int s = SrcVReg(e, i.src1, 0); int s = SrcVReg(e, i.src1, 0);
int d = i.dest.reg().getIdx(); int d = i.dest.reg().getIdx();
e.str(QReg(s), e.str(QReg(s),
@@ -4302,7 +4311,8 @@ struct LOG2_F64 : Sequence<LOG2_F64, I<OPCODE_LOG2, F64Op, F64Op>> {
}; };
struct LOG2_V128 : Sequence<LOG2_V128, I<OPCODE_LOG2, V128Op, V128Op>> { struct LOG2_V128 : Sequence<LOG2_V128, I<OPCODE_LOG2, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); // No hardware FP emitted — the C++ helper does all math.
// GuestToHostThunk restores FPCR after the native call.
int s = SrcVReg(e, i.src1, 0); int s = SrcVReg(e, i.src1, 0);
int d = i.dest.reg().getIdx(); int d = i.dest.reg().getIdx();
e.str(QReg(s), e.str(QReg(s),
@@ -4322,9 +4332,9 @@ struct DOT_PRODUCT_3_V128
: Sequence<DOT_PRODUCT_3_V128, : Sequence<DOT_PRODUCT_3_V128,
I<OPCODE_DOT_PRODUCT_3, V128Op, V128Op, V128Op>> { I<OPCODE_DOT_PRODUCT_3, V128Op, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
// Inline NEON: multiply in double precision, sum 3 elements, convert back. // Inline NEON: multiply in double precision, sum 3 elements, convert
// Uses v0-v3 as scratch. // back. Uses v0-v3 as scratch.
int s1 = SrcVReg(e, i.src1, 0); int s1 = SrcVReg(e, i.src1, 0);
int s2 = SrcVReg(e, i.src2, 1); int s2 = SrcVReg(e, i.src2, 1);
int d = i.dest.reg().getIdx(); int d = i.dest.reg().getIdx();
@@ -4337,9 +4347,7 @@ struct DOT_PRODUCT_3_V128
e.fcvtl2(VReg(3).d2, VReg(s2).s4); // v3 = {s2[2], s2[3]} as f64 e.fcvtl2(VReg(3).d2, VReg(s2).s4); // v3 = {s2[2], s2[3]} as f64
e.fmul(VReg(2).d2, VReg(2).d2, VReg(3).d2); // v2 = {a2*b2, a3*b3} e.fmul(VReg(2).d2, VReg(2).d2, VReg(3).d2); // v2 = {a2*b2, a3*b3}
// Sum: d0 = v0[0] + v0[1] + v2[0] (skip v2[1] = element 3). // Sum: d0 = v0[0] + v0[1] + v2[0] (skip v2[1] = element 3).
// faddp d1, v0.2d → d1 = v0[0] + v0[1]
e.faddp(DReg(1), VReg(0).d2); e.faddp(DReg(1), VReg(0).d2);
// fadd d1, d1, d2 → d1 = d1 + v2[0]
e.fadd(DReg(1), DReg(1), DReg(2)); e.fadd(DReg(1), DReg(1), DReg(2));
// Convert back to float. // Convert back to float.
e.fcvt(SReg(0), DReg(1)); e.fcvt(SReg(0), DReg(1));
@@ -4355,6 +4363,7 @@ struct DOT_PRODUCT_3_V128
e.L(not_inf); e.L(not_inf);
// Splat result to all 4 lanes. // Splat result to all 4 lanes.
e.dup(VReg(d).s4, VReg(0).s4[0]); e.dup(VReg(d).s4, VReg(0).s4[0]);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_DOT_PRODUCT_3, DOT_PRODUCT_3_V128); EMITTER_OPCODE_TABLE(OPCODE_DOT_PRODUCT_3, DOT_PRODUCT_3_V128);
@@ -4366,7 +4375,7 @@ struct DOT_PRODUCT_4_V128
: Sequence<DOT_PRODUCT_4_V128, : Sequence<DOT_PRODUCT_4_V128,
I<OPCODE_DOT_PRODUCT_4, V128Op, V128Op, V128Op>> { I<OPCODE_DOT_PRODUCT_4, V128Op, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
// Inline NEON: multiply in double precision, sum all 4 elements. // Inline NEON: multiply in double precision, sum all 4 elements.
int s1 = SrcVReg(e, i.src1, 0); int s1 = SrcVReg(e, i.src1, 0);
int s2 = SrcVReg(e, i.src2, 1); int s2 = SrcVReg(e, i.src2, 1);
@@ -4381,7 +4390,6 @@ struct DOT_PRODUCT_4_V128
e.fmul(VReg(2).d2, VReg(2).d2, VReg(3).d2); e.fmul(VReg(2).d2, VReg(2).d2, VReg(3).d2);
// Sum all 4 products: v0 = {a0*b0+a2*b2, a1*b1+a3*b3} // Sum all 4 products: v0 = {a0*b0+a2*b2, a1*b1+a3*b3}
e.fadd(VReg(0).d2, VReg(0).d2, VReg(2).d2); e.fadd(VReg(0).d2, VReg(0).d2, VReg(2).d2);
// faddp d1, v0.2d → d1 = sum of both lanes
e.faddp(DReg(1), VReg(0).d2); e.faddp(DReg(1), VReg(0).d2);
// Convert back to float. // Convert back to float.
e.fcvt(SReg(0), DReg(1)); e.fcvt(SReg(0), DReg(1));
@@ -4397,6 +4405,7 @@ struct DOT_PRODUCT_4_V128
e.L(not_inf); e.L(not_inf);
// Splat result to all 4 lanes. // Splat result to all 4 lanes.
e.dup(VReg(d).s4, VReg(0).s4[0]); e.dup(VReg(d).s4, VReg(0).s4[0]);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_DOT_PRODUCT_4, DOT_PRODUCT_4_V128); EMITTER_OPCODE_TABLE(OPCODE_DOT_PRODUCT_4, DOT_PRODUCT_4_V128);
@@ -4668,7 +4677,8 @@ static uint64_t PpcVrsqrtefpHelper(void* raw_context) {
struct RSQRT_V128 : Sequence<RSQRT_V128, I<OPCODE_RSQRT, V128Op, V128Op>> { struct RSQRT_V128 : Sequence<RSQRT_V128, I<OPCODE_RSQRT, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); // No hardware FP emitted — the C++ helper does all math.
// GuestToHostThunk restores FPCR after the native call.
// Save source to stack scratch (survives CallNative). // Save source to stack scratch (survives CallNative).
int src_idx = SrcVReg(e, i.src1, 0); int src_idx = SrcVReg(e, i.src1, 0);
e.str(QReg(src_idx), e.str(QReg(src_idx),
@@ -4731,7 +4741,7 @@ struct RECIP_F64 : Sequence<RECIP_F64, I<OPCODE_RECIP, F64Op, F64Op>> {
}; };
struct RECIP_V128 : Sequence<RECIP_V128, I<OPCODE_RECIP, V128Op, V128Op>> { struct RECIP_V128 : Sequence<RECIP_V128, I<OPCODE_RECIP, V128Op, V128Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
e.ChangeFpcrMode(FPCRMode::Vmx); EmitWithVmxFpcr(e, [&] {
if (i.src1.is_constant) { if (i.src1.is_constant) {
LoadV128Const(e, 1, i.src1.constant()); LoadV128Const(e, 1, i.src1.constant());
} else { } else {
@@ -4746,6 +4756,7 @@ struct RECIP_V128 : Sequence<RECIP_V128, I<OPCODE_RECIP, V128Op, V128Op>> {
e.fdiv(d, VReg(0).s4, VReg(1).s4); e.fdiv(d, VReg(0).s4, VReg(1).s4);
// Flush output denormals. // Flush output denormals.
FlushDenormals_V128(e, i.dest.reg().getIdx(), 0, 1); FlushDenormals_V128(e, i.dest.reg().getIdx(), 0, 1);
});
} }
}; };
EMITTER_OPCODE_TABLE(OPCODE_RECIP, RECIP_F32, RECIP_F64, RECIP_V128); EMITTER_OPCODE_TABLE(OPCODE_RECIP, RECIP_F32, RECIP_F64, RECIP_V128);