mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[A64] Update ARM64 backend from xenia-mac delta
- sync sequences/emitter/code cache/tracers from macOS integration - add NEON helpers, denorm flush, SET_NJM, and half-float fixes
This commit is contained in:
@@ -34,7 +34,7 @@ using xe::cpu::hir::HIRBuilder;
|
||||
|
||||
A64Assembler::A64Assembler(A64Backend* backend)
|
||||
: Assembler(backend), a64_backend_(backend), capstone_handle_(0) {
|
||||
if (cs_open(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone_handle_) !=
|
||||
if (cs_open(CS_ARCH_AARCH64, CS_MODE_LITTLE_ENDIAN, &capstone_handle_) !=
|
||||
CS_ERR_OK) {
|
||||
assert_always("Failed to initialize capstone");
|
||||
}
|
||||
|
||||
@@ -70,15 +70,12 @@ class A64ThunkEmitter : public A64Emitter {
|
||||
};
|
||||
|
||||
A64Backend::A64Backend() : Backend(), code_cache_(nullptr) {
|
||||
cs_err err = cs_open(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone_handle_);
|
||||
cs_err err =
|
||||
cs_open(CS_ARCH_AARCH64, CS_MODE_LITTLE_ENDIAN, &capstone_handle_);
|
||||
if (err) {
|
||||
printf("Failed on cs_open() with error returned: %u\n", err);
|
||||
assert_always("Failed to initialize capstone");
|
||||
}
|
||||
// if (cs_open(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone_handle_) !=
|
||||
// CS_ERR_OK) {
|
||||
// assert_always("Failed to initialize capstone");
|
||||
// }
|
||||
cs_option(capstone_handle_, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL);
|
||||
cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_ON);
|
||||
cs_option(capstone_handle_, CS_OPT_SKIPDATA, CS_OPT_OFF);
|
||||
@@ -160,7 +157,7 @@ std::unique_ptr<GuestFunction> A64Backend::CreateGuestFunction(
|
||||
return std::make_unique<A64Function>(module, address);
|
||||
}
|
||||
|
||||
uint64_t ReadCapstoneReg(HostThreadContext* context, arm64_reg reg) {
|
||||
uint64_t ReadCapstoneReg(HostThreadContext* context, aarch64_reg reg) {
|
||||
switch (reg) {
|
||||
case ARM64_REG_X0:
|
||||
return context->x[0];
|
||||
@@ -300,37 +297,37 @@ bool TestCapstonePstate(arm64_cc cond, uint32_t pstate) {
|
||||
const bool C = !!(pstate & 0x20000000);
|
||||
const bool V = !!(pstate & 0x10000000);
|
||||
switch (cond) {
|
||||
case ARM64_CC_EQ:
|
||||
case ARM64CC_EQ:
|
||||
return (Z == true);
|
||||
case ARM64_CC_NE:
|
||||
case ARM64CC_NE:
|
||||
return (Z == false);
|
||||
case ARM64_CC_HS:
|
||||
case ARM64CC_HS:
|
||||
return (C == true);
|
||||
case ARM64_CC_LO:
|
||||
case ARM64CC_LO:
|
||||
return (C == false);
|
||||
case ARM64_CC_MI:
|
||||
case ARM64CC_MI:
|
||||
return (N == true);
|
||||
case ARM64_CC_PL:
|
||||
case ARM64CC_PL:
|
||||
return (N == false);
|
||||
case ARM64_CC_VS:
|
||||
case ARM64CC_VS:
|
||||
return (V == true);
|
||||
case ARM64_CC_VC:
|
||||
case ARM64CC_VC:
|
||||
return (V == false);
|
||||
case ARM64_CC_HI:
|
||||
case ARM64CC_HI:
|
||||
return ((C == true) && (Z == false));
|
||||
case ARM64_CC_LS:
|
||||
case ARM64CC_LS:
|
||||
return ((C == false) || (Z == true));
|
||||
case ARM64_CC_GE:
|
||||
case ARM64CC_GE:
|
||||
return (N == V);
|
||||
case ARM64_CC_LT:
|
||||
case ARM64CC_LT:
|
||||
return (N != V);
|
||||
case ARM64_CC_GT:
|
||||
case ARM64CC_GT:
|
||||
return ((Z == false) && (N == V));
|
||||
case ARM64_CC_LE:
|
||||
case ARM64CC_LE:
|
||||
return ((Z == true) || (N != V));
|
||||
case ARM64_CC_AL:
|
||||
case ARM64CC_AL:
|
||||
return true;
|
||||
case ARM64_CC_NV:
|
||||
case ARM64CC_NV:
|
||||
return false;
|
||||
default:
|
||||
assert_unhandled_case(cond);
|
||||
@@ -348,14 +345,14 @@ uint64_t A64Backend::CalculateNextHostInstruction(ThreadDebugInfo* thread_info,
|
||||
insn.detail = &all_detail;
|
||||
cs_disasm_iter(capstone_handle_, &machine_code_ptr,
|
||||
&remaining_machine_code_size, &host_address, &insn);
|
||||
const auto& detail = all_detail.arm64;
|
||||
const auto& detail = all_detail.aarch64;
|
||||
switch (insn.id) {
|
||||
case ARM64_INS_B:
|
||||
case ARM64_INS_BL: {
|
||||
assert_true(detail.operands[0].type == ARM64_OP_IMM);
|
||||
const int64_t pc_offset = static_cast<int64_t>(detail.operands[0].imm);
|
||||
const bool test_passed =
|
||||
TestCapstonePstate(detail.cc, thread_info->host_context.cpsr);
|
||||
TestCapstonePstate(detail.cc, thread_info->host_context.pstate);
|
||||
if (test_passed) {
|
||||
return current_pc + pc_offset;
|
||||
} else {
|
||||
@@ -451,39 +448,6 @@ bool A64Backend::ExceptionCallbackThunk(Exception* ex, void* data) {
|
||||
}
|
||||
|
||||
bool A64Backend::ExceptionCallback(Exception* ex) {
|
||||
if (ex->code() == Exception::Code::kAccessViolation) {
|
||||
const uint64_t host_pc = ex->pc();
|
||||
const uint64_t fault_address = ex->fault_address();
|
||||
uint64_t guest_pc = 0;
|
||||
uint32_t host_offset = 0;
|
||||
auto function = code_cache_->LookupFunction(host_pc);
|
||||
if (function && function->machine_code()) {
|
||||
const uint64_t function_pc =
|
||||
reinterpret_cast<uint64_t>(function->machine_code());
|
||||
host_offset = static_cast<uint32_t>(host_pc - function_pc);
|
||||
if (const auto* entry = function->LookupMachineCodeOffset(host_offset)) {
|
||||
guest_pc = entry->guest_address;
|
||||
}
|
||||
}
|
||||
#if XE_ARCH_ARM64
|
||||
auto* thread_context = ex->thread_context();
|
||||
XELOGE(
|
||||
"A64 AV: host_pc=0x{:016X} guest_pc=0x{:08X} host_off=0x{:X} "
|
||||
"fault=0x{:016X} op={} x21=0x{:016X} x27=0x{:016X} x28=0x{:016X}",
|
||||
host_pc, guest_pc, host_offset, fault_address,
|
||||
static_cast<int>(ex->access_violation_operation()),
|
||||
thread_context ? thread_context->x[21] : 0,
|
||||
thread_context ? thread_context->x[27] : 0,
|
||||
thread_context ? thread_context->x[28] : 0);
|
||||
#else
|
||||
XELOGE(
|
||||
"A64 AV: host_pc=0x{:016X} guest_pc=0x{:08X} host_off=0x{:X} "
|
||||
"fault=0x{:016X} op={}",
|
||||
host_pc, guest_pc, host_offset, fault_address,
|
||||
static_cast<int>(ex->access_violation_operation()));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
if (ex->code() != Exception::Code::kIllegalInstruction) {
|
||||
// We only care about illegal instructions. Other things will be handled by
|
||||
// other handlers (probably). If nothing else picks it up we'll be called
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include "xenia/cpu/backend/a64/a64_code_cache.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
@@ -20,7 +19,6 @@
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
@@ -28,11 +26,6 @@
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/module.h"
|
||||
|
||||
DEFINE_bool(a64_indirection_table_log, false,
|
||||
"Log A64 indirection table mapping and updates.", "CPU");
|
||||
DEFINE_int32(a64_indirection_table_log_limit, 32,
|
||||
"Maximum number of A64 indirection table log entries.", "CPU");
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
@@ -40,23 +33,6 @@ namespace a64 {
|
||||
|
||||
using namespace xe::literals;
|
||||
|
||||
namespace {
|
||||
|
||||
bool ShouldLogIndirectionTable() {
|
||||
if (!cvars::a64_indirection_table_log) {
|
||||
return false;
|
||||
}
|
||||
const int32_t limit = cvars::a64_indirection_table_log_limit;
|
||||
if (limit <= 0) {
|
||||
return false;
|
||||
}
|
||||
static std::atomic<int32_t> log_count{0};
|
||||
const int32_t count = log_count.fetch_add(1, std::memory_order_relaxed);
|
||||
return count < limit;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Define static constants for linking
|
||||
const size_t A64CodeCache::kIndirectionTableSize;
|
||||
#if XE_A64_INDIRECTION_64BIT
|
||||
@@ -132,16 +108,17 @@ bool A64CodeCache::Initialize() {
|
||||
xe::memory::AllocationType::kReserve,
|
||||
xe::memory::PageAccess::kReadWrite));
|
||||
if (!indirection_table_base_) {
|
||||
XELOGW("Preferred indirection table base unavailable; falling back");
|
||||
indirection_table_base_ = reinterpret_cast<uint8_t*>(xe::memory::AllocFixed(
|
||||
nullptr, kIndirectionTableSize, xe::memory::AllocationType::kReserve,
|
||||
xe::memory::PageAccess::kReadWrite));
|
||||
}
|
||||
if (!indirection_table_base_) {
|
||||
XELOGE("Unable to allocate code cache indirection table");
|
||||
XELOGE("Tried preferred range {:X}-{:X} with fallback to OS-chosen",
|
||||
static_cast<uint64_t>(kIndirectionTableBase),
|
||||
kIndirectionTableBase + kIndirectionTableSize);
|
||||
XELOGE(
|
||||
"This is likely because the {:X}-{:X} range is in use by some other "
|
||||
"system DLL",
|
||||
static_cast<uint64_t>(kIndirectionTableBase),
|
||||
kIndirectionTableBase + kIndirectionTableSize);
|
||||
return false;
|
||||
}
|
||||
indirection_table_actual_base_ =
|
||||
@@ -153,18 +130,8 @@ bool A64CodeCache::Initialize() {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (ShouldLogIndirectionTable()) {
|
||||
XELOGI(
|
||||
"A64 indirection table: guest_base=0x{:08X} table_base=0x{:016X} "
|
||||
"size=0x{:X} entry_bytes={}",
|
||||
static_cast<uint32_t>(kIndirectionTableBase),
|
||||
static_cast<uint64_t>(indirection_table_actual_base_),
|
||||
static_cast<uint32_t>(kIndirectionTableSize),
|
||||
static_cast<uint32_t>(kIndirectionEntrySize));
|
||||
}
|
||||
|
||||
// Create mmap file. This allows us to share the code cache with the debugger.
|
||||
file_name_ = fmt::format("xenia_code_cache");
|
||||
file_name_ = fmt::format("xenia_code_cache_{}", Clock::QueryHostTickCount());
|
||||
mapping_ = xe::memory::CreateFileMappingHandle(
|
||||
file_name_, kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite,
|
||||
false);
|
||||
@@ -193,9 +160,6 @@ bool A64CodeCache::Initialize() {
|
||||
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
|
||||
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite, 0));
|
||||
if (!generated_code_execute_base_) {
|
||||
XELOGW(
|
||||
"Fixed address mapping for generated code failed, trying OS-chosen "
|
||||
"address");
|
||||
generated_code_execute_base_ =
|
||||
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
|
||||
mapping_, nullptr, kGeneratedCodeSize,
|
||||
@@ -231,9 +195,6 @@ bool A64CodeCache::Initialize() {
|
||||
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
|
||||
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadOnly, 0));
|
||||
if (!generated_code_execute_base_) {
|
||||
XELOGW(
|
||||
"Fixed address mapping for execute code failed, trying OS-chosen "
|
||||
"address");
|
||||
generated_code_execute_base_ = reinterpret_cast<uint8_t*>(
|
||||
xe::memory::MapFileView(mapping_, nullptr, kGeneratedCodeSize,
|
||||
xe::memory::PageAccess::kExecuteReadOnly, 0));
|
||||
@@ -243,9 +204,6 @@ bool A64CodeCache::Initialize() {
|
||||
mapping_, reinterpret_cast<void*>(kGeneratedCodeWriteBase),
|
||||
kGeneratedCodeSize, xe::memory::PageAccess::kReadWrite, 0));
|
||||
if (!generated_code_write_base_) {
|
||||
XELOGW(
|
||||
"Fixed address mapping for write code failed, trying OS-chosen "
|
||||
"address");
|
||||
generated_code_write_base_ = reinterpret_cast<uint8_t*>(
|
||||
xe::memory::MapFileView(mapping_, nullptr, kGeneratedCodeSize,
|
||||
xe::memory::PageAccess::kReadWrite, 0));
|
||||
@@ -309,51 +267,27 @@ void A64CodeCache::AddIndirection64(uint32_t guest_address,
|
||||
}
|
||||
|
||||
if (guest_address < kIndirectionTableBase) {
|
||||
XELOGE(
|
||||
"A64CodeCache::AddIndirection64: guest_address 0x{:08X} below base "
|
||||
"0x{:08X}",
|
||||
guest_address, static_cast<uint32_t>(kIndirectionTableBase));
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t guest_delta = guest_address - kIndirectionTableBase;
|
||||
if (guest_delta & 0x3) {
|
||||
XELOGW(
|
||||
"A64CodeCache::AddIndirection64: guest_address 0x{:08X} not 4-byte "
|
||||
"aligned (delta=0x{:X})",
|
||||
guest_address, guest_delta);
|
||||
}
|
||||
|
||||
// Calculate offset from the logical base (0x80000000), not from actual table
|
||||
// address.
|
||||
const uint64_t guest_offset = (guest_delta >> 2) * kIndirectionEntrySize;
|
||||
if (guest_offset + kIndirectionEntrySize > kIndirectionTableSize) {
|
||||
XELOGE(
|
||||
"A64CodeCache::AddIndirection64: guest_address 0x{:08X} offset 0x{:X} "
|
||||
"exceeds table size 0x{:X}",
|
||||
guest_address, guest_offset,
|
||||
static_cast<uint32_t>(kIndirectionTableSize));
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t* indirection_slot =
|
||||
reinterpret_cast<uint64_t*>(indirection_table_base_ + guest_offset);
|
||||
*indirection_slot = host_address;
|
||||
|
||||
if (ShouldLogIndirectionTable()) {
|
||||
XELOGI(
|
||||
"A64 indirection add: guest=0x{:08X} delta=0x{:X} offset=0x{:X} "
|
||||
"slot=0x{:016X} host=0x{:016X}",
|
||||
guest_address, guest_delta, guest_offset,
|
||||
reinterpret_cast<uint64_t>(indirection_slot), host_address);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void A64CodeCache::CommitExecutableRange(uint32_t guest_low,
|
||||
uint32_t guest_high) {
|
||||
if (!indirection_table_base_) {
|
||||
XELOGE("CommitExecutableRange: indirection_table_base_ is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -364,10 +298,6 @@ void A64CodeCache::CommitExecutableRange(uint32_t guest_low,
|
||||
|
||||
// Calculate offsets from the guest address base, not the table base
|
||||
if (guest_low < kGuestAddressBase) {
|
||||
XELOGE(
|
||||
"CommitExecutableRange: guest_low 0x{:08X} is below guest base "
|
||||
"0x{:08X}",
|
||||
guest_low, kGuestAddressBase);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -377,10 +307,6 @@ void A64CodeCache::CommitExecutableRange(uint32_t guest_low,
|
||||
|
||||
// Sanity check bounds; the table should fully cover the XEX guest range now.
|
||||
if (start_offset + size > kIndirectionTableSize) {
|
||||
XELOGE(
|
||||
"CommitExecutableRange: range [0x{:08X}, 0x{:08X}) exceeds table (size "
|
||||
"0x{:X})",
|
||||
guest_low, guest_high, (unsigned)kIndirectionTableSize);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -391,14 +317,6 @@ void A64CodeCache::CommitExecutableRange(uint32_t guest_low,
|
||||
for (uint32_t i = 0; i < entry_count; i++) {
|
||||
p[i] = indirection_default_value_;
|
||||
}
|
||||
|
||||
if (ShouldLogIndirectionTable()) {
|
||||
XELOGI(
|
||||
"A64 indirection commit: guest=[0x{:08X},0x{:08X}) "
|
||||
"offset=0x{:X} size=0x{:X} entries={} base=0x{:016X}",
|
||||
guest_low, guest_high, start_offset, size, entry_count,
|
||||
static_cast<uint64_t>(indirection_table_actual_base_));
|
||||
}
|
||||
#else
|
||||
// Other platforms: use 32-bit entries
|
||||
uint32_t start_offset = (guest_low - kIndirectionTableBase);
|
||||
@@ -536,20 +454,10 @@ void A64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
||||
|
||||
// Calculate offset from the logical guest base (0x80000000)
|
||||
if (guest_address < kIndirectionTableBase) {
|
||||
XELOGE(
|
||||
"A64CodeCache::PlaceGuestCode: ERROR - guest_address 0x{:08X} is "
|
||||
"below logical base 0x{:08X}!",
|
||||
guest_address, static_cast<uint32_t>(kIndirectionTableBase));
|
||||
return;
|
||||
}
|
||||
|
||||
uintptr_t guest_diff = guest_address - kIndirectionTableBase;
|
||||
if (guest_diff & 0x3) {
|
||||
XELOGW(
|
||||
"A64CodeCache::PlaceGuestCode: guest_address 0x{:08X} not 4-byte "
|
||||
"aligned (delta=0x{:X})",
|
||||
guest_address, guest_diff);
|
||||
}
|
||||
uintptr_t guest_offset =
|
||||
(guest_diff >> 2) * kIndirectionEntrySize; // 8-byte entries
|
||||
uintptr_t slot_address =
|
||||
@@ -560,23 +468,10 @@ void A64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
||||
uintptr_t table_end = reinterpret_cast<uintptr_t>(indirection_table_base_) +
|
||||
kIndirectionTableSize;
|
||||
if (slot_address >= table_end) {
|
||||
XELOGE(
|
||||
"A64CodeCache::PlaceGuestCode: slot 0x{:016X} beyond table end "
|
||||
"0x{:016X}",
|
||||
slot_address, table_end);
|
||||
return;
|
||||
}
|
||||
|
||||
*indirection_slot = reinterpret_cast<uint64_t>(code_execute_address);
|
||||
|
||||
if (ShouldLogIndirectionTable()) {
|
||||
XELOGI(
|
||||
"A64 indirection place: guest=0x{:08X} diff=0x{:X} offset=0x{:X} "
|
||||
"slot=0x{:016X} host=0x{:016X}",
|
||||
guest_address, guest_diff, guest_offset, slot_address,
|
||||
static_cast<uint64_t>(
|
||||
reinterpret_cast<uintptr_t>(code_execute_address)));
|
||||
}
|
||||
#else
|
||||
uint32_t* indirection_slot = reinterpret_cast<uint32_t*>(
|
||||
indirection_table_base_ + (guest_address - kIndirectionTableBase));
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
@@ -138,21 +137,6 @@ void PosixA64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code,
|
||||
// Store in the reserved slot
|
||||
unwind_table_[unwind_reservation.table_slot] = unwind_info;
|
||||
|
||||
// Validate address alignment before cache flushing
|
||||
if ((uintptr_t)code_execute_address % 4 != 0) {
|
||||
XELOGW(
|
||||
"PosixA64CodeCache::PlaceCode: WARNING - code address 0x{:016X} is not "
|
||||
"4-byte aligned",
|
||||
(uintptr_t)code_execute_address);
|
||||
}
|
||||
|
||||
if (func_info.code_size.total % 4 != 0) {
|
||||
XELOGW(
|
||||
"PosixA64CodeCache::PlaceCode: WARNING - code size {} is not 4-byte "
|
||||
"aligned",
|
||||
func_info.code_size.total);
|
||||
}
|
||||
|
||||
// Flush instruction cache
|
||||
#ifdef XE_PLATFORM_MAC
|
||||
// On macOS, use sys_icache_invalidate
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -115,6 +115,7 @@ enum VConst {
|
||||
VQNaN,
|
||||
VInt127,
|
||||
V2To32,
|
||||
VSingleDenormalMask,
|
||||
};
|
||||
|
||||
enum A64EmitterFeatureFlags {
|
||||
@@ -174,6 +175,13 @@ class A64Emitter : public oaknut::VectorCodeGenerator {
|
||||
oaknut::Label* lookup_label(const char* label_name) {
|
||||
return &label_lookup_[label_name];
|
||||
}
|
||||
oaknut::Label* lookup_label(hir::Label* label) {
|
||||
assert_not_null(label);
|
||||
if (label->name) {
|
||||
return &label_lookup_[label->name];
|
||||
}
|
||||
return &label_lookup_[label->GetIdString()];
|
||||
}
|
||||
|
||||
oaknut::Label& epilog_label() { return *epilog_label_; }
|
||||
|
||||
|
||||
@@ -419,7 +419,7 @@ EMITTER_OPCODE_TABLE(OPCODE_SET_RETURN_ADDRESS, SET_RETURN_ADDRESS);
|
||||
// ============================================================================
|
||||
struct BRANCH : Sequence<BRANCH, I<OPCODE_BRANCH, VoidOp, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src1.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src1.value);
|
||||
assert_not_null(label);
|
||||
e.B(*label);
|
||||
}
|
||||
@@ -432,7 +432,7 @@ EMITTER_OPCODE_TABLE(OPCODE_BRANCH, BRANCH);
|
||||
struct BRANCH_TRUE_I8
|
||||
: Sequence<BRANCH_TRUE_I8, I<OPCODE_BRANCH_TRUE, VoidOp, I8Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.CBNZ(i.src1, *label);
|
||||
}
|
||||
@@ -440,7 +440,7 @@ struct BRANCH_TRUE_I8
|
||||
struct BRANCH_TRUE_I16
|
||||
: Sequence<BRANCH_TRUE_I16, I<OPCODE_BRANCH_TRUE, VoidOp, I16Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.CBNZ(i.src1, *label);
|
||||
}
|
||||
@@ -448,7 +448,7 @@ struct BRANCH_TRUE_I16
|
||||
struct BRANCH_TRUE_I32
|
||||
: Sequence<BRANCH_TRUE_I32, I<OPCODE_BRANCH_TRUE, VoidOp, I32Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.CBNZ(i.src1, *label);
|
||||
}
|
||||
@@ -456,7 +456,7 @@ struct BRANCH_TRUE_I32
|
||||
struct BRANCH_TRUE_I64
|
||||
: Sequence<BRANCH_TRUE_I64, I<OPCODE_BRANCH_TRUE, VoidOp, I64Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.CBNZ(i.src1, *label);
|
||||
}
|
||||
@@ -464,7 +464,7 @@ struct BRANCH_TRUE_I64
|
||||
struct BRANCH_TRUE_F32
|
||||
: Sequence<BRANCH_TRUE_F32, I<OPCODE_BRANCH_TRUE, VoidOp, F32Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.FCMP(i.src1, 0);
|
||||
e.B(Cond::NE, *label);
|
||||
@@ -473,7 +473,7 @@ struct BRANCH_TRUE_F32
|
||||
struct BRANCH_TRUE_F64
|
||||
: Sequence<BRANCH_TRUE_F64, I<OPCODE_BRANCH_TRUE, VoidOp, F64Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.FCMP(i.src1, 0);
|
||||
e.B(Cond::NE, *label);
|
||||
@@ -489,7 +489,7 @@ EMITTER_OPCODE_TABLE(OPCODE_BRANCH_TRUE, BRANCH_TRUE_I8, BRANCH_TRUE_I16,
|
||||
struct BRANCH_FALSE_I8
|
||||
: Sequence<BRANCH_FALSE_I8, I<OPCODE_BRANCH_FALSE, VoidOp, I8Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.CBZ(i.src1, *label);
|
||||
}
|
||||
@@ -498,7 +498,7 @@ struct BRANCH_FALSE_I16
|
||||
: Sequence<BRANCH_FALSE_I16,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, I16Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.CBZ(i.src1, *label);
|
||||
}
|
||||
@@ -507,7 +507,7 @@ struct BRANCH_FALSE_I32
|
||||
: Sequence<BRANCH_FALSE_I32,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, I32Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.CBZ(i.src1, *label);
|
||||
}
|
||||
@@ -516,7 +516,7 @@ struct BRANCH_FALSE_I64
|
||||
: Sequence<BRANCH_FALSE_I64,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, I64Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.CBZ(i.src1, *label);
|
||||
}
|
||||
@@ -525,7 +525,7 @@ struct BRANCH_FALSE_F32
|
||||
: Sequence<BRANCH_FALSE_F32,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, F32Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.FCMP(i.src1, 0);
|
||||
e.B(Cond::EQ, *label);
|
||||
@@ -535,7 +535,7 @@ struct BRANCH_FALSE_F64
|
||||
: Sequence<BRANCH_FALSE_F64,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, F64Op, LabelOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value->name);
|
||||
oaknut::Label* label = e.lookup_label(i.src2.value);
|
||||
assert_not_null(label);
|
||||
e.FCMP(i.src1, 0);
|
||||
e.B(Cond::EQ, *label);
|
||||
|
||||
@@ -23,6 +23,11 @@ namespace a64 {
|
||||
|
||||
volatile int anchor_memory = 0;
|
||||
|
||||
// vec128b stores bytes in reversed 32-bit chunks; use reversed args for 0..15.
|
||||
static const vec128_t kStvlShuffle =
|
||||
vec128b(3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12);
|
||||
static const vec128_t kStvrSwapMask = vec128b(static_cast<uint8_t>(0x83));
|
||||
|
||||
template <typename T>
|
||||
XReg ComputeMemoryAddressOffset(A64Emitter& e, const T& guest, const T& offset,
|
||||
WReg address_register = W3) {
|
||||
@@ -176,6 +181,132 @@ EMITTER_OPCODE_TABLE(OPCODE_ATOMIC_EXCHANGE, ATOMIC_EXCHANGE_I8,
|
||||
ATOMIC_EXCHANGE_I16, ATOMIC_EXCHANGE_I32,
|
||||
ATOMIC_EXCHANGE_I64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_LVL/LVR/STVL/STVR
|
||||
// ============================================================================
|
||||
struct LVL_V128 : Sequence<LVL_V128, I<OPCODE_LVL, V128Op, I64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
const XReg address = ComputeMemoryAddress(e, i.src1, W4);
|
||||
e.AND(W0, address.toW(), 0xF);
|
||||
e.SUB(X1, address, X0);
|
||||
|
||||
e.LDR(Q2, X1);
|
||||
|
||||
e.MOV(X2, e.GetVConstPtr());
|
||||
e.LDR(Q0, X2, e.GetVConstOffset(VByteSwapMask));
|
||||
e.DUP(Q1.B16(), W0);
|
||||
e.ADD(Q0.B16(), Q0.B16(), Q1.B16());
|
||||
e.TBL(i.dest.reg().B16(), List{Q2.B16()}, Q0.B16());
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_LVL, LVL_V128);
|
||||
|
||||
struct LVR_V128 : Sequence<LVR_V128, I<OPCODE_LVR, V128Op, I64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
const XReg address = ComputeMemoryAddress(e, i.src1, W4);
|
||||
e.AND(W0, address.toW(), 0xF);
|
||||
e.EOR(i.dest.reg().B16(), i.dest.reg().B16(), i.dest.reg().B16());
|
||||
|
||||
oaknut::Label done;
|
||||
e.CBZ(W0, done);
|
||||
|
||||
e.SUB(X1, address, X0);
|
||||
e.LDR(Q2, X1);
|
||||
|
||||
e.MOV(X2, e.GetVConstPtr());
|
||||
e.LDR(Q0, X2, e.GetVConstOffset(VByteSwapMask));
|
||||
e.DUP(Q1.B16(), W0);
|
||||
e.ADD(Q0.B16(), Q0.B16(), Q1.B16());
|
||||
|
||||
e.MOVI(Q1.B16(), 0x10);
|
||||
e.CMHS(Q3.B16(), Q0.B16(), Q1.B16());
|
||||
e.SUB(Q0.B16(), Q0.B16(), Q1.B16());
|
||||
e.MOVI(Q1.B16(), 0x80);
|
||||
e.BSL(Q3.B16(), Q0.B16(), Q1.B16());
|
||||
|
||||
e.TBL(i.dest.reg().B16(), List{Q2.B16()}, Q3.B16());
|
||||
e.l(done);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_LVR, LVR_V128);
|
||||
|
||||
struct STVL_V128 : Sequence<STVL_V128, I<OPCODE_STVL, VoidOp, I64Op, V128Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
const XReg address = ComputeMemoryAddress(e, i.src1, W4);
|
||||
e.AND(W0, address.toW(), 0xF);
|
||||
e.SUB(X1, address, X0);
|
||||
|
||||
e.LDR(Q2, X1);
|
||||
|
||||
e.MOV(X2, reinterpret_cast<uintptr_t>(&kStvlShuffle));
|
||||
e.LDR(Q0, X2);
|
||||
e.DUP(Q1.B16(), W0);
|
||||
e.SUB(Q0.B16(), Q0.B16(), Q1.B16());
|
||||
|
||||
e.MOV(X2, e.GetVConstPtr());
|
||||
e.LDR(Q1, X2, e.GetVConstOffset(VSwapWordMask));
|
||||
e.EOR(Q0.B16(), Q0.B16(), Q1.B16());
|
||||
|
||||
const QReg shuffled = Q3;
|
||||
if (i.src2.is_constant) {
|
||||
e.LoadConstantV(shuffled, i.src2.constant());
|
||||
} else {
|
||||
e.MOV(shuffled.B16(), i.src2.reg().B16());
|
||||
}
|
||||
e.TBL(shuffled.B16(), List{shuffled.B16()}, Q0.B16());
|
||||
|
||||
e.MOVI(Q1.B16(), 0x80);
|
||||
e.CMHS(Q1.B16(), Q0.B16(), Q1.B16());
|
||||
e.BSL(Q1.B16(), Q2.B16(), shuffled.B16());
|
||||
e.STR(Q1, X1);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_STVL, STVL_V128);
|
||||
|
||||
struct STVR_V128 : Sequence<STVR_V128, I<OPCODE_STVR, VoidOp, I64Op, V128Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
const XReg address = ComputeMemoryAddress(e, i.src1, W4);
|
||||
e.AND(W0, address.toW(), 0xF);
|
||||
|
||||
oaknut::Label done;
|
||||
e.CBZ(W0, done);
|
||||
|
||||
e.SUB(X1, address, X0);
|
||||
e.LDR(Q2, X1);
|
||||
|
||||
e.MOV(X2, reinterpret_cast<uintptr_t>(&kStvlShuffle));
|
||||
e.LDR(Q0, X2);
|
||||
e.DUP(Q1.B16(), W0);
|
||||
e.SUB(Q0.B16(), Q0.B16(), Q1.B16());
|
||||
|
||||
e.MOV(X2, reinterpret_cast<uintptr_t>(&kStvrSwapMask));
|
||||
e.LDR(Q1, X2);
|
||||
e.EOR(Q0.B16(), Q0.B16(), Q1.B16());
|
||||
|
||||
e.MOVI(Q1.B16(), 0x0F);
|
||||
e.AND(Q1.B16(), Q0.B16(), Q1.B16());
|
||||
e.MOVI(Q3.B16(), 0x80);
|
||||
e.AND(Q3.B16(), Q0.B16(), Q3.B16());
|
||||
e.ORR(Q1.B16(), Q1.B16(), Q3.B16());
|
||||
|
||||
const QReg shuffled = Q3;
|
||||
if (i.src2.is_constant) {
|
||||
e.LoadConstantV(shuffled, i.src2.constant());
|
||||
} else {
|
||||
e.MOV(shuffled.B16(), i.src2.reg().B16());
|
||||
}
|
||||
e.TBL(shuffled.B16(), List{shuffled.B16()}, Q1.B16());
|
||||
|
||||
e.MOVI(Q1.B16(), 0x80);
|
||||
e.CMHS(Q1.B16(), Q0.B16(), Q1.B16());
|
||||
e.BSL(Q1.B16(), Q2.B16(), shuffled.B16());
|
||||
e.STR(Q1, X1);
|
||||
|
||||
e.l(done);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_STVR, STVR_V128);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_ATOMIC_COMPARE_EXCHANGE
|
||||
// ============================================================================
|
||||
@@ -282,6 +413,59 @@ struct ATOMIC_COMPARE_EXCHANGE_I64
|
||||
EMITTER_OPCODE_TABLE(OPCODE_ATOMIC_COMPARE_EXCHANGE,
|
||||
ATOMIC_COMPARE_EXCHANGE_I32, ATOMIC_COMPARE_EXCHANGE_I64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_RESERVED_LOAD / OPCODE_RESERVED_STORE
|
||||
// ============================================================================
|
||||
struct RESERVED_LOAD_INT32
|
||||
: Sequence<RESERVED_LOAD_INT32, I<OPCODE_RESERVED_LOAD, I32Op, I64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
const XReg address = ComputeMemoryAddress(e, i.src1, W3);
|
||||
e.LDAXR(i.dest, address);
|
||||
}
|
||||
};
|
||||
struct RESERVED_LOAD_INT64
|
||||
: Sequence<RESERVED_LOAD_INT64, I<OPCODE_RESERVED_LOAD, I64Op, I64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
const XReg address = ComputeMemoryAddress(e, i.src1, W3);
|
||||
e.LDAXR(i.dest, address);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_RESERVED_LOAD, RESERVED_LOAD_INT32,
|
||||
RESERVED_LOAD_INT64);
|
||||
|
||||
struct RESERVED_STORE_INT32
|
||||
: Sequence<RESERVED_STORE_INT32,
|
||||
I<OPCODE_RESERVED_STORE, I8Op, I64Op, I32Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
const XReg address = ComputeMemoryAddress(e, i.src1, W3);
|
||||
const WReg value = i.src2.is_constant ? W4 : i.src2;
|
||||
if (i.src2.is_constant) {
|
||||
e.MOV(value, static_cast<uint32_t>(i.src2.constant()));
|
||||
}
|
||||
e.STLXR(W0, value, address);
|
||||
e.CMP(W0, 0);
|
||||
e.CSET(i.dest, Cond::EQ);
|
||||
}
|
||||
};
|
||||
|
||||
struct RESERVED_STORE_INT64
|
||||
: Sequence<RESERVED_STORE_INT64,
|
||||
I<OPCODE_RESERVED_STORE, I8Op, I64Op, I64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
const XReg address = ComputeMemoryAddress(e, i.src1, W3);
|
||||
const XReg value = i.src2.is_constant ? X4 : i.src2;
|
||||
if (i.src2.is_constant) {
|
||||
e.MOV(value, i.src2.constant());
|
||||
}
|
||||
e.STLXR(W0, value, address);
|
||||
e.CMP(W0, 0);
|
||||
e.CSET(i.dest, Cond::EQ);
|
||||
}
|
||||
};
|
||||
|
||||
EMITTER_OPCODE_TABLE(OPCODE_RESERVED_STORE, RESERVED_STORE_INT32,
|
||||
RESERVED_STORE_INT64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_LOAD_LOCAL
|
||||
// ============================================================================
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
#include "xenia/cpu/backend/a64/a64_sequences.h"
|
||||
|
||||
#include <arm_neon.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
@@ -63,6 +64,26 @@ struct VECTOR_CONVERT_F2I
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_VECTOR_CONVERT_F2I, VECTOR_CONVERT_F2I);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_VECTOR_DENORMFLUSH
|
||||
// ============================================================================
|
||||
struct VECTOR_DENORMFLUSH
|
||||
: Sequence<VECTOR_DENORMFLUSH,
|
||||
I<OPCODE_VECTOR_DENORMFLUSH, V128Op, V128Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
// Clear denormals to signed zero, preserving sign bits.
|
||||
e.MOV(X2, e.GetVConstPtr());
|
||||
e.LDR(Q0, X2, e.GetVConstOffset(VSingleDenormalMask));
|
||||
e.AND(Q0.B16(), i.src1.reg().B16(), Q0.B16());
|
||||
e.CMEQ(Q0.S4(), Q0.S4(), 0);
|
||||
e.BIC(Q1.B16(), i.src1.reg().B16(), Q0.B16());
|
||||
e.LDR(Q2, X2, e.GetVConstOffset(VSignMaskF32));
|
||||
e.AND(Q2.B16(), i.src1.reg().B16(), Q2.B16());
|
||||
e.ORR(i.dest.reg().B16(), Q1.B16(), Q2.B16());
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_VECTOR_DENORMFLUSH, VECTOR_DENORMFLUSH);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_LOAD_VECTOR_SHL
|
||||
// ============================================================================
|
||||
@@ -1985,7 +2006,7 @@ struct UNPACK : Sequence<UNPACK, I<OPCODE_UNPACK, V128Op, V128Op>> {
|
||||
} else if (h == 0xFFFF) {
|
||||
b[i] = -131008.0f; // Special negative sentinel (0xC7FFE000)
|
||||
} else {
|
||||
b[i] = half_float::detail::half2float(h);
|
||||
b[i] = half_float::detail::half2float<float>(h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2105,7 +2126,7 @@ struct UNPACK : Sequence<UNPACK, I<OPCODE_UNPACK, V128Op, V128Op>> {
|
||||
vld1q_u8(reinterpret_cast<const uint8_t*>(src1)));
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
b[i] = half_float::detail::half2float(a[VEC128_W(4 + i)]);
|
||||
b[i] = half_float::detail::half2float<float>(a[VEC128_W(4 + i)]);
|
||||
}
|
||||
|
||||
// Store the float array into a uint8x16_t NEON register
|
||||
@@ -2414,6 +2435,31 @@ struct UNPACK : Sequence<UNPACK, I<OPCODE_UNPACK, V128Op, V128Op>> {
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_UNPACK, UNPACK);
|
||||
|
||||
namespace {
|
||||
thread_local bool a64_njm_enabled = true;
|
||||
|
||||
uint64_t SetNJMForwarder(void* raw_context, uint64_t value) {
|
||||
(void)raw_context;
|
||||
a64_njm_enabled = value != 0;
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_SET_NJM
|
||||
// ============================================================================
|
||||
struct SET_NJM_I8 : Sequence<SET_NJM_I8, I<OPCODE_SET_NJM, VoidOp, I8Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
if (i.src1.is_constant) {
|
||||
e.CallNative(SetNJMForwarder, static_cast<uint64_t>(i.src1.constant()));
|
||||
return;
|
||||
}
|
||||
e.UXTB(W1, i.src1);
|
||||
e.CallNativeSafe(reinterpret_cast<void*>(SetNJMForwarder));
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_SET_NJM, SET_NJM_I8);
|
||||
|
||||
} // namespace a64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
#include "xenia/cpu/backend/a64/a64_sequences.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
@@ -53,7 +51,6 @@ using namespace xe::cpu::hir;
|
||||
using xe::cpu::hir::Instr;
|
||||
|
||||
typedef bool (*SequenceSelectFn)(A64Emitter&, const Instr*);
|
||||
// std::unordered_map<uint32_t, SequenceSelectFn> sequence_table; Removed
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_COMMENT
|
||||
@@ -355,6 +352,22 @@ EMITTER_OPCODE_TABLE(OPCODE_CONVERT, CONVERT_I32_F32, CONVERT_I32_F64,
|
||||
CONVERT_I64_F64, CONVERT_F32_I32, CONVERT_F32_F64,
|
||||
CONVERT_F64_I64, CONVERT_F64_F32);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_TO_SINGLE
|
||||
// ============================================================================
|
||||
struct TOSINGLE_F64_F64
|
||||
: Sequence<TOSINGLE_F64_F64, I<OPCODE_TO_SINGLE, F64Op, F64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
const DReg src = i.src1.is_constant ? D1 : i.src1;
|
||||
if (i.src1.is_constant) {
|
||||
e.LoadConstantV(src.toQ(), i.src1.constant());
|
||||
}
|
||||
e.FCVT(S0, src);
|
||||
e.FCVT(i.dest, S0);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_TO_SINGLE, TOSINGLE_F64_F64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_ROUND
|
||||
// ============================================================================
|
||||
@@ -727,107 +740,6 @@ EMITTER_OPCODE_TABLE(OPCODE_SELECT, SELECT_I8, SELECT_I16, SELECT_I32,
|
||||
SELECT_I64, SELECT_F32, SELECT_F64, SELECT_V128_I8,
|
||||
SELECT_V128_V128);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_IS_TRUE
|
||||
// ============================================================================
|
||||
struct IS_TRUE_I8 : Sequence<IS_TRUE_I8, I<OPCODE_IS_TRUE, I8Op, I8Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.CMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::NE);
|
||||
}
|
||||
};
|
||||
struct IS_TRUE_I16 : Sequence<IS_TRUE_I16, I<OPCODE_IS_TRUE, I8Op, I16Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.CMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::NE);
|
||||
}
|
||||
};
|
||||
struct IS_TRUE_I32 : Sequence<IS_TRUE_I32, I<OPCODE_IS_TRUE, I8Op, I32Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.CMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::NE);
|
||||
}
|
||||
};
|
||||
struct IS_TRUE_I64 : Sequence<IS_TRUE_I64, I<OPCODE_IS_TRUE, I8Op, I64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.CMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::NE);
|
||||
}
|
||||
};
|
||||
struct IS_TRUE_F32 : Sequence<IS_TRUE_F32, I<OPCODE_IS_TRUE, I8Op, F32Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.FCMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::NE);
|
||||
}
|
||||
};
|
||||
struct IS_TRUE_F64 : Sequence<IS_TRUE_F64, I<OPCODE_IS_TRUE, I8Op, F64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.FCMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::NE);
|
||||
}
|
||||
};
|
||||
struct IS_TRUE_V128 : Sequence<IS_TRUE_V128, I<OPCODE_IS_TRUE, I8Op, V128Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.UMAXV(Q0.toS(), i.src1.reg().S4());
|
||||
e.MOV(W0, Q0.Selem()[0]);
|
||||
e.CMP(W0, 0);
|
||||
e.CSET(i.dest, Cond::NE);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_IS_TRUE, IS_TRUE_I8, IS_TRUE_I16, IS_TRUE_I32,
|
||||
IS_TRUE_I64, IS_TRUE_F32, IS_TRUE_F64, IS_TRUE_V128);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_IS_FALSE
|
||||
// ============================================================================
|
||||
struct IS_FALSE_I8 : Sequence<IS_FALSE_I8, I<OPCODE_IS_FALSE, I8Op, I8Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.CMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::EQ);
|
||||
}
|
||||
};
|
||||
struct IS_FALSE_I16 : Sequence<IS_FALSE_I16, I<OPCODE_IS_FALSE, I8Op, I16Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.CMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::EQ);
|
||||
}
|
||||
};
|
||||
struct IS_FALSE_I32 : Sequence<IS_FALSE_I32, I<OPCODE_IS_FALSE, I8Op, I32Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.CMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::EQ);
|
||||
}
|
||||
};
|
||||
struct IS_FALSE_I64 : Sequence<IS_FALSE_I64, I<OPCODE_IS_FALSE, I8Op, I64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.CMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::EQ);
|
||||
}
|
||||
};
|
||||
struct IS_FALSE_F32 : Sequence<IS_FALSE_F32, I<OPCODE_IS_FALSE, I8Op, F32Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.FCMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::EQ);
|
||||
}
|
||||
};
|
||||
struct IS_FALSE_F64 : Sequence<IS_FALSE_F64, I<OPCODE_IS_FALSE, I8Op, F64Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.FCMP(i.src1.reg(), 0);
|
||||
e.CSET(i.dest, Cond::EQ);
|
||||
}
|
||||
};
|
||||
struct IS_FALSE_V128
|
||||
: Sequence<IS_FALSE_V128, I<OPCODE_IS_FALSE, I8Op, V128Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.UMAXV(Q0.toS(), i.src1.reg().S4());
|
||||
e.MOV(W0, Q0.Selem()[0]);
|
||||
e.CMP(W0, 0);
|
||||
e.CSET(i.dest, Cond::EQ);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_IS_FALSE, IS_FALSE_I8, IS_FALSE_I16, IS_FALSE_I32,
|
||||
IS_FALSE_I64, IS_FALSE_F32, IS_FALSE_F64, IS_FALSE_V128);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_IS_NAN
|
||||
// ============================================================================
|
||||
@@ -2116,15 +2028,17 @@ EMITTER_OPCODE_TABLE(OPCODE_LOG2, LOG2_F32, LOG2_F64, LOG2_V128);
|
||||
// ============================================================================
|
||||
struct DOT_PRODUCT_3_V128
|
||||
: Sequence<DOT_PRODUCT_3_V128,
|
||||
I<OPCODE_DOT_PRODUCT_3, F32Op, V128Op, V128Op>> {
|
||||
I<OPCODE_DOT_PRODUCT_3, V128Op, V128Op, V128Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
// https://msdn.microsoft.com/en-us/library/bb514054(v=vs.90).aspx
|
||||
EmitCommutativeBinaryVOp(
|
||||
e, i, [](A64Emitter& e, SReg dest, QReg src1, QReg src2) {
|
||||
e.FMUL(dest.toQ().S4(), src1.S4(), src2.S4());
|
||||
e.MOV(dest.toQ().Selem()[3], WZR);
|
||||
e.FADDP(dest.toQ().S4(), dest.toQ().S4(), dest.toQ().S4());
|
||||
e.FADDP(dest.toS(), dest.toD().S2());
|
||||
e, i, [](A64Emitter& e, QReg dest, QReg src1, QReg src2) {
|
||||
e.FMUL(dest.S4(), src1.S4(), src2.S4());
|
||||
e.MOV(dest.Selem()[3], WZR);
|
||||
e.FADDP(dest.S4(), dest.S4(), dest.S4());
|
||||
e.FADDP(S0, dest.toD().S2());
|
||||
e.FMOV(W0, S0);
|
||||
e.DUP(dest.S4(), W0);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -2135,14 +2049,16 @@ EMITTER_OPCODE_TABLE(OPCODE_DOT_PRODUCT_3, DOT_PRODUCT_3_V128);
|
||||
// ============================================================================
|
||||
struct DOT_PRODUCT_4_V128
|
||||
: Sequence<DOT_PRODUCT_4_V128,
|
||||
I<OPCODE_DOT_PRODUCT_4, F32Op, V128Op, V128Op>> {
|
||||
I<OPCODE_DOT_PRODUCT_4, V128Op, V128Op, V128Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
// https://msdn.microsoft.com/en-us/library/bb514054(v=vs.90).aspx
|
||||
EmitCommutativeBinaryVOp(
|
||||
e, i, [](A64Emitter& e, SReg dest, QReg src1, QReg src2) {
|
||||
e.FMUL(dest.toQ().S4(), src1.S4(), src2.S4());
|
||||
e.FADDP(dest.toQ().S4(), dest.toQ().S4(), dest.toQ().S4());
|
||||
e.FADDP(dest.toS(), dest.toD().S2());
|
||||
e, i, [](A64Emitter& e, QReg dest, QReg src1, QReg src2) {
|
||||
e.FMUL(dest.S4(), src1.S4(), src2.S4());
|
||||
e.FADDP(dest.S4(), dest.S4(), dest.S4());
|
||||
e.FADDP(S0, dest.toD().S2());
|
||||
e.FMOV(W0, S0);
|
||||
e.DUP(dest.S4(), W0);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -2782,6 +2698,18 @@ struct SET_ROUNDING_MODE_I32
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_SET_ROUNDING_MODE, SET_ROUNDING_MODE_I32);
|
||||
|
||||
static void MaybeYieldForwarder(void* ctx) { xe::threading::MaybeYield(); }
|
||||
// ============================================================================
|
||||
// OPCODE_DELAY_EXECUTION
|
||||
// ============================================================================
|
||||
struct DELAY_EXECUTION
|
||||
: Sequence<DELAY_EXECUTION, I<OPCODE_DELAY_EXECUTION, VoidOp>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
e.CallNativeSafe(reinterpret_cast<void*>(MaybeYieldForwarder));
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_DELAY_EXECUTION, DELAY_EXECUTION);
|
||||
|
||||
// Include anchors to other sequence sources so they get included in the build.
|
||||
extern volatile int anchor_control;
|
||||
static int anchor_control_dest = anchor_control;
|
||||
@@ -2795,15 +2723,14 @@ static int anchor_vector_dest = anchor_vector;
|
||||
bool SelectSequence(A64Emitter* e, const hir::Instr* i,
|
||||
const hir::Instr** new_tail) {
|
||||
const InstrKey key(i);
|
||||
auto& table = GetSequenceTable(); // Use the singleton accessor
|
||||
auto it = table.find(key);
|
||||
if (it != table.end()) {
|
||||
auto it = GetSequenceTable().find(key);
|
||||
if (it != GetSequenceTable().end()) {
|
||||
if (it->second(*e, i)) {
|
||||
*new_tail = i->next;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
XELOGE("No sequence match for variant {}", i->opcode->name);
|
||||
XELOGE("No sequence match for variant {}", hir::GetOpcodeName(i->opcode));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
#ifndef XENIA_CPU_BACKEND_A64_A64_SEQUENCES_H_
|
||||
#define XENIA_CPU_BACKEND_A64_A64_SEQUENCES_H_
|
||||
|
||||
#include <functional>
|
||||
#include <iostream> // For logging
|
||||
#include <unordered_map>
|
||||
#include "xenia/cpu/hir/instr.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
@@ -24,33 +25,31 @@ class A64Emitter;
|
||||
|
||||
typedef bool (*SequenceSelectFn)(A64Emitter&, const hir::Instr*);
|
||||
|
||||
// Singleton accessor for sequence_table
|
||||
// Singleton accessor for sequence table.
|
||||
inline std::unordered_map<uint32_t, SequenceSelectFn>& GetSequenceTable() {
|
||||
static std::unordered_map<uint32_t, SequenceSelectFn> sequence_table;
|
||||
return sequence_table;
|
||||
}
|
||||
|
||||
// Registration Functions
|
||||
template <typename T>
|
||||
bool RegisterSingle() {
|
||||
bool inserted = GetSequenceTable().emplace(T::head_key(), T::Select).second;
|
||||
if (!inserted) {
|
||||
std::cerr << "Warning: Duplicate head_key detected for key "
|
||||
<< T::head_key() << std::endl;
|
||||
XELOGW("A64 sequence registration duplicate key 0x{:08X}", T::head_key());
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
bool RegisterAll() {
|
||||
return (RegisterSingle<Ts>() && ...); // Fold expression (C++17)
|
||||
bool ok = true;
|
||||
((ok &= RegisterSingle<Ts>()), ...);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Macro for Registration
|
||||
#define EMITTER_OPCODE_TABLE(name, ...) \
|
||||
static const bool A64_INSTR_##name = RegisterAll<__VA_ARGS__>();
|
||||
|
||||
// Function to Select Sequence
|
||||
bool SelectSequence(A64Emitter* e, const hir::Instr* i,
|
||||
const hir::Instr** new_tail);
|
||||
|
||||
|
||||
@@ -34,11 +34,12 @@ bool trace_enabled = true;
|
||||
#define IFLUSH()
|
||||
#define IPRINT(s) \
|
||||
if (trace_enabled && THREAD_MATCH) \
|
||||
xe::logging::AppendLogLine(xe::LogLevel::Debug, 't', s)
|
||||
xe::logging::AppendLogLine(xe::LogLevel::Debug, 't', s, xe::LogSrc::Cpu)
|
||||
#define DFLUSH()
|
||||
#define DPRINT(...) \
|
||||
if (trace_enabled && THREAD_MATCH) \
|
||||
xe::logging::AppendLogLineFormat(xe::LogLevel::Debug, 't', __VA_ARGS__)
|
||||
#define DPRINT(...) \
|
||||
if (trace_enabled && THREAD_MATCH) \
|
||||
xe::logging::AppendLogLineFormat(xe::LogSrc::Cpu, xe::LogLevel::Debug, 't', \
|
||||
__VA_ARGS__)
|
||||
|
||||
uint32_t GetTracingMode() {
|
||||
uint32_t mode = 0;
|
||||
|
||||
@@ -4,28 +4,44 @@ include(project_root.."/tools/build")
|
||||
group("src")
|
||||
project("xenia-cpu-backend-a64")
|
||||
uuid("495f3f3e-f5e8-489a-bd0f-289d0495bc08")
|
||||
|
||||
-- Apply settings only for ARM64
|
||||
filter("architecture:ARM64")
|
||||
kind("StaticLib")
|
||||
filter("architecture:not ARM64")
|
||||
kind("None")
|
||||
language("C++")
|
||||
cppdialect("C++20")
|
||||
links({
|
||||
"fmt",
|
||||
"xenia-base",
|
||||
"xenia-cpu",
|
||||
})
|
||||
sysincludedirs({
|
||||
project_root.."/third_party/oaknut/include",
|
||||
})
|
||||
defines({
|
||||
})
|
||||
|
||||
-- Add oaknut as external include to suppress warnings
|
||||
filter("toolset:clang or toolset:gcc")
|
||||
externalincludedirs({
|
||||
project_root.."/third_party/oaknut/include",
|
||||
})
|
||||
-- Also explicitly disable the warning for third-party code
|
||||
buildoptions({
|
||||
"-Wno-shorten-64-to-32",
|
||||
})
|
||||
filter("toolset:msc")
|
||||
includedirs({
|
||||
project_root.."/third_party/oaknut/include",
|
||||
})
|
||||
filter("architecture:ARM64")
|
||||
|
||||
-- Include only ARM64-specific files
|
||||
local_platform_files()
|
||||
|
||||
-- For non-ARM64 architectures, create an empty static lib
|
||||
filter("architecture:x86_64")
|
||||
kind("None")
|
||||
|
||||
-- Reset filter
|
||||
filter({})
|
||||
language("C++")
|
||||
cppdialect("C++20")
|
||||
links({
|
||||
"fmt",
|
||||
"xenia-base",
|
||||
"xenia-cpu",
|
||||
})
|
||||
defines({
|
||||
})
|
||||
|
||||
disablewarnings({
|
||||
-- Silence errors in oaknut
|
||||
"4146", -- unary minus operator applied to unsigned type, result still unsigned
|
||||
"4267" -- 'initializing': conversion from 'size_t' to 'uint32_t', possible loss of data
|
||||
})
|
||||
|
||||
includedirs({
|
||||
project_root.."/third_party/oaknut/include",
|
||||
})
|
||||
local_platform_files()
|
||||
|
||||
Reference in New Issue
Block a user