HLE bridge

This commit is contained in:
AlpinDale 2025-12-09 05:15:16 +04:30
parent 851131e888
commit 611acc7ca4
4 changed files with 265 additions and 0 deletions

139
src/core/jit/hle_bridge.cpp Normal file
View 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
View 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

View File

@ -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
View 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";
}