11 Commits

6 changed files with 1077 additions and 670 deletions

View File

@@ -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

55
include/generator.h Normal file
View File

@@ -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

200
include/operations.h Normal file
View File

@@ -0,0 +1,200 @@
#ifndef __OPERATIONS_H__
#define __OPERATIONS_H__
#include <unordered_map>
#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<InstrId, UnaryOp> unary_ops;
extern const std::unordered_map<InstrId, BinaryOp> binary_ops;
extern const std::unordered_map<InstrId, ConditionalBranchOp> conditional_branch_ops;
extern const std::unordered_map<InstrId, StoreOp> store_ops;
}
#endif

483
src/cgenerator.cpp Normal file
View File

@@ -0,0 +1,483 @@
#include <cassert>
#include <fstream>
#include "fmt/format.h"
#include "fmt/ostream.h"
#include "generator.h"
struct BinaryOpFields { std::string func_string; std::string infix_string; };
std::vector<BinaryOpFields> c_op_fields = []() {
std::vector<BinaryOpFields> ret{};
ret.resize(static_cast<size_t>(RecompPort::BinaryOpType::COUNT));
std::vector<char> ops_setup{};
ops_setup.resize(static_cast<size_t>(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<size_t>(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<int>(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<size_t>(op_type)].func_string;
infix_string = c_op_fields[static_cast<size_t>(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;
}
}

180
src/operations.cpp Normal file
View File

@@ -0,0 +1,180 @@
#include "operations.h"
namespace RecompPort {
const std::unordered_map<InstrId, UnaryOp> 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::FsDouble, 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<InstrId, BinaryOp> 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<InstrId, ConditionalBranchOp> 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<InstrId, StoreOp> 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 }},
};
}

View File

@@ -1,15 +1,16 @@
#include <vector>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include "rabbitizer.hpp"
#include "fmt/format.h"
#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,6 +21,8 @@ std::string_view ctx_gpr_prefix(int reg) {
// 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<uint32_t>& skipped_insns, size_t instr_index, const std::vector<rabbitizer::InstructionCpu>& 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<std::vector<uint32_t>> static_funcs_out) {
using namespace RecompPort;
const auto& section = context.sections[func.section_index];
const auto& instr = instructions[instr_index];
needs_link_branch = false;
@@ -51,8 +54,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
return true;
}
bool at_reloc = false;
bool reloc_handled = false;
RecompPort::RelocType reloc_type = RecompPort::RelocType::R_MIPS_NONE;
uint32_t reloc_section = 0;
uint32_t reloc_target_section_offset = 0;
@@ -82,8 +83,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
reloc_target_section_offset = reloc.section_offset;
// Ignore all relocs that aren't HI16 or LO16.
if (reloc_type == RecompPort::RelocType::R_MIPS_HI16 || reloc_type == RecompPort::RelocType::R_MIPS_LO16 || reloc_type == RecompPort::RelocType::R_MIPS_26) {
at_reloc = true;
if (reloc.reference_symbol) {
reloc_reference_symbol = reloc.symbol_index;
static RecompPort::ReferenceSection dummy_section{
@@ -93,8 +92,8 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
.relocatable = false
};
const auto& reloc_reference_section = reloc.target_section == RecompPort::SectionAbsolute ? dummy_section : context.reference_sections[reloc.target_section];
if (!reloc_reference_section.relocatable) {
at_reloc = false;
// Resolve HI16 and LO16 reference symbol relocs to non-relocatable sections by patching the instruction immediate.
if (!reloc_reference_section.relocatable && (reloc_type == RecompPort::RelocType::R_MIPS_HI16 || reloc_type == RecompPort::RelocType::R_MIPS_LO16)) {
uint32_t full_immediate = reloc.section_offset + reloc_reference_section.ram_addr;
if (reloc_type == RecompPort::RelocType::R_MIPS_HI16) {
@@ -103,6 +102,10 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
else if (reloc_type == RecompPort::RelocType::R_MIPS_LO16) {
imm = full_immediate & 0xFFFF;
}
// The reloc has been processed, so delete it to none to prevent it getting processed a second time during instruction code generation.
reloc_type = RecompPort::RelocType::R_MIPS_NONE;
reloc_reference_symbol = (size_t)-1;
}
}
}
@@ -122,11 +125,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
fmt::print(output_file, ";\n");
};
auto print_branch_condition = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
fmt::vprint(output_file, fmt_str, fmt::make_format_args(args...));
fmt::print(output_file, " ");
};
auto print_unconditional_branch = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
if (instr_index < instructions.size() - 1) {
bool dummy_needs_link_branch;
@@ -246,7 +244,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
if (branch_target < func.vram || branch_target >= func_vram_end) {
// FIXME: how to deal with static functions?
if (context.functions_by_vram.find(branch_target) != context.functions_by_vram.end()) {
fmt::print(output_file, "{{\n ");
fmt::print("Tail call in {} to 0x{:08X}\n", func.name, branch_target);
print_func_call(branch_target, false, true);
print_line(" return");
@@ -257,7 +254,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
fmt::print(stderr, "[Warn] Function {} is branching outside of the function (to 0x{:08X})\n", func.name, branch_target);
}
fmt::print(output_file, "{{\n ");
if (instr_index < instructions.size() - 1) {
bool dummy_needs_link_branch;
bool dummy_is_branch_likely;
@@ -269,12 +265,10 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out);
}
fmt::print(output_file, " ");
fmt::print(output_file, "goto L_{:08X}", branch_target);
fmt::print(output_file, " goto L_{:08X};\n", branch_target);
if (needs_link_branch) {
fmt::print(output_file, ";\n goto after_{}", link_branch_index);
fmt::print(output_file, " goto after_{};\n", link_branch_index);
}
fmt::print(output_file, ";\n }}\n");
};
if (indent) {
@@ -293,32 +287,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
int cop1_cs = (int)instr.Get_cop1cs();
std::string unsigned_imm_string;
std::string signed_imm_string;
if (!at_reloc) {
unsigned_imm_string = fmt::format("{:#X}", imm);
signed_imm_string = fmt::format("{:#X}", (int16_t)imm);
} else {
switch (reloc_type) {
case RecompPort::RelocType::R_MIPS_HI16:
unsigned_imm_string = fmt::format("RELOC_HI16({}, {:#X})", reloc_section, reloc_target_section_offset);
signed_imm_string = "(int16_t)" + unsigned_imm_string;
reloc_handled = true;
break;
case RecompPort::RelocType::R_MIPS_LO16:
unsigned_imm_string = fmt::format("RELOC_LO16({}, {:#X})", reloc_section, reloc_target_section_offset);
signed_imm_string = "(int16_t)" + unsigned_imm_string;
reloc_handled = true;
break;
case RecompPort::RelocType::R_MIPS_26:
// Nothing to do here, this will be handled by print_func_call.
reloc_handled = true;
break;
default:
throw std::runtime_error(fmt::format("Unexpected reloc type {} in {}\n", static_cast<int>(reloc_type), func.name));
}
}
bool handled = true;
switch (instr.getUniqueId()) {
case InstrId::cpu_nop:
@@ -352,9 +321,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
break;
}
// Arithmetic
case InstrId::cpu_lui:
print_line("{}{} = S32({} << 16)", ctx_gpr_prefix(rt), rt, unsigned_imm_string);
break;
case InstrId::cpu_add:
case InstrId::cpu_addu:
{
@@ -369,101 +335,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
print_line("gpr jr_addend_{:08X} = {}{}", cur_jtbl.jr_vram, ctx_gpr_prefix(cur_jtbl.addend_reg), cur_jtbl.addend_reg);
}
}
print_line("{}{} = ADD32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_daddu:
print_line("{}{} = {}{} + {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_negu: // pseudo instruction for subu x, 0, y
case InstrId::cpu_sub:
case InstrId::cpu_subu:
print_line("{}{} = SUB32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_addi:
case InstrId::cpu_addiu:
print_line("{}{} = ADD32({}{}, {})", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string);
break;
case InstrId::cpu_daddi:
case InstrId::cpu_daddiu:
print_line("{}{} = {}{} + {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string);
break;
case InstrId::cpu_and:
print_line("{}{} = {}{} & {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_andi:
print_line("{}{} = {}{} & {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, unsigned_imm_string);
break;
case InstrId::cpu_or:
print_line("{}{} = {}{} | {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_ori:
print_line("{}{} = {}{} | {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, unsigned_imm_string);
break;
case InstrId::cpu_nor:
print_line("{}{} = ~({}{} | {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_xor:
print_line("{}{} = {}{} ^ {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_xori:
print_line("{}{} = {}{} ^ {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, unsigned_imm_string);
break;
case InstrId::cpu_sll:
print_line("{}{} = S32({}{}) << {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_dsll:
print_line("{}{} = {}{} << {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_dsll32:
print_line("{}{} = ((gpr)({}{})) << ({} + 32)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_sllv:
print_line("{}{} = S32({}{}) << ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_dsllv:
print_line("{}{} = {}{} << ({}{} & 63)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_sra:
print_line("{}{} = S32({}{}) >> {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_dsra:
print_line("{}{} = SIGNED({}{}) >> {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_dsra32:
print_line("{}{} = SIGNED({}{}) >> ({} + 32)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_srav:
print_line("{}{} = S32({}{}) >> ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_dsrav:
print_line("{}{} = SIGNED({}{}) >> ({}{} & 63)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_srl:
print_line("{}{} = S32(U32({}{}) >> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_dsrl:
print_line("{}{} = {}{} >> {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_dsrl32:
print_line("{}{} = ((gpr)({}{})) >> ({} + 32)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_srlv:
print_line("{}{} = S32(U32({}{}) >> ({}{} & 31))", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_dsrlv:
print_line("{}{} = {}{} >> ({}{} & 63))", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_slt:
print_line("{}{} = SIGNED({}{}) < SIGNED({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_slti:
print_line("{}{} = SIGNED({}{}) < {} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string);
break;
case InstrId::cpu_sltu:
print_line("{}{} = {}{} < {}{} ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_sltiu:
print_line("{}{} = {}{} < {} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string);
break;
case InstrId::cpu_mult:
print_line("result = S64(S32({}{})) * S64(S32({}{})); lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
@@ -490,91 +361,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_ddivu:
print_line("DDIVU(U64({}{}), U64({}{}), &lo, &hi)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_mflo:
print_line("{}{} = lo", ctx_gpr_prefix(rd), rd);
break;
case InstrId::cpu_mfhi:
print_line("{}{} = hi", ctx_gpr_prefix(rd), rd);
break;
case InstrId::cpu_mtlo:
print_line("lo = {}{}", ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_mthi:
print_line("hi = {}{}", ctx_gpr_prefix(rs), rs);
break;
// Loads
case InstrId::cpu_ld:
print_line("{}{} = LD({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lw:
print_line("{}{} = MEM_W({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lh:
print_line("{}{} = MEM_H({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lb:
print_line("{}{} = MEM_B({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lhu:
print_line("{}{} = MEM_HU({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lbu:
print_line("{}{} = MEM_BU({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
break;
// Stores
case InstrId::cpu_sd:
print_line("SD({}{}, {}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_sw:
print_line("MEM_W({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_sh:
print_line("MEM_H({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_sb:
print_line("MEM_B({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
// Unaligned loads
// examples:
// reg = 11111111 01234567
// mem @ x = 89ABCDEF
// LWL x + 0 -> FFFFFFFF 89ABCDEF
// LWL x + 1 -> FFFFFFFF ABCDEF67
// LWL x + 2 -> FFFFFFFF CDEF4567
// LWL x + 3 -> FFFFFFFF EF234567
// LWR x + 0 -> 00000000 01234589
// LWR x + 1 -> 00000000 012389AB
// LWR x + 2 -> 00000000 0189ABCD
// LWR x + 3 -> FFFFFFFF 89ABCDEF
case InstrId::cpu_lwl:
print_line("{}{} = do_lwl(rdram, {}{}, {}, {}{})", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lwr:
print_line("{}{} = do_lwr(rdram, {}{}, {}, {}{})", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
break;
// Unaligned stores
// examples:
// reg = 11111111 01234567
// mem @ x = 89ABCDEF
// SWL x + 0 -> 01234567
// SWL x + 1 -> 89012345
// SWL x + 2 -> 89AB0123
// SWL x + 3 -> 89ABCD01
// SWR x + 0 -> 67ABCDEF
// SWR x + 1 -> 4567CDEF
// SWR x + 2 -> 234567EF
// SWR x + 3 -> 01234567
case InstrId::cpu_swl:
print_line("do_swl(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_swr:
print_line("do_swr(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
// Branches
case InstrId::cpu_jal:
print_func_call(instr.getBranchVramGeneric());
@@ -681,411 +467,11 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
// syscalls don't link, so treat it like a tail call
print_line("return");
break;
case InstrId::cpu_bnel:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bne:
print_indent();
print_branch_condition("if ({}{} != {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
print_branch((uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_beql:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_beq:
print_indent();
print_branch_condition("if ({}{} == {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
print_branch((uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_bgezl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bgez:
print_indent();
print_branch_condition("if (SIGNED({}{}) >= 0)", ctx_gpr_prefix(rs), rs);
print_branch((uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_bgtzl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bgtz:
print_indent();
print_branch_condition("if (SIGNED({}{}) > 0)", ctx_gpr_prefix(rs), rs);
print_branch((uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_blezl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_blez:
print_indent();
print_branch_condition("if (SIGNED({}{}) <= 0)", ctx_gpr_prefix(rs), rs);
print_branch((uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_bltzl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bltz:
print_indent();
print_branch_condition("if (SIGNED({}{}) < 0)", ctx_gpr_prefix(rs), rs);
print_branch((uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_break:
print_line("do_break({})", instr_vram);
break;
case InstrId::cpu_bgezall:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bgezal:
print_indent();
print_branch_condition("if (SIGNED({}{}) >= 0) {{", ctx_gpr_prefix(rs), rs);
print_func_call(instr.getBranchVramGeneric());
print_line("}}");
break;
// Cop1 loads/stores
case InstrId::cpu_mtc1:
if ((fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.u32l = {}{}", fs, ctx_gpr_prefix(rt), rt);
}
else {
// odd fpr
print_line("ctx->f_odd[({} - 1) * 2] = {}{}", fs, ctx_gpr_prefix(rt), rt);
}
break;
case InstrId::cpu_mfc1:
if ((fs & 1) == 0) {
// even fpr
print_line("{}{} = (int32_t)ctx->f{}.u32l", ctx_gpr_prefix(rt), rt, fs);
} else {
// odd fpr
print_line("{}{} = (int32_t)ctx->f_odd[({} - 1) * 2]", ctx_gpr_prefix(rt), rt, fs);
}
break;
//case InstrId::cpu_dmfc1:
// if ((fs & 1) == 0) {
// // even fpr
// print_line("{}{} = ctx->f{}.u64", ctx_gpr_prefix(rt), rt, fs);
// } else {
// fmt::print(stderr, "Invalid operand for dmfc1: f{}\n", fs);
// return false;
// }
// break;
case InstrId::cpu_lwc1:
if ((ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.u32l = MEM_W({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base);
} else {
// odd fpr
print_line("ctx->f_odd[({} - 1) * 2] = MEM_W({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base);
}
break;
case InstrId::cpu_ldc1:
print_line("CHECK_FR(ctx, {})", ft);
print_line("ctx->f{}.u64 = LD({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_swc1:
if ((ft & 1) == 0) {
// even fpr
print_line("MEM_W({}, {}{}) = ctx->f{}.u32l", signed_imm_string, ctx_gpr_prefix(base), base, ft);
} else {
// odd fpr
print_line("MEM_W({}, {}{}) = ctx->f_odd[({} - 1) * 2]", signed_imm_string, ctx_gpr_prefix(base), base, ft);
}
break;
case InstrId::cpu_sdc1:
print_line("CHECK_FR(ctx, {})", ft);
print_line("SD(ctx->f{}.u64, {}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base);
break;
// Cop1 compares
// TODO allow NaN in ordered and unordered float comparisons, default to a compare result of 1 for ordered and 0 for unordered if a NaN is present
case InstrId::cpu_c_lt_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl < ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_olt_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl < ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_ult_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl < ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_lt_d:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d < ctx->f{}.d", fs, ft);
break;
case InstrId::cpu_c_olt_d:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d < ctx->f{}.d", fs, ft);
break;
case InstrId::cpu_c_ult_d:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d < ctx->f{}.d", fs, ft);
break;
case InstrId::cpu_c_le_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_ole_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_ule_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_le_d:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft);
break;
case InstrId::cpu_c_ole_d:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft);
break;
case InstrId::cpu_c_ule_d:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft);
break;
case InstrId::cpu_c_eq_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_ueq_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_ngl_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_seq_s:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft);
break;
case InstrId::cpu_c_eq_d:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft);
break;
case InstrId::cpu_c_ueq_d:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft);
break;
case InstrId::cpu_c_ngl_d:
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft);
break;
case InstrId::cpu_c_deq_d: // TODO rename to c_seq_d when fixed in rabbitizer
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft);
break;
// Cop1 branches
case InstrId::cpu_bc1tl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bc1t:
print_indent();
print_branch_condition("if (c1cs)", ctx_gpr_prefix(rs), rs);
print_branch((uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_bc1fl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bc1f:
print_indent();
print_branch_condition("if (!c1cs)", ctx_gpr_prefix(rs), rs);
print_branch((uint32_t)instr.getBranchVramGeneric());
break;
// Cop1 arithmetic
case InstrId::cpu_mov_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.fl = ctx->f{}.fl", fd, fs);
break;
case InstrId::cpu_mov_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.d = ctx->f{}.d", fd, fs);
break;
case InstrId::cpu_neg_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.fl)", fs);
print_line("ctx->f{}.fl = -ctx->f{}.fl", fd, fs);
break;
case InstrId::cpu_neg_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.d)", fs);
print_line("ctx->f{}.d = -ctx->f{}.d", fd, fs);
break;
case InstrId::cpu_abs_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.fl)", fs);
print_line("ctx->f{}.fl = fabsf(ctx->f{}.fl)", fd, fs);
break;
case InstrId::cpu_abs_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.d)", fs);
print_line("ctx->f{}.d = fabs(ctx->f{}.d)", fd, fs);
break;
case InstrId::cpu_sqrt_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.fl)", fs);
print_line("ctx->f{}.fl = sqrtf(ctx->f{}.fl)", fd, fs);
break;
case InstrId::cpu_sqrt_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.d)", fs);
print_line("ctx->f{}.d = sqrt(ctx->f{}.d)", fd, fs);
break;
case InstrId::cpu_add_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft);
print_line("ctx->f{}.fl = ctx->f{}.fl + ctx->f{}.fl", fd, fs, ft);
break;
case InstrId::cpu_add_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft);
print_line("ctx->f{}.d = ctx->f{}.d + ctx->f{}.d", fd, fs, ft);
break;
case InstrId::cpu_sub_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft);
print_line("ctx->f{}.fl = ctx->f{}.fl - ctx->f{}.fl", fd, fs, ft);
break;
case InstrId::cpu_sub_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft);
print_line("ctx->f{}.d = ctx->f{}.d - ctx->f{}.d", fd, fs, ft);
break;
case InstrId::cpu_mul_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft);
print_line("ctx->f{}.fl = MUL_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft);
break;
case InstrId::cpu_mul_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft);
print_line("ctx->f{}.d = MUL_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft);
break;
case InstrId::cpu_div_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft);
print_line("ctx->f{}.fl = DIV_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft);
break;
case InstrId::cpu_div_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("CHECK_FR(ctx, {})", ft);
print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft);
print_line("ctx->f{}.d = DIV_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft);
break;
case InstrId::cpu_cvt_s_w:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.fl = CVT_S_W(ctx->f{}.u32l)", fd, fs);
break;
case InstrId::cpu_cvt_d_w:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.d = CVT_D_W(ctx->f{}.u32l)", fd, fs);
break;
case InstrId::cpu_cvt_d_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.fl)", fs);
print_line("ctx->f{}.d = CVT_D_S(ctx->f{}.fl)", fd, fs);
break;
case InstrId::cpu_cvt_s_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.d)", fs);
print_line("ctx->f{}.fl = CVT_S_D(ctx->f{}.d)", fd, fs);
break;
case InstrId::cpu_cvt_d_l:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.d = CVT_D_L(ctx->f{}.u64)", fd, fs);
break;
case InstrId::cpu_cvt_l_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.d)", fs);
print_line("ctx->f{}.u64 = CVT_L_D(ctx->f{}.d)", fd, fs);
break;
case InstrId::cpu_cvt_s_l:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.fl = CVT_S_L(ctx->f{}.u64)", fd, fs);
break;
case InstrId::cpu_cvt_l_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("NAN_CHECK(ctx->f{}.fl)", fs);
print_line("ctx->f{}.u64 = CVT_L_S(ctx->f{}.fl)", fd, fs);
break;
case InstrId::cpu_trunc_w_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = TRUNC_W_S(ctx->f{}.fl)", fd, fs);
break;
case InstrId::cpu_trunc_w_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = TRUNC_W_D(ctx->f{}.d)", fd, fs);
break;
//case InstrId::cpu_trunc_l_s:
// print_line("CHECK_FR(ctx, {})", fd);
// print_line("CHECK_FR(ctx, {})", fs);
// print_line("ctx->f{}.u64 = TRUNC_L_S(ctx->f{}.fl)", fd, fs);
// break;
//case InstrId::cpu_trunc_l_d:
// print_line("CHECK_FR(ctx, {})", fd);
// print_line("CHECK_FR(ctx, {})", fs);
// print_line("ctx->f{}.u64 = TRUNC_L_D(ctx->f{}.d)", fd, fs);
// break;
// Cop1 rounding mode
case InstrId::cpu_ctc1:
if (cop1_cs != 31) {
fmt::print(stderr, "Invalid FP control register for ctc1: {}\n", cop1_cs);
@@ -1100,47 +486,148 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
}
print_line("{}{} = rounding_mode", ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_cvt_w_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = CVT_W_S(ctx->f{}.fl)", fd, fs);
break;
case InstrId::cpu_cvt_w_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = CVT_W_D(ctx->f{}.d)", fd, fs);
break;
case InstrId::cpu_round_w_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = lroundf(ctx->f{}.fl)", fd, fs);
break;
case InstrId::cpu_round_w_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = lround(ctx->f{}.d)", fd, fs);
break;
case InstrId::cpu_ceil_w_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = S32(ceilf(ctx->f{}.fl))", fd, fs);
break;
case InstrId::cpu_ceil_w_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = S32(ceil(ctx->f{}.d))", fd, fs);
break;
case InstrId::cpu_floor_w_s:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = S32(floorf(ctx->f{}.fl))", fd, fs);
break;
case InstrId::cpu_floor_w_d:
print_line("CHECK_FR(ctx, {})", fd);
print_line("CHECK_FR(ctx, {})", fs);
print_line("ctx->f{}.u32l = S32(floor(ctx->f{}.d))", fd, fs);
break;
default:
handled = false;
break;
}
CGenerator generator{};
InstructionContext instruction_context{};
instruction_context.rd = rd;
instruction_context.rs = rs;
instruction_context.rt = rt;
instruction_context.sa = sa;
instruction_context.fd = fd;
instruction_context.fs = fs;
instruction_context.ft = ft;
instruction_context.cop1_cs = cop1_cs;
instruction_context.imm16 = imm;
instruction_context.reloc_type = reloc_type;
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 CGenerator& 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);
break;
case Operand::Fs:
case Operand::FsDouble:
case Operand::FsU32L:
case Operand::FsU32H:
case Operand::FsU64:
generator.emit_check_fr(output_file, 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);
break;
}
};
auto do_check_nan = [](std::ostream& output_file, const CGenerator& generator, const InstructionContext& ctx, Operand operand) {
switch (operand) {
case Operand::Fd:
generator.emit_check_nan(output_file, ctx.fd, false);
break;
case Operand::Fs:
generator.emit_check_nan(output_file, ctx.fs, false);
break;
case Operand::Ft:
generator.emit_check_nan(output_file, ctx.ft, false);
break;
case Operand::FdDouble:
generator.emit_check_nan(output_file, ctx.fd, true);
break;
case Operand::FsDouble:
generator.emit_check_nan(output_file, ctx.fs, true);
break;
case Operand::FtDouble:
generator.emit_check_nan(output_file, ctx.ft, true);
break;
}
};
auto find_binary_it = binary_ops.find(instr.getUniqueId());
if (find_binary_it != binary_ops.end()) {
print_indent();
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]);
}
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]);
fmt::print(output_file, "\n ");
}
generator.process_binary_op(output_file, op, instruction_context);
handled = true;
}
auto find_unary_it = unary_ops.find(instr.getUniqueId());
if (find_unary_it != unary_ops.end()) {
print_indent();
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);
}
if (op.check_nan) {
do_check_nan(output_file, generator, instruction_context, op.input);
fmt::print(output_file, "\n ");
}
generator.process_unary_op(output_file, 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);
print_indent();
if (find_conditional_branch_it->second.link) {
print_func_call(instr.getBranchVramGeneric());
}
else {
print_branch((uint32_t)instr.getBranchVramGeneric());
}
generator.emit_branch_close(output_file);
is_branch_likely = find_conditional_branch_it->second.likely;
handled = true;
}
auto find_store_it = store_ops.find(instr.getUniqueId());
if (find_store_it != store_ops.end()) {
print_indent();
const StoreOp& op = find_store_it->second;
if (op.type == StoreOpType::SDC1) {
do_check_fr(output_file, generator, instruction_context, op.value_input);
}
generator.process_store_op(output_file, op, instruction_context);
handled = true;
}
if (!handled) {
fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName());
return false;
}