From a8b247ed03ea09dfb81c43e4c67fb1c5b29be169 Mon Sep 17 00:00:00 2001 From: Thar0 <17233964+Thar0@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:17:15 +0100 Subject: [PATCH] Implement trap instructions as conditional breaks --- LiveRecomp/live_generator.cpp | 95 ++++++++++++++-------------- include/recompiler/generator.h | 2 + include/recompiler/live_recompiler.h | 1 + include/recompiler/operations.h | 8 +++ src/cgenerator.cpp | 8 +++ src/operations.cpp | 15 +++++ src/recompilation.cpp | 9 +++ 7 files changed, 92 insertions(+), 46 deletions(-) diff --git a/LiveRecomp/live_generator.cpp b/LiveRecomp/live_generator.cpp index 39ee42f..b08d60c 100644 --- a/LiveRecomp/live_generator.cpp +++ b/LiveRecomp/live_generator.cpp @@ -1435,6 +1435,27 @@ void N64Recomp::LiveGenerator::emit_jtbl_addend_declaration(const JumpTable& jtb // Nothing to do here, the live recompiler performs a subtraction to get the switch's case. } +static sljit_s32 get_condition_type(bool cmp_signed, N64Recomp::BinaryOpType comparison) { + // 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 (comparison) { + case N64Recomp::BinaryOpType::Equal: + return SLJIT_NOT_EQUAL; + case N64Recomp::BinaryOpType::NotEqual: + return SLJIT_EQUAL; + case N64Recomp::BinaryOpType::GreaterEq: + return cmp_signed ? SLJIT_SIG_LESS : SLJIT_LESS; + case N64Recomp::BinaryOpType::Greater: + return cmp_signed ? SLJIT_SIG_LESS_EQUAL : SLJIT_LESS_EQUAL; + case N64Recomp::BinaryOpType::LessEq: + return cmp_signed ? SLJIT_SIG_GREATER : SLJIT_GREATER; + case N64Recomp::BinaryOpType::Less: + return cmp_signed ? SLJIT_SIG_GREATER_EQUAL : SLJIT_GREATER_EQUAL; + default: + return -1; + } +} + void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp& op, const InstructionContext& ctx) const { // Make sure there's no pending jump. if(context->cur_branch_jump != nullptr) { @@ -1456,53 +1477,12 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp& return; } - sljit_s32 condition_type; 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) { - case BinaryOpType::Equal: - condition_type = SLJIT_NOT_EQUAL; - break; - case BinaryOpType::NotEqual: - condition_type = SLJIT_EQUAL; - break; - case BinaryOpType::GreaterEq: - if (cmp_signed) { - condition_type = SLJIT_SIG_LESS; - } - else { - condition_type = SLJIT_LESS; - } - break; - case BinaryOpType::Greater: - if (cmp_signed) { - condition_type = SLJIT_SIG_LESS_EQUAL; - } - else { - condition_type = SLJIT_LESS_EQUAL; - } - break; - case BinaryOpType::LessEq: - if (cmp_signed) { - condition_type = SLJIT_SIG_GREATER; - } - else { - condition_type = SLJIT_GREATER; - } - break; - case BinaryOpType::Less: - if (cmp_signed) { - condition_type = SLJIT_SIG_GREATER_EQUAL; - } - else { - condition_type = SLJIT_GREATER_EQUAL; - } - break; - default: - assert(false && "Invalid branch condition comparison operation!"); - errored = true; - return; + sljit_s32 condition_type = get_condition_type(cmp_signed, op.comparison); + if (condition_type < 0) { + assert(false && "Invalid branch condition comparison operation!"); + errored = true; + return; } sljit_sw src1; sljit_sw src1w; @@ -1876,6 +1856,29 @@ void N64Recomp::LiveGenerator::emit_do_break(uint32_t instr_vram) const { sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS1V(32), SLJIT_IMM, sljit_sw(inputs.do_break)); } +void N64Recomp::LiveGenerator::emit_trap(const TrapOp& op, const InstructionContext& ctx, uint32_t instr_vram) const { + bool cmp_signed = op.operands.operand_operations[0] == UnaryOpType::ToS64; + sljit_s32 condition_type = get_condition_type(cmp_signed, op.comparison); + if (condition_type < 0) { + assert(false && "Invalid trap condition!"); + errored = true; + return; + } + sljit_sw src1; + sljit_sw src1w; + sljit_sw src2; + sljit_sw src2w; + get_operand_values(op.operands.operands[0], ctx, src1, src1w); + get_operand_values(op.operands.operands[1], ctx, src2, src2w); + + // Emit a comparison evaluating the trap condition + struct sljit_jump *ignore_trap = sljit_emit_cmp(compiler, condition_type, src1, src1w, src2, src2w); + // Emit code to take the exception path + emit_do_break(instr_vram); + // Step over the exception path + sljit_set_label(ignore_trap, sljit_emit_label(compiler)); +} + void N64Recomp::LiveGenerator::emit_pause_self() const { // Load rdram into R0. sljit_emit_op2(compiler, SLJIT_ADD, SLJIT_R0, 0, Registers::rdram, 0, SLJIT_IMM, rdram_offset); diff --git a/include/recompiler/generator.h b/include/recompiler/generator.h index 145bcd0..95befe9 100644 --- a/include/recompiler/generator.h +++ b/include/recompiler/generator.h @@ -58,6 +58,7 @@ namespace N64Recomp { virtual void emit_muldiv(InstrId instr_id, int reg1, int reg2) const = 0; virtual void emit_syscall(uint32_t instr_vram) const = 0; virtual void emit_do_break(uint32_t instr_vram) const = 0; + virtual void emit_trap(const TrapOp& op, const InstructionContext& ctx, uint32_t instr_vram) const = 0; virtual void emit_pause_self() const = 0; virtual void emit_trigger_event(uint32_t event_index) const = 0; virtual void emit_comment(const std::string& comment) const = 0; @@ -95,6 +96,7 @@ namespace N64Recomp { void emit_muldiv(InstrId instr_id, int reg1, int reg2) const final; void emit_syscall(uint32_t instr_vram) const final; void emit_do_break(uint32_t instr_vram) const final; + void emit_trap(const TrapOp& op, const InstructionContext& ctx, uint32_t instr_vram) const final; void emit_pause_self() const final; void emit_trigger_event(uint32_t event_index) const final; void emit_comment(const std::string& comment) const final; diff --git a/include/recompiler/live_recompiler.h b/include/recompiler/live_recompiler.h index d155b83..b4a8bcb 100644 --- a/include/recompiler/live_recompiler.h +++ b/include/recompiler/live_recompiler.h @@ -127,6 +127,7 @@ namespace N64Recomp { void emit_muldiv(InstrId instr_id, int reg1, int reg2) const final; void emit_syscall(uint32_t instr_vram) const final; void emit_do_break(uint32_t instr_vram) const final; + void emit_trap(const TrapOp& op, const InstructionContext& ctx, uint32_t instr_vram) const final; void emit_pause_self() const final; void emit_trigger_event(uint32_t event_index) const final; void emit_comment(const std::string& comment) const final; diff --git a/include/recompiler/operations.h b/include/recompiler/operations.h index 65f2ed7..c2111ad 100644 --- a/include/recompiler/operations.h +++ b/include/recompiler/operations.h @@ -204,10 +204,18 @@ namespace N64Recomp { bool likely; }; + struct TrapOp { + // The type of binary operation to use for this compare + BinaryOpType comparison; + // The input operands. + BinaryOperands operands; + }; + extern const std::unordered_map unary_ops; extern const std::unordered_map binary_ops; extern const std::unordered_map conditional_branch_ops; extern const std::unordered_map store_ops; + extern const std::unordered_map trap_ops; } #endif diff --git a/src/cgenerator.cpp b/src/cgenerator.cpp index 94f85c5..a02a6e3 100644 --- a/src/cgenerator.cpp +++ b/src/cgenerator.cpp @@ -550,6 +550,14 @@ void N64Recomp::CGenerator::emit_do_break(uint32_t instr_vram) const { fmt::print(output_file, "do_break({});\n", instr_vram); } +void N64Recomp::CGenerator::emit_trap(const TrapOp& op, const InstructionContext& ctx, uint32_t instr_vram) const { + // Thread local variables to prevent allocations when possible. + // TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations. + thread_local std::string expr_string{}; + get_binary_expr_string(op.comparison, op.operands, ctx, "", expr_string); + fmt::print(output_file, "if ({}) {{\n do_break({});\n}}\n", expr_string, instr_vram); +} + void N64Recomp::CGenerator::emit_pause_self() const { fmt::print(output_file, "pause_self(rdram);\n"); } diff --git a/src/operations.cpp b/src/operations.cpp index 70201d3..84cdf96 100644 --- a/src/operations.cpp +++ b/src/operations.cpp @@ -177,4 +177,19 @@ namespace N64Recomp { { InstrId::cpu_sdc1, { StoreOpType::SDC1, Operand::FtU64 }}, { InstrId::cpu_swc1, { StoreOpType::SWC1, Operand::FtU32L }}, }; + + const std::unordered_map trap_ops { + { InstrId::cpu_tge, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::ToS64 }, { Operand::Rs, Operand::Rt }}}}, + { InstrId::cpu_tgeu, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToU64, UnaryOpType::ToU64 }, { Operand::Rs, Operand::Rt }}}}, + { InstrId::cpu_tlt, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::ToS64 }, { Operand::Rs, Operand::Rt }}}}, + { InstrId::cpu_tltu, { BinaryOpType::Less, {{ UnaryOpType::ToU64, UnaryOpType::ToU64 }, { Operand::Rs, Operand::Rt }}}}, + { InstrId::cpu_teq, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}}}, + { InstrId::cpu_tne, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}}}, + { InstrId::cpu_tgei, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::ToS64 }, { Operand::Rs, Operand::ImmS16 }}}}, + { InstrId::cpu_tgeiu, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToU64, UnaryOpType::ToU64 }, { Operand::Rs, Operand::ImmS16 }}}}, + { InstrId::cpu_tlti, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::ToS64 }, { Operand::Rs, Operand::ImmS16 }}}}, + { InstrId::cpu_tltiu, { BinaryOpType::Less, {{ UnaryOpType::ToU64, UnaryOpType::ToU64 }, { Operand::Rs, Operand::ImmS16 }}}}, + { InstrId::cpu_teqi, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}}}, + { InstrId::cpu_tnei, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}}}, + }; } \ No newline at end of file diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 5cfe8e5..9004725 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -738,6 +738,15 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con handled = true; } + auto find_trap_it = trap_ops.find(instr_id); + if (find_trap_it != trap_ops.end()) { + print_indent(); + const TrapOp& op = find_trap_it->second; + + generator.emit_trap(op, instruction_context, instr_vram); + handled = true; + } + if (!handled) { fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName()); return false;