mirror of
https://github.com/N64Recomp/N64Recomp.git
synced 2025-05-24 12:24:59 +00:00
Created Generator interface, separated operation types and tables and C generation code into new files
This commit is contained in:
parent
c4f85867f3
commit
d6be2d7a83
6 changed files with 924 additions and 873 deletions
|
@ -70,6 +70,8 @@ target_sources(N64Recomp PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/src/analysis.cpp
|
${CMAKE_SOURCE_DIR}/src/analysis.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/config.cpp
|
${CMAKE_SOURCE_DIR}/src/config.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/main.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)
|
${CMAKE_SOURCE_DIR}/src/recompilation.cpp)
|
||||||
|
|
||||||
target_include_directories(N64Recomp PRIVATE
|
target_include_directories(N64Recomp PRIVATE
|
||||||
|
|
55
include/generator.h
Normal file
55
include/generator.h
Normal 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
200
include/operations.h
Normal 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
483
src/cgenerator.cpp
Normal 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
180
src/operations.cpp
Normal 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::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<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 }},
|
||||||
|
};
|
||||||
|
}
|
|
@ -9,9 +9,8 @@
|
||||||
#include "fmt/ostream.h"
|
#include "fmt/ostream.h"
|
||||||
|
|
||||||
#include "recomp_port.h"
|
#include "recomp_port.h"
|
||||||
|
#include "operations.h"
|
||||||
using InstrId = rabbitizer::InstrId::UniqueId;
|
#include "generator.h"
|
||||||
using Cop0Reg = rabbitizer::Registers::Cpu::Cop0;
|
|
||||||
|
|
||||||
std::string_view ctx_gpr_prefix(int reg) {
|
std::string_view ctx_gpr_prefix(int reg) {
|
||||||
if (reg != 0) {
|
if (reg != 0) {
|
||||||
|
@ -20,878 +19,10 @@ std::string_view ctx_gpr_prefix(int reg) {
|
||||||
return "";
|
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<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::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<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 }},
|
|
||||||
};
|
|
||||||
|
|
||||||
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<BinaryOpFields> c_op_fields = []() {
|
|
||||||
std::vector<BinaryOpFields> ret{};
|
|
||||||
ret.resize(static_cast<size_t>(BinaryOpType::COUNT));
|
|
||||||
std::vector<char> ops_setup{};
|
|
||||||
ops_setup.resize(static_cast<size_t>(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<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(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<int>(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<size_t>(op_type)].func_string;
|
|
||||||
infix_string = c_op_fields[static_cast<size_t>(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.
|
// 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) {
|
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& section = context.sections[func.section_index];
|
||||||
const auto& instr = instructions[instr_index];
|
const auto& instr = instructions[instr_index];
|
||||||
needs_link_branch = false;
|
needs_link_branch = false;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue