mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-16 12:09:07 +00:00
HLE bridge
This commit is contained in:
parent
851131e888
commit
611acc7ca4
139
src/core/jit/hle_bridge.cpp
Normal file
139
src/core/jit/hle_bridge.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#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
|
||||
40
src/core/jit/hle_bridge.h
Normal file
40
src/core/jit/hle_bridge.h
Normal file
@ -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
|
||||
@ -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()
|
||||
|
||||
|
||||
83
tests/test_hle_bridge.cpp
Normal file
83
tests/test_hle_bridge.cpp
Normal file
@ -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 <gtest/gtest.h>
|
||||
#include <sys/mman.h>
|
||||
#if defined(__APPLE__) && defined(ARCH_ARM64)
|
||||
#include <pthread.h>
|
||||
#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<Arm64CodeGenerator>(64 * 1024, test_code_buffer);
|
||||
register_mapper = std::make_unique<RegisterMapper>();
|
||||
hle_bridge = std::make_unique<HleBridge>(*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<Arm64CodeGenerator> codegen;
|
||||
std::unique_ptr<RegisterMapper> register_mapper;
|
||||
std::unique_ptr<HleBridge> 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<void *>(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<void *>(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";
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user