[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.
This commit is contained in:
Herman S.
2026-03-29 18:07:18 +09:00
parent 904c6c8b1c
commit 59f9fb9964
4 changed files with 32 additions and 20 deletions

View File

@@ -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<uint64_t>(1u << 25));
and_(x0, x0, x17);
orr(x0, x0, static_cast<uint64_t>(1u << 24));
ldr(w0, Xbyak_aarch64::ptr(bctx, static_cast<uint32_t>(offsetof(
A64BackendContext, fpcr_vmx))));
} else {
// FPU mode: clear FZ and DN for IEEE-compliant behavior.
mov(x17, ~static_cast<uint64_t>(3u << 24));
and_(x0, x0, x17);
ldr(w0, Xbyak_aarch64::ptr(bctx, static_cast<uint32_t>(offsetof(
A64BackendContext, fpcr_fpu))));
}
msr(3, 3, 4, 4, 0, x0); // msr FPCR, x0
}

View File

@@ -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;

View File

@@ -42,16 +42,13 @@ using Xbyak_aarch64::XReg;
template <typename Fn>
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<uint32_t>(
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

View File

@@ -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<CONVERT_F32_F64, I<OPCODE_CONVERT, F32Op, F64Op>> {
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<CONVERT_F64_F32, I<OPCODE_CONVERT, F64Op, F32Op>> {
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<SQRT_F32, I<OPCODE_SQRT, F32Op, F32Op>> {
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<SQRT_F32, I<OPCODE_SQRT, F32Op, F32Op>> {
};
struct SQRT_F64 : Sequence<SQRT_F64, I<OPCODE_SQRT, F64Op, F64Op>> {
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<RSQRT_F32, I<OPCODE_RSQRT, F32Op, F32Op>> {
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<RECIP_F32, I<OPCODE_RECIP, F32Op, F32Op>> {
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<RECIP_F32, I<OPCODE_RECIP, F32Op, F32Op>> {
};
struct RECIP_F64 : Sequence<RECIP_F64, I<OPCODE_RECIP, F64Op, F64Op>> {
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<TOSINGLE, I<OPCODE_TO_SINGLE, F64Op, F64Op>> {
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 {