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;
|
||||
};
|
||||
|
||||
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);
|
||||
context = std::make_unique<LiveGeneratorContext>();
|
||||
context->func_labels.resize(num_funcs);
|
||||
|
@ -102,6 +102,9 @@ N64Recomp::LiveGeneratorOutput::~LiveGeneratorOutput() {
|
|||
if (code != nullptr) {
|
||||
sljit_free_code(code, nullptr);
|
||||
}
|
||||
for (const char* literal : string_literals) {
|
||||
delete[] literal;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
|
@ -767,8 +798,12 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp&
|
|||
// Make sure there's no pending jump.
|
||||
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;
|
||||
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
|
||||
// the condition is met, meaning the branch should be taken if the condition isn't met.
|
||||
switch (op.comparison) {
|
||||
|
@ -780,34 +815,34 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp&
|
|||
break;
|
||||
case BinaryOpType::GreaterEq:
|
||||
if (cmp_signed) {
|
||||
condition_type = SLJIT_LESS;
|
||||
condition_type = SLJIT_SIG_LESS;
|
||||
}
|
||||
else {
|
||||
condition_type = SLJIT_SIG_LESS;
|
||||
condition_type = SLJIT_LESS;
|
||||
}
|
||||
break;
|
||||
case BinaryOpType::Greater:
|
||||
if (cmp_signed) {
|
||||
condition_type = SLJIT_LESS_EQUAL;
|
||||
condition_type = SLJIT_SIG_LESS_EQUAL;
|
||||
}
|
||||
else {
|
||||
condition_type = SLJIT_SIG_LESS_EQUAL;
|
||||
condition_type = SLJIT_LESS_EQUAL;
|
||||
}
|
||||
break;
|
||||
case BinaryOpType::LessEq:
|
||||
if (cmp_signed) {
|
||||
condition_type = SLJIT_GREATER;
|
||||
condition_type = SLJIT_SIG_GREATER;
|
||||
}
|
||||
else {
|
||||
condition_type = SLJIT_SIG_GREATER;
|
||||
condition_type = SLJIT_GREATER;
|
||||
}
|
||||
break;
|
||||
case BinaryOpType::Less:
|
||||
if (cmp_signed) {
|
||||
condition_type = SLJIT_GREATER_EQUAL;
|
||||
condition_type = SLJIT_SIG_GREATER_EQUAL;
|
||||
}
|
||||
else {
|
||||
condition_type = SLJIT_SIG_GREATER_EQUAL;
|
||||
condition_type = SLJIT_GREATER_EQUAL;
|
||||
}
|
||||
break;
|
||||
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[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.
|
||||
context->cur_branch_jump = sljit_emit_cmp(compiler, condition_type, src1, src1w, src2, src2w);
|
||||
}
|
||||
|
|
|
@ -65,6 +65,18 @@ struct TestStats {
|
|||
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) {
|
||||
std::filesystem::path input_path = tests_dir / (test_name + "_data.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();
|
||||
|
||||
N64Recomp::LiveGeneratorInputs generator_inputs {
|
||||
.get_function = test_get_function,
|
||||
};
|
||||
|
||||
// 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++) {
|
||||
std::ostringstream dummy_ostream{};
|
||||
|
|
|
@ -27,13 +27,28 @@ namespace N64Recomp {
|
|||
}
|
||||
~LiveGeneratorOutput();
|
||||
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;
|
||||
// Size of the recompiled code.
|
||||
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 {
|
||||
public:
|
||||
LiveGenerator(size_t num_funcs);
|
||||
LiveGenerator(size_t num_funcs, const LiveGeneratorInputs& inputs);
|
||||
~LiveGenerator();
|
||||
LiveGeneratorOutput finish();
|
||||
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_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const;
|
||||
sljit_compiler* compiler;
|
||||
LiveGeneratorInputs inputs;
|
||||
mutable std::unique_ptr<LiveGeneratorContext> context;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue