[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;
|
fpcr_mode_ = new_mode;
|
||||||
if (!already_set) {
|
if (!already_set) {
|
||||||
// Read current FPCR.
|
// Load the pre-computed FPCR value from the backend context.
|
||||||
mrs(x0, 3, 3, 4, 4, 0); // mrs x0, FPCR
|
// This avoids an expensive MRS + read-modify-write cycle.
|
||||||
|
auto bctx = GetBackendCtxReg();
|
||||||
if (new_mode == FPCRMode::Vmx) {
|
if (new_mode == FPCRMode::Vmx) {
|
||||||
// VMX mode: set FZ (bit 24) for flush-to-zero.
|
ldr(w0, Xbyak_aarch64::ptr(bctx, static_cast<uint32_t>(offsetof(
|
||||||
// Do NOT set DN (bit 25) — PPC preserves NaN payloads.
|
A64BackendContext, fpcr_vmx))));
|
||||||
mov(x17, ~static_cast<uint64_t>(1u << 25));
|
|
||||||
and_(x0, x0, x17);
|
|
||||||
orr(x0, x0, static_cast<uint64_t>(1u << 24));
|
|
||||||
} else {
|
} else {
|
||||||
// FPU mode: clear FZ and DN for IEEE-compliant behavior.
|
ldr(w0, Xbyak_aarch64::ptr(bctx, static_cast<uint32_t>(offsetof(
|
||||||
mov(x17, ~static_cast<uint64_t>(3u << 24));
|
A64BackendContext, fpcr_fpu))));
|
||||||
and_(x0, x0, x17);
|
|
||||||
}
|
}
|
||||||
msr(3, 3, 4, 4, 0, x0); // msr FPCR, x0
|
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);
|
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 ChangeFpcrMode(FPCRMode new_mode, bool already_set = false);
|
||||||
bool IsFeatureEnabled(uint64_t feature_flag) const {
|
bool IsFeatureEnabled(uint64_t feature_flag) const {
|
||||||
return (feature_flags_ & feature_flag) == feature_flag;
|
return (feature_flags_ & feature_flag) == feature_flag;
|
||||||
|
|||||||
@@ -42,16 +42,13 @@ using Xbyak_aarch64::XReg;
|
|||||||
|
|
||||||
template <typename Fn>
|
template <typename Fn>
|
||||||
inline void EmitWithVmxFpcr(A64Emitter& e, Fn&& emit_op) {
|
inline void EmitWithVmxFpcr(A64Emitter& e, Fn&& emit_op) {
|
||||||
// VMX vector FP uses its own cached FPCR state in the backend context. Save
|
// Enter VMX FPCR mode using tracked lazy switching. If the emitter
|
||||||
// and restore around each VMX op so vector code doesn't leak FPCR changes
|
// is already in VMX mode (e.g. consecutive VMX ops in the same basic
|
||||||
// into later scalar instructions.
|
// block) this is a no-op — no system register access at all.
|
||||||
e.mrs(e.x13, 3, 3, 4, 4, 0);
|
// FPU mode is restored at block boundaries and calls via ForgetFpcrMode,
|
||||||
e.ldr(e.w15, Xbyak_aarch64::ptr(e.GetBackendCtxReg(),
|
// or on demand by scalar FP sequences via ChangeFpcrMode(Fpu).
|
||||||
static_cast<uint32_t>(
|
e.ChangeFpcrMode(FPCRMode::Vmx);
|
||||||
offsetof(A64BackendContext, fpcr_vmx))));
|
|
||||||
e.msr(3, 3, 4, 4, 0, e.x15);
|
|
||||||
emit_op();
|
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
|
// 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,
|
static void EmitFpBinOpWithPpcNan_F32(A64Emitter& e, SReg dest, SReg s1,
|
||||||
SReg s2, FpBinOp op) {
|
SReg s2, FpBinOp op) {
|
||||||
|
// Ensure FPU FPCR (no flush-to-zero) for scalar operations.
|
||||||
|
e.ChangeFpcrMode(FPCRMode::Fpu);
|
||||||
auto& nan_path = e.NewCachedLabel();
|
auto& nan_path = e.NewCachedLabel();
|
||||||
auto& done = 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,
|
static void EmitFpBinOpWithPpcNan_F64(A64Emitter& e, DReg dest, DReg s1,
|
||||||
DReg s2, FpBinOp op) {
|
DReg s2, FpBinOp op) {
|
||||||
|
e.ChangeFpcrMode(FPCRMode::Fpu);
|
||||||
auto& nan_path = e.NewCachedLabel();
|
auto& nan_path = e.NewCachedLabel();
|
||||||
auto& done = 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.
|
// rule, so NaN inputs are handled entirely in software.
|
||||||
static void EmitFmaWithPpcNan_F64(A64Emitter& e, DReg dest, DReg s1, DReg s2,
|
static void EmitFmaWithPpcNan_F64(A64Emitter& e, DReg dest, DReg s1, DReg s2,
|
||||||
DReg s3, bool is_sub) {
|
DReg s3, bool is_sub) {
|
||||||
|
e.ChangeFpcrMode(FPCRMode::Fpu);
|
||||||
auto& nan_path = e.NewCachedLabel();
|
auto& nan_path = e.NewCachedLabel();
|
||||||
auto& done = 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,
|
static void EmitFmaWithPpcNan_F32(A64Emitter& e, SReg dest, SReg s1, SReg s2,
|
||||||
SReg s3, bool is_sub) {
|
SReg s3, bool is_sub) {
|
||||||
|
e.ChangeFpcrMode(FPCRMode::Fpu);
|
||||||
auto& nan_path = e.NewCachedLabel();
|
auto& nan_path = e.NewCachedLabel();
|
||||||
auto& done = e.NewCachedLabel();
|
auto& done = e.NewCachedLabel();
|
||||||
|
|
||||||
@@ -3443,6 +3448,7 @@ struct CONVERT_F64_I64
|
|||||||
struct CONVERT_F32_F64
|
struct CONVERT_F32_F64
|
||||||
: Sequence<CONVERT_F32_F64, I<OPCODE_CONVERT, F32Op, F64Op>> {
|
: Sequence<CONVERT_F32_F64, I<OPCODE_CONVERT, F32Op, F64Op>> {
|
||||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||||
|
e.ChangeFpcrMode(FPCRMode::Fpu);
|
||||||
if (i.src1.is_constant) {
|
if (i.src1.is_constant) {
|
||||||
union {
|
union {
|
||||||
double d;
|
double d;
|
||||||
@@ -3460,6 +3466,7 @@ struct CONVERT_F32_F64
|
|||||||
struct CONVERT_F64_F32
|
struct CONVERT_F64_F32
|
||||||
: Sequence<CONVERT_F64_F32, I<OPCODE_CONVERT, F64Op, F32Op>> {
|
: Sequence<CONVERT_F64_F32, I<OPCODE_CONVERT, F64Op, F32Op>> {
|
||||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||||
|
e.ChangeFpcrMode(FPCRMode::Fpu);
|
||||||
if (i.src1.is_constant) {
|
if (i.src1.is_constant) {
|
||||||
union {
|
union {
|
||||||
float f;
|
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>> {
|
struct SQRT_F32 : Sequence<SQRT_F32, I<OPCODE_SQRT, F32Op, F32Op>> {
|
||||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||||
|
e.ChangeFpcrMode(FPCRMode::Fpu);
|
||||||
if (i.src1.is_constant) {
|
if (i.src1.is_constant) {
|
||||||
union {
|
union {
|
||||||
float f;
|
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>> {
|
struct SQRT_F64 : Sequence<SQRT_F64, I<OPCODE_SQRT, F64Op, F64Op>> {
|
||||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||||
|
e.ChangeFpcrMode(FPCRMode::Fpu);
|
||||||
if (i.src1.is_constant) {
|
if (i.src1.is_constant) {
|
||||||
union {
|
union {
|
||||||
double d;
|
double d;
|
||||||
@@ -4492,6 +4501,7 @@ static uint64_t PpcFrsqrte(void* raw_context) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
struct RSQRT_F32 : Sequence<RSQRT_F32, I<OPCODE_RSQRT, F32Op, F32Op>> {
|
struct RSQRT_F32 : Sequence<RSQRT_F32, I<OPCODE_RSQRT, F32Op, F32Op>> {
|
||||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
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());
|
SReg src = i.src1.is_constant ? e.s0 : SReg(i.src1.reg().getIdx());
|
||||||
if (i.src1.is_constant) {
|
if (i.src1.is_constant) {
|
||||||
union {
|
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>> {
|
struct RECIP_F32 : Sequence<RECIP_F32, I<OPCODE_RECIP, F32Op, F32Op>> {
|
||||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
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());
|
SReg src = i.src1.is_constant ? e.s0 : SReg(i.src1.reg().getIdx());
|
||||||
if (i.src1.is_constant) {
|
if (i.src1.is_constant) {
|
||||||
union {
|
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>> {
|
struct RECIP_F64 : Sequence<RECIP_F64, I<OPCODE_RECIP, F64Op, F64Op>> {
|
||||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
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());
|
DReg src = i.src1.is_constant ? e.d0 : DReg(i.src1.reg().getIdx());
|
||||||
if (i.src1.is_constant) {
|
if (i.src1.is_constant) {
|
||||||
union {
|
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>> {
|
struct TOSINGLE : Sequence<TOSINGLE, I<OPCODE_TO_SINGLE, F64Op, F64Op>> {
|
||||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
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());
|
DReg src = i.src1.is_constant ? e.d0 : DReg(i.src1.reg().getIdx());
|
||||||
if (i.src1.is_constant) {
|
if (i.src1.is_constant) {
|
||||||
union {
|
union {
|
||||||
|
|||||||
Reference in New Issue
Block a user