From c54855b05d2e72057bb1d4b70aabf8eced36d8de Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 29 Sep 2024 17:17:05 -0400 Subject: [PATCH] Removed output file from generator signatures, implement some of the luajit generator --- include/generator.h | 189 +++++++-------- src/cgenerator.cpp | 76 +++--- src/luajitgenerator.cpp | 497 ++++++++++++++++++++++++++++++++++++---- src/recompilation.cpp | 128 +++++------ 4 files changed, 656 insertions(+), 234 deletions(-) diff --git a/include/generator.h b/include/generator.h index 49973bd..626361a 100644 --- a/include/generator.h +++ b/include/generator.h @@ -27,111 +27,116 @@ namespace N64Recomp { class Generator { public: - virtual void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const = 0; - virtual void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const = 0; - virtual void process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const = 0; - virtual void emit_function_start(std::ostream& output_file, const std::string& function_name) const = 0; - virtual void emit_function_end(std::ostream& output_file) const = 0; - virtual void emit_function_call_lookup(std::ostream& output_file, uint32_t addr) const = 0; - virtual void emit_function_call_by_register(std::ostream& output_file, int reg) const = 0; - virtual void emit_function_call_by_name(std::ostream& output_file, const std::string& func_name) const = 0; - virtual void emit_goto(std::ostream& output_file, const std::string& target) const = 0; - virtual void emit_label(std::ostream& output_file, const std::string& label_name) const = 0; - virtual void emit_variable_declaration(std::ostream& output_file, const std::string& var_name, int reg) const = 0; - virtual void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const = 0; - virtual void emit_branch_close(std::ostream& output_file) const = 0; - virtual void emit_switch(std::ostream& output_file, const std::string& jump_variable, int shift_amount) const = 0; - virtual void emit_case(std::ostream& output_file, int case_index, const std::string& target_label) const = 0; - virtual void emit_switch_error(std::ostream& output_file, uint32_t instr_vram, uint32_t jtbl_vram) const = 0; - virtual void emit_switch_close(std::ostream& output_file) const = 0; - virtual void emit_return(std::ostream& output_file) const = 0; - virtual void emit_check_fr(std::ostream& output_file, int fpr) const = 0; - virtual void emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const = 0; - virtual void emit_cop0_status_read(std::ostream& output_file, int reg) const = 0; - virtual void emit_cop0_status_write(std::ostream& output_file, int reg) const = 0; - virtual void emit_cop1_cs_read(std::ostream& output_file, int reg) const = 0; - virtual void emit_cop1_cs_write(std::ostream& output_file, int reg) const = 0; - virtual void emit_muldiv(std::ostream& output_file, InstrId instr_id, int reg1, int reg2) const = 0; - virtual void emit_syscall(std::ostream& output_file, uint32_t instr_vram) const = 0; - virtual void emit_do_break(std::ostream& output_file, uint32_t instr_vram) const = 0; - virtual void emit_pause_self(std::ostream& output_file) const = 0; - virtual void emit_trigger_event(std::ostream& output_file, size_t event_index) const = 0; - virtual void emit_comment(std::ostream& output_file, const std::string& comment) const = 0; + virtual void process_binary_op(const BinaryOp& op, const InstructionContext& ctx) const = 0; + virtual void process_unary_op(const UnaryOp& op, const InstructionContext& ctx) const = 0; + virtual void process_store_op(const StoreOp& op, const InstructionContext& ctx) const = 0; + virtual void emit_function_start(const std::string& function_name) const = 0; + virtual void emit_function_end() const = 0; + virtual void emit_function_call_lookup(uint32_t addr) const = 0; + virtual void emit_function_call_by_register(int reg) const = 0; + virtual void emit_function_call_by_name(const std::string& func_name) const = 0; + virtual void emit_goto(const std::string& target) const = 0; + virtual void emit_label(const std::string& label_name) const = 0; + virtual void emit_variable_declaration(const std::string& var_name, int reg) const = 0; + virtual void emit_branch_condition(const ConditionalBranchOp& op, const InstructionContext& ctx) const = 0; + virtual void emit_branch_close() const = 0; + virtual void emit_switch(const std::string& jump_variable, int shift_amount) const = 0; + 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 = 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; + virtual void emit_cop0_status_write(int reg) const = 0; + virtual void emit_cop1_cs_read(int reg) const = 0; + virtual void emit_cop1_cs_write(int reg) const = 0; + 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_pause_self() const = 0; + virtual void emit_trigger_event(size_t event_index) const = 0; + virtual void emit_comment(const std::string& comment) const = 0; }; class CGenerator final : Generator { public: - CGenerator() = default; - void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const final; - void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const final; - void process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const final; - void emit_function_start(std::ostream& output_file, const std::string& function_name) const final; - void emit_function_end(std::ostream& output_file) const final; - void emit_function_call_lookup(std::ostream& output_file, uint32_t addr) const final; - void emit_function_call_by_register(std::ostream& output_file, int reg) const final; - void emit_function_call_by_name(std::ostream& output_file, const std::string& func_name) const final; - void emit_goto(std::ostream& output_file, const std::string& target) const final; - void emit_label(std::ostream& output_file, const std::string& label_name) const final; - void emit_variable_declaration(std::ostream& output_file, const std::string& var_name, int reg) const final; - void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const final; - void emit_branch_close(std::ostream& output_file) const final; - void emit_switch(std::ostream& output_file, const std::string& jump_variable, int shift_amount) const final; - void emit_case(std::ostream& output_file, int case_index, const std::string& target_label) const final; - void emit_switch_error(std::ostream& output_file, uint32_t instr_vram, uint32_t jtbl_vram) const final; - void emit_switch_close(std::ostream& output_file) const final; - void emit_return(std::ostream& output_file) const final; - void emit_check_fr(std::ostream& output_file, int fpr) const final; - void emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const final; - void emit_cop0_status_read(std::ostream& output_file, int reg) const final; - void emit_cop0_status_write(std::ostream& output_file, int reg) const final; - void emit_cop1_cs_read(std::ostream& output_file, int reg) const final; - void emit_cop1_cs_write(std::ostream& output_file, int reg) const final; - void emit_muldiv(std::ostream& output_file, InstrId instr_id, int reg1, int reg2) const final; - void emit_syscall(std::ostream& output_file, uint32_t instr_vram) const final; - void emit_do_break(std::ostream& output_file, uint32_t instr_vram) const final; - void emit_pause_self(std::ostream& output_file) const final; - void emit_trigger_event(std::ostream& output_file, size_t event_index) const final; - void emit_comment(std::ostream& output_file, const std::string& comment) const final; + CGenerator(std::ostream& output_file) : output_file(output_file) {}; + void process_binary_op(const BinaryOp& op, const InstructionContext& ctx) const final; + void process_unary_op(const UnaryOp& op, const InstructionContext& ctx) const final; + void process_store_op(const StoreOp& op, const InstructionContext& ctx) const final; + void emit_function_start(const std::string& function_name) const final; + void emit_function_end() const final; + void emit_function_call_lookup(uint32_t addr) const final; + void emit_function_call_by_register(int reg) const final; + void emit_function_call_by_name(const std::string& func_name) const final; + void emit_goto(const std::string& target) const final; + void emit_label(const std::string& label_name) const final; + void emit_variable_declaration(const std::string& var_name, int reg) const final; + void emit_branch_condition(const ConditionalBranchOp& op, const InstructionContext& ctx) const final; + void emit_branch_close() const final; + void emit_switch(const std::string& jump_variable, int shift_amount) const final; + 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 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; + void emit_cop0_status_write(int reg) const final; + void emit_cop1_cs_read(int reg) const final; + void emit_cop1_cs_write(int reg) const final; + 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_pause_self() const final; + void emit_trigger_event(size_t event_index) const final; + void emit_comment(const std::string& comment) const final; private: void get_operand_string(Operand operand, UnaryOpType operation, const InstructionContext& context, std::string& operand_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; + std::ostream& output_file; }; class LuajitGenerator final : Generator { public: - LuajitGenerator() = default; - void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const final; - void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const final; - void process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const final; - void emit_function_start(std::ostream& output_file, const std::string& function_name) const final; - void emit_function_end(std::ostream& output_file) const final; - void emit_function_call_lookup(std::ostream& output_file, uint32_t addr) const final; - void emit_function_call_by_register(std::ostream& output_file, int reg) const final; - void emit_function_call_by_name(std::ostream& output_file, const std::string& func_name) const final; - void emit_goto(std::ostream& output_file, const std::string& target) const final; - void emit_label(std::ostream& output_file, const std::string& label_name) const final; - void emit_variable_declaration(std::ostream& output_file, const std::string& var_name, int reg) const final; - void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const final; - void emit_branch_close(std::ostream& output_file) const final; - void emit_switch(std::ostream& output_file, const std::string& jump_variable, int shift_amount) const final; - void emit_case(std::ostream& output_file, int case_index, const std::string& target_label) const final; - void emit_switch_error(std::ostream& output_file, uint32_t instr_vram, uint32_t jtbl_vram) const final; - void emit_switch_close(std::ostream& output_file) const final; - void emit_return(std::ostream& output_file) const final; - void emit_check_fr(std::ostream& output_file, int fpr) const final; - void emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const final; - void emit_cop0_status_read(std::ostream& output_file, int reg) const final; - void emit_cop0_status_write(std::ostream& output_file, int reg) const final; - void emit_cop1_cs_read(std::ostream& output_file, int reg) const final; - void emit_cop1_cs_write(std::ostream& output_file, int reg) const final; - void emit_muldiv(std::ostream& output_file, InstrId instr_id, int reg1, int reg2) const final; - void emit_syscall(std::ostream& output_file, uint32_t instr_vram) const final; - void emit_do_break(std::ostream& output_file, uint32_t instr_vram) const final; - void emit_pause_self(std::ostream& output_file) const final; - void emit_trigger_event(std::ostream& output_file, size_t event_index) const final; - void emit_comment(std::ostream& output_file, const std::string& comment) const final; + LuajitGenerator(std::ostream& output_file) : output_file(output_file) {}; + void process_binary_op(const BinaryOp& op, const InstructionContext& ctx) const final; + void process_unary_op(const UnaryOp& op, const InstructionContext& ctx) const final; + void process_store_op(const StoreOp& op, const InstructionContext& ctx) const final; + void emit_function_start(const std::string& function_name) const final; + void emit_function_end() const final; + void emit_function_call_lookup(uint32_t addr) const final; + void emit_function_call_by_register(int reg) const final; + void emit_function_call_by_name(const std::string& func_name) const final; + void emit_goto(const std::string& target) const final; + void emit_label(const std::string& label_name) const final; + void emit_variable_declaration(const std::string& var_name, int reg) const final; + void emit_branch_condition(const ConditionalBranchOp& op, const InstructionContext& ctx) const final; + void emit_branch_close() const final; + void emit_switch(const std::string& jump_variable, int shift_amount) const final; + 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 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; + void emit_cop0_status_write(int reg) const final; + void emit_cop1_cs_read(int reg) const final; + void emit_cop1_cs_write(int reg) const final; + 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_pause_self() const final; + void emit_trigger_event(size_t event_index) const final; + void emit_comment(const std::string& comment) const final; private: + void get_operand_string(Operand operand, UnaryOpType operation, const InstructionContext& context, std::string& operand_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; + std::ostream& output_file; }; } diff --git a/src/cgenerator.cpp b/src/cgenerator.cpp index 191d306..f919049 100644 --- a/src/cgenerator.cpp +++ b/src/cgenerator.cpp @@ -8,7 +8,7 @@ struct BinaryOpFields { std::string func_string; std::string infix_string; }; -std::vector c_op_fields = []() { +static std::vector c_op_fields = []() { std::vector ret{}; ret.resize(static_cast(N64Recomp::BinaryOpType::COUNT)); std::vector ops_setup{}; @@ -72,22 +72,22 @@ std::vector c_op_fields = []() { return ret; }(); -std::string gpr_to_string(int gpr_index) { +static std::string gpr_to_string(int gpr_index) { if (gpr_index == 0) { return "0"; } return fmt::format("ctx->r{}", gpr_index); } -std::string fpr_to_string(int fpr_index) { +static std::string fpr_to_string(int fpr_index) { return fmt::format("ctx->f{}.fl", fpr_index); } -std::string fpr_double_to_string(int fpr_index) { +static std::string fpr_double_to_string(int fpr_index) { return fmt::format("ctx->f{}.d", fpr_index); } -std::string fpr_u32l_to_string(int fpr_index) { +static std::string fpr_u32l_to_string(int fpr_index) { if (fpr_index & 1) { return fmt::format("ctx->f_odd[({} - 1) * 2]", fpr_index); } @@ -96,11 +96,11 @@ std::string fpr_u32l_to_string(int fpr_index) { } } -std::string fpr_u64_to_string(int fpr_index) { +static std::string fpr_u64_to_string(int fpr_index) { return fmt::format("ctx->f{}.u64", fpr_index); } -std::string unsigned_reloc(const N64Recomp::InstructionContext& context) { +static std::string unsigned_reloc(const N64Recomp::InstructionContext& context) { switch (context.reloc_type) { case N64Recomp::RelocType::R_MIPS_HI16: return fmt::format("{}RELOC_HI16({}, {:#X})", @@ -113,7 +113,7 @@ std::string unsigned_reloc(const N64Recomp::InstructionContext& context) { } } -std::string signed_reloc(const N64Recomp::InstructionContext& context) { +static std::string signed_reloc(const N64Recomp::InstructionContext& context) { return "(int16_t)" + unsigned_reloc(context); } @@ -365,7 +365,7 @@ void N64Recomp::CGenerator::get_binary_expr_string(BinaryOpType type, const Bina } } -void N64Recomp::CGenerator::emit_function_start(std::ostream& output_file, const std::string& function_name) const { +void N64Recomp::CGenerator::emit_function_start(const std::string& function_name) const { 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 @@ -375,37 +375,37 @@ void N64Recomp::CGenerator::emit_function_start(std::ostream& output_file, const function_name); } -void N64Recomp::CGenerator::emit_function_end(std::ostream& output_file) const { +void N64Recomp::CGenerator::emit_function_end() const { fmt::print(output_file, ";}}\n"); } -void N64Recomp::CGenerator::emit_function_call_lookup(std::ostream& output_file, uint32_t addr) const { +void N64Recomp::CGenerator::emit_function_call_lookup(uint32_t addr) const { fmt::print(output_file, "LOOKUP_FUNC(0x{:08X})(rdram, ctx);\n", addr); } -void N64Recomp::CGenerator::emit_function_call_by_register(std::ostream& output_file, int reg) const { +void N64Recomp::CGenerator::emit_function_call_by_register(int reg) const { fmt::print(output_file, "LOOKUP_FUNC({})(rdram, ctx);\n", gpr_to_string(reg)); } -void N64Recomp::CGenerator::emit_function_call_by_name(std::ostream& output_file, const std::string& func_name) const { +void N64Recomp::CGenerator::emit_function_call_by_name(const std::string& func_name) const { fmt::print(output_file, "{}(rdram, ctx);\n", func_name); } -void N64Recomp::CGenerator::emit_goto(std::ostream& output_file, const std::string& target) const { +void N64Recomp::CGenerator::emit_goto(const std::string& target) const { fmt::print(output_file, " goto {};\n", target); } -void N64Recomp::CGenerator::emit_label(std::ostream& output_file, const std::string& label_name) const { +void N64Recomp::CGenerator::emit_label(const std::string& label_name) const { fmt::print(output_file, "{}:\n", label_name); } -void N64Recomp::CGenerator::emit_variable_declaration(std::ostream& output_file, const std::string& var_name, int reg) const { +void N64Recomp::CGenerator::emit_variable_declaration(const std::string& var_name, int reg) const { fmt::print(output_file, "gpr {} = {};\n", var_name, gpr_to_string(reg)); } -void N64Recomp::CGenerator::emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const { +void N64Recomp::CGenerator::emit_branch_condition(const ConditionalBranchOp& op, const InstructionContext& ctx) 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{}; @@ -413,55 +413,55 @@ void N64Recomp::CGenerator::emit_branch_condition(std::ostream& output_file, con fmt::print(output_file, "if ({}) {{\n", expr_string); } -void N64Recomp::CGenerator::emit_branch_close(std::ostream& output_file) const { +void N64Recomp::CGenerator::emit_branch_close() const { fmt::print(output_file, "}}\n"); } -void N64Recomp::CGenerator::emit_switch_close(std::ostream& output_file) const { +void N64Recomp::CGenerator::emit_switch_close() const { fmt::print(output_file, "}}\n"); } -void N64Recomp::CGenerator::emit_switch(std::ostream& output_file, const std::string& jump_variable, int shift_amount) const { +void N64Recomp::CGenerator::emit_switch(const std::string& jump_variable, int shift_amount) const { fmt::print(output_file, "switch ({} >> {}) {{\n", jump_variable, shift_amount); } -void N64Recomp::CGenerator::emit_case(std::ostream& output_file, int case_index, const std::string& target_label) const { +void N64Recomp::CGenerator::emit_case(int case_index, const std::string& target_label) const { fmt::print(output_file, "case {}: goto {}; break;\n", case_index, target_label); } -void N64Recomp::CGenerator::emit_switch_error(std::ostream& output_file, uint32_t instr_vram, uint32_t jtbl_vram) const { +void N64Recomp::CGenerator::emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const { fmt::print(output_file, "default: switch_error(__func__, 0x{:08X}, 0x{:08X});\n", instr_vram, jtbl_vram); } -void N64Recomp::CGenerator::emit_return(std::ostream& output_file) const { +void N64Recomp::CGenerator::emit_return() const { fmt::print(output_file, "return;\n"); } -void N64Recomp::CGenerator::emit_check_fr(std::ostream& output_file, int fpr) const { +void N64Recomp::CGenerator::emit_check_fr(int fpr) const { fmt::print(output_file, "CHECK_FR(ctx, {});\n ", fpr); } -void N64Recomp::CGenerator::emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const { +void N64Recomp::CGenerator::emit_check_nan(int fpr, bool is_double) const { fmt::print(output_file, "NAN_CHECK(ctx->f{}.{}); ", fpr, is_double ? "d" : "fl"); } -void N64Recomp::CGenerator::emit_cop0_status_read(std::ostream& output_file, int reg) const { +void N64Recomp::CGenerator::emit_cop0_status_read(int reg) const { fmt::print(output_file, "{} = cop0_status_read(ctx);\n", gpr_to_string(reg)); } -void N64Recomp::CGenerator::emit_cop0_status_write(std::ostream& output_file, int reg) const { +void N64Recomp::CGenerator::emit_cop0_status_write(int reg) const { fmt::print(output_file, "cop0_status_write(ctx, {})", gpr_to_string(reg)); } -void N64Recomp::CGenerator::emit_cop1_cs_read(std::ostream& output_file, int reg) const { +void N64Recomp::CGenerator::emit_cop1_cs_read(int reg) const { fmt::print(output_file, "{} = rounding_mode;\n", gpr_to_string(reg)); } -void N64Recomp::CGenerator::emit_cop1_cs_write(std::ostream& output_file, int reg) const { +void N64Recomp::CGenerator::emit_cop1_cs_write(int reg) const { fmt::print(output_file, "rounding_mode = ({}) & 0x3;\n", gpr_to_string(reg)); } -void N64Recomp::CGenerator::emit_muldiv(std::ostream& output_file, InstrId instr_id, int reg1, int reg2) const { +void N64Recomp::CGenerator::emit_muldiv(InstrId instr_id, int reg1, int reg2) const { switch (instr_id) { case InstrId::cpu_mult: fmt::print(output_file, "result = S64(S32({})) * S64(S32({})); lo = S32(result >> 0); hi = S32(result >> 32);\n", gpr_to_string(reg1), gpr_to_string(reg2)); @@ -491,27 +491,27 @@ void N64Recomp::CGenerator::emit_muldiv(std::ostream& output_file, InstrId instr } } -void N64Recomp::CGenerator::emit_syscall(std::ostream& output_file, uint32_t instr_vram) const { +void N64Recomp::CGenerator::emit_syscall(uint32_t instr_vram) const { fmt::print(output_file, "recomp_syscall_handler(rdram, ctx, 0x{:08X});\n", instr_vram); } -void N64Recomp::CGenerator::emit_do_break(std::ostream& output_file, uint32_t instr_vram) const { +void N64Recomp::CGenerator::emit_do_break(uint32_t instr_vram) const { fmt::print(output_file, "do_break({});\n", instr_vram); } -void N64Recomp::CGenerator::emit_pause_self(std::ostream& output_file) const { +void N64Recomp::CGenerator::emit_pause_self() const { fmt::print(output_file, "pause_self(rdram);\n"); } -void N64Recomp::CGenerator::emit_trigger_event(std::ostream& output_file, size_t event_index) const { +void N64Recomp::CGenerator::emit_trigger_event(size_t event_index) const { fmt::print(output_file, "recomp_trigger_event(rdram, ctx, base_event_index + {});\n", event_index); } -void N64Recomp::CGenerator::emit_comment(std::ostream& output_file, const std::string& comment) const { +void N64Recomp::CGenerator::emit_comment(const std::string& comment) const { fmt::print(output_file, "// {}\n", comment); } -void N64Recomp::CGenerator::process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const { +void N64Recomp::CGenerator::process_binary_op(const BinaryOp& op, const InstructionContext& ctx) 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 output{}; @@ -521,7 +521,7 @@ void N64Recomp::CGenerator::process_binary_op(std::ostream& output_file, const B fmt::print(output_file, "{} = {};\n", output, expression); } -void N64Recomp::CGenerator::process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const { +void N64Recomp::CGenerator::process_unary_op(const UnaryOp& op, const InstructionContext& ctx) 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 output{}; @@ -532,7 +532,7 @@ void N64Recomp::CGenerator::process_unary_op(std::ostream& output_file, const Un fmt::print(output_file, "{} = {};\n", output, input); } -void N64Recomp::CGenerator::process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const { +void N64Recomp::CGenerator::process_store_op(const StoreOp& op, const InstructionContext& ctx) 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 base_str{}; diff --git a/src/luajitgenerator.cpp b/src/luajitgenerator.cpp index 1074064..021eb28 100644 --- a/src/luajitgenerator.cpp +++ b/src/luajitgenerator.cpp @@ -6,139 +6,556 @@ #include "generator.h" -void N64Recomp::LuajitGenerator::process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const { - // TODO - fmt::print(output_file, "\n"); +struct BinaryOpFields { std::string func_string; std::string infix_string; }; + +static std::vector luajit_op_fields = []() { + std::vector ret{}; + ret.resize(static_cast(N64Recomp::BinaryOpType::COUNT)); + std::vector ops_setup{}; + ops_setup.resize(static_cast(N64Recomp::BinaryOpType::COUNT)); + + auto setup_op = [&ret, &ops_setup](N64Recomp::BinaryOpType op_type, const std::string& func_string, const std::string& infix_string) { + size_t index = static_cast(op_type); + // Prevent setting up an operation twice. + assert(ops_setup[index] == false && "Operation already setup!"); + ops_setup[index] = true; + ret[index] = { func_string, infix_string }; + }; + + setup_op(N64Recomp::BinaryOpType::Add32, "ADD32", ""); + setup_op(N64Recomp::BinaryOpType::Sub32, "SUB32", ""); + setup_op(N64Recomp::BinaryOpType::Add64, "", "+"); + setup_op(N64Recomp::BinaryOpType::Sub64, "", "-"); + setup_op(N64Recomp::BinaryOpType::And64, "bit.band", ""); + setup_op(N64Recomp::BinaryOpType::AddFloat, "", "+"); + setup_op(N64Recomp::BinaryOpType::AddDouble, "", "+"); + setup_op(N64Recomp::BinaryOpType::SubFloat, "", "-"); + setup_op(N64Recomp::BinaryOpType::SubDouble, "", "-"); + setup_op(N64Recomp::BinaryOpType::MulFloat, "MUL_S", ""); + setup_op(N64Recomp::BinaryOpType::MulDouble, "MUL_D", ""); + setup_op(N64Recomp::BinaryOpType::DivFloat, "DIV_S", ""); + setup_op(N64Recomp::BinaryOpType::DivDouble, "DIV_D", ""); + setup_op(N64Recomp::BinaryOpType::Or64, "bit.bor", ""); + setup_op(N64Recomp::BinaryOpType::Nor64, "NOR", ""); + setup_op(N64Recomp::BinaryOpType::Xor64, "bit.bxor", ""); + setup_op(N64Recomp::BinaryOpType::Sll32, "SLL32", ""); + setup_op(N64Recomp::BinaryOpType::Sll64, "bit.lshift", ""); + setup_op(N64Recomp::BinaryOpType::Srl32, "SRL32", ""); + setup_op(N64Recomp::BinaryOpType::Srl64, "bit.rshift", ""); + setup_op(N64Recomp::BinaryOpType::Sra32, "SRA32", ""); + setup_op(N64Recomp::BinaryOpType::Sra64, "bit.arshift", ""); + setup_op(N64Recomp::BinaryOpType::Equal, "", "=="); + setup_op(N64Recomp::BinaryOpType::NotEqual, "", "~="); + setup_op(N64Recomp::BinaryOpType::Less, "", "<"); + setup_op(N64Recomp::BinaryOpType::LessEq, "", "<="); + setup_op(N64Recomp::BinaryOpType::Greater, "", ">"); + setup_op(N64Recomp::BinaryOpType::GreaterEq, "", ">="); + setup_op(N64Recomp::BinaryOpType::LD, "MEM_LD", ""); + setup_op(N64Recomp::BinaryOpType::LW, "MEM_LW", ""); + setup_op(N64Recomp::BinaryOpType::LWU, "MEM_LWU", ""); + setup_op(N64Recomp::BinaryOpType::LH, "MEM_LH", ""); + setup_op(N64Recomp::BinaryOpType::LHU, "MEM_LHU", ""); + setup_op(N64Recomp::BinaryOpType::LB, "MEM_LB", ""); + setup_op(N64Recomp::BinaryOpType::LBU, "MEM_LBU", ""); + setup_op(N64Recomp::BinaryOpType::LDL, "MEM_LDL", ""); + setup_op(N64Recomp::BinaryOpType::LDR, "MEM_LDR", ""); + setup_op(N64Recomp::BinaryOpType::LWL, "MEM_LWL", ""); + setup_op(N64Recomp::BinaryOpType::LWR, "MEM_LWR", ""); + setup_op(N64Recomp::BinaryOpType::True, "", ""); + setup_op(N64Recomp::BinaryOpType::False, "", ""); + + // Ensure every operation has been setup. + for (char is_set : ops_setup) { + assert(is_set && "Operation has not been setup!"); + } + + return ret; +}(); + +static std::string gpr_to_string(int gpr_index) { + if (gpr_index == 0) { + return "0ULL"; + } + return fmt::format("ctx.r{}", gpr_index); } -void N64Recomp::LuajitGenerator::process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const { - // TODO - fmt::print(output_file, "\n"); +static std::string fpr_to_string(int fpr_index) { + return fmt::format("ctx.f{}.fl", fpr_index); } -void N64Recomp::LuajitGenerator::process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const { - // TODO - fmt::print(output_file, "\n"); +static std::string fpr_double_to_string(int fpr_index) { + return fmt::format("ctx.f{}.d", fpr_index); } -void N64Recomp::LuajitGenerator::emit_function_start(std::ostream& output_file, const std::string& function_name) const { - fmt::print(output_file, "function {}(rdram, ctx)\n", function_name); +static std::string fpr_u32l_to_string(int fpr_index) { + if (fpr_index & 1) { + return fmt::format("ctx.f_odd[({} - 1) * 2]", fpr_index); + } + else { + return fmt::format("ctx.f{}.u32l", fpr_index); + } } -void N64Recomp::LuajitGenerator::emit_function_end(std::ostream& output_file) const { +static std::string fpr_u64_to_string(int fpr_index) { + return fmt::format("ctx.f{}.u64", fpr_index); +} + +static std::string unsigned_reloc(const N64Recomp::InstructionContext& context) { + switch (context.reloc_type) { + case N64Recomp::RelocType::R_MIPS_HI16: + return fmt::format("{}RELOC_HI16({}, {:#X}ULL)", + context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset); + case N64Recomp::RelocType::R_MIPS_LO16: + return fmt::format("{}RELOC_LO16({}, {:#X}ULL)", + context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset); + default: + throw std::runtime_error(fmt::format("Unexpected reloc type {}\n", static_cast(context.reloc_type))); + } +} + +static std::string signed_reloc(const N64Recomp::InstructionContext& context) { + return "(int16_t)" + unsigned_reloc(context); +} + +void N64Recomp::LuajitGenerator::get_operand_string(Operand operand, UnaryOpType operation, const InstructionContext& context, std::string& operand_string) const { + switch (operand) { + case Operand::Rd: + operand_string = gpr_to_string(context.rd); + break; + case Operand::Rs: + operand_string = gpr_to_string(context.rs); + break; + case Operand::Rt: + operand_string = gpr_to_string(context.rt); + break; + case Operand::Fd: + operand_string = fpr_to_string(context.fd); + break; + case Operand::Fs: + operand_string = fpr_to_string(context.fs); + break; + case Operand::Ft: + operand_string = fpr_to_string(context.ft); + break; + case Operand::FdDouble: + operand_string = fpr_double_to_string(context.fd); + break; + case Operand::FsDouble: + operand_string = fpr_double_to_string(context.fs); + break; + case Operand::FtDouble: + operand_string = fpr_double_to_string(context.ft); + break; + case Operand::FdU32L: + operand_string = fpr_u32l_to_string(context.fd); + break; + case Operand::FsU32L: + operand_string = fpr_u32l_to_string(context.fs); + break; + case Operand::FtU32L: + operand_string = fpr_u32l_to_string(context.ft); + break; + case Operand::FdU32H: + assert(false); + break; + case Operand::FsU32H: + assert(false); + break; + case Operand::FtU32H: + assert(false); + break; + case Operand::FdU64: + operand_string = fpr_u64_to_string(context.fd); + break; + case Operand::FsU64: + operand_string = fpr_u64_to_string(context.fs); + break; + case Operand::FtU64: + operand_string = fpr_u64_to_string(context.ft); + break; + case Operand::ImmU16: + if (context.reloc_type != N64Recomp::RelocType::R_MIPS_NONE) { + operand_string = unsigned_reloc(context); + } + else { + operand_string = fmt::format("{:#X}ULL", context.imm16); + } + break; + case Operand::ImmS16: + if (context.reloc_type != N64Recomp::RelocType::R_MIPS_NONE) { + operand_string = signed_reloc(context); + } + else { + operand_string = fmt::format("{:#X}ULL", (int16_t)context.imm16); + } + break; + case Operand::Sa: + operand_string = std::to_string(context.sa); + break; + case Operand::Sa32: + operand_string = fmt::format("({} + 32ULL)", context.sa); + break; + case Operand::Cop1cs: + operand_string = fmt::format("c1cs"); + break; + case Operand::Hi: + operand_string = "hi"; + break; + case Operand::Lo: + operand_string = "lo"; + break; + case Operand::Zero: + operand_string = "0ULL"; + break; + } + switch (operation) { + case UnaryOpType::None: + break; + case UnaryOpType::ToS32: + case UnaryOpType::ToInt32: + operand_string = "S32(" + operand_string + ")"; + break; + case UnaryOpType::ToU32: + operand_string = "U32(" + operand_string + ")"; + break; + case UnaryOpType::ToS64: + operand_string = "SIGNED(" + operand_string + ")"; + break; + case UnaryOpType::ToU64: + // Nothing to do here, they're already U64 + break; + case UnaryOpType::NegateS32: + assert(false); + break; + case UnaryOpType::NegateS64: + assert(false); + break; + case UnaryOpType::Lui: + operand_string = "S32(bit.lshift(" + operand_string + "), 16ULL)"; + break; + case UnaryOpType::Mask5: + operand_string = "(bit.band(" + operand_string + "), 31ULL)"; + break; + case UnaryOpType::Mask6: + operand_string = "(bit.band(" + operand_string + "), 63ULL)"; + break; + case UnaryOpType::Negate: + operand_string = "-" + operand_string; + break; + case UnaryOpType::AbsFloat: + operand_string = "ABSF(" + operand_string + ")"; + break; + case UnaryOpType::AbsDouble: + operand_string = "ABS(" + operand_string + ")"; + break; + case UnaryOpType::SqrtFloat: + operand_string = "SQRTF(" + operand_string + ")"; + break; + case UnaryOpType::SqrtDouble: + operand_string = "SQRT(" + operand_string + ")"; + break; + case UnaryOpType::ConvertSFromW: + operand_string = "CVT_S_W(" + operand_string + ")"; + break; + case UnaryOpType::ConvertWFromS: + operand_string = "CVT_W_S(" + operand_string + ", rounding_mode)"; + break; + case UnaryOpType::ConvertDFromW: + operand_string = "CVT_D_W(" + operand_string + ")"; + break; + case UnaryOpType::ConvertWFromD: + operand_string = "CVT_W_D(" + operand_string + ", rounding_mode)"; + break; + case UnaryOpType::ConvertDFromS: + operand_string = "CVT_D_S(" + operand_string + ")"; + break; + case UnaryOpType::ConvertSFromD: + operand_string = "CVT_S_D(" + operand_string + ")"; + break; + case UnaryOpType::ConvertDFromL: + operand_string = "CVT_D_L(" + operand_string + ")"; + break; + case UnaryOpType::ConvertLFromD: + operand_string = "CVT_L_D(" + operand_string + ")"; + break; + case UnaryOpType::ConvertSFromL: + operand_string = "CVT_S_L(" + operand_string + ")"; + break; + case UnaryOpType::ConvertLFromS: + operand_string = "CVT_L_S(" + operand_string + ")"; + break; + case UnaryOpType::TruncateWFromS: + operand_string = "TRUNC_W_S(" + operand_string + ")"; + break; + case UnaryOpType::TruncateWFromD: + operand_string = "TRUNC_W_D(" + operand_string + ")"; + break; + case UnaryOpType::RoundWFromS: + operand_string = "ROUND_W_S(" + operand_string + ")"; + break; + case UnaryOpType::RoundWFromD: + operand_string = "ROUND_W_D(" + operand_string + ")"; + break; + case UnaryOpType::CeilWFromS: + operand_string = "CEILF(" + operand_string + ")"; + break; + case UnaryOpType::CeilWFromD: + operand_string = "CEIL(" + operand_string + ")"; + break; + case UnaryOpType::FloorWFromS: + operand_string = "FLOORF(" + operand_string + ")"; + break; + case UnaryOpType::FloorWFromD: + operand_string = "FLOOR(" + operand_string + ")"; + break; + } +} + +void N64Recomp::LuajitGenerator::get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const { + func_string = luajit_op_fields[static_cast(op_type)].func_string; + infix_string = luajit_op_fields[static_cast(op_type)].infix_string; +} + +void N64Recomp::LuajitGenerator::get_binary_expr_string(BinaryOpType type, const BinaryOperands& operands, const InstructionContext& ctx, const std::string& output, std::string& expr_string) const { + thread_local std::string input_a{}; + 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); + + if (!func_string.empty() && !infix_string.empty()) { + expr_string = fmt::format("{}({} {} {})", func_string, input_a, infix_string, input_b); + } + else if (!func_string.empty()) { + expr_string = fmt::format("{}({}, {})", func_string, input_a, input_b); + } + else if (!infix_string.empty()) { + expr_string = fmt::format("{} {} {}", input_a, infix_string, input_b); + } + else { + // Handle special cases + if (type == BinaryOpType::True) { + expr_string = "true"; + } + else if (type == BinaryOpType::False) { + expr_string = "false"; + } + assert(false && "Binary operation must have either a function or infix!"); + } +} + + +void N64Recomp::LuajitGenerator::process_binary_op(const BinaryOp& op, const InstructionContext& ctx) 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 output{}; + thread_local std::string expression{}; + get_operand_string(op.output, UnaryOpType::None, ctx, output); + get_binary_expr_string(op.type, op.operands, ctx, output, expression); + + // Explicitly convert coprocessor 1 compare results into a number to ensure comparisons work correctly with it. + if (op.output == Operand::Cop1cs) { + expression = "BOOL_TO_NUM(" + expression + ")"; + } + + fmt::print(output_file, "{} = {}\n", output, expression); +} + +void N64Recomp::LuajitGenerator::process_unary_op(const UnaryOp& op, const InstructionContext& ctx) 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 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); +} + +void N64Recomp::LuajitGenerator::process_store_op(const StoreOp& op, const InstructionContext& ctx) 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 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); + + std::string func_text; + + switch (op.type) { + case StoreOpType::SD: + func_text = "MEM_SD"; + break; + case StoreOpType::SDL: + func_text = "MEM_SDL"; + break; + case StoreOpType::SDR: + func_text = "MEM_SDR"; + break; + case StoreOpType::SW: + func_text = "MEM_SW"; + break; + case StoreOpType::SWL: + func_text = "MEM_SWL"; + break; + case StoreOpType::SWR: + func_text = "MEM_SWR"; + break; + case StoreOpType::SH: + func_text = "MEM_SH"; + break; + case StoreOpType::SB: + func_text = "MEM_SB"; + break; + case StoreOpType::SDC1: + func_text = "MEM_SD"; + break; + case StoreOpType::SWC1: + func_text = "MEM_SW"; + break; + default: + throw std::runtime_error("Unhandled store op"); + } + + fmt::print(output_file, "{}(rdram, {}, {}, {});\n", func_text, imm_str, base_str, value_input); +} + +void N64Recomp::LuajitGenerator::emit_function_start(const std::string& function_name) const { + fmt::print(output_file, + "function {}(rdram, ctx)\n" + // these variables shouldn't need to be preserved across function boundaries, so make them local for more efficient output + " local hi = 0ULL\n" + " local lo = 0ULL\n" + " local result = 0ULL\n" + " local rounding_mode = DEFAULT_ROUNDING_MODE\n" + " local c1cs = 0ULL\n", // cop1 conditional signal + function_name); +} + +void N64Recomp::LuajitGenerator::emit_function_end() const { fmt::print(output_file, "end\n"); } -void N64Recomp::LuajitGenerator::emit_function_call_lookup(std::ostream& output_file, uint32_t addr) const { +void N64Recomp::LuajitGenerator::emit_function_call_lookup(uint32_t addr) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_function_call_by_register(std::ostream& output_file, int reg) const { +void N64Recomp::LuajitGenerator::emit_function_call_by_register(int reg) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_function_call_by_name(std::ostream& output_file, const std::string& func_name) const { +void N64Recomp::LuajitGenerator::emit_function_call_by_name(const std::string& func_name) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_goto(std::ostream& output_file, const std::string& target) const { +void N64Recomp::LuajitGenerator::emit_goto(const std::string& target) const { fmt::print(output_file, "goto {}\n", target); } -void N64Recomp::LuajitGenerator::emit_label(std::ostream& output_file, const std::string& label_name) const { +void N64Recomp::LuajitGenerator::emit_label(const std::string& label_name) const { fmt::print(output_file, "::{}::\n", label_name); } -void N64Recomp::LuajitGenerator::emit_variable_declaration(std::ostream& output_file, const std::string& var_name, int reg) const { +void N64Recomp::LuajitGenerator::emit_variable_declaration(const std::string& var_name, int reg) const { // TODO fmt::print(output_file, "{} = 0\n", var_name); } -void N64Recomp::LuajitGenerator::emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const { - // TODO - fmt::print(output_file, "if (true) then\n"); +void N64Recomp::LuajitGenerator::emit_branch_condition(const ConditionalBranchOp& op, const InstructionContext& ctx) 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 {} then\n", expr_string); } -void N64Recomp::LuajitGenerator::emit_branch_close(std::ostream& output_file) const { +void N64Recomp::LuajitGenerator::emit_branch_close() const { fmt::print(output_file, "end\n"); } -void N64Recomp::LuajitGenerator::emit_switch(std::ostream& output_file, const std::string& jump_variable, int shift_amount) const { +void N64Recomp::LuajitGenerator::emit_switch(const std::string& jump_variable, int shift_amount) const { fmt::print(output_file, "do local case_index = bit.rshift({}, {}ULL)\n", jump_variable, shift_amount); } -void N64Recomp::LuajitGenerator::emit_case(std::ostream& output_file, int case_index, const std::string& target_label) const { +void N64Recomp::LuajitGenerator::emit_case(int case_index, const std::string& target_label) const { fmt::print(output_file, "if case_index == {} then goto {} end\n", case_index, target_label); } -void N64Recomp::LuajitGenerator::emit_switch_error(std::ostream& output_file, uint32_t instr_vram, uint32_t jtbl_vram) const { +void N64Recomp::LuajitGenerator::emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const { fmt::print(output_file, "switch_error(\'lua\', {:08X}, {:08X})\n", instr_vram, jtbl_vram); } -void N64Recomp::LuajitGenerator::emit_switch_close(std::ostream& output_file) const { +void N64Recomp::LuajitGenerator::emit_switch_close() const { fmt::print(output_file, "end\n"); } -void N64Recomp::LuajitGenerator::emit_return(std::ostream& output_file) const { - fmt::print(output_file, "return\n"); +void N64Recomp::LuajitGenerator::emit_return() const { + // Wrap the retur in a do/end construct to prevent errors from statements after the return. + fmt::print(output_file, "do return end\n"); } -void N64Recomp::LuajitGenerator::emit_check_fr(std::ostream& output_file, int fpr) const { +void N64Recomp::LuajitGenerator::emit_check_fr(int fpr) const { // Not used } -void N64Recomp::LuajitGenerator::emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const { +void N64Recomp::LuajitGenerator::emit_check_nan(int fpr, bool is_double) const { // Not used } -void N64Recomp::LuajitGenerator::emit_cop0_status_read(std::ostream& output_file, int reg) const { +void N64Recomp::LuajitGenerator::emit_cop0_status_read(int reg) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_cop0_status_write(std::ostream& output_file, int reg) const { +void N64Recomp::LuajitGenerator::emit_cop0_status_write(int reg) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_cop1_cs_read(std::ostream& output_file, int reg) const { +void N64Recomp::LuajitGenerator::emit_cop1_cs_read(int reg) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_cop1_cs_write(std::ostream& output_file, int reg) const { +void N64Recomp::LuajitGenerator::emit_cop1_cs_write(int reg) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_muldiv(std::ostream& output_file, InstrId instr_id, int reg1, int reg2) const { +void N64Recomp::LuajitGenerator::emit_muldiv(InstrId instr_id, int reg1, int reg2) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_syscall(std::ostream& output_file, uint32_t instr_vram) const { +void N64Recomp::LuajitGenerator::emit_syscall(uint32_t instr_vram) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_do_break(std::ostream& output_file, uint32_t instr_vram) const { +void N64Recomp::LuajitGenerator::emit_do_break(uint32_t instr_vram) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_pause_self(std::ostream& output_file) const { +void N64Recomp::LuajitGenerator::emit_pause_self() const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_trigger_event(std::ostream& output_file, size_t event_index) const { +void N64Recomp::LuajitGenerator::emit_trigger_event(size_t event_index) const { // TODO fmt::print(output_file, "\n"); } -void N64Recomp::LuajitGenerator::emit_comment(std::ostream& output_file, const std::string& comment) const { +void N64Recomp::LuajitGenerator::emit_comment(const std::string& comment) const { fmt::print(output_file, "-- {}\n", comment); } diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 5c1ad28..5ab95ca 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -134,11 +134,11 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con // Output a comment with the original instruction print_indent(); if (instr.isBranch() || instr.getUniqueId() == InstrId::cpu_j) { - generator.emit_comment(output_file, fmt::format("0x{:08X}: {}", instr_vram, instr.disassemble(0, fmt::format("L_{:08X}", (uint32_t)instr.getBranchVramGeneric())))); + generator.emit_comment(fmt::format("0x{:08X}: {}", instr_vram, instr.disassemble(0, fmt::format("L_{:08X}", (uint32_t)instr.getBranchVramGeneric())))); } else if (instr.getUniqueId() == InstrId::cpu_jal) { - generator.emit_comment(output_file, fmt::format("0x{:08X}: {}", instr_vram, instr.disassemble(0, fmt::format("0x{:08X}", (uint32_t)instr.getBranchVramGeneric())))); + generator.emit_comment(fmt::format("0x{:08X}: {}", instr_vram, instr.disassemble(0, fmt::format("0x{:08X}", (uint32_t)instr.getBranchVramGeneric())))); } else { - generator.emit_comment(output_file, fmt::format("0x{:08X}: {}", instr_vram, instr.disassemble(0))); + generator.emit_comment(fmt::format("0x{:08X}: {}", instr_vram, instr.disassemble(0))); } if (skipped_insns.contains(instr_vram)) { @@ -226,7 +226,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con auto print_link_branch = [&]() { if (needs_link_branch) { print_indent(); - generator.emit_goto(output_file, fmt::format("after_{}", link_branch_index)); + generator.emit_goto(fmt::format("after_{}", link_branch_index)); } }; @@ -235,7 +235,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_return(output_file); + generator.emit_return(); print_link_branch(); return true; }; @@ -245,7 +245,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_goto(output_file, target); + generator.emit_goto(target); print_link_branch(); return true; }; @@ -255,7 +255,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_function_call_by_register(output_file, reg); + generator.emit_function_call_by_register(reg); print_link_branch(); return true; }; @@ -265,7 +265,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_function_call_lookup(output_file, target_vram); + generator.emit_function_call_lookup(target_vram); print_link_branch(); return true; }; @@ -284,7 +284,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_trigger_event(output_file, reloc_reference_symbol); + generator.emit_trigger_event(reloc_reference_symbol); print_link_branch(); } // Normal symbol or reference symbol, @@ -342,10 +342,10 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con } print_indent(); if (call_by_lookup) { - generator.emit_function_call_lookup(output_file, target_func_vram); + generator.emit_function_call_lookup(target_func_vram); } else { - generator.emit_function_call_by_name(output_file, jal_target_name); + generator.emit_function_call_by_name(jal_target_name); } print_link_branch(); } @@ -363,9 +363,9 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_return(output_file); + generator.emit_return(); print_indent(); - generator.emit_branch_close(output_file); + generator.emit_branch_close(); return true; } @@ -378,11 +378,11 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con print_indent(); print_indent(); - generator.emit_goto(output_file, fmt::format("L_{:08X}", branch_target)); + generator.emit_goto(fmt::format("L_{:08X}", branch_target)); if (needs_link_branch) { print_indent(); print_indent(); - generator.emit_goto(output_file, fmt::format("after_{}", link_branch_index)); + generator.emit_goto(fmt::format("after_{}", link_branch_index)); } return true; }; @@ -416,7 +416,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con switch (reg) { case Cop0Reg::COP0_Status: print_indent(); - generator.emit_cop0_status_read(output_file, rt); + generator.emit_cop0_status_read(rt); break; default: fmt::print(stderr, "Unhandled cop0 register in mfc0: {}\n", (int)reg); @@ -430,7 +430,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con switch (reg) { case Cop0Reg::COP0_Status: print_indent(); - generator.emit_cop0_status_write(output_file, rt); + generator.emit_cop0_status_write(rt); break; default: fmt::print(stderr, "Unhandled cop0 register in mtc0: {}\n", (int)reg); @@ -451,7 +451,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con if (find_result != stats.jump_tables.end()) { const N64Recomp::JumpTable& cur_jtbl = *find_result; print_indent(); - generator.emit_variable_declaration(output_file, fmt::format("jr_addend_{:08X}", cur_jtbl.jr_vram), cur_jtbl.addend_reg); + generator.emit_variable_declaration(fmt::format("jr_addend_{:08X}", cur_jtbl.jr_vram), cur_jtbl.addend_reg); } } break; @@ -464,7 +464,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con case InstrId::cpu_divu: case InstrId::cpu_ddivu: print_indent(); - generator.emit_muldiv(output_file, instr.getUniqueId(), rs, rt); + generator.emit_muldiv(instr.getUniqueId(), rs, rt); break; // Branches case InstrId::cpu_jal: @@ -487,7 +487,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con uint32_t branch_target = instr.getBranchVramGeneric(); if (branch_target == instr_vram) { print_indent(); - generator.emit_pause_self(output_file); + generator.emit_pause_self(); } // Check if the branch is within this function else if (branch_target >= func.vram && branch_target < func_vram_end) { @@ -511,7 +511,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_return(output_file); + generator.emit_return(); } else { fmt::print(stderr, "Unhandled branch in {} at 0x{:08X} to 0x{:08X}\n", func.name, instr_vram, branch_target); @@ -534,37 +534,37 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_switch(output_file, fmt::format("jr_addend_{:08X}", cur_jtbl.jr_vram), 2); + generator.emit_switch(fmt::format("jr_addend_{:08X}", cur_jtbl.jr_vram), 2); for (size_t entry_index = 0; entry_index < cur_jtbl.entries.size(); entry_index++) { print_indent(); print_indent(); - generator.emit_case(output_file, entry_index, fmt::format("L_{:08X}", cur_jtbl.entries[entry_index])); + generator.emit_case(entry_index, fmt::format("L_{:08X}", cur_jtbl.entries[entry_index])); } print_indent(); print_indent(); - generator.emit_switch_error(output_file, instr_vram, cur_jtbl.vram); + generator.emit_switch_error(instr_vram, cur_jtbl.vram); print_indent(); - generator.emit_switch_close(output_file); + generator.emit_switch_close(); break; } fmt::print("[Info] Indirect tail call in {}\n", func.name); print_func_call_by_register(rs); print_indent(); - generator.emit_return(output_file); + generator.emit_return(); break; } break; case InstrId::cpu_syscall: print_indent(); - generator.emit_syscall(output_file, instr_vram); + generator.emit_syscall(instr_vram); // syscalls don't link, so treat it like a tail call print_indent(); - generator.emit_return(output_file); + generator.emit_return(); break; case InstrId::cpu_break: print_indent(); - generator.emit_do_break(output_file, instr_vram); + generator.emit_do_break(instr_vram); break; // Cop1 rounding mode @@ -574,7 +574,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_cop1_cs_write(output_file, rt); + generator.emit_cop1_cs_write(rt); break; case InstrId::cpu_cfc1: if (cop1_cs != 31) { @@ -582,7 +582,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_cop1_cs_read(output_file, rt); + generator.emit_cop1_cs_read(rt); break; default: handled = false; @@ -604,28 +604,28 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con instruction_context.reloc_section_index = reloc_section; instruction_context.reloc_target_section_offset = reloc_target_section_offset; - auto do_check_fr = [](std::ostream& output_file, const GeneratorType& generator, const InstructionContext& ctx, Operand operand) { + auto do_check_fr = [](const GeneratorType& generator, const InstructionContext& ctx, Operand operand) { switch (operand) { case Operand::Fd: case Operand::FdDouble: case Operand::FdU32L: case Operand::FdU32H: case Operand::FdU64: - generator.emit_check_fr(output_file, ctx.fd); + generator.emit_check_fr(ctx.fd); break; case Operand::Fs: case Operand::FsDouble: case Operand::FsU32L: case Operand::FsU32H: case Operand::FsU64: - generator.emit_check_fr(output_file, ctx.fs); + generator.emit_check_fr(ctx.fs); break; case Operand::Ft: case Operand::FtDouble: case Operand::FtU32L: case Operand::FtU32H: case Operand::FtU64: - generator.emit_check_fr(output_file, ctx.ft); + generator.emit_check_fr(ctx.ft); break; default: // No MIPS3 float check needed for non-float operands. @@ -633,25 +633,25 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con } }; - auto do_check_nan = [](std::ostream& output_file, const GeneratorType& generator, const InstructionContext& ctx, Operand operand) { + auto do_check_nan = [](const GeneratorType& generator, const InstructionContext& ctx, Operand operand) { switch (operand) { case Operand::Fd: - generator.emit_check_nan(output_file, ctx.fd, false); + generator.emit_check_nan(ctx.fd, false); break; case Operand::Fs: - generator.emit_check_nan(output_file, ctx.fs, false); + generator.emit_check_nan(ctx.fs, false); break; case Operand::Ft: - generator.emit_check_nan(output_file, ctx.ft, false); + generator.emit_check_nan(ctx.ft, false); break; case Operand::FdDouble: - generator.emit_check_nan(output_file, ctx.fd, true); + generator.emit_check_nan(ctx.fd, true); break; case Operand::FsDouble: - generator.emit_check_nan(output_file, ctx.fs, true); + generator.emit_check_nan(ctx.fs, true); break; case Operand::FtDouble: - generator.emit_check_nan(output_file, ctx.ft, true); + generator.emit_check_nan(ctx.ft, true); break; default: // No NaN checks needed for non-float operands. @@ -665,19 +665,19 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con const BinaryOp& op = find_binary_it->second; if (op.check_fr) { - do_check_fr(output_file, generator, instruction_context, op.output); - do_check_fr(output_file, generator, instruction_context, op.operands.operands[0]); - do_check_fr(output_file, generator, instruction_context, op.operands.operands[1]); + do_check_fr(generator, instruction_context, op.output); + do_check_fr(generator, instruction_context, op.operands.operands[0]); + do_check_fr(generator, instruction_context, op.operands.operands[1]); } if (op.check_nan) { - do_check_nan(output_file, generator, instruction_context, op.operands.operands[0]); - do_check_nan(output_file, generator, instruction_context, op.operands.operands[1]); + do_check_nan(generator, instruction_context, op.operands.operands[0]); + do_check_nan(generator, instruction_context, op.operands.operands[1]); fmt::print(output_file, "\n"); print_indent(); } - generator.process_binary_op(output_file, op, instruction_context); + generator.process_binary_op(op, instruction_context); handled = true; } @@ -687,24 +687,24 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con const UnaryOp& op = find_unary_it->second; if (op.check_fr) { - do_check_fr(output_file, generator, instruction_context, op.output); - do_check_fr(output_file, generator, instruction_context, op.input); + do_check_fr(generator, instruction_context, op.output); + do_check_fr(generator, instruction_context, op.input); } if (op.check_nan) { - do_check_nan(output_file, generator, instruction_context, op.input); + do_check_nan(generator, instruction_context, op.input); fmt::print(output_file, "\n"); print_indent(); } - generator.process_unary_op(output_file, op, instruction_context); + generator.process_unary_op(op, instruction_context); handled = true; } auto find_conditional_branch_it = conditional_branch_ops.find(instr.getUniqueId()); if (find_conditional_branch_it != conditional_branch_ops.end()) { print_indent(); - generator.emit_branch_condition(output_file, find_conditional_branch_it->second, instruction_context); + generator.emit_branch_condition(find_conditional_branch_it->second, instruction_context); print_indent(); if (find_conditional_branch_it->second.link) { @@ -719,7 +719,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con } print_indent(); - generator.emit_branch_close(output_file); + generator.emit_branch_close(); is_branch_likely = find_conditional_branch_it->second.likely; handled = true; @@ -731,10 +731,10 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con const StoreOp& op = find_store_it->second; if (op.type == StoreOpType::SDC1) { - do_check_fr(output_file, generator, instruction_context, op.value_input); + do_check_fr(generator, instruction_context, op.value_input); } - generator.process_store_op(output_file, op, instruction_context); + generator.process_store_op(op, instruction_context); handled = true; } @@ -746,7 +746,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con // TODO is this used? if (emit_link_branch) { print_indent(); - generator.emit_label(output_file, fmt::format("after_{}", link_branch_index)); + generator.emit_label(fmt::format("after_{}", link_branch_index)); } return true; @@ -757,7 +757,7 @@ bool recompile_function_impl(GeneratorType& generator, const N64Recomp::Context& //fmt::print("Recompiling {}\n", func.name); std::vector instructions; - generator.emit_function_start(output_file, func.name); + generator.emit_function_start(func.name); // Skip analysis and recompilation of this function is stubbed. if (!func.stubbed) { @@ -816,11 +816,11 @@ bool recompile_function_impl(GeneratorType& generator, const N64Recomp::Context& bool is_branch_likely = false; // If we're in the delay slot of a likely instruction, emit a goto to skip the instruction before any labels if (in_likely_delay_slot) { - generator.emit_goto(output_file, fmt::format("skip_{}", num_likely_branches)); + generator.emit_goto(fmt::format("skip_{}", num_likely_branches)); } // If there are any other branch labels to insert and we're at the next one, insert it if (cur_label != branch_labels.end() && vram >= *cur_label) { - generator.emit_label(output_file, fmt::format("L_{:08X}", *cur_label)); + generator.emit_label(fmt::format("L_{:08X}", *cur_label)); ++cur_label; } @@ -842,7 +842,7 @@ bool recompile_function_impl(GeneratorType& generator, const N64Recomp::Context& // Now that the instruction has been processed, emit a skip label for the likely branch if needed if (in_likely_delay_slot) { fmt::print(output_file, " "); - generator.emit_label(output_file, fmt::format("skip_{}", num_likely_branches)); + generator.emit_label(fmt::format("skip_{}", num_likely_branches)); num_likely_branches++; } // Mark the next instruction as being in a likely delay slot if the @@ -853,18 +853,18 @@ bool recompile_function_impl(GeneratorType& generator, const N64Recomp::Context& } // Terminate the function - generator.emit_function_end(output_file); + generator.emit_function_end(); return true; } // Wrap the templated function with CGenerator as the template parameter. bool N64Recomp::recompile_function(const N64Recomp::Context& context, const N64Recomp::Function& func, std::ofstream& output_file, std::span> static_funcs_out, bool tag_reference_relocs) { - CGenerator generator{}; + CGenerator generator{output_file}; return recompile_function_impl(generator, context, func, output_file, static_funcs_out, tag_reference_relocs); } bool N64Recomp::recompile_function_luajit(const N64Recomp::Context& context, const N64Recomp::Function& func, std::ofstream& output_file, std::span> static_funcs_out, bool tag_reference_relocs) { - LuajitGenerator generator{}; + LuajitGenerator generator{output_file}; return recompile_function_impl(generator, context, func, output_file, static_funcs_out, tag_reference_relocs); }