Cleanup PR

This commit is contained in:
doctashay
2020-10-08 09:16:34 -06:00
parent 9bbb47006c
commit 8d9b87cfed
6 changed files with 332 additions and 78 deletions
+15
View File
@@ -522,6 +522,17 @@ std::shared_ptr<IR> try_dsrav(Instruction& instr, int idx) {
return nullptr;
}
std::shared_ptr<IR> try_dsrlv(Instruction& instr, int idx) {
if (is_gpr_3(instr, InstructionKind::DSRLV, {}, {}, {}) &&
!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::RIGHT_SHIFT_LOGIC,
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))) {
@@ -1208,6 +1219,7 @@ std::shared_ptr<IR> try_lwu(Instruction& i0,
} // namespace
void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file) {
(void)file;
for (int instr = block.start_word; instr < block.end_word; instr++) {
auto& i = func->instructions.at(instr);
@@ -1469,6 +1481,9 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
case InstructionKind::DSRAV:
result = try_dsrav(i, instr);
break;
case InstructionKind::DSRLV:
result = try_dsrlv(i, instr);
break;
default:
result = nullptr;
}
+225 -11
View File
@@ -107,6 +107,7 @@ void clean_up_cond_with_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
assert(jump_to_next.first->branch_delay.kind == BranchDelay::NOP);
// patch the jump to next with a condition.
auto replacement = std::make_shared<IR_Compare>(jump_to_next.first->condition);
replacement->condition.invert();
*(jump_to_next.second) = replacement;
// patch the jump at the end of a block.
@@ -133,6 +134,208 @@ void clean_up_cond_with_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
}
}
/*!
* 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
* GOAL does this on comparisons to false.
*/
bool delay_slot_sets_false(IR_Branch* branch) {
if (branch->branch_delay.kind == BranchDelay::SET_REG_FALSE) {
return true;
}
if (branch->condition.kind == Condition::FALSE &&
branch->branch_delay.kind == BranchDelay::SET_REG_REG) {
auto reg_check = dynamic_cast<IR_Register*>(branch->condition.src0.get());
assert(reg_check);
auto reg_read = dynamic_cast<IR_Register*>(branch->branch_delay.source.get());
assert(reg_read);
return reg_check->reg == reg_read->reg;
}
return false;
}
/*!
* Does the instruction in the delay slot set a register to a truthy value, like in a GOAL
* or form branch? Either it explicitly sets #t, or it tests the value for being not false,
* then uses that
*/
bool delay_slot_sets_truthy(IR_Branch* branch) {
if (branch->branch_delay.kind == BranchDelay::SET_REG_TRUE) {
return true;
}
if (branch->condition.kind == Condition::TRUTHY &&
branch->branch_delay.kind == BranchDelay::SET_REG_REG) {
auto reg_check = dynamic_cast<IR_Register*>(branch->condition.src0.get());
assert(reg_check);
auto reg_read = dynamic_cast<IR_Register*>(branch->branch_delay.source.get());
assert(reg_read);
return reg_check->reg == reg_read->reg;
}
return false;
}
/*!
* Try to convert a short circuit to an and.
*/
bool try_clean_up_sc_as_and(std::shared_ptr<IR_ShortCircuit>& ir, LinkedObjectFile& file) {
(void)file;
Register destination;
std::shared_ptr<IR> ir_dest = nullptr;
for (int i = 0; i < int(ir->entries.size()) - 1; i++) {
auto branch = get_condition_branch(&ir->entries.at(i).condition);
assert(branch.first);
if (!delay_slot_sets_false(branch.first)) {
return false;
}
if (i == 0) {
ir_dest = branch.first->branch_delay.destination;
destination = dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get())->reg;
} else {
if (destination !=
dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get())->reg) {
return false;
}
}
}
ir->kind = IR_ShortCircuit::AND;
ir->final_result = ir_dest;
// now get rid of the branches
for (int i = 0; i < int(ir->entries.size()) - 1; i++) {
auto branch = get_condition_branch(&ir->entries.at(i).condition);
assert(branch.first);
auto replacement = std::make_shared<IR_Compare>(branch.first->condition);
replacement->condition.invert();
*(branch.second) = replacement;
}
return true;
}
/*!
* Try to convert a short circuit to an or.
* Note - this will convert an and to a very strange or, so always use the try as and first.
*/
bool try_clean_up_sc_as_or(std::shared_ptr<IR_ShortCircuit>& ir, LinkedObjectFile& file) {
(void)file;
Register destination;
std::shared_ptr<IR> ir_dest = nullptr;
for (int i = 0; i < int(ir->entries.size()) - 1; i++) {
auto branch = get_condition_branch(&ir->entries.at(i).condition);
assert(branch.first);
if (!delay_slot_sets_truthy(branch.first)) {
return false;
}
assert(dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get()));
if (i == 0) {
ir_dest = branch.first->branch_delay.destination;
destination = dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get())->reg;
} else {
if (destination !=
dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get())->reg) {
return false;
}
}
}
ir->kind = IR_ShortCircuit::OR;
ir->final_result = ir_dest;
for (int i = 0; i < int(ir->entries.size()) - 1; i++) {
auto branch = get_condition_branch(&ir->entries.at(i).condition);
assert(branch.first);
auto replacement = std::make_shared<IR_Compare>(branch.first->condition);
*(branch.second) = replacement;
}
return true;
}
void clean_up_sc(std::shared_ptr<IR_ShortCircuit>& ir, LinkedObjectFile& file);
/*!
* A form like (and x (or y z)) will be recognized as a single SC Vertex by the CFG pass.
* In the case where we fail to clean it up as an AND or an OR, we should attempt splitting.
* Part of the complexity here is that we want to clean up the split recursively so things like
* (and x (or y (and a b)))
* or
* (and x (or y (and a b)) c d (or z))
* will work correctly. This may require doing more splitting on both sections!
*/
bool try_splitting_nested_sc(std::shared_ptr<IR_ShortCircuit>& ir, LinkedObjectFile& file) {
auto first_branch = get_condition_branch(&ir->entries.front().condition);
assert(first_branch.first);
bool first_is_and = delay_slot_sets_false(first_branch.first);
bool first_is_or = delay_slot_sets_truthy(first_branch.first);
assert(first_is_and != first_is_or); // one or the other but not both!
int first_different = -1; // the index of the first one that's different.
for (int i = 1; i < int(ir->entries.size()) - 1; i++) {
auto branch = get_condition_branch(&ir->entries.at(i).condition);
assert(branch.first);
bool is_and = delay_slot_sets_false(branch.first);
bool is_or = delay_slot_sets_truthy(branch.first);
assert(is_and != is_or);
if (first_different == -1) {
// haven't seen a change yet.
if (first_is_and != is_and) {
// change!
first_different = i;
break;
}
}
}
assert(first_different != -1);
std::vector<IR_ShortCircuit::Entry> nested_ir;
for (int i = first_different; i < int(ir->entries.size()); i++) {
nested_ir.push_back(ir->entries.at(i));
}
auto s = int(ir->entries.size());
for (int i = first_different; i < s; i++) {
ir->entries.pop_back();
}
auto nested_sc = std::make_shared<IR_ShortCircuit>(nested_ir);
clean_up_sc(nested_sc, file);
// the real trick
IR_ShortCircuit::Entry nested_entry;
nested_entry.condition = nested_sc;
ir->entries.push_back(nested_entry);
clean_up_sc(ir, file);
return true;
}
/*!
* Try to clean up a single short circuit IR. It may get split up into nested IR_ShortCircuits
* if there is a case like (and a (or b c))
*/
void clean_up_sc(std::shared_ptr<IR_ShortCircuit>& ir, LinkedObjectFile& file) {
(void)file;
assert(ir->entries.size() > 1);
if (!try_clean_up_sc_as_and(ir, file)) {
if (!try_clean_up_sc_as_or(ir, file)) {
if (!try_splitting_nested_sc(ir, file)) {
assert(false);
}
}
}
}
/*!
* A GOAL comparison which produces a boolean is recognized as a cond-no-else by the CFG analysis.
* But it should not be decompiled as a branching statement.
@@ -153,11 +356,6 @@ void convert_cond_no_else_to_compare(std::shared_ptr<IR>* ir) {
auto condition_as_single = dynamic_cast<IR_Branch*>(cne->entries.front().condition.get());
if (condition_as_single) {
// as far as I can tell this is totally valid but just happens to not appear?
// if this case is ever hit in the future it's fine and we just need to implement this.
// but leaving empty for now so there's fewer things to test.
// assert(false);
auto replacement = std::make_shared<IR_Set>(
IR_Set::REG_64, dst, std::make_shared<IR_Compare>(condition.first->condition));
*ir = replacement;
@@ -186,6 +384,7 @@ void convert_cond_no_else_to_compare(std::shared_ptr<IR>* ir) {
* this.
*/
void clean_up_cond_no_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
(void)file;
auto cne = dynamic_cast<IR_Cond*>(ir->get());
assert(cne);
for (size_t idx = 0; idx < cne->entries.size(); idx++) {
@@ -212,6 +411,7 @@ void clean_up_cond_no_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
}
auto replacement = std::make_shared<IR_Compare>(jump_to_next.first->condition);
replacement->condition.invert();
*(jump_to_next.second) = replacement;
e.cleaned = true;
@@ -282,12 +482,19 @@ bool is_int_math_3(IR* ir,
return true;
}
/*!
* Are these IR's both the same register? False if either is not a register.
*/
bool is_same_reg(IR* a, IR* b) {
auto ar = dynamic_cast<IR_Register*>(a);
auto br = dynamic_cast<IR_Register*>(b);
return ar && br && ar->reg == br->reg;
}
/*!
* Try to convert this SC Vertex into an abs (integer).
* Will return a converted abs IR if successful, or nullptr if its not possible
*/
std::shared_ptr<IR> try_sc_as_abs(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) {
if (vtx->entries.size() != 1) {
return nullptr;
@@ -344,7 +551,7 @@ std::shared_ptr<IR> try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCirc
}
// todo, I think b0 could possibly be something more complicated, depending on how we order.
auto b0 = dynamic_cast<BlockVtx*>(vtx->entries.at(0));
auto b0 = dynamic_cast<CfgVtx*>(vtx->entries.at(0));
auto b1 = dynamic_cast<BlockVtx*>(vtx->entries.at(1));
if (!b0 || !b1) {
return nullptr;
@@ -399,8 +606,13 @@ std::shared_ptr<IR> try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCirc
assert(result);
assert(value_in);
if (!is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_ARITH, result->reg,
value_in->reg, clobber)) {
bool is_arith = is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_ARITH, result->reg,
value_in->reg, clobber);
bool is_logical = is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_LOGIC,
result->reg, value_in->reg, clobber);
if (!is_arith && !is_logical) {
return nullptr;
}
@@ -422,7 +634,8 @@ std::shared_ptr<IR> try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCirc
b0_ir->forms.pop_back();
// add the ash
b0_ir->forms.push_back(std::make_shared<IR_Set>(
IR_Set::REG_64, dest_ir, std::make_shared<IR_Ash>(shift_ir, value_ir, clobber_ir)));
IR_Set::REG_64, dest_ir,
std::make_shared<IR_Ash>(shift_ir, value_ir, clobber_ir, is_arith)));
return b0_ptr;
}
@@ -453,7 +666,7 @@ std::shared_ptr<IR> try_sc_as_type_of(Function& f, LinkedObjectFile& file, Short
return nullptr;
}
auto b0 = dynamic_cast<BlockVtx*>(vtx->entries.at(0));
auto b0 = dynamic_cast<CfgVtx*>(vtx->entries.at(0));
auto b1 = dynamic_cast<BlockVtx*>(vtx->entries.at(1));
auto b2 = dynamic_cast<BlockVtx*>(vtx->entries.at(2));
@@ -698,6 +911,7 @@ std::shared_ptr<IR> cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx)
entries.push_back(e);
}
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)) {
@@ -795,4 +1009,4 @@ std::shared_ptr<IR> build_cfg_ir(Function& function,
} catch (std::runtime_error& e) {
return nullptr;
}
}
}
+2 -2
View File
@@ -793,8 +793,8 @@ void IR_ShortCircuit::get_children(std::vector<std::shared_ptr<IR>>* output) con
}
goos::Object IR_Ash::to_form(const LinkedObjectFile& file) const {
return pretty_print::build_list(pretty_print::to_symbol("ash"), value->to_form(file),
shift_amount->to_form(file));
return pretty_print::build_list(pretty_print::to_symbol(is_signed ? "ash.si" : "ash.ui"),
value->to_form(file), shift_amount->to_form(file));
}
void IR_Ash::get_children(std::vector<std::shared_ptr<IR>>* output) const {
+5 -2
View File
@@ -357,12 +357,15 @@ class IR_ShortCircuit : public IR {
class IR_Ash : public IR {
public:
std::shared_ptr<IR> shift_amount, value, clobber;
bool is_signed = true;
IR_Ash(std::shared_ptr<IR> _shift_amount,
std::shared_ptr<IR> _value,
std::shared_ptr<IR> _clobber)
std::shared_ptr<IR> _clobber,
bool _is_signed)
: shift_amount(std::move(_shift_amount)),
value(std::move(_value)),
clobber(std::move(_clobber)) {}
clobber(std::move(_clobber)),
is_signed(_is_signed) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
+15 -15
View File
@@ -592,13 +592,13 @@ void ObjectFileDB::analyze_functions() {
// }
}
int total_nontrivial_functions = 0;
int total_resolved_nontrivial_functions = 0;
int total_trivial_cfg_functions = 0;
int total_named_functions = 0;
int total_basic_ops = 0;
int total_failed_basic_ops = 0;
int attempted_cfg_irs = 0;
int asm_funcs = 0;
int non_asm_funcs = 0;
int successful_cfg_irs = 0;
std::map<int, std::vector<std::string>> unresolved_by_length;
@@ -624,7 +624,7 @@ void ObjectFileDB::analyze_functions() {
total_failed_basic_ops += func.get_failed_basic_op_count();
func.ir = build_cfg_ir(func, *func.cfg, data.linked_data);
attempted_cfg_irs++;
non_asm_funcs++;
if (func.ir) {
successful_cfg_irs++;
}
@@ -633,13 +633,11 @@ void ObjectFileDB::analyze_functions() {
resolved_cfg_functions++;
}
} else {
resolved_cfg_functions++;
asm_funcs++;
}
if (func.basic_blocks.size() > 1 && !func.suspected_asm) {
total_nontrivial_functions++;
if (func.cfg->is_fully_resolved()) {
total_resolved_nontrivial_functions++;
} else {
if (!func.guessed_name.empty()) {
unresolved_by_length[func.end_word - func.start_word].push_back(
@@ -648,6 +646,10 @@ void ObjectFileDB::analyze_functions() {
}
}
if (!func.suspected_asm && func.basic_blocks.size() <= 1) {
total_trivial_cfg_functions++;
}
if (!func.guessed_name.empty()) {
total_named_functions++;
}
@@ -657,21 +659,19 @@ void ObjectFileDB::analyze_functions() {
// }
});
printf("Found %d functions (%d with nontrivial cfgs)\n", total_functions,
total_nontrivial_functions);
printf("Found %d functions (%d with no control flow)\n", total_functions,
total_trivial_cfg_functions);
printf("Named %d/%d functions (%.2f%%)\n", total_named_functions, total_functions,
100.f * float(total_named_functions) / float(total_functions));
printf("Excluding %d asm functions\n", asm_funcs);
printf("Found %d basic blocks in %.3f ms\n", total_basic_blocks, timer.getMs());
printf(" %d/%d functions passed cfg analysis stage (%.2f%%)\n", resolved_cfg_functions,
total_functions, 100.f * float(resolved_cfg_functions) / float(total_functions));
printf(" %d/%d nontrivial cfg's resolved (%.2f%%)\n", total_resolved_nontrivial_functions,
total_nontrivial_functions,
100.f * float(total_resolved_nontrivial_functions) / float(total_nontrivial_functions));
non_asm_funcs, 100.f * float(resolved_cfg_functions) / float(non_asm_funcs));
int successful_basic_ops = total_basic_ops - total_failed_basic_ops;
printf(" %d/%d basic ops converted successfully (%.2f%%)\n", successful_basic_ops,
total_basic_ops, 100.f * float(successful_basic_ops) / float(total_basic_ops));
printf(" %d/%d cfgs converted to ir (%.2f%%)\n", successful_cfg_irs, attempted_cfg_irs,
100.f * float(successful_cfg_irs) / float(attempted_cfg_irs));
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);
+70 -48
View File
@@ -1,72 +1,94 @@
{
"game_version":1,
// the order here matters. KERNEL and GAME should go first
"dgo_names":["CGO/KERNEL.CGO", "CGO/GAME.CGO"],
/*, "CGO/ENGINE.CGO"
, "CGO/ART.CGO", "DGO/BEA.DGO", "DGO/CIT.DGO", "CGO/COMMON.CGO", "DGO/DAR.DGO", "DGO/DEM.DGO",
"DGO/FIN.DGO", "DGO/INT.DGO", "DGO/JUB.DGO", "DGO/JUN.DGO", "CGO/JUNGLE.CGO", "CGO/L1.CGO", "DGO/FIC.DGO",
"DGO/LAV.DGO", "DGO/MAI.DGO", "CGO/MAINCAVE.CGO", "DGO/MIS.DGO", "DGO/OGR.DGO", "CGO/RACERP.CGO", "DGO/ROB.DGO", "DGO/ROL.DGO",
"DGO/SNO.DGO", "DGO/SUB.DGO", "DGO/SUN.DGO", "CGO/SUNKEN.CGO", "DGO/SWA.DGO", "DGO/TIT.DGO", "DGO/TRA.DGO", "DGO/VI1.DGO",
"DGO/VI2.DGO", "DGO/VI3.DGO", "CGO/VILLAGEP.CGO", "CGO/WATER-AN.CGO"
],*/
"game_version" : 1,
// the order here matters. KERNEL and GAME should go first
"dgo_names"
: [ "CGO/KERNEL.CGO", "CGO/GAME.CGO" ],
/*, "CGO/ENGINE.CGO"
, "CGO/ART.CGO", "DGO/BEA.DGO", "DGO/CIT.DGO", "CGO/COMMON.CGO", "DGO/DAR.DGO",
"DGO/DEM.DGO", "DGO/FIN.DGO", "DGO/INT.DGO", "DGO/JUB.DGO", "DGO/JUN.DGO",
"CGO/JUNGLE.CGO", "CGO/L1.CGO", "DGO/FIC.DGO", "DGO/LAV.DGO", "DGO/MAI.DGO",
"CGO/MAINCAVE.CGO", "DGO/MIS.DGO", "DGO/OGR.DGO", "CGO/RACERP.CGO", "DGO/ROB.DGO",
"DGO/ROL.DGO", "DGO/SNO.DGO", "DGO/SUB.DGO", "DGO/SUN.DGO", "CGO/SUNKEN.CGO",
"DGO/SWA.DGO", "DGO/TIT.DGO", "DGO/TRA.DGO", "DGO/VI1.DGO", "DGO/VI2.DGO", "DGO/VI3.DGO",
"CGO/VILLAGEP.CGO", "CGO/WATER-AN.CGO"
],*/
"write_disassembly":true,
"write_hex_near_instructions":false,
// if false, skips disassembling object files without functions, as these are usually large and not interesting yet.
"disassemble_objects_without_functions":false,
"write_disassembly" : true,
"write_hex_near_instructions"
: false,
// if false, skips disassembling object files without functions, as these are usually large
// and not interesting yet.
"disassemble_objects_without_functions" : false,
// to write out data of each object file
"write_hexdump":false,
// to write out hexdump on the v3 only, to avoid the huge level data files
"write_hexdump_on_v3_only":true,
// to write out data of each object file
"write_hexdump"
: false,
// to write out hexdump on the v3 only, to avoid the huge level data files
"write_hexdump_on_v3_only"
: true,
// to write out "scripts", which are currently just all the linked lists found
"write_scripts":true,
// to write out "scripts", which are currently just all the linked lists found
"write_scripts" : true,
// Experimental Stuff
"find_basic_blocks":true,
// Experimental Stuff
"find_basic_blocks" : true,
"asm_functions_by_name":[
"asm_functions_by_name"
: [
// gcommon
"min", "max", "(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",
"kernel-read", "kernel-read-function", "kernel-write", "kernel-write-function", "kernel-copy-to-kernel-ram",
"resend-exception", "kernel-set-interrupt-vector", "kernel-set-exception-vector",
"return-from-exception", "kernel-read", "kernel-read-function", "kernel-write",
"kernel-write-function", "kernel-copy-to-kernel-ram",
// this one needs more investigation. nothing looks weird about it but it fails...
"camera-change-to",
// this one fails due to false compaction where an else case has only a not expression in it.
// two back to back arithmetic shifts...
"texture-relocate",
// this one fails due to false compaction where an else case has only a not expression in
// it.
"master-is-hopeful-better?",
// fails for unknown reason
"target-falling-anim-trans", "change-brother",
// these are all valid, but use short circuiting branches in strange ways. There's probably a few compiler uses that we're not
"(method 21 actor-link-info)","(method 20 actor-link-info)","(method 28 collide-shape-prim-mesh)", "(method 35 collide-shape)",
"debug-menu-item-var-render", "(method 14 level)","add-blue-motion","anim-tester-add-newobj","(method 27 orb-cache-top)",
// merged right typecase... can probably handle this
"cspace-inspect-tree",
// these are all valid, but use short circuiting branches in strange ways. There's probably
// a few compiler uses that we're not
"(method 21 actor-link-info)", "(method 20 actor-link-info)",
"(method 28 collide-shape-prim-mesh)", "(method 35 collide-shape)",
"debug-menu-item-var-render", "(method 14 level)", "add-blue-motion",
"anim-tester-add-newobj", "(method 27 orb-cache-top)",
// real asm
"cspace<-parented-transformq-joint!", "blerc-a-fragment", "render-boundary-tri", "render-boundary-quad",
"(method 19 collide-shape-prim-sphere)","vector-segment-distance-point!", "exp", "(method 11 collide-mesh-cache)",
"cspace<-parented-transformq-joint!", "blerc-a-fragment", "render-boundary-tri",
"render-boundary-quad", "(method 19 collide-shape-prim-sphere)",
"vector-segment-distance-point!", "exp", "(method 11 collide-mesh-cache)",
"(method 13 collide-edge-work)", "ambient-inspect",
"(method 11 cpu-thread)", "atan0", "sincos!", "sincos-rad!", "disasm-dma-list", "vblank-handler", "vif1-handler",
"vif1-handler-debug", "entity-actor-count", "decompress-frame-data-pair-to-accumulator",
"decompress-frame-data-to-accumulator", "normalize-frame-quaternions", "clear-frame-accumulator",
"generic-copy-vtx-dclr-dtex", "generic-no-light-dproc-only", "generic-no-light-proc", "mercneric-bittable-asm",
"generic-tie-decompress", "matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!", "generic-prepare-dma-single",
"(method 13 collide-shape-prim-sphere)", "(method 14 collide-shape-prim-sphere)", "(method 12 collide-shape-prim-sphere)",
"adgif-shader<-texture-with-update!", "generic-interp-dproc", "sprite-draw-distorters", "draw-bones", "(method 9 collide-mesh-cache)",
"(method 18 collide-shape-prim-sphere)","birth-pickup-at-point",
"(method 11 cpu-thread)", "atan0", "sincos!", "sincos-rad!", "disasm-dma-list",
"vblank-handler", "vif1-handler", "vif1-handler-debug", "entity-actor-count",
"decompress-frame-data-pair-to-accumulator", "decompress-frame-data-to-accumulator",
"normalize-frame-quaternions", "clear-frame-accumulator", "generic-copy-vtx-dclr-dtex",
"generic-no-light-dproc-only", "generic-no-light-proc", "mercneric-bittable-asm",
"generic-tie-decompress", "matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!",
"generic-prepare-dma-single", "(method 13 collide-shape-prim-sphere)",
"(method 14 collide-shape-prim-sphere)", "(method 12 collide-shape-prim-sphere)",
"adgif-shader<-texture-with-update!", "generic-interp-dproc", "sprite-draw-distorters",
"draw-bones", "(method 9 collide-mesh-cache)", "(method 18 collide-shape-prim-sphere)",
"birth-pickup-at-point",
"collide-do-primitives", "draw-bones-check-longest-edge-asm",
"sp-launch-particles-var", "(method 15 collide-shape-prim-mesh)", "(method 15 collide-shape-prim-sphere)",
"(method 45 collide-shape)", "cam-layout-save-cam-trans", "kernel-copy-function", "dma-sync-hang", "generic-no-light-dproc",
"dma-sync-fast", "bsp-camera-asm",
"generic-none-dma-wait", "unpack-comp-rle", "level-remap-texture", "(method 10 collide-edge-hold-list)"
]
}
"collide-do-primitives", "draw-bones-check-longest-edge-asm", "sp-launch-particles-var",
"(method 15 collide-shape-prim-mesh)", "(method 15 collide-shape-prim-sphere)",
"(method 45 collide-shape)", "cam-layout-save-cam-trans", "kernel-copy-function",
"dma-sync-hang", "generic-no-light-dproc", "dma-sync-fast", "bsp-camera-asm",
"generic-none-dma-wait", "unpack-comp-rle", "level-remap-texture",
"(method 10 collide-edge-hold-list)"
]
}