mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 00:57:44 -04:00
Make decompiler more successful (#66)
* w/early-return-and-break * more edge cases * all instructions in non asm functions now convert to ir * cleanup * implement rarely used control flow in IR * clang format
This commit is contained in:
parent
30e0e4204b
commit
1e1b5e7c00
|
@ -116,9 +116,10 @@ bool InstructionAtom::is_link_or_label() const {
|
|||
}
|
||||
|
||||
/*!
|
||||
* Convert entire instruction to a string.
|
||||
* Convert just the name of the opcode to a string, omitting src/dst, but including
|
||||
* suffixes (interlock, broadcasts and destination)
|
||||
*/
|
||||
std::string Instruction::to_string(const LinkedObjectFile& file) const {
|
||||
std::string Instruction::op_name_to_string() const {
|
||||
auto& info = gOpcodeInfo[(int)kind];
|
||||
|
||||
// the name
|
||||
|
@ -162,6 +163,15 @@ std::string Instruction::to_string(const LinkedObjectFile& file) const {
|
|||
if (cop2_dest & 1)
|
||||
result.push_back('w');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert entire instruction to a string.
|
||||
*/
|
||||
std::string Instruction::to_string(const LinkedObjectFile& file) const {
|
||||
auto& info = gOpcodeInfo[(int)kind];
|
||||
auto result = op_name_to_string();
|
||||
|
||||
// relative store and load instructions have a special syntax in MIPS
|
||||
if (info.is_store) {
|
||||
|
|
|
@ -65,6 +65,7 @@ class Instruction {
|
|||
public:
|
||||
InstructionKind kind = InstructionKind::UNKNOWN;
|
||||
|
||||
std::string op_name_to_string() const;
|
||||
std::string to_string(const LinkedObjectFile& file) const;
|
||||
bool is_valid() const;
|
||||
|
||||
|
|
|
@ -280,6 +280,17 @@ goos::Object GotoEnd::to_form() {
|
|||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
std::string Break::to_string() {
|
||||
return "goto" + std::to_string(uid);
|
||||
}
|
||||
|
||||
goos::Object Break::to_form() {
|
||||
std::vector<goos::Object> forms = {pretty_print::to_symbol("break"),
|
||||
pretty_print::to_symbol(std::to_string(dest_block)),
|
||||
body->to_form(), unreachable_block->to_form()};
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
ControlFlowGraph::ControlFlowGraph() {
|
||||
// allocate the entry and exit vertices.
|
||||
m_entry = alloc<EntryVtx>();
|
||||
|
@ -576,6 +587,31 @@ bool ControlFlowGraph::is_until_loop(CfgVtx* b1, CfgVtx* b2) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ControlFlowGraph::is_goto_not_end_and_unreachable(CfgVtx* b0, CfgVtx* b1) {
|
||||
if (!b0 || !b1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// b0 should be an always branch, not likely.
|
||||
if (!b0->end_branch.has_branch || !b0->end_branch.branch_always || b0->end_branch.branch_likely) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// b0 should be next to b1
|
||||
if (b0->next != b1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(b1->prev == b0);
|
||||
|
||||
// b1 should have no preds and be unreachable.
|
||||
if (!b1->pred.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // match!
|
||||
}
|
||||
|
||||
bool ControlFlowGraph::is_goto_end_and_unreachable(CfgVtx* b0, CfgVtx* b1) {
|
||||
if (!b0 || !b1) {
|
||||
return false;
|
||||
|
@ -919,6 +955,68 @@ bool ControlFlowGraph::find_goto_end() {
|
|||
return replaced;
|
||||
}
|
||||
|
||||
bool ControlFlowGraph::find_goto_not_end() {
|
||||
bool replaced = false;
|
||||
|
||||
for_each_top_level_vtx([&](CfgVtx* vtx) {
|
||||
auto* b0 = vtx;
|
||||
auto* b1 = vtx->next;
|
||||
if (is_goto_not_end_and_unreachable(b0, b1)) {
|
||||
replaced = true;
|
||||
|
||||
auto* new_goto = alloc<Break>();
|
||||
new_goto->body = b0;
|
||||
new_goto->unreachable_block = b1;
|
||||
// todo set block number
|
||||
|
||||
for (auto* new_pred : b0->pred) {
|
||||
// printf("fix up pred %s of %s\n", new_pred->to_string().c_str(),
|
||||
// b0->to_string().c_str());
|
||||
new_pred->replace_succ_and_check(b0, new_goto);
|
||||
}
|
||||
new_goto->pred = b0->pred;
|
||||
|
||||
for (auto* new_succ : b1->succs()) {
|
||||
// new_succ->replace_preds_with_and_check({b1}, nullptr);
|
||||
new_succ->replace_pred_and_check(b1, new_goto);
|
||||
}
|
||||
// this is a lie, but ok
|
||||
|
||||
new_goto->succ_ft = b1->succ_ft;
|
||||
new_goto->succ_branch = b1->succ_branch;
|
||||
new_goto->end_branch = b1->end_branch;
|
||||
|
||||
// if(b1->next) {
|
||||
// b1->next->pred.push_back(new_goto);
|
||||
// }
|
||||
// new_goto->succ_branch = b1->succ_branch;
|
||||
// new_goto->end_branch = b1->end_branch;
|
||||
|
||||
new_goto->prev = b0->prev;
|
||||
if (new_goto->prev) {
|
||||
new_goto->prev->next = new_goto;
|
||||
}
|
||||
|
||||
new_goto->next = b1->next;
|
||||
if (new_goto->next) {
|
||||
new_goto->next->prev = new_goto;
|
||||
}
|
||||
|
||||
b0->succ_branch->replace_preds_with_and_check({b0}, nullptr);
|
||||
|
||||
b0->parent_claim(new_goto);
|
||||
b1->parent_claim(new_goto);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// keep looking
|
||||
return true;
|
||||
});
|
||||
|
||||
return replaced;
|
||||
}
|
||||
|
||||
bool ControlFlowGraph::is_sequence(CfgVtx* b0, CfgVtx* b1) {
|
||||
if (!b0 || !b1)
|
||||
return false;
|
||||
|
@ -1734,6 +1832,10 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
|
|||
changed = changed || cfg->find_until1_loop();
|
||||
changed = changed || cfg->find_infinite_loop();
|
||||
};
|
||||
|
||||
if (!changed) {
|
||||
changed = changed || cfg->find_goto_not_end();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cfg->is_fully_resolved()) {
|
||||
|
|
|
@ -250,6 +250,15 @@ class GotoEnd : public CfgVtx {
|
|||
CfgVtx* unreachable_block = nullptr;
|
||||
};
|
||||
|
||||
class Break : public CfgVtx {
|
||||
public:
|
||||
std::string to_string() override;
|
||||
goos::Object to_form() override;
|
||||
int dest_block = -1;
|
||||
CfgVtx* body = nullptr;
|
||||
CfgVtx* unreachable_block = nullptr;
|
||||
};
|
||||
|
||||
struct BasicBlock;
|
||||
|
||||
/*!
|
||||
|
@ -283,6 +292,7 @@ class ControlFlowGraph {
|
|||
bool find_short_circuits();
|
||||
bool find_goto_end();
|
||||
bool find_infinite_loop();
|
||||
bool find_goto_not_end();
|
||||
|
||||
/*!
|
||||
* Apply a function f to each top-level vertex.
|
||||
|
@ -324,6 +334,7 @@ class ControlFlowGraph {
|
|||
bool is_while_loop(CfgVtx* b0, CfgVtx* b1, CfgVtx* b2);
|
||||
bool is_until_loop(CfgVtx* b1, CfgVtx* b2);
|
||||
bool is_goto_end_and_unreachable(CfgVtx* b0, CfgVtx* b1);
|
||||
bool is_goto_not_end_and_unreachable(CfgVtx* b0, CfgVtx* b1);
|
||||
std::vector<BlockVtx*> m_blocks; // all block nodes, in order.
|
||||
std::vector<CfgVtx*> m_node_pool; // all nodes allocated
|
||||
EntryVtx* m_entry; // the entry vertex
|
||||
|
|
|
@ -23,6 +23,7 @@ struct FunctionName {
|
|||
std::string function_name; // only applicable for GLOBAL
|
||||
std::string type_name; // only applicable for METHOD
|
||||
int method_id = -1; // only applicable for METHOD
|
||||
int unique_id = -1;
|
||||
|
||||
std::string to_string() const {
|
||||
switch (kind) {
|
||||
|
@ -33,7 +34,7 @@ struct FunctionName {
|
|||
case FunctionKind::TOP_LEVEL_INIT:
|
||||
return "(top-level-login)";
|
||||
case FunctionKind::UNIDENTIFIED:
|
||||
return "(?)";
|
||||
return "(anon-function " + std::to_string(unique_id) + ")";
|
||||
default:
|
||||
throw std::runtime_error("Unsupported FunctionKind");
|
||||
}
|
||||
|
@ -93,6 +94,7 @@ class Function {
|
|||
int epilogue_end = -1;
|
||||
|
||||
std::string warnings;
|
||||
bool contains_asm_ops = false;
|
||||
|
||||
struct Prologue {
|
||||
bool decoded = false; // have we removed the prologue from basic blocks?
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/*!
|
||||
* @file BasicOpBuilder.cpp
|
||||
* Convert a basic block into a sequence of IR operations.
|
||||
* Build up basic set instructions from GOAL code
|
||||
* Recognize common GOAL compiler idioms
|
||||
* Recognize branch delay slot use
|
||||
* Recognize assembly ops and pass them through as IR_Asm
|
||||
*/
|
||||
|
||||
#include "BasicOpBuilder.h"
|
||||
#include "decompiler/Function/Function.h"
|
||||
#include "decompiler/Function/BasicBlocks.h"
|
||||
|
@ -5,28 +14,129 @@
|
|||
|
||||
namespace {
|
||||
|
||||
/*!
|
||||
* Create a GOAL "set!" form.
|
||||
* These will later be compacted into more complicated nested expressions.
|
||||
*/
|
||||
std::shared_ptr<IR_Set> make_set(IR_Set::Kind kind,
|
||||
const std::shared_ptr<IR>& dst,
|
||||
const std::shared_ptr<IR>& src) {
|
||||
return std::make_shared<IR_Set>(kind, dst, src);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create an IR representing a register at a certain point. Idx is the instruction index.
|
||||
*/
|
||||
std::shared_ptr<IR_Register> make_reg(Register reg, int idx) {
|
||||
return std::make_shared<IR_Register>(reg, idx);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create an IR representing a symbol. The symbol itself ('thing), not the value.
|
||||
*/
|
||||
std::shared_ptr<IR_Symbol> make_sym(const std::string& name) {
|
||||
return std::make_shared<IR_Symbol>(name);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create an IR representing the value of a symbol. Can be read/written.
|
||||
*/
|
||||
std::shared_ptr<IR_SymbolValue> make_sym_value(const std::string& name) {
|
||||
return std::make_shared<IR_SymbolValue>(name);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create an integer constant.
|
||||
*/
|
||||
std::shared_ptr<IR_IntegerConstant> make_int(int64_t x) {
|
||||
return std::make_shared<IR_IntegerConstant>(x);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create an assembly passthrough in the form op dst, src, src
|
||||
*/
|
||||
std::shared_ptr<IR> to_asm_reg_reg_reg(const std::string& str, Instruction& instr, int idx) {
|
||||
auto result = std::make_shared<IR_AsmOp>(str);
|
||||
result->dst = make_reg(instr.get_dst(0).get_reg(), idx);
|
||||
result->src0 = make_reg(instr.get_src(0).get_reg(), idx);
|
||||
result->src1 = make_reg(instr.get_src(1).get_reg(), idx);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create an assembly passthrough for op src
|
||||
*/
|
||||
std::shared_ptr<IR> to_asm_src_reg(const std::string& str, Instruction& instr, int idx) {
|
||||
auto result = std::make_shared<IR_AsmOp>(str);
|
||||
result->src0 = make_reg(instr.get_src(0).get_reg(), idx);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create an assembly passthrough for op dst src
|
||||
*/
|
||||
std::shared_ptr<IR> to_asm_dst_reg_src_reg(const std::string& str, Instruction& instr, int idx) {
|
||||
auto result = std::make_shared<IR_AsmOp>(str);
|
||||
result->dst = make_reg(instr.get_dst(0).get_reg(), idx);
|
||||
result->src0 = make_reg(instr.get_src(0).get_reg(), idx);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert an instruction atom to IR.
|
||||
*/
|
||||
std::shared_ptr<IR> instr_atom_to_ir(const InstructionAtom& ia, int idx) {
|
||||
switch (ia.kind) {
|
||||
case InstructionAtom::REGISTER:
|
||||
return make_reg(ia.get_reg(), idx);
|
||||
case InstructionAtom::VU_Q:
|
||||
return std::make_shared<IR_AsmReg>(IR_AsmReg::VU_Q);
|
||||
case InstructionAtom::VU_ACC:
|
||||
return std::make_shared<IR_AsmReg>(IR_AsmReg::VU_ACC);
|
||||
case InstructionAtom::IMM:
|
||||
return make_int(ia.get_imm());
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> to_asm_automatic(const std::string& str, Instruction& instr, int idx) {
|
||||
auto result = std::make_shared<IR_AsmOp>(str);
|
||||
assert(instr.n_dst < 2);
|
||||
assert(instr.n_src < 4);
|
||||
if (instr.n_dst >= 1) {
|
||||
result->dst = instr_atom_to_ir(instr.get_dst(0), idx);
|
||||
}
|
||||
|
||||
if (instr.n_src >= 1) {
|
||||
result->src0 = instr_atom_to_ir(instr.get_src(0), idx);
|
||||
}
|
||||
|
||||
if (instr.n_src >= 2) {
|
||||
result->src1 = instr_atom_to_ir(instr.get_src(1), idx);
|
||||
}
|
||||
|
||||
if (instr.n_src >= 3) {
|
||||
result->src1 = instr_atom_to_ir(instr.get_src(2), idx);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_subu(Instruction& instr, int idx) {
|
||||
if (is_gpr_3(instr, InstructionKind::SUBU, {}, {}, {})) {
|
||||
return to_asm_reg_reg_reg("subu", instr, idx);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_sllv(Instruction& instr, int idx) {
|
||||
if (is_gpr_3(instr, InstructionKind::SLLV, {}, {}, make_gpr(Reg::R0))) {
|
||||
return to_asm_reg_reg_reg("sllv", instr, idx);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_or(Instruction& instr, int idx) {
|
||||
if (is_gpr_3(instr, InstructionKind::OR, {}, make_gpr(Reg::S7), make_gpr(Reg::R0))) {
|
||||
return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), make_sym("#f"));
|
||||
|
@ -402,7 +512,7 @@ std::shared_ptr<IR> try_daddu(Instruction& instr, int idx) {
|
|||
std::make_shared<IR_IntMath2>(IR_IntMath2::ADD, make_reg(instr.get_src(0).get_reg(), idx),
|
||||
make_reg(instr.get_src(1).get_reg(), idx)));
|
||||
}
|
||||
return nullptr;
|
||||
return to_asm_reg_reg_reg("daddu", instr, idx);
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_dsubu(Instruction& instr, int idx) {
|
||||
|
@ -459,6 +569,16 @@ std::shared_ptr<IR> try_andi(Instruction& instr, int idx) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_xori(Instruction& instr, int idx) {
|
||||
if (instr.kind == InstructionKind::XORI) {
|
||||
return make_set(
|
||||
IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx),
|
||||
std::make_shared<IR_IntMath2>(IR_IntMath2::XOR, make_reg(instr.get_src(0).get_reg(), idx),
|
||||
make_int(instr.get_src(1).get_imm())));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_nor(Instruction& instr, int idx) {
|
||||
if (is_gpr_3(instr, InstructionKind::NOR, {}, {}, {}) &&
|
||||
!instr.get_src(0).is_reg(make_gpr(Reg::S7)) && instr.get_src(1).is_reg(make_gpr(Reg::R0))) {
|
||||
|
@ -533,6 +653,17 @@ std::shared_ptr<IR> try_dsrlv(Instruction& instr, int idx) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_dsllv(Instruction& instr, int idx) {
|
||||
if (is_gpr_3(instr, InstructionKind::DSLLV, {}, {}, {}) &&
|
||||
!instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) {
|
||||
return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx),
|
||||
std::make_shared<IR_IntMath2>(IR_IntMath2::LEFT_SHIFT,
|
||||
make_reg(instr.get_src(0).get_reg(), idx),
|
||||
make_reg(instr.get_src(1).get_reg(), idx)));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_sw(Instruction& instr, int idx) {
|
||||
if (instr.kind == InstructionKind::SW && instr.get_src(1).is_sym() &&
|
||||
instr.get_src(2).is_reg(make_gpr(Reg::S7))) {
|
||||
|
@ -647,6 +778,22 @@ std::shared_ptr<IR> try_movs(Instruction& instr, int idx) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_movn(Instruction& instr, int idx) {
|
||||
if (is_gpr_3(instr, InstructionKind::MOVN, {}, make_gpr(Reg::S7), {})) {
|
||||
return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx),
|
||||
std::make_shared<IR_CMoveF>(make_reg(instr.get_src(1).get_reg(), idx), false));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_movz(Instruction& instr, int idx) {
|
||||
if (is_gpr_3(instr, InstructionKind::MOVZ, {}, make_gpr(Reg::S7), {})) {
|
||||
return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx),
|
||||
std::make_shared<IR_CMoveF>(make_reg(instr.get_src(1).get_reg(), idx), true));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TWO Instructions
|
||||
std::shared_ptr<IR> try_div(Instruction& instr, Instruction& next_instr, int idx) {
|
||||
if (instr.kind == InstructionKind::DIV && instr.get_src(0).is_reg() &&
|
||||
|
@ -897,7 +1044,53 @@ std::shared_ptr<IR> try_lui(Instruction& i0, Instruction& i1, int idx) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_slt(Instruction& i0, Instruction& i1, int idx) {
|
||||
if (is_gpr_3(i0, InstructionKind::SLT, {}, {}, {})) {
|
||||
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 (is_gpr_3(i1, InstructionKind::MOVZ, left, right, temp)) {
|
||||
// success!
|
||||
auto result =
|
||||
make_set(IR_Set::REG_64, make_reg(left, idx),
|
||||
std::make_shared<IR_IntMath2>(IR_IntMath2::MIN_SIGNED, make_reg(left, idx),
|
||||
make_reg(right, idx)));
|
||||
result->clobber = make_reg(temp, idx);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (is_gpr_3(i1, InstructionKind::MOVN, left, right, temp)) {
|
||||
// success!
|
||||
auto result =
|
||||
make_set(IR_Set::REG_64, make_reg(left, idx),
|
||||
std::make_shared<IR_IntMath2>(IR_IntMath2::MAX_SIGNED, make_reg(left, idx),
|
||||
make_reg(right, idx)));
|
||||
result->clobber = make_reg(temp, idx);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// THREE OP
|
||||
std::shared_ptr<IR> try_lui(Instruction& i0, Instruction& i1, Instruction& i2, int idx) {
|
||||
if (i0.kind == InstructionKind::LUI && i1.kind == InstructionKind::ORI &&
|
||||
i0.get_src(0).is_label() && i1.get_src(1).is_label() &&
|
||||
is_gpr_3(i2, InstructionKind::ADDU, {}, make_gpr(Reg::FP), {})) {
|
||||
assert(i0.get_dst(0).get_reg() == i1.get_src(0).get_reg());
|
||||
assert(i0.get_src(0).get_label() == i1.get_src(1).get_label());
|
||||
assert(i2.get_dst(0).get_reg() == i2.get_src(1).get_reg());
|
||||
assert(i2.get_dst(0).get_reg() == i1.get_dst(0).get_reg());
|
||||
auto op = make_set(IR_Set::REG_64, make_reg(i1.get_dst(0).get_reg(), idx),
|
||||
std::make_shared<IR_StaticAddress>(i0.get_src(0).get_label()));
|
||||
if (i0.get_dst(0).get_reg() != i1.get_dst(0).get_reg()) {
|
||||
op->clobber = make_reg(i0.get_dst(0).get_reg(), idx);
|
||||
}
|
||||
return op;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_dsubu(Instruction& i0, Instruction& i1, Instruction& i2, int idx) {
|
||||
if (i0.kind == InstructionKind::DSUBU && i1.kind == InstructionKind::DADDIU &&
|
||||
i2.kind == InstructionKind::MOVN) {
|
||||
|
@ -1136,6 +1329,21 @@ std::shared_ptr<IR> try_clts(Instruction& i0, Instruction& i1, Instruction& i2,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_cles(Instruction& i0, Instruction& i1, Instruction& i2, int idx) {
|
||||
if (i0.kind == InstructionKind::CLES && i1.kind == InstructionKind::BC1T) {
|
||||
return std::make_shared<IR_Branch>(
|
||||
Condition(Condition::FLOAT_LEQ, make_reg(i0.get_src(0).get_reg(), idx),
|
||||
make_reg(i0.get_src(1).get_reg(), idx), nullptr),
|
||||
i1.get_src(0).get_label(), get_branch_delay(i2, idx), false);
|
||||
} else if (i0.kind == InstructionKind::CLES && i1.kind == InstructionKind::BC1F) {
|
||||
return std::make_shared<IR_Branch>(
|
||||
Condition(Condition::FLOAT_GREATER_THAN, make_reg(i0.get_src(0).get_reg(), idx),
|
||||
make_reg(i0.get_src(1).get_reg(), idx), nullptr),
|
||||
i1.get_src(0).get_label(), get_branch_delay(i2, idx), false);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR> try_sltu(Instruction& i0, Instruction& i1, Instruction& i2, int idx) {
|
||||
if (i0.kind == InstructionKind::SLTU && i1.kind == InstructionKind::BNE) {
|
||||
auto clobber_reg = i0.get_dst(0).get_reg();
|
||||
|
@ -1268,6 +1476,12 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
|||
case InstructionKind::CLTS:
|
||||
result = try_clts(i, next, next_next, instr);
|
||||
break;
|
||||
case InstructionKind::CLES:
|
||||
result = try_cles(i, next, next_next, instr);
|
||||
break;
|
||||
case InstructionKind::LUI:
|
||||
result = try_lui(i, next, next_next, instr);
|
||||
break;
|
||||
default:
|
||||
result = nullptr;
|
||||
}
|
||||
|
@ -1317,6 +1531,9 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
|||
case InstructionKind::LUI:
|
||||
result = try_lui(i, next, instr);
|
||||
break;
|
||||
case InstructionKind::SLT:
|
||||
result = try_slt(i, next, instr);
|
||||
break;
|
||||
default:
|
||||
result = nullptr;
|
||||
}
|
||||
|
@ -1343,6 +1560,9 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
|||
case InstructionKind::ANDI:
|
||||
result = try_andi(i, instr);
|
||||
break;
|
||||
case InstructionKind::XORI:
|
||||
result = try_xori(i, instr);
|
||||
break;
|
||||
case InstructionKind::NOR:
|
||||
result = try_nor(i, instr);
|
||||
break;
|
||||
|
@ -1393,6 +1613,10 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
|||
break;
|
||||
case InstructionKind::DSUBU:
|
||||
result = try_dsubu(i, instr);
|
||||
if (!result) {
|
||||
// fails if it uses s7 register.
|
||||
result = to_asm_automatic(i.op_name_to_string(), i, instr);
|
||||
}
|
||||
break;
|
||||
case InstructionKind::MULT3:
|
||||
result = try_mult3(i, instr);
|
||||
|
@ -1402,6 +1626,9 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
|||
break;
|
||||
case InstructionKind::POR:
|
||||
result = try_por(i, instr);
|
||||
if (!result) {
|
||||
result = to_asm_automatic(i.op_name_to_string(), i, instr);
|
||||
}
|
||||
break;
|
||||
case InstructionKind::LBU:
|
||||
result = try_lbu(i, instr);
|
||||
|
@ -1447,12 +1674,18 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
|||
break;
|
||||
case InstructionKind::ADDIU:
|
||||
result = try_addiu(i, instr);
|
||||
if (!result) {
|
||||
result = to_asm_automatic(i.op_name_to_string(), i, instr);
|
||||
}
|
||||
break;
|
||||
case InstructionKind::LUI:
|
||||
result = try_lui(i, instr);
|
||||
break;
|
||||
case InstructionKind::SLL:
|
||||
result = try_sll(i, instr);
|
||||
if (!result) {
|
||||
result = to_asm_automatic(i.op_name_to_string(), i, instr);
|
||||
}
|
||||
break;
|
||||
case InstructionKind::SB:
|
||||
result = try_sb(i, instr);
|
||||
|
@ -1484,6 +1717,168 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
|||
case InstructionKind::DSRLV:
|
||||
result = try_dsrlv(i, instr);
|
||||
break;
|
||||
case InstructionKind::DSLLV:
|
||||
result = try_dsllv(i, instr);
|
||||
break;
|
||||
case InstructionKind::SUBU:
|
||||
result = try_subu(i, instr);
|
||||
break;
|
||||
case InstructionKind::SLLV:
|
||||
result = try_sllv(i, instr);
|
||||
break;
|
||||
case InstructionKind::MOVN:
|
||||
result = try_movn(i, instr);
|
||||
if (!result) {
|
||||
result = to_asm_automatic(i.op_name_to_string(), i, instr);
|
||||
}
|
||||
break;
|
||||
case InstructionKind::MOVZ:
|
||||
result = try_movz(i, instr);
|
||||
if (!result) {
|
||||
result = to_asm_automatic(i.op_name_to_string(), i, instr);
|
||||
}
|
||||
break;
|
||||
|
||||
// Everything below here is an "asm passthrough".
|
||||
case InstructionKind::JR:
|
||||
result = to_asm_src_reg("jr", i, instr);
|
||||
break;
|
||||
|
||||
// reg reg
|
||||
case InstructionKind::QMFC2:
|
||||
result = to_asm_dst_reg_src_reg(i.op_name_to_string(), i, instr);
|
||||
break;
|
||||
|
||||
// VU/COP2
|
||||
case InstructionKind::VMOVE:
|
||||
case InstructionKind::VFTOI0:
|
||||
case InstructionKind::VFTOI4:
|
||||
case InstructionKind::VFTOI12:
|
||||
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::ADDAS:
|
||||
|
||||
// Moves / Loads / Stores
|
||||
case InstructionKind::CTC2:
|
||||
case InstructionKind::CFC2:
|
||||
case InstructionKind::SQC2:
|
||||
case InstructionKind::LQC2:
|
||||
case InstructionKind::LDR:
|
||||
case InstructionKind::LDL:
|
||||
case InstructionKind::QMTC2:
|
||||
case InstructionKind::MFC0:
|
||||
case InstructionKind::MTC0:
|
||||
case InstructionKind::SYNCL:
|
||||
case InstructionKind::SYNCP:
|
||||
case InstructionKind::SYSCALL:
|
||||
case InstructionKind::CACHE_DXWBIN:
|
||||
case InstructionKind::MTPC:
|
||||
case InstructionKind::MFPC:
|
||||
|
||||
// random math
|
||||
case InstructionKind::ADDU:
|
||||
case InstructionKind::SRL: // maybe bitfield ops use this?
|
||||
case InstructionKind::SRA:
|
||||
case InstructionKind::SLT:
|
||||
case InstructionKind::SLTI:
|
||||
|
||||
// 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::PCGTW:
|
||||
case InstructionKind::PEXTLH:
|
||||
case InstructionKind::PEXTLB:
|
||||
case InstructionKind::PMAXH:
|
||||
case InstructionKind::PPACB:
|
||||
case InstructionKind::PADDW:
|
||||
case InstructionKind::PADDH:
|
||||
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::PMADDH:
|
||||
case InstructionKind::PMULTH:
|
||||
case InstructionKind::PEXEW:
|
||||
case InstructionKind::PCPYUD:
|
||||
case InstructionKind::PNOR:
|
||||
case InstructionKind::PCPYH:
|
||||
case InstructionKind::PINTEH:
|
||||
|
||||
// 128 bit integer
|
||||
// case InstructionKind::LQ:
|
||||
// case InstructionKind::SQ:
|
||||
|
||||
result = to_asm_automatic(i.op_name_to_string(), i, instr);
|
||||
break;
|
||||
default:
|
||||
result = nullptr;
|
||||
}
|
||||
|
@ -1496,9 +1891,13 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
|||
// everything failed
|
||||
if (!result) {
|
||||
// temp hack for debug:
|
||||
// printf("Instruction -> BasicOp failed on %s\n", i.to_string(*file).c_str());
|
||||
printf("Instruction -> BasicOp failed on %s\n", i.to_string(*file).c_str());
|
||||
func->add_basic_op(std::make_shared<IR_Failed>(), instr, instr + 1);
|
||||
} else {
|
||||
if (!func->contains_asm_ops && dynamic_cast<IR_AsmOp*>(result.get())) {
|
||||
func->warnings += "Function contains asm op";
|
||||
func->contains_asm_ops = true;
|
||||
}
|
||||
func->add_basic_op(result, instr, instr + length);
|
||||
instr += (length - 1);
|
||||
}
|
||||
|
|
|
@ -86,6 +86,20 @@ std::pair<IR_Branch*, std::shared_ptr<IR>*> get_condition_branch(std::shared_ptr
|
|||
condition_branch_location = &as_seq->forms.back();
|
||||
}
|
||||
}
|
||||
|
||||
if (!condition_branch) {
|
||||
auto as_return = dynamic_cast<IR_Return*>(in->get());
|
||||
if (as_return) {
|
||||
return get_condition_branch(&as_return->dead_code);
|
||||
}
|
||||
}
|
||||
|
||||
if (!condition_branch) {
|
||||
auto as_break = dynamic_cast<IR_Break*>(in->get());
|
||||
if (as_break) {
|
||||
return get_condition_branch(&as_break->dead_code);
|
||||
}
|
||||
}
|
||||
return std::make_pair(condition_branch, condition_branch_location);
|
||||
}
|
||||
|
||||
|
@ -134,6 +148,63 @@ void clean_up_cond_with_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
|
|||
}
|
||||
}
|
||||
|
||||
void clean_up_until_loop(IR_UntilLoop* ir) {
|
||||
auto condition_branch = get_condition_branch(&ir->condition);
|
||||
assert(condition_branch.first);
|
||||
assert(condition_branch.first->branch_delay.kind == BranchDelay::NOP);
|
||||
auto replacement = std::make_shared<IR_Compare>(condition_branch.first->condition);
|
||||
*(condition_branch.second) = replacement;
|
||||
}
|
||||
|
||||
void clean_up_infinite_while_loop(IR_WhileLoop* ir) {
|
||||
auto jump = get_condition_branch(&ir->body);
|
||||
assert(jump.first);
|
||||
assert(jump.first->branch_delay.kind == BranchDelay::NOP);
|
||||
assert(jump.first->condition.kind == Condition::ALWAYS);
|
||||
auto as_end_of_sequence = get_condition_branch_as_vector(ir->body.get());
|
||||
if (as_end_of_sequence.first) {
|
||||
assert(as_end_of_sequence.second->size() > 1);
|
||||
as_end_of_sequence.second->pop_back();
|
||||
} else {
|
||||
// In the future we could consider having a more explicit "this case is empty" operator so
|
||||
// this doesn't get confused with an actual MIPS nop.
|
||||
*(jump.second) = std::make_shared<IR_Nop>();
|
||||
}
|
||||
ir->cleaned = true; // so we don't try this later...
|
||||
}
|
||||
|
||||
void clean_up_return(IR_Return* ir) {
|
||||
auto jump_to_end = get_condition_branch(&ir->return_code);
|
||||
assert(jump_to_end.first);
|
||||
assert(jump_to_end.first->branch_delay.kind == BranchDelay::NOP);
|
||||
assert(jump_to_end.first->condition.kind == Condition::ALWAYS);
|
||||
auto as_end_of_sequence = get_condition_branch_as_vector(ir->return_code.get());
|
||||
if (as_end_of_sequence.first) {
|
||||
assert(as_end_of_sequence.second->size() > 1);
|
||||
as_end_of_sequence.second->pop_back();
|
||||
} else {
|
||||
// In the future we could consider having a more explicit "this case is empty" operator so
|
||||
// this doesn't get confused with an actual MIPS nop.
|
||||
*(jump_to_end.second) = std::make_shared<IR_Nop>();
|
||||
}
|
||||
}
|
||||
|
||||
void clean_up_break(IR_Break* ir) {
|
||||
auto jump_to_end = get_condition_branch(&ir->return_code);
|
||||
assert(jump_to_end.first);
|
||||
assert(jump_to_end.first->branch_delay.kind == BranchDelay::NOP);
|
||||
assert(jump_to_end.first->condition.kind == Condition::ALWAYS);
|
||||
auto as_end_of_sequence = get_condition_branch_as_vector(ir->return_code.get());
|
||||
if (as_end_of_sequence.first) {
|
||||
assert(as_end_of_sequence.second->size() > 1);
|
||||
as_end_of_sequence.second->pop_back();
|
||||
} else {
|
||||
// In the future we could consider having a more explicit "this case is empty" operator so
|
||||
// this doesn't get confused with an actual MIPS nop.
|
||||
*(jump_to_end.second) = std::make_shared<IR_Nop>();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Does the instruction in the delay slot set a register to false?
|
||||
* Note. a beql s7, x followed by a or y, x, r0 will count as this. I don't know why but
|
||||
|
@ -844,6 +915,25 @@ std::shared_ptr<IR> cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx)
|
|||
auto result = std::make_shared<IR_WhileLoop>(cfg_to_ir(f, file, wvtx->condition),
|
||||
cfg_to_ir(f, file, wvtx->body));
|
||||
return result;
|
||||
} else if (dynamic_cast<UntilLoop*>(vtx)) {
|
||||
auto wvtx = dynamic_cast<UntilLoop*>(vtx);
|
||||
auto result = std::make_shared<IR_UntilLoop>(cfg_to_ir(f, file, wvtx->condition),
|
||||
cfg_to_ir(f, file, wvtx->body));
|
||||
clean_up_until_loop(result.get());
|
||||
return result;
|
||||
} else if (dynamic_cast<UntilLoop_single*>(vtx)) {
|
||||
auto wvtx = dynamic_cast<UntilLoop_single*>(vtx);
|
||||
auto result =
|
||||
std::make_shared<IR_UntilLoop>(cfg_to_ir(f, file, wvtx->block), std::make_shared<IR_Nop>());
|
||||
clean_up_until_loop(result.get());
|
||||
return result;
|
||||
} else if (dynamic_cast<InfiniteLoopBlock*>(vtx)) {
|
||||
auto wvtx = dynamic_cast<InfiniteLoopBlock*>(vtx);
|
||||
auto result = std::make_shared<IR_WhileLoop>(
|
||||
std::make_shared<IR_Compare>(Condition(Condition::ALWAYS, nullptr, nullptr, nullptr)),
|
||||
cfg_to_ir(f, file, wvtx->block));
|
||||
clean_up_infinite_while_loop(result.get());
|
||||
return result;
|
||||
} else if (dynamic_cast<CondWithElse*>(vtx)) {
|
||||
auto* cvtx = dynamic_cast<CondWithElse*>(vtx);
|
||||
|
||||
|
@ -912,7 +1002,6 @@ std::shared_ptr<IR> cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx)
|
|||
}
|
||||
auto result = std::make_shared<IR_ShortCircuit>(entries);
|
||||
clean_up_sc(result, file);
|
||||
// todo clean these into real and/or.
|
||||
return result;
|
||||
} else if (dynamic_cast<CondNoElse*>(vtx)) {
|
||||
auto* cvtx = dynamic_cast<CondNoElse*>(vtx);
|
||||
|
@ -926,6 +1015,18 @@ std::shared_ptr<IR> cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx)
|
|||
std::shared_ptr<IR> result = std::make_shared<IR_Cond>(entries);
|
||||
clean_up_cond_no_else(&result, file);
|
||||
return result;
|
||||
} else if (dynamic_cast<GotoEnd*>(vtx)) {
|
||||
auto* cvtx = dynamic_cast<GotoEnd*>(vtx);
|
||||
auto result = std::make_shared<IR_Return>(cfg_to_ir(f, file, cvtx->body),
|
||||
cfg_to_ir(f, file, cvtx->unreachable_block));
|
||||
clean_up_return(result.get());
|
||||
return result;
|
||||
} else if (dynamic_cast<Break*>(vtx)) {
|
||||
auto* cvtx = dynamic_cast<Break*>(vtx);
|
||||
auto result = std::make_shared<IR_Break>(cfg_to_ir(f, file, cvtx->body),
|
||||
cfg_to_ir(f, file, cvtx->unreachable_block));
|
||||
clean_up_break(result.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
throw std::runtime_error("not yet implemented IR conversion.");
|
||||
|
@ -942,7 +1043,7 @@ void clean_up_while_loops(IR_Begin* sequence, LinkedObjectFile& file) {
|
|||
std::vector<size_t> to_remove; // the list of branches to remove by index in this sequence
|
||||
for (size_t i = 0; i < sequence->forms.size(); i++) {
|
||||
auto* form_as_while = dynamic_cast<IR_WhileLoop*>(sequence->forms.at(i).get());
|
||||
if (form_as_while) {
|
||||
if (form_as_while && !form_as_while->cleaned) {
|
||||
assert(i != 0);
|
||||
auto prev_as_branch = dynamic_cast<IR_Branch*>(sequence->forms.at(i - 1).get());
|
||||
assert(prev_as_branch);
|
||||
|
@ -989,7 +1090,6 @@ std::shared_ptr<IR> build_cfg_ir(Function& function,
|
|||
|
||||
try {
|
||||
auto top_level = cfg.get_single_top_level();
|
||||
// todo, we should apply transformations for fixing up branch instructions for each IR.
|
||||
// and possibly annotate the IR control flow structure so that we can determine if its and/or
|
||||
// or whatever. This may require rejecting a huge number of inline assembly functions, and
|
||||
// possibly resolving the min/max/ash issue.
|
||||
|
|
|
@ -256,6 +256,12 @@ goos::Object IR_IntMath2::to_form(const LinkedObjectFile& file) const {
|
|||
case RIGHT_SHIFT_LOGIC:
|
||||
math_operator = "shr";
|
||||
break;
|
||||
case MIN_SIGNED:
|
||||
math_operator = "min.si";
|
||||
break;
|
||||
case MAX_SIGNED:
|
||||
math_operator = "max.si";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
@ -399,6 +405,8 @@ int Condition::num_args() const {
|
|||
case FLOAT_NOT_EQUAL:
|
||||
case FLOAT_LESS_THAN:
|
||||
case FLOAT_GEQ:
|
||||
case FLOAT_GREATER_THAN:
|
||||
case FLOAT_LEQ:
|
||||
return 2;
|
||||
case ZERO:
|
||||
case NONZERO:
|
||||
|
@ -501,6 +509,12 @@ void Condition::invert() {
|
|||
case FLOAT_GEQ:
|
||||
kind = FLOAT_LESS_THAN;
|
||||
break;
|
||||
case FLOAT_GREATER_THAN:
|
||||
kind = FLOAT_LEQ;
|
||||
break;
|
||||
case FLOAT_LEQ:
|
||||
kind = FLOAT_GREATER_THAN;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
@ -570,6 +584,12 @@ goos::Object Condition::to_form(const LinkedObjectFile& file) const {
|
|||
case FLOAT_GEQ:
|
||||
condtion_operator = ">=.f";
|
||||
break;
|
||||
case FLOAT_GREATER_THAN:
|
||||
condtion_operator = ">.f";
|
||||
break;
|
||||
case FLOAT_LEQ:
|
||||
condtion_operator = "<=.f";
|
||||
break;
|
||||
case GREATER_THAN_ZERO_SIGNED:
|
||||
condtion_operator = ">0.si";
|
||||
break;
|
||||
|
@ -680,6 +700,19 @@ void IR_WhileLoop::get_children(std::vector<std::shared_ptr<IR>>* output) const
|
|||
output->push_back(body);
|
||||
}
|
||||
|
||||
goos::Object IR_UntilLoop::to_form(const LinkedObjectFile& file) const {
|
||||
std::vector<goos::Object> list;
|
||||
list.push_back(pretty_print::to_symbol("until"));
|
||||
list.push_back(condition->to_form(file));
|
||||
print_inlining_begin(&list, body.get(), file);
|
||||
return pretty_print::build_list(list);
|
||||
}
|
||||
|
||||
void IR_UntilLoop::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(condition);
|
||||
output->push_back(body);
|
||||
}
|
||||
|
||||
goos::Object IR_CondWithElse::to_form(const LinkedObjectFile& file) const {
|
||||
// for now we only turn it into an if statement if both cases won't require a begin at the top
|
||||
// level. I think it is more common to write these as a two-case cond instead of an if with begin.
|
||||
|
@ -801,3 +834,74 @@ void IR_Ash::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
|||
output->push_back(value);
|
||||
output->push_back(shift_amount);
|
||||
}
|
||||
|
||||
goos::Object IR_AsmOp::to_form(const LinkedObjectFile& file) const {
|
||||
std::vector<goos::Object> forms;
|
||||
forms.push_back(pretty_print::to_symbol(name));
|
||||
for (auto& x : {dst, src0, src1, src2}) {
|
||||
if (x) {
|
||||
forms.push_back(x->to_form(file));
|
||||
}
|
||||
}
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
void IR_AsmOp::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
for (auto& x : {dst, src0, src1}) {
|
||||
if (x) {
|
||||
output->push_back(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object IR_CMoveF::to_form(const LinkedObjectFile& file) const {
|
||||
return pretty_print::build_list(
|
||||
pretty_print::to_symbol(on_zero ? "cmove-false-on-zero" : "cmove-false-on-nonzero"),
|
||||
src->to_form(file));
|
||||
}
|
||||
|
||||
void IR_CMoveF::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(src);
|
||||
}
|
||||
|
||||
goos::Object IR_AsmReg::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
switch (kind) {
|
||||
case VU_Q:
|
||||
return pretty_print::to_symbol("Q");
|
||||
case VU_ACC:
|
||||
return pretty_print::to_symbol("ACC");
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void IR_AsmReg::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
goos::Object IR_Return::to_form(const LinkedObjectFile& file) const {
|
||||
std::vector<goos::Object> forms;
|
||||
forms.push_back(pretty_print::to_symbol("return"));
|
||||
forms.push_back(pretty_print::build_list(return_code->to_form(file)));
|
||||
forms.push_back(pretty_print::build_list(dead_code->to_form(file)));
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
void IR_Return::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(return_code);
|
||||
output->push_back(dead_code);
|
||||
}
|
||||
|
||||
goos::Object IR_Break::to_form(const LinkedObjectFile& file) const {
|
||||
std::vector<goos::Object> forms;
|
||||
forms.push_back(pretty_print::to_symbol("break")); // todo break destination...
|
||||
forms.push_back(pretty_print::build_list(return_code->to_form(file)));
|
||||
forms.push_back(pretty_print::build_list(dead_code->to_form(file)));
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
void IR_Break::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(return_code);
|
||||
output->push_back(dead_code);
|
||||
}
|
|
@ -136,7 +136,9 @@ class IR_IntMath2 : public IR {
|
|||
LEFT_SHIFT,
|
||||
RIGHT_SHIFT_ARITH,
|
||||
RIGHT_SHIFT_LOGIC,
|
||||
MUL_UNSIGNED
|
||||
MUL_UNSIGNED,
|
||||
MIN_SIGNED,
|
||||
MAX_SIGNED
|
||||
} kind;
|
||||
IR_IntMath2(Kind _kind, std::shared_ptr<IR> _arg0, std::shared_ptr<IR> _arg1)
|
||||
: kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {}
|
||||
|
@ -212,7 +214,9 @@ struct Condition {
|
|||
FLOAT_EQUAL,
|
||||
FLOAT_NOT_EQUAL,
|
||||
FLOAT_LESS_THAN,
|
||||
FLOAT_GEQ
|
||||
FLOAT_GEQ,
|
||||
FLOAT_LEQ,
|
||||
FLOAT_GREATER_THAN,
|
||||
} kind;
|
||||
|
||||
Condition(Kind _kind,
|
||||
|
@ -294,6 +298,16 @@ class IR_WhileLoop : public IR {
|
|||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
std::shared_ptr<IR> condition, body;
|
||||
bool cleaned = false;
|
||||
};
|
||||
|
||||
class IR_UntilLoop : public IR {
|
||||
public:
|
||||
IR_UntilLoop(std::shared_ptr<IR> _condition, std::shared_ptr<IR> _body)
|
||||
: condition(std::move(_condition)), body(std::move(_body)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
std::shared_ptr<IR> condition, body;
|
||||
};
|
||||
|
||||
class IR_CondWithElse : public IR {
|
||||
|
@ -370,4 +384,54 @@ class IR_Ash : public IR {
|
|||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_AsmOp : public IR {
|
||||
public:
|
||||
std::shared_ptr<IR> dst = nullptr;
|
||||
std::shared_ptr<IR> src0 = nullptr;
|
||||
std::shared_ptr<IR> src1 = nullptr;
|
||||
std::shared_ptr<IR> src2 = nullptr;
|
||||
std::string name;
|
||||
IR_AsmOp(std::string _name) : name(std::move(_name)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_CMoveF : public IR {
|
||||
public:
|
||||
std::shared_ptr<IR> src = nullptr;
|
||||
bool on_zero = false;
|
||||
explicit IR_CMoveF(std::shared_ptr<IR> _src, bool _on_zero)
|
||||
: src(std::move(_src)), on_zero(_on_zero) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_AsmReg : public IR {
|
||||
public:
|
||||
enum Kind { VU_Q, VU_ACC } kind;
|
||||
explicit IR_AsmReg(Kind _kind) : kind(_kind) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Return : public IR {
|
||||
public:
|
||||
std::shared_ptr<IR> return_code;
|
||||
std::shared_ptr<IR> dead_code;
|
||||
IR_Return(std::shared_ptr<IR> _return_code, std::shared_ptr<IR> _dead_code)
|
||||
: return_code(std::move(_return_code)), dead_code(std::move(_dead_code)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Break : public IR {
|
||||
public:
|
||||
std::shared_ptr<IR> return_code;
|
||||
std::shared_ptr<IR> dead_code;
|
||||
IR_Break(std::shared_ptr<IR> _return_code, std::shared_ptr<IR> _dead_code)
|
||||
: return_code(std::move(_return_code)), dead_code(std::move(_dead_code)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
#endif // JAK_IR_H
|
||||
|
|
|
@ -556,8 +556,10 @@ void ObjectFileDB::analyze_functions() {
|
|||
std::unordered_set<std::string> unique_names;
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> duplicated_functions;
|
||||
|
||||
int uid = 1;
|
||||
for_each_function([&](Function& func, int segment_id, ObjectFileData& data) {
|
||||
(void)segment_id;
|
||||
func.guessed_name.unique_id = uid++;
|
||||
auto name = func.guessed_name.to_string();
|
||||
if (func.guessed_name.expected_unique()) {
|
||||
if (unique_names.find(name) != unique_names.end()) {
|
||||
|
@ -606,12 +608,13 @@ void ObjectFileDB::analyze_functions() {
|
|||
timer.start();
|
||||
int total_basic_blocks = 0;
|
||||
for_each_function([&](Function& func, int segment_id, ObjectFileData& data) {
|
||||
// printf("in %s\n", func.guessed_name.to_string().c_str());
|
||||
// printf("in %s\n", func.guessed_name.to_string().c_str());
|
||||
auto blocks = find_blocks_in_function(data.linked_data, segment_id, func);
|
||||
total_basic_blocks += blocks.size();
|
||||
func.basic_blocks = blocks;
|
||||
|
||||
total_functions++;
|
||||
|
||||
if (!func.suspected_asm) {
|
||||
func.analyze_prologue(data.linked_data);
|
||||
func.cfg = build_cfg(data.linked_data, segment_id, func);
|
||||
|
@ -639,10 +642,8 @@ void ObjectFileDB::analyze_functions() {
|
|||
if (func.basic_blocks.size() > 1 && !func.suspected_asm) {
|
||||
if (func.cfg->is_fully_resolved()) {
|
||||
} else {
|
||||
if (!func.guessed_name.empty()) {
|
||||
unresolved_by_length[func.end_word - func.start_word].push_back(
|
||||
func.guessed_name.to_string());
|
||||
}
|
||||
unresolved_by_length[func.end_word - func.start_word].push_back(
|
||||
func.guessed_name.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -673,11 +674,11 @@ void ObjectFileDB::analyze_functions() {
|
|||
printf(" %d/%d cfgs converted to ir (%.2f%%)\n", successful_cfg_irs, non_asm_funcs,
|
||||
100.f * float(successful_cfg_irs) / float(non_asm_funcs));
|
||||
|
||||
// for (auto& kv : unresolved_by_length) {
|
||||
// printf("LEN %d\n", kv.first);
|
||||
// for (auto& x : kv.second) {
|
||||
// printf(" %s\n", x.c_str());
|
||||
// }
|
||||
// }
|
||||
for (auto& kv : unresolved_by_length) {
|
||||
printf("LEN %d\n", kv.first);
|
||||
for (auto& x : kv.second) {
|
||||
printf(" %s\n", x.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,162 @@
|
|||
"find_basic_blocks":true,
|
||||
|
||||
"asm_functions_by_name":[
|
||||
// functions which have inline assembly
|
||||
"unpack-comp-huf", "(method 29 collide-shape-prim-group)","find-knot-span","dma-send-no-scratch",
|
||||
"raw-ray-sphere-intersect","(method 9 bounding-box)","(method 9 matrix)","shadow-find-single-edges",
|
||||
"generic-tie-dma-to-spad-sync","ray-cylinder-intersect","shadow-scissor-edges","(method 42 collide-shape)",
|
||||
"(method 9 texture-page-dir)",
|
||||
"(method 24 collide-shape-prim)",
|
||||
"(method 23 collide-shape-prim-group)",
|
||||
"shadow-find-double-edges",
|
||||
"(method 16 collide-shape-prim)",
|
||||
"(method 15 collide-shape-prim-group)",
|
||||
"generic-tie-upload-next",
|
||||
"(method 17 collide-edge-work)",
|
||||
"(method 16 drawable-tree)",
|
||||
"(method 10 collide-mesh)",
|
||||
"particle-adgif",
|
||||
"(method 14 collide-shape-prim-group)",
|
||||
"(method 12 collide-shape-prim-group)",
|
||||
"(method 14 bounding-box)",
|
||||
"process-drawable-birth-fuel-cell",
|
||||
"(method 13 collide-shape-prim-group)",
|
||||
"ray-triangle-intersect",
|
||||
"(method 20 collide-shape-prim-group)",
|
||||
"(method 10 collide-puss-work)",
|
||||
"setup-blerc-chains-for-one-fragment",
|
||||
"curve-evaluate!",
|
||||
"(method 16 collide-edge-work)",
|
||||
"(method 9 collide-cache-prim)",
|
||||
"(method 11 collide-mesh)",
|
||||
"stats-tfrag-asm",
|
||||
"(method 10 collide-cache-prim)",
|
||||
"high-speed-reject",
|
||||
"(method 12 collide-mesh)",
|
||||
"(method 19 collide-cache)",
|
||||
"(method 9 collide-puss-work)",
|
||||
"(method 29 collide-cache)",
|
||||
"time-of-day-interp-colors-scratch",
|
||||
"(method 30 collide-cache)",
|
||||
"calc-animation-from-spr",
|
||||
"(method 14 collide-shape-prim-mesh)",
|
||||
"(method 12 collide-shape-prim-mesh)",
|
||||
"(method 19 process-drawable)",
|
||||
"(method 13 collide-shape-prim-mesh)",
|
||||
"moving-sphere-triangle-intersect",
|
||||
"(method 14 collide-mesh)",
|
||||
"circle-circle-xz-intersect",
|
||||
"get-string-length",
|
||||
"draw-node-cull",
|
||||
"collide-probe-node",
|
||||
"(method 28 collide-cache)",
|
||||
"(method 26 collide-cache)",
|
||||
"load-game-text-info",
|
||||
"(method 27 collide-cache)",
|
||||
"clip-polygon-against-positive-hyperplane",
|
||||
"sp-process-block-2d",
|
||||
"sp-init-fields!",
|
||||
"clip-polygon-against-negative-hyperplane",
|
||||
"(method 9 edge-grab-info)",
|
||||
"(method 18 collide-edge-work)",
|
||||
"sp-process-block-3d",
|
||||
"time-of-day-interp-colors",
|
||||
"(method 23 collide-shape-prim-sphere)",
|
||||
"(method 15 collide-edge-work)",
|
||||
"(method 15 collide-mesh)",
|
||||
"(method 21 collide-cache)",
|
||||
"mercneric-shader-asm",
|
||||
"shadow-execute",
|
||||
"(method 16 level)",
|
||||
"(method 40 collide-shape)",
|
||||
"(method 32 collide-cache)",
|
||||
"bones-mtx-calc",
|
||||
"draw-inline-array-prototype-tie-near-asm",
|
||||
"decompress-fixed-data-to-accumulator",
|
||||
"draw-inline-array-prototype-tie-asm",
|
||||
"(method 10 external-art-buffer)",
|
||||
"level-update-after-load",
|
||||
"draw-inline-array-prototype-tie-generic-asm",
|
||||
"draw-inline-array-tfrag-near",
|
||||
"collide-probe-instance-tie",
|
||||
"draw-inline-array-tfrag",
|
||||
"mercneric-matrix-asm",
|
||||
"(method 32 nav-control)",
|
||||
"(method 11 fact-info-target)",
|
||||
"mercneric-convert",
|
||||
"generic-envmap-only-proc",
|
||||
"draw-inline-array-instance-tie",
|
||||
"generic-tie-convert-proc",
|
||||
"draw-string",
|
||||
"draw-inline-array-instance-shrub",
|
||||
"generic-tie-convert",
|
||||
"(anon-function 2503)",
|
||||
"(anon-function 5635)",
|
||||
"(anon-function 2504)",
|
||||
"(anon-function 2502)",
|
||||
"(anon-function 2501)",
|
||||
"(anon-function 2505)",
|
||||
"(anon-function 2500)",
|
||||
"(anon-function 3717)",
|
||||
"rand-uint31-gen",
|
||||
"dma-sync-with-count",
|
||||
"ripple-create-wave-table",
|
||||
"generic-debug-light-proc",
|
||||
"draw-bones-hud",
|
||||
"(method 27 ropebridge)",
|
||||
"ocean-interp-wave",
|
||||
"ocean-generate-verts",
|
||||
"draw-large-polygon-ocean",
|
||||
"(method 23 collide-shape-prim-mesh)",
|
||||
"(method 13 collide-mesh)",
|
||||
"draw-boundary-polygon",
|
||||
"draw-large-polygon",
|
||||
"update-mood-lava",
|
||||
"update-mood-lightning",
|
||||
"memcpy",
|
||||
"background-upload-vu0",
|
||||
"upload-vis-bits",
|
||||
"generic-envmap-dproc",
|
||||
"generic-prepare-dma-double",
|
||||
"generic-envmap-proc",
|
||||
"generic-light-proc",
|
||||
"generic-merc-execute-asm",
|
||||
"generic-merc-init-asm",
|
||||
"shadow-calc-dual-verts",
|
||||
"shadow-scissor-top",
|
||||
"shadow-find-facing-single-tris",
|
||||
"shadow-find-facing-double-tris",
|
||||
"shadow-add-verts",
|
||||
"shadow-add-facing-single-tris",
|
||||
"shadow-add-single-edges",
|
||||
"shadow-add-double-tris",
|
||||
"shadow-add-double-edges",
|
||||
"test-func",
|
||||
"(method 14 collide-cache)",
|
||||
"symlink2",
|
||||
"draw-bones-merc",
|
||||
"dma-count-until-done",
|
||||
"symlink3",
|
||||
"blerc-execute",
|
||||
"merc-dma-chain-to-spr",
|
||||
"ripple-matrix-scale",
|
||||
"ripple-apply-wave-table",
|
||||
"ripple-execute-init",
|
||||
"bones-mtx-calc-execute",
|
||||
"texscroll-execute",
|
||||
"generic-light",
|
||||
"generic-no-light",
|
||||
"generic-no-light+envmap",
|
||||
"generic-dma-from-spr",
|
||||
"upload-vu0-program",
|
||||
"generic-merc-execute-all",
|
||||
"closest-pt-in-triangle",
|
||||
"(method 11 sparticle-launch-control)",
|
||||
"(anon-function 3751)",
|
||||
"look-for-points-of-interest",
|
||||
|
||||
// gcommon
|
||||
"min", "max", "(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!",
|
||||
"(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!",
|
||||
|
||||
// pskernel
|
||||
"resend-exception", "kernel-set-interrupt-vector", "kernel-set-exception-vector", "return-from-exception",
|
||||
|
|
Loading…
Reference in a new issue