[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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user