diff --git a/CMakeLists.txt b/CMakeLists.txt index b2e6fca..9839dee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,8 @@ target_sources(N64Recomp PRIVATE ${CMAKE_SOURCE_DIR}/src/analysis.cpp ${CMAKE_SOURCE_DIR}/src/config.cpp ${CMAKE_SOURCE_DIR}/src/main.cpp + ${CMAKE_SOURCE_DIR}/src/operations.cpp + ${CMAKE_SOURCE_DIR}/src/cgenerator.cpp ${CMAKE_SOURCE_DIR}/src/recompilation.cpp) target_include_directories(N64Recomp PRIVATE diff --git a/include/generator.h b/include/generator.h new file mode 100644 index 0000000..486cd09 --- /dev/null +++ b/include/generator.h @@ -0,0 +1,55 @@ +#ifndef __GENERATOR_H__ +#define __GENERATOR_H__ + +#include "recomp_port.h" +#include "operations.h" + +namespace RecompPort { + struct InstructionContext { + int rd; + int rs; + int rt; + int sa; + + int fd; + int fs; + int ft; + + int cop1_cs; + + uint16_t imm16; + + RelocType reloc_type; + uint32_t reloc_section_index; + uint32_t reloc_target_section_offset; + }; + + 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_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_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; + }; + + 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_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_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; + 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; + }; +} + +#endif diff --git a/include/operations.h b/include/operations.h new file mode 100644 index 0000000..cda57e6 --- /dev/null +++ b/include/operations.h @@ -0,0 +1,200 @@ +#ifndef __OPERATIONS_H__ +#define __OPERATIONS_H__ + +#include + +#include "rabbitizer.hpp" + +namespace RecompPort { + using InstrId = rabbitizer::InstrId::UniqueId; + using Cop0Reg = rabbitizer::Registers::Cpu::Cop0; + + enum class StoreOpType { + SD, + SDL, + SDR, + SW, + SWL, + SWR, + SH, + SB, + SDC1, + SWC1 + }; + + enum class UnaryOpType { + None, + ToS32, + ToU32, + ToS64, + ToU64, + NegateS32, + NegateS64, + Lui, + Mask5, // Mask to 5 bits + Mask6, // Mask to 5 bits + ToInt32, // Functionally equivalent to ToS32, only exists for parity with old codegen + Negate, + AbsFloat, + AbsDouble, + SqrtFloat, + SqrtDouble, + ConvertSFromW, + ConvertWFromS, + ConvertDFromW, + ConvertWFromD, + ConvertDFromS, + ConvertSFromD, + ConvertDFromL, + ConvertLFromD, + ConvertSFromL, + ConvertLFromS, + TruncateWFromS, + TruncateWFromD, + RoundWFromS, + RoundWFromD, + CeilWFromS, + CeilWFromD, + FloorWFromS, + FloorWFromD + }; + + enum class BinaryOpType { + // Addition/subtraction + Add32, + Sub32, + Add64, + Sub64, + // Float arithmetic + AddFloat, + AddDouble, + SubFloat, + SubDouble, + MulFloat, + MulDouble, + DivFloat, + DivDouble, + // Bitwise + And64, + Or64, + Nor64, + Xor64, + Sll32, + Sll64, + Srl32, + Srl64, + Sra32, + Sra64, + // Comparisons + Equal, + NotEqual, + Less, + LessEq, + Greater, + GreaterEq, + // Loads + LD, + LW, + LWU, + LH, + LHU, + LB, + LBU, + LDL, + LDR, + LWL, + LWR, + // Fixed result + True, + False, + + COUNT, + }; + + enum class Operand { + Rd, // GPR + Rs, // GPR + Rt, // GPR + Fd, // FPR + Fs, // FPR + Ft, // FPR + FdDouble, // Double float in fd FPR + FsDouble, // Double float in fs FPR + FtDouble, // Double float in ft FPR + // Raw low 32-bit values of FPRs with handling for mips3 float mode behavior + FdU32L, + FsU32L, + FtU32L, + // Raw high 32-bit values of FPRs with handling for mips3 float mode behavior + FdU32H, + FsU32H, + FtU32H, + // Raw 64-bit values of FPRs + FdU64, + FsU64, + FtU64, + ImmU16, // 16-bit immediate, unsigned + ImmS16, // 16-bit immediate, signed + Sa, // Shift amount + Sa32, // Shift amount plus 32 + Cop1cs, // Coprocessor 1 Condition Signal + Hi, + Lo, + Zero, + + Base = Rs, // Alias for Rs for loads + }; + + struct StoreOp { + StoreOpType type; + Operand value_input; + }; + + struct UnaryOp { + UnaryOpType operation; + Operand output; + Operand input; + // Whether the FR bit needs to be checked for odd float registers for this instruction. + bool check_fr = false; + // Whether the input need to be checked for being NaN. + bool check_nan = false; + }; + + struct BinaryOperands { + // Operation to apply to each operand before applying the binary operation to them. + UnaryOpType operand_operations[2]; + // The source of the input operands. + Operand operands[2]; + }; + + struct BinaryOp { + // The type of binary operation this represents. + BinaryOpType type; + // The output operand. + Operand output; + // The input operands. + BinaryOperands operands; + // Whether the FR bit needs to be checked for odd float registers for this instruction. + bool check_fr = false; + // Whether the inputs need to be checked for being NaN. + bool check_nan = false; + }; + + struct ConditionalBranchOp { + // The type of binary operation to use for this compare + BinaryOpType comparison; + // The input operands. + BinaryOperands operands; + // Whether this jump should link for returns. + bool link; + // Whether this jump has "likely" behavior (doesn't execute the delay slot if skipped). + bool likely; + }; + + 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; +} + +#endif diff --git a/src/cgenerator.cpp b/src/cgenerator.cpp new file mode 100644 index 0000000..78410a0 --- /dev/null +++ b/src/cgenerator.cpp @@ -0,0 +1,483 @@ +#include +#include + +#include "fmt/format.h" +#include "fmt/ostream.h" + +#include "generator.h" + +struct BinaryOpFields { std::string func_string; std::string infix_string; }; + +std::vector c_op_fields = []() { + std::vector ret{}; + ret.resize(static_cast(RecompPort::BinaryOpType::COUNT)); + std::vector ops_setup{}; + ops_setup.resize(static_cast(RecompPort::BinaryOpType::COUNT)); + + auto setup_op = [&ret, &ops_setup](RecompPort::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(RecompPort::BinaryOpType::Add32, "ADD32", ""); + setup_op(RecompPort::BinaryOpType::Sub32, "SUB32", ""); + setup_op(RecompPort::BinaryOpType::Add64, "", "+"); + setup_op(RecompPort::BinaryOpType::Sub64, "", "-"); + setup_op(RecompPort::BinaryOpType::And64, "", "&"); + setup_op(RecompPort::BinaryOpType::AddFloat, "", "+"); + setup_op(RecompPort::BinaryOpType::AddDouble, "", "+"); + setup_op(RecompPort::BinaryOpType::SubFloat, "", "-"); + setup_op(RecompPort::BinaryOpType::SubDouble, "", "-"); + setup_op(RecompPort::BinaryOpType::MulFloat, "MUL_S", ""); + setup_op(RecompPort::BinaryOpType::MulDouble, "MUL_D", ""); + setup_op(RecompPort::BinaryOpType::DivFloat, "DIV_S", ""); + setup_op(RecompPort::BinaryOpType::DivDouble, "DIV_D", ""); + setup_op(RecompPort::BinaryOpType::Or64, "", "|"); + setup_op(RecompPort::BinaryOpType::Nor64, "~", "|"); + setup_op(RecompPort::BinaryOpType::Xor64, "", "^"); + setup_op(RecompPort::BinaryOpType::Sll32, "S32", "<<"); + setup_op(RecompPort::BinaryOpType::Sll64, "", "<<"); + setup_op(RecompPort::BinaryOpType::Srl32, "S32", ">>"); + setup_op(RecompPort::BinaryOpType::Srl64, "", ">>"); + setup_op(RecompPort::BinaryOpType::Sra32, "S32", ">>"); // Arithmetic aspect will be taken care of by unary op for first operand. + setup_op(RecompPort::BinaryOpType::Sra64, "", ">>"); // Arithmetic aspect will be taken care of by unary op for first operand. + setup_op(RecompPort::BinaryOpType::Equal, "", "=="); + setup_op(RecompPort::BinaryOpType::NotEqual, "", "!="); + setup_op(RecompPort::BinaryOpType::Less, "", "<"); + setup_op(RecompPort::BinaryOpType::LessEq, "", "<="); + setup_op(RecompPort::BinaryOpType::Greater, "", ">"); + setup_op(RecompPort::BinaryOpType::GreaterEq, "", ">="); + setup_op(RecompPort::BinaryOpType::LD, "LD", ""); + setup_op(RecompPort::BinaryOpType::LW, "MEM_W", ""); + setup_op(RecompPort::BinaryOpType::LWU, "MEM_WU", ""); + setup_op(RecompPort::BinaryOpType::LH, "MEM_H", ""); + setup_op(RecompPort::BinaryOpType::LHU, "MEM_HU", ""); + setup_op(RecompPort::BinaryOpType::LB, "MEM_B", ""); + setup_op(RecompPort::BinaryOpType::LBU, "MEM_BU", ""); + setup_op(RecompPort::BinaryOpType::LDL, "do_ldl", ""); + setup_op(RecompPort::BinaryOpType::LDR, "do_ldr", ""); + setup_op(RecompPort::BinaryOpType::LWL, "do_lwl", ""); + setup_op(RecompPort::BinaryOpType::LWR, "do_lwr", ""); + setup_op(RecompPort::BinaryOpType::True, "", ""); + setup_op(RecompPort::BinaryOpType::False, "", ""); + + // Ensure every operation has been setup. + for (char is_set : ops_setup) { + assert(is_set && "Operation has not been setup!"); + } + + return ret; +}(); + +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) { + return fmt::format("ctx->f{}.fl", fpr_index); +} + +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) { + if (fpr_index & 1) { + return fmt::format("ctx->f_odd[({} - 1) * 2]", fpr_index); + } + else { + return fmt::format("ctx->f{}.u32l", fpr_index); + } +} + +std::string fpr_u64_to_string(int fpr_index) { + return fmt::format("ctx->f{}.u64", fpr_index); +} + +std::string unsigned_reloc(const RecompPort::InstructionContext& context) { + switch (context.reloc_type) { + case RecompPort::RelocType::R_MIPS_HI16: + return fmt::format("RELOC_HI16({}, {:#X})", context.reloc_section_index, context.reloc_target_section_offset); + case RecompPort::RelocType::R_MIPS_LO16: + return fmt::format("RELOC_LO16({}, {:#X})", 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))); + } +} + +std::string signed_reloc(const RecompPort::InstructionContext& context) { + return "(int16_t)" + unsigned_reloc(context); +} + +void RecompPort::CGenerator::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 != RecompPort::RelocType::R_MIPS_NONE) { + operand_string = unsigned_reloc(context); + } + else { + operand_string = fmt::format("{:#X}", context.imm16); + } + break; + case Operand::ImmS16: + if (context.reloc_type != RecompPort::RelocType::R_MIPS_NONE) { + operand_string = signed_reloc(context); + } + else { + operand_string = fmt::format("{:#X}", (int16_t)context.imm16); + } + break; + case Operand::Sa: + operand_string = std::to_string(context.sa); + break; + case Operand::Sa32: + operand_string = fmt::format("({} + 32)", 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 = "0"; + break; + } + switch (operation) { + case UnaryOpType::None: + break; + case UnaryOpType::ToS32: + 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(" + operand_string + " << 16)"; + break; + case UnaryOpType::Mask5: + operand_string = "(" + operand_string + " & 31)"; + break; + case UnaryOpType::Mask6: + operand_string = "(" + operand_string + " & 63)"; + break; + case UnaryOpType::ToInt32: + operand_string = "(int32_t)" + operand_string; + break; + case UnaryOpType::Negate: + operand_string = "-" + operand_string; + break; + case UnaryOpType::AbsFloat: + operand_string = "fabsf(" + operand_string + ")"; + break; + case UnaryOpType::AbsDouble: + operand_string = "fabs(" + 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 + ")"; + break; + case UnaryOpType::ConvertDFromW: + operand_string = "CVT_D_W(" + operand_string + ")"; + break; + case UnaryOpType::ConvertWFromD: + operand_string = "CVT_W_D(" + operand_string + ")"; + 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 = "lroundf(" + operand_string + ")"; + break; + case UnaryOpType::RoundWFromD: + operand_string = "lround(" + operand_string + ")"; + break; + case UnaryOpType::CeilWFromS: + operand_string = "S32(ceilf(" + operand_string + "))"; + break; + case UnaryOpType::CeilWFromD: + operand_string = "S32(ceil(" + operand_string + "))"; + break; + case UnaryOpType::FloorWFromS: + operand_string = "S32(floorf(" + operand_string + "))"; + break; + case UnaryOpType::FloorWFromD: + operand_string = "S32(floor(" + operand_string + "))"; + break; + } +} + +void RecompPort::CGenerator::get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const { + func_string = c_op_fields[static_cast(op_type)].func_string; + infix_string = c_op_fields[static_cast(op_type)].infix_string; +} + +void RecompPort::CGenerator::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); + + // These cases aren't strictly necessary and are just here for parity with the old recompiler output. + if (type == BinaryOpType::Less && !((operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) || (operands.operands[0] == Operand::Fs || operands.operands[0] == Operand::FsDouble))) { + expr_string = fmt::format("{} {} {} ? 1 : 0", input_a, infix_string, input_b); + } + else if (type == BinaryOpType::Equal && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) { + expr_string = input_a; + } + else if (type == BinaryOpType::NotEqual && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) { + expr_string = "!" + input_a; + } + // End unnecessary cases. + + // TODO encode these ops to avoid needing special handling. + else if (type == BinaryOpType::LWL || type == BinaryOpType::LWR || type == BinaryOpType::LDL || type == BinaryOpType::LDR) { + expr_string = fmt::format("{}(rdram, {}, {}, {})", func_string, output, input_a, input_b); + } + else 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 = "1"; + } + else if (type == BinaryOpType::False) { + expr_string = "0"; + } + assert(false && "Binary operation must have either a function or infix!"); + } +} + +void RecompPort::CGenerator::emit_branch_condition(std::ostream& output_file, 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 ({}) {{\n", expr_string); +} + +void RecompPort::CGenerator::emit_branch_close(std::ostream& output_file) const { + fmt::print(output_file, " }}\n"); +} + +void RecompPort::CGenerator::emit_check_fr(std::ostream& output_file, int fpr) const { + fmt::print(output_file, "CHECK_FR(ctx, {});\n ", fpr); +} + +void RecompPort::CGenerator::emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const { + fmt::print(output_file, "NAN_CHECK(ctx->f{}.{}); ", fpr, is_double ? "d" : "fl"); +} + +void RecompPort::CGenerator::process_binary_op(std::ostream& output_file, 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); + fmt::print(output_file, "{} = {};\n", output, expression); +} + +void RecompPort::CGenerator::process_unary_op(std::ostream& output_file, 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 RecompPort::CGenerator::process_store_op(std::ostream& output_file, 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); + + enum class StoreSyntax { + Func, + FuncWithRdram, + Assignment, + }; + + StoreSyntax syntax; + std::string func_text; + + switch (op.type) { + case StoreOpType::SD: + func_text = "SD"; + syntax = StoreSyntax::Func; + break; + case StoreOpType::SDL: + func_text = "do_sdl"; + syntax = StoreSyntax::FuncWithRdram; + break; + case StoreOpType::SDR: + func_text = "do_sdr"; + syntax = StoreSyntax::FuncWithRdram; + break; + case StoreOpType::SW: + func_text = "MEM_W"; + syntax = StoreSyntax::Assignment; + break; + case StoreOpType::SWL: + func_text = "do_swl"; + syntax = StoreSyntax::FuncWithRdram; + break; + case StoreOpType::SWR: + func_text = "do_swr"; + syntax = StoreSyntax::FuncWithRdram; + break; + case StoreOpType::SH: + func_text = "MEM_H"; + syntax = StoreSyntax::Assignment; + break; + case StoreOpType::SB: + func_text = "MEM_B"; + syntax = StoreSyntax::Assignment; + break; + case StoreOpType::SDC1: + func_text = "SD"; + syntax = StoreSyntax::Func; + break; + case StoreOpType::SWC1: + func_text = "MEM_W"; + syntax = StoreSyntax::Assignment; + break; + default: + throw std::runtime_error("Unhandled store op"); + } + + switch (syntax) { + case StoreSyntax::Func: + fmt::print(output_file, "{}({}, {}, {});\n", func_text, value_input, imm_str, base_str); + break; + case StoreSyntax::FuncWithRdram: + fmt::print(output_file, "{}(rdram, {}, {}, {});\n", func_text, imm_str, base_str, value_input); + break; + case StoreSyntax::Assignment: + fmt::print(output_file, "{}({}, {}) = {};\n", func_text, imm_str, base_str, value_input); + break; + } +} diff --git a/src/operations.cpp b/src/operations.cpp new file mode 100644 index 0000000..0baf15d --- /dev/null +++ b/src/operations.cpp @@ -0,0 +1,180 @@ +#include "operations.h" + +namespace RecompPort { + const std::unordered_map unary_ops { + { InstrId::cpu_lui, { UnaryOpType::Lui, Operand::Rt, Operand::ImmU16 } }, + { InstrId::cpu_mthi, { UnaryOpType::None, Operand::Hi, Operand::Rs } }, + { InstrId::cpu_mtlo, { UnaryOpType::None, Operand::Lo, Operand::Rs } }, + { InstrId::cpu_mfhi, { UnaryOpType::None, Operand::Rd, Operand::Hi } }, + { InstrId::cpu_mflo, { UnaryOpType::None, Operand::Rd, Operand::Lo } }, + { InstrId::cpu_mtc1, { UnaryOpType::None, Operand::FsU32L, Operand::Rt } }, + { InstrId::cpu_mfc1, { UnaryOpType::ToInt32, Operand::Rt, Operand::FsU32L } }, + // Float operations + { InstrId::cpu_mov_s, { UnaryOpType::None, Operand::Fd, Operand::Fs, true } }, + { InstrId::cpu_mov_d, { UnaryOpType::None, Operand::FdDouble, Operand::FdDouble, true } }, + { InstrId::cpu_neg_s, { UnaryOpType::Negate, Operand::Fd, Operand::Fs, true, true } }, + { InstrId::cpu_neg_d, { UnaryOpType::Negate, Operand::FdDouble, Operand::FsDouble, true, true } }, + { InstrId::cpu_abs_s, { UnaryOpType::AbsFloat, Operand::Fd, Operand::Fs, true, true } }, + { InstrId::cpu_abs_d, { UnaryOpType::AbsDouble, Operand::FdDouble, Operand::FsDouble, true, true } }, + { InstrId::cpu_sqrt_s, { UnaryOpType::SqrtFloat, Operand::Fd, Operand::Fs, true, true } }, + { InstrId::cpu_sqrt_d, { UnaryOpType::SqrtDouble, Operand::FdDouble, Operand::FsDouble, true, true } }, + { InstrId::cpu_cvt_s_w, { UnaryOpType::ConvertSFromW, Operand::Fd, Operand::FsU32L, true } }, + { InstrId::cpu_cvt_w_s, { UnaryOpType::ConvertWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_cvt_d_w, { UnaryOpType::ConvertDFromW, Operand::FdDouble, Operand::FsU32L, true } }, + { InstrId::cpu_cvt_w_d, { UnaryOpType::ConvertWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + { InstrId::cpu_cvt_d_s, { UnaryOpType::ConvertDFromS, Operand::FdDouble, Operand::Fs, true, true } }, + { InstrId::cpu_cvt_s_d, { UnaryOpType::ConvertSFromD, Operand::Fd, Operand::FsDouble, true, true } }, + { InstrId::cpu_cvt_d_l, { UnaryOpType::ConvertDFromL, Operand::FdDouble, Operand::FsU64, true } }, + { InstrId::cpu_cvt_l_d, { UnaryOpType::ConvertLFromD, Operand::FdU64, Operand::FsDouble, true, true } }, + { InstrId::cpu_cvt_s_l, { UnaryOpType::ConvertSFromL, Operand::Fd, Operand::FsU64, true } }, + { InstrId::cpu_cvt_l_s, { UnaryOpType::ConvertLFromS, Operand::FdU64, Operand::Fs, true, true } }, + { InstrId::cpu_trunc_w_s, { UnaryOpType::TruncateWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_trunc_w_d, { UnaryOpType::TruncateWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + { InstrId::cpu_round_w_s, { UnaryOpType::RoundWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_round_w_d, { UnaryOpType::RoundWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + { InstrId::cpu_ceil_w_s, { UnaryOpType::CeilWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_ceil_w_d, { UnaryOpType::CeilWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + { InstrId::cpu_floor_w_s, { UnaryOpType::FloorWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_floor_w_d, { UnaryOpType::FloorWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + }; + + // TODO fix usage of check_nan + const std::unordered_map binary_ops { + // Addition/subtraction + { InstrId::cpu_addu, { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_add, { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_negu, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, // pseudo op for subu + { InstrId::cpu_subu, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_sub, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_daddu, { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_dadd, { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_dsubu, { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_dsub, { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + // Addition/subtraction (immediate) + { InstrId::cpu_addi, { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + { InstrId::cpu_addiu, { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + { InstrId::cpu_daddi, { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + { InstrId::cpu_daddiu, { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + // Bitwise + { InstrId::cpu_and, { BinaryOpType::And64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_or, { BinaryOpType::Or64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_nor, { BinaryOpType::Nor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_xor, { BinaryOpType::Xor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + // Bitwise (immediate) + { InstrId::cpu_andi, { BinaryOpType::And64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, + { InstrId::cpu_ori, { BinaryOpType::Or64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, + { InstrId::cpu_xori, { BinaryOpType::Xor64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, + // Shifts + /* BUG Should mask after (change op to Sll32 and input op to ToU32) */ + { InstrId::cpu_sllv, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, + { InstrId::cpu_dsllv, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, + { InstrId::cpu_srlv, { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, + { InstrId::cpu_dsrlv, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, + /* BUG Should mask after (change op to Sra32 and input op to ToS64) */ + { InstrId::cpu_srav, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, + { InstrId::cpu_dsrav, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, + // Shifts (immediate) + /* BUG Should mask after (change op to Sll32 and input op to ToU32) */ + { InstrId::cpu_sll, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsll, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsll32, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, + { InstrId::cpu_srl, { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsrl, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsrl32, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, + /* BUG should cast after (change op to Sra32 and input op to ToS64) */ + { InstrId::cpu_sra, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsra, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsra32, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, + // Comparisons + { InstrId::cpu_slt, { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::ToS64 }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_sltu, { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::ToU64 }, { Operand::Rs, Operand::Rt }}} }, + // Comparisons (immediate) + { InstrId::cpu_slti, { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + { InstrId::cpu_sltiu, { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + // Float arithmetic + { InstrId::cpu_add_s, { BinaryOpType::AddFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, + { InstrId::cpu_add_d, { BinaryOpType::AddDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, + { InstrId::cpu_sub_s, { BinaryOpType::SubFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, + { InstrId::cpu_sub_d, { BinaryOpType::SubDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, + { InstrId::cpu_mul_s, { BinaryOpType::MulFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, + { InstrId::cpu_mul_d, { BinaryOpType::MulDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, + { InstrId::cpu_div_s, { BinaryOpType::DivFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, + { InstrId::cpu_div_d, { BinaryOpType::DivDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, + // Float comparisons TODO remaining operations and investigate ordered/unordered and default values + { InstrId::cpu_c_lt_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_nge_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_olt_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ult_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_lt_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_nge_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_olt_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ult_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + + { InstrId::cpu_c_le_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ngt_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ole_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ule_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_le_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ngt_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ole_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ule_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + + { InstrId::cpu_c_eq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ueq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ngl_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_seq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_eq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ueq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ngl_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + /* TODO rename to c_seq_d when fixed in rabbitizer */ + { InstrId::cpu_c_deq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + // Loads + { InstrId::cpu_ld, { BinaryOpType::LD, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lw, { BinaryOpType::LW, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lwu, { BinaryOpType::LWU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lh, { BinaryOpType::LH, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lhu, { BinaryOpType::LHU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lb, { BinaryOpType::LB, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lbu, { BinaryOpType::LBU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_ldl, { BinaryOpType::LDL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_ldr, { BinaryOpType::LDR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lwl, { BinaryOpType::LWL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lwr, { BinaryOpType::LWR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lwc1, { BinaryOpType::LW, Operand::FtU32L, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_ldc1, { BinaryOpType::LD, Operand::FtU64, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}, true } }, + }; + + const std::unordered_map conditional_branch_ops { + { InstrId::cpu_beq, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, false }}, + { InstrId::cpu_beql, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, true }}, + { InstrId::cpu_bne, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, false }}, + { InstrId::cpu_bnel, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, true }}, + { InstrId::cpu_bgez, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bgezl, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, + { InstrId::cpu_bgtz, { BinaryOpType::Greater, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bgtzl, { BinaryOpType::Greater, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, + { InstrId::cpu_blez, { BinaryOpType::LessEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, + { InstrId::cpu_blezl, { BinaryOpType::LessEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, + { InstrId::cpu_bltz, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bltzl, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, + { InstrId::cpu_bgezal, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, false }}, + { InstrId::cpu_bgezall, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, true }}, + { InstrId::cpu_bc1f, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bc1fl, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }}, + { InstrId::cpu_bc1t, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bc1tl, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }}, + }; + + const std::unordered_map store_ops { + { InstrId::cpu_sd, { StoreOpType::SD, Operand::Rt }}, + { InstrId::cpu_sdl, { StoreOpType::SDL, Operand::Rt }}, + { InstrId::cpu_sdr, { StoreOpType::SDR, Operand::Rt }}, + { InstrId::cpu_sw, { StoreOpType::SW, Operand::Rt }}, + { InstrId::cpu_swl, { StoreOpType::SWL, Operand::Rt }}, + { InstrId::cpu_swr, { StoreOpType::SWR, Operand::Rt }}, + { InstrId::cpu_sh, { StoreOpType::SH, Operand::Rt }}, + { InstrId::cpu_sb, { StoreOpType::SB, Operand::Rt }}, + { InstrId::cpu_sdc1, { StoreOpType::SDC1, Operand::FtU64 }}, + { InstrId::cpu_swc1, { StoreOpType::SWC1, Operand::FtU32L }}, + }; +} \ No newline at end of file diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 0ac81a9..1bddc39 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -9,9 +9,8 @@ #include "fmt/ostream.h" #include "recomp_port.h" - -using InstrId = rabbitizer::InstrId::UniqueId; -using Cop0Reg = rabbitizer::Registers::Cpu::Cop0; +#include "operations.h" +#include "generator.h" std::string_view ctx_gpr_prefix(int reg) { if (reg != 0) { @@ -20,878 +19,10 @@ std::string_view ctx_gpr_prefix(int reg) { return ""; } -enum class StoreOpType { - SD, - SDL, - SDR, - SW, - SWL, - SWR, - SH, - SB, - SDC1, - SWC1 -}; - -enum class UnaryOpType { - None, - ToS32, - ToU32, - ToS64, - ToU64, - NegateS32, - NegateS64, - Lui, - Mask5, // Mask to 5 bits - Mask6, // Mask to 5 bits - ToInt32, // Functionally equivalent to ToS32, only exists for parity with old codegen - Negate, - AbsFloat, - AbsDouble, - SqrtFloat, - SqrtDouble, - ConvertSFromW, - ConvertWFromS, - ConvertDFromW, - ConvertWFromD, - ConvertDFromS, - ConvertSFromD, - ConvertDFromL, - ConvertLFromD, - ConvertSFromL, - ConvertLFromS, - TruncateWFromS, - TruncateWFromD, - RoundWFromS, - RoundWFromD, - CeilWFromS, - CeilWFromD, - FloorWFromS, - FloorWFromD -}; - -enum class BinaryOpType { - // Addition/subtraction - Add32, - Sub32, - Add64, - Sub64, - // Float arithmetic - AddFloat, - AddDouble, - SubFloat, - SubDouble, - MulFloat, - MulDouble, - DivFloat, - DivDouble, - // Bitwise - And64, - Or64, - Nor64, - Xor64, - Sll32, - Sll64, - Srl32, - Srl64, - Sra32, - Sra64, - // Comparisons - Equal, - NotEqual, - Less, - LessEq, - Greater, - GreaterEq, - // Loads - LD, - LW, - LWU, - LH, - LHU, - LB, - LBU, - LDL, - LDR, - LWL, - LWR, - // Fixed result - True, - False, - - COUNT, -}; - -enum class Operand { - Rd, // GPR - Rs, // GPR - Rt, // GPR - Fd, // FPR - Fs, // FPR - Ft, // FPR - FdDouble, // Double float in fd FPR - FsDouble, // Double float in fs FPR - FtDouble, // Double float in ft FPR - // Raw low 32-bit values of FPRs with handling for mips3 float mode behavior - FdU32L, - FsU32L, - FtU32L, - // Raw high 32-bit values of FPRs with handling for mips3 float mode behavior - FdU32H, - FsU32H, - FtU32H, - // Raw 64-bit values of FPRs - FdU64, - FsU64, - FtU64, - ImmU16, // 16-bit immediate, unsigned - ImmS16, // 16-bit immediate, signed - Sa, // Shift amount - Sa32, // Shift amount plus 32 - Cop1cs, // Coprocessor 1 Condition Signal - Hi, - Lo, - Zero, - - Base = Rs, // Alias for Rs for loads -}; - -struct StoreOp { - StoreOpType type; - Operand value_input; -}; - -struct UnaryOp { - UnaryOpType operation; - Operand output; - Operand input; - // Whether the FR bit needs to be checked for odd float registers for this instruction. - bool check_fr = false; - // Whether the input need to be checked for being NaN. - bool check_nan = false; -}; - -struct BinaryOperands { - // Operation to apply to each operand before applying the binary operation to them. - UnaryOpType operand_operations[2]; - // The source of the input operands. - Operand operands[2]; -}; - -struct BinaryOp { - // The type of binary operation this represents. - BinaryOpType type; - // The output operand. - Operand output; - // The input operands. - BinaryOperands operands; - // Whether the FR bit needs to be checked for odd float registers for this instruction. - bool check_fr = false; - // Whether the inputs need to be checked for being NaN. - bool check_nan = false; -}; - -struct ConditionalBranchOp { - // The type of binary operation to use for this compare - BinaryOpType comparison; - // The input operands. - BinaryOperands operands; - // Whether this jump should link for returns. - bool link; - // Whether this jump has "likely" behavior (doesn't execute the delay slot if skipped). - bool likely; -}; - -const std::unordered_map unary_ops { - { InstrId::cpu_lui, { UnaryOpType::Lui, Operand::Rt, Operand::ImmU16 } }, - { InstrId::cpu_mthi, { UnaryOpType::None, Operand::Hi, Operand::Rs } }, - { InstrId::cpu_mtlo, { UnaryOpType::None, Operand::Lo, Operand::Rs } }, - { InstrId::cpu_mfhi, { UnaryOpType::None, Operand::Rd, Operand::Hi } }, - { InstrId::cpu_mflo, { UnaryOpType::None, Operand::Rd, Operand::Lo } }, - { InstrId::cpu_mtc1, { UnaryOpType::None, Operand::FsU32L, Operand::Rt } }, - { InstrId::cpu_mfc1, { UnaryOpType::ToInt32, Operand::Rt, Operand::FsU32L } }, - // Float operations - { InstrId::cpu_mov_s, { UnaryOpType::None, Operand::Fd, Operand::Fs, true } }, - { InstrId::cpu_mov_d, { UnaryOpType::None, Operand::FdDouble, Operand::FdDouble, true } }, - { InstrId::cpu_neg_s, { UnaryOpType::Negate, Operand::Fd, Operand::Fs, true, true } }, - { InstrId::cpu_neg_d, { UnaryOpType::Negate, Operand::FdDouble, Operand::FsDouble, true, true } }, - { InstrId::cpu_abs_s, { UnaryOpType::AbsFloat, Operand::Fd, Operand::Fs, true, true } }, - { InstrId::cpu_abs_d, { UnaryOpType::AbsDouble, Operand::FdDouble, Operand::FsDouble, true, true } }, - { InstrId::cpu_sqrt_s, { UnaryOpType::SqrtFloat, Operand::Fd, Operand::Fs, true, true } }, - { InstrId::cpu_sqrt_d, { UnaryOpType::SqrtDouble, Operand::FdDouble, Operand::FsDouble, true, true } }, - { InstrId::cpu_cvt_s_w, { UnaryOpType::ConvertSFromW, Operand::Fd, Operand::FsU32L, true } }, - { InstrId::cpu_cvt_w_s, { UnaryOpType::ConvertWFromS, Operand::FdU32L, Operand::Fs, true } }, - { InstrId::cpu_cvt_d_w, { UnaryOpType::ConvertDFromW, Operand::FdDouble, Operand::FsU32L, true } }, - { InstrId::cpu_cvt_w_d, { UnaryOpType::ConvertWFromD, Operand::FdU32L, Operand::FsDouble, true } }, - { InstrId::cpu_cvt_d_s, { UnaryOpType::ConvertDFromS, Operand::FdDouble, Operand::Fs, true, true } }, - { InstrId::cpu_cvt_s_d, { UnaryOpType::ConvertSFromD, Operand::Fd, Operand::FsDouble, true, true } }, - { InstrId::cpu_cvt_d_l, { UnaryOpType::ConvertDFromL, Operand::FdDouble, Operand::FsU64, true } }, - { InstrId::cpu_cvt_l_d, { UnaryOpType::ConvertLFromD, Operand::FdU64, Operand::FsDouble, true, true } }, - { InstrId::cpu_cvt_s_l, { UnaryOpType::ConvertSFromL, Operand::Fd, Operand::FsU64, true } }, - { InstrId::cpu_cvt_l_s, { UnaryOpType::ConvertLFromS, Operand::FdU64, Operand::Fs, true, true } }, - { InstrId::cpu_trunc_w_s, { UnaryOpType::TruncateWFromS, Operand::FdU32L, Operand::Fs, true } }, - { InstrId::cpu_trunc_w_d, { UnaryOpType::TruncateWFromD, Operand::FdU32L, Operand::FsDouble, true } }, - { InstrId::cpu_round_w_s, { UnaryOpType::RoundWFromS, Operand::FdU32L, Operand::Fs, true } }, - { InstrId::cpu_round_w_d, { UnaryOpType::RoundWFromD, Operand::FdU32L, Operand::FsDouble, true } }, - { InstrId::cpu_ceil_w_s, { UnaryOpType::CeilWFromS, Operand::FdU32L, Operand::Fs, true } }, - { InstrId::cpu_ceil_w_d, { UnaryOpType::CeilWFromD, Operand::FdU32L, Operand::FsDouble, true } }, - { InstrId::cpu_floor_w_s, { UnaryOpType::FloorWFromS, Operand::FdU32L, Operand::Fs, true } }, - { InstrId::cpu_floor_w_d, { UnaryOpType::FloorWFromD, Operand::FdU32L, Operand::FsDouble, true } }, -}; - -// TODO fix usage of check_nan -const std::unordered_map binary_ops { - // Addition/subtraction - { InstrId::cpu_addu, { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_add, { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_negu, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, // pseudo op for subu - { InstrId::cpu_subu, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_sub, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_daddu, { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_dadd, { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_dsubu, { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_dsub, { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - // Addition/subtraction (immediate) - { InstrId::cpu_addi, { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, - { InstrId::cpu_addiu, { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, - { InstrId::cpu_daddi, { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, - { InstrId::cpu_daddiu, { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, - // Bitwise - { InstrId::cpu_and, { BinaryOpType::And64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_or, { BinaryOpType::Or64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_nor, { BinaryOpType::Nor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_xor, { BinaryOpType::Xor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, - // Bitwise (immediate) - { InstrId::cpu_andi, { BinaryOpType::And64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, - { InstrId::cpu_ori, { BinaryOpType::Or64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, - { InstrId::cpu_xori, { BinaryOpType::Xor64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, - // Shifts - /* BUG Should mask after (change op to Sll32 and input op to ToU32) */ - { InstrId::cpu_sllv, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, - { InstrId::cpu_dsllv, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, - { InstrId::cpu_srlv, { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, - { InstrId::cpu_dsrlv, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, - /* BUG Should mask after (change op to Sra32 and input op to ToS64) */ - { InstrId::cpu_srav, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, - { InstrId::cpu_dsrav, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, - // Shifts (immediate) - /* BUG Should mask after (change op to Sll32 and input op to ToU32) */ - { InstrId::cpu_sll, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, - { InstrId::cpu_dsll, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, - { InstrId::cpu_dsll32, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, - { InstrId::cpu_srl, { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, - { InstrId::cpu_dsrl, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, - { InstrId::cpu_dsrl32, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, - /* BUG should cast after (change op to Sra32 and input op to ToS64) */ - { InstrId::cpu_sra, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, - { InstrId::cpu_dsra, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, - { InstrId::cpu_dsra32, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, - // Comparisons - { InstrId::cpu_slt, { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::ToS64 }, { Operand::Rs, Operand::Rt }}} }, - { InstrId::cpu_sltu, { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::ToU64 }, { Operand::Rs, Operand::Rt }}} }, - // Comparisons (immediate) - { InstrId::cpu_slti, { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, - { InstrId::cpu_sltiu, { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, - // Float arithmetic - { InstrId::cpu_add_s, { BinaryOpType::AddFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, - { InstrId::cpu_add_d, { BinaryOpType::AddDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, - { InstrId::cpu_sub_s, { BinaryOpType::SubFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, - { InstrId::cpu_sub_d, { BinaryOpType::SubDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, - { InstrId::cpu_mul_s, { BinaryOpType::MulFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, - { InstrId::cpu_mul_d, { BinaryOpType::MulDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, - { InstrId::cpu_div_s, { BinaryOpType::DivFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, - { InstrId::cpu_div_d, { BinaryOpType::DivDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, - // Float comparisons TODO remaining operations and investigate ordered/unordered and default values - { InstrId::cpu_c_lt_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_nge_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_olt_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_ult_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_lt_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - { InstrId::cpu_c_nge_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - { InstrId::cpu_c_olt_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - { InstrId::cpu_c_ult_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - - { InstrId::cpu_c_le_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_ngt_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_ole_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_ule_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_le_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - { InstrId::cpu_c_ngt_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - { InstrId::cpu_c_ole_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - { InstrId::cpu_c_ule_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - - { InstrId::cpu_c_eq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_ueq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_ngl_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_seq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, - { InstrId::cpu_c_eq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - { InstrId::cpu_c_ueq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - { InstrId::cpu_c_ngl_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - /* TODO rename to c_seq_d when fixed in rabbitizer */ - { InstrId::cpu_c_deq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, - // Loads - { InstrId::cpu_ld, { BinaryOpType::LD, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_lw, { BinaryOpType::LW, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_lwu, { BinaryOpType::LWU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_lh, { BinaryOpType::LH, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_lhu, { BinaryOpType::LHU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_lb, { BinaryOpType::LB, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_lbu, { BinaryOpType::LBU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_ldl, { BinaryOpType::LDL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_ldr, { BinaryOpType::LDR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_lwl, { BinaryOpType::LWL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_lwr, { BinaryOpType::LWR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_lwc1, { BinaryOpType::LW, Operand::FtU32L, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, - { InstrId::cpu_ldc1, { BinaryOpType::LD, Operand::FtU64, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}, true } }, -}; - -const std::unordered_map conditional_branch_ops { - { InstrId::cpu_beq, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, false }}, - { InstrId::cpu_beql, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, true }}, - { InstrId::cpu_bne, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, false }}, - { InstrId::cpu_bnel, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, true }}, - { InstrId::cpu_bgez, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, - { InstrId::cpu_bgezl, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, - { InstrId::cpu_bgtz, { BinaryOpType::Greater, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, - { InstrId::cpu_bgtzl, { BinaryOpType::Greater, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, - { InstrId::cpu_blez, { BinaryOpType::LessEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, - { InstrId::cpu_blezl, { BinaryOpType::LessEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, - { InstrId::cpu_bltz, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, - { InstrId::cpu_bltzl, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, - { InstrId::cpu_bgezal, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, false }}, - { InstrId::cpu_bgezall, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, true }}, - { InstrId::cpu_bc1f, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }}, - { InstrId::cpu_bc1fl, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }}, - { InstrId::cpu_bc1t, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }}, - { InstrId::cpu_bc1tl, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }}, -}; - -const std::unordered_map store_ops { - { InstrId::cpu_sd, { StoreOpType::SD, Operand::Rt }}, - { InstrId::cpu_sdl, { StoreOpType::SDL, Operand::Rt }}, - { InstrId::cpu_sdr, { StoreOpType::SDR, Operand::Rt }}, - { InstrId::cpu_sw, { StoreOpType::SW, Operand::Rt }}, - { InstrId::cpu_swl, { StoreOpType::SWL, Operand::Rt }}, - { InstrId::cpu_swr, { StoreOpType::SWR, Operand::Rt }}, - { InstrId::cpu_sh, { StoreOpType::SH, Operand::Rt }}, - { InstrId::cpu_sb, { StoreOpType::SB, Operand::Rt }}, - { InstrId::cpu_sdc1, { StoreOpType::SDC1, Operand::FtU64 }}, - { InstrId::cpu_swc1, { StoreOpType::SWC1, Operand::FtU32L }}, -}; - -struct InstructionContext { - int rd; - int rs; - int rt; - int sa; - - int fd; - int fs; - int ft; - - int cop1_cs; - - uint16_t imm16; - - RecompPort::RelocType reloc_type; - uint32_t reloc_section_index; - uint32_t reloc_target_section_offset; -}; - -class CGenerator { -public: - CGenerator() = default; - void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const; - void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const; - void process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const; - void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const; - void emit_branch_close(std::ostream& output_file) const; - void emit_check_fr(std::ostream& output_file, int fpr) const; - void emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const; -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; -}; - -struct BinaryOpFields { std::string func_string; std::string infix_string; }; - -std::vector c_op_fields = []() { - std::vector ret{}; - ret.resize(static_cast(BinaryOpType::COUNT)); - std::vector ops_setup{}; - ops_setup.resize(static_cast(BinaryOpType::COUNT)); - - auto setup_op = [&ret, &ops_setup](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(BinaryOpType::Add32, "ADD32", ""); - setup_op(BinaryOpType::Sub32, "SUB32", ""); - setup_op(BinaryOpType::Add64, "", "+"); - setup_op(BinaryOpType::Sub64, "", "-"); - setup_op(BinaryOpType::And64, "", "&"); - setup_op(BinaryOpType::AddFloat, "", "+"); - setup_op(BinaryOpType::AddDouble, "", "+"); - setup_op(BinaryOpType::SubFloat, "", "-"); - setup_op(BinaryOpType::SubDouble, "", "-"); - setup_op(BinaryOpType::MulFloat, "MUL_S", ""); - setup_op(BinaryOpType::MulDouble, "MUL_D", ""); - setup_op(BinaryOpType::DivFloat, "DIV_S", ""); - setup_op(BinaryOpType::DivDouble, "DIV_D", ""); - setup_op(BinaryOpType::Or64, "", "|"); - setup_op(BinaryOpType::Nor64, "~", "|"); - setup_op(BinaryOpType::Xor64, "", "^"); - setup_op(BinaryOpType::Sll32, "S32", "<<"); - setup_op(BinaryOpType::Sll64, "", "<<"); - setup_op(BinaryOpType::Srl32, "S32", ">>"); - setup_op(BinaryOpType::Srl64, "", ">>"); - setup_op(BinaryOpType::Sra32, "S32", ">>"); // Arithmetic aspect will be taken care of by unary op for first operand. - setup_op(BinaryOpType::Sra64, "", ">>"); // Arithmetic aspect will be taken care of by unary op for first operand. - setup_op(BinaryOpType::Equal, "", "=="); - setup_op(BinaryOpType::NotEqual, "", "!="); - setup_op(BinaryOpType::Less, "", "<"); - setup_op(BinaryOpType::LessEq, "", "<="); - setup_op(BinaryOpType::Greater, "", ">"); - setup_op(BinaryOpType::GreaterEq, "", ">="); - setup_op(BinaryOpType::LD, "LD", ""); - setup_op(BinaryOpType::LW, "MEM_W", ""); - setup_op(BinaryOpType::LWU, "MEM_WU", ""); - setup_op(BinaryOpType::LH, "MEM_H", ""); - setup_op(BinaryOpType::LHU, "MEM_HU", ""); - setup_op(BinaryOpType::LB, "MEM_B", ""); - setup_op(BinaryOpType::LBU, "MEM_BU", ""); - setup_op(BinaryOpType::LDL, "do_ldl", ""); - setup_op(BinaryOpType::LDR, "do_ldr", ""); - setup_op(BinaryOpType::LWL, "do_lwl", ""); - setup_op(BinaryOpType::LWR, "do_lwr", ""); - setup_op(BinaryOpType::True, "", ""); - setup_op(BinaryOpType::False, "", ""); - - // Ensure every operation has been setup. - for (char is_set : ops_setup) { - assert(is_set && "Operation has not been setup!"); - } - - return ret; -}(); - -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) { - return fmt::format("ctx->f{}.fl", fpr_index); -} - -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) { - if (fpr_index & 1) { - return fmt::format("ctx->f_odd[({} - 1) * 2]", fpr_index); - } - else { - return fmt::format("ctx->f{}.u32l", fpr_index); - } -} - -std::string fpr_u64_to_string(int fpr_index) { - return fmt::format("ctx->f{}.u64", fpr_index); -} - -std::string unsigned_reloc(const InstructionContext& context) { - switch (context.reloc_type) { - case RecompPort::RelocType::R_MIPS_HI16: - return fmt::format("RELOC_HI16({}, {:#X})", context.reloc_section_index, context.reloc_target_section_offset); - case RecompPort::RelocType::R_MIPS_LO16: - return fmt::format("RELOC_LO16({}, {:#X})", 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))); - } -} - -std::string signed_reloc(const InstructionContext& context) { - return "(int16_t)" + unsigned_reloc(context); -} - -void CGenerator::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 != RecompPort::RelocType::R_MIPS_NONE) { - operand_string = unsigned_reloc(context); - } - else { - operand_string = fmt::format("{:#X}", context.imm16); - } - break; - case Operand::ImmS16: - if (context.reloc_type != RecompPort::RelocType::R_MIPS_NONE) { - operand_string = signed_reloc(context); - } - else { - operand_string = fmt::format("{:#X}", (int16_t)context.imm16); - } - break; - case Operand::Sa: - operand_string = std::to_string(context.sa); - break; - case Operand::Sa32: - operand_string = fmt::format("({} + 32)", 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 = "0"; - break; - } - switch (operation) { - case UnaryOpType::None: - break; - case UnaryOpType::ToS32: - 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(" + operand_string + " << 16)"; - break; - case UnaryOpType::Mask5: - operand_string = "(" + operand_string + " & 31)"; - break; - case UnaryOpType::Mask6: - operand_string = "(" + operand_string + " & 63)"; - break; - case UnaryOpType::ToInt32: - operand_string = "(int32_t)" + operand_string; - break; - case UnaryOpType::Negate: - operand_string = "-" + operand_string; - break; - case UnaryOpType::AbsFloat: - operand_string = "fabsf(" + operand_string + ")"; - break; - case UnaryOpType::AbsDouble: - operand_string = "fabs(" + 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 + ")"; - break; - case UnaryOpType::ConvertDFromW: - operand_string = "CVT_D_W(" + operand_string + ")"; - break; - case UnaryOpType::ConvertWFromD: - operand_string = "CVT_W_D(" + operand_string + ")"; - 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 = "lroundf(" + operand_string + ")"; - break; - case UnaryOpType::RoundWFromD: - operand_string = "lround(" + operand_string + ")"; - break; - case UnaryOpType::CeilWFromS: - operand_string = "S32(ceilf(" + operand_string + "))"; - break; - case UnaryOpType::CeilWFromD: - operand_string = "S32(ceil(" + operand_string + "))"; - break; - case UnaryOpType::FloorWFromS: - operand_string = "S32(floorf(" + operand_string + "))"; - break; - case UnaryOpType::FloorWFromD: - operand_string = "S32(floor(" + operand_string + "))"; - break; - } -} - -void CGenerator::get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const { - func_string = c_op_fields[static_cast(op_type)].func_string; - infix_string = c_op_fields[static_cast(op_type)].infix_string; -} - -void CGenerator::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); - - // These cases aren't strictly necessary and are just here for parity with the old recompiler output. - if (type == BinaryOpType::Less && !((operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) || (operands.operands[0] == Operand::Fs || operands.operands[0] == Operand::FsDouble))) { - expr_string = fmt::format("{} {} {} ? 1 : 0", input_a, infix_string, input_b); - } - else if (type == BinaryOpType::Equal && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) { - expr_string = input_a; - } - else if (type == BinaryOpType::NotEqual && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) { - expr_string = "!" + input_a; - } - // End unnecessary cases. - - // TODO encode these ops to avoid needing special handling. - else if (type == BinaryOpType::LWL || type == BinaryOpType::LWR || type == BinaryOpType::LDL || type == BinaryOpType::LDR) { - expr_string = fmt::format("{}(rdram, {}, {}, {})", func_string, output, input_a, input_b); - } - else 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 = "1"; - } - else if (type == BinaryOpType::False) { - expr_string = "0"; - } - assert(false && "Binary operation must have either a function or infix!"); - } -} - -void CGenerator::emit_branch_condition(std::ostream& output_file, 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 ({}) {{\n", expr_string); -} - -void CGenerator::emit_branch_close(std::ostream& output_file) const { - fmt::print(output_file, " }}\n"); -} - -void CGenerator::emit_check_fr(std::ostream& output_file, int fpr) const { - fmt::print(output_file, "CHECK_FR(ctx, {});\n ", fpr); -} - -void CGenerator::emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const { - fmt::print(output_file, "NAN_CHECK(ctx->f{}.{}); ", fpr, is_double ? "d" : "fl"); -} - -void CGenerator::process_binary_op(std::ostream& output_file, 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); - fmt::print(output_file, "{} = {};\n", output, expression); -} - -void CGenerator::process_unary_op(std::ostream& output_file, 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 CGenerator::process_store_op(std::ostream& output_file, 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); - - enum class StoreSyntax { - Func, - FuncWithRdram, - Assignment, - }; - - StoreSyntax syntax; - std::string func_text; - - switch (op.type) { - case StoreOpType::SD: - func_text = "SD"; - syntax = StoreSyntax::Func; - break; - case StoreOpType::SDL: - func_text = "do_sdl"; - syntax = StoreSyntax::FuncWithRdram; - break; - case StoreOpType::SDR: - func_text = "do_sdr"; - syntax = StoreSyntax::FuncWithRdram; - break; - case StoreOpType::SW: - func_text = "MEM_W"; - syntax = StoreSyntax::Assignment; - break; - case StoreOpType::SWL: - func_text = "do_swl"; - syntax = StoreSyntax::FuncWithRdram; - break; - case StoreOpType::SWR: - func_text = "do_swr"; - syntax = StoreSyntax::FuncWithRdram; - break; - case StoreOpType::SH: - func_text = "MEM_H"; - syntax = StoreSyntax::Assignment; - break; - case StoreOpType::SB: - func_text = "MEM_B"; - syntax = StoreSyntax::Assignment; - break; - case StoreOpType::SDC1: - func_text = "SD"; - syntax = StoreSyntax::Func; - break; - case StoreOpType::SWC1: - func_text = "MEM_W"; - syntax = StoreSyntax::Assignment; - break; - default: - throw std::runtime_error("Unhandled store op"); - } - - switch (syntax) { - case StoreSyntax::Func: - fmt::print(output_file, "{}({}, {}, {});\n", func_text, value_input, imm_str, base_str); - break; - case StoreSyntax::FuncWithRdram: - fmt::print(output_file, "{}(rdram, {}, {}, {});\n", func_text, imm_str, base_str, value_input); - break; - case StoreSyntax::Assignment: - fmt::print(output_file, "{}({}, {}) = {};\n", func_text, imm_str, base_str, value_input); - break; - } -} - // Major TODO, this function grew very organically and needs to be cleaned up. Ideally, it'll get split up into some sort of lookup table grouped by similar instruction types. bool process_instruction(const RecompPort::Context& context, const RecompPort::Config& config, const RecompPort::Function& func, const RecompPort::FunctionStats& stats, const std::unordered_set& skipped_insns, size_t instr_index, const std::vector& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, std::span> static_funcs_out) { + using namespace RecompPort; + const auto& section = context.sections[func.section_index]; const auto& instr = instructions[instr_index]; needs_link_branch = false;