From 59f9fb99646a014a71f3598444994e6b2ea1abeb Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:07:18 +0900 Subject: [PATCH] [A64] Optimize FPCR switching with lazy mode tracking Replace per-op save/restore of FPCR in EmitWithVmxFpcr with tracked lazy switching via ChangeFpcrMode. Consecutive VMX float ops in the same basic block now emit a single FPCR switch instead of one per op. ChangeFpcrMode loads pre-computed values from the backend context (fpcr_fpu/fpcr_vmx) instead of MRS + read-modify-write, eliminating an expensive system register read per switch. FPU mode is restored at block boundaries and calls via ForgetFpcrMode. Scalar FP sequences guard with ChangeFpcrMode(Fpu) which is a no-op when already in FPU mode. --- src/xenia/cpu/backend/a64/a64_emitter.cc | 17 +++++++---------- src/xenia/cpu/backend/a64/a64_emitter.h | 7 ++++++- src/xenia/cpu/backend/a64/a64_seq_util.h | 15 ++++++--------- src/xenia/cpu/backend/a64/a64_sequences.cc | 13 +++++++++++++ 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/xenia/cpu/backend/a64/a64_emitter.cc b/src/xenia/cpu/backend/a64/a64_emitter.cc index 83f0f4eae..3e894db93 100644 --- a/src/xenia/cpu/backend/a64/a64_emitter.cc +++ b/src/xenia/cpu/backend/a64/a64_emitter.cc @@ -498,18 +498,15 @@ bool A64Emitter::ChangeFpcrMode(FPCRMode new_mode, bool already_set) { } fpcr_mode_ = new_mode; if (!already_set) { - // Read current FPCR. - mrs(x0, 3, 3, 4, 4, 0); // mrs x0, FPCR + // Load the pre-computed FPCR value from the backend context. + // This avoids an expensive MRS + read-modify-write cycle. + auto bctx = GetBackendCtxReg(); if (new_mode == FPCRMode::Vmx) { - // VMX mode: set FZ (bit 24) for flush-to-zero. - // Do NOT set DN (bit 25) — PPC preserves NaN payloads. - mov(x17, ~static_cast(1u << 25)); - and_(x0, x0, x17); - orr(x0, x0, static_cast(1u << 24)); + ldr(w0, Xbyak_aarch64::ptr(bctx, static_cast(offsetof( + A64BackendContext, fpcr_vmx)))); } else { - // FPU mode: clear FZ and DN for IEEE-compliant behavior. - mov(x17, ~static_cast(3u << 24)); - and_(x0, x0, x17); + ldr(w0, Xbyak_aarch64::ptr(bctx, static_cast(offsetof( + A64BackendContext, fpcr_fpu)))); } msr(3, 3, 4, 4, 0, x0); // msr FPCR, x0 } diff --git a/src/xenia/cpu/backend/a64/a64_emitter.h b/src/xenia/cpu/backend/a64/a64_emitter.h index 5bb223f55..9007aace9 100644 --- a/src/xenia/cpu/backend/a64/a64_emitter.h +++ b/src/xenia/cpu/backend/a64/a64_emitter.h @@ -138,7 +138,12 @@ class A64Emitter : public Xbyak_aarch64::CodeGenerator { static void HandleStackpointOverflowError(ppc::PPCContext* context); - void ForgetFpcrMode() { fpcr_mode_ = FPCRMode::Unknown; } + void ForgetFpcrMode() { + if (fpcr_mode_ == FPCRMode::Vmx) { + ChangeFpcrMode(FPCRMode::Fpu); + } + fpcr_mode_ = FPCRMode::Unknown; + } bool ChangeFpcrMode(FPCRMode new_mode, bool already_set = false); bool IsFeatureEnabled(uint64_t feature_flag) const { return (feature_flags_ & feature_flag) == feature_flag; diff --git a/src/xenia/cpu/backend/a64/a64_seq_util.h b/src/xenia/cpu/backend/a64/a64_seq_util.h index d76849e8d..9b34cd6e2 100644 --- a/src/xenia/cpu/backend/a64/a64_seq_util.h +++ b/src/xenia/cpu/backend/a64/a64_seq_util.h @@ -42,16 +42,13 @@ using Xbyak_aarch64::XReg; template inline void EmitWithVmxFpcr(A64Emitter& e, Fn&& emit_op) { - // VMX vector FP uses its own cached FPCR state in the backend context. Save - // and restore around each VMX op so vector code doesn't leak FPCR changes - // into later scalar instructions. - e.mrs(e.x13, 3, 3, 4, 4, 0); - e.ldr(e.w15, Xbyak_aarch64::ptr(e.GetBackendCtxReg(), - static_cast( - offsetof(A64BackendContext, fpcr_vmx)))); - e.msr(3, 3, 4, 4, 0, e.x15); + // Enter VMX FPCR mode using tracked lazy switching. If the emitter + // is already in VMX mode (e.g. consecutive VMX ops in the same basic + // block) this is a no-op — no system register access at all. + // FPU mode is restored at block boundaries and calls via ForgetFpcrMode, + // or on demand by scalar FP sequences via ChangeFpcrMode(Fpu). + e.ChangeFpcrMode(FPCRMode::Vmx); emit_op(); - e.msr(3, 3, 4, 4, 0, e.x13); } // Try to see if the provided 64-bit value can be compressed into an 8-bit diff --git a/src/xenia/cpu/backend/a64/a64_sequences.cc b/src/xenia/cpu/backend/a64/a64_sequences.cc index c5148b510..83d27d207 100644 --- a/src/xenia/cpu/backend/a64/a64_sequences.cc +++ b/src/xenia/cpu/backend/a64/a64_sequences.cc @@ -488,6 +488,8 @@ enum class FpBinOp { Add, Sub, Mul, Div }; static void EmitFpBinOpWithPpcNan_F32(A64Emitter& e, SReg dest, SReg s1, SReg s2, FpBinOp op) { + // Ensure FPU FPCR (no flush-to-zero) for scalar operations. + e.ChangeFpcrMode(FPCRMode::Fpu); auto& nan_path = e.NewCachedLabel(); auto& done = e.NewCachedLabel(); @@ -537,6 +539,7 @@ static void EmitFpBinOpWithPpcNan_F32(A64Emitter& e, SReg dest, SReg s1, static void EmitFpBinOpWithPpcNan_F64(A64Emitter& e, DReg dest, DReg s1, DReg s2, FpBinOp op) { + e.ChangeFpcrMode(FPCRMode::Fpu); auto& nan_path = e.NewCachedLabel(); auto& done = e.NewCachedLabel(); @@ -592,6 +595,7 @@ static void EmitFpBinOpWithPpcNan_F64(A64Emitter& e, DReg dest, DReg s1, // rule, so NaN inputs are handled entirely in software. static void EmitFmaWithPpcNan_F64(A64Emitter& e, DReg dest, DReg s1, DReg s2, DReg s3, bool is_sub) { + e.ChangeFpcrMode(FPCRMode::Fpu); auto& nan_path = e.NewCachedLabel(); auto& done = e.NewCachedLabel(); @@ -643,6 +647,7 @@ static void EmitFmaWithPpcNan_F64(A64Emitter& e, DReg dest, DReg s1, DReg s2, static void EmitFmaWithPpcNan_F32(A64Emitter& e, SReg dest, SReg s1, SReg s2, SReg s3, bool is_sub) { + e.ChangeFpcrMode(FPCRMode::Fpu); auto& nan_path = e.NewCachedLabel(); auto& done = e.NewCachedLabel(); @@ -3443,6 +3448,7 @@ struct CONVERT_F64_I64 struct CONVERT_F32_F64 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + e.ChangeFpcrMode(FPCRMode::Fpu); if (i.src1.is_constant) { union { double d; @@ -3460,6 +3466,7 @@ struct CONVERT_F32_F64 struct CONVERT_F64_F32 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + e.ChangeFpcrMode(FPCRMode::Fpu); if (i.src1.is_constant) { union { float f; @@ -3579,6 +3586,7 @@ EMITTER_OPCODE_TABLE(OPCODE_ROUND, ROUND_F32, ROUND_F64, ROUND_V128); // ============================================================================ struct SQRT_F32 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + e.ChangeFpcrMode(FPCRMode::Fpu); if (i.src1.is_constant) { union { float f; @@ -3595,6 +3603,7 @@ struct SQRT_F32 : Sequence> { }; struct SQRT_F64 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + e.ChangeFpcrMode(FPCRMode::Fpu); if (i.src1.is_constant) { union { double d; @@ -4492,6 +4501,7 @@ static uint64_t PpcFrsqrte(void* raw_context) { // ============================================================================ struct RSQRT_F32 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + e.ChangeFpcrMode(FPCRMode::Fpu); SReg src = i.src1.is_constant ? e.s0 : SReg(i.src1.reg().getIdx()); if (i.src1.is_constant) { union { @@ -4652,6 +4662,7 @@ EMITTER_OPCODE_TABLE(OPCODE_RSQRT, RSQRT_F32, RSQRT_F64, RSQRT_V128); // ============================================================================ struct RECIP_F32 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + e.ChangeFpcrMode(FPCRMode::Fpu); SReg src = i.src1.is_constant ? e.s0 : SReg(i.src1.reg().getIdx()); if (i.src1.is_constant) { union { @@ -4668,6 +4679,7 @@ struct RECIP_F32 : Sequence> { }; struct RECIP_F64 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + e.ChangeFpcrMode(FPCRMode::Fpu); DReg src = i.src1.is_constant ? e.d0 : DReg(i.src1.reg().getIdx()); if (i.src1.is_constant) { union { @@ -4708,6 +4720,7 @@ EMITTER_OPCODE_TABLE(OPCODE_RECIP, RECIP_F32, RECIP_F64, RECIP_V128); // ============================================================================ struct TOSINGLE : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + e.ChangeFpcrMode(FPCRMode::Fpu); DReg src = i.src1.is_constant ? e.d0 : DReg(i.src1.reg().getIdx()); if (i.src1.is_constant) { union {