mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-16 12:09:07 +00:00
CALL/RET translation
This commit is contained in:
parent
9e8a328235
commit
851131e888
@ -132,12 +132,18 @@ CodeBlock* ExecutionEngine::TranslateBasicBlock(VAddr start_address, size_t max_
|
||||
break;
|
||||
}
|
||||
|
||||
// Track branch target before translation
|
||||
// Track branch/call target before translation
|
||||
if (instruction.mnemonic == ZYDIS_MNEMONIC_JMP &&
|
||||
operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
|
||||
s64 offset = static_cast<s64>(operands[0].imm.value.s);
|
||||
branch_target = current_address + instruction.length + offset;
|
||||
branch_patch_location = code_generator->getCurr();
|
||||
} else if (instruction.mnemonic == ZYDIS_MNEMONIC_CALL &&
|
||||
operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
|
||||
// Track CALL target for potential linking (though CALL typically goes to HLE)
|
||||
s64 offset = static_cast<s64>(operands[0].imm.value.s);
|
||||
branch_target = current_address + instruction.length + offset;
|
||||
branch_patch_location = code_generator->getCurr();
|
||||
}
|
||||
|
||||
bool translated = translator->TranslateInstruction(instruction, operands, current_address);
|
||||
|
||||
@ -559,13 +559,75 @@ bool X86_64Translator::TranslatePop(const ZydisDecodedInstruction& instruction,
|
||||
|
||||
bool X86_64Translator::TranslateCall(const ZydisDecodedInstruction& instruction,
|
||||
const ZydisDecodedOperand* operands, VAddr address) {
|
||||
LOG_WARNING(Core, "CALL instruction translation needs execution engine integration");
|
||||
return false;
|
||||
const auto& target = operands[0];
|
||||
VAddr target_address = 0;
|
||||
VAddr return_address = address + instruction.length;
|
||||
|
||||
// Calculate target address based on operand type
|
||||
if (target.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
|
||||
// Direct relative call: CALL rel32
|
||||
// Target = current_address + instruction.length + offset
|
||||
s64 offset = static_cast<s64>(target.imm.value.s);
|
||||
target_address = address + instruction.length + offset;
|
||||
} else if (target.type == ZYDIS_OPERAND_TYPE_MEMORY) {
|
||||
// Indirect call: CALL [mem]
|
||||
// Load address from memory into scratch register
|
||||
LoadMemoryOperand(RegisterMapper::SCRATCH_REG, target, 8);
|
||||
// Push return address
|
||||
int sp_reg = RegisterMapper::STACK_POINTER;
|
||||
codegen.sub_imm(sp_reg, sp_reg, 8); // Decrement stack by 8 bytes
|
||||
codegen.mov_imm(RegisterMapper::SCRATCH_REG2, return_address);
|
||||
codegen.str(RegisterMapper::SCRATCH_REG2, sp_reg, 0); // Store return address
|
||||
// Call via register
|
||||
codegen.blr(RegisterMapper::SCRATCH_REG);
|
||||
return true;
|
||||
} else if (target.type == ZYDIS_OPERAND_TYPE_REGISTER) {
|
||||
// Indirect call: CALL reg
|
||||
int reg = GetArm64Register(target);
|
||||
if (reg == -1) {
|
||||
LOG_ERROR(Core, "Invalid register for CALL");
|
||||
return false;
|
||||
}
|
||||
// Push return address
|
||||
int sp_reg = RegisterMapper::STACK_POINTER;
|
||||
codegen.sub_imm(sp_reg, sp_reg, 8); // Decrement stack by 8 bytes
|
||||
codegen.mov_imm(RegisterMapper::SCRATCH_REG, return_address);
|
||||
codegen.str(RegisterMapper::SCRATCH_REG, sp_reg, 0); // Store return address
|
||||
// Call via register
|
||||
codegen.blr(reg);
|
||||
return true;
|
||||
} else {
|
||||
LOG_ERROR(Core, "Unsupported CALL operand type");
|
||||
return false;
|
||||
}
|
||||
|
||||
// For direct calls, push return address and branch to target
|
||||
// Push return address onto stack
|
||||
int sp_reg = RegisterMapper::STACK_POINTER;
|
||||
codegen.sub_imm(sp_reg, sp_reg, 8); // Decrement stack by 8 bytes (x86_64 stack grows down)
|
||||
codegen.mov_imm(RegisterMapper::SCRATCH_REG, return_address);
|
||||
codegen.str(RegisterMapper::SCRATCH_REG, sp_reg, 0); // Store return address at [SP]
|
||||
|
||||
// Branch to target (will be linked later if target block is available)
|
||||
void* placeholder_target = reinterpret_cast<void*>(target_address);
|
||||
codegen.bl(placeholder_target); // Use bl (branch with link) for calls
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool X86_64Translator::TranslateRet(const ZydisDecodedInstruction& instruction,
|
||||
const ZydisDecodedOperand* operands) {
|
||||
codegen.ret();
|
||||
// x86_64 RET pops return address from stack and jumps to it
|
||||
int sp_reg = RegisterMapper::STACK_POINTER;
|
||||
int scratch_reg = RegisterMapper::SCRATCH_REG;
|
||||
|
||||
// Load return address from stack
|
||||
codegen.ldr(scratch_reg, sp_reg, 0); // Load return address from [SP]
|
||||
codegen.add_imm(sp_reg, sp_reg, 8); // Increment stack by 8 bytes (pop)
|
||||
|
||||
// Jump to return address
|
||||
codegen.br(scratch_reg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ add_executable(jit_tests
|
||||
test_block_manager.cpp
|
||||
test_execution_engine.cpp
|
||||
test_block_linking.cpp
|
||||
test_call_ret.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
|
||||
151
tests/test_call_ret.cpp
Normal file
151
tests/test_call_ret.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/decoder.h"
|
||||
#include "core/jit/arm64_codegen.h"
|
||||
#include "core/jit/register_mapping.h"
|
||||
#include "core/jit/x86_64_translator.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <sys/mman.h>
|
||||
#if defined(__APPLE__) && defined(ARCH_ARM64)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
using namespace Core::Jit;
|
||||
|
||||
class CallRetTest : 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>();
|
||||
translator = std::make_unique<X86_64Translator>(*codegen, *register_mapper);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
translator.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<X86_64Translator> translator;
|
||||
};
|
||||
|
||||
// Test that RET translation generates ARM64 code
|
||||
TEST_F(CallRetTest, TranslateRet) {
|
||||
// x86_64 RET instruction: C3
|
||||
u8 x86_ret[] = {0xC3};
|
||||
|
||||
ZydisDecodedInstruction instruction;
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||
|
||||
ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(
|
||||
instruction, operands, x86_ret, sizeof(x86_ret));
|
||||
|
||||
if (!ZYAN_SUCCESS(status)) {
|
||||
GTEST_SKIP()
|
||||
<< "Failed to decode RET instruction - Zydis may not be available";
|
||||
}
|
||||
|
||||
// RET translation should succeed
|
||||
bool result = translator->TranslateRet(instruction, operands);
|
||||
EXPECT_TRUE(result) << "RET translation should succeed";
|
||||
EXPECT_GT(codegen->getSize(), 0) << "RET should generate ARM64 code";
|
||||
}
|
||||
|
||||
// Test that CALL translation generates ARM64 code
|
||||
TEST_F(CallRetTest, TranslateDirectCall) {
|
||||
// x86_64 CALL instruction: E8 <offset> (near relative call, 32-bit offset)
|
||||
// E8 00 10 00 00 = CALL +0x1000
|
||||
u8 x86_call[] = {0xE8, 0x00, 0x10, 0x00, 0x00};
|
||||
|
||||
ZydisDecodedInstruction instruction;
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||
|
||||
ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(
|
||||
instruction, operands, x86_call, sizeof(x86_call));
|
||||
|
||||
if (!ZYAN_SUCCESS(status)) {
|
||||
GTEST_SKIP()
|
||||
<< "Failed to decode CALL instruction - Zydis may not be available";
|
||||
}
|
||||
|
||||
// CALL translation should succeed
|
||||
bool result = translator->TranslateCall(instruction, operands, 0x400000);
|
||||
EXPECT_TRUE(result) << "CALL translation should succeed";
|
||||
EXPECT_GT(codegen->getSize(), 0) << "CALL should generate ARM64 code";
|
||||
}
|
||||
|
||||
// Test that CALL pushes return address to stack
|
||||
TEST_F(CallRetTest, CallPushesReturnAddress) {
|
||||
// Simulate a CALL instruction
|
||||
// We need to verify that the stack pointer is decremented and return address
|
||||
// is stored This is a simplified test - full implementation will need
|
||||
// execution engine integration
|
||||
|
||||
// For now, just verify CALL generates code
|
||||
u8 x86_call[] = {0xE8, 0x00, 0x10, 0x00, 0x00};
|
||||
|
||||
ZydisDecodedInstruction instruction;
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||
|
||||
ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(
|
||||
instruction, operands, x86_call, sizeof(x86_call));
|
||||
|
||||
if (!ZYAN_SUCCESS(status)) {
|
||||
GTEST_SKIP() << "Failed to decode CALL instruction";
|
||||
}
|
||||
|
||||
size_t size_before = codegen->getSize();
|
||||
bool result = translator->TranslateCall(instruction, operands, 0x400000);
|
||||
size_t size_after = codegen->getSize();
|
||||
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_GT(size_after, size_before) << "CALL should generate code";
|
||||
// CALL should generate more code than a simple branch (needs stack
|
||||
// manipulation)
|
||||
EXPECT_GE(size_after - size_before, 4)
|
||||
<< "CALL should generate multiple instructions";
|
||||
}
|
||||
|
||||
// Test that RET pops return address from stack
|
||||
TEST_F(CallRetTest, RetPopsReturnAddress) {
|
||||
// RET instruction should pop return address and jump to it
|
||||
u8 x86_ret[] = {0xC3};
|
||||
|
||||
ZydisDecodedInstruction instruction;
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||
|
||||
ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(
|
||||
instruction, operands, x86_ret, sizeof(x86_ret));
|
||||
|
||||
if (!ZYAN_SUCCESS(status)) {
|
||||
GTEST_SKIP() << "Failed to decode RET instruction";
|
||||
}
|
||||
|
||||
size_t size_before = codegen->getSize();
|
||||
bool result = translator->TranslateRet(instruction, operands);
|
||||
size_t size_after = codegen->getSize();
|
||||
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_GT(size_after, size_before) << "RET should generate code";
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user