[A64] Skip software denormal flushing when FPCR.FZ handles inputs
Depends on hardware support, detected and runtime and controllable via a64_extension_mask bit 1.
This commit is contained in:
@@ -7,6 +7,10 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <cfenv>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#define XBYAK_NO_OP_NAMES
|
||||
@@ -17,6 +21,7 @@ DEFINE_int64(a64_extension_mask, -1LL,
|
||||
"features.\n"
|
||||
" 0 = armv8.0\n"
|
||||
" 1 = Large System Extensions(LSE) atomic operations\n"
|
||||
" 2 = FPCR.FZ flushes denormal inputs (skip software flush)\n"
|
||||
" -1 = Detect and utilize all possible processor features\n",
|
||||
"a64");
|
||||
namespace xe {
|
||||
@@ -43,6 +48,43 @@ void InitFeatureFlags() {
|
||||
Xbyak_aarch64::util::XBYAK_AARCH64_HWCAP_ATOMIC);
|
||||
#undef TEST_EMIT_FEATURE
|
||||
}
|
||||
|
||||
// Detect whether FPCR.FZ flushes denormal float32 inputs to zero.
|
||||
// The ARM spec says input flushing is implementation-defined.
|
||||
// Modern cores (Cortex-A76+, Apple M1+) flush inputs; older ones may not.
|
||||
if ((cvars::a64_extension_mask & kA64FZFlushesInputs) ==
|
||||
kA64FZFlushesInputs) {
|
||||
// Build a denormal float32: smallest positive denormal = 0x00000001.
|
||||
uint32_t denorm_bits = 1;
|
||||
float denorm;
|
||||
std::memcpy(&denorm, &denorm_bits, 4);
|
||||
|
||||
// Save FPCR, enable FZ, add two denormals, check result.
|
||||
uint64_t saved_fpcr;
|
||||
#if XE_COMPILER_MSVC
|
||||
saved_fpcr = _ReadStatusReg(ARM64_FPCR);
|
||||
_WriteStatusReg(ARM64_FPCR, saved_fpcr | (1ULL << 24));
|
||||
#else
|
||||
asm volatile("mrs %0, fpcr" : "=r"(saved_fpcr));
|
||||
uint64_t fz_fpcr = saved_fpcr | (1ULL << 24);
|
||||
asm volatile("msr fpcr, %0" ::"r"(fz_fpcr));
|
||||
#endif
|
||||
|
||||
volatile float a = denorm;
|
||||
volatile float b = denorm;
|
||||
volatile float result = a + b;
|
||||
|
||||
#if XE_COMPILER_MSVC
|
||||
_WriteStatusReg(ARM64_FPCR, saved_fpcr);
|
||||
#else
|
||||
asm volatile("msr fpcr, %0" ::"r"(saved_fpcr));
|
||||
#endif
|
||||
|
||||
if (result == 0.0f) {
|
||||
feature_flags_ |= kA64FZFlushesInputs;
|
||||
}
|
||||
}
|
||||
|
||||
g_feature_flags = feature_flags_;
|
||||
g_did_initialize_feature_flags = true;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace xe {
|
||||
namespace arm64 {
|
||||
enum A64FeatureFlags : uint64_t {
|
||||
kA64EmitLSE = 1 << 0,
|
||||
|
||||
kA64FZFlushesInputs = 1 << 1,
|
||||
};
|
||||
|
||||
XE_NOALIAS
|
||||
|
||||
@@ -363,8 +363,11 @@ inline void PrepareVmxFpSources(A64Emitter& e, const T1& op1, const T2& op2,
|
||||
// Copy to scratch v0/v1 so we don't modify live allocated registers.
|
||||
if (s1 != 0) e.mov(VReg(0).b16, VReg(s1).b16);
|
||||
if (s2 != 1) e.mov(VReg(1).b16, VReg(s2).b16);
|
||||
// Flush denormal inputs in software only if FPCR.FZ doesn't handle it.
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 0);
|
||||
FlushDenormals_V128(e, 1);
|
||||
}
|
||||
out_s1 = 0;
|
||||
out_s2 = 1;
|
||||
}
|
||||
@@ -547,8 +550,12 @@ inline void EmitVmxFpBinOp_V128(A64Emitter& e, int dest_idx, const T1& src1,
|
||||
// PPC NaN propagation fixup (fast-path skip when no NaN).
|
||||
FixupVmxNan_V128(e);
|
||||
|
||||
// Flush output denormals.
|
||||
// Flush output denormals. FPCR.FZ guarantees output flushing per the
|
||||
// ARM spec, so skip when FZ is known to also handle inputs (implying
|
||||
// the core fully supports FZ denormal handling).
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 2, 0, 1);
|
||||
}
|
||||
|
||||
// Move to dest.
|
||||
e.mov(VReg(dest_idx).b16, VReg(2).b16);
|
||||
|
||||
@@ -415,7 +415,9 @@ struct VECTOR_MAX
|
||||
e.fmax(VReg(2).s4, VReg(s1).s4, VReg(s2).s4);
|
||||
}
|
||||
FixupVmxMaxMinNan(e);
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 2, 0, 1);
|
||||
}
|
||||
e.mov(VReg(i.dest.reg().getIdx()).b16, VReg(2).b16);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3162,7 +3162,9 @@ struct MAX_V128 : Sequence<MAX_V128, I<OPCODE_MAX, V128Op, V128Op, V128Op>> {
|
||||
e.fmax(VReg(2).s4, VReg(s1).s4, VReg(s2).s4);
|
||||
// PPC vmaxfp: if either input is NaN, result = src1 (vA).
|
||||
FixupVmxMaxMinNan(e);
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 2, 0, 1);
|
||||
}
|
||||
e.mov(VReg(i.dest.reg().getIdx()).b16, VReg(2).b16);
|
||||
});
|
||||
}
|
||||
@@ -3299,7 +3301,9 @@ struct MIN_V128 : Sequence<MIN_V128, I<OPCODE_MIN, V128Op, V128Op, V128Op>> {
|
||||
e.fmin(VReg(2).s4, VReg(s1).s4, VReg(s2).s4);
|
||||
// PPC vminfp: if either input is NaN, result = src1 (vA).
|
||||
FixupVmxMaxMinNan(e);
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 2, 0, 1);
|
||||
}
|
||||
e.mov(VReg(i.dest.reg().getIdx()).b16, VReg(2).b16);
|
||||
});
|
||||
}
|
||||
@@ -3993,7 +3997,9 @@ struct MUL_ADD_V128
|
||||
// Flush s3 → v3, save to stack slot 2.
|
||||
int s3 = SrcVReg(e, i.src3, 3);
|
||||
if (s3 != 3) e.mov(VReg(3).b16, VReg(s3).b16);
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 3, 0, 1);
|
||||
}
|
||||
e.str(QReg(3),
|
||||
Xbyak_aarch64::ptr(
|
||||
e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH) + 32));
|
||||
@@ -4017,7 +4023,9 @@ struct MUL_ADD_V128
|
||||
FixupVmxNan_V128_Fma(e);
|
||||
|
||||
// Flush output denormals.
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 2, 0, 1);
|
||||
}
|
||||
e.mov(VReg(d).b16, VReg(2).b16);
|
||||
});
|
||||
}
|
||||
@@ -4077,7 +4085,9 @@ struct MUL_SUB_V128
|
||||
// Flush s3 → v3, save un-negated for NaN fixup.
|
||||
int s3 = SrcVReg(e, i.src3, 3);
|
||||
if (s3 != 3) e.mov(VReg(3).b16, VReg(s3).b16);
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 3, 0, 1);
|
||||
}
|
||||
e.str(QReg(3),
|
||||
Xbyak_aarch64::ptr(
|
||||
e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH) + 32));
|
||||
@@ -4102,7 +4112,9 @@ struct MUL_SUB_V128
|
||||
FixupVmxNan_V128_Fma(e);
|
||||
|
||||
// Flush output denormals.
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 2, 0, 1);
|
||||
}
|
||||
e.mov(VReg(d).b16, VReg(2).b16);
|
||||
});
|
||||
}
|
||||
@@ -4643,13 +4655,17 @@ struct RECIP_V128 : Sequence<RECIP_V128, I<OPCODE_RECIP, V128Op, V128Op>> {
|
||||
e.mov(VReg(1).b16, VReg(i.src1.reg().getIdx()).b16);
|
||||
}
|
||||
// Flush input denormals.
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, 1); // scratch v2, v3
|
||||
}
|
||||
auto d = VReg(i.dest.reg().getIdx()).s4;
|
||||
// Load 1.0f vector.
|
||||
e.fmov(VReg(0).s4, 1.0f);
|
||||
e.fdiv(d, VReg(0).s4, VReg(1).s4);
|
||||
// Flush output denormals.
|
||||
if (!e.IsFeatureEnabled(xe::arm64::kA64FZFlushesInputs)) {
|
||||
FlushDenormals_V128(e, i.dest.reg().getIdx(), 0, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user