mirror of
https://github.com/open-goal/jak-project
synced 2026-05-23 15:02:01 -04:00
d3cc739e43
This attempts to get into master whatever work was done in this PR / it's earlier PR https://github.com/open-goal/jak-project/pull/3965 I don't want this work to be lost / floating around in massive PRs. However the changes are: - switch to ntsc_v1 instead of PAL as the development target, as we have done for all other games - remove most of the copied-from-jak2/3 changes as they need to be confirmed during the decompilation process not just assumed - avoids committing any changes to `game/kernel/common` as it was not clear to me if these were changes made in jak x's kernel that were not properly broken out into it's own functions. We don't want to accidentally introduce bugs into jak1-3's kernel code. - in other words, if the change in the kernel only happens in jak x...it should likely be specific to jak x's kernel, not common. --------- Co-authored-by: VodBox <dillon@vodbox.io> Co-authored-by: yodah <greenboyyodah@gmail.com>
2672 lines
99 KiB
C++
2672 lines
99 KiB
C++
#include "atomic_op_builder.h"
|
|
|
|
#include <memory>
|
|
|
|
#include "common/log/log.h"
|
|
#include "common/symbols.h"
|
|
|
|
#include "decompiler/Disasm/DecompilerLabel.h"
|
|
#include "decompiler/Disasm/InstructionMatching.h"
|
|
#include "decompiler/Function/Function.h"
|
|
#include "decompiler/Function/Warnings.h"
|
|
|
|
namespace decompiler {
|
|
|
|
namespace {
|
|
|
|
std::unique_ptr<AtomicOp> convert_1(const Instruction& i0,
|
|
int idx,
|
|
bool hint_inline_asm,
|
|
bool force_asm_branch,
|
|
GameVersion version);
|
|
|
|
//////////////////////
|
|
// Register Helpers
|
|
//////////////////////
|
|
|
|
Register rs7() {
|
|
return make_gpr(Reg::S7);
|
|
}
|
|
|
|
Register rr0() {
|
|
return make_gpr(Reg::R0);
|
|
}
|
|
|
|
Register rfp() {
|
|
return make_gpr(Reg::FP);
|
|
}
|
|
|
|
Register rra() {
|
|
return make_gpr(Reg::RA);
|
|
}
|
|
|
|
Register rt9() {
|
|
return make_gpr(Reg::T9);
|
|
}
|
|
|
|
Register rv0() {
|
|
return make_gpr(Reg::V0);
|
|
}
|
|
|
|
Register rsp() {
|
|
return make_gpr(Reg::SP);
|
|
}
|
|
|
|
Register make_vf(int idx) {
|
|
return Register(Reg::VF, idx);
|
|
}
|
|
|
|
/////////////////////////
|
|
// Variable Helpers
|
|
/////////////////////////
|
|
|
|
RegisterAccess make_dst_var(Register reg, int idx) {
|
|
return RegisterAccess(AccessMode::WRITE, reg, idx);
|
|
}
|
|
|
|
RegisterAccess make_src_var(Register reg, int idx) {
|
|
return RegisterAccess(AccessMode::READ, reg, idx);
|
|
}
|
|
|
|
RegisterAccess make_dst_var(const Instruction& i, int idx) {
|
|
ASSERT(i.n_dst == 1);
|
|
return make_dst_var(i.get_dst(0).get_reg(), idx);
|
|
}
|
|
|
|
////////////////////////
|
|
// Atom Helpers
|
|
////////////////////////
|
|
|
|
SimpleAtom false_sym() {
|
|
return SimpleAtom::make_sym_val("#f");
|
|
}
|
|
|
|
SimpleAtom make_src_atom(Register reg, int idx) {
|
|
if (reg == Register(Reg::GPR, Reg::R0)) {
|
|
return SimpleAtom::make_int_constant(0);
|
|
}
|
|
if (reg == Register(Reg::GPR, Reg::S7)) {
|
|
return false_sym();
|
|
}
|
|
return SimpleAtom::make_var(make_src_var(reg, idx));
|
|
}
|
|
|
|
////////////////////////
|
|
// Expression Helpers
|
|
////////////////////////
|
|
|
|
SimpleExpression make_2reg_expr(const Instruction& instr, SimpleExpression::Kind kind, int idx) {
|
|
auto src0 = make_src_atom(instr.get_src(0).get_reg(), idx);
|
|
auto src1 = make_src_atom(instr.get_src(1).get_reg(), idx);
|
|
return SimpleExpression(kind, src0, src1);
|
|
}
|
|
|
|
SimpleExpression make_1reg_1imm_expr(const Instruction& instr,
|
|
SimpleExpression::Kind kind,
|
|
int idx,
|
|
int imm_offset = 0) {
|
|
auto src0 = make_src_atom(instr.get_src(0).get_reg(), idx);
|
|
auto src1 = SimpleAtom::make_int_constant(instr.get_src(1).get_imm() + imm_offset);
|
|
return SimpleExpression(kind, src0, src1);
|
|
}
|
|
|
|
SimpleExpression make_1reg_expr(const Instruction& instr, SimpleExpression::Kind kind, int idx) {
|
|
auto src = make_src_atom(instr.get_src(0).get_reg(), idx);
|
|
return SimpleExpression(kind, src);
|
|
}
|
|
|
|
SimpleExpression make_reg_plus_int(Register reg, int integer, int idx) {
|
|
return SimpleExpression(SimpleExpression::Kind::ADD, make_src_atom(reg, idx),
|
|
SimpleAtom::make_int_constant(integer));
|
|
}
|
|
|
|
////////////////////////
|
|
// AtmoicOp Helpers
|
|
////////////////////////
|
|
|
|
/*!
|
|
* Convert a single instruction in the form instr dest_reg, src_reg, src_reg
|
|
* to an atomic op of (set! dst_reg (op src_reg src_reg))
|
|
* Like daddu a0, a1, a2
|
|
*/
|
|
std::unique_ptr<AtomicOp> make_3reg_op(const Instruction& instr,
|
|
SimpleExpression::Kind kind,
|
|
int idx) {
|
|
auto dst = make_dst_var(instr.get_dst(0).get_reg(), idx);
|
|
return std::make_unique<SetVarOp>(dst, make_2reg_expr(instr, kind, idx), idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> make_2reg_1imm_op(const Instruction& instr,
|
|
SimpleExpression::Kind kind,
|
|
int idx,
|
|
int imm_offset = 0) {
|
|
auto dst = make_dst_var(instr.get_dst(0).get_reg(), idx);
|
|
return std::make_unique<SetVarOp>(dst, make_1reg_1imm_expr(instr, kind, idx, imm_offset), idx);
|
|
}
|
|
|
|
/*!
|
|
* Convert a single instruction in the form instr dest_reg, src_reg
|
|
* to an atomic op of (set! dest_reg (op src_reg))
|
|
*/
|
|
std::unique_ptr<AtomicOp> make_2reg_op(const Instruction& instr,
|
|
SimpleExpression::Kind kind,
|
|
int idx) {
|
|
auto dst = make_dst_var(instr.get_dst(0).get_reg(), idx);
|
|
return std::make_unique<SetVarOp>(dst, make_1reg_expr(instr, kind, idx), idx);
|
|
}
|
|
|
|
/*!
|
|
* Common load helper. Supports fp relative, 0 offset, or integer constant offset
|
|
*/
|
|
std::unique_ptr<AtomicOp> make_standard_load(const Instruction& i0,
|
|
int idx,
|
|
int load_size,
|
|
LoadVarOp::Kind kind) {
|
|
if (i0.get_dst(0).is_reg(Register(Reg::GPR, Reg::SP))) {
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
}
|
|
auto dst = make_dst_var(i0, idx);
|
|
SimpleExpression src;
|
|
if (i0.get_src(0).is_label() && i0.get_src(1).is_reg(rfp())) {
|
|
// it's an FP relative load.
|
|
src = SimpleAtom::make_static_address(i0.get_src(0).get_label()).as_expr();
|
|
} else if (i0.get_src(0).is_imm() && i0.get_src(1).is_reg(rsp())) {
|
|
// it's a stack spill.
|
|
return std::make_unique<StackSpillLoadOp>(make_dst_var(i0, idx), load_size,
|
|
i0.get_src(0).get_imm(),
|
|
kind == LoadVarOp::Kind::SIGNED, idx);
|
|
} else if (i0.get_src(0).is_imm() && i0.get_src(0).get_imm() == 0) {
|
|
// the offset is 0
|
|
src = make_src_atom(i0.get_src(1).get_reg(), idx).as_expr();
|
|
} else if (i0.get_src(0).is_imm()) {
|
|
// the offset is not 0
|
|
src = make_reg_plus_int(i0.get_src(1).get_reg(), i0.get_src(0).get_imm(), idx);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
return std::make_unique<LoadVarOp>(kind, load_size, dst, src, idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> make_standard_store(const Instruction& i0,
|
|
int idx,
|
|
int store_size,
|
|
StoreOp::Kind kind) {
|
|
if (i0.get_src(2).is_reg(Register(Reg::GPR, Reg::SP))) {
|
|
if (kind == StoreOp::Kind::INTEGER && store_size == 4 && i0.get_src(1).get_imm() == 0) {
|
|
// this is a bit of a hack. enter-state does a sw onto the stack that's not a spill, but
|
|
// instead manipulates the stores "ra" register that will later be restored.
|
|
// I believe sw is never used for stack spills, and no stack variable is ever located at
|
|
// sp + 0, so this should be safe.
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
}
|
|
// it's a stack spill.
|
|
return std::make_unique<StackSpillStoreOp>(make_src_atom(i0.get_src(0).get_reg(), idx),
|
|
store_size, i0.get_src(1).get_imm(), idx);
|
|
}
|
|
SimpleAtom val;
|
|
SimpleExpression dst;
|
|
if (i0.get_src(0).is_reg(rs7())) {
|
|
ASSERT(kind == StoreOp::Kind::INTEGER);
|
|
val = SimpleAtom::make_sym_val("#f");
|
|
} else if (i0.get_src(0).is_reg(rr0())) {
|
|
ASSERT(kind == StoreOp::Kind::INTEGER);
|
|
val = SimpleAtom::make_int_constant(0);
|
|
} else {
|
|
val = make_src_atom(i0.get_src(0).get_reg(), idx);
|
|
}
|
|
|
|
auto base_reg = make_src_atom(i0.get_src(2).get_reg(), idx);
|
|
auto offset = i0.get_src(1).get_imm();
|
|
if (offset == 0) {
|
|
dst = base_reg.as_expr();
|
|
} else {
|
|
dst = SimpleExpression(SimpleExpression::Kind::ADD, base_reg,
|
|
SimpleAtom::make_int_constant(offset));
|
|
}
|
|
|
|
return std::make_unique<StoreOp>(store_size, kind, dst, val, idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> make_asm_op(const Instruction& i0, int idx) {
|
|
switch (i0.kind) {
|
|
case InstructionKind::SLLV: // goal will use dsllv
|
|
case InstructionKind::SLL: // goal will use dsll
|
|
case InstructionKind::PCPYUD:
|
|
case InstructionKind::LQ:
|
|
case InstructionKind::SQ:
|
|
case InstructionKind::MTC0:
|
|
case InstructionKind::MTDAB:
|
|
case InstructionKind::MTDABM:
|
|
case InstructionKind::SUBU: // goal uses dsubu
|
|
case InstructionKind::JR: // normal returns included in epilogue
|
|
case InstructionKind::SYSCALL:
|
|
case InstructionKind::ADDU: // goal uses daddu
|
|
case InstructionKind::SRL: // goal uses dsrl.. except maybe to access bitfields?
|
|
case InstructionKind::SRA:
|
|
case InstructionKind::ADDIU:
|
|
// some weird inline assembly macros in nav-mesh stuff use these a lot in a weird way.
|
|
case InstructionKind::SLT:
|
|
case InstructionKind::MOVN:
|
|
case InstructionKind::SLTI: // a few cases used in inline asm
|
|
case InstructionKind::MOVZ: // in font.gc
|
|
|
|
// VU/COP2
|
|
case InstructionKind::VMOVE:
|
|
case InstructionKind::VFTOI0:
|
|
case InstructionKind::VFTOI4:
|
|
case InstructionKind::VFTOI12:
|
|
case InstructionKind::VFTOI15:
|
|
case InstructionKind::VITOF0:
|
|
case InstructionKind::VITOF12:
|
|
case InstructionKind::VITOF15:
|
|
case InstructionKind::VABS:
|
|
case InstructionKind::VADD:
|
|
case InstructionKind::VSUB:
|
|
case InstructionKind::VMUL:
|
|
case InstructionKind::VMINI:
|
|
case InstructionKind::VMAX:
|
|
case InstructionKind::VOPMSUB:
|
|
case InstructionKind::VMADD:
|
|
case InstructionKind::VMSUB:
|
|
case InstructionKind::VADD_BC:
|
|
case InstructionKind::VSUB_BC:
|
|
case InstructionKind::VMUL_BC:
|
|
case InstructionKind::VMULA_BC:
|
|
case InstructionKind::VMADD_BC:
|
|
case InstructionKind::VADDA_BC:
|
|
case InstructionKind::VMADDA_BC:
|
|
case InstructionKind::VMSUBA_BC:
|
|
case InstructionKind::VMSUB_BC:
|
|
case InstructionKind::VMINI_BC:
|
|
case InstructionKind::VMAX_BC:
|
|
case InstructionKind::VADDQ:
|
|
case InstructionKind::VSUBQ:
|
|
case InstructionKind::VMULQ:
|
|
case InstructionKind::VMSUBQ:
|
|
case InstructionKind::VMULA:
|
|
case InstructionKind::VADDA:
|
|
case InstructionKind::VMADDA:
|
|
case InstructionKind::VOPMULA:
|
|
case InstructionKind::VDIV:
|
|
case InstructionKind::VCLIP:
|
|
case InstructionKind::VMULAQ:
|
|
case InstructionKind::VMTIR:
|
|
case InstructionKind::VIAND:
|
|
case InstructionKind::VLQI:
|
|
case InstructionKind::VIADDI:
|
|
case InstructionKind::VSQI:
|
|
case InstructionKind::VRGET:
|
|
case InstructionKind::VSQRT:
|
|
case InstructionKind::VRSQRT:
|
|
case InstructionKind::VRXOR:
|
|
case InstructionKind::VRNEXT:
|
|
case InstructionKind::VNOP:
|
|
case InstructionKind::VWAITQ:
|
|
case InstructionKind::VCALLMS:
|
|
|
|
// FPU/COP1
|
|
case InstructionKind::MULAS:
|
|
case InstructionKind::MADDAS:
|
|
case InstructionKind::MADDS:
|
|
case InstructionKind::MSUBAS:
|
|
case InstructionKind::MSUBS:
|
|
case InstructionKind::ADDAS:
|
|
case InstructionKind::RSQRTS:
|
|
|
|
// Moves / Loads / Stores
|
|
case InstructionKind::CTC2:
|
|
case InstructionKind::CFC2:
|
|
case InstructionKind::LDR:
|
|
case InstructionKind::LDL:
|
|
case InstructionKind::QMTC2:
|
|
case InstructionKind::QMFC2:
|
|
case InstructionKind::MFC0:
|
|
case InstructionKind::SYNCL:
|
|
case InstructionKind::SYNCP:
|
|
case InstructionKind::CACHE_DXWBIN:
|
|
case InstructionKind::MTPC:
|
|
case InstructionKind::MFPC:
|
|
|
|
// MMI
|
|
case InstructionKind::PSLLW:
|
|
case InstructionKind::PSRAW:
|
|
case InstructionKind::PSRAH:
|
|
case InstructionKind::PLZCW:
|
|
case InstructionKind::PMFHL_UW:
|
|
case InstructionKind::PMFHL_LW:
|
|
case InstructionKind::PMFHL_LH:
|
|
case InstructionKind::PSLLH:
|
|
case InstructionKind::PSRLH:
|
|
case InstructionKind::PEXTLW:
|
|
case InstructionKind::PPACH:
|
|
case InstructionKind::PSUBW:
|
|
case InstructionKind::PCGTB:
|
|
case InstructionKind::PCGTW:
|
|
case InstructionKind::PEXTLH:
|
|
case InstructionKind::PEXTLB:
|
|
case InstructionKind::PMAXH:
|
|
case InstructionKind::PPACB:
|
|
case InstructionKind::PADDW:
|
|
case InstructionKind::PADDH:
|
|
case InstructionKind::PADDB:
|
|
case InstructionKind::PMAXW:
|
|
case InstructionKind::PPACW:
|
|
case InstructionKind::PCEQW:
|
|
case InstructionKind::PEXTUW:
|
|
case InstructionKind::PMINH:
|
|
case InstructionKind::PEXTUH:
|
|
case InstructionKind::PEXTUB:
|
|
case InstructionKind::PCEQB:
|
|
case InstructionKind::PMINW:
|
|
case InstructionKind::PABSW:
|
|
case InstructionKind::PCPYLD:
|
|
case InstructionKind::PROT3W:
|
|
case InstructionKind::PAND:
|
|
case InstructionKind::PXOR:
|
|
case InstructionKind::PMADDH:
|
|
case InstructionKind::PMULTH:
|
|
case InstructionKind::PEXEW:
|
|
case InstructionKind::PEXCW:
|
|
case InstructionKind::PNOR:
|
|
case InstructionKind::PCPYH:
|
|
case InstructionKind::PINTEH:
|
|
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_1_allow_asm(const Instruction& i0, int idx, GameVersion version) {
|
|
// only used for delay slots, so fine to assume that this can never be an asm branch itself
|
|
// as there are no branches in delay slots anywhere.
|
|
auto as_normal = convert_1(i0, idx, false, false, version);
|
|
if (as_normal) {
|
|
return as_normal;
|
|
}
|
|
return make_asm_op(i0, idx);
|
|
}
|
|
|
|
////////////////////////
|
|
// Branch Helpers
|
|
////////////////////////
|
|
|
|
IR2_BranchDelay get_branch_delay(const Instruction& i0, int idx, GameVersion version) {
|
|
if (is_nop(i0)) {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::NOP);
|
|
} else if (is_gpr_3(i0, InstructionKind::OR, {}, rs7(), rr0())) {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::SET_REG_FALSE, make_dst_var(i0, idx));
|
|
} else if (is_gpr_3(i0, InstructionKind::OR, {}, {}, rr0()) && !i0.get_src(0).is_reg(rr0())) {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::SET_REG_REG, make_dst_var(i0, idx),
|
|
make_src_var(i0.get_src(0).get_reg(), idx));
|
|
} else if (i0.kind == InstructionKind::DADDIU && i0.get_src(0).is_reg(rs7()) &&
|
|
i0.get_src(1).is_imm(true_symbol_offset(version))) {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::SET_REG_TRUE, make_dst_var(i0, idx));
|
|
} else if (i0.kind == InstructionKind::LW && i0.get_src(1).is_reg(rs7()) &&
|
|
i0.get_src(0).is_sym()) {
|
|
if (i0.get_src(0).is_sym("binteger")) {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::SET_BINTEGER, make_dst_var(i0, idx));
|
|
} else if (i0.get_src(0).is_sym("pair")) {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::SET_PAIR, make_dst_var(i0, idx));
|
|
} else {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::UNKNOWN);
|
|
}
|
|
} else if (i0.kind == InstructionKind::DSLLV) {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::DSLLV, make_dst_var(i0, idx),
|
|
make_src_var(i0.get_src(0).get_reg(), idx),
|
|
make_src_var(i0.get_src(1).get_reg(), idx));
|
|
} else if (is_gpr_3(i0, InstructionKind::DSUBU, {}, rr0(), {})) {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::NEGATE, make_dst_var(i0, idx),
|
|
make_src_var(i0.get_src(1).get_reg(), idx));
|
|
} else {
|
|
return IR2_BranchDelay(IR2_BranchDelay::Kind::UNKNOWN);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> make_branch(const IR2_Condition& condition,
|
|
const Instruction& delay,
|
|
bool likely,
|
|
int dest_label,
|
|
int my_idx,
|
|
bool force_asm,
|
|
GameVersion version) {
|
|
ASSERT(!likely);
|
|
auto branch_delay = get_branch_delay(delay, my_idx, version);
|
|
if (!force_asm && branch_delay.is_known()) {
|
|
return std::make_unique<BranchOp>(likely, condition, dest_label, branch_delay, my_idx);
|
|
} else {
|
|
auto delay_op = std::shared_ptr<AtomicOp>(convert_1_allow_asm(delay, my_idx, version));
|
|
if (!delay_op) {
|
|
throw std::runtime_error(
|
|
fmt::format("Failed to convert branch delay slot instruction for branch at {}", my_idx));
|
|
}
|
|
return std::make_unique<AsmBranchOp>(likely, condition, dest_label, delay_op, my_idx);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> make_asm_branch_no_delay(const IR2_Condition& condition,
|
|
bool likely,
|
|
int dest_label,
|
|
int my_idx) {
|
|
ASSERT(likely);
|
|
return std::make_unique<AsmBranchOp>(likely, condition, dest_label, nullptr, my_idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> make_branch_no_delay(const IR2_Condition& condition,
|
|
bool likely,
|
|
int dest_label,
|
|
int my_idx,
|
|
bool force_asm_branch) {
|
|
if (force_asm_branch) {
|
|
return make_asm_branch_no_delay(condition, likely, dest_label, my_idx);
|
|
}
|
|
ASSERT(likely);
|
|
IR2_BranchDelay delay(IR2_BranchDelay::Kind::NO_DELAY);
|
|
return std::make_unique<BranchOp>(likely, condition, dest_label, delay, my_idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> make_asm_branch(const IR2_Condition& condition,
|
|
const Instruction& delay,
|
|
bool likely,
|
|
int dest_label,
|
|
int my_idx,
|
|
GameVersion version) {
|
|
ASSERT(!likely);
|
|
auto delay_op = std::shared_ptr<AtomicOp>(convert_1_allow_asm(delay, my_idx, version));
|
|
if (!delay_op) {
|
|
throw std::runtime_error(
|
|
fmt::format("Failed to convert branch delay slot instruction for branch at {}", my_idx));
|
|
}
|
|
return std::make_unique<AsmBranchOp>(likely, condition, dest_label, delay_op, my_idx);
|
|
}
|
|
|
|
///////////////////////
|
|
// OP 1 Conversions
|
|
//////////////////////
|
|
|
|
std::unique_ptr<AtomicOp> convert_or_1(const Instruction& i0, int idx) {
|
|
if (i0.get_src(1).is_reg(rra())) {
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
}
|
|
auto dest = make_dst_var(i0, idx);
|
|
SimpleExpression src;
|
|
|
|
if (is_gpr_3(i0, InstructionKind::OR, {}, rs7(), rr0())) {
|
|
// set reg_dest to #f : or reg_dest, s7, r0
|
|
src = false_sym().as_expr();
|
|
} else if (is_gpr_3(i0, InstructionKind::OR, {}, rr0(), rr0())) {
|
|
// set reg_dest to 0 : or reg_dest, r0, r0
|
|
src = SimpleAtom::make_int_constant(0).as_expr();
|
|
} else if (is_gpr_3(i0, InstructionKind::OR, {}, {}, rr0())) {
|
|
// set dst to src : or dst, src, r0
|
|
src = make_src_atom(i0.get_src(0).get_reg(), idx).as_expr();
|
|
} else {
|
|
// actually do a logical OR of two registers: or a0, a1, a2
|
|
src = make_2reg_expr(i0, SimpleExpression::Kind::OR, idx);
|
|
}
|
|
return std::make_unique<SetVarOp>(dest, src, idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_por_1(const Instruction& i0, int idx) {
|
|
if (is_gpr_3(i0, InstructionKind::POR, {}, {}, rr0())) {
|
|
return std::make_unique<SetVarOp>(make_dst_var(i0, idx),
|
|
make_src_atom(i0.get_src(0).get_reg(), idx).as_expr(), idx);
|
|
}
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_ori_1(const Instruction& i0, int idx) {
|
|
auto dest = make_dst_var(i0, idx);
|
|
SimpleExpression src;
|
|
if (i0.get_src(0).is_reg(rr0()) && i0.get_src(1).is_imm()) {
|
|
// load a 16-bit integer constant
|
|
// ori reg, r0, 1234
|
|
src = SimpleAtom::make_int_constant(i0.get_src(1).get_imm()).as_expr();
|
|
} else if (i0.get_src(1).is_imm()) {
|
|
// logical or with constant integer
|
|
// ori dst, a0, 1234
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::OR, idx);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
return std::make_unique<SetVarOp>(dest, src, idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_mtc1_1(const Instruction& i0, int idx) {
|
|
if (i0.get_src(0).is_reg(rr0())) {
|
|
return std::make_unique<SetVarOp>(make_dst_var(i0, idx),
|
|
SimpleAtom::make_int_constant(0).as_expr(), idx);
|
|
} else {
|
|
return make_2reg_op(i0, SimpleExpression::Kind::GPR_TO_FPR, idx);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_mfc1_1(const Instruction& i0, int idx) {
|
|
if (i0.get_dst(0).is_reg(rr0()) && i0.get_src(0).is_reg(Register(Reg::FPR, 31))) {
|
|
// mfc r0, f31 is a nop
|
|
return std::make_unique<SpecialOp>(SpecialOp::Kind::NOP, idx);
|
|
}
|
|
|
|
if (i0.get_dst(0).is_reg(rr0()) || i0.get_dst(0).is_reg(make_gpr(Reg::AT)) ||
|
|
i0.get_dst(0).is_reg(rra())) {
|
|
// sometimes mfc1 r0, f31 is used like a 'nop'. No idea why.
|
|
// at used in some inline assembly gpr -> vf conversions. might as well drop to assembly
|
|
// as soon as wel can.
|
|
// cursed mfc1 ra, f0 is also assembly.
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
} else {
|
|
return make_2reg_op(i0, SimpleExpression::Kind::FPR_TO_GPR, idx);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_lw_1(const Instruction& i0, int idx) {
|
|
if (i0.get_dst(0).is_reg(rra()) ||
|
|
(i0.get_dst(0).is_reg(make_gpr(Reg::AT)) && !i0.get_src(1).is_reg(rs7()))) {
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
}
|
|
if (i0.get_dst(0).is_reg(rr0()) && i0.get_src(0).is_imm(2) && i0.get_src(1).is_reg(rr0())) {
|
|
// lw r0, 2(r0), used to trigger an exception on purpose.
|
|
return std::make_unique<SpecialOp>(SpecialOp::Kind::BREAK, idx);
|
|
} else if (i0.get_src(1).is_reg(rs7()) && i0.get_src(0).is_sym()) {
|
|
// symbol load.
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(i0, idx), SimpleAtom::make_sym_val(i0.get_src(0).get_sym()).as_expr(), idx);
|
|
} else {
|
|
// fall back to standard loads
|
|
return make_standard_load(i0, idx, 4, LoadVarOp::Kind::SIGNED);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_daddiu_1(const Instruction& i0, int idx, GameVersion version) {
|
|
if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_sym()) {
|
|
// get symbol pointer
|
|
if (i0.get_src(1).kind == InstructionAtom::IMM_SYM_VAL_PTR) {
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(i0, idx), SimpleAtom::make_sym_val_ptr(i0.get_src(1).get_sym()).as_expr(),
|
|
idx);
|
|
} else {
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(i0, idx), SimpleAtom::make_sym_ptr(i0.get_src(1).get_sym()).as_expr(), idx);
|
|
}
|
|
} else if (i0.get_src(0).is_reg(rs7()) &&
|
|
i0.get_src(1).is_imm(empty_pair_offset_from_s7(version))) {
|
|
// get empty pair
|
|
return std::make_unique<SetVarOp>(make_dst_var(i0, idx),
|
|
SimpleAtom::make_empty_list().as_expr(), idx);
|
|
} else if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_imm(-32768)) {
|
|
// get pointer to beginning of symbol table (this is a bit of a hack)
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(i0, idx), SimpleAtom::make_sym_val("__START-OF-TABLE__").as_expr(), idx);
|
|
} else if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_imm(true_symbol_offset(version))) {
|
|
return std::make_unique<SetVarOp>(make_dst_var(i0, idx),
|
|
SimpleAtom::make_sym_ptr("#t").as_expr(), idx);
|
|
} else if (i0.get_src(0).is_reg(rfp()) && i0.get_src(1).is_label()) {
|
|
// get address of static
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(i0, idx), SimpleAtom::make_static_address(i0.get_src(1).get_label()).as_expr(),
|
|
idx);
|
|
} else {
|
|
// fall back to normal add.
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::ADD, idx);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_daddu_1(const Instruction& i0, int idx) {
|
|
if (i0.get_src(1).is_reg(rs7())) {
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
} else if (i0.get_src(0).is_reg(rr0())) {
|
|
// I think the array access code sometimes generates this. To be safe, let's pass it through
|
|
// as an explicit reg + 0 access.
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(i0, idx),
|
|
SimpleExpression(SimpleExpression::Kind::ADD, make_src_atom(i0.get_src(1).get_reg(), idx),
|
|
SimpleAtom::make_int_constant(0)),
|
|
idx);
|
|
} else {
|
|
return make_3reg_op(i0, SimpleExpression::Kind::ADD, idx);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_dsubu_1(const Instruction& i0, int idx) {
|
|
if (i0.get_src(0).is_reg(rr0())) {
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(i0, idx),
|
|
SimpleExpression(SimpleExpression::Kind::NEG, make_src_atom(i0.get_src(1).get_reg(), idx)),
|
|
idx);
|
|
} else {
|
|
// fall back
|
|
return make_3reg_op(i0, SimpleExpression::Kind::SUB, idx);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_nor_1(const Instruction& i0, int idx) {
|
|
if (i0.get_src(1).is_reg(rr0())) {
|
|
return std::make_unique<SetVarOp>(make_dst_var(i0, idx),
|
|
SimpleExpression(SimpleExpression::Kind::LOGNOT,
|
|
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
|
idx);
|
|
} else {
|
|
// fall back
|
|
return make_3reg_op(i0, SimpleExpression::Kind::NOR, idx);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_addiu_1(const Instruction& i0, int idx) {
|
|
// addiu is used to load a constant. sometimes.
|
|
if (i0.get_src(0).is_reg(rr0())) {
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(i0, idx), SimpleAtom::make_int_constant(i0.get_src(1).get_imm()).as_expr(),
|
|
idx);
|
|
} else {
|
|
// may be assembly
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_lui_1(const Instruction& i0, int idx) {
|
|
if (i0.get_dst(0).is_reg(make_gpr(Reg::AT))) {
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
}
|
|
if (i0.get_src(0).is_imm()) {
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(i0, idx),
|
|
SimpleAtom::make_int_constant(i0.get_src(0).get_imm() << 16).as_expr(), idx);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_sll_1(const Instruction& i0, int idx) {
|
|
if (is_nop(i0)) {
|
|
return std::make_unique<SpecialOp>(SpecialOp::Kind::NOP, idx);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_sw_1(const Instruction& i0, int idx) {
|
|
if (i0.get_src(1).is_sym() && i0.get_src(2).is_reg(rs7())) {
|
|
auto name = i0.get_src(1).get_sym();
|
|
// store into symbol table!
|
|
SimpleAtom val;
|
|
if (i0.get_src(0).is_reg(rs7())) {
|
|
// store a false
|
|
val = SimpleAtom::make_sym_val("#f");
|
|
} else if (i0.get_src(0).is_reg(rr0())) {
|
|
// store a 0
|
|
val = SimpleAtom::make_int_constant(0);
|
|
} else {
|
|
// store a register.
|
|
val = make_src_atom(i0.get_src(0).get_reg(), idx);
|
|
}
|
|
return std::make_unique<StoreOp>(4, StoreOp::Kind::INTEGER,
|
|
SimpleAtom::make_sym_val(name).as_expr(), val, idx);
|
|
} else {
|
|
return make_standard_store(i0, idx, 4, StoreOp::Kind::INTEGER);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_swc1_1(const Instruction& i0, int idx) {
|
|
if (i0.get_src(1).is_sym() && i0.get_src(2).is_reg(rs7())) {
|
|
// storing a float in the symbol table. It's very rare, but possible
|
|
return std::make_unique<StoreOp>(4, StoreOp::Kind::FLOAT,
|
|
SimpleAtom::make_sym_val(i0.get_src(1).get_sym()).as_expr(),
|
|
make_src_atom(i0.get_src(0).get_reg(), idx), idx);
|
|
} else {
|
|
return make_standard_store(i0, idx, 4, StoreOp::Kind::FLOAT);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_sd_1(const Instruction& i0, int idx) {
|
|
if (i0.get_src(0).is_reg(rr0()) && i0.get_src(1).is_imm(2) && i0.get_src(2).is_reg(rr0())) {
|
|
return std::make_unique<SpecialOp>(SpecialOp::Kind::CRASH, idx);
|
|
} else {
|
|
return make_standard_store(i0, idx, 8, StoreOp::Kind::INTEGER);
|
|
}
|
|
}
|
|
|
|
// movn or movz
|
|
std::unique_ptr<AtomicOp> convert_cmov_1(const Instruction& i0, int idx) {
|
|
if (i0.get_src(0).is_reg(rs7())) {
|
|
return std::make_unique<ConditionalMoveFalseOp>(
|
|
make_dst_var(i0, idx), make_src_var(i0.get_src(1).get_reg(), idx),
|
|
make_src_var(i0.get_dst(0).get_reg(), idx), i0.kind == InstructionKind::MOVZ, idx);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_dsll32_1(const Instruction& i0, int idx) {
|
|
if (i0.get_dst(0).is_reg(rra())) {
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
}
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::LEFT_SHIFT, idx, 32);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_dsrl32_1(const Instruction& i0, int idx) {
|
|
if (i0.get_dst(0).is_reg(rra())) {
|
|
return std::make_unique<AsmOp>(i0, idx);
|
|
}
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::RIGHT_SHIFT_LOGIC, idx, 32);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_likely_branch_1(const Instruction& i0,
|
|
IR2_Condition::Kind kind,
|
|
bool likely,
|
|
int idx,
|
|
bool force_asm) {
|
|
return make_branch_no_delay(IR2_Condition(kind, make_src_atom(i0.get_src(0).get_reg(), idx)),
|
|
likely, i0.get_src(1).get_label(), idx, force_asm);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_beql_1(const Instruction& i0,
|
|
int idx,
|
|
bool likely,
|
|
bool force_asm) {
|
|
auto s0 = i0.get_src(0).get_reg();
|
|
auto s1 = i0.get_src(1).get_reg();
|
|
auto dest = i0.get_src(2).get_label();
|
|
IR2_Condition condition;
|
|
if (s0 == rr0() && s1 == rr0()) {
|
|
condition = IR2_Condition(IR2_Condition::Kind::ALWAYS);
|
|
} else if (s1 == rr0()) {
|
|
condition = IR2_Condition(IR2_Condition::Kind::ZERO, make_src_atom(s0, idx));
|
|
} else if (i0.get_src(0).is_reg(rs7())) {
|
|
if (s1 == rs7()) {
|
|
// (if #f ...) type code?
|
|
condition = IR2_Condition(IR2_Condition::Kind::FALSE, SimpleAtom::make_sym_val("#f"));
|
|
} else {
|
|
condition = IR2_Condition(IR2_Condition::Kind::FALSE, make_src_atom(s1, idx));
|
|
}
|
|
} else if (s1 == rs7()) {
|
|
// likely a case where somebody wrote (= x #f) or (!= x #f). much rarer than the flipped one
|
|
condition = IR2_Condition(IR2_Condition::Kind::EQUAL, make_src_atom(s0, idx),
|
|
SimpleAtom::make_sym_val("#f"));
|
|
} else {
|
|
condition =
|
|
IR2_Condition(IR2_Condition::Kind::EQUAL, make_src_atom(s0, idx), make_src_atom(s1, idx));
|
|
condition.make_flipped();
|
|
}
|
|
return make_branch_no_delay(condition, likely, dest, idx, force_asm);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_bnel_1(const Instruction& i0,
|
|
int idx,
|
|
bool likely,
|
|
bool force_asm) {
|
|
auto s0 = i0.get_src(0).get_reg();
|
|
auto s1 = i0.get_src(1).get_reg();
|
|
auto dest = i0.get_src(2).get_label();
|
|
IR2_Condition condition;
|
|
if (s1 == rr0()) {
|
|
condition = IR2_Condition(IR2_Condition::Kind::NONZERO, make_src_atom(s0, idx));
|
|
} else if (i0.get_src(0).is_reg(rs7())) {
|
|
condition = IR2_Condition(IR2_Condition::Kind::TRUTHY, make_src_atom(s1, idx));
|
|
} else if (s1 == rs7()) {
|
|
// likely a case where somebody wrote (= x #f) or (!= x #f). much rarer than the flipped one
|
|
condition = IR2_Condition(IR2_Condition::Kind::NOT_EQUAL, make_src_atom(s0, idx),
|
|
SimpleAtom::make_sym_val("#f"));
|
|
} else {
|
|
condition = IR2_Condition(IR2_Condition::Kind::NOT_EQUAL, make_src_atom(s0, idx),
|
|
make_src_atom(s1, idx));
|
|
condition.make_flipped();
|
|
}
|
|
return make_branch_no_delay(condition, likely, dest, idx, force_asm);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_subu_1(const Instruction& i0, int idx) {
|
|
// subu a2, v1, s7
|
|
if (i0.get_src(1).is_reg(rs7())) {
|
|
auto src = make_src_atom(i0.get_src(0).get_reg(), idx);
|
|
auto expr = SimpleExpression(SimpleExpression::Kind::SUBU_L32_S7, src);
|
|
auto dst = make_dst_var(i0.get_dst(0).get_reg(), idx);
|
|
return std::make_unique<SetVarOp>(dst, expr, idx);
|
|
} else {
|
|
return nullptr; // go to asm fallback
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_1(const Instruction& i0,
|
|
int idx,
|
|
bool hint_inline_asm,
|
|
bool force_asm_branch,
|
|
GameVersion version) {
|
|
switch (i0.kind) {
|
|
case InstructionKind::OR:
|
|
return convert_or_1(i0, idx);
|
|
case InstructionKind::POR:
|
|
return convert_por_1(i0, idx);
|
|
case InstructionKind::ORI:
|
|
return convert_ori_1(i0, idx);
|
|
case InstructionKind::AND:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::AND, idx);
|
|
case InstructionKind::PCPYLD:
|
|
if (hint_inline_asm) {
|
|
break;
|
|
} else {
|
|
return make_3reg_op(i0, SimpleExpression::Kind::PCPYLD, idx);
|
|
}
|
|
case InstructionKind::MTC1:
|
|
return convert_mtc1_1(i0, idx);
|
|
case InstructionKind::MFC1:
|
|
return convert_mfc1_1(i0, idx);
|
|
case InstructionKind::LWC1:
|
|
return make_standard_load(i0, idx, 4, LoadVarOp::Kind::FLOAT);
|
|
case InstructionKind::LB:
|
|
return make_standard_load(i0, idx, 1, LoadVarOp::Kind::SIGNED);
|
|
case InstructionKind::LBU:
|
|
return make_standard_load(i0, idx, 1, LoadVarOp::Kind::UNSIGNED);
|
|
case InstructionKind::LHU:
|
|
return make_standard_load(i0, idx, 2, LoadVarOp::Kind::UNSIGNED);
|
|
case InstructionKind::LH:
|
|
return make_standard_load(i0, idx, 2, LoadVarOp::Kind::SIGNED);
|
|
case InstructionKind::LWU:
|
|
return make_standard_load(i0, idx, 4, LoadVarOp::Kind::UNSIGNED);
|
|
case InstructionKind::LW:
|
|
return convert_lw_1(i0, idx);
|
|
case InstructionKind::LD:
|
|
return make_standard_load(i0, idx, 8, LoadVarOp::Kind::UNSIGNED);
|
|
case InstructionKind::LQ:
|
|
return make_standard_load(i0, idx, 16, LoadVarOp::Kind::UNSIGNED);
|
|
case InstructionKind::LQC2:
|
|
return make_standard_load(i0, idx, 16, LoadVarOp::Kind::VECTOR_FLOAT);
|
|
case InstructionKind::DSLL:
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::LEFT_SHIFT, idx);
|
|
case InstructionKind::DSLL32:
|
|
return convert_dsll32_1(i0, idx);
|
|
case InstructionKind::DSRA:
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::RIGHT_SHIFT_ARITH, idx);
|
|
case InstructionKind::DSRA32:
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::RIGHT_SHIFT_ARITH, idx, 32);
|
|
case InstructionKind::DSRL:
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::RIGHT_SHIFT_LOGIC, idx);
|
|
case InstructionKind::DSRL32:
|
|
return convert_dsrl32_1(i0, idx);
|
|
case InstructionKind::DIVS:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::DIV_S, idx);
|
|
case InstructionKind::SUBS:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::SUB_S, idx);
|
|
case InstructionKind::ADDS:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::ADD_S, idx);
|
|
case InstructionKind::MULS:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::MUL_S, idx);
|
|
case InstructionKind::MINS:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::MIN_S, idx);
|
|
case InstructionKind::MAXS:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::MAX_S, idx);
|
|
case InstructionKind::DADDIU:
|
|
return convert_daddiu_1(i0, idx, version);
|
|
case InstructionKind::DADDU:
|
|
return convert_daddu_1(i0, idx);
|
|
case InstructionKind::DSUBU:
|
|
return convert_dsubu_1(i0, idx);
|
|
case InstructionKind::MULT3:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::MUL_SIGNED, idx);
|
|
case InstructionKind::MULTU3:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::MUL_UNSIGNED, idx);
|
|
case InstructionKind::ANDI:
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::AND, idx);
|
|
case InstructionKind::XORI:
|
|
return make_2reg_1imm_op(i0, SimpleExpression::Kind::XOR, idx);
|
|
case InstructionKind::NOR:
|
|
return convert_nor_1(i0, idx);
|
|
case InstructionKind::XOR:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::XOR, idx);
|
|
case InstructionKind::ADDIU:
|
|
return convert_addiu_1(i0, idx);
|
|
case InstructionKind::LUI:
|
|
return convert_lui_1(i0, idx);
|
|
case InstructionKind::SLL:
|
|
return convert_sll_1(i0, idx);
|
|
case InstructionKind::DSRAV:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::RIGHT_SHIFT_ARITH, idx);
|
|
case InstructionKind::DSRLV:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::RIGHT_SHIFT_LOGIC, idx);
|
|
case InstructionKind::DSLLV:
|
|
return make_3reg_op(i0, SimpleExpression::Kind::LEFT_SHIFT, idx);
|
|
case InstructionKind::SB:
|
|
return make_standard_store(i0, idx, 1, StoreOp::Kind::INTEGER);
|
|
case InstructionKind::SH:
|
|
return make_standard_store(i0, idx, 2, StoreOp::Kind::INTEGER);
|
|
case InstructionKind::SW:
|
|
return convert_sw_1(i0, idx);
|
|
case InstructionKind::SD:
|
|
return convert_sd_1(i0, idx);
|
|
case InstructionKind::SQ:
|
|
return make_standard_store(i0, idx, 16, StoreOp::Kind::INTEGER);
|
|
case InstructionKind::SQC2:
|
|
return make_standard_store(i0, idx, 16, StoreOp::Kind::VECTOR_FLOAT);
|
|
case InstructionKind::SWC1:
|
|
return convert_swc1_1(i0, idx);
|
|
case InstructionKind::CVTWS: // float to int
|
|
return make_2reg_op(i0, SimpleExpression::Kind::FLOAT_TO_INT, idx);
|
|
case InstructionKind::CVTSW: // int to float
|
|
return make_2reg_op(i0, SimpleExpression::Kind::INT_TO_FLOAT, idx);
|
|
case InstructionKind::ABSS:
|
|
return make_2reg_op(i0, SimpleExpression::Kind::ABS_S, idx);
|
|
case InstructionKind::NEGS:
|
|
return make_2reg_op(i0, SimpleExpression::Kind::NEG_S, idx);
|
|
case InstructionKind::SQRTS:
|
|
return make_2reg_op(i0, SimpleExpression::Kind::SQRT_S, idx);
|
|
case InstructionKind::MOVS:
|
|
return make_2reg_op(i0, SimpleExpression::Kind::IDENTITY, idx);
|
|
case InstructionKind::MOVN:
|
|
case InstructionKind::MOVZ:
|
|
return convert_cmov_1(i0, idx);
|
|
case InstructionKind::BGTZL:
|
|
return convert_likely_branch_1(i0, IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED, true, idx,
|
|
force_asm_branch);
|
|
case InstructionKind::BGEZL:
|
|
return convert_likely_branch_1(i0, IR2_Condition::Kind::GEQ_ZERO_SIGNED, true, idx,
|
|
force_asm_branch);
|
|
case InstructionKind::BLTZL:
|
|
return convert_likely_branch_1(i0, IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED, true, idx,
|
|
force_asm_branch);
|
|
case InstructionKind::BEQL:
|
|
return convert_beql_1(i0, idx, true, force_asm_branch);
|
|
case InstructionKind::BNEL:
|
|
return convert_bnel_1(i0, idx, true, force_asm_branch);
|
|
case InstructionKind::SUBU:
|
|
return convert_subu_1(i0, idx); // may fail
|
|
default:
|
|
return nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
///////////////////////
|
|
// OP 2 Conversions
|
|
//////////////////////
|
|
|
|
std::unique_ptr<AtomicOp> convert_fp_branch_asm(const Instruction& i0,
|
|
const Instruction& i1,
|
|
IR2_Condition::Kind kind,
|
|
int idx) {
|
|
if (i1.kind == InstructionKind::BC1TL || i1.kind == InstructionKind::BC1FL) {
|
|
IR2_Condition condition(kind, make_src_atom(i0.get_src(0).get_reg(), idx),
|
|
make_src_atom(i0.get_src(1).get_reg(), idx));
|
|
if (i1.kind == InstructionKind::BC1FL) {
|
|
condition.invert();
|
|
}
|
|
// return make_branch(condition, i2, false, i1.get_src(0).get_label(), idx);
|
|
return make_asm_branch_no_delay(condition, true, i1.get_src(0).get_label(), idx);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_division_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
bool is_signed) {
|
|
if (i1.kind == InstructionKind::MFLO) {
|
|
// divide
|
|
auto src = make_2reg_expr(
|
|
i0, is_signed ? SimpleExpression::Kind::DIV_SIGNED : SimpleExpression::Kind::DIV_UNSIGNED,
|
|
idx);
|
|
return std::make_unique<SetVarOp>(make_dst_var(i1, idx), src, idx);
|
|
} else if (i1.kind == InstructionKind::MFHI) {
|
|
// mod
|
|
auto src = make_2reg_expr(
|
|
i0, is_signed ? SimpleExpression::Kind::MOD_SIGNED : SimpleExpression::Kind::MOD_UNSIGNED,
|
|
idx);
|
|
return std::make_unique<SetVarOp>(make_dst_var(i1, idx), src, idx);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_jalr_2(const Instruction& i0, const Instruction& i1, int idx) {
|
|
if (i0.kind == InstructionKind::JALR && i0.get_dst(0).is_reg(rra()) &&
|
|
i0.get_src(0).is_reg(rt9()) && is_gpr_2_imm_int(i1, InstructionKind::SLL, rv0(), rra(), 0)) {
|
|
return std::make_unique<CallOp>(idx);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_bne_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
bool likely,
|
|
bool force_asm,
|
|
GameVersion version) {
|
|
auto s0 = i0.get_src(0).get_reg();
|
|
auto s1 = i0.get_src(1).get_reg();
|
|
auto dest = i0.get_src(2).get_label();
|
|
IR2_Condition condition;
|
|
if (s1 == rr0()) {
|
|
condition = IR2_Condition(IR2_Condition::Kind::NONZERO, make_src_atom(s0, idx));
|
|
} else if (i0.get_src(0).is_reg(rs7())) {
|
|
condition = IR2_Condition(IR2_Condition::Kind::TRUTHY, make_src_atom(s1, idx));
|
|
} else if (s1 == rs7()) {
|
|
// likely a case where somebody wrote (= x #f) or (!= x #f). much rarer than the flipped one
|
|
condition = IR2_Condition(IR2_Condition::Kind::NOT_EQUAL, make_src_atom(s0, idx),
|
|
SimpleAtom::make_sym_val("#f"));
|
|
} else {
|
|
condition = IR2_Condition(IR2_Condition::Kind::NOT_EQUAL, make_src_atom(s0, idx),
|
|
make_src_atom(s1, idx));
|
|
condition.make_flipped();
|
|
}
|
|
return make_branch(condition, i1, likely, dest, idx, force_asm, version);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_beq_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
bool likely,
|
|
bool force_asm,
|
|
GameVersion version) {
|
|
auto s0 = i0.get_src(0).get_reg();
|
|
auto s1 = i0.get_src(1).get_reg();
|
|
auto dest = i0.get_src(2).get_label();
|
|
IR2_Condition condition;
|
|
if (s0 == rr0() && s1 == rr0()) {
|
|
condition = IR2_Condition(IR2_Condition::Kind::ALWAYS);
|
|
} else if (s1 == rr0()) {
|
|
condition = IR2_Condition(IR2_Condition::Kind::ZERO, make_src_atom(s0, idx));
|
|
} else if (i0.get_src(0).is_reg(rs7())) {
|
|
if (s1 == rs7()) {
|
|
// (if #f ...) type code?
|
|
condition = IR2_Condition(IR2_Condition::Kind::FALSE, SimpleAtom::make_sym_val("#f"));
|
|
} else {
|
|
condition = IR2_Condition(IR2_Condition::Kind::FALSE, make_src_atom(s1, idx));
|
|
}
|
|
} else if (s1 == rs7()) {
|
|
// likely a case where somebody wrote (= x #f) or (!= x #f). much rarer than the flipped one
|
|
condition = IR2_Condition(IR2_Condition::Kind::EQUAL, make_src_atom(s0, idx),
|
|
SimpleAtom::make_sym_val("#f"));
|
|
} else {
|
|
condition =
|
|
IR2_Condition(IR2_Condition::Kind::EQUAL, make_src_atom(s0, idx), make_src_atom(s1, idx));
|
|
condition.make_flipped();
|
|
}
|
|
return make_branch(condition, i1, likely, dest, idx, force_asm, version);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_daddiu_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
GameVersion version) {
|
|
// daddiu dest, s7, 8
|
|
// mov{n,z} dest, s7, src
|
|
if (i1.kind == InstructionKind::MOVN || i1.kind == InstructionKind::MOVZ) {
|
|
auto dest = i0.get_dst(0).get_reg();
|
|
auto src = i1.get_src(1).get_reg();
|
|
if (!i0.get_src(0).is_reg(rs7())) {
|
|
return nullptr;
|
|
}
|
|
ASSERT(i0.get_src(0).is_reg(rs7()));
|
|
ASSERT(i0.get_src(1).is_imm(true_symbol_offset(version)));
|
|
ASSERT(i1.get_dst(0).is_reg(dest));
|
|
ASSERT(i1.get_src(0).is_reg(rs7()));
|
|
auto kind =
|
|
i1.kind == InstructionKind::MOVN ? IR2_Condition::Kind::ZERO : IR2_Condition::Kind::NONZERO;
|
|
return std::make_unique<SetVarConditionOp>(make_dst_var(dest, idx),
|
|
IR2_Condition(kind, make_src_atom(src, idx)), idx);
|
|
}
|
|
|
|
// daddiu v1, v1, L152
|
|
// daddu v1, v1, fp
|
|
if (i1.kind == InstructionKind::DADDU && i0.get_src(1).is_label()) {
|
|
auto dest = i0.get_src(0).get_reg();
|
|
if (!i0.get_src(0).is_reg(dest)) {
|
|
return nullptr;
|
|
}
|
|
if (!i1.get_src(0).is_reg(dest)) {
|
|
return nullptr;
|
|
}
|
|
if (!i1.get_src(0).is_reg(dest)) {
|
|
return nullptr;
|
|
}
|
|
if (!i1.get_src(1).is_reg(rfp())) {
|
|
return nullptr;
|
|
}
|
|
auto stat = SimpleAtom::make_static_address(i0.get_src(1).get_label());
|
|
auto expr = SimpleExpression(SimpleExpression::Kind::ADD, make_src_atom(dest, idx), stat);
|
|
return std::make_unique<SetVarOp>(make_dst_var(dest, idx), expr, idx);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_lui_2(const Instruction& i0, const Instruction& i1, int idx) {
|
|
if (i1.kind == InstructionKind::ORI) {
|
|
// lui temp, <>
|
|
// ori dst, temp, <>
|
|
// possibly temp = dst.
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
if (i1.get_src(0).get_reg() != temp) {
|
|
return nullptr;
|
|
}
|
|
auto dst = i1.get_dst(0).get_reg();
|
|
|
|
SimpleAtom src;
|
|
if (i0.get_src(0).is_imm() && i1.get_src(1).is_imm()) {
|
|
src = SimpleAtom::make_int_constant(s64(i1.get_src(1).get_imm()) +
|
|
(s64(i0.get_src(0).get_imm()) << 16));
|
|
} else if (i0.get_src(0).is_label() && i1.get_src(1).is_label()) {
|
|
auto label = i0.get_src(0).get_label();
|
|
ASSERT(label == i1.get_src(1).get_label());
|
|
src = SimpleAtom::make_static_address(label);
|
|
}
|
|
|
|
auto result = std::make_unique<SetVarOp>(make_dst_var(dst, idx), src.as_expr(), idx);
|
|
if (temp != dst) {
|
|
result->add_clobber_reg(temp);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_slt_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
bool is_signed) {
|
|
// this is to do a min or max.
|
|
// possibly due to a GOAL compiler bug, the output always goes in left and there is always
|
|
// a clobbered register. Likely there was a swapped register allocation setup here.
|
|
// it doesn't generate wrong code, just not optimal.
|
|
// slt temp, left, right
|
|
// mov{n,z} left, right, temp
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
auto left = i0.get_src(0).get_reg();
|
|
auto right = i0.get_src(1).get_reg();
|
|
if (temp == left) {
|
|
return nullptr;
|
|
}
|
|
ASSERT(temp != left);
|
|
ASSERT(temp != right);
|
|
ASSERT(left != right);
|
|
std::unique_ptr<AtomicOp> result;
|
|
SimpleExpression::Kind kind;
|
|
if (is_gpr_3(i1, InstructionKind::MOVZ, left, right, temp)) {
|
|
kind = is_signed ? SimpleExpression::Kind::MIN_SIGNED : SimpleExpression::Kind::MIN_UNSIGNED;
|
|
} else if (is_gpr_3(i1, InstructionKind::MOVN, left, right, temp)) {
|
|
kind = is_signed ? SimpleExpression::Kind::MAX_SIGNED : SimpleExpression::Kind::MAX_UNSIGNED;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
result = std::make_unique<SetVarOp>(
|
|
make_dst_var(left, idx),
|
|
SimpleExpression(kind, make_src_atom(left, idx), make_src_atom(right, idx)), idx);
|
|
result->add_clobber_reg(temp);
|
|
return result;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_bltz_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
GameVersion version) {
|
|
// bltz is never emitted outside of inline asm.
|
|
auto dest = i0.get_src(1).get_label();
|
|
return make_asm_branch(IR2_Condition(IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED,
|
|
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
|
i1, false, dest, idx, version);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_bgez_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
GameVersion version) {
|
|
// bgez is never emitted outside of inline asm.
|
|
auto dest = i0.get_src(1).get_label();
|
|
return make_asm_branch(IR2_Condition(IR2_Condition::Kind::GEQ_ZERO_SIGNED,
|
|
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
|
i1, false, dest, idx, version);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_blez_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
GameVersion version) {
|
|
// blez is never emitted outside of inline asm.
|
|
auto dest = i0.get_src(1).get_label();
|
|
return make_asm_branch(IR2_Condition(IR2_Condition::Kind::LEQ_ZERO_SIGNED,
|
|
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
|
i1, false, dest, idx, version);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_bgtz_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
GameVersion version) {
|
|
// bgtz is never emitted outside of inline asm.
|
|
auto dest = i0.get_src(1).get_label();
|
|
return make_asm_branch(IR2_Condition(IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED,
|
|
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
|
i1, false, dest, idx, version);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_2(const Instruction& i0,
|
|
const Instruction& i1,
|
|
int idx,
|
|
bool force_asm_branch,
|
|
GameVersion version) {
|
|
switch (i0.kind) {
|
|
case InstructionKind::DIV:
|
|
return convert_division_2(i0, i1, idx, true);
|
|
case InstructionKind::DIVU:
|
|
return convert_division_2(i0, i1, idx, false);
|
|
case InstructionKind::JALR:
|
|
return convert_jalr_2(i0, i1, idx);
|
|
case InstructionKind::BNE:
|
|
return convert_bne_2(i0, i1, idx, false, force_asm_branch, version);
|
|
case InstructionKind::BEQ:
|
|
return convert_beq_2(i0, i1, idx, false, force_asm_branch, version);
|
|
case InstructionKind::DADDIU:
|
|
return convert_daddiu_2(i0, i1, idx, version);
|
|
case InstructionKind::LUI:
|
|
return convert_lui_2(i0, i1, idx);
|
|
case InstructionKind::SLT:
|
|
return convert_slt_2(i0, i1, idx, true);
|
|
case InstructionKind::SLTU:
|
|
return convert_slt_2(i0, i1, idx, false);
|
|
case InstructionKind::CLTS:
|
|
return convert_fp_branch_asm(i0, i1, IR2_Condition::Kind::FLOAT_LESS_THAN, idx);
|
|
case InstructionKind::BLTZ:
|
|
return convert_bltz_2(i0, i1, idx, version);
|
|
case InstructionKind::BGEZ:
|
|
return convert_bgez_2(i0, i1, idx, version);
|
|
case InstructionKind::BGTZ:
|
|
return convert_bgtz_2(i0, i1, idx, version);
|
|
case InstructionKind::BLEZ:
|
|
return convert_blez_2(i0, i1, idx, version);
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
///////////////////////
|
|
// OP 3 Conversions
|
|
//////////////////////
|
|
|
|
std::unique_ptr<AtomicOp> convert_lui_3(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
int idx) {
|
|
if (i1.kind == InstructionKind::ORI && i0.get_src(0).is_label() && i1.get_src(1).is_label() &&
|
|
is_gpr_3(i2, InstructionKind::ADDU, {}, rfp(), {})) {
|
|
// lui temp, <>
|
|
// ori dst, temp, <>
|
|
// addu dst, fp, dst
|
|
ASSERT(i0.get_dst(0).get_reg() == i1.get_src(0).get_reg()); // temp
|
|
ASSERT(i0.get_src(0).get_label() == i1.get_src(1).get_label()); // labels
|
|
ASSERT(i2.get_dst(0).get_reg() == i2.get_src(1).get_reg()); // dst
|
|
ASSERT(i2.get_dst(0).get_reg() == i1.get_dst(0).get_reg()); // dst
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
auto dst = i2.get_dst(0).get_reg();
|
|
auto label = i0.get_src(0).get_label();
|
|
auto result = std::make_unique<SetVarOp>(make_dst_var(dst, idx),
|
|
SimpleAtom::make_static_address(label).as_expr(), idx);
|
|
if (dst != temp) {
|
|
result->add_clobber_reg(temp);
|
|
}
|
|
return result;
|
|
} else if (i1.kind == InstructionKind::ORI && i1.get_src(1).is_label() &&
|
|
is_gpr_3(i2, InstructionKind::DADDU, {}, {}, rfp())) {
|
|
// lui temp, <>
|
|
// ori temp, temp, <>
|
|
// daddu dst, temp, fp
|
|
ASSERT(i0.get_dst(0).get_reg() == i1.get_src(0).get_reg()); // temp
|
|
ASSERT(i0.get_src(0).get_label() == i1.get_src(1).get_label()); // labels
|
|
ASSERT(i0.get_dst(0).get_reg() == i1.get_dst(0).get_reg()); // temp
|
|
ASSERT(i2.get_src(0).get_reg() == i0.get_dst(0).get_reg()); // temp
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
auto dst = i2.get_dst(0).get_reg();
|
|
auto label = i0.get_src(0).get_label();
|
|
auto result = std::make_unique<SetVarOp>(make_dst_var(dst, idx),
|
|
SimpleAtom::make_static_address(label).as_expr(), idx);
|
|
if (dst != temp) {
|
|
result->add_clobber_reg(temp);
|
|
}
|
|
return result;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_dsubu_3(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
int idx,
|
|
GameVersion version) {
|
|
if (i1.kind == InstructionKind::DADDIU &&
|
|
(i2.kind == InstructionKind::MOVN || i2.kind == InstructionKind::MOVZ)) {
|
|
// dsubu temp, a, b
|
|
// daddiu dst, s7, 8
|
|
// mov{n,z} dst, s7, temp
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
auto a = i0.get_src(0).get_reg();
|
|
auto b = i0.get_src(1).get_reg();
|
|
auto dest = i1.get_dst(0).get_reg();
|
|
ASSERT(i1.get_src(0).is_reg(rs7()));
|
|
ASSERT(i1.get_src(1).is_imm(true_symbol_offset(version)));
|
|
ASSERT(i2.get_dst(0).get_reg() == dest);
|
|
ASSERT(i2.get_src(0).is_reg(rs7()));
|
|
ASSERT(i2.get_src(1).get_reg() == temp);
|
|
ASSERT(temp != dest);
|
|
auto kind = i2.kind == InstructionKind::MOVN ? IR2_Condition::Kind::EQUAL
|
|
: IR2_Condition::Kind::NOT_EQUAL;
|
|
std::unique_ptr<AtomicOp> result;
|
|
if (b == rs7()) {
|
|
// some sort of not gone wrong?
|
|
result = std::make_unique<SetVarConditionOp>(
|
|
make_dst_var(dest, idx),
|
|
IR2_Condition(kind, make_src_atom(a, idx), SimpleAtom::make_sym_val("#f")), idx);
|
|
} else if (b == rr0()) {
|
|
// not the greatest codegen...
|
|
result = std::make_unique<SetVarConditionOp>(
|
|
make_dst_var(dest, idx),
|
|
IR2_Condition(kind, make_src_atom(a, idx), SimpleAtom::make_int_constant(0)), idx);
|
|
} else {
|
|
result = std::make_unique<SetVarConditionOp>(
|
|
make_dst_var(dest, idx),
|
|
IR2_Condition(kind, make_src_atom(a, idx), make_src_atom(b, idx)), idx);
|
|
}
|
|
|
|
result->add_clobber_reg(temp);
|
|
return result;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void add_clobber_if_unwritten(AtomicOp& op, Register clobber) {
|
|
op.update_register_info();
|
|
std::vector<Register> clobber_regs = op.clobber_regs();
|
|
if (std::find(op.write_regs().begin(), op.write_regs().end(), clobber) == op.write_regs().end()) {
|
|
clobber_regs.push_back(clobber);
|
|
}
|
|
op.clear_register_info();
|
|
for (auto& reg : clobber_regs) {
|
|
op.add_clobber_reg(reg);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_slt_3(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
bool is_signed,
|
|
int idx,
|
|
GameVersion version) {
|
|
auto s0 = i0.get_src(0).get_reg();
|
|
auto s1 = i0.get_src(1).get_reg();
|
|
std::unique_ptr<AtomicOp> result;
|
|
if (i1.kind == InstructionKind::BNE || i1.kind == InstructionKind::BEQ) {
|
|
// assume bne, invert at the end if it's beq
|
|
// slt temp, a0, a1
|
|
// bne temp, r0, dest
|
|
// delay slot
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
auto dest = i1.get_src(2).get_label();
|
|
ASSERT(i1.get_src(0).get_reg() == temp);
|
|
ASSERT(i1.get_src(1).is_reg(rr0()));
|
|
|
|
IR2_Condition condition;
|
|
if (s1 == rr0()) {
|
|
// ????
|
|
auto kind = is_signed ? IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED
|
|
: IR2_Condition::Kind::LESS_THAN_ZERO_UNSIGNED;
|
|
condition = IR2_Condition(kind, make_src_atom(s0, idx));
|
|
} else if (s0 == rr0()) {
|
|
auto kind = is_signed ? IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED
|
|
: IR2_Condition::Kind::GREATER_THAN_ZERO_UNSIGNED;
|
|
condition = IR2_Condition(kind, make_src_atom(s1, idx));
|
|
} else {
|
|
auto kind = is_signed ? IR2_Condition::Kind::LESS_THAN_SIGNED
|
|
: IR2_Condition::Kind::LESS_THAN_UNSIGNED;
|
|
condition = IR2_Condition(kind, make_src_atom(s0, idx), make_src_atom(s1, idx));
|
|
}
|
|
|
|
if (i1.kind == InstructionKind::BEQ) {
|
|
condition.invert();
|
|
}
|
|
result = make_branch(condition, i2, false, dest, idx, false, version);
|
|
add_clobber_if_unwritten(*result, temp);
|
|
return result;
|
|
} else if (i1.kind == InstructionKind::DADDIU &&
|
|
(i2.kind == InstructionKind::MOVZ || i2.kind == InstructionKind::MOVN)) {
|
|
// all this assumes movz. Then at the end we invert it if it's actually a movn
|
|
// slt temp, a0, a1
|
|
// daddiu dest, s7, 8
|
|
// movz dest, s7, temp
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
auto dest = i1.get_dst(0).get_reg();
|
|
ASSERT(i1.get_src(0).is_reg(rs7()));
|
|
ASSERT(i1.get_src(1).is_imm(true_symbol_offset(version)));
|
|
ASSERT(i2.get_dst(0).get_reg() == dest);
|
|
ASSERT(i2.get_src(0).is_reg(rs7()));
|
|
ASSERT(i2.get_src(1).get_reg() == temp);
|
|
ASSERT(temp != dest);
|
|
IR2_Condition condition;
|
|
if (s1 == rr0()) {
|
|
auto kind = is_signed ? IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED
|
|
: IR2_Condition::Kind::LESS_THAN_ZERO_UNSIGNED;
|
|
// < 0
|
|
condition = IR2_Condition(kind, make_src_atom(s0, idx));
|
|
} else if (s0 == rr0()) {
|
|
auto kind = is_signed ? IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED
|
|
: IR2_Condition::Kind::GREATER_THAN_ZERO_UNSIGNED;
|
|
condition = IR2_Condition(kind, make_src_atom(s1, idx));
|
|
} else {
|
|
auto kind = is_signed ? IR2_Condition::Kind::LESS_THAN_SIGNED
|
|
: IR2_Condition::Kind::LESS_THAN_UNSIGNED;
|
|
condition = IR2_Condition(kind, make_src_atom(s0, idx), make_src_atom(s1, idx));
|
|
}
|
|
if (i2.kind == InstructionKind::MOVN) {
|
|
condition.invert();
|
|
}
|
|
result = std::make_unique<SetVarConditionOp>(make_dst_var(dest, idx), condition, idx);
|
|
add_clobber_if_unwritten(*result, temp);
|
|
return result;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_slti_3(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
bool is_signed,
|
|
int idx,
|
|
GameVersion version) {
|
|
auto s0 = i0.get_src(0).get_reg();
|
|
auto s1 = SimpleAtom::make_int_constant(i0.get_src(1).get_imm());
|
|
std::unique_ptr<AtomicOp> result;
|
|
if (i1.kind == InstructionKind::BNE || i1.kind == InstructionKind::BEQ) {
|
|
// assume bne, invert at the end if it's beq
|
|
// slt temp, a0, <>
|
|
// bne temp, r0, dest
|
|
// delay slot
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
auto dest = i1.get_src(2).get_label();
|
|
ASSERT(i1.get_src(0).get_reg() == temp);
|
|
ASSERT(i1.get_src(1).is_reg(rr0()));
|
|
auto kind =
|
|
is_signed ? IR2_Condition::Kind::LESS_THAN_SIGNED : IR2_Condition::Kind::LESS_THAN_UNSIGNED;
|
|
auto condition = IR2_Condition(kind, make_src_atom(s0, idx), s1);
|
|
if (i1.kind == InstructionKind::BEQ) {
|
|
condition.invert();
|
|
}
|
|
result = make_branch(condition, i2, false, dest, idx, false, version);
|
|
add_clobber_if_unwritten(*result, temp);
|
|
return result;
|
|
} else if (i1.kind == InstructionKind::DADDIU &&
|
|
(i2.kind == InstructionKind::MOVZ || i2.kind == InstructionKind::MOVN)) {
|
|
// all this assumes movz. Then at the end we invert it if it's actually a movn
|
|
// slt temp, a0, <>
|
|
// daddiu dest, s7, 8
|
|
// movz dest, s7, temp
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
auto dest = i1.get_dst(0).get_reg();
|
|
ASSERT(i1.get_src(0).is_reg(rs7()));
|
|
ASSERT(i1.get_src(1).is_imm(true_symbol_offset(version)));
|
|
ASSERT(i2.get_dst(0).get_reg() == dest);
|
|
ASSERT(i2.get_src(0).is_reg(rs7()));
|
|
ASSERT(i2.get_src(1).get_reg() == temp);
|
|
ASSERT(temp != dest);
|
|
IR2_Condition condition;
|
|
|
|
auto kind =
|
|
is_signed ? IR2_Condition::Kind::LESS_THAN_SIGNED : IR2_Condition::Kind::LESS_THAN_UNSIGNED;
|
|
condition = IR2_Condition(kind, make_src_atom(s0, idx), s1);
|
|
if (i2.kind == InstructionKind::MOVN) {
|
|
condition.invert();
|
|
}
|
|
result = std::make_unique<SetVarConditionOp>(make_dst_var(dest, idx), condition, idx);
|
|
add_clobber_if_unwritten(*result, temp);
|
|
return result;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_fp_branch(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
IR2_Condition::Kind kind,
|
|
int idx,
|
|
GameVersion version) {
|
|
if (i1.kind == InstructionKind::BC1T || i1.kind == InstructionKind::BC1F) {
|
|
IR2_Condition condition(kind, make_src_atom(i0.get_src(0).get_reg(), idx),
|
|
make_src_atom(i0.get_src(1).get_reg(), idx));
|
|
if (i1.kind == InstructionKind::BC1F) {
|
|
condition.invert();
|
|
}
|
|
return make_branch(condition, i2, false, i1.get_src(0).get_label(), idx, false, version);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_3(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
int idx,
|
|
GameVersion version) {
|
|
switch (i0.kind) {
|
|
case InstructionKind::LUI:
|
|
return convert_lui_3(i0, i1, i2, idx);
|
|
case InstructionKind::DSUBU:
|
|
return convert_dsubu_3(i0, i1, i2, idx, version);
|
|
case InstructionKind::SLT:
|
|
return convert_slt_3(i0, i1, i2, true, idx, version);
|
|
case InstructionKind::SLTU:
|
|
return convert_slt_3(i0, i1, i2, false, idx, version);
|
|
case InstructionKind::SLTI:
|
|
return convert_slti_3(i0, i1, i2, true, idx, version);
|
|
case InstructionKind::SLTIU:
|
|
return convert_slti_3(i0, i1, i2, false, idx, version);
|
|
case InstructionKind::CEQS:
|
|
return convert_fp_branch(i0, i1, i2, IR2_Condition::Kind::FLOAT_EQUAL, idx, version);
|
|
case InstructionKind::CLTS:
|
|
return convert_fp_branch(i0, i1, i2, IR2_Condition::Kind::FLOAT_LESS_THAN, idx, version);
|
|
case InstructionKind::CLES:
|
|
return convert_fp_branch(i0, i1, i2, IR2_Condition::Kind::FLOAT_LEQ, idx, version);
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
///////////////////////
|
|
// OP 4 Conversions
|
|
//////////////////////
|
|
|
|
std::unique_ptr<AtomicOp> convert_dsll32_4(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
int idx,
|
|
GameVersion version) {
|
|
if (i1.kind == InstructionKind::SLT && i2.kind == InstructionKind::BEQ) {
|
|
// dsll32 temp, a0, 30
|
|
// slt temp, temp, r0
|
|
// beq temp, r0, <>
|
|
// delay
|
|
|
|
auto temp = i0.get_dst(0).get_reg();
|
|
auto arg = i0.get_src(0).get_reg();
|
|
auto sa = i0.get_src(1).get_imm();
|
|
// 30 = 64 - (32 + log2(0b10) + 1)
|
|
if (sa != 30) {
|
|
return nullptr;
|
|
}
|
|
ASSERT(i1.get_dst(0).get_reg() == temp);
|
|
ASSERT(i1.get_src(0).get_reg() == temp);
|
|
ASSERT(i1.get_src(1).is_reg(rr0()));
|
|
ASSERT(i2.get_src(0).get_reg() == temp);
|
|
ASSERT(i2.get_src(1).is_reg(rr0()));
|
|
|
|
IR2_Condition condition(IR2_Condition::Kind::IS_NOT_PAIR, make_src_atom(arg, idx));
|
|
auto result = make_branch(condition, i3, false, i2.get_src(2).get_label(), idx, false, version);
|
|
result->add_clobber_reg(temp);
|
|
return result;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_fp_branch_with_nop(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
IR2_Condition::Kind kind,
|
|
int idx,
|
|
GameVersion version) {
|
|
if (i1.kind != InstructionKind::VNOP) {
|
|
return nullptr;
|
|
}
|
|
if (i2.kind == InstructionKind::BC1T || i2.kind == InstructionKind::BC1F) {
|
|
IR2_Condition condition(kind, make_src_atom(i0.get_src(0).get_reg(), idx),
|
|
make_src_atom(i0.get_src(1).get_reg(), idx));
|
|
if (i2.kind == InstructionKind::BC1F) {
|
|
condition.invert();
|
|
}
|
|
return make_branch(condition, i3, false, i2.get_src(0).get_label(), idx, false, version);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_4(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
int idx,
|
|
GameVersion version) {
|
|
switch (i0.kind) {
|
|
case InstructionKind::DSLL32:
|
|
return convert_dsll32_4(i0, i1, i2, i3, idx, version);
|
|
case InstructionKind::CEQS:
|
|
return convert_fp_branch_with_nop(i0, i1, i2, i3, IR2_Condition::Kind::FLOAT_EQUAL, idx,
|
|
version);
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
///////////////////////
|
|
// OP 5 Conversions
|
|
//////////////////////
|
|
|
|
std::unique_ptr<AtomicOp> convert_vector_plus(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
const Instruction& i4,
|
|
int idx) {
|
|
// vmove.w vf6, vf0
|
|
if (i0.kind != InstructionKind::VMOVE || i0.get_src(0).get_reg() != make_vf(0) ||
|
|
i0.get_dst(0).get_reg() != make_vf(6) || i0.cop2_dest != 1) {
|
|
return nullptr;
|
|
}
|
|
|
|
// lqc2 vf4, 0(a1) (src1)
|
|
if (i1.kind != InstructionKind::LQC2 || i1.get_dst(0).get_reg() != make_vf(4) ||
|
|
!i1.get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register src1 = i1.get_src(1).get_reg();
|
|
|
|
// lqc2 vf5, 0(a2) (src2)
|
|
if (i2.kind != InstructionKind::LQC2 || i2.get_dst(0).get_reg() != make_vf(5) ||
|
|
!i2.get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register src2 = i2.get_src(1).get_reg();
|
|
|
|
// vadd.xyz vf6, vf4, vf5
|
|
if (i3.kind != InstructionKind::VADD || i3.get_dst(0).get_reg() != make_vf(6) ||
|
|
i3.get_src(0).get_reg() != make_vf(4) || i3.get_src(1).get_reg() != make_vf(5) ||
|
|
i3.cop2_dest != 14) {
|
|
return nullptr;
|
|
}
|
|
|
|
// sqc2 vf6, 0(a0) (dst)
|
|
if (i4.kind != InstructionKind::SQC2 || i4.get_src(0).get_reg() != make_vf(6) ||
|
|
!i4.get_src(1).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register dst = i4.get_src(2).get_reg();
|
|
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_PLUS, make_src_atom(dst, idx),
|
|
make_src_atom(src1, idx), make_src_atom(src2, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_vector_xyz_product(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
const Instruction& i4,
|
|
int idx) {
|
|
/*
|
|
lqc2 vf4, 0(a1)
|
|
lqc2 vf5, 0(a2)
|
|
vaddx.w vf6, vf0, vf0
|
|
vmul.xyz vf6, vf4, vf5
|
|
sqc2 vf6, 0(a0)
|
|
*/
|
|
|
|
// lqc2 vf4, 0(a1) (src1)
|
|
if (i0.kind != InstructionKind::LQC2 || i0.get_dst(0).get_reg() != make_vf(4) ||
|
|
!i0.get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register src1 = i0.get_src(1).get_reg();
|
|
|
|
// lqc2 vf5, 0(a2) (src2)
|
|
if (i1.kind != InstructionKind::LQC2 || i1.get_dst(0).get_reg() != make_vf(5) ||
|
|
!i1.get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register src2 = i1.get_src(1).get_reg();
|
|
|
|
// vaddx.w vf6, vf0, vf0
|
|
if (i2.kind != InstructionKind::VADD_BC || i2.get_dst(0).get_reg() != make_vf(6) ||
|
|
i2.get_src(0).get_reg() != make_vf(0) || i2.get_src(1).get_reg() != make_vf(0) ||
|
|
i2.cop2_dest != 1 || i2.cop2_bc != 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
// vmul.xyz vf6, vf4, vf5
|
|
if (i3.kind != InstructionKind::VMUL || i3.get_dst(0).get_reg() != make_vf(6) ||
|
|
i3.get_src(0).get_reg() != make_vf(4) || i3.get_src(1).get_reg() != make_vf(5) ||
|
|
i3.cop2_dest != 14) {
|
|
return nullptr;
|
|
}
|
|
|
|
// sqc2 vf6, 0(a0) (dst)
|
|
if (i4.kind != InstructionKind::SQC2 || i4.get_src(0).get_reg() != make_vf(6) ||
|
|
!i4.get_src(1).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register dst = i4.get_src(2).get_reg();
|
|
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_XYZ_PRODUCT, make_src_atom(dst, idx),
|
|
make_src_atom(src1, idx), make_src_atom(src2, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_vector_minus(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
const Instruction& i4,
|
|
int idx) {
|
|
// lqc2 vf4, 0(a1) (src1)
|
|
if (i0.kind != InstructionKind::LQC2 || i0.get_dst(0).get_reg() != make_vf(4) ||
|
|
!i0.get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register src1 = i0.get_src(1).get_reg();
|
|
|
|
// lqc2 vf5, 0(a2) (src2)
|
|
if (i1.kind != InstructionKind::LQC2 || i1.get_dst(0).get_reg() != make_vf(5) ||
|
|
!i1.get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register src2 = i1.get_src(1).get_reg();
|
|
|
|
// vmove.w vf6, vf0
|
|
if (i2.kind != InstructionKind::VMOVE || i2.get_src(0).get_reg() != make_vf(0) ||
|
|
i2.get_dst(0).get_reg() != make_vf(6) || i2.cop2_dest != 1) {
|
|
return nullptr;
|
|
}
|
|
|
|
// vadd.xyz vf6, vf4, vf5
|
|
if (i3.kind != InstructionKind::VSUB || i3.get_dst(0).get_reg() != make_vf(6) ||
|
|
i3.get_src(0).get_reg() != make_vf(4) || i3.get_src(1).get_reg() != make_vf(5) ||
|
|
i3.cop2_dest != 14) {
|
|
return nullptr;
|
|
}
|
|
|
|
// sqc2 vf6, 0(a0) (dst)
|
|
if (i4.kind != InstructionKind::SQC2 || i4.get_src(0).get_reg() != make_vf(6) ||
|
|
!i4.get_src(1).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register dst = i4.get_src(2).get_reg();
|
|
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_MINUS, make_src_atom(dst, idx),
|
|
make_src_atom(src1, idx), make_src_atom(src2, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_vector_cross(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
const Instruction& i4,
|
|
int idx) {
|
|
// lqc2 vf1, 0(v1) (src1)
|
|
if (i0.kind != InstructionKind::LQC2 || i0.get_dst(0).get_reg() != make_vf(1) ||
|
|
!i0.get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register src1 = i0.get_src(1).get_reg();
|
|
|
|
// lqc2 vf5, 0(a2) (src2)
|
|
if (i1.kind != InstructionKind::LQC2 || i1.get_dst(0).get_reg() != make_vf(2) ||
|
|
!i1.get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register src2 = i1.get_src(1).get_reg();
|
|
|
|
// vopmula.xyz acc, vf1, vf2
|
|
if (i2.kind != InstructionKind::VOPMULA || i2.get_src(0).get_reg() != make_vf(1) ||
|
|
i2.get_src(1).get_reg() != make_vf(2) || i2.cop2_dest != 14) {
|
|
return nullptr;
|
|
}
|
|
|
|
// vopmsub.xyz vf3, vf2, vf1
|
|
if (i3.kind != InstructionKind::VOPMSUB || i3.get_dst(0).get_reg() != make_vf(3) ||
|
|
i3.get_src(0).get_reg() != make_vf(2) || i3.get_src(1).get_reg() != make_vf(1) ||
|
|
i3.cop2_dest != 14) {
|
|
return nullptr;
|
|
}
|
|
|
|
// sqc2 vf3, 0(a0)
|
|
if (i4.kind != InstructionKind::SQC2 || i4.get_src(0).get_reg() != make_vf(3) ||
|
|
!i4.get_src(1).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register dst = i4.get_src(2).get_reg();
|
|
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_CROSS, make_src_atom(dst, idx),
|
|
make_src_atom(src1, idx), make_src_atom(src2, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_5(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
const Instruction& i4,
|
|
int idx,
|
|
GameVersion version) {
|
|
auto s6 = make_gpr(Reg::S6);
|
|
|
|
int process_offset = -1;
|
|
switch (version) {
|
|
case GameVersion::Jak1:
|
|
process_offset = 44;
|
|
break;
|
|
case GameVersion::Jak2:
|
|
case GameVersion::Jak3:
|
|
case GameVersion::JakX:
|
|
process_offset = 48;
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
if (i0.kind == InstructionKind::LWU && i0.get_dst(0).is_reg(s6) &&
|
|
i0.get_src(0).get_imm() == process_offset && i0.get_src(1).is_reg(s6) &&
|
|
i1.kind == InstructionKind::MTLO1 && i1.get_src(0).is_reg(s6) &&
|
|
i2.kind == InstructionKind::LWU && i2.get_dst(0).is_reg(s6) &&
|
|
i2.get_src(0).get_imm() == 12 && i2.get_src(1).is_reg(s6) &&
|
|
i3.kind == InstructionKind::JALR && i3.get_dst(0).is_reg(make_gpr(Reg::RA)) &&
|
|
i3.get_src(0).is_reg(s6) && i4.kind == InstructionKind::MFLO1 && i4.get_dst(0).is_reg(s6)) {
|
|
return std::make_unique<SpecialOp>(SpecialOp::Kind::SUSPEND, idx);
|
|
}
|
|
|
|
auto as_vector_plus = convert_vector_plus(i0, i1, i2, i3, i4, idx);
|
|
if (as_vector_plus) {
|
|
return as_vector_plus;
|
|
}
|
|
|
|
auto as_vector_xyz = convert_vector_xyz_product(i0, i1, i2, i3, i4, idx);
|
|
if (as_vector_xyz) {
|
|
return as_vector_xyz;
|
|
}
|
|
|
|
auto as_vector_minus = convert_vector_minus(i0, i1, i2, i3, i4, idx);
|
|
if (as_vector_minus) {
|
|
return as_vector_minus;
|
|
}
|
|
|
|
auto as_vector_cross = convert_vector_cross(i0, i1, i2, i3, i4, idx);
|
|
if (as_vector_cross) {
|
|
return as_vector_cross;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_vector_float_product(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
const Instruction& i4,
|
|
const Instruction& i5,
|
|
int idx) {
|
|
// lqc2 vf1, 0(vect_in)
|
|
if (i0.kind != InstructionKind::LQC2 || i0.get_dst(0).get_reg() != make_vf(1) ||
|
|
!i0.get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register vec_src = i0.get_src(1).get_reg();
|
|
|
|
// mfc1 gpr_temp, float_in
|
|
if (i1.kind != InstructionKind::MFC1) {
|
|
return nullptr;
|
|
}
|
|
Register gpr_temp = i1.get_dst(0).get_reg();
|
|
Register float_src = i1.get_src(0).get_reg();
|
|
|
|
// qmtc2.i vf2, gpr_temp
|
|
if (i2.kind != InstructionKind::QMTC2 || i2.get_dst(0).get_reg() != make_vf(2) ||
|
|
i2.get_src(0).get_reg() != gpr_temp) {
|
|
return nullptr;
|
|
}
|
|
|
|
// vaddx.w vf1, vf0, vf0
|
|
if (i3.kind != InstructionKind::VADD_BC || i3.get_dst(0).get_reg() != make_vf(1) ||
|
|
i3.get_src(0).get_reg() != make_vf(0) || i3.get_src(1).get_reg() != make_vf(0) ||
|
|
i3.cop2_bc != 0 || i3.cop2_dest != 1) {
|
|
return nullptr;
|
|
}
|
|
|
|
// vmulx.xyz vf1, vf1, vf2
|
|
if (i4.kind != InstructionKind::VMUL_BC || i4.get_dst(0).get_reg() != make_vf(1) ||
|
|
i4.get_src(0).get_reg() != make_vf(1) || i4.get_src(1).get_reg() != make_vf(2) ||
|
|
i4.cop2_dest != 14 || i4.cop2_bc != 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
// sqc2 vf1, 0(gE)
|
|
if (i5.kind != InstructionKind::SQC2 || i5.get_src(0).get_reg() != make_vf(1) ||
|
|
!i5.get_src(1).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register dst = i5.get_src(2).get_reg();
|
|
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_FLOAT_PRODUCT, make_src_atom(dst, idx),
|
|
make_src_atom(vec_src, idx), make_src_atom(float_src, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_6(const Instruction& i0,
|
|
const Instruction& i1,
|
|
const Instruction& i2,
|
|
const Instruction& i3,
|
|
const Instruction& i4,
|
|
const Instruction& i5,
|
|
int idx) {
|
|
auto as_vector_float_product = convert_vector_float_product(i0, i1, i2, i3, i4, i5, idx);
|
|
if (as_vector_float_product) {
|
|
return as_vector_float_product;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_vector_length_squared(const Instruction* instrs, int idx) {
|
|
// 0: lqc2 vf1, 0(a0)
|
|
if (instrs[0].kind != InstructionKind::LQC2 || instrs[0].get_dst(0).get_reg() != make_vf(1) ||
|
|
!instrs[0].get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register vec_src = instrs[0].get_src(1).get_reg();
|
|
|
|
auto vf0 = make_vf(0);
|
|
auto vf1 = make_vf(1);
|
|
auto vf2 = make_vf(2);
|
|
|
|
// 1: vaddw.x vf2, vf0, vf0
|
|
if (instrs[1].kind != InstructionKind::VADD_BC || instrs[1].get_dst(0).get_reg() != vf2 ||
|
|
instrs[1].get_src(0).get_reg() != vf0 || instrs[1].get_src(1).get_reg() != vf0 ||
|
|
instrs[1].cop2_dest != 0b1000 || instrs[1].cop2_bc != 3) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 2: vmul.xyzw vf1, vf1, vf1
|
|
if (instrs[2].kind != InstructionKind::VMUL || instrs[2].get_dst(0).get_reg() != vf1 ||
|
|
instrs[2].get_src(0).get_reg() != vf1 || instrs[2].get_src(1).get_reg() != vf1 ||
|
|
instrs[2].cop2_dest != 0b1111) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 3: vmulax.x acc, vf2, vf1
|
|
if (instrs[3].kind != InstructionKind::VMULA_BC || instrs[3].get_src(0).get_reg() != vf2 ||
|
|
instrs[3].get_src(1).get_reg() != vf1 || instrs[3].cop2_dest != 0b1000 ||
|
|
instrs[3].cop2_bc != 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 4: vmadday.x acc, vf2, vf1
|
|
if (instrs[4].kind != InstructionKind::VMADDA_BC || instrs[4].get_src(0).get_reg() != vf2 ||
|
|
instrs[4].get_src(1).get_reg() != vf1 || instrs[4].cop2_dest != 0b1000 ||
|
|
instrs[4].cop2_bc != 1) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 5: vmaddz.x vf1, vf2, vf1
|
|
if (instrs[5].kind != InstructionKind::VMADD_BC || instrs[5].get_dst(0).get_reg() != vf1 ||
|
|
instrs[5].get_src(0).get_reg() != vf2 || instrs[5].get_src(1).get_reg() != vf1 ||
|
|
instrs[5].cop2_dest != 0b1000 || instrs[5].cop2_bc != 2) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 6: qmfc2.i v0, vf1
|
|
if (instrs[6].kind != InstructionKind::QMFC2 || instrs[6].src->get_reg() != vf1) {
|
|
return nullptr;
|
|
}
|
|
auto dst = instrs[6].get_dst(0).get_reg();
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_LENGTH_SQUARED, make_src_atom(vec_src, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_7(const Instruction* instrs, int idx, GameVersion version) {
|
|
if (version == GameVersion::Jak3) {
|
|
auto as_vector_length_squared = convert_vector_length_squared(instrs, idx);
|
|
if (as_vector_length_squared) {
|
|
return as_vector_length_squared;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_vector_plus_float_times(const Instruction* instrs, int idx) {
|
|
// lqc2 vf2, 0(a2)
|
|
if (instrs[0].kind != InstructionKind::LQC2 || instrs[0].get_dst(0).get_reg() != make_vf(2) ||
|
|
!instrs[0].get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register vec_src_2 = instrs[0].get_src(1).get_reg();
|
|
|
|
// lqc2 vf1, 0(a1)
|
|
if (instrs[1].kind != InstructionKind::LQC2 || instrs[1].get_dst(0).get_reg() != make_vf(1) ||
|
|
!instrs[1].get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register vec_src_1 = instrs[1].get_src(1).get_reg();
|
|
|
|
// mfc1 a0, f0
|
|
if (instrs[2].kind != InstructionKind::MFC1) {
|
|
return nullptr;
|
|
}
|
|
|
|
Register flt_src_3 = instrs[2].get_src(0).get_reg();
|
|
Register temp = instrs[2].get_dst(0).get_reg();
|
|
|
|
// qmtc2.i vf3, a3
|
|
if (instrs[3].kind != InstructionKind::QMTC2 || instrs[3].get_dst(0).get_reg() != make_vf(3) ||
|
|
instrs[3].get_src(0).get_reg() != temp) {
|
|
return nullptr;
|
|
}
|
|
|
|
// vaddx.w vf4, vf0, vf0
|
|
if (instrs[4].kind != InstructionKind::VADD_BC || instrs[4].get_dst(0).get_reg() != make_vf(4) ||
|
|
instrs[4].get_src(0).get_reg() != make_vf(0) ||
|
|
instrs[4].get_src(1).get_reg() != make_vf(0) || instrs[4].cop2_bc != 0 ||
|
|
instrs[4].cop2_dest != 0b0001) {
|
|
return nullptr;
|
|
}
|
|
|
|
// vmulax.xyzw acc, vf2, vf3
|
|
if (instrs[5].kind != InstructionKind::VMULA_BC || instrs[5].get_src(0).get_reg() != make_vf(2) ||
|
|
instrs[5].get_src(1).get_reg() != make_vf(3) || instrs[5].cop2_dest != 0b1111 ||
|
|
instrs[5].cop2_bc != 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
// vmaddw.xyz vf4, vf1, vf0
|
|
if (instrs[6].kind != InstructionKind::VMADD_BC || instrs[6].get_dst(0).get_reg() != make_vf(4) ||
|
|
instrs[6].get_src(0).get_reg() != make_vf(1) ||
|
|
instrs[6].get_src(1).get_reg() != make_vf(0) || instrs[6].cop2_dest != 0b1110 ||
|
|
instrs[6].cop2_bc != 3) {
|
|
return nullptr;
|
|
}
|
|
|
|
// sqc2 vf4, 0(a0)
|
|
if (instrs[7].kind != InstructionKind::SQC2 || instrs[7].get_src(0).get_reg() != make_vf(4) ||
|
|
!instrs[7].get_src(1).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register dst = instrs[7].get_src(2).get_reg();
|
|
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_PLUS_FLOAT_TIMES, make_src_atom(dst, idx),
|
|
make_src_atom(vec_src_1, idx), make_src_atom(vec_src_2, idx),
|
|
make_src_atom(flt_src_3, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_vector_plus_times(const Instruction* instrs, int idx) {
|
|
// 0: mfc1 a3, f0
|
|
if (instrs[0].kind != InstructionKind::MFC1) {
|
|
return nullptr;
|
|
}
|
|
Register flt_src_3 = instrs[0].get_src(0).get_reg();
|
|
Register temp = instrs[0].get_dst(0).get_reg();
|
|
|
|
// 1: qmtc2.i vf7, a3
|
|
if (instrs[1].kind != InstructionKind::QMTC2 || instrs[1].get_dst(0).get_reg() != make_vf(7) ||
|
|
instrs[1].get_src(0).get_reg() != temp) {
|
|
return nullptr;
|
|
}
|
|
// 2: lqc2 vf5, 0(a2)
|
|
if (instrs[2].kind != InstructionKind::LQC2 || instrs[2].get_dst(0).get_reg() != make_vf(5) ||
|
|
!instrs[2].get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register vec_src_2 = instrs[2].get_src(1).get_reg();
|
|
|
|
// 3: lqc2 vf4, 0(a1)
|
|
if (instrs[3].kind != InstructionKind::LQC2 || instrs[3].get_dst(0).get_reg() != make_vf(4) ||
|
|
!instrs[3].get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register vec_src_1 = instrs[3].get_src(1).get_reg();
|
|
|
|
// 4: vaddx.w vf6, vf0, vf0
|
|
if (instrs[4].kind != InstructionKind::VADD_BC || instrs[4].get_dst(0).get_reg() != make_vf(6) ||
|
|
instrs[4].get_src(0).get_reg() != make_vf(0) ||
|
|
instrs[4].get_src(1).get_reg() != make_vf(0) || instrs[4].cop2_bc != 0 ||
|
|
instrs[4].cop2_dest != 0b0001) {
|
|
return nullptr;
|
|
}
|
|
// 5: vmulax.xyz acc, vf5, vf7
|
|
// vmulax.xyz acc, vf5, vf7
|
|
if (instrs[5].kind != InstructionKind::VMULA_BC || instrs[5].get_src(0).get_reg() != make_vf(5) ||
|
|
instrs[5].get_src(1).get_reg() != make_vf(7) || instrs[5].cop2_dest != 0b1110 ||
|
|
instrs[5].cop2_bc != 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 6: vmaddw.xyz vf6, vf4, vf0
|
|
if (instrs[6].kind != InstructionKind::VMADD_BC || instrs[6].get_dst(0).get_reg() != make_vf(6) ||
|
|
instrs[6].get_src(0).get_reg() != make_vf(4) ||
|
|
instrs[6].get_src(1).get_reg() != make_vf(0) || instrs[6].cop2_dest != 0b1110 ||
|
|
instrs[6].cop2_bc != 3) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 7: sqc2 vf6, 0(a0)
|
|
if (instrs[7].kind != InstructionKind::SQC2 || instrs[7].get_src(0).get_reg() != make_vf(6) ||
|
|
!instrs[7].get_src(1).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register dst = instrs[7].get_src(2).get_reg();
|
|
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_PLUS_TIMES, make_src_atom(dst, idx),
|
|
make_src_atom(vec_src_1, idx), make_src_atom(vec_src_2, idx),
|
|
make_src_atom(flt_src_3, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_8(const Instruction* instrs, int idx, GameVersion version) {
|
|
auto as_vector_float_plus_times = convert_vector_plus_float_times(instrs, idx);
|
|
if (as_vector_float_plus_times) {
|
|
return as_vector_float_plus_times;
|
|
}
|
|
|
|
if (version == GameVersion::Jak3) {
|
|
auto as_vector_plus_times = convert_vector_plus_times(instrs, idx);
|
|
if (as_vector_plus_times) {
|
|
return as_vector_plus_times;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool is_lwc(const Instruction& instr, int offset) {
|
|
return instr.kind == InstructionKind::LWC1 && instr.get_src(0).is_imm(offset);
|
|
}
|
|
|
|
// 9 instructions
|
|
std::unique_ptr<AtomicOp> convert_vector3_dot(const Instruction* instrs, int idx) {
|
|
// lwc1 f0, 0(a0)
|
|
if (!is_lwc(instrs[0], 0)) {
|
|
return nullptr;
|
|
}
|
|
auto t0 = instrs[0].get_dst(0).get_reg();
|
|
|
|
// lwc1 f1, 4(a0)
|
|
if (!is_lwc(instrs[1], 4)) {
|
|
return nullptr;
|
|
}
|
|
auto t1 = instrs[1].get_dst(0).get_reg();
|
|
|
|
// lwc1 f2, 8(a0)
|
|
if (!is_lwc(instrs[2], 8)) {
|
|
return nullptr;
|
|
}
|
|
auto t2 = instrs[2].get_dst(0).get_reg();
|
|
|
|
// lwc1 f3, 0(v1)
|
|
if (!is_lwc(instrs[3], 0)) {
|
|
return nullptr;
|
|
}
|
|
auto t3 = instrs[3].get_dst(0).get_reg();
|
|
|
|
// lwc1 f4, 4(v1)
|
|
if (!is_lwc(instrs[4], 4)) {
|
|
return nullptr;
|
|
}
|
|
auto t4 = instrs[4].get_dst(0).get_reg();
|
|
|
|
// lwc1 f5, 8(v1)
|
|
if (!is_lwc(instrs[5], 8)) {
|
|
return nullptr;
|
|
}
|
|
auto t5 = instrs[5].get_dst(0).get_reg();
|
|
|
|
auto src0 = instrs[0].get_src(1).get_reg();
|
|
auto src1 = instrs[3].get_src(1).get_reg();
|
|
if (instrs[1].get_src(1).get_reg() != src0) {
|
|
return nullptr;
|
|
}
|
|
if (instrs[2].get_src(1).get_reg() != src0) {
|
|
return nullptr;
|
|
}
|
|
if (instrs[4].get_src(1).get_reg() != src1) {
|
|
return nullptr;
|
|
}
|
|
if (instrs[5].get_src(1).get_reg() != src1) {
|
|
return nullptr;
|
|
}
|
|
|
|
// mula.s f0, f3
|
|
if (instrs[6].kind != InstructionKind::MULAS || instrs[6].get_src(0).get_reg() != t0 ||
|
|
instrs[6].get_src(1).get_reg() != t3) {
|
|
return nullptr;
|
|
}
|
|
|
|
// madda.s f1, f4
|
|
if (instrs[7].kind != InstructionKind::MADDAS || instrs[7].get_src(0).get_reg() != t1 ||
|
|
instrs[7].get_src(1).get_reg() != t4) {
|
|
return nullptr;
|
|
}
|
|
|
|
// madd.s f0, f2, f5
|
|
if (instrs[8].kind != InstructionKind::MADDS || instrs[8].get_src(0).get_reg() != t2 ||
|
|
instrs[8].get_src(1).get_reg() != t5) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto dst = instrs[8].get_dst(0).get_reg();
|
|
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_3_DOT, make_src_atom(src0, idx),
|
|
make_src_atom(src1, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_9(const Instruction* instrs, int idx) {
|
|
auto as_vector3_dot = convert_vector3_dot(instrs, idx);
|
|
if (as_vector3_dot) {
|
|
return as_vector3_dot;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_vector_length(const Instruction* instrs, int idx) {
|
|
// 0: lqc2 vf1, 0(a1)
|
|
if (instrs[0].kind != InstructionKind::LQC2 || instrs[0].get_dst(0).get_reg() != make_vf(1) ||
|
|
!instrs[0].get_src(0).is_imm(0)) {
|
|
return nullptr;
|
|
}
|
|
Register vec_src = instrs[0].get_src(1).get_reg();
|
|
|
|
auto vf0 = make_vf(0);
|
|
auto vf1 = make_vf(1);
|
|
|
|
// 1: vmul.xyzw vf1, vf1, vf1
|
|
std::vector<DecompilerLabel> labels;
|
|
if (instrs[1].kind != InstructionKind::VMUL || instrs[1].get_dst(0).get_reg() != vf1 ||
|
|
instrs[1].get_src(0).get_reg() != vf1 || instrs[1].get_src(1).get_reg() != vf1 ||
|
|
instrs[1].cop2_dest != 0b1111) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 2: vmulax.w acc, vf0, vf1
|
|
if (instrs[2].kind != InstructionKind::VMULA_BC || instrs[2].get_src(0).get_reg() != vf0 ||
|
|
instrs[2].get_src(1).get_reg() != vf1 || instrs[2].cop2_dest != 0b0001 ||
|
|
instrs[2].cop2_bc != 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 3: vmadday.w acc, vf0, vf1
|
|
if (instrs[3].kind != InstructionKind::VMADDA_BC || instrs[3].get_src(0).get_reg() != vf0 ||
|
|
instrs[3].get_src(1).get_reg() != vf1 || instrs[3].cop2_dest != 0b0001 ||
|
|
instrs[3].cop2_bc != 1) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 4: vmaddz.w vf1, vf0, vf1
|
|
if (instrs[4].kind != InstructionKind::VMADD_BC || instrs[4].get_dst(0).get_reg() != vf1 ||
|
|
instrs[4].get_src(0).get_reg() != vf0 || instrs[4].get_src(1).get_reg() != vf1 ||
|
|
instrs[4].cop2_dest != 0b0001 || instrs[4].cop2_bc != 2) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 5: vsqrt Q, vf1.w
|
|
if (instrs[5].kind != InstructionKind::VSQRT || instrs[5].get_src(0).get_reg() != vf1) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 6: vaddw.x vf1, vf0, vf0
|
|
if (instrs[6].kind != InstructionKind::VADD_BC || instrs[6].get_dst(0).get_reg() != vf1 ||
|
|
instrs[6].get_src(0).get_reg() != vf0 || instrs[6].get_src(1).get_reg() != vf0 ||
|
|
instrs[6].cop2_dest != 0b1000 || instrs[6].cop2_bc != 3) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 7: vwaitq
|
|
if (instrs[7].kind != InstructionKind::VWAITQ) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 8: vmulq.x vf1, vf1, Q
|
|
if (instrs[8].kind != InstructionKind::VMULQ || instrs[8].get_dst(0).get_reg() != vf1 ||
|
|
instrs[8].get_src(0).get_reg() != vf1 || instrs[8].cop2_dest != 0b1000) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 9: vnop
|
|
if (instrs[9].kind != InstructionKind::VNOP) {
|
|
return nullptr;
|
|
}
|
|
// 10: vnop
|
|
if (instrs[10].kind != InstructionKind::VNOP) {
|
|
return nullptr;
|
|
}
|
|
// 11: qmfc2.i a1, vf1
|
|
if (instrs[11].kind != InstructionKind::QMFC2 || instrs[11].src->get_reg() != vf1) {
|
|
return nullptr;
|
|
}
|
|
auto dst = instrs[11].get_dst(0).get_reg();
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_LENGTH, make_src_atom(vec_src, idx)), idx);
|
|
}
|
|
|
|
// 12 instructions
|
|
std::unique_ptr<AtomicOp> convert_vector4_dot(const Instruction* instrs, int idx) {
|
|
// lwc1 f0, 0(a0)
|
|
if (!is_lwc(instrs[0], 0)) {
|
|
return nullptr;
|
|
}
|
|
auto t0 = instrs[0].get_dst(0).get_reg();
|
|
|
|
// lwc1 f1, 4(a0)
|
|
if (!is_lwc(instrs[1], 4)) {
|
|
return nullptr;
|
|
}
|
|
auto t1 = instrs[1].get_dst(0).get_reg();
|
|
|
|
// lwc1 f2, 8(a0)
|
|
if (!is_lwc(instrs[2], 8)) {
|
|
return nullptr;
|
|
}
|
|
auto t2 = instrs[2].get_dst(0).get_reg();
|
|
|
|
// lwc1 f3, 12(a0)
|
|
if (!is_lwc(instrs[3], 12)) {
|
|
return nullptr;
|
|
}
|
|
auto t3 = instrs[3].get_dst(0).get_reg();
|
|
|
|
// lwc1 f3, 0(v1)
|
|
if (!is_lwc(instrs[4], 0)) {
|
|
return nullptr;
|
|
}
|
|
auto t4 = instrs[4].get_dst(0).get_reg();
|
|
|
|
// lwc1 f5, 4(v1)
|
|
if (!is_lwc(instrs[5], 4)) {
|
|
return nullptr;
|
|
}
|
|
auto t5 = instrs[5].get_dst(0).get_reg();
|
|
|
|
// lwc1 f5, 8(v1)
|
|
if (!is_lwc(instrs[6], 8)) {
|
|
return nullptr;
|
|
}
|
|
auto t6 = instrs[6].get_dst(0).get_reg();
|
|
|
|
// lwc1 f5, 12(v1)
|
|
if (!is_lwc(instrs[7], 12)) {
|
|
return nullptr;
|
|
}
|
|
auto t7 = instrs[7].get_dst(0).get_reg();
|
|
|
|
auto src0 = instrs[0].get_src(1).get_reg();
|
|
auto src1 = instrs[4].get_src(1).get_reg();
|
|
if (instrs[1].get_src(1).get_reg() != src0) {
|
|
return nullptr;
|
|
}
|
|
if (instrs[2].get_src(1).get_reg() != src0) {
|
|
return nullptr;
|
|
}
|
|
if (instrs[3].get_src(1).get_reg() != src0) {
|
|
return nullptr;
|
|
}
|
|
if (instrs[5].get_src(1).get_reg() != src1) {
|
|
return nullptr;
|
|
}
|
|
if (instrs[6].get_src(1).get_reg() != src1) {
|
|
return nullptr;
|
|
}
|
|
if (instrs[7].get_src(1).get_reg() != src1) {
|
|
return nullptr;
|
|
}
|
|
|
|
// mula.s f0, f4
|
|
if (instrs[8].kind != InstructionKind::MULAS || instrs[8].get_src(0).get_reg() != t0 ||
|
|
instrs[8].get_src(1).get_reg() != t4) {
|
|
return nullptr;
|
|
}
|
|
|
|
// madda.s f1, f5
|
|
if (instrs[9].kind != InstructionKind::MADDAS || instrs[9].get_src(0).get_reg() != t1 ||
|
|
instrs[9].get_src(1).get_reg() != t5) {
|
|
return nullptr;
|
|
}
|
|
|
|
// madda.s f2, f6
|
|
if (instrs[10].kind != InstructionKind::MADDAS || instrs[10].get_src(0).get_reg() != t2 ||
|
|
instrs[10].get_src(1).get_reg() != t6) {
|
|
return nullptr;
|
|
}
|
|
|
|
// madd.s f0, f3, f7
|
|
if (instrs[11].kind != InstructionKind::MADDS || instrs[11].get_src(0).get_reg() != t3 ||
|
|
instrs[11].get_src(1).get_reg() != t7) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto dst = instrs[11].get_dst(0).get_reg();
|
|
|
|
return std::make_unique<SetVarOp>(
|
|
make_dst_var(dst, idx),
|
|
SimpleExpression(SimpleExpression::Kind::VECTOR_4_DOT, make_src_atom(src0, idx),
|
|
make_src_atom(src1, idx)),
|
|
idx);
|
|
}
|
|
|
|
std::unique_ptr<AtomicOp> convert_12(const Instruction* instrs, int idx) {
|
|
auto as_vector4_dot = convert_vector4_dot(instrs, idx);
|
|
if (as_vector4_dot) {
|
|
return as_vector4_dot;
|
|
}
|
|
|
|
auto as_vector_length = convert_vector_length(instrs, idx);
|
|
if (as_vector_length) {
|
|
return as_vector_length;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/*!
|
|
* Convert an entire basic block and add the results to a FunctionAtomicOps
|
|
* @param block_id : the index of the block
|
|
* @param begin : the start of the instructions for the block
|
|
* @param end : the end of the instructions for the block
|
|
* @param container : the container to add to
|
|
*/
|
|
int convert_block_to_atomic_ops(int begin_idx,
|
|
std::vector<Instruction>::const_iterator begin,
|
|
std::vector<Instruction>::const_iterator end,
|
|
const std::vector<DecompilerLabel>& labels,
|
|
FunctionAtomicOps* container,
|
|
DecompWarnings& warnings,
|
|
GameVersion version,
|
|
bool hint_inline_asm,
|
|
bool block_ends_in_asm_branch) {
|
|
container->block_id_to_first_atomic_op.push_back(container->ops.size());
|
|
for (auto& instr = begin; instr < end;) {
|
|
// how many instructions can we look at, at most?
|
|
int n_instr = end - instr;
|
|
// how many instructions did we use?
|
|
int length = 0;
|
|
// what is the index of the atomic op we would add
|
|
int op_idx = int(container->ops.size());
|
|
|
|
bool converted = false;
|
|
std::unique_ptr<AtomicOp> op;
|
|
|
|
if (instr[0].kind == InstructionKind::SQ || instr[0].kind == InstructionKind::LQ) {
|
|
warnings.unique_info("Used lq/sq");
|
|
}
|
|
|
|
if (!converted && n_instr >= 12) {
|
|
op = convert_12(&instr[0], op_idx);
|
|
if (op) {
|
|
converted = true;
|
|
length = 12;
|
|
}
|
|
}
|
|
|
|
if (!converted && n_instr >= 9) {
|
|
op = convert_9(&instr[0], op_idx);
|
|
if (op) {
|
|
converted = true;
|
|
length = 9;
|
|
}
|
|
}
|
|
|
|
if (!converted && n_instr >= 8) {
|
|
op = convert_8(&instr[0], op_idx, version);
|
|
if (op) {
|
|
converted = true;
|
|
length = 8;
|
|
}
|
|
}
|
|
|
|
if (!converted && n_instr >= 7) {
|
|
op = convert_7(&instr[0], op_idx, version);
|
|
if (op) {
|
|
converted = true;
|
|
length = 7;
|
|
}
|
|
}
|
|
|
|
if (!converted && n_instr >= 6) {
|
|
// try 6 instructions
|
|
op = convert_6(instr[0], instr[1], instr[2], instr[3], instr[4], instr[5], op_idx);
|
|
if (op) {
|
|
converted = true;
|
|
length = 6;
|
|
}
|
|
}
|
|
|
|
if (!converted && n_instr >= 5) {
|
|
// try 5 instructions
|
|
op = convert_5(instr[0], instr[1], instr[2], instr[3], instr[4], op_idx, version);
|
|
if (op) {
|
|
converted = true;
|
|
length = 5;
|
|
}
|
|
}
|
|
|
|
if (!converted && n_instr >= 4) {
|
|
// try 4 instructions
|
|
op = convert_4(instr[0], instr[1], instr[2], instr[3], op_idx, version);
|
|
if (op) {
|
|
converted = true;
|
|
length = 4;
|
|
}
|
|
}
|
|
|
|
if (!converted && n_instr >= 3) {
|
|
// try 3 instructions
|
|
op = convert_3(instr[0], instr[1], instr[2], op_idx, version);
|
|
if (op) {
|
|
converted = true;
|
|
length = 3;
|
|
}
|
|
}
|
|
|
|
if (!converted && n_instr >= 2) {
|
|
// try 2 instructions
|
|
op = convert_2(instr[0], instr[1], op_idx, block_ends_in_asm_branch, version);
|
|
if (op) {
|
|
converted = true;
|
|
length = 2;
|
|
}
|
|
}
|
|
|
|
if (!converted) {
|
|
// try 1 instruction
|
|
bool force_asm_branch = n_instr == 1 && block_ends_in_asm_branch;
|
|
op = convert_1(*instr, op_idx, hint_inline_asm, force_asm_branch, version);
|
|
if (op) {
|
|
converted = true;
|
|
length = 1;
|
|
}
|
|
}
|
|
|
|
if (!converted) {
|
|
// try assembly fallback.
|
|
op = make_asm_op(*instr, op_idx);
|
|
if (op) {
|
|
converted = true;
|
|
length = 1;
|
|
}
|
|
}
|
|
|
|
if (!converted) {
|
|
// failed!
|
|
throw std::runtime_error(
|
|
fmt::format("Failed to convert ({} instrs) {}\n", n_instr, instr->to_string(labels)));
|
|
// lg::die("Failed to convert instruction {} to an atomic op",
|
|
// instr->to_string(labels));
|
|
}
|
|
|
|
ASSERT(converted && length && op);
|
|
// add mappings:
|
|
container->atomic_op_to_instruction[container->ops.size()] = begin_idx;
|
|
for (int i = 0; i < length; i++) {
|
|
container->instruction_to_atomic_op[begin_idx + i] = container->ops.size();
|
|
}
|
|
// add
|
|
op->update_register_info();
|
|
container->ops.emplace_back(std::move(op));
|
|
instr += length;
|
|
begin_idx += length;
|
|
}
|
|
container->block_id_to_end_atomic_op.push_back(container->ops.size());
|
|
return int(container->ops.size());
|
|
}
|
|
|
|
FunctionAtomicOps convert_function_to_atomic_ops(
|
|
const Function& func,
|
|
const std::vector<DecompilerLabel>& labels,
|
|
DecompWarnings& warnings,
|
|
bool hint_inline_asm,
|
|
const std::unordered_set<int>& blocks_ending_in_asm_branches,
|
|
GameVersion version) {
|
|
FunctionAtomicOps result;
|
|
|
|
int last_op = 0;
|
|
for (int i = 0; i < int(func.basic_blocks.size()); i++) {
|
|
const auto& block = func.basic_blocks.at(i);
|
|
// we should only consider the blocks which actually have instructions:
|
|
if (block.end_word > block.start_word) {
|
|
auto begin = func.instructions.begin() + block.start_word;
|
|
auto end = func.instructions.begin() + block.end_word;
|
|
last_op = convert_block_to_atomic_ops(
|
|
block.start_word, begin, end, labels, &result, warnings, version, hint_inline_asm,
|
|
blocks_ending_in_asm_branches.find(i) != blocks_ending_in_asm_branches.end());
|
|
if (i == int(func.basic_blocks.size()) - 1) {
|
|
// we're the last block. insert the function end op.
|
|
result.ops.push_back(std::make_unique<FunctionEndOp>(int(result.ops.size())));
|
|
result.ops.back()->update_register_info();
|
|
// add to block.
|
|
result.block_id_to_end_atomic_op.back()++;
|
|
}
|
|
} else {
|
|
if (i == int(func.basic_blocks.size()) - 1) {
|
|
// we're the last block. insert the function end op.
|
|
result.ops.push_back(std::make_unique<FunctionEndOp>(int(result.ops.size())));
|
|
result.ops.back()->update_register_info();
|
|
// add block (no longer a zero-size block)
|
|
result.block_id_to_first_atomic_op.push_back(last_op);
|
|
result.block_id_to_end_atomic_op.push_back(last_op + 1);
|
|
} else {
|
|
result.block_id_to_first_atomic_op.push_back(last_op);
|
|
result.block_id_to_end_atomic_op.push_back(last_op);
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(func.basic_blocks.size() == result.block_id_to_end_atomic_op.size());
|
|
ASSERT(func.basic_blocks.size() == result.block_id_to_first_atomic_op.size());
|
|
return result;
|
|
}
|
|
} // namespace decompiler
|