diff --git a/LiveRecomp/live_generator.cpp b/LiveRecomp/live_generator.cpp index 9dad0c5..141e5ad 100644 --- a/LiveRecomp/live_generator.cpp +++ b/LiveRecomp/live_generator.cpp @@ -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(); 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); } diff --git a/LiveRecomp/live_recompiler_test.cpp b/LiveRecomp/live_recompiler_test.cpp index 329d8a4..89b2ff3 100644 --- a/LiveRecomp/live_recompiler_test.cpp +++ b/LiveRecomp/live_recompiler_test.cpp @@ -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{}; diff --git a/include/recompiler/live_recompiler.h b/include/recompiler/live_recompiler.h index 365e37b..bc81601 100644 --- a/include/recompiler/live_recompiler.h +++ b/include/recompiler/live_recompiler.h @@ -27,13 +27,28 @@ namespace N64Recomp { } ~LiveGeneratorOutput(); bool good = false; - std::vector 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 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 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 context; };