mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[ARM64] Initial commit for arm64 backend
Based entirely off existing xbyak x86 implementation and available tests. Still needs a lot of optimization and testing on non-Windows platforms. So far passes all tests and boots at least some games on Windows.
This commit is contained in:
@@ -92,6 +92,8 @@ node_modules/.bin/
|
||||
/scratch/
|
||||
|
||||
/build/
|
||||
/build-arm64/
|
||||
/build-x64/
|
||||
|
||||
# ==============================================================================
|
||||
# Local-only paths
|
||||
|
||||
+33
-6
@@ -6,6 +6,27 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# Detect target architecture.
|
||||
# - VS generator: CMAKE_GENERATOR_PLATFORM (from -A flag) takes priority.
|
||||
# - Ninja/Makefiles: CMAKE_SYSTEM_PROCESSOR (set via -DCMAKE_SYSTEM_NAME
|
||||
# + -DCMAKE_SYSTEM_PROCESSOR for cross-compile, or auto-detected natively).
|
||||
if(CMAKE_GENERATOR_PLATFORM)
|
||||
if(CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64")
|
||||
set(XE_TARGET_AARCH64 TRUE)
|
||||
set(XE_TARGET_X86_64 FALSE)
|
||||
else()
|
||||
set(XE_TARGET_AARCH64 FALSE)
|
||||
set(XE_TARGET_X86_64 TRUE)
|
||||
endif()
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
|
||||
set(XE_TARGET_AARCH64 TRUE)
|
||||
set(XE_TARGET_X86_64 FALSE)
|
||||
else()
|
||||
set(XE_TARGET_AARCH64 FALSE)
|
||||
set(XE_TARGET_X86_64 TRUE)
|
||||
endif()
|
||||
message(STATUS "Target architecture: XE_TARGET_AARCH64=${XE_TARGET_AARCH64} XE_TARGET_X86_64=${XE_TARGET_X86_64} (CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR})")
|
||||
|
||||
# Options
|
||||
option(XENIA_BUILD_TESTS "Build test suites" OFF)
|
||||
option(XENIA_BUILD_MISC "Build misc subprojects (trace viewers, shader compiler, vfs-dump, demos)" OFF)
|
||||
@@ -35,10 +56,12 @@ else()
|
||||
set(XE_PLATFORM_NAME "Linux")
|
||||
endif()
|
||||
|
||||
# Output directories — use CMAKE_BINARY_DIR so each build tree (build/, build-arm64/,
|
||||
# build/vs-arm64/, etc.) gets its own output paths.
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${XE_PLATFORM_NAME}")
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${XE_PLATFORM_NAME}")
|
||||
# Output directories — strip any vs-* nesting from CMAKE_BINARY_DIR so that
|
||||
# VS and Ninja builds for the same architecture share an output directory,
|
||||
# while different architectures (build/ vs build-arm64/) stay separate.
|
||||
string(REGEX REPLACE "/vs-[^/]+$" "" XE_OUTPUT_ROOT "${CMAKE_BINARY_DIR}")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${XE_OUTPUT_ROOT}/bin/${XE_PLATFORM_NAME}")
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${XE_OUTPUT_ROOT}/bin/${XE_PLATFORM_NAME}")
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/obj/${XE_PLATFORM_NAME}")
|
||||
|
||||
# Create scratch/ if needed
|
||||
@@ -78,9 +101,11 @@ if(MSVC)
|
||||
add_compile_options(
|
||||
/utf-8 # Build correctly on systems with non-Latin codepages
|
||||
/wd4201 # Nameless struct/unions are ok
|
||||
/arch:AVX # AVX vector extensions
|
||||
/MP # Multi-processor compilation
|
||||
)
|
||||
if(XE_TARGET_X86_64)
|
||||
add_compile_options(/arch:AVX)
|
||||
endif()
|
||||
add_compile_definitions(
|
||||
_CRT_NONSTDC_NO_DEPRECATE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
@@ -139,7 +164,9 @@ if(MSVC)
|
||||
|
||||
else()
|
||||
# GCC/Clang (Linux)
|
||||
add_compile_options(-mavx)
|
||||
if(XE_TARGET_X86_64)
|
||||
add_compile_options(-mavx)
|
||||
endif()
|
||||
|
||||
# Disable specific warnings for C++
|
||||
add_compile_options(
|
||||
|
||||
@@ -23,6 +23,22 @@
|
||||
},
|
||||
"architecture": "x64",
|
||||
"binaryDir": "${sourceDir}/build"
|
||||
},
|
||||
{
|
||||
"name": "vs-arm64",
|
||||
"displayName": "Visual Studio 2022 (ARM64)",
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
},
|
||||
"architecture": "ARM64",
|
||||
"toolset": "host=x64",
|
||||
"binaryDir": "${sourceDir}/build/vs-arm64",
|
||||
"cacheVariables": {
|
||||
"CMAKE_SYSTEM_PROCESSOR": "ARM64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
@@ -58,6 +74,21 @@
|
||||
"name": "vs-checked",
|
||||
"configurePreset": "vs",
|
||||
"configuration": "Checked"
|
||||
},
|
||||
{
|
||||
"name": "vs-arm64-debug",
|
||||
"configurePreset": "vs-arm64",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "vs-arm64-release",
|
||||
"configurePreset": "vs-arm64",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "vs-arm64-checked",
|
||||
"configurePreset": "vs-arm64",
|
||||
"configuration": "Checked"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ include(CMakeParseArguments)
|
||||
|
||||
# Platform suffix lists for file filtering
|
||||
set(XE_PLATFORM_SUFFIXES
|
||||
_win _linux _posix _gnulinux _x11 _gtk _android _mac
|
||||
_win _linux _posix _gnulinux _x11 _gtk _android _mac _amd64 _arm64
|
||||
)
|
||||
|
||||
# xe_platform_sources(target base_path [RECURSIVE])
|
||||
@@ -78,6 +78,17 @@ function(xe_platform_sources target base_path)
|
||||
endif()
|
||||
|
||||
list(APPEND _sources ${_plat_sources})
|
||||
|
||||
# Add back architecture-specific files
|
||||
if(XE_TARGET_X86_64)
|
||||
file(${glob_mode} _arch_sources "${base_path}/*_amd64.h" "${base_path}/*_amd64.cc")
|
||||
elseif(XE_TARGET_AARCH64)
|
||||
file(${glob_mode} _arch_sources "${base_path}/*_arm64.h" "${base_path}/*_arm64.cc")
|
||||
endif()
|
||||
if(_arch_sources)
|
||||
list(APPEND _sources ${_arch_sources})
|
||||
endif()
|
||||
|
||||
target_sources(${target} PRIVATE ${_sources})
|
||||
endfunction()
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@ xe_target_defaults(xenia-core)
|
||||
# All subdirectories
|
||||
add_subdirectory(base)
|
||||
add_subdirectory(cpu)
|
||||
add_subdirectory(cpu/backend/x64)
|
||||
if(XE_TARGET_X86_64)
|
||||
add_subdirectory(cpu/backend/x64)
|
||||
endif()
|
||||
if(XE_TARGET_AARCH64)
|
||||
add_subdirectory(cpu/backend/a64)
|
||||
endif()
|
||||
add_subdirectory(apu)
|
||||
add_subdirectory(apu/nop)
|
||||
add_subdirectory(gpu)
|
||||
|
||||
@@ -24,17 +24,19 @@ if(WIN32)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/main_resources.rc
|
||||
${PROJECT_SOURCE_DIR}/src/xenia/base/app_win32.manifest
|
||||
)
|
||||
# main_init_win.cc needs SSE2 only (not AVX)
|
||||
set_source_files_properties(
|
||||
${PROJECT_SOURCE_DIR}/src/xenia/base/main_init_win.cc
|
||||
PROPERTIES COMPILE_OPTIONS "/arch:SSE2"
|
||||
)
|
||||
if(XE_TARGET_X86_64)
|
||||
# main_init_win.cc needs SSE2 only (not AVX)
|
||||
set_source_files_properties(
|
||||
${PROJECT_SOURCE_DIR}/src/xenia/base/main_init_win.cc
|
||||
PROPERTIES COMPILE_OPTIONS "/arch:SSE2"
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
target_sources(xenia-app PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/src/xenia/base/main_init_posix.cc
|
||||
${PROJECT_SOURCE_DIR}/src/xenia/ui/windowed_app_main_posix.cc
|
||||
)
|
||||
if(NOT MSVC)
|
||||
if(NOT MSVC AND XE_TARGET_X86_64)
|
||||
set_source_files_properties(
|
||||
${PROJECT_SOURCE_DIR}/src/xenia/base/main_init_posix.cc
|
||||
PROPERTIES COMPILE_OPTIONS "-msse2;-mno-avx"
|
||||
@@ -88,9 +90,11 @@ target_link_libraries(xenia-app PRIVATE
|
||||
xenia-hid-sdl
|
||||
)
|
||||
|
||||
# x64 backend
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
|
||||
# Architecture-specific backend
|
||||
if(XE_TARGET_X86_64)
|
||||
target_link_libraries(xenia-app PRIVATE xenia-cpu-backend-x64)
|
||||
elseif(XE_TARGET_AARCH64)
|
||||
target_link_libraries(xenia-app PRIVATE xenia-cpu-backend-a64)
|
||||
endif()
|
||||
|
||||
# Platform-specific libraries
|
||||
|
||||
@@ -29,6 +29,57 @@ constexpr size_t kMaxHandlerCount = 8;
|
||||
// Executed in order.
|
||||
std::pair<ExceptionHandler::Handler, void*> handlers_[kMaxHandlerCount];
|
||||
|
||||
static void CaptureThreadContext(HostThreadContext& thread_context,
|
||||
PCONTEXT ctx) {
|
||||
#if XE_ARCH_AMD64
|
||||
thread_context.rip = ctx->Rip;
|
||||
thread_context.eflags = ctx->EFlags;
|
||||
std::memcpy(thread_context.int_registers, &ctx->Rax,
|
||||
sizeof(thread_context.int_registers));
|
||||
std::memcpy(thread_context.xmm_registers, &ctx->Xmm0,
|
||||
sizeof(thread_context.xmm_registers));
|
||||
#elif XE_ARCH_ARM64
|
||||
thread_context.pc = ctx->Pc;
|
||||
thread_context.pstate = ctx->Cpsr;
|
||||
thread_context.sp = ctx->Sp;
|
||||
std::memcpy(thread_context.x, &ctx->X0, sizeof(thread_context.x));
|
||||
std::memcpy(thread_context.v, &ctx->V[0], sizeof(thread_context.v));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void RestoreThreadContext(PCONTEXT ctx,
|
||||
const HostThreadContext& thread_context,
|
||||
const Exception& ex) {
|
||||
#if XE_ARCH_AMD64
|
||||
ctx->Rip = thread_context.rip;
|
||||
ctx->EFlags = thread_context.eflags;
|
||||
uint32_t modified_register_index;
|
||||
uint16_t modified_int_registers_remaining = ex.modified_int_registers();
|
||||
while (xe::bit_scan_forward(modified_int_registers_remaining,
|
||||
&modified_register_index)) {
|
||||
modified_int_registers_remaining &=
|
||||
~(UINT16_C(1) << modified_register_index);
|
||||
(&ctx->Rax)[modified_register_index] =
|
||||
thread_context.int_registers[modified_register_index];
|
||||
}
|
||||
uint16_t modified_xmm_registers_remaining = ex.modified_xmm_registers();
|
||||
while (xe::bit_scan_forward(modified_xmm_registers_remaining,
|
||||
&modified_register_index)) {
|
||||
modified_xmm_registers_remaining &=
|
||||
~(UINT16_C(1) << modified_register_index);
|
||||
std::memcpy(&ctx->Xmm0 + modified_register_index,
|
||||
&thread_context.xmm_registers[modified_register_index],
|
||||
sizeof(vec128_t));
|
||||
}
|
||||
#elif XE_ARCH_ARM64
|
||||
ctx->Pc = thread_context.pc;
|
||||
ctx->Cpsr = thread_context.pstate;
|
||||
ctx->Sp = thread_context.sp;
|
||||
std::memcpy(&ctx->X0, thread_context.x, sizeof(thread_context.x));
|
||||
std::memcpy(&ctx->V[0], thread_context.v, sizeof(thread_context.v));
|
||||
#endif
|
||||
}
|
||||
|
||||
LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
|
||||
// Visual Studio SetThreadName.
|
||||
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
|
||||
@@ -36,12 +87,7 @@ LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
|
||||
}
|
||||
|
||||
HostThreadContext thread_context;
|
||||
thread_context.rip = ex_info->ContextRecord->Rip;
|
||||
thread_context.eflags = ex_info->ContextRecord->EFlags;
|
||||
std::memcpy(thread_context.int_registers, &ex_info->ContextRecord->Rax,
|
||||
sizeof(thread_context.int_registers));
|
||||
std::memcpy(thread_context.xmm_registers, &ex_info->ContextRecord->Xmm0,
|
||||
sizeof(thread_context.xmm_registers));
|
||||
CaptureThreadContext(thread_context, ex_info->ContextRecord);
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx
|
||||
// https://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx
|
||||
@@ -78,26 +124,7 @@ LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
|
||||
for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) {
|
||||
if (handlers_[i].first(&ex, handlers_[i].second)) {
|
||||
// Exception handled.
|
||||
ex_info->ContextRecord->Rip = thread_context.rip;
|
||||
ex_info->ContextRecord->EFlags = thread_context.eflags;
|
||||
uint32_t modified_register_index;
|
||||
uint16_t modified_int_registers_remaining = ex.modified_int_registers();
|
||||
while (xe::bit_scan_forward(modified_int_registers_remaining,
|
||||
&modified_register_index)) {
|
||||
modified_int_registers_remaining &=
|
||||
~(UINT16_C(1) << modified_register_index);
|
||||
(&ex_info->ContextRecord->Rax)[modified_register_index] =
|
||||
thread_context.int_registers[modified_register_index];
|
||||
}
|
||||
uint16_t modified_xmm_registers_remaining = ex.modified_xmm_registers();
|
||||
while (xe::bit_scan_forward(modified_xmm_registers_remaining,
|
||||
&modified_register_index)) {
|
||||
modified_xmm_registers_remaining &=
|
||||
~(UINT16_C(1) << modified_register_index);
|
||||
std::memcpy(&ex_info->ContextRecord->Xmm0 + modified_register_index,
|
||||
&thread_context.xmm_registers[modified_register_index],
|
||||
sizeof(vec128_t));
|
||||
}
|
||||
RestoreThreadContext(ex_info->ContextRecord, thread_context, ex);
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
add_library(xenia-cpu-backend-a64 STATIC)
|
||||
xe_platform_sources(xenia-cpu-backend-a64 ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_definitions(xenia-cpu-backend-a64 PRIVATE
|
||||
CAPSTONE_HAS_ARM64
|
||||
CAPSTONE_USE_SYS_DYN_MEM
|
||||
)
|
||||
target_include_directories(xenia-cpu-backend-a64 PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/third_party/capstone/include
|
||||
${PROJECT_SOURCE_DIR}/third_party/xbyak_aarch64/xbyak_aarch64
|
||||
)
|
||||
target_link_libraries(xenia-cpu-backend-a64 PUBLIC capstone fmt xenia-base xenia-cpu xbyak_aarch64)
|
||||
xe_target_defaults(xenia-cpu-backend-a64)
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/backend/a64/a64_assembler.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
#include "third_party/capstone/include/capstone/aarch64.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_ARM, &capstone_handle_) != CS_ERR_OK) {
|
||||
assert_always("Failed to initialize capstone for ARM64");
|
||||
}
|
||||
cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_OFF);
|
||||
}
|
||||
|
||||
A64Assembler::~A64Assembler() {
|
||||
emitter_.reset();
|
||||
|
||||
if (capstone_handle_) {
|
||||
cs_close(&capstone_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool A64Assembler::Initialize() {
|
||||
if (!Assembler::Initialize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
emitter_.reset(new A64Emitter(a64_backend_, &allocator_));
|
||||
|
||||
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 -> ARM64.
|
||||
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.
|
||||
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
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_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/a64/a64_emitter.h"
|
||||
#include "xenia/cpu/backend/assembler.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
namespace a64 {
|
||||
|
||||
class A64Backend;
|
||||
|
||||
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_;
|
||||
XbyakA64Allocator allocator_;
|
||||
uintptr_t capstone_handle_ = 0;
|
||||
|
||||
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
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CPU_BACKEND_A64_A64_BACKEND_H_
|
||||
#define XENIA_CPU_BACKEND_A64_A64_BACKEND_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/base/bit_map.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/cpu/backend/backend.h"
|
||||
|
||||
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)();
|
||||
|
||||
// Place guest trampolines in an address range that the HV normally occupies.
|
||||
static constexpr uint32_t GUEST_TRAMPOLINE_BASE = 0x80000000;
|
||||
static constexpr uint32_t GUEST_TRAMPOLINE_END = 0x80040000;
|
||||
static constexpr uint32_t GUEST_TRAMPOLINE_MIN_LEN = 8;
|
||||
static constexpr uint32_t MAX_GUEST_TRAMPOLINES =
|
||||
(GUEST_TRAMPOLINE_END - GUEST_TRAMPOLINE_BASE) / GUEST_TRAMPOLINE_MIN_LEN;
|
||||
|
||||
#define A64_RESERVE_BLOCK_SHIFT 16
|
||||
#define A64_RESERVE_NUM_ENTRIES \
|
||||
((1024ULL * 1024ULL * 1024ULL * 4ULL) >> A64_RESERVE_BLOCK_SHIFT)
|
||||
|
||||
struct ReserveHelper {
|
||||
uint64_t blocks[A64_RESERVE_NUM_ENTRIES / 64];
|
||||
|
||||
ReserveHelper() { memset(blocks, 0, sizeof(blocks)); }
|
||||
};
|
||||
|
||||
struct A64BackendStackpoint {
|
||||
uint64_t host_stack_;
|
||||
unsigned guest_stack_;
|
||||
unsigned guest_return_address_;
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
kA64BackendFPCRModeBit = 0,
|
||||
kA64BackendHasReserveBit = 1,
|
||||
kA64BackendNJMOn = 2,
|
||||
kA64BackendNonIEEEMode = 3,
|
||||
};
|
||||
|
||||
// Located prior to the context register (x20) in memory.
|
||||
struct A64BackendContext {
|
||||
// Scratch vectors for helper routines.
|
||||
// Using uint8_t[16] instead of NEON intrinsic types to avoid including
|
||||
// arm_neon.h in the header.
|
||||
alignas(16) uint8_t helper_scratch_v128s[4][16];
|
||||
union {
|
||||
uint64_t helper_scratch_u64s[8];
|
||||
uint32_t helper_scratch_u32s[16];
|
||||
};
|
||||
ReserveHelper* reserve_helper_;
|
||||
uint64_t cached_reserve_value_;
|
||||
uint64_t* guest_tick_count;
|
||||
A64BackendStackpoint* stackpoints;
|
||||
uint64_t cached_reserve_offset;
|
||||
uint32_t cached_reserve_bit;
|
||||
unsigned int current_stackpoint_depth;
|
||||
unsigned int fpcr_fpu;
|
||||
unsigned int fpcr_vmx;
|
||||
// bit 0 = 0 if fpcr is fpu, else it is vmx
|
||||
// bit 1 = got reserve
|
||||
unsigned int flags;
|
||||
unsigned int Ox1000; // constant 0x1000
|
||||
};
|
||||
|
||||
// Default FPCR for FPU mode (round to nearest, no flush to zero).
|
||||
constexpr unsigned int DEFAULT_FPU_FPCR = 0;
|
||||
// Default FPCR for VMX mode (flush to zero, default NaN).
|
||||
constexpr unsigned int DEFAULT_VMX_FPCR = (1 << 24) | (1 << 25); // FZ | DN
|
||||
|
||||
class A64Backend : public Backend {
|
||||
public:
|
||||
static constexpr uint32_t kForceReturnAddress = 0x9FFF0000u;
|
||||
|
||||
explicit A64Backend();
|
||||
~A64Backend() override;
|
||||
|
||||
A64CodeCache* code_cache() const { return code_cache_.get(); }
|
||||
uintptr_t emitter_data() const { return emitter_data_; }
|
||||
|
||||
HostToGuestThunk host_to_guest_thunk() const { return host_to_guest_thunk_; }
|
||||
GuestToHostThunk guest_to_host_thunk() const { return guest_to_host_thunk_; }
|
||||
ResolveFunctionThunk resolve_function_thunk() const {
|
||||
return resolve_function_thunk_;
|
||||
}
|
||||
void* synchronize_guest_and_host_stack_helper() const {
|
||||
return synchronize_guest_and_host_stack_helper_;
|
||||
}
|
||||
|
||||
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;
|
||||
void InitializeBackendContext(void* ctx) override;
|
||||
void DeinitializeBackendContext(void* ctx) override;
|
||||
void PrepareForReentry(void* ctx) override;
|
||||
|
||||
A64BackendContext* BackendContextForGuestContext(void* ctx) {
|
||||
return reinterpret_cast<A64BackendContext*>(
|
||||
reinterpret_cast<intptr_t>(ctx) - sizeof(A64BackendContext));
|
||||
}
|
||||
|
||||
uint32_t CreateGuestTrampoline(GuestTrampolineProc proc, void* userdata1,
|
||||
void* userdata2, bool long_term) override;
|
||||
void FreeGuestTrampoline(uint32_t trampoline_addr) override;
|
||||
void SetGuestRoundingMode(void* ctx, unsigned int mode) override;
|
||||
bool PopulatePseudoStacktrace(GuestPseudoStackTrace* st) override;
|
||||
|
||||
void RecordMMIOExceptionForGuestInstruction(void* host_address);
|
||||
|
||||
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_ = nullptr;
|
||||
GuestToHostThunk guest_to_host_thunk_ = nullptr;
|
||||
ResolveFunctionThunk resolve_function_thunk_ = nullptr;
|
||||
void* synchronize_guest_and_host_stack_helper_ = nullptr;
|
||||
|
||||
public:
|
||||
void* try_acquire_reservation_helper_ = nullptr;
|
||||
void* reserved_store_32_helper = nullptr;
|
||||
void* reserved_store_64_helper = nullptr;
|
||||
|
||||
private:
|
||||
alignas(64) ReserveHelper reserve_helper_;
|
||||
BitMap guest_trampoline_address_bitmap_;
|
||||
uint8_t* guest_trampoline_memory_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace a64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_BACKEND_A64_A64_BACKEND_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/backend/a64/a64_code_cache.h"
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
#if XE_PLATFORM_WIN32
|
||||
#include "xenia/base/platform_win.h"
|
||||
#endif
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
namespace a64 {
|
||||
|
||||
bool A64CodeCache::Initialize() { return CodeCacheBase::Initialize(); }
|
||||
|
||||
void A64CodeCache::FillCode(void* write_address, size_t size) {
|
||||
// Fill with BRK #0 (0xD4200000), 4-byte aligned.
|
||||
constexpr uint32_t kBrk0 = 0xD4200000;
|
||||
auto* p = reinterpret_cast<uint32_t*>(write_address);
|
||||
auto* end =
|
||||
reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(write_address) + size);
|
||||
for (; p < end; ++p) {
|
||||
*p = kBrk0;
|
||||
}
|
||||
}
|
||||
|
||||
void A64CodeCache::FlushCodeRange(void* address, size_t size) {
|
||||
#if XE_PLATFORM_WIN32
|
||||
FlushInstructionCache(GetCurrentProcess(), address, size);
|
||||
#else
|
||||
__builtin___clear_cache(
|
||||
reinterpret_cast<char*>(address),
|
||||
reinterpret_cast<char*>(static_cast<uint8_t*>(address) + size));
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace a64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CPU_BACKEND_A64_A64_CODE_CACHE_H_
|
||||
#define XENIA_CPU_BACKEND_A64_A64_CODE_CACHE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/cpu/backend/code_cache_base.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
namespace a64 {
|
||||
|
||||
class A64CodeCache : public CodeCacheBase<A64CodeCache> {
|
||||
public:
|
||||
~A64CodeCache() override = default;
|
||||
|
||||
static std::unique_ptr<A64CodeCache> Create();
|
||||
|
||||
virtual bool Initialize();
|
||||
|
||||
void* LookupUnwindInfo(uint64_t host_pc) override { return nullptr; }
|
||||
|
||||
// CRTP hooks for CodeCacheBase.
|
||||
void FillCode(void* write_address, size_t size);
|
||||
void FlushCodeRange(void* address, size_t size);
|
||||
|
||||
// Virtual for platform-specific overrides (_win.cc / _posix.cc).
|
||||
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) {}
|
||||
|
||||
protected:
|
||||
A64CodeCache() = default;
|
||||
};
|
||||
|
||||
} // namespace a64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_BACKEND_A64_A64_CODE_CACHE_H_
|
||||
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/backend/a64/a64_code_cache.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/cpu/backend/a64/a64_stack_layout.h"
|
||||
|
||||
// libgcc/libunwind APIs for registering DWARF .eh_frame unwind info.
|
||||
extern "C" void __register_frame(void*);
|
||||
extern "C" void __deregister_frame(void*);
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
namespace a64 {
|
||||
|
||||
// Maximum size of DWARF .eh_frame data per function (CIE + FDE + terminator).
|
||||
static constexpr uint32_t kMaxUnwindInfoSize = 128;
|
||||
|
||||
// DWARF register numbers for AArch64.
|
||||
static constexpr uint8_t kDwarfRegX19 = 19;
|
||||
static constexpr uint8_t kDwarfRegX20 = 20;
|
||||
static constexpr uint8_t kDwarfRegX21 = 21;
|
||||
static constexpr uint8_t kDwarfRegX22 = 22;
|
||||
static constexpr uint8_t kDwarfRegX23 = 23;
|
||||
static constexpr uint8_t kDwarfRegX24 = 24;
|
||||
static constexpr uint8_t kDwarfRegX25 = 25;
|
||||
static constexpr uint8_t kDwarfRegX26 = 26;
|
||||
static constexpr uint8_t kDwarfRegX27 = 27;
|
||||
static constexpr uint8_t kDwarfRegX28 = 28;
|
||||
static constexpr uint8_t kDwarfRegFP = 29; // x29 / frame pointer
|
||||
static constexpr uint8_t kDwarfRegLR = 30; // x30 / link register
|
||||
static constexpr uint8_t kDwarfRegSP = 31; // stack pointer
|
||||
static constexpr uint8_t kDwarfRegD8 = 72; // d8-d15 are callee-saved
|
||||
static constexpr uint8_t kDwarfRegD9 = 73;
|
||||
static constexpr uint8_t kDwarfRegD10 = 74;
|
||||
static constexpr uint8_t kDwarfRegD11 = 75;
|
||||
static constexpr uint8_t kDwarfRegD12 = 76;
|
||||
static constexpr uint8_t kDwarfRegD13 = 77;
|
||||
static constexpr uint8_t kDwarfRegD14 = 78;
|
||||
static constexpr uint8_t kDwarfRegD15 = 79;
|
||||
|
||||
// DWARF CFA opcodes.
|
||||
static constexpr uint8_t kDW_CFA_advance_loc1 = 0x02;
|
||||
static constexpr uint8_t kDW_CFA_advance_loc2 = 0x03;
|
||||
static constexpr uint8_t kDW_CFA_def_cfa = 0x0c;
|
||||
static constexpr uint8_t kDW_CFA_def_cfa_offset = 0x0e;
|
||||
static constexpr uint8_t kDW_CFA_nop = 0x00;
|
||||
|
||||
// DWARF pointer encoding constants.
|
||||
static constexpr uint8_t kDW_EH_PE_pcrel = 0x10;
|
||||
static constexpr uint8_t kDW_EH_PE_sdata4 = 0x0b;
|
||||
|
||||
static size_t WriteULEB128(uint8_t* p, uint64_t value) {
|
||||
size_t count = 0;
|
||||
do {
|
||||
uint8_t byte = value & 0x7F;
|
||||
value >>= 7;
|
||||
if (value) byte |= 0x80;
|
||||
p[count++] = byte;
|
||||
} while (value);
|
||||
return count;
|
||||
}
|
||||
|
||||
static size_t WriteSLEB128(uint8_t* p, int64_t value) {
|
||||
size_t count = 0;
|
||||
bool more = true;
|
||||
while (more) {
|
||||
uint8_t byte = value & 0x7F;
|
||||
value >>= 7;
|
||||
if ((value == 0 && !(byte & 0x40)) || (value == -1 && (byte & 0x40))) {
|
||||
more = false;
|
||||
} else {
|
||||
byte |= 0x80;
|
||||
}
|
||||
p[count++] = byte;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
class PosixA64CodeCache : public A64CodeCache {
|
||||
public:
|
||||
PosixA64CodeCache();
|
||||
~PosixA64CodeCache() override;
|
||||
|
||||
bool Initialize() override;
|
||||
|
||||
void* LookupUnwindInfo(uint64_t host_pc) override { return nullptr; }
|
||||
|
||||
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,
|
||||
void* code_execute_address,
|
||||
const EmitFunctionInfo& func_info);
|
||||
|
||||
std::vector<void*> registered_frames_;
|
||||
uint32_t unwind_table_count_ = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<A64CodeCache> A64CodeCache::Create() {
|
||||
return std::make_unique<PosixA64CodeCache>();
|
||||
}
|
||||
|
||||
PosixA64CodeCache::PosixA64CodeCache() = default;
|
||||
|
||||
PosixA64CodeCache::~PosixA64CodeCache() {
|
||||
for (auto frame : registered_frames_) {
|
||||
__deregister_frame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool PosixA64CodeCache::Initialize() {
|
||||
if (!A64CodeCache::Initialize()) {
|
||||
return false;
|
||||
}
|
||||
registered_frames_.reserve(kMaximumFunctionCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
A64CodeCache::UnwindReservation PosixA64CodeCache::RequestUnwindReservation(
|
||||
uint8_t* entry_address) {
|
||||
#if defined(NDEBUG)
|
||||
if (unwind_table_count_ >= kMaximumFunctionCount) {
|
||||
xe::FatalError(
|
||||
"Unwind table count exceeded maximum! Please report this to "
|
||||
"Xenia developers");
|
||||
}
|
||||
#else
|
||||
assert_false(unwind_table_count_ >= kMaximumFunctionCount);
|
||||
#endif
|
||||
UnwindReservation unwind_reservation;
|
||||
unwind_reservation.data_size = xe::round_up(kMaxUnwindInfoSize, 16);
|
||||
unwind_reservation.table_slot = unwind_table_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) {
|
||||
InitializeUnwindEntry(unwind_reservation.entry_address, code_execute_address,
|
||||
func_info);
|
||||
|
||||
void* unwind_execute_address = unwind_reservation.entry_address -
|
||||
generated_code_write_base_ +
|
||||
generated_code_execute_base_;
|
||||
__register_frame(unwind_execute_address);
|
||||
registered_frames_.push_back(unwind_execute_address);
|
||||
}
|
||||
|
||||
void PosixA64CodeCache::InitializeUnwindEntry(
|
||||
uint8_t* unwind_entry_address, void* code_execute_address,
|
||||
const EmitFunctionInfo& func_info) {
|
||||
// Compute execute-side base address of the unwind buffer.
|
||||
uint8_t* unwind_execute_base = unwind_entry_address -
|
||||
generated_code_write_base_ +
|
||||
generated_code_execute_base_;
|
||||
|
||||
uint8_t* p = unwind_entry_address;
|
||||
uint8_t* cie_start = p;
|
||||
|
||||
// === CIE (Common Information Entry) ===
|
||||
uint8_t* cie_length_ptr = p;
|
||||
p += 4;
|
||||
|
||||
uint8_t* cie_content_start = p;
|
||||
|
||||
// CIE ID = 0.
|
||||
*reinterpret_cast<uint32_t*>(p) = 0;
|
||||
p += 4;
|
||||
|
||||
// Version = 1.
|
||||
*p++ = 1;
|
||||
|
||||
// Augmentation string "zR".
|
||||
*p++ = 'z';
|
||||
*p++ = 'R';
|
||||
*p++ = '\0';
|
||||
|
||||
// Code alignment factor = 4 (ARM64 instructions are 4 bytes).
|
||||
p += WriteULEB128(p, 4);
|
||||
|
||||
// Data alignment factor = -8.
|
||||
p += WriteSLEB128(p, -8);
|
||||
|
||||
// Return address register = x30 (LR).
|
||||
p += WriteULEB128(p, kDwarfRegLR);
|
||||
|
||||
// Augmentation data length = 1.
|
||||
p += WriteULEB128(p, 1);
|
||||
|
||||
// FDE pointer encoding: pc-relative, signed 32-bit.
|
||||
*p++ = kDW_EH_PE_pcrel | kDW_EH_PE_sdata4;
|
||||
|
||||
// Initial instructions:
|
||||
// DW_CFA_def_cfa SP, 0 — at function entry, CFA = SP.
|
||||
*p++ = kDW_CFA_def_cfa;
|
||||
p += WriteULEB128(p, kDwarfRegSP);
|
||||
p += WriteULEB128(p, 0);
|
||||
|
||||
// Pad CIE to pointer-size (8-byte) alignment.
|
||||
size_t cie_content_len = static_cast<size_t>(p - cie_content_start);
|
||||
size_t cie_padded_len = xe::round_up(cie_content_len, sizeof(void*));
|
||||
while (p < cie_content_start + cie_padded_len) {
|
||||
*p++ = kDW_CFA_nop;
|
||||
}
|
||||
|
||||
*reinterpret_cast<uint32_t*>(cie_length_ptr) =
|
||||
static_cast<uint32_t>(p - cie_content_start);
|
||||
|
||||
// === FDE (Frame Description Entry) ===
|
||||
uint8_t* fde_length_ptr = p;
|
||||
p += 4;
|
||||
|
||||
uint8_t* fde_content_start = p;
|
||||
|
||||
// CIE pointer.
|
||||
*reinterpret_cast<uint32_t*>(p) = static_cast<uint32_t>(p - cie_start);
|
||||
p += 4;
|
||||
|
||||
// PC begin (pc-relative).
|
||||
uint8_t* pc_begin_execute_addr =
|
||||
unwind_execute_base + (p - unwind_entry_address);
|
||||
*reinterpret_cast<int32_t*>(p) =
|
||||
static_cast<int32_t>(reinterpret_cast<intptr_t>(code_execute_address) -
|
||||
reinterpret_cast<intptr_t>(pc_begin_execute_addr));
|
||||
p += 4;
|
||||
|
||||
// PC range.
|
||||
*reinterpret_cast<uint32_t*>(p) =
|
||||
static_cast<uint32_t>(func_info.code_size.total);
|
||||
p += 4;
|
||||
|
||||
// Augmentation data length = 0.
|
||||
p += WriteULEB128(p, 0);
|
||||
|
||||
// FDE instructions.
|
||||
if (func_info.stack_size > 0) {
|
||||
// Advance to the instruction after the stack allocation.
|
||||
size_t alloc_offset = func_info.prolog_stack_alloc_offset;
|
||||
if (alloc_offset > 0) {
|
||||
// ARM64 code alignment factor is 4, so divide by 4.
|
||||
uint32_t factored_offset = static_cast<uint32_t>(alloc_offset / 4);
|
||||
if (factored_offset < 64) {
|
||||
*p++ = 0x40 | static_cast<uint8_t>(factored_offset);
|
||||
} else if (factored_offset < 256) {
|
||||
*p++ = kDW_CFA_advance_loc1;
|
||||
*p++ = static_cast<uint8_t>(factored_offset);
|
||||
} else {
|
||||
*p++ = kDW_CFA_advance_loc2;
|
||||
*reinterpret_cast<uint16_t*>(p) =
|
||||
static_cast<uint16_t>(factored_offset);
|
||||
p += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// DW_CFA_def_cfa_offset: CFA = SP + stack_size.
|
||||
*p++ = kDW_CFA_def_cfa_offset;
|
||||
p += WriteULEB128(p, func_info.stack_size);
|
||||
|
||||
// For thunk functions, encode callee-saved register save locations.
|
||||
if (func_info.stack_size == StackLayout::THUNK_STACK_SIZE) {
|
||||
size_t cfa = func_info.stack_size;
|
||||
|
||||
// x19 at sp+0x000
|
||||
*p++ = 0x80 | kDwarfRegX19;
|
||||
p += WriteULEB128(p, (cfa - 0x000) / 8);
|
||||
|
||||
// x20 at sp+0x008
|
||||
*p++ = 0x80 | kDwarfRegX20;
|
||||
p += WriteULEB128(p, (cfa - 0x008) / 8);
|
||||
|
||||
// x21 at sp+0x010
|
||||
*p++ = 0x80 | kDwarfRegX21;
|
||||
p += WriteULEB128(p, (cfa - 0x010) / 8);
|
||||
|
||||
// x29 at sp+0x050
|
||||
*p++ = 0x80 | kDwarfRegFP;
|
||||
p += WriteULEB128(p, (cfa - 0x050) / 8);
|
||||
|
||||
// x30 at sp+0x058
|
||||
*p++ = 0x80 | kDwarfRegLR;
|
||||
p += WriteULEB128(p, (cfa - 0x058) / 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Pad FDE.
|
||||
size_t fde_content_len = static_cast<size_t>(p - fde_content_start);
|
||||
size_t fde_padded_len = xe::round_up(fde_content_len, sizeof(void*));
|
||||
while (p < fde_content_start + fde_padded_len) {
|
||||
*p++ = kDW_CFA_nop;
|
||||
}
|
||||
|
||||
*reinterpret_cast<uint32_t*>(fde_length_ptr) =
|
||||
static_cast<uint32_t>(p - fde_content_start);
|
||||
|
||||
// === Terminator ===
|
||||
*reinterpret_cast<uint32_t*>(p) = 0;
|
||||
p += 4;
|
||||
|
||||
assert_true(static_cast<size_t>(p - unwind_entry_address) <=
|
||||
kMaxUnwindInfoSize);
|
||||
}
|
||||
|
||||
} // namespace a64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
@@ -0,0 +1,419 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/backend/a64/a64_code_cache.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/cpu/backend/a64/a64_stack_layout.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
// Function pointer definitions for growable function tables.
|
||||
using FnRtlAddGrowableFunctionTable = decltype(&RtlAddGrowableFunctionTable);
|
||||
using FnRtlGrowFunctionTable = decltype(&RtlGrowFunctionTable);
|
||||
using FnRtlDeleteGrowableFunctionTable =
|
||||
decltype(&RtlDeleteGrowableFunctionTable);
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
namespace a64 {
|
||||
|
||||
// ARM64 .xdata unwind codes.
|
||||
// See: https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling
|
||||
//
|
||||
// Codes are stored as a byte array (big-endian for multi-byte codes), listed
|
||||
// in reverse prolog order (last prolog instruction's code first).
|
||||
//
|
||||
// For the thunk prolog:
|
||||
// sub sp, sp, #0xA0 (alloc_s or alloc_m)
|
||||
// stp x19,x20, [sp, #0x00] (save_regp)
|
||||
// stp x21,x22, [sp, #0x10] (save_regp)
|
||||
// stp x23,x24, [sp, #0x20] (save_regp)
|
||||
// stp x25,x26, [sp, #0x30] (save_regp)
|
||||
// stp x27,x28, [sp, #0x40] (save_regp)
|
||||
// stp x29,x30, [sp, #0x50] (save_fplr)
|
||||
// stp d8, d9, [sp, #0x60] (save_fregp)
|
||||
// stp d10,d11, [sp, #0x70] (save_fregp)
|
||||
// stp d12,d13, [sp, #0x80] (save_fregp)
|
||||
// stp d14,d15, [sp, #0x90] (save_fregp)
|
||||
|
||||
// ARM64 unwind code builders.
|
||||
// alloc_s: 000XXXXX, allocate X*16 bytes (0..496).
|
||||
static void EmitAllocS(uint8_t* buf, size_t& off, uint32_t size_bytes) {
|
||||
assert_true(size_bytes <= 496 && (size_bytes % 16) == 0);
|
||||
buf[off++] = static_cast<uint8_t>(size_bytes / 16);
|
||||
}
|
||||
|
||||
// alloc_m: 11000XXX XXXXXXXX, allocate X*16 bytes (0..32752).
|
||||
static void EmitAllocM(uint8_t* buf, size_t& off, uint32_t size_bytes) {
|
||||
assert_true(size_bytes <= 32752 && (size_bytes % 16) == 0);
|
||||
uint16_t val = static_cast<uint16_t>(size_bytes / 16);
|
||||
buf[off++] = static_cast<uint8_t>(0xC0 | ((val >> 8) & 0x07));
|
||||
buf[off++] = static_cast<uint8_t>(val & 0xFF);
|
||||
}
|
||||
|
||||
// save_regp: 110010XX XXzzzzzz
|
||||
// Save x(19+X), x(20+X) pair at [sp + Z*8].
|
||||
// X is the register offset from x19 (not a pair ordinal).
|
||||
// e.g., x21,x22 -> X=2, x27,x28 -> X=8.
|
||||
static void EmitSaveRegp(uint8_t* buf, size_t& off, uint32_t reg_offset,
|
||||
uint32_t sp_offset) {
|
||||
assert_true(reg_offset <= 10);
|
||||
assert_true((sp_offset % 8) == 0 && sp_offset / 8 <= 63);
|
||||
uint32_t z = sp_offset / 8;
|
||||
buf[off++] = static_cast<uint8_t>(0xC8 | ((reg_offset >> 2) & 0x03));
|
||||
buf[off++] = static_cast<uint8_t>(((reg_offset & 0x03) << 6) | (z & 0x3F));
|
||||
}
|
||||
|
||||
// save_fplr: 01zzzzzz
|
||||
// Save <x29, lr> at [sp + Z*8].
|
||||
static void EmitSaveFplr(uint8_t* buf, size_t& off, uint32_t sp_offset) {
|
||||
assert_true((sp_offset % 8) == 0 && sp_offset / 8 <= 63);
|
||||
buf[off++] = static_cast<uint8_t>(0x40 | (sp_offset / 8));
|
||||
}
|
||||
|
||||
// save_fregp: 1101100X XXzzzzzz
|
||||
// Save d(8+X), d(9+X) pair at [sp + Z*8].
|
||||
// X is the register offset from d8 (not a pair ordinal).
|
||||
// e.g., d10,d11 -> X=2, d14,d15 -> X=6.
|
||||
static void EmitSaveFregp(uint8_t* buf, size_t& off, uint32_t reg_offset,
|
||||
uint32_t sp_offset) {
|
||||
assert_true(reg_offset <= 7);
|
||||
assert_true((sp_offset % 8) == 0 && sp_offset / 8 <= 63);
|
||||
uint32_t z = sp_offset / 8;
|
||||
buf[off++] = static_cast<uint8_t>(0xD8 | ((reg_offset >> 2) & 0x01));
|
||||
buf[off++] = static_cast<uint8_t>(((reg_offset & 0x03) << 6) | (z & 0x3F));
|
||||
}
|
||||
|
||||
// end: 0xE4
|
||||
static void EmitEnd(uint8_t* buf, size_t& off) { buf[off++] = 0xE4; }
|
||||
|
||||
// Build the .xdata unwind codes for a thunk prolog that saves callee-saved
|
||||
// registers at known offsets (see StackLayout in a64_stack_layout.h).
|
||||
// Returns the number of bytes written.
|
||||
static size_t BuildThunkUnwindCodes(uint8_t* buf) {
|
||||
size_t off = 0;
|
||||
// Codes listed in reverse prolog order (last prolog instruction first).
|
||||
// stp d14, d15, [sp, #0x90] — d14 = d(8+6)
|
||||
EmitSaveFregp(buf, off, 6, 0x90);
|
||||
// stp d12, d13, [sp, #0x80] — d12 = d(8+4)
|
||||
EmitSaveFregp(buf, off, 4, 0x80);
|
||||
// stp d10, d11, [sp, #0x70] — d10 = d(8+2)
|
||||
EmitSaveFregp(buf, off, 2, 0x70);
|
||||
// stp d8, d9, [sp, #0x60] — d8 = d(8+0)
|
||||
EmitSaveFregp(buf, off, 0, 0x60);
|
||||
// stp x29, x30, [sp, #0x50]
|
||||
EmitSaveFplr(buf, off, 0x50);
|
||||
// stp x27, x28, [sp, #0x40] — x27 = x(19+8)
|
||||
EmitSaveRegp(buf, off, 8, 0x40);
|
||||
// stp x25, x26, [sp, #0x30] — x25 = x(19+6)
|
||||
EmitSaveRegp(buf, off, 6, 0x30);
|
||||
// stp x23, x24, [sp, #0x20] — x23 = x(19+4)
|
||||
EmitSaveRegp(buf, off, 4, 0x20);
|
||||
// stp x21, x22, [sp, #0x10] — x21 = x(19+2)
|
||||
EmitSaveRegp(buf, off, 2, 0x10);
|
||||
// stp x19, x20, [sp, #0x00] — x19 = x(19+0)
|
||||
EmitSaveRegp(buf, off, 0, 0x00);
|
||||
// sub sp, sp, #0xA0 (160 bytes)
|
||||
EmitAllocS(buf, off, StackLayout::THUNK_STACK_SIZE);
|
||||
EmitEnd(buf, off);
|
||||
return off;
|
||||
}
|
||||
|
||||
// Build minimal unwind codes for a guest function prolog.
|
||||
// Guest functions only do: sub sp, sp, #N; str x30, [sp, #64]
|
||||
// The callee-saved registers were already saved by the thunk.
|
||||
static size_t BuildGuestUnwindCodes(uint8_t* buf, uint32_t stack_size) {
|
||||
size_t off = 0;
|
||||
// The guest function stores x30 at [sp + HOST_RET_ADDR] via STR, not STP.
|
||||
// Windows unwinder needs to know where LR is to unwind. We encode this as
|
||||
// save_lrpair — but there's no single-register LR save opcode on ARM64.
|
||||
// Instead we describe the stack allocation only. The host return address
|
||||
// is stored by the JIT but is not a callee-save operation (it's the thunk's
|
||||
// LR, not the guest function's). The unwinder will walk up to the thunk
|
||||
// frame which has the full unwind info.
|
||||
if (stack_size <= 496) {
|
||||
EmitAllocS(buf, off, stack_size);
|
||||
} else {
|
||||
EmitAllocM(buf, off, stack_size);
|
||||
}
|
||||
EmitEnd(buf, off);
|
||||
return off;
|
||||
}
|
||||
|
||||
// Size of .xdata record for a thunk (header + codes + padding).
|
||||
// Thunk codes: 5x save_regp(2B) + 1x save_fplr(1B) + 4x save_fregp(2B) +
|
||||
// 1x alloc_s(1B) + end(1B) = 21 bytes -> 24 bytes padded -> 6
|
||||
// code words. Header is 1 word. Total: 7 words = 28 bytes.
|
||||
static constexpr uint32_t kThunkXdataSize = 28;
|
||||
|
||||
// Size of .xdata record for a guest function (header + codes + padding).
|
||||
// Guest codes: alloc_s(1B) or alloc_m(2B) + end(1B) = 2-3 bytes -> 4 bytes
|
||||
// padded -> 1 code word. Header is 1 word. Total: 2 words = 8
|
||||
// bytes. We reserve the larger case.
|
||||
static constexpr uint32_t kGuestXdataSize = 12;
|
||||
|
||||
// Compute the maximum unwind data size for any function.
|
||||
static constexpr uint32_t kMaxUnwindSize = kThunkXdataSize;
|
||||
|
||||
// Build a complete .xdata record. Returns the total size written.
|
||||
static size_t BuildXdataRecord(uint8_t* xdata, uint32_t func_length_bytes,
|
||||
const uint8_t* codes, size_t codes_length) {
|
||||
// Pad codes to 4-byte boundary.
|
||||
size_t codes_padded = xe::round_up(codes_length, size_t{4});
|
||||
uint32_t code_words = static_cast<uint32_t>(codes_padded / 4);
|
||||
assert_true(code_words <= 31);
|
||||
assert_true(func_length_bytes % 4 == 0);
|
||||
|
||||
uint32_t func_len_div4 = func_length_bytes / 4;
|
||||
assert_true(func_len_div4 <= 0x3FFFF);
|
||||
|
||||
// Header word:
|
||||
// bits 0-17: Function Length / 4
|
||||
// bits 18-19: Version = 0
|
||||
// bit 20: X = 0 (no exception handler)
|
||||
// bit 21: E = 1 (single epilog, packed in header)
|
||||
// bits 22-26: Epilog start index (0 = epilog uses same codes from start)
|
||||
// bits 27-31: Code Words
|
||||
uint32_t header = (func_len_div4 & 0x3FFFF) | (0u << 18) // Vers = 0
|
||||
| (0u << 20) // X = 0
|
||||
| (1u << 21) // E = 1
|
||||
| (0u << 22) // Epilog start index = 0
|
||||
| (code_words << 27);
|
||||
|
||||
std::memcpy(xdata, &header, 4);
|
||||
|
||||
// Write codes, zero-padded to code_words * 4 bytes.
|
||||
std::memset(xdata + 4, 0, codes_padded);
|
||||
std::memcpy(xdata + 4, codes, codes_length);
|
||||
|
||||
return 4 + codes_padded;
|
||||
}
|
||||
|
||||
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_;
|
||||
// End addresses for each entry (ARM64 RUNTIME_FUNCTION lacks EndAddress).
|
||||
std::vector<DWORD> unwind_table_end_address_;
|
||||
// Current number of entries in the table.
|
||||
std::atomic<uint32_t> unwind_table_count_ = {0};
|
||||
// Does this version of Windows support growable function 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;
|
||||
}
|
||||
|
||||
// Allocate unwind table with maximum entry count.
|
||||
unwind_table_.resize(kMaximumFunctionCount);
|
||||
unwind_table_end_address_.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_;
|
||||
|
||||
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 {
|
||||
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) {
|
||||
#if defined(NDEBUG)
|
||||
if (unwind_table_count_ >= kMaximumFunctionCount) {
|
||||
xe::FatalError(
|
||||
"Unwind table count exceeded maximum! Please report this to "
|
||||
"Xenia/Canary developers");
|
||||
}
|
||||
#else
|
||||
assert_false(unwind_table_count_ >= kMaximumFunctionCount);
|
||||
#endif
|
||||
UnwindReservation unwind_reservation;
|
||||
unwind_reservation.data_size = xe::round_up(kMaxUnwindSize, size_t{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_) {
|
||||
grow_table_(unwind_table_handle_, unwind_table_count_);
|
||||
}
|
||||
|
||||
FlushInstructionCache(GetCurrentProcess(), code_execute_address,
|
||||
func_info.code_size.total);
|
||||
}
|
||||
|
||||
void Win32A64CodeCache::InitializeUnwindEntry(
|
||||
uint8_t* unwind_entry_address, size_t unwind_table_slot,
|
||||
void* code_execute_address, const EmitFunctionInfo& func_info) {
|
||||
uint8_t codes[32];
|
||||
size_t codes_length;
|
||||
|
||||
// Guest function prologs are exactly 4 instructions (16 bytes):
|
||||
// sub sp, sp, #N; str x30, [sp, #64]; str x0, [sp, #48]; str xzr, [sp, #56]
|
||||
// The HostToGuest thunk has a much larger prolog that saves all
|
||||
// callee-saved registers. We detect it by stack size.
|
||||
// Other thunks (GuestToHost, ResolveFunction) have different layouts
|
||||
// but are only called from within JIT'd code; stack-alloc-only unwind
|
||||
// info is sufficient for them since the unwinder will walk up to the
|
||||
// HostToGuest frame which has full unwind info.
|
||||
bool is_host_to_guest_thunk =
|
||||
(func_info.stack_size == StackLayout::THUNK_STACK_SIZE &&
|
||||
func_info.code_size.prolog > 16);
|
||||
|
||||
if (is_host_to_guest_thunk) {
|
||||
codes_length = BuildThunkUnwindCodes(codes);
|
||||
} else if (func_info.stack_size > 0) {
|
||||
codes_length = BuildGuestUnwindCodes(
|
||||
codes, static_cast<uint32_t>(func_info.stack_size));
|
||||
} else {
|
||||
// Thunks with no stack allocation (e.g. GuestToHost, ResolveFunction)
|
||||
// still need minimal unwind info — emit empty unwind codes.
|
||||
codes_length = 0;
|
||||
}
|
||||
|
||||
size_t xdata_size = BuildXdataRecord(
|
||||
unwind_entry_address, static_cast<uint32_t>(func_info.code_size.total),
|
||||
codes, codes_length);
|
||||
|
||||
// Add RUNTIME_FUNCTION entry.
|
||||
// ARM64 RUNTIME_FUNCTION has BeginAddress and UnwindData but no EndAddress
|
||||
// (the function length is encoded in the .xdata header).
|
||||
auto& 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_);
|
||||
// Store end address in parallel array for LookupUnwindInfo.
|
||||
unwind_table_end_address_[unwind_table_slot] =
|
||||
DWORD(fn_entry.BeginAddress + func_info.code_size.total);
|
||||
}
|
||||
|
||||
void* Win32A64CodeCache::LookupUnwindInfo(uint64_t host_pc) {
|
||||
// ARM64 RUNTIME_FUNCTION lacks EndAddress, so we do a manual binary search
|
||||
// using our parallel end address array.
|
||||
uint32_t key = static_cast<uint32_t>(host_pc - kGeneratedCodeExecuteBase);
|
||||
uint32_t count = unwind_table_count_;
|
||||
uint32_t lo = 0, hi = count;
|
||||
while (lo < hi) {
|
||||
uint32_t mid = lo + (hi - lo) / 2;
|
||||
if (key < unwind_table_[mid].BeginAddress) {
|
||||
hi = mid;
|
||||
} else if (key >= unwind_table_end_address_[mid]) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
return &unwind_table_[mid];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace a64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CPU_BACKEND_A64_A64_EMITTER_H_
|
||||
#define XENIA_CPU_BACKEND_A64_A64_EMITTER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/arena.h"
|
||||
#include "xenia/cpu/backend/code_cache_base.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/cpu/xex_module.h"
|
||||
#include "xenia/memory.h"
|
||||
|
||||
#include "xbyak_aarch64.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
class Processor;
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
namespace a64 {
|
||||
|
||||
class A64Backend;
|
||||
class A64CodeCache;
|
||||
|
||||
enum class FPCRMode : uint32_t { Unknown, Fpu, Vmx };
|
||||
|
||||
// Unfortunately due to the design of xbyak we have to pass this to the ctor.
|
||||
class XbyakA64Allocator : public Xbyak_aarch64::Allocator {
|
||||
public:
|
||||
virtual bool useProtect() const { return false; }
|
||||
};
|
||||
|
||||
class A64Emitter;
|
||||
using TailEmitCallback =
|
||||
std::function<void(A64Emitter& e, Xbyak_aarch64::Label& lbl)>;
|
||||
struct TailEmitter {
|
||||
Xbyak_aarch64::Label label;
|
||||
uint32_t alignment;
|
||||
TailEmitCallback func;
|
||||
};
|
||||
|
||||
class A64Emitter : public Xbyak_aarch64::CodeGenerator {
|
||||
public:
|
||||
A64Emitter(A64Backend* backend, XbyakA64Allocator* allocator);
|
||||
virtual ~A64Emitter();
|
||||
|
||||
Processor* processor() const { return processor_; }
|
||||
A64Backend* backend() const { return backend_; }
|
||||
|
||||
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: sp, x20 (context), x21 (membase)
|
||||
// Scratch: x0-x18 (caller-saved), v0-v3
|
||||
// Available GPRs for register allocator: x19, x22-x28
|
||||
static constexpr int GPR_COUNT = 8;
|
||||
// Available VEC regs: v4-v7, v16-v31
|
||||
static constexpr int VEC_COUNT = 20;
|
||||
static constexpr size_t kStashOffset = 32;
|
||||
|
||||
static void SetupReg(const hir::Value* v, Xbyak_aarch64::WReg& r) {
|
||||
auto idx = gpr_reg_map_[v->reg.index];
|
||||
r = Xbyak_aarch64::WReg(idx);
|
||||
}
|
||||
static void SetupReg(const hir::Value* v, Xbyak_aarch64::XReg& r) {
|
||||
auto idx = gpr_reg_map_[v->reg.index];
|
||||
r = Xbyak_aarch64::XReg(idx);
|
||||
}
|
||||
static void SetupReg(const hir::Value* v, Xbyak_aarch64::SReg& r) {
|
||||
auto idx = vec_reg_map_[v->reg.index];
|
||||
r = Xbyak_aarch64::SReg(idx);
|
||||
}
|
||||
static void SetupReg(const hir::Value* v, Xbyak_aarch64::DReg& r) {
|
||||
auto idx = vec_reg_map_[v->reg.index];
|
||||
r = Xbyak_aarch64::DReg(idx);
|
||||
}
|
||||
static void SetupReg(const hir::Value* v, Xbyak_aarch64::QReg& r) {
|
||||
auto idx = vec_reg_map_[v->reg.index];
|
||||
r = Xbyak_aarch64::QReg(idx);
|
||||
}
|
||||
static void SetupReg(const hir::Value* v, Xbyak_aarch64::VReg& r) {
|
||||
auto idx = vec_reg_map_[v->reg.index];
|
||||
r = Xbyak_aarch64::VReg(idx);
|
||||
}
|
||||
|
||||
Xbyak_aarch64::Label& epilog_label() { return *epilog_label_; }
|
||||
|
||||
FunctionDebugInfo* debug_info() const { return debug_info_; }
|
||||
size_t stack_size() const { return stack_size_; }
|
||||
|
||||
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, int reg_index);
|
||||
void CallExtern(const hir::Instr* instr, const Function* function);
|
||||
void CallNative(void* fn);
|
||||
void CallNativeSafe(void* fn);
|
||||
void SetReturnAddress(uint64_t value);
|
||||
|
||||
// Context register = x20.
|
||||
const Xbyak_aarch64::XReg& GetContextReg() const { return x20; }
|
||||
// Memory base register = x21.
|
||||
const Xbyak_aarch64::XReg& GetMembaseReg() const { return x21; }
|
||||
|
||||
void ReloadMembase();
|
||||
|
||||
void PushStackpoint();
|
||||
void PopStackpoint();
|
||||
void EnsureSynchronizedGuestAndHostStack();
|
||||
|
||||
static void HandleStackpointOverflowError(ppc::PPCContext* context);
|
||||
|
||||
void ForgetFpcrMode() { fpcr_mode_ = FPCRMode::Unknown; }
|
||||
bool ChangeFpcrMode(FPCRMode new_mode, bool already_set = false);
|
||||
|
||||
Xbyak_aarch64::Label& AddToTail(TailEmitCallback callback,
|
||||
uint32_t alignment = 0);
|
||||
Xbyak_aarch64::Label& NewCachedLabel();
|
||||
|
||||
// Get or create a xbyak_aarch64 label for a HIR label ID.
|
||||
Xbyak_aarch64::Label& GetLabel(uint32_t label_id);
|
||||
|
||||
XexModule* GuestModule() { return guest_module_; }
|
||||
|
||||
protected:
|
||||
void* Emplace(const EmitFunctionInfo& func_info,
|
||||
GuestFunction* function = nullptr);
|
||||
bool Emit(hir::HIRBuilder* builder, EmitFunctionInfo& func_info);
|
||||
|
||||
protected:
|
||||
Processor* processor_ = nullptr;
|
||||
A64Backend* backend_ = nullptr;
|
||||
A64CodeCache* code_cache_ = nullptr;
|
||||
XbyakA64Allocator* allocator_ = nullptr;
|
||||
XexModule* guest_module_ = nullptr;
|
||||
uint32_t current_guest_function_ = 0;
|
||||
|
||||
Xbyak_aarch64::Label* epilog_label_ = nullptr;
|
||||
|
||||
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 uint32_t gpr_reg_map_[GPR_COUNT];
|
||||
static const uint32_t vec_reg_map_[VEC_COUNT];
|
||||
|
||||
std::vector<TailEmitter> tail_code_;
|
||||
std::vector<Xbyak_aarch64::Label*> label_cache_;
|
||||
|
||||
// Map from HIR label IDs to xbyak_aarch64 Labels.
|
||||
std::unordered_map<uint32_t, Xbyak_aarch64::Label*> label_map_;
|
||||
|
||||
FPCRMode fpcr_mode_ = FPCRMode::Unknown;
|
||||
bool synchronize_stack_on_next_instruction_ = false;
|
||||
};
|
||||
|
||||
} // namespace a64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_BACKEND_A64_A64_EMITTER_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/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();
|
||||
if (!thunk || !machine_code_) {
|
||||
return false;
|
||||
}
|
||||
thunk(machine_code_, thread_state->context(),
|
||||
reinterpret_cast<void*>(uintptr_t(return_address)));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace a64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_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_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user