diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index 868c66a50..ceff37570 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -492,6 +492,8 @@ bool EmulatorApp::OnInitialize() { #if XE_ARCH_AMD64 == 1 amd64::InitFeatureFlags(); +#elif XE_ARCH_ARM64 == 1 + arm64::InitFeatureFlags(); #endif std::filesystem::path content_root = cvars::content_root; diff --git a/src/xenia/base/platform.h b/src/xenia/base/platform.h index 7525f3419..3aea30fb3 100644 --- a/src/xenia/base/platform.h +++ b/src/xenia/base/platform.h @@ -186,5 +186,7 @@ constexpr char kGuestPathSeparator = '\\'; } // namespace xe #if XE_ARCH_AMD64 == 1 #include "platform_amd64.h" +#elif XE_ARCH_ARM64 == 1 +#include "platform_arm64.h" #endif #endif // XENIA_BASE_PLATFORM_H_ diff --git a/src/xenia/base/platform_arm64.cc b/src/xenia/base/platform_arm64.cc new file mode 100644 index 000000000..825b334ca --- /dev/null +++ b/src/xenia/base/platform_arm64.cc @@ -0,0 +1,50 @@ +/** + ****************************************************************************** + * 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 "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" + " -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 + } + g_feature_flags = feature_flags_; + g_did_initialize_feature_flags = true; +} +} // namespace arm64 +} // namespace xe diff --git a/src/xenia/base/platform_arm64.h b/src/xenia/base/platform_arm64.h new file mode 100644 index 000000000..b9e521e52 --- /dev/null +++ b/src/xenia/base/platform_arm64.h @@ -0,0 +1,29 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#ifndef XENIA_BASE_PLATFORM_ARM64_H_ +#define XENIA_BASE_PLATFORM_ARM64_H_ +#include + +namespace xe { +namespace arm64 { +enum A64FeatureFlags : uint64_t { + kA64EmitLSE = 1 << 0, + +}; + +XE_NOALIAS +uint64_t GetFeatureFlags(); +XE_COLD +void InitFeatureFlags(); + +} // namespace arm64 +} // namespace xe + +#endif // XENIA_BASE_PLATFORM_ARM64_H_ diff --git a/src/xenia/cpu/backend/a64/a64_emitter.cc b/src/xenia/cpu/backend/a64/a64_emitter.cc index a84a32351..f61e4c36f 100644 --- a/src/xenia/cpu/backend/a64/a64_emitter.cc +++ b/src/xenia/cpu/backend/a64/a64_emitter.cc @@ -75,7 +75,8 @@ A64Emitter::A64Emitter(A64Backend* backend, XbyakA64Allocator* allocator) processor_(backend->processor()), backend_(backend), code_cache_(backend->code_cache()), - allocator_(allocator) {} + allocator_(allocator), + feature_flags_(arm64::GetFeatureFlags()) {} A64Emitter::~A64Emitter() = default; diff --git a/src/xenia/cpu/backend/a64/a64_emitter.h b/src/xenia/cpu/backend/a64/a64_emitter.h index 714456bfc..5bb223f55 100644 --- a/src/xenia/cpu/backend/a64/a64_emitter.h +++ b/src/xenia/cpu/backend/a64/a64_emitter.h @@ -36,7 +36,7 @@ namespace xe { namespace cpu { namespace backend { namespace a64 { - +using namespace arm64; class A64Backend; class A64CodeCache; @@ -140,6 +140,9 @@ class A64Emitter : public Xbyak_aarch64::CodeGenerator { void ForgetFpcrMode() { 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; + } Xbyak_aarch64::Label& AddToTail(TailEmitCallback callback, uint32_t alignment = 0); @@ -161,6 +164,7 @@ class A64Emitter : public Xbyak_aarch64::CodeGenerator { A64CodeCache* code_cache_ = nullptr; XbyakA64Allocator* allocator_ = nullptr; XexModule* guest_module_ = nullptr; + uint64_t feature_flags_ = 0; uint32_t current_guest_function_ = 0; Xbyak_aarch64::Label* epilog_label_ = nullptr; diff --git a/src/xenia/cpu/backend/a64/a64_seq_memory.cc b/src/xenia/cpu/backend/a64/a64_seq_memory.cc index 32c94521a..e5424bd99 100644 --- a/src/xenia/cpu/backend/a64/a64_seq_memory.cc +++ b/src/xenia/cpu/backend/a64/a64_seq_memory.cc @@ -907,6 +907,12 @@ struct ATOMIC_EXCHANGE_I8 } else { e.and_(e.w0, i.src2, 0xFF); } + + if (e.IsFeatureEnabled(kA64EmitLSE)) { + e.swpalb(i.dest, e.w0, ptr(e.x4)); + return; + } + auto& retry = e.NewCachedLabel(); e.L(retry); e.ldaxrb(e.w1, ptr(e.x4)); @@ -930,6 +936,12 @@ struct ATOMIC_EXCHANGE_I16 } else { e.and_(e.w0, i.src2, 0xFFFF); } + + if (e.IsFeatureEnabled(kA64EmitLSE)) { + e.swpalh(i.dest, e.w0, ptr(e.x4)); + return; + } + auto& retry = e.NewCachedLabel(); e.L(retry); e.ldaxrh(e.w1, ptr(e.x4)); @@ -954,6 +966,12 @@ struct ATOMIC_EXCHANGE_I32 } else { e.mov(e.w0, i.src2); } + + if (e.IsFeatureEnabled(kA64EmitLSE)) { + e.swpal(i.dest, e.w0, ptr(e.x4)); + return; + } + auto& retry = e.NewCachedLabel(); e.L(retry); e.ldaxr(e.w1, ptr(e.x4)); @@ -976,6 +994,12 @@ struct ATOMIC_EXCHANGE_I64 } else { e.mov(e.x0, i.src2); } + + if (e.IsFeatureEnabled(kA64EmitLSE)) { + e.swpal(i.dest, e.x0, ptr(e.x4)); + return; + } + auto& retry = e.NewCachedLabel(); e.L(retry); e.ldaxr(e.x1, ptr(e.x4)); @@ -1011,6 +1035,15 @@ struct ATOMIC_COMPARE_EXCHANGE_I32 } else { e.mov(e.w6, i.src3); } + + if (e.IsFeatureEnabled(kA64EmitLSE)) { + e.mov(e.w0, e.w5); + e.casal(e.w5, e.w6, ptr(e.x4)); + e.cmp(e.w5, e.w0); + e.cset(i.dest, Xbyak_aarch64::EQ); + return; + } + auto& retry = e.NewCachedLabel(); auto& fail = e.NewCachedLabel(); auto& done = e.NewCachedLabel(); @@ -1044,6 +1077,15 @@ struct ATOMIC_COMPARE_EXCHANGE_I64 } else { e.mov(e.x6, i.src3); } + + if (e.IsFeatureEnabled(kA64EmitLSE)) { + e.mov(e.x0, e.x5); + e.casal(e.x5, e.x6, ptr(e.x4)); + e.cmp(e.x5, e.x0); + e.cset(i.dest, Xbyak_aarch64::EQ); + return; + } + auto& retry = e.NewCachedLabel(); auto& fail = e.NewCachedLabel(); auto& done = e.NewCachedLabel(); @@ -1201,6 +1243,15 @@ struct RESERVED_STORE_I32 } // Compute host address. e.add(e.x4, e.GetMembaseReg(), addr); + + if (e.IsFeatureEnabled(kA64EmitLSE)) { + e.mov(e.w0, e.w5); + e.casal(e.w5, e.w6, ptr(e.x4)); + e.cmp(e.w5, e.w0); + e.cset(i.dest, Xbyak_aarch64::EQ); + return; + } + // LDXR/STXR loop. auto& cas_loop = e.NewCachedLabel(); auto& cas_fail = e.NewCachedLabel(); @@ -1249,6 +1300,15 @@ struct RESERVED_STORE_I64 e.mov(e.x6, XReg(i.src2.reg().getIdx())); } e.add(e.x4, e.GetMembaseReg(), addr); + + if (e.IsFeatureEnabled(kA64EmitLSE)) { + e.mov(e.x0, e.x5); + e.casal(e.x5, e.x6, ptr(e.x4)); + e.cmp(e.x5, e.x0); + e.cset(i.dest, Xbyak_aarch64::EQ); + return; + } + auto& cas_loop = e.NewCachedLabel(); auto& cas_fail = e.NewCachedLabel(); e.L(cas_loop);