[decompiler] ASM Branching Support (#677)

* basic example working in geometry

* before updating offline'

* clean up

* temp

* progress
This commit is contained in:
water111
2021-07-05 16:07:07 -04:00
committed by GitHub
parent 54c63ff42c
commit 551a9c4955
42 changed files with 4161 additions and 272 deletions
+2 -1
View File
@@ -45,7 +45,8 @@ InstructionParser::InstructionParser() {
InstructionKind::MFLO, InstructionKind::MFHI, InstructionKind::MTLO1,
InstructionKind::MFLO1, InstructionKind::SYNCL, InstructionKind::PCPYUD,
InstructionKind::PEXTUW, InstructionKind::POR, InstructionKind::VMOVE,
InstructionKind::VSUB, InstructionKind::LQC2, InstructionKind::SQC2}) {
InstructionKind::VSUB, InstructionKind::LQC2, InstructionKind::SQC2,
InstructionKind::MULAS, InstructionKind::MADDAS}) {
auto& info = gOpcodeInfo[int(i)];
if (info.defined) {
m_opcode_name_lookup[info.name] = int(i);
+459 -13
View File
@@ -121,6 +121,12 @@ std::string CfgVtx::links_to_string() {
result += " prev: " + prev->to_string() + "\n";
}
result += " start: " + std::to_string(get_first_block_id()) + "\n";
if (end_branch.asm_branch) {
result += " ASM BRANCH\n";
}
if (!pred.empty()) {
result += " preds:\n";
for (auto* x : pred) {
@@ -332,10 +338,17 @@ std::string Break::to_string() const {
}
goos::Object Break::to_form() const {
std::vector<goos::Object> forms = {pretty_print::to_symbol("break"),
pretty_print::to_symbol(std::to_string(dest_block_id)),
body->to_form(), unreachable_block->to_form()};
return pretty_print::build_list(forms);
if (unreachable_block) {
std::vector<goos::Object> forms = {pretty_print::to_symbol("break"),
pretty_print::to_symbol(std::to_string(dest_block_id)),
body->to_form(), unreachable_block->to_form()};
return pretty_print::build_list(forms);
} else {
std::vector<goos::Object> forms = {pretty_print::to_symbol("break"),
pretty_print::to_symbol(std::to_string(dest_block_id)),
body->to_form(), pretty_print::to_symbol("no-unreachable")};
return pretty_print::build_list(forms);
}
}
int Break::get_first_block_id() const {
@@ -537,6 +550,11 @@ bool ControlFlowGraph::is_until_loop(CfgVtx* b1, CfgVtx* b2) {
return false;
assert(!b1->end_branch.has_branch);
if (!b2->has_pred(b1)) {
fmt::print("Graph error {} (s {}) should have pred {} (s {})\n", b2->to_string(),
b2->get_first_block_id(), b1->to_string(), b1->get_first_block_id());
}
assert(b2->has_pred(b1));
if (b2->pred.size() != 1)
return false;
@@ -730,7 +748,7 @@ bool ControlFlowGraph::find_infinite_loop() {
bool found = false;
for_each_top_level_vtx([&](CfgVtx* vtx) {
if (vtx->succ_branch == vtx && !vtx->succ_ft) {
if (vtx->succ_branch == vtx && !vtx->succ_ft && !vtx->end_branch.asm_branch) {
auto inf = alloc<InfiniteLoopBlock>();
inf->block = vtx;
inf->pred = vtx->pred;
@@ -806,7 +824,7 @@ bool ControlFlowGraph::find_goto_end() {
for_each_top_level_vtx([&](CfgVtx* vtx) {
auto* b0 = vtx;
auto* b1 = vtx->next;
if (is_goto_end_and_unreachable(b0, b1)) {
if (is_goto_end_and_unreachable(b0, b1) && !b0->end_branch.asm_branch) {
replaced = true;
auto* new_goto = alloc<GotoEnd>();
@@ -959,7 +977,9 @@ bool ControlFlowGraph::find_goto_not_end() {
for_each_top_level_vtx([&](CfgVtx* vtx) {
auto* b0 = vtx;
auto* b1 = vtx->next;
if (is_goto_not_end_and_unreachable(b0, b1)) {
// this can't work on asm branches because the structuring will fail.
if (is_goto_not_end_and_unreachable(b0, b1) && b0 && !b0->end_branch.asm_branch) {
replaced = true;
auto* new_goto = alloc<Break>();
@@ -1051,6 +1071,339 @@ bool ControlFlowGraph::is_sequence(CfgVtx* b0, CfgVtx* b1, bool allow_self_loops
return true;
}
bool debug_asm_branch = false;
/*!
* This is a weird and special pass that takes something like:
* B0
* asm branch
* B1
*
* and merges B0B1 into a single block with a
*/
bool ControlFlowGraph::clean_up_asm_branches() {
bool replaced = false;
for_each_top_level_vtx([&](CfgVtx* vtx) {
auto* b0 = vtx;
auto* b1 = vtx->next;
if (!b1) {
return true;
}
if (!b0->end_branch.asm_branch) {
return true;
}
// don't want to combine two with an incoming edge in between.
if (b1->pred.size() > 1) {
return true;
} else {
if (b1->pred.size() == 1 && !b1->has_pred(b1->prev)) {
return true;
}
}
if (b0->end_branch.branch_likely) {
auto* bds = b1;
b1 = bds->next;
if (!b1) {
return true;
}
if (debug_asm_branch) {
fmt::print("Looks like asm likely branch: {} {} to {}\n", b0->to_string(), bds->to_string(),
b1->to_string());
}
auto* b0_seq = dynamic_cast<SequenceVtx*>(b0);
auto* b1_seq = dynamic_cast<SequenceVtx*>(b1);
if (!b0_seq && !b1_seq) {
// build new sequence
replaced = true;
m_blocks.at(bds->succ_branch->get_first_block_id())->needs_label = true;
auto* new_seq = alloc<SequenceVtx>();
new_seq->seq.push_back(b0);
new_seq->seq.push_back(bds);
new_seq->seq.push_back(b1);
for (auto* new_pred : b0->pred) {
if (debug_asm_branch) {
fmt::print(" pred {}\n", new_pred->to_string());
}
new_pred->replace_succ_and_check(b0, new_seq);
}
new_seq->pred = b0->pred;
if (b0->succ_branch) {
b0->succ_branch->replace_preds_with_and_check({b0}, nullptr);
}
if (bds->succ_branch) {
// likely delay slots "branch" in this graph.
bds->succ_branch->replace_preds_with_and_check({bds}, nullptr);
}
for (auto* new_succ : b1->succs()) {
new_succ->replace_pred_and_check(b1, new_seq);
}
new_seq->succ_ft = b1->succ_ft;
new_seq->succ_branch = b1->succ_branch;
new_seq->prev = b0->prev;
if (new_seq->prev) {
new_seq->prev->next = new_seq;
}
new_seq->next = b1->next;
if (new_seq->next) {
new_seq->next->prev = new_seq;
}
b0->parent_claim(new_seq);
bds->parent_claim(new_seq);
b1->parent_claim(new_seq);
new_seq->end_branch = b1->end_branch;
return false;
} else if (b0_seq && b1_seq) {
replaced = true;
m_blocks.at(bds->succ_branch->get_first_block_id())->needs_label = true;
auto* seq = dynamic_cast<SequenceVtx*>(b0);
assert(seq);
auto* old_seq = dynamic_cast<SequenceVtx*>(b1);
assert(old_seq);
if (b0->succ_branch) {
b0->succ_branch->replace_preds_with_and_check({b0}, nullptr);
}
if (bds->succ_branch) {
// likely delay slots "branch" in this graph.
bds->succ_branch->replace_preds_with_and_check({bds}, nullptr);
}
seq->seq.push_back(bds);
for (auto* x : old_seq->seq) {
x->parent_claim(seq);
seq->seq.push_back(x);
}
for (auto* x : old_seq->succs()) {
// printf("fix preds of %s\n", x->to_string().c_str());
x->replace_pred_and_check(old_seq, seq);
}
seq->succ_branch = old_seq->succ_branch;
seq->succ_ft = old_seq->succ_ft;
seq->end_branch = old_seq->end_branch;
seq->next = old_seq->next;
if (seq->next) {
seq->next->prev = seq;
}
// todo - proper trash?
old_seq->parent_claim(seq);
bds->parent_claim(seq);
return false;
}
else {
lg::error("unhandled sequences in clean_up_asm_branches likely seq: {} {}", !!b0_seq,
!!b1_seq);
}
} else {
if (debug_asm_branch) {
fmt::print("Looks like asm normal branch: {} to {}\n", b0->to_string(), b1->to_string());
}
auto* b0_seq = dynamic_cast<SequenceVtx*>(b0);
auto* b1_seq = dynamic_cast<SequenceVtx*>(b1);
if (!b0_seq && !b1_seq) {
if (debug_asm_branch) {
fmt::print("[combo nn] {} and {}\n", b0->get_first_block_id(), b1->get_first_block_id());
}
// build new sequence
replaced = true;
m_blocks.at(b0->succ_branch->get_first_block_id())->needs_label = true;
auto* new_seq = alloc<SequenceVtx>();
new_seq->seq.push_back(b0);
new_seq->seq.push_back(b1);
for (auto* new_pred : b0->pred) {
new_pred->replace_succ_and_check(b0, new_seq);
}
new_seq->pred = b0->pred;
if (b0->succ_branch) {
b0->succ_branch->replace_preds_with_and_check({b0}, nullptr);
}
for (auto* new_succ : b1->succs()) {
if (debug_asm_branch) {
fmt::print("changing {}'s pred {} to seq. bc: {}", new_succ->to_string(),
b1->to_string(), new_succ->pred.size());
}
new_succ->replace_pred_and_check(b1, new_seq);
if (debug_asm_branch) {
fmt::print(" ac: {}\n", new_succ->pred.size());
}
}
new_seq->succ_ft = b1->succ_ft;
if (b1->succ_branch && debug_asm_branch) {
fmt::print("combining {} and {} into a sequence, succ {}\n", b0->get_first_block_id(),
b1->get_first_block_id(), b1->succ_branch->get_first_block_id());
}
new_seq->succ_branch = b1->succ_branch;
new_seq->prev = b0->prev;
if (new_seq->prev) {
new_seq->prev->next = new_seq;
}
new_seq->next = b1->next;
if (new_seq->next) {
new_seq->next->prev = new_seq;
}
b0->parent_claim(new_seq);
b1->parent_claim(new_seq);
if (new_seq->succ_branch) {
assert(!new_seq->succ_branch->parent);
}
new_seq->end_branch = b1->end_branch;
return false;
} else if (b0_seq && !b1_seq) {
if (debug_asm_branch) {
fmt::print("[combo sn] {} and {}\n", b0->get_first_block_id(), b1->get_first_block_id());
fmt::print("expanding sequence: {} (s {}) to include {}\n", b0_seq->to_string(),
b0_seq->get_first_block_id(), b1->get_first_block_id());
}
if (b1->succ_ft) {
if (debug_asm_branch) {
fmt::print(" b1 succ_ft is {}\n", b1->succ_ft->to_string());
}
assert(b1->succ_ft->has_pred(b1));
}
replaced = true;
m_blocks.at(b0->succ_branch->get_first_block_id())->needs_label = true;
auto* seq = dynamic_cast<SequenceVtx*>(b0);
assert(seq);
seq->seq.push_back(b1);
if (b0->succ_branch) {
if (debug_asm_branch) {
fmt::print("succ {} has {} preds parent: {}\n", b0->succ_branch->get_first_block_id(),
b0->succ_branch->pred.size(), !!b0->succ_branch->parent);
}
b0->succ_branch->replace_preds_with_and_check({b0}, nullptr);
if (debug_asm_branch) {
fmt::print("OKOK\n");
}
}
for (auto* new_succ : b1->succs()) {
if (debug_asm_branch) {
fmt::print("fixing up succ {}\n", new_succ->to_string());
}
new_succ->replace_pred_and_check(b1, b0);
}
if (b1->succ_ft) {
assert(b1->succ_ft->has_pred(b0));
}
if (b1->succ_ft) {
assert(b1->succ_ft->has_pred(b0));
}
// try
seq->succ_ft = b1->succ_ft;
seq->succ_branch = b1->succ_branch;
if (b1->succ_branch) {
assert(!b1->succ_branch->parent);
}
if (seq->succ_branch && debug_asm_branch) {
fmt::print(" new sb: {}\n", seq->succ_branch->get_first_block_id());
}
seq->next = b1->next;
if (seq->next) {
seq->next->prev = seq;
}
b1->parent_claim(seq);
if (seq->succ_branch) {
assert(!seq->succ_branch->parent);
}
seq->end_branch = b1->end_branch;
return false;
} else if (b0_seq && b1_seq) {
if (debug_asm_branch) {
fmt::print("[combo ss] {} and {}\n", b0->get_first_block_id(), b1->get_first_block_id());
fmt::print(" {} and {}\n", b0->to_string(), b1->to_string());
}
// printf("make seq type 3 %s %s\n", b0->to_string().c_str(), b1->to_string().c_str());
replaced = true;
m_blocks.at(b0->succ_branch->get_first_block_id())->needs_label = true;
auto* seq = dynamic_cast<SequenceVtx*>(b0);
assert(seq);
auto* old_seq = dynamic_cast<SequenceVtx*>(b1);
assert(old_seq);
if (b0->succ_branch) {
if (debug_asm_branch) {
fmt::print(" sbp: {}\n", !!b0->succ_branch->parent);
fmt::print(" sb: {}\n", b0->succ_branch->to_string());
}
b0->succ_branch->replace_preds_with_and_check({b0}, nullptr);
}
for (auto* x : old_seq->seq) {
x->parent_claim(seq);
seq->seq.push_back(x);
}
for (auto* x : old_seq->succs()) {
// printf("fix preds of %s\n", x->to_string().c_str());
x->replace_pred_and_check(old_seq, seq);
}
seq->succ_branch = old_seq->succ_branch;
seq->succ_branch = b1->succ_branch;
if (seq->succ_branch && debug_asm_branch) {
fmt::print(" DS new sb: {}\n", seq->succ_branch->get_first_block_id());
}
seq->succ_ft = old_seq->succ_ft;
seq->end_branch = old_seq->end_branch;
seq->next = old_seq->next;
if (seq->next) {
seq->next->prev = seq;
}
// todo - proper trash?
old_seq->parent_claim(seq);
return false;
} else {
lg::error("unhandled sequences in clean_up_asm_branches seq: {} {}", !!b0_seq, !!b1_seq);
}
}
return true; // keep looking
});
return replaced;
}
bool ControlFlowGraph::is_sequence_of_non_sequences(CfgVtx* b0, CfgVtx* b1, bool allow_self_loops) {
if (!b0 || !b1)
return false;
@@ -1826,6 +2179,10 @@ bool ControlFlowGraph::find_short_circuits() {
return true;
}
if (vtx->end_branch.asm_branch) {
return true;
}
// set up the first entry:
ShortCircuit::Entry candidate = {vtx, vtx->next};
CfgVtx* end = vtx->next->succ_branch;
@@ -2085,13 +2442,49 @@ CfgVtx::DelaySlotKind get_delay_slot(const Instruction& i) {
}
}
namespace {
/*!
* Is this instruction possible in the delay slot, without using inline assembly?
*/
bool branch_delay_asm(const Instruction& i) {
if (is_nop(i)) {
// nop can be used as a delay
return false;
} else if (is_gpr_3(i, InstructionKind::OR, {}, Register(Reg::GPR, Reg::S7),
Register(Reg::GPR, Reg::R0))) {
// set false is used in ifs, etc
return false;
} else if (is_gpr_2_imm_int(i, InstructionKind::DADDIU, {}, Register(Reg::GPR, Reg::S7), 8)) {
// set true is used in sc
return false;
} else if (is_gpr_3(i, InstructionKind::OR, {}, {}, Register(Reg::GPR, Reg::R0))) {
// set var to var
return false;
} else if (is_gpr_3(i, InstructionKind::DSLLV, {}, {}, {})) {
// shift trick
return false;
} else if (is_gpr_3(i, InstructionKind::DSUBU, {}, Register(Reg::GPR, Reg::R0), {})) {
// abs trick
return false;
} else if (i.kind == InstructionKind::LW &&
(i.get_src(0).is_sym("binteger") || i.get_src(0).is_sym("pair"))) {
// rtype trick
return false;
} else {
return true;
}
}
} // namespace
/*!
* Build and resolve a Control Flow Graph as much as possible.
*/
std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file,
int seg,
Function& func,
const CondWithElseLengthHack& cond_with_else_hack) {
std::shared_ptr<ControlFlowGraph> build_cfg(
const LinkedObjectFile& file,
int seg,
Function& func,
const CondWithElseLengthHack& cond_with_else_hack,
const std::unordered_set<int>& blocks_ending_in_asm_br) {
// fmt::print("START {}\n", func.guessed_name.to_string());
auto cfg = std::make_shared<ControlFlowGraph>();
@@ -2105,8 +2498,6 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file,
cfg->exit()->pred.push_back(blocks.back());
blocks.back()->succ_ft = cfg->exit();
// todo - early returns!
// set up succ / pred
for (int i = 0; i < int(func.basic_blocks.size()); i++) {
auto& b = func.basic_blocks[i];
@@ -2234,6 +2625,57 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file,
}
}
for (int i = 0; i < int(func.basic_blocks.size()); i++) {
auto& bb = func.basic_blocks[i];
auto& b = blocks.at(i);
if (bb.end_word == bb.start_word) {
continue; // zero sized block, there is no branch here.
}
if (blocks_ending_in_asm_br.find(i) != blocks_ending_in_asm_br.end()) {
b->end_branch.asm_branch = true;
if (debug_asm_branch) {
fmt::print("OVERRIDE asm branch at block {}\n", i);
}
continue;
}
// room for at least a likely branch, try that first.
int likely_branch_idx = bb.end_word - 1;
assert(likely_branch_idx >= bb.start_word);
auto& likely_branch_candidate = func.instructions.at(likely_branch_idx);
if (is_branch(likely_branch_candidate, true)) {
// likely branch!
auto following = func.instructions.at(likely_branch_idx + 1);
if (branch_delay_asm(following)) {
b->end_branch.asm_branch = true;
if (debug_asm_branch) {
fmt::print("LIKELY ASM BRANCH: {} and {}\n",
likely_branch_candidate.to_string(file.labels),
following.to_string(file.labels));
}
}
}
if (bb.end_word - bb.start_word >= 2) {
int idx = bb.end_word - 2;
assert(idx >= bb.start_word);
auto& branch_candidate = func.instructions.at(idx);
auto& delay_slot_candidate = func.instructions.at(idx + 1);
if (is_branch(branch_candidate, false)) {
if (branch_delay_asm(delay_slot_candidate)) {
b->end_branch.asm_branch = true;
if (debug_asm_branch) {
fmt::print("NORMAL ASM BRANCH: {} and {}\n", branch_candidate.to_string(file.labels),
delay_slot_candidate.to_string(file.labels));
}
}
}
}
}
cfg->flag_early_exit(func.basic_blocks);
bool changed = true;
@@ -2272,6 +2714,10 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file,
changed = changed || cfg->find_cond_w_empty_else();
}
if (!changed) {
changed = changed || cfg->clean_up_asm_branches();
}
if (!changed) {
changed = changed || cfg->find_infinite_continue();
if (changed && !complained_about_weird_gotos) {
+4 -1
View File
@@ -86,6 +86,7 @@ class CfgVtx {
bool has_branch = false; // does the block end in a branch (any kind)?
bool branch_likely = false; // does the block end in a likely branch?
bool branch_always = false; // does the branch always get taken?
bool asm_branch = false; // is this an inline assembly branch?
DelaySlotKind kind = DelaySlotKind::NO_BRANCH;
} end_branch;
@@ -331,6 +332,7 @@ class ControlFlowGraph {
bool find_goto_end();
bool find_infinite_loop();
bool find_goto_not_end();
bool clean_up_asm_branches();
/*!
* Apply a function f to each top-level vertex.
@@ -387,6 +389,7 @@ class Function;
std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file,
int seg,
Function& func,
const CondWithElseLengthHack& cond_with_else_hack);
const CondWithElseLengthHack& cond_with_else_hack,
const std::unordered_set<int>& blocks_ending_in_asm_br);
} // namespace decompiler
#endif // JAK_DISASSEMBLER_CFGVTX_H
+45 -19
View File
@@ -300,6 +300,8 @@ std::string get_simple_expression_op_name(SimpleExpression::Kind kind) {
return "vector-float*!2";
case SimpleExpression::Kind::SUBU_L32_S7:
return "subu-s7";
case SimpleExpression::Kind::VECTOR_3_DOT:
return "vec3dot";
default:
assert(false);
return {};
@@ -357,6 +359,8 @@ int get_simple_expression_arg_count(SimpleExpression::Kind kind) {
return 3;
case SimpleExpression::Kind::SUBU_L32_S7:
return 1;
case SimpleExpression::Kind::VECTOR_3_DOT:
return 2;
default:
assert(false);
return -1;
@@ -1370,6 +1374,17 @@ void BranchOp::collect_vars(RegAccessSet& vars) const {
// AsmBranchOp
/////////////////////////////
AsmBranchOp::AsmBranchOp(bool likely,
IR2_Condition condition,
int label,
AtomicOp* branch_delay,
int my_idx)
: AtomicOp(my_idx),
m_likely(likely),
m_condition(std::move(condition)),
m_label(label),
m_branch_delay(branch_delay) {}
AsmBranchOp::AsmBranchOp(bool likely,
IR2_Condition condition,
int label,
@@ -1379,7 +1394,9 @@ AsmBranchOp::AsmBranchOp(bool likely,
m_likely(likely),
m_condition(std::move(condition)),
m_label(label),
m_branch_delay(std::move(branch_delay)) {}
m_branch_delay_sp(branch_delay) {
m_branch_delay = m_branch_delay_sp.get();
}
goos::Object AsmBranchOp::to_form(const std::vector<DecompilerLabel>& labels,
const Env& env) const {
@@ -1393,7 +1410,10 @@ goos::Object AsmBranchOp::to_form(const std::vector<DecompilerLabel>& labels,
forms.push_back(m_condition.to_form(labels, env));
forms.push_back(pretty_print::to_symbol(labels.at(m_label).name));
forms.push_back(m_branch_delay->to_form(labels, env));
if (m_branch_delay) {
forms.push_back(m_branch_delay->to_form(labels, env));
}
return pretty_print::build_list(forms);
}
@@ -1419,23 +1439,28 @@ RegisterAccess AsmBranchOp::get_set_destination() const {
void AsmBranchOp::update_register_info() {
m_condition.get_regs(&m_read_regs);
m_branch_delay->update_register_info();
for (auto x : m_branch_delay->read_regs()) {
m_read_regs.push_back(x);
}
if (m_branch_delay) {
m_branch_delay->update_register_info();
for (auto x : m_branch_delay->write_regs()) {
m_write_regs.push_back(x);
}
for (auto x : m_branch_delay->read_regs()) {
m_read_regs.push_back(x);
}
for (auto x : m_branch_delay->clobber_regs()) {
m_clobber_regs.push_back(x);
for (auto x : m_branch_delay->write_regs()) {
m_write_regs.push_back(x);
}
for (auto x : m_branch_delay->clobber_regs()) {
m_clobber_regs.push_back(x);
}
}
}
void AsmBranchOp::collect_vars(RegAccessSet& vars) const {
m_condition.collect_vars(vars);
m_branch_delay->collect_vars(vars);
if (m_branch_delay) {
m_branch_delay->collect_vars(vars);
}
}
/////////////////////////////
@@ -1489,9 +1514,9 @@ void SpecialOp::update_register_info() {
return;
case Kind::SUSPEND:
// todo - confirm this is true.
// the suspend operation is written in a way where it doesn't use temporaries to make the call
// but the actual suspend operation doesn't seem to preserve temporaries. Maybe the plan was
// to save temp registers at some point, but they later gave up on this?
// the suspend operation is written in a way where it doesn't use temporaries to make the
// call but the actual suspend operation doesn't seem to preserve temporaries. Maybe the
// plan was to save temp registers at some point, but they later gave up on this?
clobber_temps();
return;
default:
@@ -1540,11 +1565,12 @@ RegisterAccess CallOp::get_set_destination() const {
}
void CallOp::update_register_info() {
// throw std::runtime_error("CallOp::update_register_info cannot be done until types are known");
// throw std::runtime_error("CallOp::update_register_info cannot be done until types are
// known");
m_read_regs.push_back(Register(Reg::GPR, Reg::T9));
// previously, if the type analysis succeeds, it would remove this if the function doesn't return
// a value. however, this turned out to be not quite right because GOAL internally thinks that all
// functions return a value.
// previously, if the type analysis succeeds, it would remove this if the function doesn't
// return a value. however, this turned out to be not quite right because GOAL internally thinks
// that all functions return a value.
m_write_regs.push_back(Register(Reg::GPR, Reg::V0));
clobber_temps();
}
+11 -1
View File
@@ -227,6 +227,7 @@ class SimpleExpression {
VECTOR_MINUS,
VECTOR_FLOAT_PRODUCT,
SUBU_L32_S7, // use SUBU X, src0, s7 to check if lower 32-bits are s7.
VECTOR_3_DOT,
};
// how many arguments?
@@ -559,6 +560,7 @@ class BranchOp : public AtomicOp {
const IR2_Condition& condition() const { return m_condition; }
ConditionElement* get_condition_as_form(FormPool& pool, const Env& env) const;
bool likely() const { return m_likely; }
int label_id() const { return m_label; }
private:
bool m_likely = false;
@@ -572,11 +574,14 @@ class BranchOp : public AtomicOp {
*/
class AsmBranchOp : public AtomicOp {
public:
AsmBranchOp(bool likely, IR2_Condition condition, int label, AtomicOp* branch_delay, int my_idx);
AsmBranchOp(bool likely,
IR2_Condition condition,
int label,
std::shared_ptr<AtomicOp> branch_delay,
int my_idx);
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env& env) const override;
bool operator==(const AtomicOp& other) const override;
bool is_sequence_point() const override;
@@ -587,12 +592,17 @@ class AsmBranchOp : public AtomicOp {
const Env& env,
DecompilerTypeSystem& dts) override;
void collect_vars(RegAccessSet& vars) const override;
bool is_likely() const { return m_likely; }
const IR2_Condition& condition() const { return m_condition; }
int label_id() const { return m_label; }
AtomicOp* branch_delay() { return m_branch_delay; }
private:
bool m_likely = false;
IR2_Condition m_condition;
int m_label = -1;
std::shared_ptr<AtomicOp> m_branch_delay;
AtomicOp* m_branch_delay;
std::shared_ptr<AtomicOp> m_branch_delay_sp;
};
/*!
+11 -4
View File
@@ -728,7 +728,7 @@ FormElement* BranchOp::get_as_form(FormPool& pool, const Env&) const {
}
FormElement* SpecialOp::get_as_form(FormPool& pool, const Env&) const {
return pool.alloc_element<AtomicOpElement>(this);
return pool.alloc_element<AtomicOpElement>(const_cast<SpecialOp*>(this));
}
FormElement* CallOp::get_as_form(FormPool& pool, const Env& env) const {
@@ -764,11 +764,18 @@ FormElement* ConditionalMoveFalseOp::get_as_form(FormPool& pool, const Env&) con
}
FormElement* FunctionEndOp::get_as_form(FormPool& pool, const Env&) const {
return pool.alloc_element<AtomicOpElement>(this);
return pool.alloc_element<AtomicOpElement>(const_cast<FunctionEndOp*>(this));
}
FormElement* AsmBranchOp::get_as_form(FormPool& pool, const Env&) const {
return pool.alloc_element<AtomicOpElement>(this);
FormElement* AsmBranchOp::get_as_form(FormPool& pool, const Env& env) const {
if (m_branch_delay) {
auto delay = m_branch_delay->get_as_form(pool, env);
auto delay_form = pool.alloc_single_form(nullptr, delay);
return pool.alloc_element<AsmBranchElement>(const_cast<AsmBranchOp*>(this), delay_form,
m_likely);
} else {
return pool.alloc_element<AtomicOpElement>(const_cast<AsmBranchOp*>(this));
}
}
FormElement* StackSpillLoadOp::get_as_form(FormPool& pool, const Env& env) const {
+43 -4
View File
@@ -202,6 +202,8 @@ TP_Type SimpleExpression::get_type(const TypeState& input,
return TP_Type::make_from_ts("vector");
case Kind::SUBU_L32_S7:
return TP_Type::make_from_ts("int");
case Kind::VECTOR_3_DOT:
return TP_Type::make_from_ts("float");
default:
throw std::runtime_error("Simple expression cannot get_type: " +
to_form(env.file->labels, env).print());
@@ -491,10 +493,31 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
rd_in.stride = arg1_type.get_multiplier();
rd_in.offset = 0;
rd_in.base_type = arg0_type.typespec();
auto rd = dts.ts.reverse_field_lookup(rd_in);
auto rd = dts.ts.reverse_field_multi_lookup(rd_in);
if (rd.success) {
return TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type));
for (int i = 0; i < (int)rd.results.size(); i++) {
if (rd.results.at(i).has_variable_token()) {
return TP_Type::make_from_ts(coerce_to_reg_type(rd.results.at(i).result_type));
break;
}
}
}
if (m_kind == Kind::ADD && arg1_type.kind == TP_Type::Kind::TYPESPEC &&
arg1_type.typespec().base_type() == "inline-array" &&
arg0_type.kind == TP_Type::Kind::PRODUCT_WITH_CONSTANT) {
FieldReverseLookupInput rd_in;
rd_in.deref = std::nullopt;
rd_in.stride = arg0_type.get_multiplier();
rd_in.offset = 0;
rd_in.base_type = arg1_type.typespec();
auto rd = dts.ts.reverse_field_multi_lookup(rd_in);
for (int i = 0; i < (int)rd.results.size(); i++) {
if (rd.results.at(i).has_variable_token()) {
return TP_Type::make_from_ts(coerce_to_reg_type(rd.results.at(i).result_type));
break;
}
}
}
@@ -558,6 +581,20 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
return TP_Type::make_from_ts(TypeSpec("int"));
}
if (m_kind == Kind::RIGHT_SHIFT_LOGIC && arg0_type.typespec() == TypeSpec("float") &&
arg1_type.is_integer_constant(63)) {
//
return TP_Type::make_from_ts(TypeSpec("int"));
}
if (m_kind == Kind::OR && arg0_type.typespec() == TypeSpec("float") &&
arg1_type.typespec() == TypeSpec("float")) {
env.func->warnings.general_warning("Using logior on floats");
// returning int instead of uint because they like to use the float sign bit as an integer sign
// bit.
return TP_Type::make_from_ts(TypeSpec("float"));
}
throw std::runtime_error(fmt::format("Cannot get_type_int2: {}, args {} and {}",
to_form(env.file->labels, env).print(), arg0_type.print(),
arg1_type.print()));
@@ -1149,7 +1186,9 @@ TypeState AsmBranchOp::propagate_types_internal(const TypeState& input,
// for now, just make everything uint
TypeState output = input;
for (auto x : m_write_regs) {
output.get(x) = TP_Type::make_from_ts("uint");
if (x.allowed_local_gpr()) {
output.get(x) = TP_Type::make_from_ts("uint");
}
}
return output;
}
+136 -2
View File
@@ -33,7 +33,8 @@ std::string FormElement::to_string(const Env& env) const {
}
void FormElement::push_to_stack(const Env& env, FormPool&, FormStack&) {
throw std::runtime_error("push_to_stack not implemented for " + to_string(env));
throw std::runtime_error(fmt::format("push_to_stack not implemented for {}: {}", to_string(env),
typeid(*this).name()));
}
goos::Object FormElement::to_form_as_condition_internal(const Env& env) const {
@@ -494,7 +495,7 @@ void SetFormFormElement::get_modified_regs(RegSet& regs) const {
// AtomicOpElement
/////////////////////////////
AtomicOpElement::AtomicOpElement(const AtomicOp* op) : m_op(op) {}
AtomicOpElement::AtomicOpElement(AtomicOp* op) : m_op(op) {}
goos::Object AtomicOpElement::to_form_internal(const Env& env) const {
return m_op->to_form(env.file->labels, env);
@@ -520,6 +521,137 @@ void AtomicOpElement::get_modified_regs(RegSet& regs) const {
}
}
/////////////////////////////
// AsmBranchElement
/////////////////////////////
AsmBranchElement::AsmBranchElement(AsmBranchOp* branch_op, Form* branch_delay, bool likely)
: m_branch_op(branch_op), m_branch_delay(branch_delay), m_likely(likely) {
m_branch_delay->parent_element = this;
}
goos::Object AsmBranchElement::to_form_internal(const Env& env) const {
auto f = m_branch_op->to_form(env.file->labels, env);
return pretty_print::build_list(f, m_branch_delay->to_form(env)); // temp hack
}
void AsmBranchElement::apply(const std::function<void(FormElement*)>& f) {
f(this);
m_branch_delay->apply(f);
}
void AsmBranchElement::apply_form(const std::function<void(Form*)>& f) {
m_branch_delay->apply_form(f);
}
void AsmBranchElement::collect_vars(RegAccessSet& vars, bool recursive) const {
if (recursive) {
m_branch_delay->collect_vars(vars, recursive);
}
m_branch_op->collect_vars(vars);
}
void AsmBranchElement::get_modified_regs(RegSet& regs) const {
m_branch_delay->get_modified_regs(regs);
for (auto r : m_branch_op->write_regs()) {
regs.insert(r);
}
for (auto r : m_branch_op->clobber_regs()) {
regs.insert(r);
}
}
/////////////////////////////
// TranslatedAsmBranch
/////////////////////////////
TranslatedAsmBranch::TranslatedAsmBranch(Form* branch_condition,
Form* branch_delay,
int label_id,
bool likely)
: m_branch_condition(branch_condition),
m_branch_delay(branch_delay),
m_label_id(label_id),
m_likely(likely) {
if (m_branch_delay) {
m_branch_delay->parent_element = this;
}
m_branch_condition->parent_element = this;
}
goos::Object TranslatedAsmBranch::to_form_internal(const Env& env) const {
// auto& cfg = env.func->cfg;
auto& label = env.file->labels.at(m_label_id);
int instr_in_function = (label.offset / 4 - env.func->start_word);
int block_id = -20;
if (instr_in_function == env.func->basic_blocks.back().end_word) {
block_id = env.func->basic_blocks.size() - 1;
} else {
int atomic_op_in_function =
env.func->ir2.atomic_ops->instruction_to_atomic_op.at(instr_in_function);
auto& ao = env.func->ir2.atomic_ops;
for (int i = 0; i < (int)ao->block_id_to_first_atomic_op.size(); i++) {
if (ao->block_id_to_first_atomic_op.at(i) == atomic_op_in_function) {
block_id = i;
break;
}
}
}
assert(block_id >= 0);
if (m_branch_delay) {
std::vector<goos::Object> list = {
pretty_print::to_symbol("b!"), m_branch_condition->to_form(env),
pretty_print::to_symbol(fmt::format("cfg-{}", block_id)),
pretty_print::to_symbol(m_likely ? ":likely-delay" : ":delay"),
m_branch_delay->to_form(env)};
return pretty_print::build_list(list);
} else {
std::vector<goos::Object> list = {pretty_print::to_symbol("b!"),
m_branch_condition->to_form(env),
pretty_print::to_symbol(fmt::format("cfg-{}", block_id))};
return pretty_print::build_list(list);
}
}
void TranslatedAsmBranch::apply(const std::function<void(FormElement*)>& f) {
f(this);
m_branch_condition->apply(f);
if (m_branch_delay) {
m_branch_delay->apply(f);
}
}
void TranslatedAsmBranch::apply_form(const std::function<void(Form*)>& f) {
m_branch_condition->apply_form(f);
if (m_branch_delay) {
m_branch_delay->apply_form(f);
}
}
void TranslatedAsmBranch::collect_vars(RegAccessSet& vars, bool recursive) const {
if (recursive) {
m_branch_condition->collect_vars(vars, recursive);
if (m_branch_delay) {
m_branch_delay->collect_vars(vars, recursive);
}
}
}
void TranslatedAsmBranch::get_modified_regs(RegSet& regs) const {
m_branch_condition->get_modified_regs(regs);
if (m_branch_delay) {
m_branch_delay->get_modified_regs(regs);
}
}
/////////////////////////////
// AsmOpElement
/////////////////////////////
@@ -1550,6 +1682,8 @@ std::string fixed_operator_to_string(FixedOperatorKind kind) {
return "vector-float*!";
case FixedOperatorKind::L32_NOT_FALSE_CBOOL:
return "l32-false-check";
case FixedOperatorKind::VECTOR_3_DOT:
return "vector-dot";
default:
assert(false);
return "";
+42 -2
View File
@@ -207,6 +207,11 @@ class SimpleExpressionElement : public FormElement {
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects);
void update_from_stack_vector_3_dot(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects);
const SimpleExpression& expr() const { return m_expr; }
@@ -408,7 +413,7 @@ class SetFormFormElement : public FormElement {
*/
class AtomicOpElement : public FormElement {
public:
explicit AtomicOpElement(const AtomicOp* op);
explicit AtomicOpElement(AtomicOp* op);
goos::Object to_form_internal(const Env& env) const override;
void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override;
@@ -416,9 +421,43 @@ class AtomicOpElement : public FormElement {
void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override;
void get_modified_regs(RegSet& regs) const override;
const AtomicOp* op() const { return m_op; }
AtomicOp* op() { return m_op; }
private:
const AtomicOp* m_op;
AtomicOp* m_op = nullptr; // not const because of asm likely merging
};
class AsmBranchElement : public FormElement {
public:
AsmBranchElement(AsmBranchOp* branch_op, Form* branch_delay, bool likely);
goos::Object to_form_internal(const Env& env) const override;
void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(RegAccessSet& vars, bool recursive) const override;
void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override;
void get_modified_regs(RegSet& regs) const override;
private:
AsmBranchOp* m_branch_op = nullptr;
Form* m_branch_delay = nullptr;
bool m_likely = false;
};
class TranslatedAsmBranch : public FormElement {
public:
TranslatedAsmBranch(Form* branch_condition, Form* branch_delay, int label_id, bool likely);
goos::Object to_form_internal(const Env& env) const override;
void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(RegAccessSet& vars, bool recursive) const override;
// void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override;
void get_modified_regs(RegSet& regs) const override;
private:
Form* m_branch_condition = nullptr;
Form* m_branch_delay = nullptr;
int m_label_id = -1;
bool m_likely = false;
};
/*!
@@ -559,6 +598,7 @@ class BranchElement : public FormElement {
void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(RegAccessSet& vars, bool recursive) const override;
void get_modified_regs(RegSet& regs) const override;
void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override;
const BranchOp* op() const { return m_op; }
private:
+186 -7
View File
@@ -789,9 +789,19 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env,
rd_in.stride = arg1_type.get_multiplier();
rd_in.offset = 0;
rd_in.base_type = arg0_type.typespec();
auto rd = env.dts->ts.reverse_field_lookup(rd_in);
auto rd = env.dts->ts.reverse_field_multi_lookup(rd_in);
int idx_of_success = -1;
if (rd.success) {
for (int i = 0; i < (int)rd.results.size(); i++) {
if (rd.results.at(i).has_variable_token()) {
idx_of_success = i;
break;
}
}
}
if (rd.success && rd.has_variable_token()) {
if (idx_of_success >= 0) {
auto& rd_ok = rd.results.at(idx_of_success);
auto arg1_matcher = Matcher::match_or(
{Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION),
{Matcher::any(0), Matcher::integer(rd_in.stride)}),
@@ -801,7 +811,7 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env,
if (match_result.matched) {
bool used_index = false;
std::vector<DerefToken> tokens;
for (auto& tok : rd.tokens) {
for (auto& tok : rd_ok.tokens) {
if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) {
assert(!used_index);
used_index = true;
@@ -811,7 +821,55 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env,
}
}
assert(used_index);
result->push_back(pool.alloc_element<DerefElement>(args.at(0), rd.addr_of, tokens));
result->push_back(pool.alloc_element<DerefElement>(args.at(0), rd_ok.addr_of, tokens));
return;
} else {
throw std::runtime_error("Failed to match product_with_constant inline array access.");
}
}
} else if (arg0_type.kind == TP_Type::Kind::PRODUCT_WITH_CONSTANT &&
arg1_type.kind == TP_Type::Kind::TYPESPEC &&
arg1_type.typespec().base_type() == "inline-array") {
FieldReverseLookupInput rd_in;
rd_in.deref = std::nullopt;
rd_in.stride = arg0_type.get_multiplier();
rd_in.offset = 0;
rd_in.base_type = arg1_type.typespec();
auto rd = env.dts->ts.reverse_field_multi_lookup(rd_in);
int idx_of_success = -1;
if (rd.success) {
for (int i = 0; i < (int)rd.results.size(); i++) {
if (rd.results.at(i).has_variable_token()) {
idx_of_success = i;
break;
}
}
}
// fmt::print("here {} {} {}\n", rd_in.base_type.print(), rd.success,
// rd.has_variable_token());
if (idx_of_success >= 0) {
auto& rd_ok = rd.results.at(idx_of_success);
auto arg0_matcher = Matcher::match_or(
{Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION),
{Matcher::any(0), Matcher::integer(rd_in.stride)}),
Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION),
{Matcher::integer(rd_in.stride), Matcher::any(0)})});
auto match_result = match(arg0_matcher, args.at(0));
if (match_result.matched) {
bool used_index = false;
std::vector<DerefToken> tokens;
for (auto& tok : rd_ok.tokens) {
if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) {
assert(!used_index);
used_index = true;
tokens.push_back(DerefToken::make_int_expr(match_result.maps.forms.at(0)));
} else {
tokens.push_back(to_token(tok));
}
}
assert(used_index);
result->push_back(pool.alloc_element<DerefElement>(args.at(1), rd_ok.addr_of, tokens));
return;
} else {
throw std::runtime_error("Failed to match product_with_constant inline array access.");
@@ -1032,6 +1090,27 @@ void SimpleExpressionElement::update_from_stack_vector_float_product(
result->push_back(new_form);
}
void SimpleExpressionElement::update_from_stack_vector_3_dot(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects) {
std::vector<Form*> popped_args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()},
env, pool, stack, allow_side_effects);
for (int i = 0; i < 2; i++) {
auto arg_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(i).var().reg());
if (arg_type.typespec() != TypeSpec("vector")) {
popped_args.at(i) = cast_form(popped_args.at(i), TypeSpec("vector"), pool, env);
}
}
auto new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::VECTOR_3_DOT),
std::vector<Form*>{popped_args.at(0), popped_args.at(1)});
result->push_back(new_form);
}
void SimpleExpressionElement::update_from_stack_copy_first_int_2(const Env& env,
FixedOperatorKind kind,
FormPool& pool,
@@ -1675,6 +1754,9 @@ void SimpleExpressionElement::update_from_stack(const Env& env,
case SimpleExpression::Kind::SUBU_L32_S7:
update_from_stack_subu_l32_s7(env, pool, stack, result, allow_side_effects);
break;
case SimpleExpression::Kind::VECTOR_3_DOT:
update_from_stack_vector_3_dot(env, pool, stack, result, allow_side_effects);
break;
default:
throw std::runtime_error(
fmt::format("SimpleExpressionElement::update_from_stack NYI for {}", to_string(env)));
@@ -3239,6 +3321,13 @@ FormElement* ConditionElement::make_generic(const Env& env,
casted);
}
case IR2_Condition::Kind::FLOAT_GREATER_THAN: {
// never emitted by normal branch conditions
auto casted = make_casts_if_needed(source_forms, types, TypeSpec("float"), pool, env);
return pool.alloc_element<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::GT),
casted);
}
default:
throw std::runtime_error("ConditionElement::make_generic NYI for kind " +
get_condition_kind_name(m_kind));
@@ -3317,7 +3406,11 @@ void ConditionElement::update_from_stack(const Env& env,
} else {
source_types.push_back(TypeSpec("int"));
}
} else {
} else if (m_src[i]->is_sym_val() && m_src[i]->get_str() == "#f") {
source_types.push_back(TypeSpec("symbol"));
}
else {
throw std::runtime_error("Unsupported atom in ConditionElement::update_from_stack");
}
}
@@ -3604,6 +3697,18 @@ void AtomicOpElement::push_to_stack(const Env& env, FormPool& pool, FormStack& s
return;
}
auto as_branch = dynamic_cast<AsmBranchOp*>(m_op);
if (as_branch && !as_branch->is_likely()) {
// this is a bit of a hack, but we go AsmBranchOp -> AsmBranchElement -> TranslatedAsmBranch
auto delay = as_branch->branch_delay();
assert(delay);
// this might not be enough - we may need to back up to the cfg builder and do something there.
auto del = pool.alloc_single_element_form<AtomicOpElement>(nullptr, delay);
auto be = pool.alloc_element<AsmBranchElement>(as_branch, del, false);
be->push_to_stack(env, pool, stack);
return;
}
throw std::runtime_error("Cannot push atomic op to stack: " + m_op->to_string(env));
}
@@ -3626,6 +3731,77 @@ void GenericElement::update_from_stack(const Env& env,
result->push_back(this);
}
void AsmBranchElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
// create a condition element
RegSet consumed;
if (env.has_reg_use()) {
consumed = env.reg_use().op.at(m_branch_op->op_id()).consumes;
}
std::optional<SimpleAtom> vars[2];
for (int i = 0; i < get_condition_num_args(m_branch_op->condition().kind()); i++) {
vars[i] = m_branch_op->condition().src(i);
}
auto ce = pool.alloc_element<ConditionElement>(m_branch_op->condition().kind(), vars[0], vars[1],
consumed, false);
// and update it from the stack.
std::vector<FormElement*> ce_updated;
ce->update_from_stack(env, pool, stack, &ce_updated, true);
auto branch_condition = pool.alloc_sequence_form(nullptr, ce_updated);
auto op = pool.alloc_element<TranslatedAsmBranch>(
branch_condition, m_branch_delay, m_branch_op->label_id(), m_branch_op->is_likely());
// fmt::print("rewrote as {}\n", op->to_string(env));
stack.push_form_element(op, true);
}
void BranchElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
// These will appear if we have an asm-branch that looked like a normal branch.
// create a condition element
RegSet consumed;
if (env.has_reg_use()) {
consumed = env.reg_use().op.at(m_op->op_id()).consumes;
}
std::optional<SimpleAtom> vars[2];
for (int i = 0; i < get_condition_num_args(m_op->condition().kind()); i++) {
vars[i] = m_op->condition().src(i);
}
auto ce = pool.alloc_element<ConditionElement>(m_op->condition().kind(), vars[0], vars[1],
consumed, false);
// and update it from the stack.
std::vector<FormElement*> ce_updated;
ce->update_from_stack(env, pool, stack, &ce_updated, true);
auto branch_condition = pool.alloc_sequence_form(nullptr, ce_updated);
Form* branch_delay = nullptr;
switch (m_op->branch_delay().kind()) {
case IR2_BranchDelay::Kind::NOP: {
branch_delay = nullptr;
} break;
case IR2_BranchDelay::Kind::SET_REG_REG: {
auto src = m_op->branch_delay().var(1);
auto dst = m_op->branch_delay().var(0);
auto src_form =
pool.alloc_single_element_form<SimpleAtomElement>(nullptr, SimpleAtom::make_var(src));
branch_delay = pool.alloc_single_element_form<SetVarElement>(
nullptr, dst, src_form, true, env.get_variable_type(src, true));
} break;
default:
throw std::runtime_error("Unhandled branch delay in BranchElement::push_to_stack: " +
m_op->to_string(env));
}
auto op = pool.alloc_element<TranslatedAsmBranch>(branch_condition, branch_delay,
m_op->label_id(), m_op->likely());
// fmt::print("rewrote (non-asm) as {}\n", op->to_string(env));
stack.push_form_element(op, true);
}
void GenericElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
(void)env;
(void)pool;
@@ -3643,7 +3819,8 @@ void DynamicMethodAccess::update_from_stack(const Env& env,
std::vector<FormElement*>* result,
bool allow_side_effects) {
mark_popped();
auto new_val = stack.pop_reg(m_source, {}, env, allow_side_effects);
// auto new_val = stack.pop_reg(m_source, {}, env, allow_side_effects);
auto new_val = pop_to_forms({m_source}, env, pool, stack, allow_side_effects).at(0);
auto reg0_matcher =
Matcher::match_or({Matcher::any_reg(0), Matcher::cast("uint", Matcher::any_reg(0))});
auto reg1_matcher =
@@ -3740,6 +3917,8 @@ void ArrayFieldAccess::update_with_val(Form* new_val,
Matcher::fixed_op(FixedOperatorKind::ADDITION_PTR, {mult_matcher, reg0_matcher})});
match_result = match(matcher, new_val);
if (!match_result.matched) {
result->push_back(this);
return;
fmt::print("power {}\n", power_of_two);
throw std::runtime_error(
"Couldn't match ArrayFieldAccess (stride power of 2, 0 offset) values: " +
@@ -3875,7 +4054,7 @@ void ArrayFieldAccess::update_from_stack(const Env& env,
std::vector<FormElement*>* result,
bool allow_side_effects) {
mark_popped();
auto new_val = stack.pop_reg(m_source, {}, env, allow_side_effects);
auto new_val = pop_to_forms({m_source}, env, pool, stack, allow_side_effects).at(0);
update_with_val(new_val, env, pool, result, allow_side_effects);
}
+1
View File
@@ -152,6 +152,7 @@ enum class FixedOperatorKind {
VECTOR_MINUS,
VECTOR_FLOAT_PRODUCT,
L32_NOT_FALSE_CBOOL,
VECTOR_3_DOT,
INVALID
};
+1 -1
View File
@@ -733,7 +733,7 @@ void ObjectFileDB::analyze_functions_ir1(const Config& config) {
// run analysis
// build a control flow graph, just looking at branch instructions.
func.cfg = build_cfg(data.linked_data, segment_id, func, {});
func.cfg = build_cfg(data.linked_data, segment_id, func, {}, {});
// convert individual basic blocks to sequences of IR Basic Ops
for (auto& block : func.basic_blocks) {
+9 -1
View File
@@ -219,7 +219,15 @@ void ObjectFileDB::ir2_basic_block_pass(const Config& config) {
if (lookup != config.hacks.cond_with_else_len_by_func_name.end()) {
hack = lookup->second;
}
func.cfg = build_cfg(data.linked_data, segment_id, func, hack);
std::unordered_set<int> asm_br_blocks;
auto asm_lookup =
config.hacks.blocks_ending_in_asm_branch_by_func_name.find(func.guessed_name.to_string());
if (asm_lookup != config.hacks.blocks_ending_in_asm_branch_by_func_name.end()) {
asm_br_blocks = asm_lookup->second;
}
func.cfg = build_cfg(data.linked_data, segment_id, func, hack, asm_br_blocks);
if (!func.cfg->is_fully_resolved()) {
lg::warn("Function {} from {} failed to build control flow graph!",
func.guessed_name.to_string(), data.to_unique_name());
+145 -3
View File
@@ -299,6 +299,8 @@ std::unique_ptr<AtomicOp> make_asm_op(const Instruction& i0, int idx) {
case InstructionKind::MULAS:
case InstructionKind::MADDAS:
case InstructionKind::MADDS:
case InstructionKind::MSUBAS:
case InstructionKind::MSUBS:
case InstructionKind::ADDAS:
// Moves / Loads / Stores
@@ -361,6 +363,14 @@ std::unique_ptr<AtomicOp> make_asm_op(const Instruction& i0, int idx) {
}
}
std::unique_ptr<AtomicOp> convert_1_allow_asm(const Instruction& i0, int idx) {
auto as_normal = convert_1(i0, idx, false);
if (as_normal) {
return as_normal;
}
return make_asm_op(i0, idx);
}
////////////////////////
// Branch Helpers
////////////////////////
@@ -407,7 +417,11 @@ std::unique_ptr<AtomicOp> make_branch(const IR2_Condition& condition,
if (branch_delay.is_known()) {
return std::make_unique<BranchOp>(likely, condition, dest_label, branch_delay, my_idx);
} else {
auto delay_op = std::shared_ptr<AtomicOp>(convert_1(delay, my_idx, false));
auto delay_op = std::shared_ptr<AtomicOp>(convert_1_allow_asm(delay, my_idx));
if (!delay_op) {
throw std::runtime_error(
fmt::format("Failed to convert branch delay slot instruction for branch at {}", my_idx));
}
return std::make_unique<AsmBranchOp>(likely, condition, dest_label, delay_op, my_idx);
}
}
@@ -421,6 +435,14 @@ std::unique_ptr<AtomicOp> make_branch_no_delay(const IR2_Condition& condition,
return std::make_unique<BranchOp>(likely, condition, dest_label, delay, my_idx);
}
std::unique_ptr<AtomicOp> make_asm_branch_no_delay(const IR2_Condition& condition,
bool likely,
int dest_label,
int my_idx) {
assert(likely);
return std::make_unique<AsmBranchOp>(likely, condition, dest_label, nullptr, my_idx);
}
///////////////////////
// OP 1 Conversions
//////////////////////
@@ -880,6 +902,23 @@ std::unique_ptr<AtomicOp> convert_1(const Instruction& i0, int idx, bool hint_in
// OP 2 Conversions
//////////////////////
std::unique_ptr<AtomicOp> convert_fp_branch_asm(const Instruction& i0,
const Instruction& i1,
IR2_Condition::Kind kind,
int idx) {
if (i1.kind == InstructionKind::BC1TL || i1.kind == InstructionKind::BC1FL) {
IR2_Condition condition(kind, make_src_atom(i0.get_src(0).get_reg(), idx),
make_src_atom(i0.get_src(1).get_reg(), idx));
if (i1.kind == InstructionKind::BC1FL) {
condition.invert();
}
// return make_branch(condition, i2, false, i1.get_src(0).get_label(), idx);
return make_asm_branch_no_delay(condition, true, i1.get_src(0).get_label(), idx);
}
return nullptr;
}
std::unique_ptr<AtomicOp> convert_division_2(const Instruction& i0,
const Instruction& i1,
int idx,
@@ -1071,6 +1110,8 @@ std::unique_ptr<AtomicOp> convert_2(const Instruction& i0, const Instruction& i1
return convert_slt_2(i0, i1, idx, true);
case InstructionKind::SLTU:
return convert_slt_2(i0, i1, idx, false);
case InstructionKind::CLTS:
return convert_fp_branch_asm(i0, i1, IR2_Condition::Kind::FLOAT_LESS_THAN, idx);
default:
return nullptr;
}
@@ -1605,6 +1646,98 @@ std::unique_ptr<AtomicOp> convert_6(const Instruction& i0,
return nullptr;
}
bool is_lwc(const Instruction& instr, int offset) {
return instr.kind == InstructionKind::LWC1 && instr.get_src(0).is_imm(offset);
}
// 9 instructions
std::unique_ptr<AtomicOp> convert_vector3_dot(const Instruction* instrs, int idx) {
// lwc1 f0, 0(a0)
if (!is_lwc(instrs[0], 0)) {
return nullptr;
}
auto t0 = instrs[0].get_dst(0).get_reg();
// lwc1 f1, 4(a0)
if (!is_lwc(instrs[1], 4)) {
return nullptr;
}
auto t1 = instrs[1].get_dst(0).get_reg();
// lwc1 f2, 8(a0)
if (!is_lwc(instrs[2], 8)) {
return nullptr;
}
auto t2 = instrs[2].get_dst(0).get_reg();
// lwc1 f3, 0(v1)
if (!is_lwc(instrs[3], 0)) {
return nullptr;
}
auto t3 = instrs[3].get_dst(0).get_reg();
// lwc1 f4, 4(v1)
if (!is_lwc(instrs[4], 4)) {
return nullptr;
}
auto t4 = instrs[4].get_dst(0).get_reg();
// lwc1 f5, 8(v1)
if (!is_lwc(instrs[5], 8)) {
return nullptr;
}
auto t5 = instrs[5].get_dst(0).get_reg();
auto src0 = instrs[0].get_src(1).get_reg();
auto src1 = instrs[3].get_src(1).get_reg();
if (instrs[1].get_src(1).get_reg() != src0) {
return nullptr;
}
if (instrs[2].get_src(1).get_reg() != src0) {
return nullptr;
}
if (instrs[4].get_src(1).get_reg() != src1) {
return nullptr;
}
if (instrs[5].get_src(1).get_reg() != src1) {
return nullptr;
}
// mula.s f0, f3
if (instrs[6].kind != InstructionKind::MULAS || instrs[6].get_src(0).get_reg() != t0 ||
instrs[6].get_src(1).get_reg() != t3) {
return nullptr;
}
// madda.s f1, f4
if (instrs[7].kind != InstructionKind::MADDAS || instrs[7].get_src(0).get_reg() != t1 ||
instrs[7].get_src(1).get_reg() != t4) {
return nullptr;
}
// madd.s f0, f2, f5
if (instrs[8].kind != InstructionKind::MADDS || instrs[8].get_src(0).get_reg() != t2 ||
instrs[8].get_src(1).get_reg() != t5) {
return nullptr;
}
auto dst = instrs[8].get_dst(0).get_reg();
return std::make_unique<SetVarOp>(
make_dst_var(dst, idx),
SimpleExpression(SimpleExpression::Kind::VECTOR_3_DOT, make_src_atom(src0, idx),
make_src_atom(src1, idx)),
idx);
}
std::unique_ptr<AtomicOp> convert_9(const Instruction* instrs, int idx) {
auto as_vector3_dot = convert_vector3_dot(instrs, idx);
if (as_vector3_dot) {
return as_vector3_dot;
}
return nullptr;
}
} // namespace
/*!
@@ -1637,7 +1770,15 @@ int convert_block_to_atomic_ops(int begin_idx,
warnings.warn_sq_lq();
}
if (n_instr >= 6) {
if (n_instr >= 9) {
op = convert_9(&instr[0], op_idx);
if (op) {
converted = true;
length = 9;
}
}
if (!converted && n_instr >= 6) {
// try 6 instructions
op = convert_6(instr[0], instr[1], instr[2], instr[3], instr[4], instr[5], op_idx);
if (op) {
@@ -1702,7 +1843,8 @@ int convert_block_to_atomic_ops(int begin_idx,
if (!converted) {
// failed!
throw std::runtime_error("Failed to convert " + instr->to_string(labels));
throw std::runtime_error(
fmt::format("Failed to convert ({} instrs) {}\n", n_instr, instr->to_string(labels)));
// lg::die("Failed to convert instruction {} to an atomic op",
// instr->to_string(labels));
}
+40 -6
View File
@@ -178,7 +178,7 @@ void clean_up_return_final(const Function& f, ReturnElement* ir) {
/*!
* Remove the branch in a break (really return-from nonfunction scope)
*/
void clean_up_break(FormPool& pool, BreakElement* ir) {
void clean_up_break(FormPool& pool, BreakElement* ir, const Env&) {
auto jump_to_end = get_condition_branch(ir->return_code);
assert(jump_to_end.first);
assert(jump_to_end.first->op()->branch_delay().kind() == IR2_BranchDelay::Kind::NOP);
@@ -192,7 +192,7 @@ void clean_up_break(FormPool& pool, BreakElement* ir) {
}
}
void clean_up_break_final(const Function& f, BreakElement* ir) {
void clean_up_break_final(const Function& f, BreakElement* ir, const Env& env) {
EmptyElement* dead_empty = dynamic_cast<EmptyElement*>(ir->dead_code->try_as_single_element());
if (dead_empty) {
ir->dead_code = nullptr;
@@ -210,6 +210,13 @@ void clean_up_break_final(const Function& f, BreakElement* ir) {
}
}
if (!dead) {
if (ir->dead_code->to_string(env) == "(nop!)") {
ir->dead_code = nullptr;
return;
}
}
if (!dead) {
lg::error("failed to recognize dead code after break, got {}",
ir->dead_code->to_string(f.ir2.env));
@@ -1379,6 +1386,31 @@ bool contains(const std::vector<T>& vec, const T& val) {
}
} // namespace
/*!
* Push x to output (can be a Form or std::vector<FormElement>).
* Will take of grouping the delay slots for likely asm branches into a single operation.
*/
template <typename T>
void push_back_form_regroup_asm_likely_branches(T* output, FormElement* x, Function& f) {
std::vector<FormElement*> hack_temp;
if (output->size() > 0) {
auto back_as_asm = dynamic_cast<AtomicOpElement*>(output->back());
if (back_as_asm) {
auto back_as_branch = dynamic_cast<AsmBranchOp*>(back_as_asm->op());
if (back_as_branch && back_as_branch->is_likely()) {
auto& pool = *f.ir2.form_pool;
auto elt = pool.alloc_element<AsmBranchElement>(back_as_branch,
pool.alloc_single_form(nullptr, x), true);
output->pop_back();
output->push_back(elt);
return;
}
}
}
output->push_back(x);
}
template <typename T>
void convert_and_inline(FormPool& pool, Function& f, const BlockVtx* as_block, T* output) {
auto start_op = f.ir2.atomic_ops->block_id_to_first_atomic_op.at(as_block->block_id);
@@ -1436,7 +1468,8 @@ void convert_and_inline(FormPool& pool, Function& f, const BlockVtx* as_block, T
}
if (add) {
add_map.push_back(output->size());
output->push_back(op);
// output->push_back(op);
push_back_form_regroup_asm_likely_branches(output, op, f);
} else {
add_map.push_back(-1);
}
@@ -1465,7 +1498,8 @@ void insert_cfg_into_list(FormPool& pool,
} else {
auto ir = cfg_to_ir(pool, f, vtx);
for (auto x : ir->elts()) {
output->push_back(x);
push_back_form_regroup_asm_likely_branches(output, x, f);
// output->push_back(x);
}
}
}
@@ -1643,7 +1677,7 @@ Form* cfg_to_ir_helper(FormPool& pool, Function& f, const CfgVtx* vtx) {
auto result = pool.alloc_single_element_form<BreakElement>(
nullptr, cfg_to_ir(pool, f, cvtx->body),
cfg_to_ir_allow_null(pool, f, cvtx->unreachable_block), cvtx->dest_block_id);
clean_up_break(pool, dynamic_cast<BreakElement*>(result->try_as_single_element()));
clean_up_break(pool, dynamic_cast<BreakElement*>(result->try_as_single_element()), f.ir2.env);
return result;
} else if (dynamic_cast<const EmptyVtx*>(vtx)) {
return pool.alloc_single_element_form<EmptyElement>(nullptr);
@@ -1739,7 +1773,7 @@ void build_initial_forms(Function& function) {
auto as_break = dynamic_cast<BreakElement*>(form);
if (as_break) {
clean_up_break_final(function, as_break);
clean_up_break_final(function, as_break, function.ir2.env);
}
});
+3
View File
@@ -162,6 +162,9 @@ Config read_config_file(const std::string& path_to_config_file) {
hacks_json.at("types_with_bad_inspect_methods").get<std::unordered_set<std::string>>();
config.hacks.reject_cond_to_value = hacks_json.at("aggressively_reject_cond_to_value_rewrite")
.get<std::unordered_set<std::string>>();
config.hacks.blocks_ending_in_asm_branch_by_func_name =
hacks_json.at("blocks_ending_in_asm_branch")
.get<std::unordered_map<std::string, std::unordered_set<int>>>();
for (auto& entry : hacks_json.at("cond_with_else_max_lengths")) {
auto func_name = entry.at(0).get<std::string>();
+1
View File
@@ -59,6 +59,7 @@ struct DecompileHacks {
std::unordered_set<std::string> pair_functions_by_name;
std::unordered_map<std::string, CondWithElseLengthHack> cond_with_else_len_by_func_name;
std::unordered_set<std::string> reject_cond_to_value;
std::unordered_map<std::string, std::unordered_set<int>> blocks_ending_in_asm_branch_by_func_name;
};
struct Config {
+32 -27
View File
@@ -1727,11 +1727,11 @@
)
(deftype vector (structure)
((data float 4 :do-not-decompile :score -9999 :offset-assert 0)
(x float :offset 0)
((x float :offset 0)
(y float :offset 4)
(z float :offset 8)
(w float :offset 12)
(data float 4 :score -9999 :offset 0)
(quad uint128 :offset 0)
)
:method-count-assert 9
@@ -2100,7 +2100,7 @@
(deftype curve (structure)
((cverts pointer :offset-assert 0)
(num-cverts int32 :offset-assert 4)
(knots pointer :offset-assert 8)
(knots (inline-array vector) :offset-assert 8)
(num-knots int32 :offset-assert 12)
(length float :offset-assert 16)
)
@@ -2224,7 +2224,7 @@
(define-extern atan (function float float float))
(define-extern matrix-4x4-determinant (function matrix float))
(define-extern matrix-3x3-determinant (function matrix float))
(define-extern matrix-axis-sin-cos! (function matrix vector float float none))
(define-extern matrix-axis-sin-cos! (function matrix vector float float matrix))
(define-extern sin (function float float))
(define-extern cos (function float float))
(define-extern matrix-rotate-y! (function matrix float matrix))
@@ -2381,16 +2381,16 @@
;; - Functions
(define-extern curve-length function)
(define-extern curve-get-pos! function)
(define-extern vector-vector-distance-squared (function vector vector float))
(define-extern curve-evaluate! (function vector float int int vector int int))
(define-extern vector-vector-distance (function vector vector float))
(define-extern circle-circle-xz-intersect (function vector vector vector vector int))
(define-extern circle-circle-xz-intersect (function sphere sphere vector vector int))
(define-extern vector-normalize-copy! (function vector vector float vector))
(define-extern forward-up->quaternion (function quaternion vector vector quaternion))
(define-extern matrix-from-two-vectors-partial-linear! (function matrix vector vector float float none))
(define-extern matrix-from-two-vectors-max-angle! (function matrix vector vector none))
(define-extern matrix-from-two-vectors-partial-linear! (function matrix vector vector float matrix))
(define-extern matrix-from-two-vectors-max-angle! (function matrix vector vector float matrix))
(define-extern vector-negate! (function vector vector vector))
(define-extern vector-normalize-ret-len! (function vector float float))
(define-extern vector-flatten! (function vector vector vector vector))
@@ -2408,30 +2408,35 @@
(define-extern vector-segment-distance-point! (function vector vector vector vector float))
(define-extern vector-line-distance (function vector vector vector float))
(define-extern vector-line-distance-point! (function vector vector vector vector float))
(define-extern vector-orient-by-quat! (function vector vector vector vector))
(define-extern vector-orient-by-quat! (function vector vector quaternion vector))
(define-extern forward-up-nopitch->quaternion (function quaternion vector vector quaternion))
(define-extern quaternion-from-two-vectors! (function quaternion vector vector quaternion))
(define-extern matrix-from-two-vectors! (function matrix vector vector none))
(define-extern matrix-from-two-vectors-max-angle-partial! (function matrix vector vector float float none))
(define-extern matrix-remove-z-rot (function matrix matrix))
(define-extern matrix-rot-diff! (function matrix matrix matrix float))
(define-extern quaternion-seek (function quaternion quaternion quaternion float quaternion))
(define-extern vector-deg-seek (function vector vector vector none))
(define-extern matrix-from-two-vectors! (function matrix vector vector matrix))
(define-extern matrix-from-two-vectors-max-angle-partial! (function matrix vector vector float float matrix))
(define-extern matrix-remove-z-rot (function matrix matrix matrix))
(define-extern matrix-rot-diff! (function vector matrix matrix float))
(define-extern quaternion-seek (function quaternion quaternion quaternion float float quaternion))
(define-extern vector-deg-seek (function vector vector vector float vector))
(define-extern vector-deg-slerp (function vector vector vector float vector))
(define-extern vector-vector-deg-slerp! function) ;; stack spills!
(define-extern vector-vector-deg-slerp! (function vector vector vector float vector vector)) ;; stack spills!
(define-extern normal-of-plane (function vector vector vector vector vector))
(define-extern vector-3pt-cross! (function vector vector vector vector vector))
(define-extern closest-pt-in-triangle function) ;; asm branches
(define-extern closest-pt-in-triangle (function vector vector matrix vector none)) ;; asm branches
(define-extern point-in-triangle-cross (function vector vector vector vector vector symbol))
(define-extern point-in-plane-<-point+normal! (function vector vector vector vector))
(define-extern circle-test (function none))
(define-extern vector-circle-tangent-new function)
(define-extern vector-circle-tangent function)
(define-extern find-knot-span function)
(define-extern calculate-basis-functions-vector! function)
(define-extern curve-copy! function)
(define-extern curve-closest-point function)
(define-extern vector-plane-distance function)
(define-extern vector-circle-tangent-new (function vector vector vector vector none))
(define-extern vector-circle-tangent (function vector vector vector vector none))
(define-extern find-knot-span (function int int float (inline-array vector) int))
(define-extern calculate-basis-functions-vector! (function (pointer float) int float (pointer float) (pointer float)))
(define-extern curve-closest-point (function curve vector float float int float float))
(define-extern vector-plane-distance (function vector plane vector float))
(define-extern curve-get-pos! (function vector float curve vector))
(define-extern curve-evaluate! (function vector float pointer int (inline-array vector) int vector))
(define-extern curve-length (function curve float))
(define-extern curve-copy! (function curve curve curve))
;; ----------------------
@@ -15051,7 +15056,7 @@
(define-extern add-debug-light (function symbol bucket-id light vector string symbol))
(define-extern add-debug-text-3d (function symbol bucket-id string vector rgba vector2h symbol))
(define-extern add-debug-x (function symbol bucket-id vector rgba symbol))
(define-extern add-debug-curve (function symbol bucket-id pointer int vector int rgba symbol))
(define-extern add-debug-curve (function symbol bucket-id pointer int (inline-array vector) int rgba symbol))
(define-extern add-debug-sphere (function symbol bucket-id vector float rgba symbol))
(define-extern get-debug-text-3d (function debug-text-3d))
(define-extern internal-draw-debug-text-3d (function bucket-id string vector rgba vector2h pointer))
@@ -69,11 +69,7 @@
"matrix-axis-sin-cos-vu!",
// geometry
"curve-evaluate!", // BUG: cfg fails, suspected weird gotos
"circle-circle-xz-intersect", // F: asm branching
"closest-pt-in-triangle", // F: asm branching
"find-knot-span", // ??
"vector-segment-distance-point!",
// trigonometry
"exp", // BUG: cfg is wrong.
@@ -493,5 +489,16 @@
"ERROR: dma tag has data in reserved bits ~X~%": 0,
"#<surface f0:~m f1:~f tf+:~f tf-:~f sf:~f tvv:~m": 5,
"ERROR<GMJ>: value of symbol ~A in task-controls is not a task-control~%": 0
},
"blocks_ending_in_asm_branch": {
"closest-pt-in-triangle": [17],
// this one is all asm branches
"circle-circle-xz-intersect": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
"find-knot-span": [0, 1, 2, 3, 5, 6, 7, 8, 9],
"curve-evaluate!": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
@@ -551,7 +551,7 @@
"main": [["L230", "_lambda_", true]],
"geometry": [["L125", "float", true]],
"geometry": [["L125", "float", true], ["L126", "float", true], ["L112", "(pointer float)", true, 4]],
"level": [
["L452", "_auto_", true],
@@ -468,5 +468,46 @@
"(method 9 game-info)": [[16, "event-message-block"]],
"(method 9 continue-point)": [[16, "vector"]],
"(method 9 game-save)": [[16, "file-stream"]],
"(method 10 game-save)": [[16, "file-stream"]]
"(method 10 game-save)": [[16, "file-stream"]],
"vector-vector-deg-slerp!": [
[16, "vector"],
[32, "vector"],
[48, "quaternion"],
[64, "quaternion"],
[80, "quaternion"],
[96, "vector"]
],
"closest-pt-in-triangle": [
[16, "vector"],
[32, "vector"],
[48, "vector"]
],
"vector-circle-tangent-new": [
[16, "sphere"],
[32, "vector"],
[48, "vector"]
],
"vector-circle-tangent": [
[16, "sphere"],
[32, "vector"],
[48, "vector"],
[64, "vector"]
],
"vector-plane-distance": [
[16, "vector"]
],
"curve-length": [
[16, "vector"],
[32, "vector"]
],
"curve-closest-point": [
[16, "vector"],
[32, "vector"]
]
}
@@ -1018,5 +1018,10 @@
[21, "t9", "(function int int int int int)"]
],
"calculate-basis-functions-vector!": [
[[8, 20], "v1", "(pointer float)"],
[[0, 60], "f1", "float"]
],
"placeholder-do-not-add-below": []
}
+1 -1
View File
@@ -711,7 +711,7 @@ This is updated from the entity system used in Crash 2, which had most of these
(set! curve-data (get-property-data obj knots-name 'exact time (the pointer #f) (& knots-tag) *res-static-buf*))
(when curve-data
(set! (-> curve-target knots) curve-data)
(set! (-> curve-target knots) (the (inline-array vector) curve-data))
(set! (-> curve-target num-knots) (the int (-> knots-tag elt-count)))
(set! result #t)
)
+4 -4
View File
@@ -1304,7 +1304,7 @@
)
)
(defun-debug add-debug-curve ((arg0 symbol) (arg1 bucket-id) (arg2 pointer) (arg3 int) (arg4 vector) (arg5 int) (arg6 rgba))
(defun-debug add-debug-curve ((arg0 symbol) (arg1 bucket-id) (arg2 pointer) (arg3 int) (arg4 (inline-array vector)) (arg5 int) (arg6 rgba))
(local-vars (sv-48 vector) (sv-64 int) (sv-80 int))
(if (not arg0)
(return #f)
@@ -1313,14 +1313,14 @@
(set! sv-48 (new 'stack-no-clear 'vector))
(set! (-> sv-48 quad) (the-as uint128 0))
(set! sv-64 (* arg3 4))
(curve-evaluate! sv-48 (-> arg4 x) (the-as int arg2) arg3 arg4 arg5)
(curve-evaluate! sv-48 (-> arg4 0 x) arg2 arg3 arg4 arg5)
(set! sv-80 0)
(while (< sv-80 sv-64)
(set! (-> s0-0 quad) (-> sv-48 quad))
(curve-evaluate!
sv-48
(/ (the float (+ sv-80 1)) (the float sv-64))
(the-as int arg2)
arg2
arg3
arg4
arg5
@@ -1339,7 +1339,7 @@
arg1
(-> arg2 cverts)
(-> arg2 num-cverts)
(the-as vector (-> arg2 knots))
(-> arg2 knots)
(-> arg2 num-knots)
arg3
)
+1 -1
View File
@@ -10,7 +10,7 @@
(deftype curve (structure)
((cverts pointer :offset-assert 0)
(num-cverts int32 :offset-assert 4)
(knots pointer :offset-assert 8)
(knots (inline-array vector) :offset-assert 8)
(num-knots int32 :offset-assert 12)
(length float :offset-assert 16)
)
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1001,7 +1001,7 @@
(matrix-identity! dst)
(label end)
)
(none)
dst
)
(defun matrix-axis-angle! ((dst matrix) (axis vector) (angle-deg float))
-19
View File
@@ -976,25 +976,6 @@
)
)
;; (local-vars (f0-1 float))
;; (let ((gp-0 acos))
;; (let* ((s5-0 (vector-z-quaternion! (new 'stack 'vector) arg0))
;; (v1-1 (vector-z-quaternion! (new 'stack 'vector) arg1))
;; (f0-0 (-> s5-0 data 0))
;; (f1-0 (-> s5-0 data 1))
;; (f2-0 (-> s5-0 data 2))
;; (f3-0 (-> v1-1 data 0))
;; (f4-0 (-> v1-1 data 1))
;; (f5-0 (-> v1-1 data 2))
;; )
;; (.mula.s f0-0 f3-0)
;; (.madda.s f1-0 f4-0)
;; (.madd.s f0-1 f2-0 f5-0)
;; )
;; (gp-0 f0-1)
;; )
(defun quaternion-rotate-y-to-vector! ((arg0 quaternion) (arg1 quaternion) (arg2 quaternion) (arg3 float))
(let ((s5-0 (new 'stack-no-clear 'quaternion)))
(let ((t9-0 vector-xz-normalize!)
+8 -2
View File
@@ -296,11 +296,11 @@
;; Vector of 4 floats. Shortened to "vector" because it is commonly used.
(deftype vector (structure)
((data float 4 :do-not-decompile :score -9999 :offset-assert 0)
(x float :offset 0)
((x float :offset 0)
(y float :offset 4)
(z float :offset 8)
(w float :offset 12)
(data float 4 :do-not-decompile :score -9999 :offset 0)
(quad uint128 :offset 0)
)
:method-count-assert 9
@@ -714,3 +714,9 @@
(defun-extern vector+float*! vector vector vector float vector)
(defun-extern vector-normalize! vector float vector)
(defun-extern vector-float*! vector vector float vector)
(define-extern vector-normalize-copy! (function vector vector float vector))
(define-extern vector-cross! (function vector vector vector vector))
(define-extern vector-negate! (function vector vector vector))
(define-extern vector-normalize-ret-len! (function vector float float))
(define-extern vector-vector-distance (function vector vector float))
(define-extern vector-vector-distance-squared (function vector vector float))
+18
View File
@@ -1365,3 +1365,21 @@
;; Copies the contents of a gpr to a cop0 (system control) register
(fake-asm .mtc0 dest src)
)
;;;;;;;;;;;;;;;;;;;;;;;;
;; Branch Macro
;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro b! (pred destination &key (delay '()) &key (likely-delay '()))
"Branch!"
;; evaluate the predicate
`(let ((should-branch ,pred))
;; normal delay slot:
,delay
(when should-branch
,likely-delay
(goto ,destination)
)
)
)
+19 -2
View File
@@ -6,9 +6,9 @@ import argparse
### Script to track decompilation progress.
### Example usage: python3 scripts/decomp_progress.py ~/jak-project/goal_src
def get_goal_files(root_dir):
def get_goal_files(root_dir, ext = "*.gc"):
"""Get all GOAL source files under root_dir."""
return [goal_file for file in os.walk(root_dir) for goal_file in glob.glob(os.path.join(file[0], '*.gc'))]
return [goal_file for file in os.walk(root_dir) for goal_file in glob.glob(os.path.join(file[0], ext))]
def lines_in_file(file_path):
@@ -41,10 +41,17 @@ def main():
args = parser.parse_args()
all_files = get_goal_files(args.goal_src)
ref_files = get_goal_files(args.goal_src + "/../test/", "*_REF.gc")
ref_files_no_ext = [os.path.basename(fn)[:-7] for fn in ref_files]
file_stats = []
total_gc_files = 0
excluded_files = {"game_dgos.gc", "all_files.gc", "goal-lib.gc", "ocean-trans-tables.gc", "ocean-frames.gc",
"ocean-tables.gc"}
modified = set()
for fn in all_files:
short_name = os.path.basename(fn)
@@ -60,8 +67,18 @@ def main():
continue
file_stats.append((short_name, line_count))
modified.add(short_name[:-3])
file_stats.sort(key=lambda x: x[1])
missing_ref_files = modified - set(ref_files_no_ext)
print("Missing ref files:")
for fn in missing_ref_files:
print(" {}".format(fn))
print_table(file_stats, total_gc_files)
+1 -1
View File
@@ -149,7 +149,7 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
// analyze function prologue/epilogue
test->func.analyze_prologue(test->file);
// build control flow graph
test->func.cfg = build_cfg(test->file, 0, test->func, {});
test->func.cfg = build_cfg(test->file, 0, test->func, {}, {});
EXPECT_TRUE(test->func.cfg->is_fully_resolved());
if (!test->func.cfg->is_fully_resolved()) {
fmt::print("CFG:\n{}\n", test->func.cfg->to_dot());
+14 -1
View File
@@ -113,4 +113,17 @@
;; otherwise don't ignore it.
`(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@body))
)
)
)
(defmacro b! (pred destination &key (delay '()) &key (likely-delay '()))
"Branch!"
;; evaluate the predicate
`(let ((should-branch ,pred))
;; normal delay slot:
,delay
(when should-branch
,likely-delay
(goto ,destination)
)
)
)
@@ -3,11 +3,11 @@
;; definition of type curve
(deftype curve (structure)
((cverts pointer :offset-assert 0)
(num-cverts int32 :offset-assert 4)
(knots pointer :offset-assert 8)
(num-knots int32 :offset-assert 12)
(length float :offset-assert 16)
((cverts pointer :offset-assert 0)
(num-cverts int32 :offset-assert 4)
(knots (inline-array vector) :offset-assert 8)
(num-knots int32 :offset-assert 12)
(length float :offset-assert 16)
)
:method-count-assert 9
:size-assert #x14
File diff suppressed because it is too large Load Diff
@@ -785,6 +785,7 @@
;; ERROR: function was not converted to expressions. Cannot decompile.
;; definition for function matrix-axis-angle!
;; INFO: Return type mismatch matrix vs none.
(defun matrix-axis-angle! ((dst matrix) (axis vector) (angle-deg float))
(matrix-axis-sin-cos! dst axis (sin angle-deg) (cos angle-deg))
(none)
@@ -598,18 +598,9 @@
)
;; definition for function matrix-with-scale->quaternion
;; WARN: Unsupported inline assembly instruction kind - [mula.s f0, f3]
;; WARN: Unsupported inline assembly instruction kind - [madda.s f1, f4]
;; WARN: Unsupported inline assembly instruction kind - [madd.s f0, f2, f5]
;; WARN: Unsupported inline assembly instruction kind - [mula.s f1, f4]
;; WARN: Unsupported inline assembly instruction kind - [madda.s f2, f5]
;; WARN: Unsupported inline assembly instruction kind - [madd.s f1, f3, f6]
;; WARN: Unsupported inline assembly instruction kind - [mula.s f2, f5]
;; WARN: Unsupported inline assembly instruction kind - [madda.s f3, f6]
;; WARN: Unsupported inline assembly instruction kind - [madd.s f2, f4, f7]
;; Used lq/sq
(defun matrix-with-scale->quaternion ((arg0 quaternion) (arg1 matrix))
(local-vars (a1-4 float) (f0-1 float) (f1-2 float) (f2-3 float))
(local-vars (a1-4 float))
(rlet ((vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
@@ -619,50 +610,31 @@
(vf7 :class vf)
)
(let ((v1-0 (new-stack-matrix0)))
(let* ((a3-0 (-> arg1 vector))
(a2-0 (-> arg1 vector))
(f0-0 (-> a3-0 0 x))
(f1-0 (-> a3-0 0 y))
(f2-0 (-> a3-0 0 z))
(f3-0 (-> a2-0 0 x))
(f4-0 (-> a2-0 0 y))
(f5-0 (-> a2-0 0 z))
)
(.mula.s f0-0 f3-0)
(.madda.s f1-0 f4-0)
(.madd.s f0-1 f2-0 f5-0)
)
(let ((f0-2 f0-1))
(let* ((a3-1 (-> arg1 vector 1))
(a2-2 (-> arg1 vector 1))
)
(set! f1-2 (vector-dot a3-1 a2-2))
(let*
((f0-1
(vector-dot
(the-as vector (-> arg1 vector))
(the-as vector (-> arg1 vector))
)
)
(f1-1 (vector-dot (-> arg1 vector 1) (-> arg1 vector 1)))
(f2-1 (vector-dot (-> arg1 vector 2) (-> arg1 vector 2)))
(f0-3 (/ 1.0 (sqrtf f0-1)))
(f1-3 (/ 1.0 (sqrtf f1-1)))
(f2-3 (/ 1.0 (sqrtf f2-1)))
)
(let ((f1-3 f1-2))
(let* ((a3-2 (-> arg1 vector 2))
(a2-4 (-> arg1 vector 2))
)
(set! f2-3 (vector-dot a3-2 a2-4))
)
(let* ((f2-4 f2-3)
(f0-4 (/ 1.0 (sqrtf f0-2)))
(f1-5 (/ 1.0 (sqrtf f1-3)))
(f2-6 (/ 1.0 (sqrtf f2-4)))
)
(.lvf vf1 (&-> arg1 vector 0 quad))
(.lvf vf2 (&-> arg1 vector 1 quad))
(.lvf vf3 (&-> arg1 vector 2 quad))
(.lvf vf4 (&-> arg1 vector 3 quad))
(let ((a1-1 f0-4))
(.mov vf5 a1-1)
)
(let ((a1-2 f1-5))
(.mov vf6 a1-2)
)
(let ((a1-3 f2-6))
(.mov vf7 a1-3)
)
)
(.lvf vf1 (&-> arg1 vector 0 quad))
(.lvf vf2 (&-> arg1 vector 1 quad))
(.lvf vf3 (&-> arg1 vector 2 quad))
(.lvf vf4 (&-> arg1 vector 3 quad))
(let ((a1-1 f0-3))
(.mov vf5 a1-1)
)
(let ((a1-2 f1-3))
(.mov vf6 a1-2)
)
(let ((a1-3 f2-3))
(.mov vf7 a1-3)
)
)
(.mul.x.vf vf1 vf1 vf5)
@@ -1021,18 +993,12 @@
)
;; definition for function quaternion-delta-y
;; WARN: Unsupported inline assembly instruction kind - [mula.s f0, f3]
;; WARN: Unsupported inline assembly instruction kind - [madda.s f1, f4]
;; WARN: Unsupported inline assembly instruction kind - [madd.s f0, f2, f5]
(defun quaternion-delta-y ((arg0 quaternion) (arg1 quaternion))
(local-vars (f0-1 float))
(let ((gp-0 acos))
(let* ((s5-0 (vector-z-quaternion! (new 'stack-no-clear 'vector) arg0))
(v1-1 (vector-z-quaternion! (new 'stack-no-clear 'vector) arg1))
)
(set! f0-1 (vector-dot s5-0 v1-1))
(acos
(vector-dot
(vector-z-quaternion! (new 'stack-no-clear 'vector) arg0)
(vector-z-quaternion! (new 'stack-no-clear 'vector) arg1)
)
(gp-0 f0-1)
)
)
@@ -433,12 +433,12 @@
;; definition of type vector
(deftype vector (structure)
((data float 4 :offset-assert 0)
(x float :offset 0)
(y float :offset 4)
(z float :offset 8)
(w float :offset 12)
(quad uint128 :offset 0)
((x float :offset 0)
(y float :offset 4)
(z float :offset 8)
(w float :offset 12)
(data float 4 :offset 0)
(quad uint128 :offset 0)
)
:method-count-assert 9
:size-assert #x10
@@ -820,23 +820,8 @@
)
;; definition for function vector-dot
;; WARN: Unsupported inline assembly instruction kind - [mula.s f0, f3]
;; WARN: Unsupported inline assembly instruction kind - [madda.s f1, f4]
;; WARN: Unsupported inline assembly instruction kind - [madd.s f0, f2, f5]
(defun vector-dot ((arg0 vector) (arg1 vector))
(local-vars (f0-1 float))
(let ((f0-0 (-> arg0 x))
(f1-0 (-> arg0 y))
(f2-0 (-> arg0 z))
(f3-0 (-> arg1 x))
(f4-0 (-> arg1 y))
(f5-0 (-> arg1 z))
)
(.mula.s f0-0 f3-0)
(.madda.s f1-0 f4-0)
(.madd.s f0-1 f2-0 f5-0)
)
f0-1
(vector-dot arg0 arg1)
)
;; definition for function vector-dot-vu
@@ -367,12 +367,9 @@
;; definition for function joint-mod-look-at-handler
;; INFO: Return type mismatch int vs none.
;; WARN: Unsupported inline assembly instruction kind - [mula.s f1, f4]
;; WARN: Unsupported inline assembly instruction kind - [madda.s f2, f5]
;; WARN: Unsupported inline assembly instruction kind - [madd.s f1, f3, f6]
;; Used lq/sq
(defun joint-mod-look-at-handler ((csp cspace) (xform transformq))
(local-vars (f1-12 float) (sv-48 vector) (sv-52 vector) (sv-56 vector))
(local-vars (sv-48 vector) (sv-52 vector) (sv-56 vector))
(let ((gp-0 (the-as joint-mod (-> csp param1))))
(cspace<-parented-transformq-joint! csp xform)
(set!
@@ -468,10 +465,7 @@
)
)
)
(let* ((v1-22 sv-52))
(set! f1-12 (vector-dot s3-2 v1-22))
)
(if (< f1-12 0.1)
(if (< (vector-dot s3-2 sv-52) 0.1)
(set! f0-21 0.0)
)
(set!
@@ -518,12 +512,9 @@
;; definition for function joint-mod-world-look-at-handler
;; INFO: Return type mismatch int vs none.
;; WARN: Unsupported inline assembly instruction kind - [mula.s f1, f4]
;; WARN: Unsupported inline assembly instruction kind - [madda.s f2, f5]
;; WARN: Unsupported inline assembly instruction kind - [madd.s f1, f3, f6]
;; Used lq/sq
(defun joint-mod-world-look-at-handler ((arg0 cspace) (arg1 transformq))
(local-vars (f1-14 float) (sv-48 vector) (sv-52 vector) (sv-56 vector))
(local-vars (sv-48 vector) (sv-52 vector) (sv-56 vector))
(let ((gp-0 (the-as joint-mod (-> arg0 param1))))
(let ((s5-0 (-> arg0 bone transform)))
(cspace<-parented-transformq-joint! arg0 arg1)
@@ -606,10 +597,7 @@
)
)
)
(let* ((v1-14 sv-52))
(set! f1-14 (vector-dot s4-4 v1-14))
)
(if (< f1-14 0.1)
(if (< (vector-dot s4-4 sv-52) 0.1)
(set! f0-20 0.0)
)
(set!
@@ -792,12 +780,8 @@
)
;; definition for function joint-mod-wheel-callback
;; WARN: Unsupported inline assembly instruction kind - [mula.s f0, f3]
;; WARN: Unsupported inline assembly instruction kind - [madda.s f1, f4]
;; WARN: Unsupported inline assembly instruction kind - [madd.s f0, f2, f5]
;; Used lq/sq
(defun joint-mod-wheel-callback ((arg0 cspace) (arg1 transformq))
(local-vars (f0-3 float))
(let ((s4-0 (the-as joint-mod-wheel (-> arg0 param1))))
(let ((v1-1 (-> s4-0 process root))
(s1-0 (new-stack-vector0))
@@ -810,14 +794,13 @@
(vector<-cspace! s1-0 arg0)
(vector-! s3-0 s1-0 (-> s4-0 last-position))
(set! (-> s4-0 last-position quad) (-> s1-0 quad))
(set! f0-3 (vector-dot s2-0 s3-0))
)
(let* ((f0-4 f0-3)
(f1-1 65536.0)
(f2-2 (* 6.28318 (-> s4-0 wheel-radius)))
(f0-5 (* (* f1-1 (/ 1.0 f2-2)) f0-4))
)
(set! (-> s4-0 angle) (+ (-> s4-0 angle) f0-5))
(let* ((f0-3 (vector-dot s2-0 s3-0))
(f1-0 65536.0)
(f2-1 (* 6.28318 (-> s4-0 wheel-radius)))
(f0-4 (* (* f1-0 (/ 1.0 f2-1)) f0-3))
)
(set! (-> s4-0 angle) (+ (-> s4-0 angle) f0-4))
)
)
(quaternion-vector-angle!
(-> arg1 quat)
@@ -1526,4 +1526,100 @@ TEST_F(FormRegressionTest, Method23Trsqv) {
std::string expected =
"(vector-y-angle (vector-! (new 'stack-no-clear 'vector) arg1 (-> arg0 trans)))";
test_with_stack_structures(func, type, expected, R"([[16, "vector"]])");
}
TEST_F(FormRegressionTest, VectorLineDistance) {
std::string func =
"sll r0, r0, 0\n"
"\n"
" daddiu sp, sp, -128\n"
" sd ra, 0(sp)\n"
" sd fp, 8(sp)\n"
" or fp, t9, r0\n"
" sq s4, 80(sp)\n"
" sq s5, 96(sp)\n"
" sq gp, 112(sp)\n"
" or s5, a0, r0\n"
" or s4, a1, r0\n"
" lw t9, vector-normalize!(s7)\n"
" daddiu a0, sp, 16\n"
" sq r0, 0(a0)\n"
" or v1, a2, r0\n"
" or a1, s4, r0\n"
" lqc2 vf4, 0(v1)\n"
" lqc2 vf5, 0(a1)\n"
" vmove.w vf6, vf0\n"
" vsub.xyz vf6, vf4, vf5\n"
" sqc2 vf6, 0(a0)\n"
//" lw a1, L125(fp)\n"
" or a1, r0, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" or a1, v0, r0\n"
" daddiu gp, sp, 32\n"
" sq r0, 0(gp)\n"
" lqc2 vf4, 0(s5)\n"
" lqc2 vf5, 0(s4)\n"
" vmove.w vf6, vf0\n"
" vsub.xyz vf6, vf4, vf5\n"
" sqc2 vf6, 0(gp)\n"
" or a0, a1, r0\n"
" or v1, gp, r0\n"
" lwc1 f0, 0(a0)\n"
" lwc1 f1, 4(a0)\n"
" lwc1 f2, 8(a0)\n"
" lwc1 f3, 0(v1)\n"
" lwc1 f4, 4(v1)\n"
" lwc1 f5, 8(v1)\n"
" mula.s f0, f3\n"
" madda.s f1, f4\n"
" madd.s f0, f2, f5\n"
" mfc1 v1, f0\n"
" mtc1 f0, v1\n"
" lw t9, vector-float*!(s7)\n"
" daddiu a0, sp, 48\n"
" sq r0, 0(a0)\n"
" mfc1 a2, f0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" or v1, v0, r0\n"
" lw t9, vector-length(s7)\n"
" daddiu a0, sp, 64\n"
" sq r0, 0(a0)\n"
" lqc2 vf4, 0(gp)\n"
" lqc2 vf5, 0(v1)\n"
" vmove.w vf6, vf0\n"
" vsub.xyz vf6, vf4, vf5\n"
" sqc2 vf6, 0(a0)\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" ld ra, 0(sp)\n"
" ld fp, 8(sp)\n"
" lq gp, 112(sp)\n"
" lq s5, 96(sp)\n"
" lq s4, 80(sp)\n"
" jr ra\n"
" daddiu sp, sp, 128\n"
" sll r0, r0, 0\n"
" sll r0, r0, 0\n";
std::string type = "(function vector vector vector float)";
std::string expected =
"(let*\n"
" ((a1-3\n"
" (vector-normalize!\n"
" (vector-! (new-stack-vector0) arg2 arg1)\n"
" (the-as float 0)\n"
" )\n"
" )\n"
" (gp-1 (vector-! (new-stack-vector0) arg0 arg1))\n"
" (f0-1 (vector-dot a1-3 gp-1))\n"
" (v1-3 (vector-float*! (new-stack-vector0) a1-3 f0-1))\n"
" )\n"
" (vector-length (vector-! (new-stack-vector0) gp-1 v1-3))\n"
" )";
test_with_stack_structures(func, type, expected,
R"([[16, "vector"], [32, "vector"], [48, "vector"], [64, "vector"]])");
}
+7 -4
View File
@@ -50,6 +50,8 @@ const std::unordered_set<std::string> g_functions_expected_to_reject = {
// matrix
"(method 9 matrix)", // handwritten asm loop
"matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!",
// geometry
"circle-circle-xz-intersect", // unused not bothering
// dma-h
"dma-count-until-done", // dma asm loop
"dma-sync-with-count", "dma-send-no-scratch", "dma-sync-fast",
@@ -117,12 +119,8 @@ const std::unordered_set<std::string> g_functions_to_skip_compiling = {
/// VECTOR-H
"(method 3 vector)", // this function appears twice, which confuses the compiler.
"vector-dot", // fpu acc
"vector4-dot", // fpu acc
// quaternion
"matrix-with-scale->quaternion", // fpu-acc
"(method 3 profile-frame)", // double definition.
// dma-disasm
@@ -143,6 +141,11 @@ const std::unordered_set<std::string> g_functions_to_skip_compiling = {
"vector-deg-diff",
"vector-degi",
// geometry
"calculate-basis-functions-vector!", // asm requiring manual rewrite
"curve-evaluate!", // asm requiring manual rewrite
"point-in-triangle-cross", // logior on floats manual fixup
// asm
"invalidate-cache-line",