diff --git a/decompiler/Disasm/Instruction.cpp b/decompiler/Disasm/Instruction.cpp index 721115c97..bde9c6f6e 100644 --- a/decompiler/Disasm/Instruction.cpp +++ b/decompiler/Disasm/Instruction.cpp @@ -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) { diff --git a/decompiler/Disasm/Instruction.h b/decompiler/Disasm/Instruction.h index f3ccaf969..c97ce621e 100644 --- a/decompiler/Disasm/Instruction.h +++ b/decompiler/Disasm/Instruction.h @@ -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; diff --git a/decompiler/Function/CfgVtx.cpp b/decompiler/Function/CfgVtx.cpp index b95b13427..0362affe7 100644 --- a/decompiler/Function/CfgVtx.cpp +++ b/decompiler/Function/CfgVtx.cpp @@ -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 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(); @@ -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(); + 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 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()) { diff --git a/decompiler/Function/CfgVtx.h b/decompiler/Function/CfgVtx.h index e3f46eb55..7031c3721 100644 --- a/decompiler/Function/CfgVtx.h +++ b/decompiler/Function/CfgVtx.h @@ -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 m_blocks; // all block nodes, in order. std::vector m_node_pool; // all nodes allocated EntryVtx* m_entry; // the entry vertex diff --git a/decompiler/Function/Function.h b/decompiler/Function/Function.h index 156d1b8e1..5e8ddfe71 100644 --- a/decompiler/Function/Function.h +++ b/decompiler/Function/Function.h @@ -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? diff --git a/decompiler/IR/BasicOpBuilder.cpp b/decompiler/IR/BasicOpBuilder.cpp index 754965050..adc8d25e4 100644 --- a/decompiler/IR/BasicOpBuilder.cpp +++ b/decompiler/IR/BasicOpBuilder.cpp @@ -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 make_set(IR_Set::Kind kind, const std::shared_ptr& dst, const std::shared_ptr& src) { return std::make_shared(kind, dst, src); } +/*! + * Create an IR representing a register at a certain point. Idx is the instruction index. + */ std::shared_ptr make_reg(Register reg, int idx) { return std::make_shared(reg, idx); } +/*! + * Create an IR representing a symbol. The symbol itself ('thing), not the value. + */ std::shared_ptr make_sym(const std::string& name) { return std::make_shared(name); } +/*! + * Create an IR representing the value of a symbol. Can be read/written. + */ std::shared_ptr make_sym_value(const std::string& name) { return std::make_shared(name); } +/*! + * Create an integer constant. + */ std::shared_ptr make_int(int64_t x) { return std::make_shared(x); } +/*! + * Create an assembly passthrough in the form op dst, src, src + */ +std::shared_ptr to_asm_reg_reg_reg(const std::string& str, Instruction& instr, int idx) { + auto result = std::make_shared(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 to_asm_src_reg(const std::string& str, Instruction& instr, int idx) { + auto result = std::make_shared(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 to_asm_dst_reg_src_reg(const std::string& str, Instruction& instr, int idx) { + auto result = std::make_shared(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 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::VU_Q); + case InstructionAtom::VU_ACC: + return std::make_shared(IR_AsmReg::VU_ACC); + case InstructionAtom::IMM: + return make_int(ia.get_imm()); + default: + assert(false); + } +} + +std::shared_ptr to_asm_automatic(const std::string& str, Instruction& instr, int idx) { + auto result = std::make_shared(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 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 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 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 try_daddu(Instruction& instr, int idx) { std::make_shared(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 try_dsubu(Instruction& instr, int idx) { @@ -459,6 +569,16 @@ std::shared_ptr try_andi(Instruction& instr, int idx) { return nullptr; } +std::shared_ptr 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::XOR, make_reg(instr.get_src(0).get_reg(), idx), + make_int(instr.get_src(1).get_imm()))); + } + return nullptr; +} + std::shared_ptr 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 try_dsrlv(Instruction& instr, int idx) { return nullptr; } +std::shared_ptr 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::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 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 try_movs(Instruction& instr, int idx) { return nullptr; } +std::shared_ptr 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(make_reg(instr.get_src(1).get_reg(), idx), false)); + } + return nullptr; +} + +std::shared_ptr 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(make_reg(instr.get_src(1).get_reg(), idx), true)); + } + return nullptr; +} + // TWO Instructions std::shared_ptr 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 try_lui(Instruction& i0, Instruction& i1, int idx) { return nullptr; } +std::shared_ptr 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::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::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 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(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 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 try_clts(Instruction& i0, Instruction& i1, Instruction& i2, return nullptr; } +std::shared_ptr try_cles(Instruction& i0, Instruction& i1, Instruction& i2, int idx) { + if (i0.kind == InstructionKind::CLES && i1.kind == InstructionKind::BC1T) { + return std::make_shared( + 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( + 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 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(), instr, instr + 1); } else { + if (!func->contains_asm_ops && dynamic_cast(result.get())) { + func->warnings += "Function contains asm op"; + func->contains_asm_ops = true; + } func->add_basic_op(result, instr, instr + length); instr += (length - 1); } diff --git a/decompiler/IR/CfgBuilder.cpp b/decompiler/IR/CfgBuilder.cpp index eca4a3d84..c7180e96d 100644 --- a/decompiler/IR/CfgBuilder.cpp +++ b/decompiler/IR/CfgBuilder.cpp @@ -86,6 +86,20 @@ std::pair*> get_condition_branch(std::shared_ptr condition_branch_location = &as_seq->forms.back(); } } + + if (!condition_branch) { + auto as_return = dynamic_cast(in->get()); + if (as_return) { + return get_condition_branch(&as_return->dead_code); + } + } + + if (!condition_branch) { + auto as_break = dynamic_cast(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, 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(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->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(); + } +} + +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(); + } +} + /*! * 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 cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx) auto result = std::make_shared(cfg_to_ir(f, file, wvtx->condition), cfg_to_ir(f, file, wvtx->body)); return result; + } else if (dynamic_cast(vtx)) { + auto wvtx = dynamic_cast(vtx); + auto result = std::make_shared(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(vtx)) { + auto wvtx = dynamic_cast(vtx); + auto result = + std::make_shared(cfg_to_ir(f, file, wvtx->block), std::make_shared()); + clean_up_until_loop(result.get()); + return result; + } else if (dynamic_cast(vtx)) { + auto wvtx = dynamic_cast(vtx); + auto result = std::make_shared( + std::make_shared(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(vtx)) { auto* cvtx = dynamic_cast(vtx); @@ -912,7 +1002,6 @@ std::shared_ptr cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx) } auto result = std::make_shared(entries); clean_up_sc(result, file); - // todo clean these into real and/or. return result; } else if (dynamic_cast(vtx)) { auto* cvtx = dynamic_cast(vtx); @@ -926,6 +1015,18 @@ std::shared_ptr cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx) std::shared_ptr result = std::make_shared(entries); clean_up_cond_no_else(&result, file); return result; + } else if (dynamic_cast(vtx)) { + auto* cvtx = dynamic_cast(vtx); + auto result = std::make_shared(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(vtx)) { + auto* cvtx = dynamic_cast(vtx); + auto result = std::make_shared(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 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(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(sequence->forms.at(i - 1).get()); assert(prev_as_branch); @@ -989,7 +1090,6 @@ std::shared_ptr 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. diff --git a/decompiler/IR/IR.cpp b/decompiler/IR/IR.cpp index 277e51b6e..425e8719c 100644 --- a/decompiler/IR/IR.cpp +++ b/decompiler/IR/IR.cpp @@ -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>* output) const output->push_back(body); } +goos::Object IR_UntilLoop::to_form(const LinkedObjectFile& file) const { + std::vector 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>* 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>* output) const { output->push_back(value); output->push_back(shift_amount); } + +goos::Object IR_AsmOp::to_form(const LinkedObjectFile& file) const { + std::vector 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>* 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>* 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>* output) const { + (void)output; +} + +goos::Object IR_Return::to_form(const LinkedObjectFile& file) const { + std::vector 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>* output) const { + output->push_back(return_code); + output->push_back(dead_code); +} + +goos::Object IR_Break::to_form(const LinkedObjectFile& file) const { + std::vector 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>* output) const { + output->push_back(return_code); + output->push_back(dead_code); +} \ No newline at end of file diff --git a/decompiler/IR/IR.h b/decompiler/IR/IR.h index 4ea0b2ac7..979c94ff3 100644 --- a/decompiler/IR/IR.h +++ b/decompiler/IR/IR.h @@ -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 _arg0, std::shared_ptr _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>* output) const override; std::shared_ptr condition, body; + bool cleaned = false; +}; + +class IR_UntilLoop : public IR { + public: + IR_UntilLoop(std::shared_ptr _condition, std::shared_ptr _body) + : condition(std::move(_condition)), body(std::move(_body)) {} + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* output) const override; + std::shared_ptr condition, body; }; class IR_CondWithElse : public IR { @@ -370,4 +384,54 @@ class IR_Ash : public IR { void get_children(std::vector>* output) const override; }; +class IR_AsmOp : public IR { + public: + std::shared_ptr dst = nullptr; + std::shared_ptr src0 = nullptr; + std::shared_ptr src1 = nullptr; + std::shared_ptr 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>* output) const override; +}; + +class IR_CMoveF : public IR { + public: + std::shared_ptr src = nullptr; + bool on_zero = false; + explicit IR_CMoveF(std::shared_ptr _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>* 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>* output) const override; +}; + +class IR_Return : public IR { + public: + std::shared_ptr return_code; + std::shared_ptr dead_code; + IR_Return(std::shared_ptr _return_code, std::shared_ptr _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>* output) const override; +}; + +class IR_Break : public IR { + public: + std::shared_ptr return_code; + std::shared_ptr dead_code; + IR_Break(std::shared_ptr _return_code, std::shared_ptr _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>* output) const override; +}; + #endif // JAK_IR_H diff --git a/decompiler/ObjectFile/ObjectFileDB.cpp b/decompiler/ObjectFile/ObjectFileDB.cpp index 184dbd34f..70c6210b7 100644 --- a/decompiler/ObjectFile/ObjectFileDB.cpp +++ b/decompiler/ObjectFile/ObjectFileDB.cpp @@ -556,8 +556,10 @@ void ObjectFileDB::analyze_functions() { std::unordered_set unique_names; std::unordered_map> 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()); + } + } } } diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index 5404b03c4..b62b8c149 100644 --- a/decompiler/config/jak1_ntsc_black_label.jsonc +++ b/decompiler/config/jak1_ntsc_black_label.jsonc @@ -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",