mirror of
https://github.com/open-goal/jak-project
synced 2026-07-08 14:36:52 -04:00
[decompiler] ASM Branching Support (#677)
* basic example working in geometry * before updating offline' * clean up * temp * progress
This commit is contained in:
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ enum class FixedOperatorKind {
|
||||
VECTOR_MINUS,
|
||||
VECTOR_FLOAT_PRODUCT,
|
||||
L32_NOT_FALSE_CBOOL,
|
||||
VECTOR_3_DOT,
|
||||
INVALID
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -1001,7 +1001,7 @@
|
||||
(matrix-identity! dst)
|
||||
(label end)
|
||||
)
|
||||
(none)
|
||||
dst
|
||||
)
|
||||
|
||||
(defun matrix-axis-angle! ((dst matrix) (axis vector) (angle-deg float))
|
||||
|
||||
@@ -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!)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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"]])");
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
|
||||
Vendored
+1
-1
Submodule third-party/googletest updated: adeef19294...7153098229
Reference in New Issue
Block a user