Merge remote-tracking branch 'wmarti/a64-backend' into edge

This commit is contained in:
Herman S.
2026-01-27 09:49:23 +09:00
23 changed files with 12990 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/a64/a64_assembler.h"
#include <climits>
#include "third_party/capstone/include/capstone/arm64.h"
#include "third_party/capstone/include/capstone/capstone.h"
#include "xenia/base/profiling.h"
#include "xenia/base/reset_scope.h"
#include "xenia/base/string.h"
#include "xenia/cpu/backend/a64/a64_backend.h"
#include "xenia/cpu/backend/a64/a64_code_cache.h"
#include "xenia/cpu/backend/a64/a64_emitter.h"
#include "xenia/cpu/backend/a64/a64_function.h"
#include "xenia/cpu/cpu_flags.h"
#include "xenia/cpu/hir/hir_builder.h"
#include "xenia/cpu/hir/label.h"
#include "xenia/cpu/processor.h"
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
using xe::cpu::hir::HIRBuilder;
A64Assembler::A64Assembler(A64Backend* backend)
: Assembler(backend), a64_backend_(backend), capstone_handle_(0) {
if (cs_open(CS_ARCH_AARCH64, CS_MODE_LITTLE_ENDIAN, &capstone_handle_) !=
CS_ERR_OK) {
assert_always("Failed to initialize capstone");
}
// Remove Intel syntax option as it's not applicable to ARM64
cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_OFF);
}
A64Assembler::~A64Assembler() {
// Emitter must be freed before the allocator.
emitter_.reset();
if (capstone_handle_) {
cs_close(&capstone_handle_);
}
}
bool A64Assembler::Initialize() {
if (!Assembler::Initialize()) {
return false;
}
emitter_.reset(new A64Emitter(a64_backend_));
return true;
}
void A64Assembler::Reset() {
string_buffer_.Reset();
Assembler::Reset();
}
bool A64Assembler::Assemble(GuestFunction* function, HIRBuilder* builder,
uint32_t debug_info_flags,
std::unique_ptr<FunctionDebugInfo> debug_info) {
SCOPE_profile_cpu_f("cpu");
// Reset when we leave.
xe::make_reset_scope(this);
// Lower HIR -> a64.
void* machine_code = nullptr;
size_t code_size = 0;
if (!emitter_->Emit(function, builder, debug_info_flags, debug_info.get(),
&machine_code, &code_size, &function->source_map())) {
return false;
}
// Stash generated machine code.
if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmMachineCode) {
DumpMachineCode(machine_code, code_size, function->source_map(),
&string_buffer_);
debug_info->set_machine_code_disasm(xe_strdup(string_buffer_.buffer()));
string_buffer_.Reset();
}
function->set_debug_info(std::move(debug_info));
static_cast<A64Function*>(function)->Setup(
reinterpret_cast<uint8_t*>(machine_code), code_size);
// Install into indirection table.
const uint64_t host_address = reinterpret_cast<uint64_t>(machine_code);
#if XE_A64_INDIRECTION_64BIT
// On ARM64 platforms, machine code might be allocated in high address space.
// Use the 64-bit version of AddIndirection to store the full address.
reinterpret_cast<A64CodeCache*>(backend_->code_cache())
->AddIndirection64(function->address(), host_address);
#else
assert_true((host_address >> 32) == 0);
reinterpret_cast<A64CodeCache*>(backend_->code_cache())
->AddIndirection(function->address(),
static_cast<uint32_t>(host_address));
#endif
return true;
}
void A64Assembler::DumpMachineCode(
void* machine_code, size_t code_size,
const std::vector<SourceMapEntry>& source_map, StringBuffer* str) {
if (source_map.empty()) {
return;
}
auto source_map_index = 0;
uint32_t next_code_offset = source_map[0].code_offset;
const uint8_t* code_ptr = reinterpret_cast<uint8_t*>(machine_code);
size_t remaining_code_size = code_size;
uint64_t address = uint64_t(machine_code);
cs_insn insn = {};
while (remaining_code_size &&
cs_disasm_iter(capstone_handle_, &code_ptr, &remaining_code_size,
&address, &insn)) {
// Look up source offset.
auto code_offset =
uint32_t(code_ptr - reinterpret_cast<uint8_t*>(machine_code));
if (code_offset >= next_code_offset &&
source_map_index < static_cast<int>(source_map.size())) {
auto& source_map_entry = source_map[source_map_index];
str->AppendFormat("{:08X} ", source_map_entry.guest_address);
++source_map_index;
next_code_offset = source_map_index < static_cast<int>(source_map.size())
? source_map[source_map_index].code_offset
: UINT_MAX;
} else {
str->Append(" ");
}
str->AppendFormat("{:08X} {:<6} {}\n", uint32_t(insn.address),
insn.mnemonic, insn.op_str);
}
}
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
+59
View File
@@ -0,0 +1,59 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_CPU_BACKEND_A64_A64_ASSEMBLER_H_
#define XENIA_CPU_BACKEND_A64_A64_ASSEMBLER_H_
#include <memory>
#include <vector>
#include "xenia/base/string_buffer.h"
#include "xenia/cpu/backend/assembler.h"
#include "xenia/cpu/function.h"
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
class A64Backend;
class A64Emitter;
class A64Assembler : public Assembler {
public:
explicit A64Assembler(A64Backend* backend);
~A64Assembler() override;
bool Initialize() override;
void Reset() override;
bool Assemble(GuestFunction* function, hir::HIRBuilder* builder,
uint32_t debug_info_flags,
std::unique_ptr<FunctionDebugInfo> debug_info) override;
private:
void DumpMachineCode(void* machine_code, size_t code_size,
const std::vector<SourceMapEntry>& source_map,
StringBuffer* str);
private:
A64Backend* a64_backend_;
std::unique_ptr<A64Emitter> emitter_;
uintptr_t capstone_handle_;
StringBuffer string_buffer_;
};
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_BACKEND_A64_A64_ASSEMBLER_H_
File diff suppressed because it is too large Load Diff
+88
View File
@@ -0,0 +1,88 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_CPU_BACKEND_A64_A64_BACKEND_H_
#define XENIA_CPU_BACKEND_A64_A64_BACKEND_H_
#include <memory>
#include "xenia/base/cvar.h"
#include "xenia/cpu/backend/backend.h"
DECLARE_int32(a64_extension_mask);
namespace xe {
class Exception;
} // namespace xe
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
class A64CodeCache;
typedef void* (*HostToGuestThunk)(void* target, void* arg0, void* arg1);
typedef void* (*GuestToHostThunk)(void* target, void* arg0, void* arg1);
typedef void (*ResolveFunctionThunk)();
class A64Backend : public Backend {
public:
static const uint32_t kForceReturnAddress = 0x9FFF0000u;
explicit A64Backend();
~A64Backend() override;
A64CodeCache* code_cache() const { return code_cache_.get(); }
uintptr_t emitter_data() const { return emitter_data_; }
// Call a generated function, saving all stack parameters.
HostToGuestThunk host_to_guest_thunk() const { return host_to_guest_thunk_; }
// Function that guest code can call to transition into host code.
GuestToHostThunk guest_to_host_thunk() const { return guest_to_host_thunk_; }
// Function that thunks to the ResolveFunction in A64Emitter.
ResolveFunctionThunk resolve_function_thunk() const {
return resolve_function_thunk_;
}
bool Initialize(Processor* processor) override;
void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high) override;
std::unique_ptr<Assembler> CreateAssembler() override;
std::unique_ptr<GuestFunction> CreateGuestFunction(Module* module,
uint32_t address) override;
uint64_t CalculateNextHostInstruction(ThreadDebugInfo* thread_info,
uint64_t current_pc) override;
void InstallBreakpoint(Breakpoint* breakpoint) override;
void InstallBreakpoint(Breakpoint* breakpoint, Function* fn) override;
void UninstallBreakpoint(Breakpoint* breakpoint) override;
private:
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
uintptr_t capstone_handle_ = 0;
std::unique_ptr<A64CodeCache> code_cache_;
uintptr_t emitter_data_ = 0;
HostToGuestThunk host_to_guest_thunk_;
GuestToHostThunk guest_to_host_thunk_;
ResolveFunctionThunk resolve_function_thunk_;
};
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_BACKEND_A64_A64_BACKEND_H_
File diff suppressed because it is too large Load Diff
+219
View File
@@ -0,0 +1,219 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_CPU_BACKEND_A64_A64_CODE_CACHE_H_
#define XENIA_CPU_BACKEND_A64_A64_CODE_CACHE_H_
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "xenia/base/memory.h"
#include "xenia/base/mutex.h"
#include "xenia/cpu/backend/code_cache.h"
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
#if XE_ARCH_ARM64
#define XE_A64_INDIRECTION_64BIT 1
#else
#define XE_A64_INDIRECTION_64BIT 0
#endif
struct EmitFunctionInfo {
struct _code_size {
size_t prolog;
size_t body;
size_t epilog;
size_t tail;
size_t total;
} code_size;
size_t prolog_stack_alloc_offset; // offset of instruction after stack alloc
size_t stack_size;
};
class A64CodeCache : public CodeCache {
public:
~A64CodeCache() override;
static std::unique_ptr<A64CodeCache> Create();
virtual bool Initialize();
const std::filesystem::path& file_name() const override { return file_name_; }
uintptr_t execute_base_address() const override {
return generated_code_execute_base_
? reinterpret_cast<uintptr_t>(generated_code_execute_base_)
: kGeneratedCodeExecuteBase;
}
size_t total_size() const override { return kGeneratedCodeSize; }
// TODO(benvanik): ELF serialization/etc
// TODO(benvanik): keep track of code blocks
// TODO(benvanik): padding/guards/etc
bool has_indirection_table() { return indirection_table_base_ != nullptr; }
void set_indirection_default(uint32_t default_value);
#if XE_A64_INDIRECTION_64BIT
void set_indirection_default_64(uint64_t default_value);
#endif
void AddIndirection(uint32_t guest_address, uint32_t host_address);
#if XE_A64_INDIRECTION_64BIT
void AddIndirection64(uint32_t guest_address, uint64_t host_address);
#endif
void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high);
void PlaceHostCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info,
void*& code_execute_address_out,
void*& code_write_address_out);
void PlaceGuestCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info,
GuestFunction* function_info,
void*& code_execute_address_out,
void*& code_write_address_out);
uint32_t PlaceData(const void* data, size_t length);
GuestFunction* LookupFunction(uint64_t host_pc) override;
// Access to indirection table base for emitter
uint8_t* indirection_table_base() const { return indirection_table_base_; }
// Returns the actual base address used for indirection table
uintptr_t indirection_table_base_address() const {
return indirection_table_actual_base_;
}
#if XE_A64_INDIRECTION_64BIT
uintptr_t indirection_table_base_bias() const {
return indirection_table_base_bias_;
}
#endif
public:
// All executable code falls within 0x80000000 to 0x9FFFFFFF, so we can
// only map enough for lookups within that range.
// Size of the indirection table in bytes.
// On ARM64 platforms we store 64-bit entries (8 bytes) per 4-byte guest slot
// for the 0x2000_0000-byte guest executable range (0x8000_0000..0xA000_0000),
// so we need 0x4000_0000 bytes to cover the full space.
#if XE_A64_INDIRECTION_64BIT
static const size_t kIndirectionTableSize = 0x40000000; // 1 GiB
#else
static const size_t kIndirectionTableSize =
0x20000000 - 1; // 512 MiB - 1 (legacy)
#endif
#if XE_A64_INDIRECTION_64BIT
// On ARM64 platforms, the base address is determined dynamically at runtime
// based on where the OS allows us to allocate memory
static uintptr_t kIndirectionTableBase;
#else
static const uintptr_t kIndirectionTableBase = 0x80000000;
#endif
// The code range is 512MB, but we know the total code games will have is
// pretty small (dozens of mb at most) and our expansion is reasonablish
// so 256MB should be more than enough.
static const size_t kGeneratedCodeSize = 0x0FFFFFFF;
static const uintptr_t kGeneratedCodeExecuteBase = 0xA0000000;
// Used for writing when PageAccess::kExecuteReadWrite is not supported.
static const uintptr_t kGeneratedCodeWriteBase =
kGeneratedCodeExecuteBase + kGeneratedCodeSize + 1;
// This is picked to be high enough to cover whatever we can reasonably
// expect. If we hit issues with this it probably means some corner case
// in analysis triggering.
static const size_t kMaximumFunctionCount = 100000;
struct UnwindReservation {
size_t data_size = 0;
size_t table_slot = 0;
uint8_t* entry_address = 0;
};
A64CodeCache();
virtual UnwindReservation RequestUnwindReservation(uint8_t* entry_address) {
return UnwindReservation();
}
virtual void PlaceCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info,
void* code_execute_address,
UnwindReservation unwind_reservation) {}
// Platform-specific code copying with JIT protection handling
virtual void CopyMachineCode(void* dest, const void* src, size_t size) {
std::memcpy(dest, src, size);
}
std::filesystem::path file_name_;
xe::memory::FileMappingHandle mapping_ =
xe::memory::kFileMappingHandleInvalid;
// NOTE: the global critical region must be held when manipulating the offsets
// or counts of anything, to keep the tables consistent and ordered.
xe::global_critical_region global_critical_region_;
// Value that the indirection table will be initialized with upon commit.
#if XE_A64_INDIRECTION_64BIT
uint64_t indirection_default_value_ = 0xFEEDF00D;
#else
uint32_t indirection_default_value_ = 0xFEEDF00D;
#endif
#if XE_A64_INDIRECTION_64BIT
// On ARM64 platforms, we use 64-bit pointers in the indirection table to
// handle high addresses that can't fit in 32-bit values.
using indirection_entry_t = uint64_t;
static constexpr size_t kIndirectionEntrySize = 8;
#else
// Other platforms use 32-bit pointers
using indirection_entry_t = uint32_t;
static constexpr size_t kIndirectionEntrySize = 4;
#endif
// Fixed at kIndirectionTableBase in host space, holding pointers into
// the generated code table that correspond to the PPC functions in guest
// space.
uint8_t* indirection_table_base_ = nullptr;
// Actual base address of the indirection table (may differ from
// kIndirectionTableBase on systems where fixed address allocation fails)
uintptr_t indirection_table_actual_base_ = 0;
#if XE_A64_INDIRECTION_64BIT
uintptr_t indirection_table_base_bias_ = 0;
#endif
// Fixed at kGeneratedCodeExecuteBase and holding all generated code, growing
// as needed.
uint8_t* generated_code_execute_base_ = nullptr;
// View of the memory that backs generated_code_execute_base_ when
// PageAccess::kExecuteReadWrite is not supported, for writing the generated
// code. Equals to generated_code_execute_base_ when it's supported.
uint8_t* generated_code_write_base_ = nullptr;
// Current offset to empty space in generated code.
size_t generated_code_offset_ = 0;
// Current high water mark of COMMITTED code.
std::atomic<size_t> generated_code_commit_mark_ = {0};
// Sorted map by host PC base offsets to source function info.
// This can be used to bsearch on host PC to find the guest function.
// The key is [start address | end address].
std::vector<std::pair<uint64_t, GuestFunction*>> generated_code_map_;
};
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_BACKEND_A64_A64_CODE_CACHE_H_
@@ -0,0 +1,201 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved.
* Released under the BSD license - see LICENSE in the root for more details.
******************************************************************************
*/
#include "xenia/cpu/backend/a64/a64_code_cache.h"
#include <sys/mman.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#ifdef XE_PLATFORM_MAC
#include <libkern/OSCacheControl.h>
#include <pthread.h>
#endif
#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"
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
// ARM64 unwind-op codes for POSIX (simplified)
typedef enum _UNWIND_OP_CODES_POSIX {
UWOP_POSIX_NOP = 0x00,
UWOP_POSIX_ALLOC_STACK = 0x01,
UWOP_POSIX_SAVE_FP_LR = 0x02,
UWOP_POSIX_SET_FP = 0x03,
UWOP_POSIX_END = 0xFF,
} UNWIND_CODE_OPS_POSIX;
using UNWIND_CODE_POSIX = uint8_t;
// Size of unwind info per function.
static const size_t kUnwindInfoSize = 16;
class PosixA64CodeCache : public A64CodeCache {
public:
PosixA64CodeCache();
~PosixA64CodeCache() override;
bool Initialize() override;
void* LookupUnwindInfo(uint64_t host_pc) override;
protected:
void CopyMachineCode(void* dest, const void* src, size_t size) override;
private:
struct UnwindInfo {
uint64_t begin_address;
uint64_t end_address;
// Additional unwind information can be added here
};
UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override;
void PlaceCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info, void* code_execute_address,
UnwindReservation unwind_reservation) override;
void InitializeUnwindEntry(uint8_t* unwind_entry_address,
size_t unwind_table_slot,
void* code_execute_address,
const EmitFunctionInfo& func_info);
// Unwind table entries.
std::vector<UnwindInfo> unwind_table_;
// Current number of entries in the table.
std::atomic<uint32_t> unwind_table_count_ = {0};
};
std::unique_ptr<A64CodeCache> A64CodeCache::Create() {
return std::make_unique<PosixA64CodeCache>();
}
PosixA64CodeCache::PosixA64CodeCache() = default;
PosixA64CodeCache::~PosixA64CodeCache() {
// Cleanup if necessary
}
bool PosixA64CodeCache::Initialize() {
if (!A64CodeCache::Initialize()) {
return false;
}
// Resize (not reserve) space for unwind table entries to ensure vector has
// actual elements
unwind_table_.resize(kMaximumFunctionCount);
// Additional POSIX-specific initialization can be done here
return true;
}
void PosixA64CodeCache::CopyMachineCode(void* dest, const void* src,
size_t size) {
std::memcpy(dest, src, size);
}
PosixA64CodeCache::UnwindReservation
PosixA64CodeCache::RequestUnwindReservation(uint8_t* entry_address) {
uint32_t current_count = unwind_table_count_.fetch_add(1);
assert_false(current_count >= kMaximumFunctionCount);
UnwindReservation unwind_reservation;
unwind_reservation.data_size = xe::round_up(kUnwindInfoSize, 16);
unwind_reservation.table_slot = current_count;
unwind_reservation.entry_address = entry_address;
return unwind_reservation;
}
void PosixA64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info,
void* code_execute_address,
UnwindReservation unwind_reservation) {
// Add unwind info.
InitializeUnwindEntry(
reinterpret_cast<uint8_t*>(unwind_reservation.entry_address),
unwind_reservation.table_slot, code_execute_address, func_info);
// Add entry to unwind table at the reserved slot only
UnwindInfo unwind_info;
unwind_info.begin_address = reinterpret_cast<uintptr_t>(code_execute_address);
unwind_info.end_address =
unwind_info.begin_address + func_info.code_size.total;
// Store in the reserved slot
unwind_table_[unwind_reservation.table_slot] = unwind_info;
// Validate address alignment before cache flushing
if (reinterpret_cast<uintptr_t>(code_execute_address) % 4 != 0) {
XELOGW(
"PosixA64CodeCache::PlaceCode: WARNING - code address 0x{:016X} is not "
"4-byte aligned",
reinterpret_cast<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
sys_icache_invalidate(code_execute_address, func_info.code_size.total);
#else
// On Linux and other POSIX systems, use GCC builtin
__builtin___clear_cache(
static_cast<char*>(code_execute_address),
static_cast<char*>(code_execute_address) + func_info.code_size.total);
#endif
}
void PosixA64CodeCache::InitializeUnwindEntry(
uint8_t* unwind_entry_address, size_t unwind_table_slot,
void* code_execute_address, const EmitFunctionInfo& func_info) {
// Initialize unwind information for POSIX (simplified example)
// In practice, you would populate this with proper unwind info
// based on the function prologue and epilogue.
// NOTE: Unwind info is already stored in PlaceCode, so we don't store it
// again here to avoid the double-storage bug that was causing memory
// corruption.
}
void* PosixA64CodeCache::LookupUnwindInfo(uint64_t host_pc) {
// Binary search the unwind table for the given program counter
size_t left = 0;
size_t right = unwind_table_count_.load();
while (left < right) {
size_t mid = left + (right - left) / 2;
const UnwindInfo& info = unwind_table_[mid];
if (host_pc < info.begin_address) {
right = mid;
} else if (host_pc >= info.end_address) {
left = mid + 1;
} else {
return &unwind_table_[mid];
}
}
return nullptr;
}
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
@@ -0,0 +1,319 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/a64/a64_code_cache.h"
#include <cstdlib>
#include <cstring>
#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/base/platform_win.h"
#include "xenia/cpu/function.h"
// Function pointer definitions
using FnRtlAddGrowableFunctionTable = decltype(&RtlAddGrowableFunctionTable);
using FnRtlGrowFunctionTable = decltype(&RtlGrowFunctionTable);
using FnRtlDeleteGrowableFunctionTable =
decltype(&RtlDeleteGrowableFunctionTable);
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
// ARM64 unwind-op codes
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
// https://www.corsix.org/content/windows-arm64-unwind-codes
typedef enum _UNWIND_OP_CODES {
UWOP_NOP = 0xE3,
UWOP_ALLOC_S = 0x00, // sub sp, sp, i*16
UWOP_ALLOC_L = 0xE0'00'00'00, // sub sp, sp, i*16
UWOP_SAVE_FPLR = 0x40, // stp fp, lr, [sp+i*8]
UWOP_SAVE_FPLRX = 0x80, // stp fp, lr, [sp-(i+1)*8]!
UWOP_SET_FP = 0xE1, // mov fp, sp
UWOP_END = 0xE4,
} UNWIND_CODE_OPS;
using UNWIND_CODE = uint32_t;
static_assert(sizeof(UNWIND_CODE) == sizeof(uint32_t));
// UNWIND_INFO defines the static part (first 32-bit) of the .xdata record
typedef struct _UNWIND_INFO {
uint32_t FunctionLength : 18;
uint32_t Version : 2;
uint32_t X : 1;
uint32_t E : 1;
uint32_t EpilogCount : 5;
uint32_t CodeWords : 5;
UNWIND_CODE UnwindCodes[2];
} UNWIND_INFO, *PUNWIND_INFO;
static_assert(offsetof(UNWIND_INFO, UnwindCodes[0]) == 4);
static_assert(offsetof(UNWIND_INFO, UnwindCodes[1]) == 8);
// Size of unwind info per function.
static const uint32_t kUnwindInfoSize = sizeof(UNWIND_INFO);
class Win32A64CodeCache : public A64CodeCache {
public:
Win32A64CodeCache();
~Win32A64CodeCache() override;
bool Initialize() override;
void* LookupUnwindInfo(uint64_t host_pc) override;
private:
UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override;
void PlaceCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info, void* code_execute_address,
UnwindReservation unwind_reservation) override;
void InitializeUnwindEntry(uint8_t* unwind_entry_address,
size_t unwind_table_slot,
void* code_execute_address,
const EmitFunctionInfo& func_info);
// Growable function table system handle.
void* unwind_table_handle_ = nullptr;
// Actual unwind table entries.
std::vector<RUNTIME_FUNCTION> unwind_table_;
// Current number of entries in the table.
std::atomic<uint32_t> unwind_table_count_ = {0};
// Does this version of Windows support growable funciton tables?
bool supports_growable_table_ = false;
FnRtlAddGrowableFunctionTable add_growable_table_ = nullptr;
FnRtlDeleteGrowableFunctionTable delete_growable_table_ = nullptr;
FnRtlGrowFunctionTable grow_table_ = nullptr;
};
std::unique_ptr<A64CodeCache> A64CodeCache::Create() {
return std::make_unique<Win32A64CodeCache>();
}
Win32A64CodeCache::Win32A64CodeCache() = default;
Win32A64CodeCache::~Win32A64CodeCache() {
if (supports_growable_table_) {
if (unwind_table_handle_) {
delete_growable_table_(unwind_table_handle_);
}
} else {
if (generated_code_execute_base_) {
RtlDeleteFunctionTable(reinterpret_cast<PRUNTIME_FUNCTION>(
reinterpret_cast<DWORD64>(generated_code_execute_base_) | 0x3));
}
}
}
bool Win32A64CodeCache::Initialize() {
if (!A64CodeCache::Initialize()) {
return false;
}
// Compute total number of unwind entries we should allocate.
// We don't support reallocing right now, so this should be high.
unwind_table_.resize(kMaximumFunctionCount);
// Check if this version of Windows supports growable function tables.
auto ntdll_handle = GetModuleHandleW(L"ntdll.dll");
if (!ntdll_handle) {
add_growable_table_ = nullptr;
delete_growable_table_ = nullptr;
grow_table_ = nullptr;
} else {
add_growable_table_ = (FnRtlAddGrowableFunctionTable)GetProcAddress(
ntdll_handle, "RtlAddGrowableFunctionTable");
delete_growable_table_ = (FnRtlDeleteGrowableFunctionTable)GetProcAddress(
ntdll_handle, "RtlDeleteGrowableFunctionTable");
grow_table_ = (FnRtlGrowFunctionTable)GetProcAddress(
ntdll_handle, "RtlGrowFunctionTable");
}
supports_growable_table_ =
add_growable_table_ && delete_growable_table_ && grow_table_;
// Create table and register with the system. It's empty now, but we'll grow
// it as functions are added.
if (supports_growable_table_) {
if (add_growable_table_(
&unwind_table_handle_, unwind_table_.data(), unwind_table_count_,
DWORD(unwind_table_.size()),
reinterpret_cast<ULONG_PTR>(generated_code_execute_base_),
reinterpret_cast<ULONG_PTR>(generated_code_execute_base_ +
kGeneratedCodeSize))) {
XELOGE("Unable to create unwind function table");
return false;
}
} else {
// Install a callback that the debugger will use to lookup unwind info on
// demand.
if (!RtlInstallFunctionTableCallback(
reinterpret_cast<DWORD64>(generated_code_execute_base_) | 0x3,
reinterpret_cast<DWORD64>(generated_code_execute_base_),
kGeneratedCodeSize,
[](DWORD64 control_pc, PVOID context) {
auto code_cache = reinterpret_cast<Win32A64CodeCache*>(context);
return reinterpret_cast<PRUNTIME_FUNCTION>(
code_cache->LookupUnwindInfo(control_pc));
},
this, nullptr)) {
XELOGE("Unable to install function table callback");
return false;
}
}
return true;
}
Win32A64CodeCache::UnwindReservation
Win32A64CodeCache::RequestUnwindReservation(uint8_t* entry_address) {
assert_false(unwind_table_count_ >= kMaximumFunctionCount);
UnwindReservation unwind_reservation;
unwind_reservation.data_size = xe::round_up(kUnwindInfoSize, 16);
unwind_reservation.table_slot = unwind_table_count_++;
unwind_reservation.entry_address = entry_address;
return unwind_reservation;
}
void Win32A64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info,
void* code_execute_address,
UnwindReservation unwind_reservation) {
// Add unwind info.
InitializeUnwindEntry(unwind_reservation.entry_address,
unwind_reservation.table_slot, code_execute_address,
func_info);
if (supports_growable_table_) {
// Notify that the unwind table has grown.
// We do this outside of the lock, but with the latest total count.
grow_table_(unwind_table_handle_, unwind_table_count_);
}
// https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis
FlushInstructionCache(GetCurrentProcess(), code_execute_address,
func_info.code_size.total);
}
constexpr UNWIND_CODE UnwindOpWord(uint8_t code0 = UWOP_NOP,
uint8_t code1 = UWOP_NOP,
uint8_t code2 = UWOP_NOP,
uint8_t code3 = UWOP_NOP) {
return static_cast<uint32_t>(code0) | (static_cast<uint32_t>(code1) << 8) |
(static_cast<uint32_t>(code2) << 16) |
(static_cast<uint32_t>(code3) << 24);
}
// 8-byte unwind code for "stp fp, lr, [sp, #-16]!
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
static uint8_t OpSaveFpLrX(int16_t pre_index_offset) {
assert_true(pre_index_offset <= -8);
assert_true(pre_index_offset >= -512);
// 16-byte aligned
constexpr int IndexShift = 3;
constexpr int IndexMask = (1 << IndexShift) - 1;
assert_true((pre_index_offset & IndexMask) == 0);
const uint32_t encoded_value = (-pre_index_offset >> IndexShift) - 1;
return UWOP_SAVE_FPLRX | encoded_value;
}
// Ensure a 16-byte aligned stack
static constexpr size_t StackAlignShift = 4; // n / 16
static constexpr size_t StackAlignMask = (1 << StackAlignShift) - 1; // n % 16
// 8-byte unwind code for up to +512-byte "sub sp, sp, #stack_space"
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
static uint8_t OpAllocS(int16_t stack_space) {
assert_true(stack_space >= 0);
assert_true(stack_space < 512);
assert_true((stack_space & StackAlignMask) == 0);
return UWOP_ALLOC_S | (stack_space >> StackAlignShift);
}
// 4-byte unwind code for +256MiB "sub sp, sp, #stack_space"
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
uint32_t OpAllocL(int32_t stack_space) {
assert_true(stack_space >= 0);
assert_true(stack_space < (0xFFFFFF * 16));
assert_true((stack_space & StackAlignMask) == 0);
return xe::byte_swap(UWOP_ALLOC_L |
((stack_space >> StackAlignShift) & 0xFF'FF'FF));
}
void Win32A64CodeCache::InitializeUnwindEntry(
uint8_t* unwind_entry_address, size_t unwind_table_slot,
void* code_execute_address, const EmitFunctionInfo& func_info) {
auto unwind_info = reinterpret_cast<UNWIND_INFO*>(unwind_entry_address);
*unwind_info = {};
// ARM64 instructions are always multiples of 4 bytes
// Windows ignores the bottom 2 bits
unwind_info->FunctionLength = func_info.code_size.total / 4;
unwind_info->CodeWords = 2;
// https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#unwind-codes
// The array of unwind codes is a pool of sequences that describe exactly how
// to undo the effects of the prolog. They're stored in the same order the
// operations need to be undone. The unwind codes can be thought of as a small
// instruction set, encoded as a string of bytes. When execution is complete,
// the return address to the calling function is in the lr register. And, all
// non-volatile registers are restored to their values at the time the
// function was called.
// Function frames are generally:
// STP(X29, X30, SP, PRE_INDEXED, -16);
// MOV(X29, XSP);
// SUB(XSP, XSP, stack_size);
// ... function body ...
// ADD(XSP, XSP, stack_size);
// MOV(XSP, X29);
// LDP(X29, X30, SP, POST_INDEXED, 16);
// These opcodes must undo the epilog and put the return address within lr
unwind_info->UnwindCodes[0] = OpAllocL(func_info.stack_size);
unwind_info->UnwindCodes[1] =
UnwindOpWord(UWOP_SET_FP, OpSaveFpLrX(-16), UWOP_END);
// Add entry.
RUNTIME_FUNCTION& fn_entry = unwind_table_[unwind_table_slot];
fn_entry.BeginAddress =
DWORD(reinterpret_cast<uint8_t*>(code_execute_address) -
generated_code_execute_base_);
fn_entry.UnwindData =
DWORD(unwind_entry_address - generated_code_execute_base_);
}
void* Win32A64CodeCache::LookupUnwindInfo(uint64_t host_pc) {
return std::bsearch(
&host_pc, unwind_table_.data(), unwind_table_count_,
sizeof(RUNTIME_FUNCTION),
[](const void* key_ptr, const void* element_ptr) {
auto key = *reinterpret_cast<const uintptr_t*>(key_ptr) -
kGeneratedCodeExecuteBase;
auto element = reinterpret_cast<const RUNTIME_FUNCTION*>(element_ptr);
if (key < element->BeginAddress) {
return -1;
} else if (key > (element->BeginAddress + element->FunctionLength)) {
return 1;
} else {
return 0;
}
});
}
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
File diff suppressed because it is too large Load Diff
+277
View File
@@ -0,0 +1,277 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_CPU_BACKEND_A64_A64_EMITTER_H_
#define XENIA_CPU_BACKEND_A64_A64_EMITTER_H_
#include <unordered_map>
#include <vector>
#include "xenia/base/arena.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/function_trace_data.h"
#include "xenia/cpu/hir/hir_builder.h"
#include "xenia/cpu/hir/instr.h"
#include "xenia/cpu/hir/value.h"
#include "xenia/memory.h"
#include "oaknut/code_block.hpp"
#include "oaknut/oaknut.hpp"
namespace xe {
namespace cpu {
class Processor;
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
class A64Backend;
class A64CodeCache;
struct EmitFunctionInfo;
enum RegisterFlags {
REG_DEST = (1 << 0),
REG_ABCD = (1 << 1),
};
enum VConst {
VZero = 0,
VOnePD,
VNegativeOne,
VFFFF,
VMaskX16Y16,
VFlipX16Y16,
VFixX16Y16,
VNormalizeX16Y16,
V0001,
V3301,
V3331,
V3333,
VSignMaskPS,
VSignMaskPD,
VAbsMaskPS,
VAbsMaskPD,
VByteSwapMask,
VByteOrderMask,
VPermuteControl15,
VPermuteByteMask,
VPackD3DCOLORSat,
VPackD3DCOLOR,
VUnpackD3DCOLOR,
VPackFLOAT16_2,
VUnpackFLOAT16_2,
VPackFLOAT16_4,
VUnpackFLOAT16_4,
VPackSHORT_Min,
VPackSHORT_Max,
VPackSHORT_2,
VPackSHORT_4,
VUnpackSHORT_2,
VUnpackSHORT_4,
VUnpackSHORT_Overflow,
VPackUINT_2101010_MinUnpacked,
VPackUINT_2101010_MaxUnpacked,
VPackUINT_2101010_MaskUnpacked,
VPackUINT_2101010_MaskPacked,
VPackUINT_2101010_Shift,
VUnpackUINT_2101010_Overflow,
VPackULONG_4202020_MinUnpacked,
VPackULONG_4202020_MaxUnpacked,
VPackULONG_4202020_MaskUnpacked,
VPackULONG_4202020_PermuteXZ,
VPackULONG_4202020_PermuteYW,
VUnpackULONG_4202020_Permute,
VUnpackULONG_4202020_Overflow,
VOneOver255,
VMaskEvenPI16,
VShiftMaskEvenPI16,
VShiftMaskPS,
VShiftByteMask,
VSwapWordMask,
VUnsignedDwordMax,
V255,
VPI32,
VSignMaskI8,
VSignMaskI16,
VSignMaskI32,
VSignMaskF32,
VShortMinPS,
VShortMaxPS,
VIntMin,
VIntMax,
VIntMaxPD,
VPosIntMinPS,
VQNaN,
VInt127,
V2To32,
VSingleDenormalMask,
};
enum A64EmitterFeatureFlags {
kA64EmitLSE = 1 << 0,
kA64EmitF16C = 1 << 1,
};
class A64Emitter : public oaknut::VectorCodeGenerator {
public:
A64Emitter(A64Backend* backend);
virtual ~A64Emitter();
Processor* processor() const { return processor_; }
A64Backend* backend() const { return backend_; }
static uintptr_t PlaceConstData();
static void FreeConstData(uintptr_t data);
bool Emit(GuestFunction* function, hir::HIRBuilder* builder,
uint32_t debug_info_flags, FunctionDebugInfo* debug_info,
void** out_code_address, size_t* out_code_size,
std::vector<SourceMapEntry>* out_source_map);
public:
// Reserved: XSP, X27, X28
// Scratch: X1-X15, X30 | V0-v7 and V16-V31
// V0-2
// Available: X19-X26
// V4-V15 (save to get V3)
static const size_t GPR_COUNT = 8;
static const size_t FPR_COUNT = 8;
static void SetupReg(const hir::Value* v, oaknut::WReg& r) {
const auto idx = gpr_reg_map_[v->reg.index];
r = oaknut::WReg(idx);
}
static void SetupReg(const hir::Value* v, oaknut::XReg& r) {
const auto idx = gpr_reg_map_[v->reg.index];
r = oaknut::XReg(idx);
}
static void SetupReg(const hir::Value* v, oaknut::SReg& r) {
const auto idx = fpr_reg_map_[v->reg.index];
r = oaknut::SReg(idx);
}
static void SetupReg(const hir::Value* v, oaknut::DReg& r) {
const auto idx = fpr_reg_map_[v->reg.index];
r = oaknut::DReg(idx);
}
static void SetupReg(const hir::Value* v, oaknut::QReg& r) {
const auto idx = fpr_reg_map_[v->reg.index];
r = oaknut::QReg(idx);
}
void EmitBtiJc();
// Gets(and possibly create) an HIR label with the specified name
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_; }
void MarkSourceOffset(const hir::Instr* i);
void DebugBreak();
void Trap(uint16_t trap_type = 0);
void UnimplementedInstr(const hir::Instr* i);
void Call(const hir::Instr* instr, GuestFunction* function);
void CallIndirect(const hir::Instr* instr, const oaknut::XReg& reg);
void CallExtern(const hir::Instr* instr, const Function* function);
void CallNative(void* fn);
void CallNative(uint64_t (*fn)(void* raw_context));
void CallNative(uint64_t (*fn)(void* raw_context, uint64_t arg0));
void CallNative(uint64_t (*fn)(void* raw_context, uint64_t arg0),
uint64_t arg0);
void CallNativeSafe(void* fn);
void SetReturnAddress(uint64_t value);
static oaknut::XReg GetNativeParam(uint32_t param);
static oaknut::XReg GetContextReg();
static oaknut::XReg GetMembaseReg();
void ReloadContext();
void ReloadMembase();
// Moves a 64bit immediate into memory.
static bool ConstantFitsIn32Reg(uint64_t v);
void MovMem64(const oaknut::XRegSp& addr, intptr_t offset, uint64_t v);
uintptr_t GetVConstPtr() const;
uintptr_t GetVConstPtr(VConst id) const;
static constexpr uintptr_t GetVConstOffset(VConst id) {
return sizeof(vec128_t) * id;
}
void LoadConstantV(oaknut::QReg dest, float v);
void LoadConstantV(oaknut::QReg dest, double v);
void LoadConstantV(oaknut::QReg dest, const vec128_t& v);
// Returned addresses are relative to XSP
uintptr_t StashV(int index, const oaknut::QReg& r);
uintptr_t StashConstantV(int index, float v);
uintptr_t StashConstantV(int index, double v);
uintptr_t StashConstantV(int index, const vec128_t& v);
bool IsFeatureEnabled(uint32_t feature_flag) const {
return (feature_flags_ & feature_flag) == feature_flag;
}
FunctionDebugInfo* debug_info() const { return debug_info_; }
size_t stack_size() const { return stack_size_; }
protected:
void* Emplace(const EmitFunctionInfo& func_info,
GuestFunction* function = nullptr);
bool Emit(hir::HIRBuilder* builder, EmitFunctionInfo& func_info);
void EmitGetCurrentThreadId();
void EmitTraceUserCallReturn();
protected:
Processor* processor_ = nullptr;
A64Backend* backend_ = nullptr;
A64CodeCache* code_cache_ = nullptr;
uint32_t feature_flags_ = 0;
std::vector<std::uint32_t> assembly_buffer;
oaknut::Label* epilog_label_ = nullptr;
// Convert from plain-text label-names into oaknut-labels
std::unordered_map<std::string, oaknut::Label> label_lookup_;
hir::Instr* current_instr_ = nullptr;
FunctionDebugInfo* debug_info_ = nullptr;
uint32_t debug_info_flags_ = 0;
FunctionTraceData* trace_data_ = nullptr;
Arena source_map_arena_;
size_t stack_size_ = 0;
static const uint8_t gpr_reg_map_[GPR_COUNT];
static const uint8_t fpr_reg_map_[FPR_COUNT];
};
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_BACKEND_A64_A64_EMITTER_H_
+75
View File
@@ -0,0 +1,75 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/a64/a64_function.h"
#ifdef XE_PLATFORM_MAC
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <mach/vm_map.h>
#include <pthread.h>
#endif
#include "xenia/base/logging.h"
#include "xenia/cpu/backend/a64/a64_backend.h"
#include "xenia/cpu/processor.h"
#include "xenia/cpu/thread_state.h"
#if XE_PLATFORM_MAC && defined(__aarch64__)
thread_local bool jit_thread_initialized = false;
// Initialize JIT execution for the current thread
static void InitializeJITThread() {
if (!jit_thread_initialized) {
// Ensure this thread can execute JIT code by setting execute mode
pthread_jit_write_protect_np(1); // Enable execute, disable write
jit_thread_initialized = true;
}
}
#endif
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
A64Function::A64Function(Module* module, uint32_t address)
: GuestFunction(module, address) {}
A64Function::~A64Function() {
// machine_code_ is freed by code cache.
}
void A64Function::Setup(uint8_t* machine_code, size_t machine_code_length) {
machine_code_ = machine_code;
machine_code_length_ = machine_code_length;
}
bool A64Function::CallImpl(ThreadState* thread_state, uint32_t return_address) {
#if XE_PLATFORM_MAC && defined(__aarch64__)
// Initialize JIT execution for this thread
// This ensures pthread_jit_write_protect_np is set correctly for execution
InitializeJITThread();
#endif
auto backend =
reinterpret_cast<A64Backend*>(thread_state->processor()->backend());
auto thunk = backend->host_to_guest_thunk();
// Make the actual thunk call
thunk(machine_code_, thread_state->context(),
reinterpret_cast<void*>(uintptr_t(return_address)));
return true;
}
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
+44
View File
@@ -0,0 +1,44 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_CPU_BACKEND_A64_A64_FUNCTION_H_
#define XENIA_CPU_BACKEND_A64_A64_FUNCTION_H_
#include "xenia/cpu/function.h"
#include "xenia/cpu/thread_state.h"
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
class A64Function : public GuestFunction {
public:
A64Function(Module* module, uint32_t address);
~A64Function() override;
uint8_t* machine_code() const override { return machine_code_; }
size_t machine_code_length() const override { return machine_code_length_; }
void Setup(uint8_t* machine_code, size_t machine_code_length);
protected:
bool CallImpl(ThreadState* thread_state, uint32_t return_address) override;
private:
uint8_t* machine_code_ = nullptr;
size_t machine_code_length_ = 0;
};
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_BACKEND_A64_A64_FUNCTION_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_CPU_BACKEND_A64_A64_SEQUENCES_H_
#define XENIA_CPU_BACKEND_A64_A64_SEQUENCES_H_
#include "xenia/cpu/hir/instr.h"
#include <unordered_map>
#include "xenia/base/logging.h"
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
class A64Emitter;
typedef bool (*SequenceSelectFn)(A64Emitter&, const hir::Instr*);
// 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;
}
template <typename T>
bool RegisterSingle() {
bool inserted = GetSequenceTable().emplace(T::head_key(), T::Select).second;
if (!inserted) {
XELOGW("A64 sequence registration duplicate key 0x{:08X}", T::head_key());
}
return inserted;
}
template <typename... Ts>
bool RegisterAll() {
bool ok = true;
((ok &= RegisterSingle<Ts>()), ...);
return ok;
}
#define EMITTER_OPCODE_TABLE(name, ...) \
static const bool A64_INSTR_##name = RegisterAll<__VA_ARGS__>();
bool SelectSequence(A64Emitter* e, const hir::Instr* i,
const hir::Instr** new_tail);
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_BACKEND_A64_A64_SEQUENCES_H_
@@ -0,0 +1,129 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_CPU_BACKEND_A64_A64_STACK_LAYOUT_H_
#define XENIA_CPU_BACKEND_A64_A64_STACK_LAYOUT_H_
#include "xenia/base/vec128.h"
#include "xenia/cpu/backend/a64/a64_backend.h"
#include "xenia/cpu/backend/a64/a64_emitter.h"
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
class StackLayout {
public:
/**
* Stack Layout
* ----------------------------
* NOTE: stack must always be 16b aligned.
*
* Thunk stack:
* Non-Volatile Volatile
* +------------------+------------------+
* | arg temp, 3 * 8 | arg temp, 3 * 8 | sp + 0x000
* | | |
* | | |
* +------------------+------------------+
* | rbx | (unused) | sp + 0x018
* +------------------+------------------+
* | rbp | X1 | sp + 0x020
* +------------------+------------------+
* | rcx (Win32) | X2 | sp + 0x028
* +------------------+------------------+
* | rsi (Win32) | X3 | sp + 0x030
* +------------------+------------------+
* | rdi (Win32) | X4 | sp + 0x038
* +------------------+------------------+
* | r12 | X5 | sp + 0x040
* +------------------+------------------+
* | r13 | X6 | sp + 0x048
* +------------------+------------------+
* | r14 | X7 | sp + 0x050
* +------------------+------------------+
* | r15 | X8 | sp + 0x058
* +------------------+------------------+
* | xmm6 (Win32) | X9 | sp + 0x060
* | | |
* +------------------+------------------+
* | xmm7 (Win32) | X10 | sp + 0x070
* | | |
* +------------------+------------------+
* | xmm8 (Win32) | X11 | sp + 0x080
* | | |
* +------------------+------------------+
* | xmm9 (Win32) | X12 | sp + 0x090
* | | |
* +------------------+------------------+
* | xmm10 (Win32) | X13 | sp + 0x0A0
* | | |
* +------------------+------------------+
* | xmm11 (Win32) | X14 | sp + 0x0B0
* | | |
* +------------------+------------------+
* | xmm12 (Win32) | X15 | sp + 0x0C0
* | | |
* +------------------+------------------+
* | xmm13 (Win32) | X16 | sp + 0x0D0
* | | |
* +------------------+------------------+
* | xmm14 (Win32) | X17 | sp + 0x0E0
* | | |
* +------------------+------------------+
* | xmm15 (Win32) | X18 | sp + 0x0F0
* | | |
* +------------------+------------------+
*/
XEPACKEDSTRUCT(Thunk, {
uint64_t arg_temp[3];
uint64_t r[17];
vec128_t xmm[22];
});
static_assert(sizeof(Thunk) % 16 == 0,
"sizeof(Thunk) must be a multiple of 16!");
static const size_t THUNK_STACK_SIZE = sizeof(Thunk);
/**
*
*
* Guest stack:
* +------------------+
* | arg temp, 3 * 8 | sp + 0
* | |
* | |
* +------------------+
* | scratch, 48b | sp + 32(kStashOffset)
* | |
* +------------------+
* | X0 / context | sp + 80
* +------------------+
* | guest ret addr | sp + 88
* +------------------+
* | call ret addr | sp + 96
* +------------------+
* ... locals ...
* +------------------+
* | (return address) |
* +------------------+
*
*/
static const size_t GUEST_STACK_SIZE = 96 + 16;
static const size_t GUEST_CTX_HOME = 80;
static const size_t GUEST_RET_ADDR = 88;
static const size_t GUEST_CALL_RET_ADDR = 96;
};
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_BACKEND_A64_A64_STACK_LAYOUT_H_
+226
View File
@@ -0,0 +1,226 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/a64/a64_tracers.h"
#include <cinttypes>
#include "xenia/base/logging.h"
#include "xenia/base/vec128.h"
#include "xenia/cpu/backend/a64/a64_emitter.h"
#include "xenia/cpu/processor.h"
#include "xenia/cpu/thread_state.h"
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
#define ITRACE 0
#define DTRACE 0
#define TARGET_THREAD 0
bool trace_enabled = true;
#define THREAD_MATCH \
(!TARGET_THREAD || thread_state->thread_id() == TARGET_THREAD)
#define IFLUSH()
#define IPRINT(s) \
if (trace_enabled && THREAD_MATCH) \
xe::logging::AppendLogLine(xe::LogLevel::Debug, 't', s, xe::LogSrc::Cpu)
#define DFLUSH()
#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;
#if ITRACE
mode |= TRACING_INSTR;
#endif // ITRACE
#if DTRACE
mode |= TRACING_DATA;
#endif // DTRACE
return mode;
}
void TraceString(void* raw_context, const char* str) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
IPRINT(str);
IFLUSH();
}
void TraceContextLoadI8(void* raw_context, uint64_t offset, uint8_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = ctx i8 +{}\n", (int8_t)value, value, offset);
}
void TraceContextLoadI16(void* raw_context, uint64_t offset, uint16_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = ctx i16 +{}\n", (int16_t)value, value, offset);
}
void TraceContextLoadI32(void* raw_context, uint64_t offset, uint32_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = ctx i32 +{}\n", (int32_t)value, value, offset);
}
void TraceContextLoadI64(void* raw_context, uint64_t offset, uint64_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = ctx i64 +{}\n", (int64_t)value, value, offset);
}
void TraceContextLoadF32(void* raw_context, uint64_t offset,
float32x4_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = ctx f32 +{}\n", xe::m128_f32<0>(value),
xe::m128_i32<0>(value), offset);
}
void TraceContextLoadF64(void* raw_context, uint64_t offset,
const double* value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
// auto v = _mm_loadu_pd(value);
auto v = vld1q_f64(value);
DPRINT("{} ({:X}) = ctx f64 +{}\n", xe::m128_f64<0>(v), xe::m128_i64<0>(v),
offset);
}
void TraceContextLoadV128(void* raw_context, uint64_t offset,
float32x4_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("[{}, {}, {}, {}] [{:08X}, {:08X}, {:08X}, {:08X}] = ctx v128 +{}\n",
xe::m128_f32<0>(value), xe::m128_f32<1>(value), xe::m128_f32<2>(value),
xe::m128_f32<3>(value), xe::m128_i32<0>(value), xe::m128_i32<1>(value),
xe::m128_i32<2>(value), xe::m128_i32<3>(value), offset);
}
void TraceContextStoreI8(void* raw_context, uint64_t offset, uint8_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("ctx i8 +{} = {} ({:X})\n", offset, (int8_t)value, value);
}
void TraceContextStoreI16(void* raw_context, uint64_t offset, uint16_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("ctx i16 +{} = {} ({:X})\n", offset, (int16_t)value, value);
}
void TraceContextStoreI32(void* raw_context, uint64_t offset, uint32_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("ctx i32 +{} = {} ({:X})\n", offset, (int32_t)value, value);
}
void TraceContextStoreI64(void* raw_context, uint64_t offset, uint64_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("ctx i64 +{} = {} ({:X})\n", offset, (int64_t)value, value);
}
void TraceContextStoreF32(void* raw_context, uint64_t offset,
float32x4_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("ctx f32 +{} = {} ({:X})\n", offset, xe::m128_f32<0>(value),
xe::m128_i32<0>(value));
}
void TraceContextStoreF64(void* raw_context, uint64_t offset,
const double* value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
// auto v = _mm_loadu_pd(value);
auto v = vld1q_f64(value);
DPRINT("ctx f64 +{} = {} ({:X})\n", offset, xe::m128_f64<0>(v),
xe::m128_i64<0>(v));
}
void TraceContextStoreV128(void* raw_context, uint64_t offset,
float32x4_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("ctx v128 +{} = [{}, {}, {}, {}] [{:08X}, {:08X}, {:08X}, {:08X}]\n",
offset, xe::m128_f32<0>(value), xe::m128_f32<1>(value),
xe::m128_f32<2>(value), xe::m128_f32<3>(value), xe::m128_i32<0>(value),
xe::m128_i32<1>(value), xe::m128_i32<2>(value),
xe::m128_i32<3>(value));
}
void TraceMemoryLoadI8(void* raw_context, uint32_t address, uint8_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = load.i8 {:08X}\n", (int8_t)value, value, address);
}
void TraceMemoryLoadI16(void* raw_context, uint32_t address, uint16_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = load.i16 {:08X}\n", (int16_t)value, value, address);
}
void TraceMemoryLoadI32(void* raw_context, uint32_t address, uint32_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = load.i32 {:08X}\n", (int32_t)value, value, address);
}
void TraceMemoryLoadI64(void* raw_context, uint32_t address, uint64_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = load.i64 {:08X}\n", (int64_t)value, value, address);
}
void TraceMemoryLoadF32(void* raw_context, uint32_t address,
float32x4_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = load.f32 {:08X}\n", xe::m128_f32<0>(value),
xe::m128_i32<0>(value), address);
}
void TraceMemoryLoadF64(void* raw_context, uint32_t address,
float64x2_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("{} ({:X}) = load.f64 {:08X}\n", xe::m128_f64<0>(value),
xe::m128_i64<0>(value), address);
}
void TraceMemoryLoadV128(void* raw_context, uint32_t address,
float32x4_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT(
"[{}, {}, {}, {}] [{:08X}, {:08X}, {:08X}, {:08X}] = load.v128 {:08X}\n",
xe::m128_f32<0>(value), xe::m128_f32<1>(value), xe::m128_f32<2>(value),
xe::m128_f32<3>(value), xe::m128_i32<0>(value), xe::m128_i32<1>(value),
xe::m128_i32<2>(value), xe::m128_i32<3>(value), address);
}
void TraceMemoryStoreI8(void* raw_context, uint32_t address, uint8_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("store.i8 {:08X} = {} ({:X})\n", address, (int8_t)value, value);
}
void TraceMemoryStoreI16(void* raw_context, uint32_t address, uint16_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("store.i16 {:08X} = {} ({:X})\n", address, (int16_t)value, value);
}
void TraceMemoryStoreI32(void* raw_context, uint32_t address, uint32_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("store.i32 {:08X} = {} ({:X})\n", address, (int32_t)value, value);
}
void TraceMemoryStoreI64(void* raw_context, uint32_t address, uint64_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("store.i64 {:08X} = {} ({:X})\n", address, (int64_t)value, value);
}
void TraceMemoryStoreF32(void* raw_context, uint32_t address,
float32x4_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("store.f32 {:08X} = {} ({:X})\n", address, xe::m128_f32<0>(value),
xe::m128_i32<0>(value));
}
void TraceMemoryStoreF64(void* raw_context, uint32_t address,
float64x2_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("store.f64 {:08X} = {} ({:X})\n", address, xe::m128_f64<0>(value),
xe::m128_i64<0>(value));
}
void TraceMemoryStoreV128(void* raw_context, uint32_t address,
float32x4_t value) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT(
"store.v128 {:08X} = [{}, {}, {}, {}] [{:08X}, {:08X}, {:08X}, {:08X}]\n",
address, xe::m128_f32<0>(value), xe::m128_f32<1>(value),
xe::m128_f32<2>(value), xe::m128_f32<3>(value), xe::m128_i32<0>(value),
xe::m128_i32<1>(value), xe::m128_i32<2>(value), xe::m128_i32<3>(value));
}
void TraceMemset(void* raw_context, uint32_t address, uint8_t value,
uint32_t length) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
DPRINT("memset {:08X}-{:08X} ({}) = {:02X}", address, address + length,
length, value);
}
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe

Some files were not shown because too many files have changed in this diff Show More