Depends on hardware support, detected and runtime and controllable via a64_extension_mask bit 1.
93 lines
3.1 KiB
C++
93 lines
3.1 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2026 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <cfenv>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
#include "xenia/base/cvar.h"
|
|
#include "xenia/base/platform.h"
|
|
#define XBYAK_NO_OP_NAMES
|
|
#include "third_party/xbyak_aarch64/xbyak_aarch64/xbyak_aarch64.h"
|
|
#include "third_party/xbyak_aarch64/xbyak_aarch64/xbyak_aarch64_util.h"
|
|
DEFINE_int64(a64_extension_mask, -1LL,
|
|
"Allow the detection and utilization of specific instruction set "
|
|
"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 {
|
|
namespace arm64 {
|
|
static uint64_t g_feature_flags = 0U;
|
|
static bool g_did_initialize_feature_flags = false;
|
|
uint64_t GetFeatureFlags() {
|
|
if (!g_did_initialize_feature_flags) {
|
|
InitFeatureFlags();
|
|
}
|
|
return g_feature_flags;
|
|
}
|
|
XE_COLD
|
|
XE_NOINLINE
|
|
void InitFeatureFlags() {
|
|
uint64_t feature_flags_ = 0U;
|
|
{
|
|
Xbyak_aarch64::util::Cpu cpu_;
|
|
#define TEST_EMIT_FEATURE(emit, ext) \
|
|
if ((cvars::a64_extension_mask & emit) == emit) { \
|
|
feature_flags_ |= (cpu_.has(ext) ? emit : 0); \
|
|
}
|
|
TEST_EMIT_FEATURE(kA64EmitLSE,
|
|
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;
|
|
}
|
|
} // namespace arm64
|
|
} // namespace xe
|