From f09031e84e2fcb64e93a470beeddd10b91282915 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 5 Jan 2025 14:51:53 -0500 Subject: [PATCH] Add support for function hooks in live generator --- LiveRecomp/live_generator.cpp | 25 ++++++++++++++++++++++++- include/recompiler/generator.h | 4 ++-- include/recompiler/live_recompiler.h | 7 ++++++- src/cgenerator.cpp | 7 +++---- src/recompilation.cpp | 16 ++++++++-------- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/LiveRecomp/live_generator.cpp b/LiveRecomp/live_generator.cpp index cb733f1..45a992a 100644 --- a/LiveRecomp/live_generator.cpp +++ b/LiveRecomp/live_generator.cpp @@ -1259,6 +1259,17 @@ void N64Recomp::LiveGenerator::emit_function_start(const std::string& function_n // sljit_emit_op0(compiler, SLJIT_BREAKPOINT); sljit_emit_enter(compiler, 0, SLJIT_ARGS2V(P, P), 4 | SLJIT_ENTER_FLOAT(1), 5 | SLJIT_ENTER_FLOAT(0), 0); sljit_emit_op2(compiler, SLJIT_SUB, Registers::rdram, 0, Registers::rdram, 0, SLJIT_IMM, rdram_offset); + + // Check if this function's entry is hooked and emit the hook call if so. + auto find_hook_it = inputs.entry_func_hooks.find(func_index); + if (find_hook_it != inputs.entry_func_hooks.end()) { + // Load rdram and ctx into R0 and R1. + sljit_emit_op2(compiler, SLJIT_ADD, SLJIT_R0, 0, Registers::rdram, 0, SLJIT_IMM, rdram_offset); + sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R1, 0, Registers::ctx, 0); + // Load the hook's index into R2. + sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R2, 0, SLJIT_IMM, find_hook_it->second); + sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS3V(P, P, W), SLJIT_IMM, sljit_sw(inputs.run_hook)); + } } void N64Recomp::LiveGenerator::emit_function_end() const { @@ -1590,7 +1601,19 @@ void N64Recomp::LiveGenerator::emit_switch_close() const { // Nothing to do here, the jump table is built in emit_switch. } -void N64Recomp::LiveGenerator::emit_return(const Context& context) const { +void N64Recomp::LiveGenerator::emit_return(const Context& context, size_t func_index) const { + (void)context; + + // Check if this function's return is hooked and emit the hook call if so. + auto find_hook_it = inputs.return_func_hooks.find(func_index); + if (find_hook_it != inputs.return_func_hooks.end()) { + // Load rdram and ctx into R0 and R1. + sljit_emit_op2(compiler, SLJIT_ADD, SLJIT_R0, 0, Registers::rdram, 0, SLJIT_IMM, rdram_offset); + sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R1, 0, Registers::ctx, 0); + // Load the return hook's index into R2. + sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R2, 0, SLJIT_IMM, find_hook_it->second); + sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS3V(P, P, W), SLJIT_IMM, sljit_sw(inputs.run_hook)); + } sljit_emit_return_void(compiler); } diff --git a/include/recompiler/generator.h b/include/recompiler/generator.h index 4d36f6c..145bcd0 100644 --- a/include/recompiler/generator.h +++ b/include/recompiler/generator.h @@ -48,7 +48,7 @@ namespace N64Recomp { virtual void emit_case(int case_index, const std::string& target_label) const = 0; virtual void emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const = 0; virtual void emit_switch_close() const = 0; - virtual void emit_return(const Context& context) const = 0; + virtual void emit_return(const Context& context, size_t func_index) const = 0; virtual void emit_check_fr(int fpr) const = 0; virtual void emit_check_nan(int fpr, bool is_double) const = 0; virtual void emit_cop0_status_read(int reg) const = 0; @@ -85,7 +85,7 @@ namespace N64Recomp { void emit_case(int case_index, const std::string& target_label) const final; void emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const final; void emit_switch_close() const final; - void emit_return(const Context& context) const final; + void emit_return(const Context& context, size_t func_index) const final; void emit_check_fr(int fpr) const final; void emit_check_nan(int fpr, bool is_double) const final; void emit_cop0_status_read(int reg) const final; diff --git a/include/recompiler/live_recompiler.h b/include/recompiler/live_recompiler.h index cc7670f..6f763ca 100644 --- a/include/recompiler/live_recompiler.h +++ b/include/recompiler/live_recompiler.h @@ -78,6 +78,11 @@ namespace N64Recomp { void (*trigger_event)(uint8_t* rdram, recomp_context* ctx, uint32_t event_index); int32_t *reference_section_addresses; int32_t *local_section_addresses; + void (*run_hook)(uint8_t* rdram, recomp_context* ctx, size_t hook_table_index); + // Maps function index in recompiler context to function's entry hook slot. + std::unordered_map entry_func_hooks; + // Maps function index in recompiler context to function's return hook slot. + std::unordered_map return_func_hooks; }; class LiveGenerator final : public Generator { public: @@ -109,7 +114,7 @@ namespace N64Recomp { void emit_case(int case_index, const std::string& target_label) const final; void emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const final; void emit_switch_close() const final; - void emit_return(const Context& context) const final; + void emit_return(const Context& context, size_t func_index) const final; void emit_check_fr(int fpr) const final; void emit_check_nan(int fpr, bool is_double) const final; void emit_cop0_status_read(int reg) const final; diff --git a/src/cgenerator.cpp b/src/cgenerator.cpp index 1ac63a8..b237856 100644 --- a/src/cgenerator.cpp +++ b/src/cgenerator.cpp @@ -350,7 +350,6 @@ void N64Recomp::CGenerator::get_binary_expr_string(BinaryOpType type, const Bina thread_local std::string input_b{}; thread_local std::string func_string{}; thread_local std::string infix_string{}; - bool is_infix; get_operand_string(operands.operands[0], operands.operand_operations[0], ctx, input_a); get_operand_string(operands.operands[1], operands.operand_operations[1], ctx, input_b); get_notation(type, func_string, infix_string); @@ -393,6 +392,7 @@ void N64Recomp::CGenerator::get_binary_expr_string(BinaryOpType type, const Bina } void N64Recomp::CGenerator::emit_function_start(const std::string& function_name, size_t func_index) const { + (void)func_index; fmt::print(output_file, "RECOMP_FUNC void {}(uint8_t* rdram, recomp_context* ctx) {{\n" // these variables shouldn't need to be preserved across function boundaries, so make them local for more efficient output @@ -476,7 +476,8 @@ void N64Recomp::CGenerator::emit_switch_error(uint32_t instr_vram, uint32_t jtbl fmt::print(output_file, "default: switch_error(__func__, 0x{:08X}, 0x{:08X});\n", instr_vram, jtbl_vram); } -void N64Recomp::CGenerator::emit_return(const Context& context) const { +void N64Recomp::CGenerator::emit_return(const Context& context, size_t func_index) const { + (void)func_index; if (context.trace_mode) { fmt::print(output_file, "TRACE_RETURN()\n "); } @@ -575,7 +576,6 @@ void N64Recomp::CGenerator::process_unary_op(const UnaryOp& op, const Instructio // TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations. thread_local std::string output{}; thread_local std::string input{}; - bool is_infix; get_operand_string(op.output, UnaryOpType::None, ctx, output); get_operand_string(op.input, op.operation, ctx, input); fmt::print(output_file, "{} = {};\n", output, input); @@ -587,7 +587,6 @@ void N64Recomp::CGenerator::process_store_op(const StoreOp& op, const Instructio thread_local std::string base_str{}; thread_local std::string imm_str{}; thread_local std::string value_input{}; - bool is_infix; get_operand_string(Operand::Base, UnaryOpType::None, ctx, base_str); get_operand_string(Operand::ImmS16, UnaryOpType::None, ctx, imm_str); get_operand_string(op.value_input, UnaryOpType::None, ctx, value_input); diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 5761b79..0253d17 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -109,7 +109,7 @@ std::string_view ctx_gpr_prefix(int reg) { } template -bool process_instruction(GeneratorType& generator, const N64Recomp::Context& context, const N64Recomp::Function& func, const N64Recomp::FunctionStats& stats, const std::unordered_set& jtbl_lw_instructions, size_t instr_index, const std::vector& instructions, std::ostream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, bool tag_reference_relocs, std::span> static_funcs_out) { +bool process_instruction(GeneratorType& generator, const N64Recomp::Context& context, const N64Recomp::Function& func, size_t func_index, const N64Recomp::FunctionStats& stats, const std::unordered_set& jtbl_lw_instructions, size_t instr_index, const std::vector& instructions, std::ostream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, bool tag_reference_relocs, std::span> static_funcs_out) { using namespace N64Recomp; const auto& section = context.sections[func.section_index]; @@ -219,7 +219,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - if (!process_instruction(generator, context, func, stats, jtbl_lw_instructions, instr_index + 1, instructions, output_file, use_indent, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { + if (!process_instruction(generator, context, func, func_index, stats, jtbl_lw_instructions, instr_index + 1, instructions, output_file, use_indent, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { return false; } } @@ -238,7 +238,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); print_link_branch(); return true; }; @@ -363,7 +363,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); // TODO check if this branch close should exist. // print_indent(); // generator.emit_branch_close(); @@ -512,7 +512,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); } else { fmt::print(stderr, "Unhandled branch in {} at 0x{:08X} to 0x{:08X}\n", func.name, instr_vram, branch_target); @@ -552,7 +552,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con fmt::print("[Info] Indirect tail call in {}\n", func.name); print_func_call_by_register(rs); print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); break; } break; @@ -561,7 +561,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con generator.emit_syscall(instr_vram); // syscalls don't link, so treat it like a tail call print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); break; case InstrId::cpu_break: print_indent(); @@ -840,7 +840,7 @@ bool recompile_function_impl(GeneratorType& generator, const N64Recomp::Context& } // Process the current instruction and check for errors - if (process_instruction(generator, context, func, stats, jtbl_lw_instructions, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, tag_reference_relocs, static_funcs_out) == false) { + if (process_instruction(generator, context, func, func_index, stats, jtbl_lw_instructions, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, tag_reference_relocs, static_funcs_out) == false) { fmt::print(stderr, "Error in recompiling {}, clearing output file\n", func.name); output_file.clear(); return false;