diff --git a/src/core/jit/hle_bridge.cpp b/src/core/jit/hle_bridge.cpp new file mode 100644 index 000000000..21d179769 --- /dev/null +++ b/src/core/jit/hle_bridge.cpp @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/logging/log.h" +#include "hle_bridge.h" + +namespace Core::Jit { + +HleBridge::HleBridge(Arm64CodeGenerator& codegen, RegisterMapper& reg_mapper) + : codegen(codegen), reg_mapper(reg_mapper), calling_conv(codegen, reg_mapper) {} + +void HleBridge::GenerateBridge(void* hle_func, int int_arg_count, int float_arg_count) { + // Save caller-saved registers (x86_64: RAX, RCX, RDX, RSI, RDI, R8-R11) + // These correspond to ARM64: X0-X7, X9-X15 (some are callee-saved, but we save all to be safe) + SaveCallerSavedRegisters(); + + // Map x86_64 arguments to ARM64 calling convention + // x86_64 System V ABI: RDI, RSI, RDX, RCX, R8, R9 (integer), XMM0-XMM7 (float) + // ARM64: X0-X7 (integer), V0-V7 (float) + MapArguments(int_arg_count, float_arg_count); + + // Call the HLE function + calling_conv.CallFunction(hle_func); + + // Map return value from ARM64 X0 to x86_64 RAX + MapReturnValue(); + + // Restore caller-saved registers + RestoreCallerSavedRegisters(); +} + +void HleBridge::SaveCallerSavedRegisters() { + // x86_64 caller-saved registers: RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11 + // Map to ARM64 and save them + // Note: We need to be careful about which registers are actually caller-saved in ARM64 + // ARM64 caller-saved: X0-X7, X9-X15, V0-V7, V16-V31 + // We'll save the x86_64 registers that map to ARM64 caller-saved registers + + // Save integer registers that are caller-saved + // RAX -> X0, RCX -> X1, RDX -> X2, RSI -> X3, RDI -> X0 (reused), R8 -> X4, R9 -> X5 + // We'll save X0-X7 to be safe (they're all caller-saved in ARM64) + for (int i = 0; i < 8; ++i) { + codegen.push(i); // Save X0-X7 + } + + // Save XMM registers (V0-V7 in ARM64) + // x86_64 XMM0-XMM7 map to ARM64 V0-V7 + for (int i = 0; i < 8; ++i) { + codegen.sub_imm(31, 31, 16); // Decrement stack pointer by 16 bytes + codegen.str_v(i, 31, 0); // Store V0-V7 + } +} + +void HleBridge::RestoreCallerSavedRegisters() { + // Restore XMM registers first (reverse order) + for (int i = 7; i >= 0; --i) { + codegen.ldr_v(i, 31, 0); // Load V0-V7 + codegen.add_imm(31, 31, 16); // Increment stack pointer by 16 bytes + } + + // Restore integer registers (reverse order) + for (int i = 7; i >= 0; --i) { + codegen.pop(i); // Restore X0-X7 + } +} + +void HleBridge::MapArguments(int int_arg_count, int float_arg_count) { + // x86_64 System V ABI argument registers: + // Integer: RDI (arg1), RSI (arg2), RDX (arg3), RCX (arg4), R8 (arg5), R9 (arg6) + // Float: XMM0 (arg1), XMM1 (arg2), XMM2 (arg3), XMM3 (arg4), XMM4 (arg5), XMM5 (arg6), XMM6 + // (arg7), XMM7 (arg8) + + // ARM64 calling convention: + // Integer: X0 (arg1), X1 (arg2), X2 (arg3), X3 (arg4), X4 (arg5), X5 (arg6), X6 (arg7), X7 + // (arg8) Float: V0 (arg1), V1 (arg2), V2 (arg3), V3 (arg4), V4 (arg5), V5 (arg6), V6 (arg7), V7 + // (arg8) + + // Map integer arguments + static constexpr X86_64Register x86_int_args[] = { + X86_64Register::RDI, // arg1 + X86_64Register::RSI, // arg2 + X86_64Register::RDX, // arg3 + X86_64Register::RCX, // arg4 + X86_64Register::R8, // arg5 + X86_64Register::R9, // arg6 + }; + + for (int i = 0; i < int_arg_count && i < 6; ++i) { + int x86_reg = reg_mapper.MapX86_64ToArm64(x86_int_args[i]); + int arm64_arg_reg = i; // X0, X1, X2, etc. + if (x86_reg != arm64_arg_reg) { + codegen.mov(arm64_arg_reg, x86_reg); + } + } + + // Map floating point arguments + static constexpr X86_64Register x86_float_args[] = { + X86_64Register::XMM0, // arg1 + X86_64Register::XMM1, // arg2 + X86_64Register::XMM2, // arg3 + X86_64Register::XMM3, // arg4 + X86_64Register::XMM4, // arg5 + X86_64Register::XMM5, // arg6 + X86_64Register::XMM6, // arg7 + X86_64Register::XMM7, // arg8 + }; + + for (int i = 0; i < float_arg_count && i < 8; ++i) { + int x86_xmm_reg = reg_mapper.MapX86_64XmmToArm64Neon(x86_float_args[i]); + int arm64_arg_reg = i; // V0, V1, V2, etc. + if (x86_xmm_reg != arm64_arg_reg) { + codegen.mov_v(arm64_arg_reg, x86_xmm_reg); + } + } +} + +void HleBridge::MapReturnValue() { + // Return value: ARM64 X0 -> x86_64 RAX + int arm64_return = 0; // X0 + int x86_return = reg_mapper.MapX86_64ToArm64(X86_64Register::RAX); + if (x86_return != arm64_return) { + codegen.mov(x86_return, arm64_return); + } +} + +bool HleBridge::IsHleAddress(VAddr address) { + // TODO: Implement HLE address lookup + (void)address; + return false; +} + +void* HleBridge::GetHleFunction(VAddr address) { + // TODO: Implement HLE function lookup + (void)address; + return nullptr; +} + +} // namespace Core::Jit diff --git a/src/core/jit/hle_bridge.h b/src/core/jit/hle_bridge.h new file mode 100644 index 000000000..3866cad8c --- /dev/null +++ b/src/core/jit/hle_bridge.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "arm64_codegen.h" +#include "calling_convention.h" +#include "register_mapping.h" + +namespace Core::Jit { + +class HleBridge { +public: + explicit HleBridge(Arm64CodeGenerator& codegen, RegisterMapper& reg_mapper); + ~HleBridge() = default; + + // Generate bridge code to call an HLE function + // hle_func: Pointer to the HLE function + // int_arg_count: Number of integer arguments (0-6 for x86_64 System V ABI) + // float_arg_count: Number of floating point arguments (0-8 for x86_64 System V ABI) + void GenerateBridge(void* hle_func, int int_arg_count = 0, int float_arg_count = 0); + + // Check if an address is an HLE function + static bool IsHleAddress(VAddr address); + + // Get HLE function pointer for an address + static void* GetHleFunction(VAddr address); + +private: + void SaveCallerSavedRegisters(); + void RestoreCallerSavedRegisters(); + void MapArguments(int int_arg_count, int float_arg_count); + void MapReturnValue(); + + Arm64CodeGenerator& codegen; + RegisterMapper& reg_mapper; + CallingConvention calling_conv; +}; + +} // namespace Core::Jit diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b2bc0cb0d..3ca86fe8a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(jit_tests test_execution_engine.cpp test_block_linking.cpp test_call_ret.cpp + test_hle_bridge.cpp main.cpp ) @@ -25,6 +26,8 @@ if (ARCHITECTURE STREQUAL "arm64") ${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 + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/hle_bridge.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/core/jit/hle_bridge.h ) endif() diff --git a/tests/test_hle_bridge.cpp b/tests/test_hle_bridge.cpp new file mode 100644 index 000000000..e3134af47 --- /dev/null +++ b/tests/test_hle_bridge.cpp @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/jit/arm64_codegen.h" +#include "core/jit/hle_bridge.h" +#include "core/jit/register_mapping.h" +#include +#include +#if defined(__APPLE__) && defined(ARCH_ARM64) +#include +#endif + +using namespace Core::Jit; + +// Simple test HLE function +extern "C" PS4_SYSV_ABI u64 TestHleFunction(u64 arg1, u64 arg2) { + return arg1 + arg2; +} + +class HleBridgeTest : public ::testing::Test { +protected: + void SetUp() override { + // Allocate executable memory for test code +#if defined(__APPLE__) && defined(ARCH_ARM64) + test_code_buffer = mmap(nullptr, 64 * 1024, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(test_code_buffer, MAP_FAILED) + << "Failed to allocate executable memory for test"; + pthread_jit_write_protect_np(0); +#else + test_code_buffer = + mmap(nullptr, 64 * 1024, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(test_code_buffer, MAP_FAILED) + << "Failed to allocate executable memory for test"; +#endif + codegen = std::make_unique(64 * 1024, test_code_buffer); + register_mapper = std::make_unique(); + hle_bridge = std::make_unique(*codegen, *register_mapper); + } + + void TearDown() override { + hle_bridge.reset(); + register_mapper.reset(); + codegen.reset(); + if (test_code_buffer != MAP_FAILED) { + munmap(test_code_buffer, 64 * 1024); + } + } + + void *test_code_buffer = MAP_FAILED; + std::unique_ptr codegen; + std::unique_ptr register_mapper; + std::unique_ptr hle_bridge; +}; + +// Test that HLE bridge can be constructed +TEST_F(HleBridgeTest, Construction) { EXPECT_NE(hle_bridge, nullptr); } + +// Test that we can generate a bridge to an HLE function +TEST_F(HleBridgeTest, GenerateBridge) { + void *hle_func = reinterpret_cast(TestHleFunction); + + // Generate bridge code + hle_bridge->GenerateBridge(hle_func, 2); // 2 integer arguments + + // Should generate some code + EXPECT_GT(codegen->getSize(), 0) << "HLE bridge should generate code"; +} + +// Test that bridge preserves caller-saved registers +TEST_F(HleBridgeTest, BridgePreservesRegisters) { + // This is a placeholder test - full register preservation testing + // would require execution, which is complex + void *hle_func = reinterpret_cast(TestHleFunction); + + size_t size_before = codegen->getSize(); + hle_bridge->GenerateBridge(hle_func, 2); + size_t size_after = codegen->getSize(); + + // Bridge should generate substantial code for register preservation + EXPECT_GT(size_after - size_before, 8) << "Bridge should preserve registers"; +}