[A64] Import Wunkolo ARM64 backend history

git-subtree-dir: src/xenia/cpu/backend/a64
git-subtree-mainline: 621430444b
git-subtree-split: 8b47bd8b76
This commit is contained in:
Will Martin
2026-01-20 23:45:50 +09:00
22 changed files with 11138 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
/**
******************************************************************************
* 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_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_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);
assert_true((host_address >> 32) == 0);
reinterpret_cast<A64CodeCache*>(backend_->code_cache())
->AddIndirection(function->address(),
static_cast<uint32_t>(host_address));
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 = {0};
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 < 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 < 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_
+342
View File
@@ -0,0 +1,342 @@
/**
******************************************************************************
* 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 "third_party/fmt/include/fmt/format.h"
#include "xenia/base/assert.h"
#include "xenia/base/clock.h"
#include "xenia/base/literals.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/module.h"
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
using namespace xe::literals;
A64CodeCache::A64CodeCache() = default;
A64CodeCache::~A64CodeCache() {
if (indirection_table_base_) {
xe::memory::DeallocFixed(indirection_table_base_, 0,
xe::memory::DeallocationType::kRelease);
}
// Unmap all views and close mapping.
if (mapping_ != xe::memory::kFileMappingHandleInvalid) {
if (generated_code_write_base_ &&
generated_code_write_base_ != generated_code_execute_base_) {
xe::memory::UnmapFileView(mapping_, generated_code_write_base_,
kGeneratedCodeSize);
}
if (generated_code_execute_base_) {
xe::memory::UnmapFileView(mapping_, generated_code_execute_base_,
kGeneratedCodeSize);
}
xe::memory::CloseFileMappingHandle(mapping_, file_name_);
mapping_ = xe::memory::kFileMappingHandleInvalid;
}
}
bool A64CodeCache::Initialize() {
indirection_table_base_ = reinterpret_cast<uint8_t*>(xe::memory::AllocFixed(
reinterpret_cast<void*>(kIndirectionTableBase), kIndirectionTableSize,
xe::memory::AllocationType::kReserve,
xe::memory::PageAccess::kReadWrite));
if (!indirection_table_base_) {
XELOGE("Unable to allocate code cache indirection table");
XELOGE(
"This is likely because the {:X}-{:X} range is in use by some other "
"system DLL",
static_cast<uint64_t>(kIndirectionTableBase),
kIndirectionTableBase + kIndirectionTableSize);
}
// Create mmap file. This allows us to share the code cache with the debugger.
file_name_ = fmt::format("xenia_code_cache_{}", Clock::QueryHostTickCount());
mapping_ = xe::memory::CreateFileMappingHandle(
file_name_, kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite,
false);
if (mapping_ == xe::memory::kFileMappingHandleInvalid) {
XELOGE("Unable to create code cache mmap");
return false;
}
// Map generated code region into the file. Pages are committed as required.
if (xe::memory::IsWritableExecutableMemoryPreferred()) {
generated_code_execute_base_ =
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite, 0));
generated_code_write_base_ = generated_code_execute_base_;
if (!generated_code_execute_base_ || !generated_code_write_base_) {
XELOGE("Unable to allocate code cache generated code storage");
XELOGE(
"This is likely because the {:X}-{:X} range is in use by some other "
"system DLL",
uint64_t(kGeneratedCodeExecuteBase),
uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize));
return false;
}
} else {
generated_code_execute_base_ =
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadOnly, 0));
generated_code_write_base_ =
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
mapping_, reinterpret_cast<void*>(kGeneratedCodeWriteBase),
kGeneratedCodeSize, xe::memory::PageAccess::kReadWrite, 0));
if (!generated_code_execute_base_ || !generated_code_write_base_) {
XELOGE("Unable to allocate code cache generated code storage");
XELOGE(
"This is likely because the {:X}-{:X} and {:X}-{:X} ranges are in "
"use by some other system DLL",
uint64_t(kGeneratedCodeExecuteBase),
uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize),
uint64_t(kGeneratedCodeWriteBase),
uint64_t(kGeneratedCodeWriteBase + kGeneratedCodeSize));
return false;
}
}
// Preallocate the function map to a large, reasonable size.
generated_code_map_.reserve(kMaximumFunctionCount);
return true;
}
void A64CodeCache::set_indirection_default(uint32_t default_value) {
indirection_default_value_ = default_value;
}
void A64CodeCache::AddIndirection(uint32_t guest_address,
uint32_t host_address) {
if (!indirection_table_base_) {
return;
}
uint32_t* indirection_slot = reinterpret_cast<uint32_t*>(
indirection_table_base_ + (guest_address - kIndirectionTableBase));
*indirection_slot = host_address;
}
void A64CodeCache::CommitExecutableRange(uint32_t guest_low,
uint32_t guest_high) {
if (!indirection_table_base_) {
return;
}
// Commit the memory.
xe::memory::AllocFixed(
indirection_table_base_ + (guest_low - kIndirectionTableBase),
guest_high - guest_low, xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kReadWrite);
// Fill memory with the default value.
uint32_t* p = reinterpret_cast<uint32_t*>(indirection_table_base_);
for (uint32_t address = guest_low; address < guest_high; ++address) {
p[(address - kIndirectionTableBase) / 4] = indirection_default_value_;
}
}
void A64CodeCache::PlaceHostCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info,
void*& code_execute_address_out,
void*& code_write_address_out) {
// Same for now. We may use different pools or whatnot later on, like when
// we only want to place guest code in a serialized cache on disk.
PlaceGuestCode(guest_address, machine_code, func_info, nullptr,
code_execute_address_out, code_write_address_out);
}
void A64CodeCache::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) {
// Hold a lock while we bump the pointers up. This is important as the
// unwind table requires entries AND code to be sorted in order.
size_t low_mark;
size_t high_mark;
uint8_t* code_execute_address;
UnwindReservation unwind_reservation;
{
auto global_lock = global_critical_region_.Acquire();
low_mark = generated_code_offset_;
// Reserve code.
// Always move the code to land on 16b alignment.
code_execute_address =
generated_code_execute_base_ + generated_code_offset_;
code_execute_address_out = code_execute_address;
uint8_t* code_write_address =
generated_code_write_base_ + generated_code_offset_;
code_write_address_out = code_write_address;
generated_code_offset_ += xe::round_up(func_info.code_size.total, 16);
auto tail_write_address =
generated_code_write_base_ + generated_code_offset_;
// Reserve unwind info.
// We go on the high size of the unwind info as we don't know how big we
// need it, and a few extra bytes of padding isn't the worst thing.
unwind_reservation = RequestUnwindReservation(generated_code_write_base_ +
generated_code_offset_);
generated_code_offset_ += xe::round_up(unwind_reservation.data_size, 16);
auto end_write_address =
generated_code_write_base_ + generated_code_offset_;
high_mark = generated_code_offset_;
// Store in map. It is maintained in sorted order of host PC dependent on
// us also being append-only.
generated_code_map_.emplace_back(
(uint64_t(code_execute_address - generated_code_execute_base_) << 32) |
generated_code_offset_,
function_info);
// TODO(DrChat): The following code doesn't really need to be under the
// global lock except for PlaceCode (but it depends on the previous code
// already being ran)
// If we are going above the high water mark of committed memory, commit
// some more. It's ok if multiple threads do this, as redundant commits
// aren't harmful.
size_t old_commit_mark, new_commit_mark;
do {
old_commit_mark = generated_code_commit_mark_;
if (high_mark <= old_commit_mark) break;
new_commit_mark = old_commit_mark + 16_MiB;
if (generated_code_execute_base_ == generated_code_write_base_) {
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kExecuteReadWrite);
} else {
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kExecuteReadOnly);
xe::memory::AllocFixed(generated_code_write_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kReadWrite);
}
} while (generated_code_commit_mark_.compare_exchange_weak(
old_commit_mark, new_commit_mark));
// Copy code.
std::memcpy(code_write_address, machine_code, func_info.code_size.total);
// Fill unused slots with 0x00
std::memset(tail_write_address, 0x00,
static_cast<size_t>(end_write_address - tail_write_address));
// Notify subclasses of placed code.
PlaceCode(guest_address, machine_code, func_info, code_execute_address,
unwind_reservation);
}
// Now that everything is ready, fix up the indirection table.
// Note that we do support code that doesn't have an indirection fixup, so
// ignore those when we see them.
if (guest_address && indirection_table_base_) {
uint32_t* indirection_slot = reinterpret_cast<uint32_t*>(
indirection_table_base_ + (guest_address - kIndirectionTableBase));
*indirection_slot =
uint32_t(reinterpret_cast<uint64_t>(code_execute_address));
}
}
uint32_t A64CodeCache::PlaceData(const void* data, size_t length) {
// Hold a lock while we bump the pointers up.
size_t high_mark;
uint8_t* data_address = nullptr;
{
auto global_lock = global_critical_region_.Acquire();
// Reserve code.
// Always move the code to land on 16b alignment.
data_address = generated_code_write_base_ + generated_code_offset_;
generated_code_offset_ += xe::round_up(length, 16);
high_mark = generated_code_offset_;
}
// If we are going above the high water mark of committed memory, commit some
// more. It's ok if multiple threads do this, as redundant commits aren't
// harmful.
size_t old_commit_mark, new_commit_mark;
do {
old_commit_mark = generated_code_commit_mark_;
if (high_mark <= old_commit_mark) break;
new_commit_mark = old_commit_mark + 16_MiB;
if (generated_code_execute_base_ == generated_code_write_base_) {
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kExecuteReadWrite);
} else {
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kExecuteReadOnly);
xe::memory::AllocFixed(generated_code_write_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kReadWrite);
}
} while (generated_code_commit_mark_.compare_exchange_weak(old_commit_mark,
new_commit_mark));
// Copy code.
std::memcpy(data_address, data, length);
return uint32_t(uintptr_t(data_address));
}
GuestFunction* A64CodeCache::LookupFunction(uint64_t host_pc) {
uint32_t key = uint32_t(host_pc - kGeneratedCodeExecuteBase);
void* fn_entry = std::bsearch(
&key, generated_code_map_.data(), generated_code_map_.size() + 1,
sizeof(std::pair<uint32_t, Function*>),
[](const void* key_ptr, const void* element_ptr) {
auto key = *reinterpret_cast<const uint32_t*>(key_ptr);
auto element =
reinterpret_cast<const std::pair<uint64_t, GuestFunction*>*>(
element_ptr);
if (key < (element->first >> 32)) {
return -1;
} else if (key > uint32_t(element->first)) {
return 1;
} else {
return 0;
}
});
if (fn_entry) {
return reinterpret_cast<const std::pair<uint64_t, GuestFunction*>*>(
fn_entry)
->second;
} else {
return nullptr;
}
}
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
+151
View File
@@ -0,0 +1,151 @@
/**
******************************************************************************
* 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 {
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 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);
void AddIndirection(uint32_t guest_address, uint32_t host_address);
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;
protected:
// All executable code falls within 0x80000000 to 0x9FFFFFFF, so we can
// only map enough for lookups within that range.
static const size_t kIndirectionTableSize = 0x1FFFFFFF;
static const uintptr_t kIndirectionTableBase = 0x80000000;
// 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) {}
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.
uint32_t indirection_default_value_ = 0xFEEDF00D;
// Fixed at kIndirectionTableBase in host space, holding 4 byte pointers into
// the generated code table that correspond to the PPC functions in guest
// space.
uint8_t* indirection_table_base_ = nullptr;
// 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,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
+267
View File
@@ -0,0 +1,267 @@
/**
******************************************************************************
* 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,
};
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);
}
// 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& 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_
+45
View File
@@ -0,0 +1,45 @@
/**
******************************************************************************
* 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"
#include "xenia/cpu/backend/a64/a64_backend.h"
#include "xenia/cpu/processor.h"
#include "xenia/cpu/thread_state.h"
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) {
auto backend =
reinterpret_cast<A64Backend*>(thread_state->processor()->backend());
auto thunk = backend->host_to_guest_thunk();
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
+51
View File
@@ -0,0 +1,51 @@
/**
******************************************************************************
* 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>
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
class A64Emitter;
typedef bool (*SequenceSelectFn)(A64Emitter&, const hir::Instr*);
extern std::unordered_map<uint32_t, SequenceSelectFn> sequence_table;
template <typename T>
bool Register() {
sequence_table.insert({T::head_key(), T::Select});
return true;
}
template <typename T, typename Tn, typename... Ts>
static bool Register() {
bool b = true;
b = b && Register<T>(); // Call the above function
b = b && Register<Tn, Ts...>(); // Call ourself again (recursively)
return b;
}
#define EMITTER_OPCODE_TABLE(name, ...) \
const auto A64_INSTR_##name = Register<__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_
+225
View File
@@ -0,0 +1,225 @@
/**
******************************************************************************
* 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)
#define DFLUSH()
#define DPRINT(...) \
if (trace_enabled && THREAD_MATCH) \
xe::logging::AppendLogLineFormat(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
+82
View File
@@ -0,0 +1,82 @@
/**
******************************************************************************
* 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_TRACERS_H_
#define XENIA_CPU_BACKEND_A64_A64_TRACERS_H_
#include <arm64_neon.h>
#include <cstdint>
namespace xe {
namespace cpu {
namespace backend {
namespace a64 {
class A64Emitter;
enum TracingMode {
TRACING_INSTR = (1 << 1),
TRACING_DATA = (1 << 2),
};
uint32_t GetTracingMode();
inline bool IsTracingInstr() { return (GetTracingMode() & TRACING_INSTR) != 0; }
inline bool IsTracingData() { return (GetTracingMode() & TRACING_DATA) != 0; }
void TraceString(void* raw_context, const char* str);
void TraceContextLoadI8(void* raw_context, uint64_t offset, uint8_t value);
void TraceContextLoadI16(void* raw_context, uint64_t offset, uint16_t value);
void TraceContextLoadI32(void* raw_context, uint64_t offset, uint32_t value);
void TraceContextLoadI64(void* raw_context, uint64_t offset, uint64_t value);
void TraceContextLoadF32(void* raw_context, uint64_t offset, float32x4_t value);
void TraceContextLoadF64(void* raw_context, uint64_t offset,
const double* value);
void TraceContextLoadV128(void* raw_context, uint64_t offset,
float32x4_t value);
void TraceContextStoreI8(void* raw_context, uint64_t offset, uint8_t value);
void TraceContextStoreI16(void* raw_context, uint64_t offset, uint16_t value);
void TraceContextStoreI32(void* raw_context, uint64_t offset, uint32_t value);
void TraceContextStoreI64(void* raw_context, uint64_t offset, uint64_t value);
void TraceContextStoreF32(void* raw_context, uint64_t offset,
float32x4_t value);
void TraceContextStoreF64(void* raw_context, uint64_t offset,
const double* value);
void TraceContextStoreV128(void* raw_context, uint64_t offset,
float32x4_t value);
void TraceMemoryLoadI8(void* raw_context, uint32_t address, uint8_t value);
void TraceMemoryLoadI16(void* raw_context, uint32_t address, uint16_t value);
void TraceMemoryLoadI32(void* raw_context, uint32_t address, uint32_t value);
void TraceMemoryLoadI64(void* raw_context, uint32_t address, uint64_t value);
void TraceMemoryLoadF32(void* raw_context, uint32_t address, float32x4_t value);
void TraceMemoryLoadF64(void* raw_context, uint32_t address, float64x2_t value);
void TraceMemoryLoadV128(void* raw_context, uint32_t address,
float32x4_t value);
void TraceMemoryStoreI8(void* raw_context, uint32_t address, uint8_t value);
void TraceMemoryStoreI16(void* raw_context, uint32_t address, uint16_t value);
void TraceMemoryStoreI32(void* raw_context, uint32_t address, uint32_t value);
void TraceMemoryStoreI64(void* raw_context, uint32_t address, uint64_t value);
void TraceMemoryStoreF32(void* raw_context, uint32_t address,
float32x4_t value);
void TraceMemoryStoreF64(void* raw_context, uint32_t address,
float64x2_t value);
void TraceMemoryStoreV128(void* raw_context, uint32_t address,
float32x4_t value);
void TraceMemset(void* raw_context, uint32_t address, uint8_t value,
uint32_t length);
} // namespace a64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_BACKEND_A64_A64_TRACERS_H_

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