diff --git a/src/core/jit/execution_engine.cpp b/src/core/jit/execution_engine.cpp index 35d22908d..ed7c986e2 100644 --- a/src/core/jit/execution_engine.cpp +++ b/src/core/jit/execution_engine.cpp @@ -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(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(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); diff --git a/src/core/jit/x86_64_translator.cpp b/src/core/jit/x86_64_translator.cpp index e322ce3f3..84fe8f7ec 100644 --- a/src/core/jit/x86_64_translator.cpp +++ b/src/core/jit/x86_64_translator.cpp @@ -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(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(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; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 48537e79d..b2bc0cb0d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 ) diff --git a/tests/test_call_ret.cpp b/tests/test_call_ret.cpp new file mode 100644 index 000000000..c892bfd3a --- /dev/null +++ b/tests/test_call_ret.cpp @@ -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 +#include +#if defined(__APPLE__) && defined(ARCH_ARM64) +#include +#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(64 * 1024, test_code_buffer); + register_mapper = std::make_unique(); + translator = std::make_unique(*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 codegen; + std::unique_ptr register_mapper; + std::unique_ptr 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 (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"; +}