CALL/RET translation

This commit is contained in:
AlpinDale 2025-12-09 04:38:54 +04:30
parent 9e8a328235
commit 851131e888
4 changed files with 224 additions and 4 deletions

View File

@ -132,12 +132,18 @@ CodeBlock* ExecutionEngine::TranslateBasicBlock(VAddr start_address, size_t max_
break; break;
} }
// Track branch target before translation // Track branch/call target before translation
if (instruction.mnemonic == ZYDIS_MNEMONIC_JMP && if (instruction.mnemonic == ZYDIS_MNEMONIC_JMP &&
operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
s64 offset = static_cast<s64>(operands[0].imm.value.s); s64 offset = static_cast<s64>(operands[0].imm.value.s);
branch_target = current_address + instruction.length + offset; branch_target = current_address + instruction.length + offset;
branch_patch_location = code_generator->getCurr(); 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); bool translated = translator->TranslateInstruction(instruction, operands, current_address);

View File

@ -559,13 +559,75 @@ bool X86_64Translator::TranslatePop(const ZydisDecodedInstruction& instruction,
bool X86_64Translator::TranslateCall(const ZydisDecodedInstruction& instruction, bool X86_64Translator::TranslateCall(const ZydisDecodedInstruction& instruction,
const ZydisDecodedOperand* operands, VAddr address) { const ZydisDecodedOperand* operands, VAddr address) {
LOG_WARNING(Core, "CALL instruction translation needs execution engine integration"); const auto& target = operands[0];
return false; 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, bool X86_64Translator::TranslateRet(const ZydisDecodedInstruction& instruction,
const ZydisDecodedOperand* operands) { 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; return true;
} }

View File

@ -7,6 +7,7 @@ add_executable(jit_tests
test_block_manager.cpp test_block_manager.cpp
test_execution_engine.cpp test_execution_engine.cpp
test_block_linking.cpp test_block_linking.cpp
test_call_ret.cpp
main.cpp main.cpp
) )

151
tests/test_call_ret.cpp Normal file
View 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";
}