mirror of
https://github.com/N64Recomp/N64Recomp.git
synced 2025-05-14 08:12:19 +00:00
Implement indirect calls and fix signed branch conditions in live recompiler
This commit is contained in:
parent
898de82da7
commit
dc2a5b6a46
3 changed files with 82 additions and 19 deletions
|
@ -48,7 +48,7 @@ struct N64Recomp::LiveGeneratorContext {
|
||||||
sljit_jump* cur_branch_jump;
|
sljit_jump* cur_branch_jump;
|
||||||
};
|
};
|
||||||
|
|
||||||
N64Recomp::LiveGenerator::LiveGenerator(size_t num_funcs) : compiler(compiler) {
|
N64Recomp::LiveGenerator::LiveGenerator(size_t num_funcs, const LiveGeneratorInputs& inputs) : compiler(compiler), inputs(inputs) {
|
||||||
compiler = sljit_create_compiler(NULL);
|
compiler = sljit_create_compiler(NULL);
|
||||||
context = std::make_unique<LiveGeneratorContext>();
|
context = std::make_unique<LiveGeneratorContext>();
|
||||||
context->func_labels.resize(num_funcs);
|
context->func_labels.resize(num_funcs);
|
||||||
|
@ -102,6 +102,9 @@ N64Recomp::LiveGeneratorOutput::~LiveGeneratorOutput() {
|
||||||
if (code != nullptr) {
|
if (code != nullptr) {
|
||||||
sljit_free_code(code, nullptr);
|
sljit_free_code(code, nullptr);
|
||||||
}
|
}
|
||||||
|
for (const char* literal : string_literals) {
|
||||||
|
delete[] literal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int get_gpr_context_offset(int gpr_index) {
|
constexpr int get_gpr_context_offset(int gpr_index) {
|
||||||
|
@ -710,11 +713,39 @@ void N64Recomp::LiveGenerator::emit_function_end() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void N64Recomp::LiveGenerator::emit_function_call_lookup(uint32_t addr) const {
|
void N64Recomp::LiveGenerator::emit_function_call_lookup(uint32_t addr) const {
|
||||||
assert(false);
|
// Load the address immediate into the first argument.
|
||||||
|
sljit_emit_op1(compiler, SLJIT_MOV32, SLJIT_R0, 0, SLJIT_IMM, int32_t(addr));
|
||||||
|
|
||||||
|
// Call get_function.
|
||||||
|
sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS1(P, 32), SLJIT_IMM, sljit_sw(inputs.get_function));
|
||||||
|
|
||||||
|
// Copy the return value into R2 so that it can be used for icall
|
||||||
|
sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R2, 0, SLJIT_R0, 0);
|
||||||
|
|
||||||
|
// Load rdram and ctx into R0 and R1.
|
||||||
|
sljit_emit_op2(compiler, SLJIT_ADD, SLJIT_R0, 0, Registers::rdram, 0, SLJIT_IMM, 0xFFFFFFFF80000000);
|
||||||
|
sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R1, 0, Registers::ctx, 0);
|
||||||
|
|
||||||
|
// Call the function.
|
||||||
|
sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS2V(P, P), SLJIT_R2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void N64Recomp::LiveGenerator::emit_function_call_by_register(int reg) const {
|
void N64Recomp::LiveGenerator::emit_function_call_by_register(int reg) const {
|
||||||
assert(false);
|
// Load the register's value into the first argument.
|
||||||
|
sljit_emit_op1(compiler, SLJIT_MOV32, SLJIT_R0, 0, SLJIT_MEM1(Registers::ctx), get_gpr_context_offset(reg));
|
||||||
|
|
||||||
|
// Call get_function.
|
||||||
|
sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS1(P, 32), SLJIT_IMM, sljit_sw(inputs.get_function));
|
||||||
|
|
||||||
|
// Copy the return value into R2 so that it can be used for icall
|
||||||
|
sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R2, 0, SLJIT_R0, 0);
|
||||||
|
|
||||||
|
// Load rdram and ctx into R0 and R1.
|
||||||
|
sljit_emit_op2(compiler, SLJIT_ADD, SLJIT_R0, 0, Registers::rdram, 0, SLJIT_IMM, 0xFFFFFFFF80000000);
|
||||||
|
sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R1, 0, Registers::ctx, 0);
|
||||||
|
|
||||||
|
// Call the function.
|
||||||
|
sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS2V(P, P), SLJIT_R2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void N64Recomp::LiveGenerator::emit_function_call_reference_symbol(const Context& context, uint16_t section_index, size_t symbol_index) const {
|
void N64Recomp::LiveGenerator::emit_function_call_reference_symbol(const Context& context, uint16_t section_index, size_t symbol_index) const {
|
||||||
|
@ -767,8 +798,12 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp&
|
||||||
// Make sure there's no pending jump.
|
// Make sure there's no pending jump.
|
||||||
assert(context->cur_branch_jump == nullptr);
|
assert(context->cur_branch_jump == nullptr);
|
||||||
|
|
||||||
|
// Branch conditions do not allow unary ops, except for ToS64 on the first operand to indicate the branch comparison is signed.
|
||||||
|
assert(op.operands.operand_operations[0] == UnaryOpType::None || op.operands.operand_operations[0] == UnaryOpType::ToS64);
|
||||||
|
assert(op.operands.operand_operations[1] == UnaryOpType::None);
|
||||||
|
|
||||||
sljit_s32 condition_type;
|
sljit_s32 condition_type;
|
||||||
bool cmp_signed = op.operands.operand_operations[1] == UnaryOpType::ToS64;
|
bool cmp_signed = op.operands.operand_operations[0] == UnaryOpType::ToS64;
|
||||||
// Comparisons need to be inverted to account for the fact that the generator is expected to generate a code block that only runs if
|
// Comparisons need to be inverted to account for the fact that the generator is expected to generate a code block that only runs if
|
||||||
// the condition is met, meaning the branch should be taken if the condition isn't met.
|
// the condition is met, meaning the branch should be taken if the condition isn't met.
|
||||||
switch (op.comparison) {
|
switch (op.comparison) {
|
||||||
|
@ -780,34 +815,34 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp&
|
||||||
break;
|
break;
|
||||||
case BinaryOpType::GreaterEq:
|
case BinaryOpType::GreaterEq:
|
||||||
if (cmp_signed) {
|
if (cmp_signed) {
|
||||||
condition_type = SLJIT_LESS;
|
condition_type = SLJIT_SIG_LESS;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
condition_type = SLJIT_SIG_LESS;
|
condition_type = SLJIT_LESS;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BinaryOpType::Greater:
|
case BinaryOpType::Greater:
|
||||||
if (cmp_signed) {
|
if (cmp_signed) {
|
||||||
condition_type = SLJIT_LESS_EQUAL;
|
condition_type = SLJIT_SIG_LESS_EQUAL;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
condition_type = SLJIT_SIG_LESS_EQUAL;
|
condition_type = SLJIT_LESS_EQUAL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BinaryOpType::LessEq:
|
case BinaryOpType::LessEq:
|
||||||
if (cmp_signed) {
|
if (cmp_signed) {
|
||||||
condition_type = SLJIT_GREATER;
|
condition_type = SLJIT_SIG_GREATER;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
condition_type = SLJIT_SIG_GREATER;
|
condition_type = SLJIT_GREATER;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BinaryOpType::Less:
|
case BinaryOpType::Less:
|
||||||
if (cmp_signed) {
|
if (cmp_signed) {
|
||||||
condition_type = SLJIT_GREATER_EQUAL;
|
condition_type = SLJIT_SIG_GREATER_EQUAL;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
condition_type = SLJIT_SIG_GREATER_EQUAL;
|
condition_type = SLJIT_GREATER_EQUAL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -821,10 +856,6 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp&
|
||||||
get_operand_values(op.operands.operands[0], ctx, src1, src1w);
|
get_operand_values(op.operands.operands[0], ctx, src1, src1w);
|
||||||
get_operand_values(op.operands.operands[1], ctx, src2, src2w);
|
get_operand_values(op.operands.operands[1], ctx, src2, src2w);
|
||||||
|
|
||||||
// Branch conditions do not allow unary ops, except for ToS64 on the first operand to indicate the branch comparison is signed.
|
|
||||||
assert(op.operands.operand_operations[0] == UnaryOpType::None || op.operands.operand_operations[1] == UnaryOpType::ToS64);
|
|
||||||
assert(op.operands.operand_operations[1] == UnaryOpType::None);
|
|
||||||
|
|
||||||
// Create a compare jump and track it as the pending branch jump.
|
// Create a compare jump and track it as the pending branch jump.
|
||||||
context->cur_branch_jump = sljit_emit_cmp(compiler, condition_type, src1, src1w, src2, src2w);
|
context->cur_branch_jump = sljit_emit_cmp(compiler, condition_type, src1, src1w, src2, src2w);
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,18 @@ struct TestStats {
|
||||||
uint64_t code_size;
|
uint64_t code_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void write1(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
MEM_B(0, ctx->r4) = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
recomp_func_t* test_get_function(int32_t vram) {
|
||||||
|
if (vram == 0x80100000) {
|
||||||
|
return write1;
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
TestStats run_test(const std::filesystem::path& tests_dir, const std::string& test_name) {
|
TestStats run_test(const std::filesystem::path& tests_dir, const std::string& test_name) {
|
||||||
std::filesystem::path input_path = tests_dir / (test_name + "_data.bin");
|
std::filesystem::path input_path = tests_dir / (test_name + "_data.bin");
|
||||||
std::filesystem::path data_dump_path = tests_dir / (test_name + "_data_out.bin");
|
std::filesystem::path data_dump_path = tests_dir / (test_name + "_data_out.bin");
|
||||||
|
@ -185,8 +197,12 @@ TestStats run_test(const std::filesystem::path& tests_dir, const std::string& te
|
||||||
|
|
||||||
auto before_codegen = std::chrono::system_clock::now();
|
auto before_codegen = std::chrono::system_clock::now();
|
||||||
|
|
||||||
|
N64Recomp::LiveGeneratorInputs generator_inputs {
|
||||||
|
.get_function = test_get_function,
|
||||||
|
};
|
||||||
|
|
||||||
// Create the sljit compiler and the generator.
|
// Create the sljit compiler and the generator.
|
||||||
N64Recomp::LiveGenerator generator{ context.functions.size() };
|
N64Recomp::LiveGenerator generator{ context.functions.size(), generator_inputs };
|
||||||
|
|
||||||
for (size_t func_index = 0; func_index < context.functions.size(); func_index++) {
|
for (size_t func_index = 0; func_index < context.functions.size(); func_index++) {
|
||||||
std::ostringstream dummy_ostream{};
|
std::ostringstream dummy_ostream{};
|
||||||
|
|
|
@ -27,13 +27,28 @@ namespace N64Recomp {
|
||||||
}
|
}
|
||||||
~LiveGeneratorOutput();
|
~LiveGeneratorOutput();
|
||||||
bool good = false;
|
bool good = false;
|
||||||
std::vector<recomp_func_t*> functions;
|
// Storage for string literals referenced by recompiled code. These must be manually allocated to prevent
|
||||||
|
// them from moving, as the referenced address is baked into the recompiled code.
|
||||||
|
std::vector<const char*> string_literals;
|
||||||
|
// Recompiled code.
|
||||||
void* code;
|
void* code;
|
||||||
|
// Size of the recompiled code.
|
||||||
size_t code_size;
|
size_t code_size;
|
||||||
|
// Pointers to each individual function within the recompiled code.
|
||||||
|
std::vector<recomp_func_t*> functions;
|
||||||
|
};
|
||||||
|
struct LiveGeneratorInputs {
|
||||||
|
void (*cop0_status_write)(recomp_context* ctx, gpr value);
|
||||||
|
gpr (*cop0_status_read)(recomp_context* ctx);
|
||||||
|
void (*switch_error)(const char* func, uint32_t vram, uint32_t jtbl);
|
||||||
|
void (*do_break)(uint32_t vram);
|
||||||
|
recomp_func_t* (*get_function)(int32_t vram);
|
||||||
|
void (*syscall_handler)(uint8_t* rdram, recomp_context* ctx, int32_t instruction_vram);
|
||||||
|
void (*pause_self)(uint8_t* rdram);
|
||||||
};
|
};
|
||||||
class LiveGenerator final : public Generator {
|
class LiveGenerator final : public Generator {
|
||||||
public:
|
public:
|
||||||
LiveGenerator(size_t num_funcs);
|
LiveGenerator(size_t num_funcs, const LiveGeneratorInputs& inputs);
|
||||||
~LiveGenerator();
|
~LiveGenerator();
|
||||||
LiveGeneratorOutput finish();
|
LiveGeneratorOutput finish();
|
||||||
void process_binary_op(const BinaryOp& op, const InstructionContext& ctx) const final;
|
void process_binary_op(const BinaryOp& op, const InstructionContext& ctx) const final;
|
||||||
|
@ -72,6 +87,7 @@ namespace N64Recomp {
|
||||||
void get_binary_expr_string(BinaryOpType type, const BinaryOperands& operands, const InstructionContext& ctx, const std::string& output, std::string& expr_string) const;
|
void get_binary_expr_string(BinaryOpType type, const BinaryOperands& operands, const InstructionContext& ctx, const std::string& output, std::string& expr_string) const;
|
||||||
void get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const;
|
void get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const;
|
||||||
sljit_compiler* compiler;
|
sljit_compiler* compiler;
|
||||||
|
LiveGeneratorInputs inputs;
|
||||||
mutable std::unique_ptr<LiveGeneratorContext> context;
|
mutable std::unique_ptr<LiveGeneratorContext> context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue