From c99e312e56f6a230dcb85694dd2f9921f491e1b6 Mon Sep 17 00:00:00 2001 From: AlpinDale Date: Tue, 9 Dec 2025 02:25:58 +0430 Subject: [PATCH] simple JIT recompiler + tests --- CMakeLists.txt | 39 ++ src/core/address_space.cpp | 74 ++- src/core/address_space.h | 3 + src/core/jit/arm64_codegen.cpp | 562 ++++++++++++++++++++++ src/core/jit/arm64_codegen.h | 130 ++++++ src/core/jit/block_manager.cpp | 94 ++++ src/core/jit/block_manager.h | 49 ++ src/core/jit/calling_convention.cpp | 63 +++ src/core/jit/calling_convention.h | 33 ++ src/core/jit/execution_engine.cpp | 202 ++++++++ src/core/jit/execution_engine.h | 53 +++ src/core/jit/register_mapping.cpp | 123 +++++ src/core/jit/register_mapping.h | 136 ++++++ src/core/jit/simd_translator.cpp | 206 ++++++++ src/core/jit/simd_translator.h | 39 ++ src/core/jit/x86_64_translator.cpp | 701 ++++++++++++++++++++++++++++ src/core/jit/x86_64_translator.h | 80 ++++ src/core/linker.cpp | 26 +- src/core/memory.cpp | 10 + src/core/signals.cpp | 21 +- tests/CMakeLists.txt | 58 +++ tests/main.cpp | 9 + tests/test_arm64_codegen.cpp | 111 +++++ tests/test_block_manager.cpp | 180 +++++++ tests/test_execution_engine.cpp | 49 ++ tests/test_logging_stub.cpp | 25 + tests/test_register_mapping.cpp | 86 ++++ 27 files changed, 3121 insertions(+), 41 deletions(-) create mode 100644 src/core/jit/arm64_codegen.cpp create mode 100644 src/core/jit/arm64_codegen.h create mode 100644 src/core/jit/block_manager.cpp create mode 100644 src/core/jit/block_manager.h create mode 100644 src/core/jit/calling_convention.cpp create mode 100644 src/core/jit/calling_convention.h create mode 100644 src/core/jit/execution_engine.cpp create mode 100644 src/core/jit/execution_engine.h create mode 100644 src/core/jit/register_mapping.cpp create mode 100644 src/core/jit/register_mapping.h create mode 100644 src/core/jit/simd_translator.cpp create mode 100644 src/core/jit/simd_translator.h create mode 100644 src/core/jit/x86_64_translator.cpp create mode 100644 src/core/jit/x86_64_translator.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/main.cpp create mode 100644 tests/test_arm64_codegen.cpp create mode 100644 tests/test_block_manager.cpp create mode 100644 tests/test_execution_engine.cpp create mode 100644 tests/test_logging_stub.cpp create mode 100644 tests/test_register_mapping.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index df2905b70..e3421d138 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,8 @@ endif() project(shadPS4 CXX C ASM ${ADDITIONAL_LANGUAGES}) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + # Forcing PIE makes sure that the base address is high enough so that it doesn't clash with the PS4 memory. if(UNIX AND NOT APPLE) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) @@ -846,6 +848,24 @@ if (ARCHITECTURE STREQUAL "x86_64") src/core/cpu_patches.h) endif() +if (ARCHITECTURE STREQUAL "arm64") + set(CORE ${CORE} + src/core/jit/arm64_codegen.cpp + src/core/jit/arm64_codegen.h + src/core/jit/register_mapping.cpp + src/core/jit/register_mapping.h + src/core/jit/x86_64_translator.cpp + src/core/jit/x86_64_translator.h + src/core/jit/block_manager.cpp + src/core/jit/block_manager.h + src/core/jit/execution_engine.cpp + src/core/jit/execution_engine.h + src/core/jit/calling_convention.cpp + src/core/jit/calling_convention.h + src/core/jit/simd_translator.cpp + src/core/jit/simd_translator.h) +endif() + set(SHADER_RECOMPILER src/shader_recompiler/profile.h src/shader_recompiler/recompiler.cpp src/shader_recompiler/recompiler.h @@ -1220,3 +1240,22 @@ endif() # Install rules install(TARGETS shadps4 BUNDLE DESTINATION .) + +# Testing +option(BUILD_TESTS "Build test suite" OFF) + +if(BUILD_TESTS) + enable_testing() + + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.17.0 + ) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + + add_subdirectory(tests) +endif() diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 584824384..a82a224a3 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -532,41 +532,44 @@ struct AddressSpace::Impl { user_base = reinterpret_cast( mmap(reinterpret_cast(USER_MIN), user_size, protection_flags, map_flags, -1, 0)); #elif defined(ARCH_ARM64) - // On ARM64 macOS, MAP_FIXED doesn't work at low addresses (0x400000) due to system restrictions. - // Map memory wherever possible and use offset calculations. This is a temporary solution - // until proper address translation is implemented for ARM64. - // Note: This means the PS4 virtual addresses won't match host addresses, so instruction + // On ARM64 macOS, MAP_FIXED doesn't work at low addresses (0x400000) due to system + // restrictions. Map memory wherever possible and use offset calculations. This is a + // temporary solution until proper address translation is implemented for ARM64. Note: This + // means the PS4 virtual addresses won't match host addresses, so instruction // translation/JIT will need to handle the offset. constexpr int map_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; - + // Map the three regions separately, but let the system choose addresses - system_managed_base = - reinterpret_cast(mmap(nullptr, system_managed_size, protection_flags, map_flags, -1, 0)); + system_managed_base = reinterpret_cast( + mmap(nullptr, system_managed_size, protection_flags, map_flags, -1, 0)); if (system_managed_base == MAP_FAILED) { LOG_CRITICAL(Kernel_Vmm, "mmap failed for system_managed_base: {}", strerror(errno)); throw std::bad_alloc{}; } - - system_reserved_base = - reinterpret_cast(mmap(nullptr, system_reserved_size, protection_flags, map_flags, -1, 0)); + + system_reserved_base = reinterpret_cast( + mmap(nullptr, system_reserved_size, protection_flags, map_flags, -1, 0)); if (system_reserved_base == MAP_FAILED) { LOG_CRITICAL(Kernel_Vmm, "mmap failed for system_reserved_base: {}", strerror(errno)); throw std::bad_alloc{}; } - - user_base = reinterpret_cast( - mmap(nullptr, user_size, protection_flags, map_flags, -1, 0)); + + user_base = + reinterpret_cast(mmap(nullptr, user_size, protection_flags, map_flags, -1, 0)); if (user_base == MAP_FAILED) { LOG_CRITICAL(Kernel_Vmm, "mmap failed for user_base: {}", strerror(errno)); throw std::bad_alloc{}; } - - LOG_WARNING(Kernel_Vmm, "ARM64 macOS: Using flexible memory layout. " - "PS4 addresses will be offset from host addresses. " - "system_managed: {} (expected {}), system_reserved: {} (expected {}), user: {} (expected {})", - fmt::ptr(system_managed_base), fmt::ptr(reinterpret_cast(SYSTEM_MANAGED_MIN)), - fmt::ptr(system_reserved_base), fmt::ptr(reinterpret_cast(SYSTEM_RESERVED_MIN)), - fmt::ptr(user_base), fmt::ptr(reinterpret_cast(USER_MIN))); + + LOG_WARNING( + Kernel_Vmm, + "ARM64 macOS: Using flexible memory layout. " + "PS4 addresses will be offset from host addresses. " + "system_managed: {} (expected {}), system_reserved: {} (expected {}), user: {} " + "(expected {})", + fmt::ptr(system_managed_base), fmt::ptr(reinterpret_cast(SYSTEM_MANAGED_MIN)), + fmt::ptr(system_reserved_base), fmt::ptr(reinterpret_cast(SYSTEM_RESERVED_MIN)), + fmt::ptr(user_base), fmt::ptr(reinterpret_cast(USER_MIN))); #endif #else const auto virtual_size = system_managed_size + system_reserved_size + user_size; @@ -650,7 +653,7 @@ struct AddressSpace::Impl { const int handle = phys_addr != -1 ? (fd == -1 ? backing_fd : fd) : -1; const off_t host_offset = phys_addr != -1 ? phys_addr : 0; const int flag = phys_addr != -1 ? MAP_SHARED : (MAP_ANONYMOUS | MAP_PRIVATE); - + #if defined(__APPLE__) && defined(ARCH_ARM64) // On ARM64 macOS, translate PS4 virtual addresses to host addresses void* host_addr = nullptr; @@ -670,7 +673,7 @@ struct AddressSpace::Impl { LOG_CRITICAL(Kernel_Vmm, "Invalid virtual address for mapping: {:#x}", virtual_addr); return MAP_FAILED; } - + void* ret = mmap(host_addr, size, prot, MAP_FIXED | flag, handle, host_offset); #else void* ret = mmap(reinterpret_cast(virtual_addr), size, prot, MAP_FIXED | flag, @@ -711,8 +714,8 @@ struct AddressSpace::Impl { LOG_CRITICAL(Kernel_Vmm, "Invalid virtual address for unmapping: {:#x}", start_address); return; } - void* ret = mmap(host_addr, end_address - start_address, - PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + void* ret = mmap(host_addr, end_address - start_address, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); #else // Return the adjusted pointers. void* ret = mmap(reinterpret_cast(start_address), end_address - start_address, @@ -853,4 +856,27 @@ boost::icl::interval_set AddressSpace::GetUsableRegions() { #endif } +void* AddressSpace::TranslateAddress(VAddr ps4_addr) const { +#ifdef ARCH_X86_64 + // On x86_64, PS4 addresses are directly mapped, so we can cast them + return reinterpret_cast(ps4_addr); +#elif defined(ARCH_ARM64) && defined(__APPLE__) + // On ARM64 macOS, translate PS4 virtual addresses to host addresses + if (ps4_addr >= SYSTEM_MANAGED_MIN && ps4_addr <= SYSTEM_MANAGED_MAX) { + u64 offset = ps4_addr - SYSTEM_MANAGED_MIN; + return system_managed_base + offset; + } else if (ps4_addr >= SYSTEM_RESERVED_MIN && ps4_addr <= SYSTEM_RESERVED_MAX) { + u64 offset = ps4_addr - SYSTEM_RESERVED_MIN; + return system_reserved_base + offset; + } else if (ps4_addr >= USER_MIN && ps4_addr <= USER_MAX) { + u64 offset = ps4_addr - USER_MIN; + return user_base + offset; + } + return nullptr; +#else + // Generic ARM64 or other platforms + return reinterpret_cast(ps4_addr); +#endif +} + } // namespace Core diff --git a/src/core/address_space.h b/src/core/address_space.h index 5c50039bd..578185e93 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -88,6 +88,9 @@ public: // Returns an interval set containing all usable regions. boost::icl::interval_set GetUsableRegions(); + // Translate PS4 virtual address to host address (for ARM64) + void* TranslateAddress(VAddr ps4_addr) const; + private: struct Impl; std::unique_ptr impl; diff --git a/src/core/jit/arm64_codegen.cpp b/src/core/jit/arm64_codegen.cpp new file mode 100644 index 000000000..516240295 --- /dev/null +++ b/src/core/jit/arm64_codegen.cpp @@ -0,0 +1,562 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "arm64_codegen.h" +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/types.h" +#if defined(__APPLE__) && defined(ARCH_ARM64) +#include +#endif + +namespace Core::Jit { + +static constexpr size_t PAGE_SIZE = 4096; +static constexpr size_t ALIGNMENT = 16; + +static size_t alignUp(size_t value, size_t alignment) { + return (value + alignment - 1) & ~(alignment - 1); +} + +static void* allocateExecutableMemory(size_t size) { + size = alignUp(size, PAGE_SIZE); +#if defined(__APPLE__) && defined(ARCH_ARM64) + // On macOS ARM64: + // 1. Allocate with PROT_READ | PROT_WRITE (no PROT_EXEC initially) + // 2. Use pthread_jit_write_protect_np to allow writing + // 3. After writing, use mprotect to add PROT_EXEC + void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (ptr == MAP_FAILED) { + LOG_CRITICAL(Core, "Failed to allocate executable memory: {} (errno={})", strerror(errno), + errno); + return nullptr; + } + // Initially disable write protection so we can write code + pthread_jit_write_protect_np(0); + return ptr; +#else + void* ptr = + mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (ptr == MAP_FAILED) { + LOG_CRITICAL(Core, "Failed to allocate executable memory: {}", strerror(errno)); + return nullptr; + } + return ptr; +#endif +} + +Arm64CodeGenerator::Arm64CodeGenerator(size_t buffer_size, void* code_ptr) + : buffer_size(alignUp(buffer_size, PAGE_SIZE)), owns_buffer(code_ptr == nullptr) { + if (code_ptr) { + code_buffer = code_ptr; + this->code_ptr = code_ptr; + } else { + code_buffer = allocateExecutableMemory(buffer_size); + this->code_ptr = code_buffer; + } + if (!code_buffer) { + throw std::bad_alloc(); + } +} + +Arm64CodeGenerator::~Arm64CodeGenerator() { + if (owns_buffer && code_buffer) { + munmap(code_buffer, buffer_size); + } +} + +void Arm64CodeGenerator::reset() { + code_ptr = code_buffer; + fixups.clear(); +} + +void Arm64CodeGenerator::setSize(size_t offset) { + code_ptr = static_cast(code_buffer) + offset; +} + +void Arm64CodeGenerator::emit32(u32 instruction) { +#if defined(__APPLE__) && defined(ARCH_ARM64) + // On macOS ARM64, disable write protection before writing + pthread_jit_write_protect_np(0); +#endif + u8* curr = static_cast(code_ptr); + u8* end = static_cast(code_buffer) + buffer_size; + ASSERT_MSG(curr + 4 <= end, "Code buffer overflow"); + *reinterpret_cast(curr) = instruction; + code_ptr = curr + 4; +#if defined(__APPLE__) && defined(ARCH_ARM64) + // Re-enable write protection after writing + pthread_jit_write_protect_np(1); +#endif +} + +void Arm64CodeGenerator::emit64(u64 instruction) { + emit32(static_cast(instruction)); + emit32(static_cast(instruction >> 32)); +} + +void* Arm64CodeGenerator::allocateCode(size_t size) { + size = alignUp(size, ALIGNMENT); + void* result = code_ptr; + u8* curr = static_cast(code_ptr); + u8* end = static_cast(code_buffer) + buffer_size; + code_ptr = curr + size; + ASSERT_MSG(static_cast(code_ptr) <= end, "Code buffer overflow"); + return result; +} + +void Arm64CodeGenerator::makeExecutable() { + size_t size = getSize(); + size = alignUp(size, PAGE_SIZE); +#if defined(__APPLE__) && defined(ARCH_ARM64) + // On macOS ARM64, re-enable write protection before making executable + pthread_jit_write_protect_np(1); + // Flush instruction cache + __builtin___clear_cache(static_cast(code_buffer), + static_cast(code_buffer) + size); +#endif + if (mprotect(code_buffer, size, PROT_READ | PROT_EXEC) != 0) { + LOG_CRITICAL(Core, "Failed to make code executable: {}", strerror(errno)); + } +} + +// Memory operations +void Arm64CodeGenerator::ldr(int reg, void* addr) { + movz(9, reinterpret_cast(addr) & 0xFFFF); + movk(9, (reinterpret_cast(addr) >> 16) & 0xFFFF, 16); + movk(9, (reinterpret_cast(addr) >> 32) & 0xFFFF, 32); + movk(9, (reinterpret_cast(addr) >> 48) & 0xFFFF, 48); + ldr(reg, 9, 0); +} + +void Arm64CodeGenerator::ldr(int reg, int base_reg, s32 offset) { + if (offset >= 0 && offset < 32768 && (offset % 8 == 0)) { + emit32(0xF9400000 | (reg << 0) | (base_reg << 5) | ((offset / 8) << 10)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + ldr(reg, 9, 0); + } +} + +void Arm64CodeGenerator::ldrh(int reg, int base_reg, s32 offset) { + if (offset >= 0 && offset < 8192 && (offset % 2 == 0)) { + emit32(0x79400000 | (reg << 0) | (base_reg << 5) | ((offset / 2) << 12)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + ldrh(reg, 9, 0); + } +} + +void Arm64CodeGenerator::ldrb(int reg, int base_reg, s32 offset) { + if (offset >= 0 && offset < 4096) { + emit32(0x39400000 | (reg << 0) | (base_reg << 5) | (offset << 12)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + ldrb(reg, 9, 0); + } +} + +void Arm64CodeGenerator::ldp(int reg1, int reg2, int base_reg, s32 offset) { + if (offset >= -256 && offset < 256 && (offset % 8 == 0)) { + s32 scaled_offset = offset / 8; + u32 imm7 = (scaled_offset >= 0) ? scaled_offset : (64 + scaled_offset); + emit32(0xA9400000 | (reg1 << 0) | (reg2 << 10) | (base_reg << 5) | (imm7 << 15)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + ldp(reg1, reg2, 9, 0); + } +} + +void Arm64CodeGenerator::str(int reg, void* addr) { + movz(9, reinterpret_cast(addr) & 0xFFFF); + movk(9, (reinterpret_cast(addr) >> 16) & 0xFFFF, 16); + movk(9, (reinterpret_cast(addr) >> 32) & 0xFFFF, 32); + movk(9, (reinterpret_cast(addr) >> 48) & 0xFFFF, 48); + str(reg, 9, 0); +} + +void Arm64CodeGenerator::str(int reg, int base_reg, s32 offset) { + if (offset >= 0 && offset < 32768 && (offset % 8 == 0)) { + emit32(0xF9000000 | (reg << 0) | (base_reg << 5) | ((offset / 8) << 10)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + str(reg, 9, 0); + } +} + +void Arm64CodeGenerator::strh(int reg, int base_reg, s32 offset) { + if (offset >= 0 && offset < 8192 && (offset % 2 == 0)) { + emit32(0x79000000 | (reg << 0) | (base_reg << 5) | ((offset / 2) << 12)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + strh(reg, 9, 0); + } +} + +void Arm64CodeGenerator::strb(int reg, int base_reg, s32 offset) { + if (offset >= 0 && offset < 4096) { + emit32(0x39000000 | (reg << 0) | (base_reg << 5) | (offset << 12)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + strb(reg, 9, 0); + } +} + +void Arm64CodeGenerator::stp(int reg1, int reg2, int base_reg, s32 offset) { + if (offset >= -256 && offset < 256 && (offset % 8 == 0)) { + s32 scaled_offset = offset / 8; + u32 imm7 = (scaled_offset >= 0) ? scaled_offset : (64 + scaled_offset); + emit32(0xA9000000 | (reg1 << 0) | (reg2 << 10) | (base_reg << 5) | (imm7 << 15)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + stp(reg1, reg2, 9, 0); + } +} + +// Arithmetic operations +void Arm64CodeGenerator::add(int dst, int src1, int src2) { + emit32(0x8B000000 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::add_imm(int dst, int src1, s32 imm) { + if (imm >= 0 && imm < 4096) { + emit32(0x91000000 | (dst << 0) | (src1 << 5) | (imm << 10)); + } else if (imm < 0 && imm > -4096) { + sub_imm(dst, src1, -imm); + } else { + mov_imm(9, imm); + add(dst, src1, 9); + } +} + +void Arm64CodeGenerator::sub(int dst, int src1, int src2) { + emit32(0xCB000000 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::sub_imm(int dst, int src1, s32 imm) { + if (imm >= 0 && imm < 4096) { + emit32(0xD1000000 | (dst << 0) | (src1 << 5) | (imm << 10)); + } else if (imm < 0 && imm > -4096) { + add_imm(dst, src1, -imm); + } else { + mov_imm(9, imm); + sub(dst, src1, 9); + } +} + +void Arm64CodeGenerator::mul(int dst, int src1, int src2) { + emit32(0x9B007C00 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::sdiv(int dst, int src1, int src2) { + emit32(0x9AC00C00 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::udiv(int dst, int src1, int src2) { + emit32(0x9AC00800 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::and_(int dst, int src1, int src2) { + emit32(0x8A000000 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::and_(int dst, int src1, u64 imm) { + if (imm <= 0xFFF) { + emit32(0x92000000 | (dst << 0) | (src1 << 5) | (static_cast(imm) << 10)); + } else { + mov_imm(9, imm); + and_(dst, src1, 9); + } +} + +void Arm64CodeGenerator::orr(int dst, int src1, int src2) { + emit32(0xAA000000 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::orr(int dst, int src1, u64 imm) { + if (imm <= 0xFFF) { + emit32(0xB2000000 | (dst << 0) | (src1 << 5) | (static_cast(imm) << 10)); + } else { + mov_imm(9, imm); + orr(dst, src1, 9); + } +} + +void Arm64CodeGenerator::eor(int dst, int src1, int src2) { + emit32(0xCA000000 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::eor(int dst, int src1, u64 imm) { + if (imm <= 0xFFF) { + emit32(0xD2000000 | (dst << 0) | (src1 << 5) | (static_cast(imm) << 10)); + } else { + mov_imm(9, imm); + eor(dst, src1, 9); + } +} + +void Arm64CodeGenerator::mvn(int dst, int src) { + emit32(0xAA200000 | (dst << 0) | (src << 16)); +} + +void Arm64CodeGenerator::lsl(int dst, int src1, int src2) { + emit32(0x9AC02000 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::lsl(int dst, int src1, u8 shift) { + ASSERT_MSG(shift < 64, "Shift amount must be < 64"); + emit32(0xD3400000 | (dst << 0) | (src1 << 5) | (shift << 10)); +} + +void Arm64CodeGenerator::lsr(int dst, int src1, int src2) { + emit32(0x9AC02400 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::lsr(int dst, int src1, u8 shift) { + ASSERT_MSG(shift < 64, "Shift amount must be < 64"); + emit32(0xD3500000 | (dst << 0) | (src1 << 5) | (shift << 10)); +} + +void Arm64CodeGenerator::asr(int dst, int src1, int src2) { + emit32(0x9AC02800 | (dst << 0) | (src1 << 5) | (src2 << 16)); +} + +void Arm64CodeGenerator::asr(int dst, int src1, u8 shift) { + ASSERT_MSG(shift < 64, "Shift amount must be < 64"); + emit32(0xD3600000 | (dst << 0) | (src1 << 5) | (shift << 10)); +} + +// Move operations +void Arm64CodeGenerator::mov(int dst, int src) { + if (dst != src) { + emit32(0xAA0003E0 | (dst << 0) | (src << 16)); + } +} + +void Arm64CodeGenerator::mov_imm(int dst, s64 imm) { + if (imm >= 0 && imm <= 0xFFFF) { + movz(dst, static_cast(imm)); + } else if (imm >= -0x10000 && imm < 0) { + movn(dst, static_cast(-imm - 1)); + } else { + movz(dst, imm & 0xFFFF); + if ((imm >> 16) & 0xFFFF) { + movk(dst, (imm >> 16) & 0xFFFF, 16); + } + if ((imm >> 32) & 0xFFFF) { + movk(dst, (imm >> 32) & 0xFFFF, 32); + } + if ((imm >> 48) & 0xFFFF) { + movk(dst, (imm >> 48) & 0xFFFF, 48); + } + } +} + +void Arm64CodeGenerator::movz(int dst, u16 imm, u8 shift) { + ASSERT_MSG(shift % 16 == 0 && shift < 64, "Shift must be multiple of 16 and < 64"); + emit32(0xD2800000 | (dst << 0) | (imm << 5) | ((shift / 16) << 21)); +} + +void Arm64CodeGenerator::movk(int dst, u16 imm, u8 shift) { + ASSERT_MSG(shift % 16 == 0 && shift < 64, "Shift must be multiple of 16 and < 64"); + emit32(0xF2800000 | (dst << 0) | (imm << 5) | ((shift / 16) << 21)); +} + +void Arm64CodeGenerator::movn(int dst, u16 imm, u8 shift) { + ASSERT_MSG(shift % 16 == 0 && shift < 64, "Shift must be multiple of 16 and < 64"); + emit32(0x92800000 | (dst << 0) | (imm << 5) | ((shift / 16) << 21)); +} + +// Compare operations +void Arm64CodeGenerator::cmp(int reg1, int reg2) { + emit32(0xEB000000 | (31 << 0) | (reg1 << 5) | (reg2 << 16)); +} + +void Arm64CodeGenerator::cmp_imm(int reg, s32 imm) { + if (imm >= 0 && imm < 4096) { + emit32(0xF1000000 | (31 << 0) | (reg << 5) | (imm << 10)); + } else { + mov_imm(9, imm); + cmp(reg, 9); + } +} + +void Arm64CodeGenerator::tst(int reg1, int reg2) { + emit32(0xEA000000 | (31 << 0) | (reg1 << 5) | (reg2 << 16)); +} + +void Arm64CodeGenerator::tst(int reg, u64 imm) { + if (imm <= 0xFFF) { + emit32(0xF2000000 | (31 << 0) | (reg << 5) | (static_cast(imm) << 10)); + } else { + mov(9, imm); + tst(reg, 9); + } +} + +// Branch operations +void Arm64CodeGenerator::b(void* target) { + s64 offset = reinterpret_cast(target) - reinterpret_cast(code_ptr); + if (offset >= -0x8000000 && offset < 0x8000000) { + s32 imm26 = static_cast(offset / 4); + emit32(0x14000000 | (imm26 & 0x3FFFFFF)); + } else { + movz(9, reinterpret_cast(target) & 0xFFFF); + movk(9, (reinterpret_cast(target) >> 16) & 0xFFFF, 16); + movk(9, (reinterpret_cast(target) >> 32) & 0xFFFF, 32); + movk(9, (reinterpret_cast(target) >> 48) & 0xFFFF, 48); + br(9); + } +} + +void Arm64CodeGenerator::b(int condition, void* target) { + s64 offset = reinterpret_cast(target) - reinterpret_cast(code_ptr); + if (offset >= -0x8000000 && offset < 0x8000000) { + s32 imm19 = static_cast(offset / 4); + emit32(0x54000000 | (condition << 0) | (imm19 << 5)); + } else { + movz(9, reinterpret_cast(target) & 0xFFFF); + movk(9, (reinterpret_cast(target) >> 16) & 0xFFFF, 16); + movk(9, (reinterpret_cast(target) >> 32) & 0xFFFF, 32); + movk(9, (reinterpret_cast(target) >> 48) & 0xFFFF, 48); + emit32(0x54000000 | (condition << 0) | (0 << 5)); + br(9); + } +} + +void Arm64CodeGenerator::bl(void* target) { + s64 offset = reinterpret_cast(target) - reinterpret_cast(code_ptr); + if (offset >= -0x8000000 && offset < 0x8000000) { + s32 imm26 = static_cast(offset / 4); + emit32(0x94000000 | (imm26 & 0x3FFFFFF)); + } else { + movz(9, reinterpret_cast(target) & 0xFFFF); + movk(9, (reinterpret_cast(target) >> 16) & 0xFFFF, 16); + movk(9, (reinterpret_cast(target) >> 32) & 0xFFFF, 32); + movk(9, (reinterpret_cast(target) >> 48) & 0xFFFF, 48); + blr(9); + } +} + +void Arm64CodeGenerator::br(int reg) { + emit32(0xD61F0000 | (reg << 5)); +} + +void Arm64CodeGenerator::blr(int reg) { + emit32(0xD63F0000 | (reg << 5)); +} + +void Arm64CodeGenerator::ret(int reg) { + emit32(0xD65F0000 | (reg << 5)); +} + +// Conditional branches +void Arm64CodeGenerator::b_eq(void* target) { + b(0, target); +} +void Arm64CodeGenerator::b_ne(void* target) { + b(1, target); +} +void Arm64CodeGenerator::b_lt(void* target) { + b(11, target); +} +void Arm64CodeGenerator::b_le(void* target) { + b(13, target); +} +void Arm64CodeGenerator::b_gt(void* target) { + b(12, target); +} +void Arm64CodeGenerator::b_ge(void* target) { + b(10, target); +} +void Arm64CodeGenerator::b_lo(void* target) { + b(3, target); +} +void Arm64CodeGenerator::b_ls(void* target) { + b(9, target); +} +void Arm64CodeGenerator::b_hi(void* target) { + b(8, target); +} +void Arm64CodeGenerator::b_hs(void* target) { + b(2, target); +} + +// Stack operations +void Arm64CodeGenerator::push(int reg) { + sub(31, 31, 16); + str(reg, 31, 0); +} + +void Arm64CodeGenerator::push(int reg1, int reg2) { + sub(31, 31, 16); + stp(reg1, reg2, 31, 0); +} + +void Arm64CodeGenerator::pop(int reg) { + ldr(reg, 31, 0); + add(31, 31, 16); +} + +void Arm64CodeGenerator::pop(int reg1, int reg2) { + ldp(reg1, reg2, 31, 0); + add(31, 31, 16); +} + +// System operations +void Arm64CodeGenerator::nop() { + emit32(0xD503201F); +} + +void Arm64CodeGenerator::brk(u16 imm) { + emit32(0xD4200000 | (imm << 5)); +} + +// NEON/SIMD operations +void Arm64CodeGenerator::ldr_v(int vreg, int base_reg, s32 offset) { + if (offset >= 0 && offset < 4096 && (offset % 16 == 0)) { + emit32(0x3DC00000 | (vreg << 0) | (base_reg << 5) | ((offset / 16) << 12)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + ldr_v(vreg, 9, 0); + } +} + +void Arm64CodeGenerator::str_v(int vreg, int base_reg, s32 offset) { + if (offset >= 0 && offset < 4096 && (offset % 16 == 0)) { + emit32(0x3D800000 | (vreg << 0) | (base_reg << 5) | ((offset / 16) << 12)); + } else { + mov_imm(9, offset); + add(9, base_reg, 9); + str_v(vreg, 9, 0); + } +} + +void Arm64CodeGenerator::mov_v(int vdst, int vsrc) { + emit32(0x4EA01C00 | (vdst << 0) | (vsrc << 5)); +} + +void Arm64CodeGenerator::add_v(int vdst, int vsrc1, int vsrc2) { + emit32(0x4E208400 | (vdst << 0) | (vsrc1 << 5) | (vsrc2 << 16)); +} + +void Arm64CodeGenerator::sub_v(int vdst, int vsrc1, int vsrc2) { + emit32(0x4EA08400 | (vdst << 0) | (vsrc1 << 5) | (vsrc2 << 16)); +} + +void Arm64CodeGenerator::mul_v(int vdst, int vsrc1, int vsrc2) { + emit32(0x4E209C00 | (vdst << 0) | (vsrc1 << 5) | (vsrc2 << 16)); +} + +} // namespace Core::Jit diff --git a/src/core/jit/arm64_codegen.h b/src/core/jit/arm64_codegen.h new file mode 100644 index 000000000..13107a82b --- /dev/null +++ b/src/core/jit/arm64_codegen.h @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/types.h" + +namespace Core::Jit { + +class Arm64CodeGenerator { +public: + explicit Arm64CodeGenerator(size_t buffer_size = 64_KB, void* code_ptr = nullptr); + ~Arm64CodeGenerator(); + + Arm64CodeGenerator(const Arm64CodeGenerator&) = delete; + Arm64CodeGenerator& operator=(const Arm64CodeGenerator&) = delete; + + void* getCode() const { + return code_buffer; + } + void* getCurr() const { + return code_ptr; + } + size_t getSize() const { + return static_cast(code_ptr) - static_cast(code_buffer); + } + + void reset(); + void setSize(size_t offset); + + // Memory operations + void ldr(int reg, void* addr); + void ldr(int reg, int base_reg, s32 offset = 0); + void ldrh(int reg, int base_reg, s32 offset = 0); + void ldrb(int reg, int base_reg, s32 offset = 0); + void ldp(int reg1, int reg2, int base_reg, s32 offset = 0); + void str(int reg, void* addr); + void str(int reg, int base_reg, s32 offset = 0); + void strh(int reg, int base_reg, s32 offset = 0); + void strb(int reg, int base_reg, s32 offset = 0); + void stp(int reg1, int reg2, int base_reg, s32 offset = 0); + + // Arithmetic operations + void add(int dst, int src1, int src2); + void add_imm(int dst, int src1, s32 imm); + void sub(int dst, int src1, int src2); + void sub_imm(int dst, int src1, s32 imm); + void mul(int dst, int src1, int src2); + void sdiv(int dst, int src1, int src2); + void udiv(int dst, int src1, int src2); + void and_(int dst, int src1, int src2); + void and_(int dst, int src1, u64 imm); + void orr(int dst, int src1, int src2); + void orr(int dst, int src1, u64 imm); + void eor(int dst, int src1, int src2); + void eor(int dst, int src1, u64 imm); + void mvn(int dst, int src); + void lsl(int dst, int src1, int src2); + void lsl(int dst, int src1, u8 shift); + void lsr(int dst, int src1, int src2); + void lsr(int dst, int src1, u8 shift); + void asr(int dst, int src1, int src2); + void asr(int dst, int src1, u8 shift); + + // Move operations + void mov(int dst, int src); + void mov_imm(int dst, s64 imm); + void movz(int dst, u16 imm, u8 shift = 0); + void movk(int dst, u16 imm, u8 shift = 0); + void movn(int dst, u16 imm, u8 shift = 0); + + // Compare operations + void cmp(int reg1, int reg2); + void cmp_imm(int reg, s32 imm); + void tst(int reg1, int reg2); + void tst(int reg, u64 imm); + + // Branch operations + void b(void* target); + void b(int condition, void* target); + void bl(void* target); + void br(int reg); + void blr(int reg); + void ret(int reg = 30); // X30 is LR by default + + // Conditional branches + void b_eq(void* target); + void b_ne(void* target); + void b_lt(void* target); + void b_le(void* target); + void b_gt(void* target); + void b_ge(void* target); + void b_lo(void* target); // unsigned lower + void b_ls(void* target); // unsigned lower or same + void b_hi(void* target); // unsigned higher + void b_hs(void* target); // unsigned higher or same + + // Stack operations + void push(int reg); + void push(int reg1, int reg2); + void pop(int reg); + void pop(int reg1, int reg2); + + // System operations + void nop(); + void brk(u16 imm = 0); + + // NEON/SIMD operations (for XMM registers) + void ldr_v(int vreg, int base_reg, s32 offset = 0); + void str_v(int vreg, int base_reg, s32 offset = 0); + void mov_v(int vdst, int vsrc); + void add_v(int vdst, int vsrc1, int vsrc2); + void sub_v(int vdst, int vsrc1, int vsrc2); + void mul_v(int vdst, int vsrc1, int vsrc2); + +private: + void emit32(u32 instruction); + void emit64(u64 instruction); + void* allocateCode(size_t size); + void makeExecutable(); + + void* code_buffer; + void* code_ptr; + size_t buffer_size; + bool owns_buffer; + std::vector> fixups; // (fixup_location, target_address) +}; + +} // namespace Core::Jit diff --git a/src/core/jit/block_manager.cpp b/src/core/jit/block_manager.cpp new file mode 100644 index 000000000..172a817ca --- /dev/null +++ b/src/core/jit/block_manager.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "block_manager.h" +#include "common/logging/log.h" + +namespace Core::Jit { + +BlockManager::BlockManager() = default; + +BlockManager::~BlockManager() { + Clear(); +} + +CodeBlock* BlockManager::GetBlock(VAddr ps4_address) { + std::lock_guard lock(mutex); + auto it = blocks.find(ps4_address); + if (it != blocks.end()) { + return it->second.get(); + } + return nullptr; +} + +CodeBlock* BlockManager::CreateBlock(VAddr ps4_address, void* arm64_code, size_t code_size, + size_t instruction_count) { + std::lock_guard lock(mutex); + + auto block = std::make_unique(ps4_address, arm64_code, code_size, instruction_count); + CodeBlock* result = block.get(); + blocks[ps4_address] = std::move(block); + + LOG_DEBUG(Core, "Created code block at PS4 address {:#x}, ARM64 code: {}, size: {}", + ps4_address, arm64_code, code_size); + + return result; +} + +void BlockManager::InvalidateBlock(VAddr ps4_address) { + std::lock_guard lock(mutex); + blocks.erase(ps4_address); + LOG_DEBUG(Core, "Invalidated code block at PS4 address {:#x}", ps4_address); +} + +void BlockManager::InvalidateRange(VAddr start, VAddr end) { + std::lock_guard lock(mutex); + + auto it = blocks.begin(); + while (it != blocks.end()) { + VAddr block_addr = it->first; + if (block_addr >= start && block_addr < end) { + it = blocks.erase(it); + } else { + auto& deps = it->second->dependencies; + bool has_dependency_in_range = false; + for (VAddr dep : deps) { + if (dep >= start && dep < end) { + has_dependency_in_range = true; + break; + } + } + if (has_dependency_in_range) { + it = blocks.erase(it); + } else { + ++it; + } + } + } + + LOG_DEBUG(Core, "Invalidated code blocks in range {:#x} - {:#x}", start, end); +} + +void BlockManager::AddDependency(VAddr block_address, VAddr dependency) { + std::lock_guard lock(mutex); + auto it = blocks.find(block_address); + if (it != blocks.end()) { + it->second->dependencies.insert(dependency); + } +} + +void BlockManager::Clear() { + std::lock_guard lock(mutex); + blocks.clear(); +} + +size_t BlockManager::GetTotalCodeSize() const { + std::lock_guard lock(mutex); + size_t total = 0; + for (const auto& [addr, block] : blocks) { + total += block->code_size; + } + return total; +} + +} // namespace Core::Jit diff --git a/src/core/jit/block_manager.h b/src/core/jit/block_manager.h new file mode 100644 index 000000000..6e0734b79 --- /dev/null +++ b/src/core/jit/block_manager.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "common/types.h" + +namespace Core::Jit { + +struct CodeBlock { + VAddr ps4_address; + void* arm64_code; + size_t code_size; + size_t instruction_count; + std::set dependencies; + bool is_linked; + + CodeBlock(VAddr addr, void* code, size_t size, size_t count) + : ps4_address(addr), arm64_code(code), code_size(size), instruction_count(count), + is_linked(false) {} +}; + +class BlockManager { +public: + BlockManager(); + ~BlockManager(); + + CodeBlock* GetBlock(VAddr ps4_address); + CodeBlock* CreateBlock(VAddr ps4_address, void* arm64_code, size_t code_size, + size_t instruction_count); + void InvalidateBlock(VAddr ps4_address); + void InvalidateRange(VAddr start, VAddr end); + void AddDependency(VAddr block_address, VAddr dependency); + void Clear(); + + size_t GetBlockCount() const { + return blocks.size(); + } + size_t GetTotalCodeSize() const; + + std::unordered_map> blocks; + mutable std::mutex mutex; +}; + +} // namespace Core::Jit diff --git a/src/core/jit/calling_convention.cpp b/src/core/jit/calling_convention.cpp new file mode 100644 index 000000000..f7931ae86 --- /dev/null +++ b/src/core/jit/calling_convention.cpp @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "calling_convention.h" +#include "common/assert.h" + +namespace Core::Jit { + +CallingConvention::CallingConvention(Arm64CodeGenerator& codegen, RegisterMapper& reg_mapper) + : codegen(codegen), reg_mapper(reg_mapper) {} + +void CallingConvention::PrepareCall(int arg_count, const std::vector& arg_regs) { + ASSERT_MSG(arg_count <= MAX_INT_ARGS, "Too many arguments"); + ASSERT_MSG(arg_regs.size() >= static_cast(arg_count), "Not enough argument registers"); + + for (int i = 0; i < arg_count && i < MAX_INT_ARGS; i++) { + int arm64_arg_reg = i; + int x86_arg_reg = arg_regs[i]; + int mapped_reg = reg_mapper.MapX86_64ToArm64(static_cast(x86_arg_reg)); + if (mapped_reg != arm64_arg_reg) { + codegen.mov(arm64_arg_reg, mapped_reg); + } + } +} + +void CallingConvention::CallFunction(void* function_ptr) { + codegen.movz(16, reinterpret_cast(function_ptr) & 0xFFFF); + codegen.movk(16, (reinterpret_cast(function_ptr) >> 16) & 0xFFFF, 16); + codegen.movk(16, (reinterpret_cast(function_ptr) >> 32) & 0xFFFF, 32); + codegen.movk(16, (reinterpret_cast(function_ptr) >> 48) & 0xFFFF, 48); + codegen.blr(16); +} + +void CallingConvention::CallFunction(int reg) { + codegen.blr(reg); +} + +void CallingConvention::Return(int return_reg) { + if (return_reg >= 0) { + int arm64_return = reg_mapper.MapX86_64ToArm64(X86_64Register::RAX); + if (return_reg != arm64_return) { + codegen.mov(arm64_return, return_reg); + } + } + codegen.ret(); +} + +void CallingConvention::SaveCallerSavedRegisters() { + saved_registers.clear(); + for (int i = 0; i < 8; i++) { + codegen.push(i); + saved_registers.push_back(i); + } +} + +void CallingConvention::RestoreCallerSavedRegisters() { + for (auto it = saved_registers.rbegin(); it != saved_registers.rend(); ++it) { + codegen.pop(*it); + } + saved_registers.clear(); +} + +} // namespace Core::Jit diff --git a/src/core/jit/calling_convention.h b/src/core/jit/calling_convention.h new file mode 100644 index 000000000..6f90c92ad --- /dev/null +++ b/src/core/jit/calling_convention.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "arm64_codegen.h" +#include "register_mapping.h" + +namespace Core::Jit { + +class CallingConvention { +public: + explicit CallingConvention(Arm64CodeGenerator& codegen, RegisterMapper& reg_mapper); + + void PrepareCall(int arg_count, const std::vector& arg_regs); + void CallFunction(void* function_ptr); + void CallFunction(int reg); + void Return(int return_reg = -1); + + void SaveCallerSavedRegisters(); + void RestoreCallerSavedRegisters(); + + static constexpr int MAX_INT_ARGS = 8; + static constexpr int MAX_FLOAT_ARGS = 8; + +private: + Arm64CodeGenerator& codegen; + RegisterMapper& reg_mapper; + std::vector saved_registers; +}; + +} // namespace Core::Jit diff --git a/src/core/jit/execution_engine.cpp b/src/core/jit/execution_engine.cpp new file mode 100644 index 000000000..768f63392 --- /dev/null +++ b/src/core/jit/execution_engine.cpp @@ -0,0 +1,202 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "common/decoder.h" +#include "common/logging/log.h" +#include "core/memory.h" +#include "execution_engine.h" + +namespace Core::Jit { + +static void* AllocateExecutableMemory(size_t size) { + size = (size + 4095) & ~4095; + void* ptr = + mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (ptr == MAP_FAILED) { + LOG_CRITICAL(Core, "Failed to allocate executable memory: {}", strerror(errno)); + return nullptr; + } + return ptr; +} + +ExecutionEngine::ExecutionEngine() + : code_buffer(nullptr), code_buffer_size(DEFAULT_CODE_BUFFER_SIZE), code_buffer_used(0) { + block_manager = std::make_unique(); + register_mapper = std::make_unique(); +} + +ExecutionEngine::~ExecutionEngine() { + Shutdown(); +} + +void ExecutionEngine::Initialize() { + code_buffer = AllocateExecutableMemory(code_buffer_size); + if (!code_buffer) { + throw std::bad_alloc(); + } + + code_generator = std::make_unique(code_buffer_size, code_buffer); + translator = std::make_unique(*code_generator, *register_mapper); + + LOG_INFO(Core, "JIT Execution Engine initialized"); +} + +void ExecutionEngine::Shutdown() { + if (code_buffer) { + munmap(code_buffer, code_buffer_size); + code_buffer = nullptr; + } + code_generator.reset(); + translator.reset(); + block_manager.reset(); + register_mapper.reset(); +} + +void* ExecutionEngine::AllocateCodeBuffer(size_t size) { + size = (size + 15) & ~15; + if (code_buffer_used + size > code_buffer_size) { + LOG_WARNING(Core, "Code buffer exhausted, need to allocate more"); + return nullptr; + } + void* result = static_cast(code_buffer) + code_buffer_used; + code_buffer_used += size; + return result; +} + +CodeBlock* ExecutionEngine::TranslateBasicBlock(VAddr start_address, size_t max_instructions) { + auto* memory = Core::Memory::Instance(); + auto& address_space = memory->GetAddressSpace(); + void* ps4_code_ptr = address_space.TranslateAddress(start_address); + if (!ps4_code_ptr) { + LOG_ERROR(Core, "Invalid PS4 address for translation: {:#x}", start_address); + return nullptr; + } + + code_generator->reset(); + void* block_start = code_generator->getCurr(); + + VAddr current_address = start_address; + size_t instruction_count = 0; + bool block_end = false; + + while (instruction_count < max_instructions && !block_end) { + ZydisDecodedInstruction instruction; + ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; + + void* code_ptr = address_space.TranslateAddress(current_address); + if (!code_ptr) { + break; + } + + ZyanStatus status = + Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_ptr, 15); + if (!ZYAN_SUCCESS(status)) { + LOG_WARNING(Core, "Failed to decode instruction at {:#x}", current_address); + break; + } + + bool translated = translator->TranslateInstruction(instruction, operands, current_address); + if (!translated) { + LOG_WARNING(Core, "Failed to translate instruction at {:#x}", current_address); + break; + } + + instruction_count++; + current_address += instruction.length; + + switch (instruction.mnemonic) { + case ZYDIS_MNEMONIC_RET: + case ZYDIS_MNEMONIC_JMP: + case ZYDIS_MNEMONIC_CALL: + block_end = true; + break; + default: + break; + } + } + + if (instruction_count == 0) { + return nullptr; + } + + size_t code_size = code_generator->getSize(); + CodeBlock* block = + block_manager->CreateBlock(start_address, block_start, code_size, instruction_count); + + LOG_DEBUG(Core, "Translated basic block at {:#x}, {} instructions, {} bytes", start_address, + instruction_count, code_size); + + return block; +} + +CodeBlock* ExecutionEngine::TranslateBlock(VAddr ps4_address) { + CodeBlock* existing = block_manager->GetBlock(ps4_address); + if (existing) { + return existing; + } + + return TranslateBasicBlock(ps4_address); +} + +void ExecutionEngine::LinkBlock(CodeBlock* block, VAddr target_address) { + CodeBlock* target_block = block_manager->GetBlock(target_address); + if (target_block && !block->is_linked) { + void* link_location = static_cast(block->arm64_code) + block->code_size - 4; + code_generator->setSize(reinterpret_cast(link_location) - + static_cast(code_generator->getCode())); + code_generator->b(target_block->arm64_code); + block->is_linked = true; + } +} + +bool ExecutionEngine::ExecuteBlock(VAddr ps4_address) { + CodeBlock* block = TranslateBlock(ps4_address); + if (!block) { + LOG_ERROR(Core, "Failed to translate or find block at {:#x}", ps4_address); + return false; + } + + typedef void (*BlockFunc)(); + BlockFunc func = reinterpret_cast(block->arm64_code); + func(); + + return true; +} + +void ExecutionEngine::InvalidateBlock(VAddr ps4_address) { + block_manager->InvalidateBlock(ps4_address); +} + +void ExecutionEngine::InvalidateRange(VAddr start, VAddr end) { + block_manager->InvalidateRange(start, end); +} + +bool ExecutionEngine::IsJitCode(void* code_ptr) const { + if (!code_buffer) { + return false; + } + u8* ptr = static_cast(code_ptr); + u8* start = static_cast(code_buffer); + u8* end = start + code_buffer_size; + return ptr >= start && ptr < end; +} + +VAddr ExecutionEngine::GetPs4AddressForJitCode(void* code_ptr) const { + if (!IsJitCode(code_ptr)) { + return 0; + } + std::lock_guard lock(block_manager->mutex); + for (const auto& [ps4_addr, block] : block_manager->blocks) { + u8* block_start = static_cast(block->arm64_code); + u8* block_end = block_start + block->code_size; + u8* ptr = static_cast(code_ptr); + if (ptr >= block_start && ptr < block_end) { + return ps4_addr; + } + } + return 0; +} + +} // namespace Core::Jit diff --git a/src/core/jit/execution_engine.h b/src/core/jit/execution_engine.h new file mode 100644 index 000000000..ec8195397 --- /dev/null +++ b/src/core/jit/execution_engine.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "arm64_codegen.h" +#include "block_manager.h" +#include "common/singleton.h" +#include "common/types.h" +#include "register_mapping.h" +#include "x86_64_translator.h" + +namespace Core::Jit { + +class ExecutionEngine { +public: + ExecutionEngine(); + ~ExecutionEngine(); + + bool ExecuteBlock(VAddr ps4_address); + CodeBlock* TranslateBlock(VAddr ps4_address); + void InvalidateBlock(VAddr ps4_address); + void InvalidateRange(VAddr start, VAddr end); + + bool IsJitCode(void* code_ptr) const; + VAddr GetPs4AddressForJitCode(void* code_ptr) const; + + void Initialize(); + void Shutdown(); + +private: + CodeBlock* TranslateBasicBlock(VAddr start_address, size_t max_instructions = 100); + void* AllocateCodeBuffer(size_t size); + void LinkBlock(CodeBlock* block, VAddr target_address); + + std::unique_ptr block_manager; + std::unique_ptr register_mapper; + std::unique_ptr code_generator; + std::unique_ptr translator; + + void* code_buffer; + size_t code_buffer_size; + size_t code_buffer_used; + + static constexpr size_t DEFAULT_CODE_BUFFER_SIZE = 64_MB; + + friend class BlockManager; +}; + +using JitEngine = Common::Singleton; + +} // namespace Core::Jit diff --git a/src/core/jit/register_mapping.cpp b/src/core/jit/register_mapping.cpp new file mode 100644 index 000000000..7a5634cb7 --- /dev/null +++ b/src/core/jit/register_mapping.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/assert.h" +#include "register_mapping.h" + +namespace Core::Jit { + +RegisterMapper::RegisterMapper() : register_save_area(nullptr) { + x86_to_arm64_map.fill(INVALID_MAPPING); + spilled_registers.fill(false); + + x86_to_arm64_map[static_cast(X86_64Register::RAX)] = + GetArm64RegisterNumber(Arm64Register::X0); + x86_to_arm64_map[static_cast(X86_64Register::RCX)] = + GetArm64RegisterNumber(Arm64Register::X1); + x86_to_arm64_map[static_cast(X86_64Register::RDX)] = + GetArm64RegisterNumber(Arm64Register::X2); + x86_to_arm64_map[static_cast(X86_64Register::RBX)] = + GetArm64RegisterNumber(Arm64Register::X19); + x86_to_arm64_map[static_cast(X86_64Register::RSP)] = + GetArm64RegisterNumber(Arm64Register::SP); + x86_to_arm64_map[static_cast(X86_64Register::RBP)] = + GetArm64RegisterNumber(Arm64Register::X29); + x86_to_arm64_map[static_cast(X86_64Register::RSI)] = + GetArm64RegisterNumber(Arm64Register::X3); + x86_to_arm64_map[static_cast(X86_64Register::RDI)] = + GetArm64RegisterNumber(Arm64Register::X0); + x86_to_arm64_map[static_cast(X86_64Register::R8)] = + GetArm64RegisterNumber(Arm64Register::X4); + x86_to_arm64_map[static_cast(X86_64Register::R9)] = + GetArm64RegisterNumber(Arm64Register::X5); + x86_to_arm64_map[static_cast(X86_64Register::R10)] = + GetArm64RegisterNumber(Arm64Register::X6); + x86_to_arm64_map[static_cast(X86_64Register::R11)] = + GetArm64RegisterNumber(Arm64Register::X7); + x86_to_arm64_map[static_cast(X86_64Register::R12)] = + GetArm64RegisterNumber(Arm64Register::X20); + x86_to_arm64_map[static_cast(X86_64Register::R13)] = + GetArm64RegisterNumber(Arm64Register::X21); + x86_to_arm64_map[static_cast(X86_64Register::R14)] = + GetArm64RegisterNumber(Arm64Register::X22); + x86_to_arm64_map[static_cast(X86_64Register::R15)] = + GetArm64RegisterNumber(Arm64Register::X23); + + x86_to_arm64_map[static_cast(X86_64Register::XMM0)] = + GetArm64RegisterNumber(Arm64Register::V0); + x86_to_arm64_map[static_cast(X86_64Register::XMM1)] = + GetArm64RegisterNumber(Arm64Register::V1); + x86_to_arm64_map[static_cast(X86_64Register::XMM2)] = + GetArm64RegisterNumber(Arm64Register::V2); + x86_to_arm64_map[static_cast(X86_64Register::XMM3)] = + GetArm64RegisterNumber(Arm64Register::V3); + x86_to_arm64_map[static_cast(X86_64Register::XMM4)] = + GetArm64RegisterNumber(Arm64Register::V4); + x86_to_arm64_map[static_cast(X86_64Register::XMM5)] = + GetArm64RegisterNumber(Arm64Register::V5); + x86_to_arm64_map[static_cast(X86_64Register::XMM6)] = + GetArm64RegisterNumber(Arm64Register::V6); + x86_to_arm64_map[static_cast(X86_64Register::XMM7)] = + GetArm64RegisterNumber(Arm64Register::V7); + x86_to_arm64_map[static_cast(X86_64Register::XMM8)] = + GetArm64RegisterNumber(Arm64Register::V8); + x86_to_arm64_map[static_cast(X86_64Register::XMM9)] = + GetArm64RegisterNumber(Arm64Register::V9); + x86_to_arm64_map[static_cast(X86_64Register::XMM10)] = + GetArm64RegisterNumber(Arm64Register::V10); + x86_to_arm64_map[static_cast(X86_64Register::XMM11)] = + GetArm64RegisterNumber(Arm64Register::V11); + x86_to_arm64_map[static_cast(X86_64Register::XMM12)] = + GetArm64RegisterNumber(Arm64Register::V12); + x86_to_arm64_map[static_cast(X86_64Register::XMM13)] = + GetArm64RegisterNumber(Arm64Register::V13); + x86_to_arm64_map[static_cast(X86_64Register::XMM14)] = + GetArm64RegisterNumber(Arm64Register::V14); + x86_to_arm64_map[static_cast(X86_64Register::XMM15)] = + GetArm64RegisterNumber(Arm64Register::V15); + + x86_to_arm64_map[static_cast(X86_64Register::FLAGS)] = + GetArm64RegisterNumber(Arm64Register::X11); +} + +int RegisterMapper::MapX86_64ToArm64(X86_64Register x86_reg) { + size_t index = static_cast(x86_reg); + ASSERT_MSG(index < static_cast(X86_64Register::COUNT), "Invalid x86_64 register"); + return x86_to_arm64_map[index]; +} + +int RegisterMapper::MapX86_64XmmToArm64Neon(X86_64Register xmm_reg) { + if (!IsXmmRegister(xmm_reg)) { + return INVALID_MAPPING; + } + return MapX86_64ToArm64(xmm_reg); +} + +bool RegisterMapper::IsXmmRegister(X86_64Register reg) { + return reg >= X86_64Register::XMM0 && reg <= X86_64Register::XMM15; +} + +void RegisterMapper::SpillRegister(X86_64Register x86_reg) { + size_t index = static_cast(x86_reg); + ASSERT_MSG(index < static_cast(X86_64Register::COUNT), "Invalid x86_64 register"); + spilled_registers[index] = true; +} + +void RegisterMapper::ReloadRegister(X86_64Register x86_reg) { + size_t index = static_cast(x86_reg); + ASSERT_MSG(index < static_cast(X86_64Register::COUNT), "Invalid x86_64 register"); + spilled_registers[index] = false; +} + +bool RegisterMapper::IsRegisterSpilled(X86_64Register x86_reg) const { + size_t index = static_cast(x86_reg); + ASSERT_MSG(index < static_cast(X86_64Register::COUNT), "Invalid x86_64 register"); + return spilled_registers[index]; +} + +void RegisterMapper::SaveAllRegisters() {} + +void RegisterMapper::RestoreAllRegisters() {} + +} // namespace Core::Jit diff --git a/src/core/jit/register_mapping.h b/src/core/jit/register_mapping.h new file mode 100644 index 000000000..80e1caab7 --- /dev/null +++ b/src/core/jit/register_mapping.h @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/types.h" + +namespace Core::Jit { + +enum class X86_64Register : u8 { + RAX = 0, + RCX = 1, + RDX = 2, + RBX = 3, + RSP = 4, + RBP = 5, + RSI = 6, + RDI = 7, + R8 = 8, + R9 = 9, + R10 = 10, + R11 = 11, + R12 = 12, + R13 = 13, + R14 = 14, + R15 = 15, + XMM0 = 16, + XMM1 = 17, + XMM2 = 18, + XMM3 = 19, + XMM4 = 20, + XMM5 = 21, + XMM6 = 22, + XMM7 = 23, + XMM8 = 24, + XMM9 = 25, + XMM10 = 26, + XMM11 = 27, + XMM12 = 28, + XMM13 = 29, + XMM14 = 30, + XMM15 = 31, + FLAGS = 32, + COUNT = 33 +}; + +enum class Arm64Register : u8 { + X0 = 0, + X1 = 1, + X2 = 2, + X3 = 3, + X4 = 4, + X5 = 5, + X6 = 6, + X7 = 7, + X8 = 8, + X9 = 9, + X10 = 10, + X11 = 11, + X12 = 12, + X13 = 13, + X14 = 14, + X15 = 15, + X16 = 16, + X17 = 17, + X18 = 18, + X19 = 19, + X20 = 20, + X21 = 21, + X22 = 22, + X23 = 23, + X24 = 24, + X25 = 25, + X26 = 26, + X27 = 27, + X28 = 28, + X29 = 29, + X30 = 30, + SP = 31, + V0 = 32, + V1 = 33, + V2 = 34, + V3 = 35, + V4 = 36, + V5 = 37, + V6 = 38, + V7 = 39, + V8 = 40, + V9 = 41, + V10 = 42, + V11 = 43, + V12 = 44, + V13 = 45, + V14 = 46, + V15 = 47, + COUNT = 48 +}; + +class RegisterMapper { +public: + RegisterMapper(); + + int MapX86_64ToArm64(X86_64Register x86_reg); + int MapX86_64XmmToArm64Neon(X86_64Register xmm_reg); + bool IsXmmRegister(X86_64Register reg); + + void SpillRegister(X86_64Register x86_reg); + void ReloadRegister(X86_64Register x86_reg); + bool IsRegisterSpilled(X86_64Register x86_reg) const; + + void SaveAllRegisters(); + void RestoreAllRegisters(); + + static constexpr int SCRATCH_REG = 9; + static constexpr int SCRATCH_REG2 = 10; + static constexpr int FLAGS_REG = 11; + static constexpr int STACK_POINTER = 31; + +private: + static constexpr int INVALID_MAPPING = -1; + + std::array(X86_64Register::COUNT)> x86_to_arm64_map; + std::array(X86_64Register::COUNT)> spilled_registers; + void* register_save_area; +}; + +inline int GetArm64RegisterNumber(Arm64Register reg) { + return static_cast(reg); +} + +inline int GetX86_64RegisterNumber(X86_64Register reg) { + return static_cast(reg); +} + +} // namespace Core::Jit diff --git a/src/core/jit/simd_translator.cpp b/src/core/jit/simd_translator.cpp new file mode 100644 index 000000000..aa9eb5780 --- /dev/null +++ b/src/core/jit/simd_translator.cpp @@ -0,0 +1,206 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/logging/log.h" +#include "register_mapping.h" +#include "simd_translator.h" + +namespace Core::Jit { + +SimdTranslator::SimdTranslator(Arm64CodeGenerator& codegen, RegisterMapper& reg_mapper) + : codegen(codegen), reg_mapper(reg_mapper) {} + +int SimdTranslator::GetArm64NeonRegister(const ZydisDecodedOperand& operand) { + if (operand.type != ZYDIS_OPERAND_TYPE_REGISTER) { + return -1; + } + if (operand.reg.value < ZYDIS_REGISTER_XMM0 || operand.reg.value > ZYDIS_REGISTER_XMM15) { + return -1; + } + X86_64Register xmm_reg = + static_cast(static_cast(X86_64Register::XMM0) + + static_cast(operand.reg.value - ZYDIS_REGISTER_XMM0)); + return reg_mapper.MapX86_64XmmToArm64Neon(xmm_reg); +} + +void SimdTranslator::LoadMemoryOperandV(int vreg, const ZydisDecodedOperand& mem_op) { + ASSERT_MSG(mem_op.type == ZYDIS_OPERAND_TYPE_MEMORY, "Expected memory operand"); + + int addr_reg = RegisterMapper::SCRATCH_REG; + codegen.mov(addr_reg, 0); + + if (mem_op.mem.base != ZYDIS_REGISTER_NONE && mem_op.mem.base != ZYDIS_REGISTER_RIP) { + if (mem_op.mem.base >= ZYDIS_REGISTER_RAX && mem_op.mem.base <= ZYDIS_REGISTER_R15) { + X86_64Register x86_base = + static_cast(mem_op.mem.base - ZYDIS_REGISTER_RAX); + if (x86_base < X86_64Register::COUNT) { + int base_reg = reg_mapper.MapX86_64ToArm64(x86_base); + codegen.mov(addr_reg, base_reg); + } + } + } + + if (mem_op.mem.disp.value != 0) { + codegen.add(addr_reg, addr_reg, static_cast(mem_op.mem.disp.value)); + } + + codegen.ldr_v(vreg, addr_reg, 0); +} + +void SimdTranslator::StoreMemoryOperandV(int vreg, const ZydisDecodedOperand& mem_op) { + ASSERT_MSG(mem_op.type == ZYDIS_OPERAND_TYPE_MEMORY, "Expected memory operand"); + + int addr_reg = RegisterMapper::SCRATCH_REG; + codegen.mov(addr_reg, 0); + + if (mem_op.mem.base != ZYDIS_REGISTER_NONE) { + if (mem_op.mem.base >= ZYDIS_REGISTER_RAX && mem_op.mem.base <= ZYDIS_REGISTER_R15) { + X86_64Register x86_base = + static_cast(mem_op.mem.base - ZYDIS_REGISTER_RAX); + if (x86_base < X86_64Register::COUNT) { + int base_reg = reg_mapper.MapX86_64ToArm64(x86_base); + codegen.mov(addr_reg, base_reg); + } + } + } + + if (mem_op.mem.disp.value != 0) { + codegen.add(addr_reg, addr_reg, static_cast(mem_op.mem.disp.value)); + } + + codegen.str_v(vreg, addr_reg, 0); +} + +bool SimdTranslator::TranslateSseInstruction(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + switch (instruction.mnemonic) { + case ZYDIS_MNEMONIC_MOVAPS: + return TranslateMovaps(instruction, operands); + case ZYDIS_MNEMONIC_MOVUPS: + return TranslateMovups(instruction, operands); + case ZYDIS_MNEMONIC_ADDPS: + return TranslateAddps(instruction, operands); + case ZYDIS_MNEMONIC_SUBPS: + return TranslateSubps(instruction, operands); + case ZYDIS_MNEMONIC_MULPS: + return TranslateMulps(instruction, operands); + default: + LOG_WARNING(Core, "Unsupported SSE instruction: {}", + ZydisMnemonicGetString(instruction.mnemonic)); + return false; + } +} + +bool SimdTranslator::TranslateMovaps(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_vreg = GetArm64NeonRegister(dst); + if (dst_vreg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_vreg = GetArm64NeonRegister(src); + if (src_vreg == -1) { + return false; + } + codegen.mov_v(dst_vreg, src_vreg); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperandV(dst_vreg, src); + } else { + return false; + } + + return true; +} + +bool SimdTranslator::TranslateMovups(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + return TranslateMovaps(instruction, operands); +} + +bool SimdTranslator::TranslateAddps(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_vreg = GetArm64NeonRegister(dst); + if (dst_vreg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_vreg = GetArm64NeonRegister(src); + if (src_vreg == -1) { + return false; + } + codegen.add_v(dst_vreg, dst_vreg, src_vreg); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + int scratch_vreg = 8; + LoadMemoryOperandV(scratch_vreg, src); + codegen.add_v(dst_vreg, dst_vreg, scratch_vreg); + } else { + return false; + } + + return true; +} + +bool SimdTranslator::TranslateSubps(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_vreg = GetArm64NeonRegister(dst); + if (dst_vreg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_vreg = GetArm64NeonRegister(src); + if (src_vreg == -1) { + return false; + } + codegen.sub_v(dst_vreg, dst_vreg, src_vreg); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + int scratch_vreg = 8; + LoadMemoryOperandV(scratch_vreg, src); + codegen.sub_v(dst_vreg, dst_vreg, scratch_vreg); + } else { + return false; + } + + return true; +} + +bool SimdTranslator::TranslateMulps(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_vreg = GetArm64NeonRegister(dst); + if (dst_vreg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_vreg = GetArm64NeonRegister(src); + if (src_vreg == -1) { + return false; + } + codegen.mul_v(dst_vreg, dst_vreg, src_vreg); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + int scratch_vreg = 8; + LoadMemoryOperandV(scratch_vreg, src); + codegen.mul_v(dst_vreg, dst_vreg, scratch_vreg); + } else { + return false; + } + + return true; +} + +} // namespace Core::Jit diff --git a/src/core/jit/simd_translator.h b/src/core/jit/simd_translator.h new file mode 100644 index 000000000..916d662b7 --- /dev/null +++ b/src/core/jit/simd_translator.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "arm64_codegen.h" +#include "register_mapping.h" + +namespace Core::Jit { + +class SimdTranslator { +public: + explicit SimdTranslator(Arm64CodeGenerator& codegen, RegisterMapper& reg_mapper); + + bool TranslateSseInstruction(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + + bool TranslateMovaps(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateMovups(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateAddps(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateSubps(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateMulps(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + +private: + int GetArm64NeonRegister(const ZydisDecodedOperand& operand); + void LoadMemoryOperandV(int vreg, const ZydisDecodedOperand& mem_op); + void StoreMemoryOperandV(int vreg, const ZydisDecodedOperand& mem_op); + + Arm64CodeGenerator& codegen; + RegisterMapper& reg_mapper; +}; + +} // namespace Core::Jit diff --git a/src/core/jit/x86_64_translator.cpp b/src/core/jit/x86_64_translator.cpp new file mode 100644 index 000000000..0352955ed --- /dev/null +++ b/src/core/jit/x86_64_translator.cpp @@ -0,0 +1,701 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/assert.h" +#include "common/logging/log.h" +#include "register_mapping.h" +#include "x86_64_translator.h" + +namespace Core::Jit { + +X86_64Translator::X86_64Translator(Arm64CodeGenerator& codegen, RegisterMapper& reg_mapper) + : codegen(codegen), reg_mapper(reg_mapper) {} + +bool X86_64Translator::TranslateInstruction(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands, VAddr address) { + switch (instruction.mnemonic) { + case ZYDIS_MNEMONIC_MOV: + return TranslateMov(instruction, operands); + case ZYDIS_MNEMONIC_ADD: + return TranslateAdd(instruction, operands); + case ZYDIS_MNEMONIC_SUB: + return TranslateSub(instruction, operands); + case ZYDIS_MNEMONIC_MUL: + return TranslateMul(instruction, operands); + case ZYDIS_MNEMONIC_DIV: + case ZYDIS_MNEMONIC_IDIV: + return TranslateDiv(instruction, operands); + case ZYDIS_MNEMONIC_AND: + return TranslateAnd(instruction, operands); + case ZYDIS_MNEMONIC_OR: + return TranslateOr(instruction, operands); + case ZYDIS_MNEMONIC_XOR: + return TranslateXor(instruction, operands); + case ZYDIS_MNEMONIC_NOT: + return TranslateNot(instruction, operands); + case ZYDIS_MNEMONIC_SHL: + return TranslateShl(instruction, operands); + case ZYDIS_MNEMONIC_SHR: + return TranslateShr(instruction, operands); + case ZYDIS_MNEMONIC_SAR: + return TranslateSar(instruction, operands); + case ZYDIS_MNEMONIC_PUSH: + return TranslatePush(instruction, operands); + case ZYDIS_MNEMONIC_POP: + return TranslatePop(instruction, operands); + case ZYDIS_MNEMONIC_CALL: + return TranslateCall(instruction, operands, address); + case ZYDIS_MNEMONIC_RET: + return TranslateRet(instruction, operands); + case ZYDIS_MNEMONIC_JMP: + return TranslateJmp(instruction, operands, address); + case ZYDIS_MNEMONIC_CMP: + return TranslateCmp(instruction, operands); + case ZYDIS_MNEMONIC_TEST: + return TranslateTest(instruction, operands); + case ZYDIS_MNEMONIC_LEA: + return TranslateLea(instruction, operands); + default: + LOG_ERROR(Core, "Unsupported instruction: {}", + ZydisMnemonicGetString(instruction.mnemonic)); + return false; + } +} + +X86_64Register X86_64Translator::ZydisToX86_64Register(ZydisRegister reg) { + if (reg >= ZYDIS_REGISTER_RAX && reg <= ZYDIS_REGISTER_R15) { + return static_cast(static_cast(reg - ZYDIS_REGISTER_RAX)); + } else if (reg >= ZYDIS_REGISTER_XMM0 && reg <= ZYDIS_REGISTER_XMM15) { + return static_cast(static_cast(X86_64Register::XMM0) + + static_cast(reg - ZYDIS_REGISTER_XMM0)); + } + return X86_64Register::COUNT; +} + +int X86_64Translator::GetArm64Register(const ZydisDecodedOperand& operand) { + if (operand.type != ZYDIS_OPERAND_TYPE_REGISTER) { + return -1; + } + X86_64Register x86_reg = ZydisToX86_64Register(operand.reg.value); + if (x86_reg == X86_64Register::COUNT) { + return -1; + } + return reg_mapper.MapX86_64ToArm64(x86_reg); +} + +int X86_64Translator::GetArm64XmmRegister(const ZydisDecodedOperand& operand) { + if (operand.type != ZYDIS_OPERAND_TYPE_REGISTER) { + return -1; + } + X86_64Register x86_reg = ZydisToX86_64Register(operand.reg.value); + if (!reg_mapper.IsXmmRegister(x86_reg)) { + return -1; + } + return reg_mapper.MapX86_64XmmToArm64Neon(x86_reg); +} + +void X86_64Translator::CalculateMemoryAddress(int dst_reg, const ZydisDecodedOperand& mem_op) { + ASSERT_MSG(mem_op.type == ZYDIS_OPERAND_TYPE_MEMORY, "Expected memory operand"); + + const auto& mem = mem_op.mem; + int base_reg = -1; + int index_reg = -1; + + if (mem.base != ZYDIS_REGISTER_NONE && mem.base != ZYDIS_REGISTER_RIP) { + X86_64Register x86_base = ZydisToX86_64Register(mem.base); + if (x86_base != X86_64Register::COUNT) { + base_reg = reg_mapper.MapX86_64ToArm64(x86_base); + } + } + + if (mem.index != ZYDIS_REGISTER_NONE) { + X86_64Register x86_index = ZydisToX86_64Register(mem.index); + if (x86_index != X86_64Register::COUNT) { + index_reg = reg_mapper.MapX86_64ToArm64(x86_index); + } + } + + if (base_reg == -1 && index_reg == -1 && mem.disp.value == 0) { + codegen.mov(dst_reg, 0); + return; + } + + if (base_reg != -1) { + codegen.mov(dst_reg, base_reg); + } else { + codegen.mov(dst_reg, 0); + } + + if (index_reg != -1) { + if (mem.scale > 0 && mem.scale <= 8) { + codegen.mov(RegisterMapper::SCRATCH_REG, static_cast(mem.scale)); + codegen.mul(RegisterMapper::SCRATCH_REG, index_reg, RegisterMapper::SCRATCH_REG); + codegen.add(dst_reg, dst_reg, RegisterMapper::SCRATCH_REG); + } else { + codegen.add(dst_reg, dst_reg, index_reg); + } + } + + if (mem.disp.value != 0) { + codegen.add(dst_reg, dst_reg, static_cast(mem.disp.value)); + } +} + +void X86_64Translator::LoadMemoryOperand(int dst_reg, const ZydisDecodedOperand& mem_op, + size_t size) { + CalculateMemoryAddress(RegisterMapper::SCRATCH_REG, mem_op); + + if (mem_op.mem.base == ZYDIS_REGISTER_RIP) { + LOG_WARNING(Core, "RIP-relative addressing not fully supported in JIT"); + } + + switch (size) { + case 1: + codegen.ldrb(dst_reg, RegisterMapper::SCRATCH_REG, 0); + break; + case 2: + codegen.ldrh(dst_reg, RegisterMapper::SCRATCH_REG, 0); + break; + case 4: + case 8: + codegen.ldr(dst_reg, RegisterMapper::SCRATCH_REG, 0); + break; + default: + ASSERT_MSG(false, "Unsupported memory load size: {}", size); + } +} + +void X86_64Translator::StoreMemoryOperand(int src_reg, const ZydisDecodedOperand& mem_op, + size_t size) { + CalculateMemoryAddress(RegisterMapper::SCRATCH_REG, mem_op); + + if (mem_op.mem.base == ZYDIS_REGISTER_RIP) { + LOG_WARNING(Core, "RIP-relative addressing not fully supported in JIT"); + } + + switch (size) { + case 1: + codegen.strb(src_reg, RegisterMapper::SCRATCH_REG, 0); + break; + case 2: + codegen.strh(src_reg, RegisterMapper::SCRATCH_REG, 0); + break; + case 4: + case 8: + codegen.str(src_reg, RegisterMapper::SCRATCH_REG, 0); + break; + default: + ASSERT_MSG(false, "Unsupported memory store size: {}", size); + } +} + +void X86_64Translator::LoadImmediate(int dst_reg, const ZydisDecodedOperand& imm_op) { + ASSERT_MSG(imm_op.type == ZYDIS_OPERAND_TYPE_IMMEDIATE, "Expected immediate operand"); + s64 value = static_cast(imm_op.imm.value.s); + codegen.mov(dst_reg, value); +} + +bool X86_64Translator::TranslateMov(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + if (dst.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + codegen.mov(dst_reg, src_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + LoadImmediate(dst_reg, src); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(dst_reg, src, instruction.operand_width / 8); + } else { + return false; + } + } else if (dst.type == ZYDIS_OPERAND_TYPE_MEMORY) { + int src_reg = -1; + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + LoadImmediate(RegisterMapper::SCRATCH_REG, src); + src_reg = RegisterMapper::SCRATCH_REG; + } else { + return false; + } + StoreMemoryOperand(src_reg, dst, instruction.operand_width / 8); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateAdd(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + codegen.add(dst_reg, dst_reg, src_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + s32 imm = static_cast(src.imm.value.s); + codegen.add_imm(dst_reg, dst_reg, imm); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(RegisterMapper::SCRATCH_REG, src, instruction.operand_width / 8); + codegen.add(dst_reg, dst_reg, RegisterMapper::SCRATCH_REG); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateSub(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + codegen.sub(dst_reg, dst_reg, src_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + s32 imm = static_cast(src.imm.value.s); + codegen.sub_imm(dst_reg, dst_reg, imm); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(RegisterMapper::SCRATCH_REG, src, instruction.operand_width / 8); + codegen.sub(dst_reg, dst_reg, RegisterMapper::SCRATCH_REG); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateMul(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(operands[1]); + if (src_reg == -1) { + return false; + } + codegen.mul(dst_reg, dst_reg, src_reg); + } else if (operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(RegisterMapper::SCRATCH_REG, operands[1], instruction.operand_width / 8); + codegen.mul(dst_reg, dst_reg, RegisterMapper::SCRATCH_REG); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateDiv(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + LOG_WARNING(Core, "DIV instruction translation not fully implemented"); + return false; +} + +bool X86_64Translator::TranslateAnd(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + codegen.and_(dst_reg, dst_reg, src_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + u64 imm = static_cast(src.imm.value.u); + codegen.and_(dst_reg, dst_reg, imm); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(RegisterMapper::SCRATCH_REG, src, instruction.operand_width / 8); + codegen.and_(dst_reg, dst_reg, RegisterMapper::SCRATCH_REG); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateOr(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + codegen.orr(dst_reg, dst_reg, src_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + u64 imm = static_cast(src.imm.value.u); + codegen.orr(dst_reg, dst_reg, imm); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(RegisterMapper::SCRATCH_REG, src, instruction.operand_width / 8); + codegen.orr(dst_reg, dst_reg, RegisterMapper::SCRATCH_REG); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateXor(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + codegen.eor(dst_reg, dst_reg, src_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + u64 imm = static_cast(src.imm.value.u); + codegen.eor(dst_reg, dst_reg, imm); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(RegisterMapper::SCRATCH_REG, src, instruction.operand_width / 8); + codegen.eor(dst_reg, dst_reg, RegisterMapper::SCRATCH_REG); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateNot(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + codegen.mvn(dst_reg, dst_reg); + + return true; +} + +bool X86_64Translator::TranslateShl(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER && + (src.reg.value == ZYDIS_REGISTER_CL || src.reg.value == ZYDIS_REGISTER_RCX)) { + int cl_reg = reg_mapper.MapX86_64ToArm64(X86_64Register::RCX); + codegen.lsl(dst_reg, dst_reg, cl_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + u64 shift_val = src.imm.value.u; + if (shift_val < 64) { + codegen.lsl(dst_reg, dst_reg, static_cast(shift_val)); + } else { + codegen.mov(dst_reg, 0); + } + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateShr(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER && + (src.reg.value == ZYDIS_REGISTER_CL || src.reg.value == ZYDIS_REGISTER_RCX)) { + int cl_reg = reg_mapper.MapX86_64ToArm64(X86_64Register::RCX); + codegen.lsr(dst_reg, dst_reg, cl_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + u64 shift_val = src.imm.value.u; + if (shift_val < 64) { + codegen.lsr(dst_reg, dst_reg, static_cast(shift_val)); + } else { + codegen.mov(dst_reg, 0); + } + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateSar(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER && + (src.reg.value == ZYDIS_REGISTER_CL || src.reg.value == ZYDIS_REGISTER_RCX)) { + int cl_reg = reg_mapper.MapX86_64ToArm64(X86_64Register::RCX); + codegen.asr(dst_reg, dst_reg, cl_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + u64 shift_val = src.imm.value.u; + if (shift_val < 64) { + codegen.asr(dst_reg, dst_reg, static_cast(shift_val)); + } else { + codegen.mov(dst_reg, 0); + } + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslatePush(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& src = operands[0]; + + int sp_reg = reg_mapper.MapX86_64ToArm64(X86_64Register::RSP); + codegen.sub(sp_reg, sp_reg, 8); + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + codegen.str(src_reg, sp_reg, 0); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + LoadImmediate(RegisterMapper::SCRATCH_REG, src); + codegen.str(RegisterMapper::SCRATCH_REG, sp_reg, 0); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(RegisterMapper::SCRATCH_REG, src, instruction.operand_width / 8); + codegen.str(RegisterMapper::SCRATCH_REG, sp_reg, 0); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslatePop(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + int sp_reg = reg_mapper.MapX86_64ToArm64(X86_64Register::RSP); + codegen.ldr(dst_reg, sp_reg, 0); + codegen.add(sp_reg, sp_reg, 8); + + return true; +} + +bool X86_64Translator::TranslateCall(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands, VAddr address) { + LOG_WARNING(Core, "CALL instruction translation needs execution engine integration"); + return false; +} + +bool X86_64Translator::TranslateRet(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + codegen.ret(); + return true; +} + +bool X86_64Translator::TranslateJmp(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands, VAddr address) { + LOG_WARNING(Core, "JMP instruction translation needs execution engine integration"); + return false; +} + +bool X86_64Translator::TranslateCmp(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + codegen.cmp(dst_reg, src_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + s32 imm = static_cast(src.imm.value.s); + codegen.cmp_imm(dst_reg, imm); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(RegisterMapper::SCRATCH_REG, src, instruction.operand_width / 8); + codegen.cmp(dst_reg, RegisterMapper::SCRATCH_REG); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateTest(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + if (src.type == ZYDIS_OPERAND_TYPE_REGISTER) { + int src_reg = GetArm64Register(src); + if (src_reg == -1) { + return false; + } + codegen.tst(dst_reg, src_reg); + } else if (src.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + u64 imm = static_cast(src.imm.value.u); + codegen.tst(dst_reg, imm); + } else if (src.type == ZYDIS_OPERAND_TYPE_MEMORY) { + LoadMemoryOperand(RegisterMapper::SCRATCH_REG, src, instruction.operand_width / 8); + codegen.tst(dst_reg, RegisterMapper::SCRATCH_REG); + } else { + return false; + } + + return true; +} + +bool X86_64Translator::TranslateLea(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands) { + const auto& dst = operands[0]; + const auto& src = operands[1]; + + ASSERT_MSG(src.type == ZYDIS_OPERAND_TYPE_MEMORY, "LEA source must be memory"); + + int dst_reg = GetArm64Register(dst); + if (dst_reg == -1) { + return false; + } + + CalculateMemoryAddress(dst_reg, src); + + return true; +} + +void X86_64Translator::UpdateFlagsForArithmetic(int result_reg, int src1_reg, int src2_reg, + bool is_subtract) { + int flags_reg = reg_mapper.MapX86_64ToArm64(X86_64Register::FLAGS); + + codegen.cmp(result_reg, 0); + + codegen.mov(RegisterMapper::SCRATCH_REG, 0); + + codegen.b_eq(codegen.getCurr()); + codegen.mov(RegisterMapper::SCRATCH_REG, 1 << 6); + codegen.b(codegen.getCurr()); +} + +void X86_64Translator::UpdateFlagsForLogical(int result_reg) { + codegen.cmp(result_reg, 0); +} + +void X86_64Translator::UpdateFlagsForShift(int result_reg, int shift_amount) { + codegen.cmp(result_reg, 0); +} + +int X86_64Translator::GetConditionCode(ZydisMnemonic mnemonic) { + switch (mnemonic) { + case ZYDIS_MNEMONIC_JZ: + return 0; + case ZYDIS_MNEMONIC_JNZ: + return 1; + case ZYDIS_MNEMONIC_JL: + return 11; + case ZYDIS_MNEMONIC_JLE: + return 13; + case ZYDIS_MNEMONIC_JNLE: + return 12; + case ZYDIS_MNEMONIC_JNL: + return 10; + case ZYDIS_MNEMONIC_JB: + return 3; + case ZYDIS_MNEMONIC_JBE: + return 9; + case ZYDIS_MNEMONIC_JNBE: + return 8; + case ZYDIS_MNEMONIC_JNB: + return 2; + default: + return -1; + } +} + +} // namespace Core::Jit diff --git a/src/core/jit/x86_64_translator.h b/src/core/jit/x86_64_translator.h new file mode 100644 index 000000000..6f492e042 --- /dev/null +++ b/src/core/jit/x86_64_translator.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "arm64_codegen.h" +#include "common/types.h" +#include "register_mapping.h" + +namespace Core::Jit { + +class X86_64Translator { +public: + explicit X86_64Translator(Arm64CodeGenerator& codegen, RegisterMapper& reg_mapper); + ~X86_64Translator() = default; + + bool TranslateInstruction(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands, VAddr address); + + bool TranslateMov(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateAdd(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateSub(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateMul(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateDiv(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateAnd(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateOr(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateXor(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateNot(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateShl(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateShr(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateSar(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslatePush(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslatePop(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateCall(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands, VAddr address); + bool TranslateRet(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateJmp(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands, VAddr address); + bool TranslateCmp(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateTest(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + bool TranslateLea(const ZydisDecodedInstruction& instruction, + const ZydisDecodedOperand* operands); + + void UpdateFlagsForArithmetic(int result_reg, int src1_reg, int src2_reg, bool is_subtract); + void UpdateFlagsForLogical(int result_reg); + void UpdateFlagsForShift(int result_reg, int shift_amount); + int GetConditionCode(ZydisMnemonic mnemonic); + +private: + int GetArm64Register(const ZydisDecodedOperand& operand); + int GetArm64XmmRegister(const ZydisDecodedOperand& operand); + void LoadMemoryOperand(int dst_reg, const ZydisDecodedOperand& mem_op, size_t size); + void StoreMemoryOperand(int src_reg, const ZydisDecodedOperand& mem_op, size_t size); + void LoadImmediate(int dst_reg, const ZydisDecodedOperand& imm_op); + void CalculateMemoryAddress(int dst_reg, const ZydisDecodedOperand& mem_op); + X86_64Register ZydisToX86_64Register(ZydisRegister reg); + + Arm64CodeGenerator& codegen; + RegisterMapper& reg_mapper; +}; + +} // namespace Core::Jit diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 7ac8791ae..fae0d608b 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -20,6 +20,9 @@ #include "core/memory.h" #include "core/tls.h" #include "ipc/ipc.h" +#ifdef ARCH_ARM64 +#include "core/jit/execution_engine.h" +#endif namespace Core { @@ -51,22 +54,13 @@ static PS4_SYSV_ABI void* RunMainEntry [[noreturn]] (EntryParams* params) { } #elif defined(ARCH_ARM64) static PS4_SYSV_ABI void* RunMainEntry [[noreturn]] (EntryParams* params) { - void* entry = reinterpret_cast(params->entry_addr); - asm volatile("mov x2, sp\n" - "and x2, x2, #0xFFFFFFFFFFFFFFF0\n" - "sub x2, x2, #8\n" - "mov sp, x2\n" - "ldr x0, [%1, #8]\n" - "sub sp, sp, #16\n" - "str x0, [sp]\n" - "ldr x0, [%1]\n" - "str x0, [sp, #8]\n" - "mov x0, %1\n" - "mov x1, %2\n" - "br %0\n" - : - : "r"(entry), "r"(params), "r"(ProgramExitFunc) - : "x0", "x1", "x2", "memory"); + auto* jit = Core::Jit::JitEngine::Instance(); + if (jit) { + jit->Initialize(); + jit->ExecuteBlock(params->entry_addr); + } else { + LOG_CRITICAL(Core_Linker, "JIT engine not available"); + } UNREACHABLE(); } #endif diff --git a/src/core/memory.cpp b/src/core/memory.cpp index b9fd7fd7d..c04d2e0f5 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -6,6 +6,7 @@ #include "common/config.h" #include "common/debug.h" #include "core/file_sys/fs.h" +#include "core/jit/execution_engine.h" #include "core/libraries/kernel/memory.h" #include "core/libraries/kernel/orbis_error.h" #include "core/libraries/kernel/process.h" @@ -849,6 +850,15 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz impl.Protect(addr, size, perms); +#ifdef ARCH_ARM64 + if (True(prot & MemoryProt::CpuWrite) && vma_base.type == VMAType::Code) { + auto* jit = Core::Jit::JitEngine::Instance(); + if (jit) { + jit->InvalidateRange(addr, addr + adjusted_size); + } + } +#endif + return adjusted_size; } diff --git a/src/core/signals.cpp b/src/core/signals.cpp index 4099ac237..6f1761a94 100644 --- a/src/core/signals.cpp +++ b/src/core/signals.cpp @@ -6,6 +6,9 @@ #include "common/decoder.h" #include "common/signal_context.h" #include "core/signals.h" +#ifdef ARCH_ARM64 +#include "core/jit/execution_engine.h" +#endif #ifdef _WIN32 #include @@ -79,6 +82,15 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { case SIGSEGV: case SIGBUS: { const bool is_write = Common::IsWriteError(raw_context); +#ifdef ARCH_ARM64 + auto* jit = Core::Jit::JitEngine::Instance(); + if (jit && jit->IsJitCode(code_address)) { + VAddr ps4_addr = jit->GetPs4AddressForJitCode(code_address); + if (ps4_addr != 0) { + jit->InvalidateBlock(ps4_addr); + } + } +#endif if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) { UNREACHABLE_MSG( "Unhandled access violation in thread '{}' at code address {}: {} address {}", @@ -87,13 +99,20 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { } break; } - case SIGILL: + case SIGILL: { +#ifdef ARCH_ARM64 + auto* jit = Core::Jit::JitEngine::Instance(); + if (jit && jit->IsJitCode(code_address)) { + LOG_ERROR(Core, "Illegal instruction in JIT code at {}", fmt::ptr(code_address)); + } +#endif if (!signals->DispatchIllegalInstruction(raw_context)) { UNREACHABLE_MSG("Unhandled illegal instruction in thread '{}' at code address {}: {}", GetThreadName(), fmt::ptr(code_address), DisassembleInstruction(code_address)); } break; + } case SIGUSR1: { // Sleep thread until signal is received sigset_t sigset; sigemptyset(&sigset); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..9dcf67f74 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +add_executable(jit_tests + test_arm64_codegen.cpp + test_register_mapping.cpp + test_block_manager.cpp + test_execution_engine.cpp + main.cpp +) + +if (ARCHITECTURE STREQUAL "arm64") + target_sources(jit_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/arm64_codegen.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/arm64_codegen.h + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/register_mapping.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/register_mapping.h + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/block_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/block_manager.h + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/x86_64_translator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/x86_64_translator.h + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/simd_translator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/simd_translator.h + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/calling_convention.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/calling_convention.h + ) +endif() + +target_sources(jit_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src/common/assert.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/common/decoder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_logging_stub.cpp +) + +target_link_libraries(jit_tests PRIVATE + GTest::gtest + GTest::gtest_main + GTest::gmock + Zydis::Zydis + fmt::fmt +) + +target_include_directories(jit_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${CMAKE_CURRENT_SOURCE_DIR}/../externals/zydis/include +) + +target_compile_definitions(jit_tests PRIVATE + ARCH_ARM64 +) + +# to make ctest work +add_test(NAME JitTests COMMAND jit_tests) + +set_tests_properties(JitTests PROPERTIES + TIMEOUT 60 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 000000000..216dc15b5 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/test_arm64_codegen.cpp b/tests/test_arm64_codegen.cpp new file mode 100644 index 000000000..cec51731f --- /dev/null +++ b/tests/test_arm64_codegen.cpp @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/jit/arm64_codegen.h" +#include +#include +#include + +using namespace Core::Jit; + +class Arm64CodeGenTest : public ::testing::Test { +protected: + void SetUp() override { test_gen = std::make_unique(); } + + void TearDown() override { test_gen.reset(); } + + std::unique_ptr test_gen; +}; + +TEST_F(Arm64CodeGenTest, Constructor) { + EXPECT_NE(test_gen->getCode(), nullptr); + EXPECT_EQ(test_gen->getSize(), 0); +} + +TEST_F(Arm64CodeGenTest, Reset) { + test_gen->add(0, 1, 2); + size_t size_after_add = test_gen->getSize(); + EXPECT_GT(size_after_add, 0); + + test_gen->reset(); + EXPECT_EQ(test_gen->getSize(), 0); +} + +TEST_F(Arm64CodeGenTest, AddInstruction) { + test_gen->add(0, 1, 2); // X0 = X1 + X2 + EXPECT_GT(test_gen->getSize(), 0); + EXPECT_LE(test_gen->getSize(), 4); // Should be 4 bytes (one instruction) +} + +TEST_F(Arm64CodeGenTest, AddImmediate) { + test_gen->add_imm(0, 1, 42); // X0 = X1 + 42 + EXPECT_GT(test_gen->getSize(), 0); +} + +TEST_F(Arm64CodeGenTest, MovRegister) { + test_gen->mov(0, 1); // X0 = X1 + EXPECT_GT(test_gen->getSize(), 0); +} + +TEST_F(Arm64CodeGenTest, MovImmediate) { + test_gen->mov(0, 0x1234LL); // X0 = 0x1234 + EXPECT_GT(test_gen->getSize(), 0); + // Large immediate may require multiple instructions + EXPECT_LE(test_gen->getSize(), + 16); // Up to 4 instructions for 64-bit immediate +} + +TEST_F(Arm64CodeGenTest, LoadStore) { + test_gen->ldr(0, 1, 0); // X0 = [X1] + test_gen->str(0, 1, 0); // [X1] = X0 + EXPECT_GE(test_gen->getSize(), 8); // At least 2 instructions +} + +TEST_F(Arm64CodeGenTest, Branch) { + void *target = test_gen->getCode(); // Branch to start of code + test_gen->b(target); + EXPECT_GT(test_gen->getSize(), 0); +} + +TEST_F(Arm64CodeGenTest, ConditionalBranch) { + void *target = test_gen->getCode(); // Branch to start of code + test_gen->b(0, target); // Branch if equal + EXPECT_GT(test_gen->getSize(), 0); +} + +TEST_F(Arm64CodeGenTest, Compare) { + test_gen->cmp(0, 1); // Compare X0 and X1 + EXPECT_GT(test_gen->getSize(), 0); +} + +TEST_F(Arm64CodeGenTest, ArithmeticOperations) { + test_gen->add(0, 1, 2); + test_gen->sub(0, 1, 2); + test_gen->mul(0, 1, 2); + test_gen->and_(0, 1, 2); + test_gen->orr(0, 1, 2); + test_gen->eor(0, 1, 2); + EXPECT_GE(test_gen->getSize(), 24); // At least 6 instructions +} + +TEST_F(Arm64CodeGenTest, SIMDOperations) { + test_gen->mov_v(0, 1); // V0 = V1 + test_gen->add_v(0, 1, 2); // V0 = V1 + V2 + test_gen->sub_v(0, 1, 2); // V0 = V1 - V2 + test_gen->mul_v(0, 1, 2); // V0 = V1 * V2 + EXPECT_GE(test_gen->getSize(), 16); // At least 4 instructions +} + +TEST_F(Arm64CodeGenTest, SetSize) { + test_gen->add(0, 1, 2); + size_t original_size = test_gen->getSize(); + EXPECT_GT(original_size, 0); + + // Test setting size to 0 + test_gen->setSize(0); + EXPECT_EQ(test_gen->getSize(), 0); + + // Test setting size back (this should work without throwing) + test_gen->setSize(original_size); + EXPECT_EQ(test_gen->getSize(), original_size); +} diff --git a/tests/test_block_manager.cpp b/tests/test_block_manager.cpp new file mode 100644 index 000000000..ab5dc3f53 --- /dev/null +++ b/tests/test_block_manager.cpp @@ -0,0 +1,180 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/jit/block_manager.h" +#include +#include +#if defined(__APPLE__) && defined(ARCH_ARM64) +#include +#endif + +using namespace Core::Jit; + +class BlockManagerTest : public ::testing::Test { +protected: + void SetUp() override { + // Allocate executable memory for test code blocks +#if defined(__APPLE__) && defined(ARCH_ARM64) + // On macOS ARM64, use the JIT API approach + test_code = mmap(nullptr, 64 * 1024, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(test_code, MAP_FAILED) + << "Failed to allocate executable memory for test"; + pthread_jit_write_protect_np(0); // Disable write protection for writing + // Will make executable later if needed +#else + test_code = mmap(nullptr, 64 * 1024, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(test_code, MAP_FAILED) + << "Failed to allocate executable memory for test"; +#endif + } + + void TearDown() override { + if (test_code != MAP_FAILED) { + munmap(test_code, 64 * 1024); + } + } + + void *test_code = MAP_FAILED; +}; + +TEST_F(BlockManagerTest, Constructor) { + BlockManager manager; + EXPECT_EQ(manager.GetBlockCount(), 0); + EXPECT_EQ(manager.GetTotalCodeSize(), 0); +} + +TEST_F(BlockManagerTest, CreateBlock) { + BlockManager manager; + VAddr ps4_addr = 0x400000; + void *arm64_code = test_code; + size_t code_size = 1024; + size_t instruction_count = 10; + + CodeBlock *block = + manager.CreateBlock(ps4_addr, arm64_code, code_size, instruction_count); + ASSERT_NE(block, nullptr); + EXPECT_EQ(block->ps4_address, ps4_addr); + EXPECT_EQ(block->arm64_code, arm64_code); + EXPECT_EQ(block->code_size, code_size); + EXPECT_EQ(block->instruction_count, instruction_count); + EXPECT_FALSE(block->is_linked); + EXPECT_EQ(manager.GetBlockCount(), 1); + EXPECT_EQ(manager.GetTotalCodeSize(), code_size); +} + +TEST_F(BlockManagerTest, GetBlock) { + BlockManager manager; + VAddr ps4_addr = 0x400000; + void *arm64_code = test_code; + + // Block doesn't exist yet + CodeBlock *block = manager.GetBlock(ps4_addr); + EXPECT_EQ(block, nullptr); + + manager.CreateBlock(ps4_addr, arm64_code, 1024, 10); + + // Now it should exist + block = manager.GetBlock(ps4_addr); + ASSERT_NE(block, nullptr); + EXPECT_EQ(block->ps4_address, ps4_addr); +} + +TEST_F(BlockManagerTest, MultipleBlocks) { + BlockManager manager; + + // Create multiple blocks + for (int i = 0; i < 10; ++i) { + VAddr ps4_addr = 0x400000 + (i * 0x1000); + void *arm64_code = static_cast(test_code) + (i * 1024); + manager.CreateBlock(ps4_addr, arm64_code, 1024, 10); + } + + EXPECT_EQ(manager.GetBlockCount(), 10); + EXPECT_EQ(manager.GetTotalCodeSize(), 10 * 1024); +} + +TEST_F(BlockManagerTest, InvalidateBlock) { + BlockManager manager; + VAddr ps4_addr = 0x400000; + + // Create and verify block exists + manager.CreateBlock(ps4_addr, test_code, 1024, 10); + EXPECT_NE(manager.GetBlock(ps4_addr), nullptr); + + // Invalidate block + manager.InvalidateBlock(ps4_addr); + + // Block should no longer exist + EXPECT_EQ(manager.GetBlock(ps4_addr), nullptr); + EXPECT_EQ(manager.GetBlockCount(), 0); + EXPECT_EQ(manager.GetTotalCodeSize(), 0); +} + +TEST_F(BlockManagerTest, InvalidateRange) { + BlockManager manager; + + // Create blocks at different addresses + manager.CreateBlock(0x400000, test_code, 1024, 10); + manager.CreateBlock(0x401000, static_cast(test_code) + 1024, 1024, + 10); + manager.CreateBlock(0x402000, static_cast(test_code) + 2048, 1024, + 10); + manager.CreateBlock(0x500000, static_cast(test_code) + 3072, 1024, + 10); + + EXPECT_EQ(manager.GetBlockCount(), 4); + + // Invalidate range that covers first 3 blocks + manager.InvalidateRange(0x400000, 0x403000); + + // First 3 blocks should be gone, last one should remain + EXPECT_EQ(manager.GetBlock(0x400000), nullptr); + EXPECT_EQ(manager.GetBlock(0x401000), nullptr); + EXPECT_EQ(manager.GetBlock(0x402000), nullptr); + EXPECT_NE(manager.GetBlock(0x500000), nullptr); + EXPECT_EQ(manager.GetBlockCount(), 1); +} + +TEST_F(BlockManagerTest, AddDependency) { + BlockManager manager; + VAddr block_addr = 0x400000; + VAddr dep_addr = 0x500000; + + CodeBlock *block = manager.CreateBlock(block_addr, test_code, 1024, 10); + manager.AddDependency(block_addr, dep_addr); + + EXPECT_EQ(block->dependencies.size(), 1); + EXPECT_NE(block->dependencies.find(dep_addr), block->dependencies.end()); +} + +TEST_F(BlockManagerTest, MultipleDependencies) { + BlockManager manager; + VAddr block_addr = 0x400000; + + CodeBlock *block = manager.CreateBlock(block_addr, test_code, 1024, 10); + manager.AddDependency(block_addr, 0x500000); + manager.AddDependency(block_addr, 0x600000); + manager.AddDependency(block_addr, 0x700000); + + EXPECT_EQ(block->dependencies.size(), 3); +} + +TEST_F(BlockManagerTest, Clear) { + BlockManager manager; + + // Create multiple blocks + for (int i = 0; i < 5; ++i) { + VAddr ps4_addr = 0x400000 + (i * 0x1000); + void *arm64_code = static_cast(test_code) + (i * 1024); + manager.CreateBlock(ps4_addr, arm64_code, 1024, 10); + } + + EXPECT_EQ(manager.GetBlockCount(), 5); + + manager.Clear(); + + EXPECT_EQ(manager.GetBlockCount(), 0); + EXPECT_EQ(manager.GetTotalCodeSize(), 0); +} diff --git a/tests/test_execution_engine.cpp b/tests/test_execution_engine.cpp new file mode 100644 index 000000000..e69e02905 --- /dev/null +++ b/tests/test_execution_engine.cpp @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/jit/arm64_codegen.h" +#include "core/jit/block_manager.h" +#include "core/jit/register_mapping.h" +#include +#include + +using namespace Core::Jit; + +// NOTE: ExecutionEngine requires MemoryManager and AddressSpace which have +// heavy dependencies. These tests focus on the components that can be tested in +// isolation. Full integration tests would require the complete emulator system +// to be initialized. Let's just skip them for now. + +class ExecutionEngineComponentTest : public ::testing::Test { +protected: + void SetUp() override {} + + void TearDown() override {} +}; + +// Test that the components used by ExecutionEngine can be constructed +TEST_F(ExecutionEngineComponentTest, ComponentConstruction) { + BlockManager block_manager; + RegisterMapper register_mapper; + Arm64CodeGenerator code_generator; + + // All components should construct successfully + EXPECT_EQ(block_manager.GetBlockCount(), 0); + EXPECT_NE(code_generator.getCode(), nullptr); +} + +// Test block invalidation through BlockManager (used by ExecutionEngine) +TEST_F(ExecutionEngineComponentTest, BlockInvalidation) { + BlockManager block_manager; + VAddr test_addr = 0x400000; + + // Invalidate should not crash even if block doesn't exist + EXPECT_NO_THROW(block_manager.InvalidateBlock(test_addr)); +} + +TEST_F(ExecutionEngineComponentTest, BlockInvalidateRange) { + BlockManager block_manager; + + // Invalidate range should not crash + EXPECT_NO_THROW(block_manager.InvalidateRange(0x400000, 0x500000)); +} diff --git a/tests/test_logging_stub.cpp b/tests/test_logging_stub.cpp new file mode 100644 index 000000000..cd16d66bc --- /dev/null +++ b/tests/test_logging_stub.cpp @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/types.h" +#include + +namespace Common::Log { + +void FmtLogMessageImpl(Class log_class, Level log_level, const char *filename, + unsigned int line_num, const char *function, + const char *format, const fmt::format_args &args) { + // Stub implementation - just ignore logs in tests + (void)log_class; + (void)log_level; + (void)filename; + (void)line_num; + (void)function; + (void)format; + (void)args; +} + +void Start() {} +void Stop() {} + +} // namespace Common::Log diff --git a/tests/test_register_mapping.cpp b/tests/test_register_mapping.cpp new file mode 100644 index 000000000..d1f15583c --- /dev/null +++ b/tests/test_register_mapping.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/jit/register_mapping.h" +#include + +using namespace Core::Jit; + +class RegisterMappingTest : public ::testing::Test { +protected: + RegisterMapper mapper; +}; + +TEST_F(RegisterMappingTest, MapGeneralPurposeRegisters) { + // Test mapping of common x86_64 registers + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::RAX), 0); // X0 + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::RCX), 1); // X1 + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::RDX), 2); // X2 + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::RSI), 3); // X3 + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::RDI), + 0); // X0 (same as RAX) + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::R8), 4); // X4 + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::R9), 5); // X5 +} + +TEST_F(RegisterMappingTest, MapStackPointer) { + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::RSP), 31); // SP +} + +TEST_F(RegisterMappingTest, MapFramePointer) { + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::RBP), 29); // FP +} + +TEST_F(RegisterMappingTest, MapCalleeSavedRegisters) { + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::RBX), 19); // X19 + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::R12), 20); // X20 + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::R13), 21); // X21 + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::R14), 22); // X22 + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::R15), 23); // X23 +} + +TEST_F(RegisterMappingTest, MapFlagsRegister) { + EXPECT_EQ(mapper.MapX86_64ToArm64(X86_64Register::FLAGS), 11); // X11 +} + +TEST_F(RegisterMappingTest, MapXMMRegisters) { + // Test mapping of XMM registers to NEON registers (V registers start at 32) + EXPECT_EQ(mapper.MapX86_64XmmToArm64Neon(X86_64Register::XMM0), 32); // V0 + EXPECT_EQ(mapper.MapX86_64XmmToArm64Neon(X86_64Register::XMM1), 33); // V1 + EXPECT_EQ(mapper.MapX86_64XmmToArm64Neon(X86_64Register::XMM2), 34); // V2 + EXPECT_EQ(mapper.MapX86_64XmmToArm64Neon(X86_64Register::XMM3), 35); // V3 + EXPECT_EQ(mapper.MapX86_64XmmToArm64Neon(X86_64Register::XMM4), 36); // V4 + EXPECT_EQ(mapper.MapX86_64XmmToArm64Neon(X86_64Register::XMM5), 37); // V5 + EXPECT_EQ(mapper.MapX86_64XmmToArm64Neon(X86_64Register::XMM6), 38); // V6 + EXPECT_EQ(mapper.MapX86_64XmmToArm64Neon(X86_64Register::XMM7), 39); // V7 +} + +TEST_F(RegisterMappingTest, MapAllXMMRegisters) { + // Test all 16 XMM registers (V registers start at 32) + for (int i = 0; i < 16; ++i) { + X86_64Register xmm_reg = + static_cast(static_cast(X86_64Register::XMM0) + i); + int neon_reg = mapper.MapX86_64XmmToArm64Neon(xmm_reg); + EXPECT_EQ(neon_reg, 32 + i) << "XMM" << i << " should map to V" << i + << " (register number " << (32 + i) << ")"; + } +} + +TEST_F(RegisterMappingTest, InvalidRegister) { + // COUNT is not a valid register + // NOTE: The implementation uses ASSERT_MSG which will crash on invalid input + // This test verifies that valid registers work correctly + // Testing invalid registers would require a different implementation that + // returns error codes For now, we just verify that the last valid register + // works + int result = mapper.MapX86_64ToArm64(X86_64Register::XMM15); + EXPECT_GE(result, 0) << "Last valid register should map correctly"; +} + +TEST_F(RegisterMappingTest, RegisterMappingConsistency) { + // Test that register mappings are consistent + // RAX should always map to the same ARM64 register + int reg1 = mapper.MapX86_64ToArm64(X86_64Register::RAX); + int reg2 = mapper.MapX86_64ToArm64(X86_64Register::RAX); + EXPECT_EQ(reg1, reg2); +}