[Decompiler] Put likely delay slots in their own block (#237)

* first part of fix

* atomic op conversion fix

* update tests

* oops
This commit is contained in:
water111 2021-02-06 17:04:03 -05:00 committed by GitHub
parent 5b6a8dcf98
commit f8b63a3f92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 669 additions and 383 deletions

View file

@ -23,7 +23,7 @@ std::vector<BasicBlock> find_blocks_in_function(const LinkedObjectFile& file,
const auto& instr = func.instructions.at(i); const auto& instr = func.instructions.at(i);
const auto& instr_info = instr.get_info(); const auto& instr_info = instr.get_info();
if (instr_info.is_branch || instr_info.is_branch_likely) { if (instr_info.is_branch && !instr_info.is_branch_likely) {
// make sure the delay slot of this branch is included in the function // make sure the delay slot of this branch is included in the function
assert(i + func.start_word < func.end_word - 1); assert(i + func.start_word < func.end_word - 1);
// divider after delay slot // divider after delay slot
@ -37,6 +37,24 @@ std::vector<BasicBlock> find_blocks_in_function(const LinkedObjectFile& file,
assert(label.offset / 4 < func.end_word - 1); assert(label.offset / 4 < func.end_word - 1);
dividers.push_back(label.offset / 4 - func.start_word); dividers.push_back(label.offset / 4 - func.start_word);
} }
// for branch likely, we treat the likely instruction as a separate block.
if (instr_info.is_branch_likely) {
assert(i + func.start_word < func.end_word - 1);
// divider after branch instruction
dividers.push_back(i + 1);
// divider after likely delay slot.
dividers.push_back(i + 2);
// divider at the destination.
auto label_id = instr.get_label_target();
assert(label_id != -1);
const auto& label = file.labels.at(label_id);
// should only jump to within our own function
assert(label.target_segment == seg);
assert(label.offset / 4 > func.start_word);
assert(label.offset / 4 < func.end_word - 1);
dividers.push_back(label.offset / 4 - func.start_word);
}
} }
std::sort(dividers.begin(), dividers.end()); std::sort(dividers.begin(), dividers.end());

View file

@ -255,7 +255,11 @@ goos::Object ShortCircuit::to_form() const {
std::vector<goos::Object> forms; std::vector<goos::Object> forms;
forms.push_back(pretty_print::to_symbol("sc")); forms.push_back(pretty_print::to_symbol("sc"));
for (const auto& x : entries) { for (const auto& x : entries) {
forms.push_back(x->to_form()); if (x.likely_delay) {
forms.push_back(pretty_print::build_list(x.condition->to_form(), x.likely_delay->to_form()));
} else {
forms.push_back(x.condition->to_form());
}
} }
return pretty_print::build_list(forms); return pretty_print::build_list(forms);
} }
@ -384,105 +388,6 @@ std::string ControlFlowGraph::to_form_string() {
return pretty_print::to_string(to_form()); return pretty_print::to_string(to_form());
} }
// bool ControlFlowGraph::compact_top_level() {
// int compact_count = 0;
//
// std::string orig = to_dot();
//
// while (compact_one_in_top_level()) {
// compact_count++;
// }
//
// if (compact_count) {
// printf("%s\nCHANGED TO\n%s\n", orig.c_str(), to_dot().c_str());
// return true;
// }
//
// return false;
//}
//
// bool ControlFlowGraph::compact_one_in_top_level() {
// for (auto* node : m_node_pool) {
// if (node->parent_container) {
// continue;
// }
//
// if (node != entry() && node->succ.size() == 1 && !node->has_succ(exit()) &&
// node->succ.front()->pred.size() == 1 && !node->succ.front()->has_succ(node)) {
// // can compact!
// auto first = node;
// auto second = node->succ.front();
// assert(second->has_pred(first));
//
// make_sequence(first, second);
// return true;
// }
// }
//
// return false;
//}
//
// bool ControlFlowGraph::is_if_else(CfgVtx* b0, CfgVtx* b1, CfgVtx* b2, CfgVtx* b3) {
// // check existance
// if (!b0 || !b1 || !b2 || !b3)
// return false;
//
// // check verts
// if (b0->next != b1)
// return false;
// if (b0->succ_ft != b1)
// return false;
// if (b0->succ_branch != b2)
// return false;
// if (b0->end_branch.branch_always)
// return false;
// if (b0->end_branch.branch_likely)
// return false;
// assert(b0->end_branch.has_branch);
// // b0 prev, pred don't care
//
// if (b1->prev != b0)
// return false;
// if (!b1->has_pred(b0))
// return false;
// if (b1->pred.size() != 1)
// return false;
// if (b1->next != b2)
// return false;
// if (b1->succ_ft)
// return false;
// if (b1->succ_branch != b3)
// return false;
// assert(b1->end_branch.branch_always);
// assert(b1->end_branch.has_branch);
// if (b1->end_branch.branch_likely)
// return false;
//
// if (b2->prev != b1)
// return false;
// if (!b2->has_pred(b0))
// return false;
// if (b2->pred.size() != 1)
// return false;
// if (b2->next != b3)
// return false;
// if (b2->succ_branch)
// return false;
// assert(!b2->end_branch.has_branch);
// if (b2->succ_ft != b3)
// return false;
//
// if (b3->prev != b2)
// return false;
// if (!b3->has_pred(b2))
// return false;
// if (!b3->has_pred(b1))
// return false;
//
// return true;
//}
//
bool ControlFlowGraph::is_while_loop(CfgVtx* b0, CfgVtx* b1, CfgVtx* b2) { bool ControlFlowGraph::is_while_loop(CfgVtx* b0, CfgVtx* b1, CfgVtx* b2) {
// todo - check delay slots! // todo - check delay slots!
if (!b0 || !b1 || !b2) if (!b0 || !b1 || !b2)
@ -635,78 +540,6 @@ bool ControlFlowGraph::is_goto_end_and_unreachable(CfgVtx* b0, CfgVtx* b1) {
return true; // match! return true; // match!
} }
/*
bool ControlFlowGraph::find_if_else_top_level() {
// todo check delay slots
// Example:
// B0:
// beq s7, v1, B2 ;; inverted branch condition (branch on condition not met)
// sll r0, r0, 0 ;; nop in delay slot
// B1:
// true case!
// beq r0, r0, B3 ;; unconditional branch
// sll r0, r0, 0 ;; nop in delay slot
// B2:
// false case! ;; fall through
// B3:
// rest of code
bool found_one = false;
bool needs_work = true;
while (needs_work) {
needs_work = false; // until we change something, assume we're done.
for_each_top_level_vtx([&](CfgVtx* vtx) {
// return true means "try again with a different vertex"
// return false means "I changed something, bail out so we can start from the beginning
// again."
// attempt to match b0, b1, b2, b3
auto* b0 = vtx;
auto* b1 = vtx->succ_ft;
auto* b2 = vtx->succ_branch;
auto* b3 = b2 ? b2->succ_ft : nullptr;
if (is_if_else(b0, b1, b2, b3)) {
needs_work = true;
// create the new vertex!
auto* new_vtx = alloc<IfElseVtx>();
new_vtx->condition = b0;
new_vtx->true_case = b1;
new_vtx->false_case = b2;
// link new vertex pred
for (auto* new_pred : b0->pred) {
new_pred->replace_succ_and_check(b0, new_vtx);
}
new_vtx->pred = b0->pred;
// link new vertex succ
b3->replace_preds_with_and_check({b1, b2}, new_vtx);
new_vtx->succ_ft = b3;
// setup next/prev
new_vtx->prev = b0->prev;
if (new_vtx->prev) {
new_vtx->prev->next = new_vtx;
}
new_vtx->next = b3;
b3->prev = new_vtx;
b0->parent_claim(new_vtx);
b1->parent_claim(new_vtx);
b2->parent_claim(new_vtx);
found_one = true;
return false;
} else {
return true; // try again!
}
});
}
return found_one;
}
*/
bool ControlFlowGraph::find_while_loop_top_level() { bool ControlFlowGraph::find_while_loop_top_level() {
// B0 can start with whatever // B0 can start with whatever
// B0 ends in unconditional branch to B2 (condition). // B0 ends in unconditional branch to B2 (condition).
@ -1010,8 +843,10 @@ bool ControlFlowGraph::find_goto_not_end() {
bool ControlFlowGraph::is_sequence(CfgVtx* b0, CfgVtx* b1) { bool ControlFlowGraph::is_sequence(CfgVtx* b0, CfgVtx* b1) {
if (!b0 || !b1) if (!b0 || !b1)
return false; return false;
if (b0->next != b1)
if (b0->next != b1) {
return false; return false;
}
if (b0->succ_ft != b1) { if (b0->succ_ft != b1) {
// may unconditionally branch to get to a loop. // may unconditionally branch to get to a loop.
if (b0->succ_branch != b1) if (b0->succ_branch != b1)
@ -1034,7 +869,6 @@ bool ControlFlowGraph::is_sequence(CfgVtx* b0, CfgVtx* b1) {
return false; return false;
if (b1->succ_branch == b0) if (b1->succ_branch == b0)
return false; return false;
return true; return true;
} }
@ -1089,12 +923,7 @@ bool ControlFlowGraph::find_seq_top_level() {
auto* b0 = vtx; auto* b0 = vtx;
auto* b1 = vtx->next; auto* b1 = vtx->next;
// if (b0 && b1) {
// printf("try seq %s %s\n", b0->to_string().c_str(), b1->to_string().c_str());
// }
if (is_sequence_of_non_sequences(b0, b1)) { // todo, avoid nesting sequences. if (is_sequence_of_non_sequences(b0, b1)) { // todo, avoid nesting sequences.
// printf("make seq type 1 %s %s\n", b0->to_string().c_str(), b1->to_string().c_str());
replaced = true; replaced = true;
auto* new_seq = alloc<SequenceVtx>(); auto* new_seq = alloc<SequenceVtx>();
@ -1622,63 +1451,118 @@ bool ControlFlowGraph::find_short_circuits() {
bool found = false; bool found = false;
for_each_top_level_vtx([&](CfgVtx* vtx) { for_each_top_level_vtx([&](CfgVtx* vtx) {
std::vector<CfgVtx*> entries = {vtx}; std::vector<ShortCircuit::Entry> entries;
auto* end = vtx->succ_branch; if (!vtx->next) {
auto* next = vtx->next;
// printf("try sc @ %s\n", vtx->to_string().c_str());
if (!end || !vtx->end_branch.branch_likely || next != vtx->succ_ft) {
// printf("reject 1\n");
return true; return true;
} }
// set up the first entry:
ShortCircuit::Entry candidate = {vtx, vtx->next};
CfgVtx* end = vtx->next->succ_branch;
// fmt::print("Starting loop\n");
while (true) { while (true) {
// printf("loop sc %s, end %s\n", vtx->to_string().c_str(), end->to_string().c_str()); // check candidate:
if (next == end) { if (!candidate.condition || !candidate.likely_delay || !end) {
// one entry sc! // fmt::print("reject begin {} {} {}\n", !!candidate.condition,
break; // !!candidate.likely_delay,
// !!end);
return true;
} }
if (next->next == end) { // fmt::print(" try {} and {} end {}\n", candidate.condition->to_string(),
// check 1 pred // candidate.likely_delay->to_string(), end->to_string());
if (next->pred.size() != 1) {
// printf("reject 2\n"); if (candidate.condition->next == end) {
return true; if (!entries.empty()) {
if (candidate.condition->end_branch.has_branch) {
// should fall through to the end.
return true;
}
candidate.likely_delay = nullptr;
entries.push_back(candidate);
// fmt::print("all done!");
break;
} }
entries.push_back(next); }
// done! if (!candidate.condition->next || !candidate.condition->succ_branch) {
// fmt::print(" fail 0 {}, {}\n", !!candidate.condition->next,
// !!candidate.condition->succ_branch);
return true;
}
// root -> slot
if (!candidate.condition->next ||
candidate.condition->next != candidate.condition->succ_branch ||
!candidate.condition->end_branch.branch_likely ||
candidate.condition->end_branch.kind != CfgVtx::DelaySlotKind::NO_DELAY) {
// fmt::print(" fail 1 {} {} {} {}\n", !candidate.condition->next,
// candidate.condition->next != candidate.condition->succ_branch,
// !candidate.condition->end_branch.branch_likely,
// candidate.condition->end_branch.kind !=
// CfgVtx::DelaySlotKind::NO_DELAY);
// fmt::print(" fail 1 condition->next {}, condition->succ_branch {}\n",
// candidate.condition->next->to_string(),
// candidate.condition->succ_branch->to_string());
return true;
}
if (entries.empty() && candidate.likely_delay->next == end) {
entries.push_back(candidate);
// fmt::print("all don2!");
break; break;
} }
// check 1 pred if (candidate.likely_delay->pred.size() != 1 || candidate.likely_delay->succ_ft ||
if (next->pred.size() != 1) { !candidate.likely_delay->succ_branch || !candidate.likely_delay->end_branch.has_branch ||
// printf("reject 3\n"); !candidate.likely_delay->end_branch.branch_always ||
candidate.likely_delay->end_branch.branch_likely ||
candidate.likely_delay->end_branch.kind != CfgVtx::DelaySlotKind::NO_DELAY) {
// fmt::print(" fail 2 {} {} {} {} {} {} {}\n", candidate.likely_delay->pred.size()
// != 1,
// !!candidate.likely_delay->succ_ft,
// !candidate.likely_delay->succ_branch,
// !candidate.likely_delay->end_branch.has_branch,
// !candidate.likely_delay->end_branch.branch_always,
// !!candidate.likely_delay->end_branch.branch_likely,
// candidate.likely_delay->end_branch.kind !=
// CfgVtx::DelaySlotKind::NO_DELAY);
// fmt::print("delay {} has ft {}\n", candidate.likely_delay->to_string(),
// candidate.likely_delay->succ_ft->to_string());
return true; return true;
} }
// check branch to end // slot -> end
if (next->succ_branch != end || !next->end_branch.branch_likely) { if (candidate.likely_delay->succ_branch != end) {
// printf("reject 4\n"); // fmt::print(" fail 3\n");
return true; return true;
} }
// check fallthrough to next // root -> next root
if (!next->succ_ft) { if (!candidate.condition->next->next ||
// printf("reject 5\n"); candidate.condition->next->next != candidate.condition->succ_ft) {
// fmt::print(" fail 4\n");
return true; return true;
} }
assert(next->succ_ft == next->next); // bonus check // add candidate:
entries.push_back(next); entries.push_back(candidate);
next = next->succ_ft; auto next_root = candidate.condition->next->next;
auto next_slot = next_root->next;
candidate = {next_root, next_slot};
// pre next root check
if (next_root->pred.size() != 1) {
// fmt::print(" fail 5\n");
return true;
}
// fmt::print("on to next!\n");
} }
// printf("got sc: \n");
// for (auto* x : entries) {
// printf(" %s\n", x->to_string().c_str());
// }
auto new_sc = alloc<ShortCircuit>(); auto new_sc = alloc<ShortCircuit>();
for (auto* npred : vtx->pred) { for (auto* npred : vtx->pred) {
@ -1690,13 +1574,29 @@ bool ControlFlowGraph::find_short_circuits() {
new_sc->prev->next = new_sc; new_sc->prev->next = new_sc;
} }
end->replace_preds_with_and_check(entries, new_sc); std::vector<CfgVtx*> end_preds;
for (auto& e : entries) {
if (e.likely_delay) {
end_preds.push_back(e.likely_delay);
} else {
end_preds.push_back(e.condition);
}
}
if (entries.size() == 1) {
end_preds.push_back(entries.front().condition);
}
end->replace_preds_with_and_check(end_preds, new_sc);
new_sc->succ_ft = end; new_sc->succ_ft = end;
new_sc->next = end; new_sc->next = end;
end->prev = new_sc; end->prev = new_sc;
new_sc->entries = std::move(entries); new_sc->entries = std::move(entries);
for (auto* x : new_sc->entries) { for (auto& x : new_sc->entries) {
x->parent_claim(new_sc); x.condition->parent_claim(new_sc);
if (x.likely_delay) {
x.likely_delay->parent_claim(new_sc);
}
} }
found = true; found = true;
@ -1772,6 +1672,26 @@ void ControlFlowGraph::link_branch(BlockVtx* first,
} }
} }
void ControlFlowGraph::link_fall_through_likely(BlockVtx* first,
BlockVtx* second,
std::vector<BasicBlock>& blocks) {
assert(!first->succ_ft); // don't want to overwrite something by accident.
// can only fall through to the next code in memory.
assert(first->next->next == second);
assert(second->prev->prev == first);
first->succ_ft = second;
assert(blocks.at(first->block_id).succ_ft == -1);
blocks.at(first->block_id).succ_ft = second->block_id;
if (!second->has_pred(first)) {
// if a block can block fall through and branch to the same block, we want to avoid adding
// it as a pred twice. This is rare, but does happen and makes sense with likely branches
// which only run the delay slot when taken.
second->pred.push_back(first);
blocks.at(second->block_id).pred.push_back(first->block_id);
}
}
void ControlFlowGraph::flag_early_exit(const std::vector<BasicBlock>& blocks) { void ControlFlowGraph::flag_early_exit(const std::vector<BasicBlock>& blocks) {
auto* b = m_blocks.back(); auto* b = m_blocks.back();
const auto& block = blocks.at(b->block_id); const auto& block = blocks.at(b->block_id);
@ -1799,6 +1719,7 @@ CfgVtx::DelaySlotKind get_delay_slot(const Instruction& i) {
* Build and resolve a Control Flow Graph as much as possible. * Build and resolve a Control Flow Graph as much as possible.
*/ */
std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int seg, Function& func) { std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int seg, Function& func) {
// fmt::print("START {}\n", func.guessed_name.to_string());
auto cfg = std::make_shared<ControlFlowGraph>(); auto cfg = std::make_shared<ControlFlowGraph>();
const auto& blocks = cfg->create_blocks(func.basic_blocks.size()); const auto& blocks = cfg->create_blocks(func.basic_blocks.size());
@ -1816,39 +1737,39 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
// set up succ / pred // set up succ / pred
for (int i = 0; i < int(func.basic_blocks.size()); i++) { for (int i = 0; i < int(func.basic_blocks.size()); i++) {
auto& b = func.basic_blocks[i]; auto& b = func.basic_blocks[i];
if (blocks.at(i)->end_branch.branch_always) {
// already set.
continue;
}
bool not_last = (i + 1) < int(func.basic_blocks.size()); bool not_last = (i + 1) < int(func.basic_blocks.size());
if (b.end_word - b.start_word < 2) { if (b.end_word == b.start_word) {
// there's no room for a branch here, fall through to the end // there's no room for a branch here, fall through to the end
if (not_last) { if (not_last) {
cfg->link_fall_through(blocks.at(i), blocks.at(i + 1), func.basic_blocks); cfg->link_fall_through(blocks.at(i), blocks.at(i + 1), func.basic_blocks);
} }
} else { } else {
// might be a branch // room for at least a likely branch, try that first.
int idx = b.end_word - 2; int likely_branch_idx = b.end_word - 1;
assert(idx >= b.start_word); assert(likely_branch_idx >= b.start_word);
auto& branch_candidate = func.instructions.at(idx); auto& likely_branch_candidate = func.instructions.at(likely_branch_idx);
auto& delay_slot_candidate = func.instructions.at(idx + 1);
if (is_branch(branch_candidate, {})) { if (is_branch(likely_branch_candidate, true)) {
// is a likely branch
blocks.at(i)->end_branch.has_branch = true; blocks.at(i)->end_branch.has_branch = true;
blocks.at(i)->end_branch.branch_likely = is_branch(branch_candidate, true); blocks.at(i)->end_branch.branch_likely = true;
blocks.at(i)->end_branch.kind = get_delay_slot(delay_slot_candidate); blocks.at(i)->end_branch.kind = CfgVtx::DelaySlotKind::NO_DELAY;
bool branch_always = is_always_branch(branch_candidate); bool branch_always = is_always_branch(likely_branch_candidate);
// need to find block target // need to find block target
int block_target = -1; int block_target = -1;
int label_target = branch_candidate.get_label_target(); int label_target = likely_branch_candidate.get_label_target();
assert(label_target != -1); assert(label_target != -1);
const auto& label = file.labels.at(label_target); const auto& label = file.labels.at(label_target);
assert(label.target_segment == seg); assert(label.target_segment == seg);
assert((label.offset % 4) == 0); assert((label.offset % 4) == 0);
int offset = label.offset / 4 - func.start_word; int offset = label.offset / 4 - func.start_word;
assert(offset >= 0); assert(offset >= 0);
// the order here matters when there are zero size blocks. Unclear what the best answer is.
// i think in end it doesn't actually matter??
// for (int j = 0; j < int(func.basic_blocks.size()); j++) {
for (int j = int(func.basic_blocks.size()); j-- > 0;) { for (int j = int(func.basic_blocks.size()); j-- > 0;) {
if (func.basic_blocks[j].start_word == offset) { if (func.basic_blocks[j].start_word == offset) {
block_target = j; block_target = j;
@ -1857,7 +1778,8 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
} }
assert(block_target != -1); assert(block_target != -1);
cfg->link_branch(blocks.at(i), blocks.at(block_target), func.basic_blocks); // branch to delay slot
cfg->link_branch(blocks.at(i), blocks.at(i + 1), func.basic_blocks);
if (branch_always) { if (branch_always) {
// don't continue to the next one // don't continue to the next one
@ -1865,13 +1787,75 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
} else { } else {
// not an always branch // not an always branch
if (not_last) { if (not_last) {
cfg->link_fall_through(blocks.at(i), blocks.at(i + 1), func.basic_blocks); // don't take the delay slot.
cfg->link_fall_through_likely(blocks.at(i), blocks.at(i + 2), func.basic_blocks);
} }
} }
auto& delay_block = blocks.at(i + 1);
delay_block->end_branch.branch_likely = false;
delay_block->end_branch.branch_always = true;
delay_block->end_branch.has_branch = true;
delay_block->end_branch.kind = CfgVtx::DelaySlotKind::NO_DELAY;
cfg->link_branch(blocks.at(i + 1), blocks.at(block_target), func.basic_blocks);
// printf("SC block target is %d\n", block_target);
} else { } else {
// not a branch at all if (b.end_word - b.start_word < 2) {
if (not_last) { // no room for a branch, just fall through
cfg->link_fall_through(blocks.at(i), blocks.at(i + 1), func.basic_blocks); if (not_last) {
cfg->link_fall_through(blocks.at(i), blocks.at(i + 1), func.basic_blocks);
}
} else {
// try as a normal branch.
int idx = b.end_word - 2;
assert(idx >= b.start_word);
auto& branch_candidate = func.instructions.at(idx);
auto& delay_slot_candidate = func.instructions.at(idx + 1);
if (is_branch(branch_candidate, false)) {
blocks.at(i)->end_branch.has_branch = true;
blocks.at(i)->end_branch.branch_likely = false;
blocks.at(i)->end_branch.kind = get_delay_slot(delay_slot_candidate);
bool branch_always = is_always_branch(branch_candidate);
// need to find block target
int block_target = -1;
int label_target = branch_candidate.get_label_target();
assert(label_target != -1);
const auto& label = file.labels.at(label_target);
assert(label.target_segment == seg);
assert((label.offset % 4) == 0);
int offset = label.offset / 4 - func.start_word;
assert(offset >= 0);
// the order here matters when there are zero size blocks. Unclear what the best answer
// is.
// i think in end it doesn't actually matter??
// for (int j = 0; j < int(func.basic_blocks.size()); j++) {
for (int j = int(func.basic_blocks.size()); j-- > 0;) {
if (func.basic_blocks[j].start_word == offset) {
block_target = j;
break;
}
}
assert(block_target != -1);
cfg->link_branch(blocks.at(i), blocks.at(block_target), func.basic_blocks);
if (branch_always) {
// don't continue to the next one
blocks.at(i)->end_branch.branch_always = true;
} else {
// not an always branch
if (not_last) {
cfg->link_fall_through(blocks.at(i), blocks.at(i + 1), func.basic_blocks);
}
}
} else {
// not a branch.
if (not_last) {
cfg->link_fall_through(blocks.at(i), blocks.at(i + 1), func.basic_blocks);
}
}
} }
} }
} }

View file

@ -77,7 +77,7 @@ class CfgVtx {
std::vector<CfgVtx*> pred; // all vertices which have us as succ_branch or succ_ft std::vector<CfgVtx*> pred; // all vertices which have us as succ_branch or succ_ft
int uid = -1; int uid = -1;
enum class DelaySlotKind { NO_BRANCH, SET_REG_FALSE, SET_REG_TRUE, NOP, OTHER }; enum class DelaySlotKind { NO_BRANCH, SET_REG_FALSE, SET_REG_TRUE, NOP, OTHER, NO_DELAY };
struct { struct {
bool has_branch = false; // does the block end in a branch (any kind)? bool has_branch = false; // does the block end in a branch (any kind)?
@ -239,7 +239,11 @@ class ShortCircuit : public CfgVtx {
public: public:
std::string to_string() const override; std::string to_string() const override;
goos::Object to_form() const override; goos::Object to_form() const override;
std::vector<CfgVtx*> entries; struct Entry {
CfgVtx* condition = nullptr;
CfgVtx* likely_delay = nullptr; // will be nullptr on last case
};
std::vector<Entry> entries;
}; };
class InfiniteLoopBlock : public CfgVtx { class InfiniteLoopBlock : public CfgVtx {
@ -287,6 +291,7 @@ class ControlFlowGraph {
const std::vector<BlockVtx*>& create_blocks(int count); const std::vector<BlockVtx*>& create_blocks(int count);
void link_fall_through(BlockVtx* first, BlockVtx* second, std::vector<BasicBlock>& blocks); void link_fall_through(BlockVtx* first, BlockVtx* second, std::vector<BasicBlock>& blocks);
void link_fall_through_likely(BlockVtx* first, BlockVtx* second, std::vector<BasicBlock>& blocks);
void link_branch(BlockVtx* first, BlockVtx* second, std::vector<BasicBlock>& blocks); void link_branch(BlockVtx* first, BlockVtx* second, std::vector<BasicBlock>& blocks);
bool find_cond_w_else(); bool find_cond_w_else();
bool find_cond_n_else(); bool find_cond_n_else();

View file

@ -954,7 +954,7 @@ void LoadVarOp::collect_vars(VariableSet& vars) const {
///////////////////////////// /////////////////////////////
IR2_BranchDelay::IR2_BranchDelay(Kind kind) : m_kind(kind) { IR2_BranchDelay::IR2_BranchDelay(Kind kind) : m_kind(kind) {
assert(m_kind == Kind::NOP); assert(m_kind == Kind::NOP || m_kind == Kind::NO_DELAY);
} }
IR2_BranchDelay::IR2_BranchDelay(Kind kind, Variable var0) : m_kind(kind) { IR2_BranchDelay::IR2_BranchDelay(Kind kind, Variable var0) : m_kind(kind) {
@ -989,6 +989,8 @@ goos::Object IR2_BranchDelay::to_form(const std::vector<DecompilerLabel>& labels
switch (m_kind) { switch (m_kind) {
case Kind::NOP: case Kind::NOP:
return pretty_print::build_list("nop!"); return pretty_print::build_list("nop!");
case Kind::NO_DELAY:
return pretty_print::build_list("no-delay!");
case Kind::SET_REG_FALSE: case Kind::SET_REG_FALSE:
assert(m_var[0].has_value()); assert(m_var[0].has_value());
return pretty_print::build_list("set!", m_var[0]->to_form(env), "#f"); return pretty_print::build_list("set!", m_var[0]->to_form(env), "#f");
@ -1034,6 +1036,7 @@ bool IR2_BranchDelay::operator==(const IR2_BranchDelay& other) const {
void IR2_BranchDelay::get_regs(std::vector<Register>* write, std::vector<Register>* read) const { void IR2_BranchDelay::get_regs(std::vector<Register>* write, std::vector<Register>* read) const {
switch (m_kind) { switch (m_kind) {
case Kind::NOP: case Kind::NOP:
case Kind::NO_DELAY:
break; break;
case Kind::SET_REG_FALSE: case Kind::SET_REG_FALSE:
case Kind::SET_REG_TRUE: case Kind::SET_REG_TRUE:

View file

@ -263,6 +263,8 @@ class SetVarOp : public AtomicOp {
const Env& env, const Env& env,
DecompilerTypeSystem& dts) override; DecompilerTypeSystem& dts) override;
void collect_vars(VariableSet& vars) const override; void collect_vars(VariableSet& vars) const override;
const Variable& dst() const { return m_dst; }
const SimpleExpression& src() const { return m_src; }
private: private:
Variable m_dst; Variable m_dst;
@ -464,6 +466,7 @@ class IR2_BranchDelay {
SET_PAIR, SET_PAIR,
DSLLV, DSLLV,
NEGATE, NEGATE,
NO_DELAY,
UNKNOWN UNKNOWN
}; };

View file

@ -379,6 +379,7 @@ TypeState IR2_BranchDelay::propagate_types(const TypeState& input,
output.get(m_var[0]->reg()) = TP_Type::make_type_object(TypeSpec("pair")); output.get(m_var[0]->reg()) = TP_Type::make_type_object(TypeSpec("pair"));
break; break;
case Kind::NOP: case Kind::NOP:
case Kind::NO_DELAY:
break; break;
default: default:
throw std::runtime_error("Unhandled branch delay in type_prop: " + throw std::runtime_error("Unhandled branch delay in type_prop: " +

View file

@ -38,6 +38,11 @@ class Env {
return m_reg_use; return m_reg_use;
} }
RegUsageInfo& reg_use() {
assert(m_has_reg_use);
return m_reg_use;
}
goos::Object get_variable_name(Register reg, int atomic_idx, VariableMode mode) const; goos::Object get_variable_name(Register reg, int atomic_idx, VariableMode mode) const;
/*! /*!

View file

@ -492,6 +492,7 @@ class ShortCircuitElement : public FormElement {
public: public:
struct Entry { struct Entry {
Form* condition = nullptr; Form* condition = nullptr;
std::optional<SetVarOp> branch_delay;
// in the case where there's no else, each delay slot will write #f to the "output" register. // in the case where there's no else, each delay slot will write #f to the "output" register.
// this can be with an or <output>, s7, r0 // this can be with an or <output>, s7, r0
// Form* output = nullptr; // todo, what? add to collect vars if we need it? // Form* output = nullptr; // todo, what? add to collect vars if we need it?

View file

@ -187,12 +187,14 @@ void ObjectFileDB::ir2_basic_block_pass() {
// run analysis // run analysis
// build a control flow graph, just looking at branch instructions. // build a control flow graph, just looking at branch instructions.
// if (func.guessed_name.to_string() == "abs") {
func.cfg = build_cfg(data.linked_data, segment_id, func); func.cfg = build_cfg(data.linked_data, segment_id, func);
if (!func.cfg->is_fully_resolved()) { if (!func.cfg->is_fully_resolved()) {
lg::warn("Function {} from {} failed to build control flow graph!", lg::warn("Function {} from {} failed to build control flow graph!",
func.guessed_name.to_string(), data.to_unique_name()); func.guessed_name.to_string(), data.to_unique_name());
failed_to_build_cfg++; failed_to_build_cfg++;
} }
// }
// if we got an inspect method, inspect it. // if we got an inspect method, inspect it.
if (func.is_inspect_method) { if (func.is_inspect_method) {
@ -604,7 +606,8 @@ std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function&
auto& op = func.get_atomic_op_at_instr(instr_id); auto& op = func.get_atomic_op_at_instr(instr_id);
op_id = func.ir2.atomic_ops->instruction_to_atomic_op.at(instr_id); op_id = func.ir2.atomic_ops->instruction_to_atomic_op.at(instr_id);
append_commented(line, printed_comment, append_commented(line, printed_comment,
op.to_form(data.linked_data.labels, func.ir2.env).print()); op.to_form(data.linked_data.labels, func.ir2.env).print() + "[" +
std::to_string(op_id) + "]");
if (func.ir2.env.has_type_analysis()) { if (func.ir2.env.has_type_analysis()) {
append_commented( append_commented(
@ -650,6 +653,16 @@ std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function&
print_instr_end(i); print_instr_end(i);
} }
if (func.cfg) {
result += func.cfg->to_form_string();
if (!func.cfg->is_fully_resolved()) {
result += "\n";
result += func.cfg->to_dot();
result += "\n";
}
}
result += "\n"; result += "\n";
assert(total_instructions_printed == (func.end_word - func.start_word - 1)); assert(total_instructions_printed == (func.end_word - func.start_word - 1));

View file

@ -371,6 +371,7 @@ std::unique_ptr<AtomicOp> make_branch(const IR2_Condition& condition,
bool likely, bool likely,
int dest_label, int dest_label,
int my_idx) { int my_idx) {
assert(!likely);
auto branch_delay = get_branch_delay(delay, my_idx); auto branch_delay = get_branch_delay(delay, my_idx);
if (branch_delay.is_known()) { if (branch_delay.is_known()) {
return std::make_unique<BranchOp>(likely, condition, dest_label, branch_delay, my_idx); return std::make_unique<BranchOp>(likely, condition, dest_label, branch_delay, my_idx);
@ -379,6 +380,15 @@ std::unique_ptr<AtomicOp> make_branch(const IR2_Condition& condition,
} }
} }
std::unique_ptr<AtomicOp> make_branch_no_delay(const IR2_Condition& condition,
bool likely,
int dest_label,
int my_idx) {
assert(likely);
IR2_BranchDelay delay(IR2_BranchDelay::Kind::NO_DELAY);
return std::make_unique<BranchOp>(likely, condition, dest_label, delay, my_idx);
}
/////////////////////// ///////////////////////
// OP 1 Conversions // OP 1 Conversions
////////////////////// //////////////////////
@ -615,6 +625,63 @@ std::unique_ptr<AtomicOp> convert_dsrl32_1(const Instruction& i0, int idx) {
return make_2reg_1imm_op(i0, SimpleExpression::Kind::RIGHT_SHIFT_LOGIC, idx, 32); return make_2reg_1imm_op(i0, SimpleExpression::Kind::RIGHT_SHIFT_LOGIC, idx, 32);
} }
std::unique_ptr<AtomicOp> convert_likely_branch_1(const Instruction& i0,
IR2_Condition::Kind kind,
bool likely,
int idx) {
return make_branch_no_delay(IR2_Condition(kind, make_src_atom(i0.get_src(0).get_reg(), idx)),
likely, i0.get_src(1).get_label(), idx);
}
std::unique_ptr<AtomicOp> convert_beql_1(const Instruction& i0, int idx, bool likely) {
auto s0 = i0.get_src(0).get_reg();
auto s1 = i0.get_src(1).get_reg();
auto dest = i0.get_src(2).get_label();
IR2_Condition condition;
if (s0 == rr0() && s1 == rr0()) {
condition = IR2_Condition(IR2_Condition::Kind::ALWAYS);
} else if (s1 == rr0()) {
condition = IR2_Condition(IR2_Condition::Kind::ZERO, make_src_atom(s0, idx));
} else if (i0.get_src(0).is_reg(rs7())) {
if (s1 == rs7()) {
// (if #f ...) type code?
condition = IR2_Condition(IR2_Condition::Kind::FALSE, SimpleAtom::make_sym_ptr("#f"));
} else {
condition = IR2_Condition(IR2_Condition::Kind::FALSE, make_src_atom(s1, idx));
}
} else if (s1 == rs7()) {
// likely a case where somebody wrote (= x #f) or (!= x #f). much rarer than the flipped one
condition = IR2_Condition(IR2_Condition::Kind::EQUAL, make_src_atom(s0, idx),
SimpleAtom::make_sym_ptr("#f"));
} else {
condition =
IR2_Condition(IR2_Condition::Kind::EQUAL, make_src_atom(s0, idx), make_src_atom(s1, idx));
condition.make_flipped();
}
return make_branch_no_delay(condition, likely, dest, idx);
}
std::unique_ptr<AtomicOp> convert_bnel_1(const Instruction& i0, int idx, bool likely) {
auto s0 = i0.get_src(0).get_reg();
auto s1 = i0.get_src(1).get_reg();
auto dest = i0.get_src(2).get_label();
IR2_Condition condition;
if (s1 == rr0()) {
condition = IR2_Condition(IR2_Condition::Kind::NONZERO, make_src_atom(s0, idx));
} else if (i0.get_src(0).is_reg(rs7())) {
condition = IR2_Condition(IR2_Condition::Kind::TRUTHY, make_src_atom(s1, idx));
} else if (s1 == rs7()) {
// likely a case where somebody wrote (= x #f) or (!= x #f). much rarer than the flipped one
condition = IR2_Condition(IR2_Condition::Kind::NOT_EQUAL, make_src_atom(s0, idx),
SimpleAtom::make_sym_ptr("#f"));
} else {
condition = IR2_Condition(IR2_Condition::Kind::NOT_EQUAL, make_src_atom(s0, idx),
make_src_atom(s1, idx));
condition.make_flipped();
}
return make_branch_no_delay(condition, likely, dest, idx);
}
std::unique_ptr<AtomicOp> convert_1(const Instruction& i0, int idx) { std::unique_ptr<AtomicOp> convert_1(const Instruction& i0, int idx) {
switch (i0.kind) { switch (i0.kind) {
case InstructionKind::OR: case InstructionKind::OR:
@ -722,6 +789,16 @@ std::unique_ptr<AtomicOp> convert_1(const Instruction& i0, int idx) {
case InstructionKind::MOVN: case InstructionKind::MOVN:
case InstructionKind::MOVZ: case InstructionKind::MOVZ:
return convert_cmov_1(i0, idx); return convert_cmov_1(i0, idx);
case InstructionKind::BGTZL:
return convert_likely_branch_1(i0, IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED, true, idx);
case InstructionKind::BGEZL:
return convert_likely_branch_1(i0, IR2_Condition::Kind::GEQ_ZERO_SIGNED, true, idx);
case InstructionKind::BLTZL:
return convert_likely_branch_1(i0, IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED, true, idx);
case InstructionKind::BEQL:
return convert_beql_1(i0, idx, true);
case InstructionKind::BNEL:
return convert_bnel_1(i0, idx, true);
default: default:
return nullptr; return nullptr;
} }
@ -815,15 +892,6 @@ std::unique_ptr<AtomicOp> convert_beq_2(const Instruction& i0,
return make_branch(condition, i1, likely, dest, idx); return make_branch(condition, i1, likely, dest, idx);
} }
std::unique_ptr<AtomicOp> convert_branch_r1_2(const Instruction& i0,
const Instruction& i1,
IR2_Condition::Kind kind,
bool likely,
int idx) {
return make_branch(IR2_Condition(kind, make_src_atom(i0.get_src(0).get_reg(), idx)), i1, likely,
i0.get_src(1).get_label(), idx);
}
std::unique_ptr<AtomicOp> convert_daddiu_2(const Instruction& i0, const Instruction& i1, int idx) { std::unique_ptr<AtomicOp> convert_daddiu_2(const Instruction& i0, const Instruction& i1, int idx) {
// daddiu dest, s7, 8 // daddiu dest, s7, 8
// mov{n,z} dest, s7, src // mov{n,z} dest, s7, src
@ -921,18 +989,8 @@ std::unique_ptr<AtomicOp> convert_2(const Instruction& i0, const Instruction& i1
return convert_jalr_2(i0, i1, idx); return convert_jalr_2(i0, i1, idx);
case InstructionKind::BNE: case InstructionKind::BNE:
return convert_bne_2(i0, i1, idx, false); return convert_bne_2(i0, i1, idx, false);
case InstructionKind::BNEL:
return convert_bne_2(i0, i1, idx, true);
case InstructionKind::BEQ: case InstructionKind::BEQ:
return convert_beq_2(i0, i1, idx, false); return convert_beq_2(i0, i1, idx, false);
case InstructionKind::BEQL:
return convert_beq_2(i0, i1, idx, true);
case InstructionKind::BGTZL:
return convert_branch_r1_2(i0, i1, IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED, true, idx);
case InstructionKind::BGEZL:
return convert_branch_r1_2(i0, i1, IR2_Condition::Kind::GEQ_ZERO_SIGNED, true, idx);
case InstructionKind::BLTZL:
return convert_branch_r1_2(i0, i1, IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED, true, idx);
case InstructionKind::DADDIU: case InstructionKind::DADDIU:
return convert_daddiu_2(i0, i1, idx); return convert_daddiu_2(i0, i1, idx);
case InstructionKind::LUI: case InstructionKind::LUI:

View file

@ -11,7 +11,7 @@
namespace decompiler { namespace decompiler {
namespace { namespace {
Form* cfg_to_ir(FormPool& pool, const Function& f, const CfgVtx* vtx); Form* cfg_to_ir(FormPool& pool, Function& f, const CfgVtx* vtx);
/*! /*!
* If it's a form containing multiple elements, return a pointer to the branch element and the end * If it's a form containing multiple elements, return a pointer to the branch element and the end
@ -172,21 +172,40 @@ void clean_up_break(FormPool& pool, BreakElement* ir) {
* Note. a beql s7, x followed by a or y, x, r0 will count as this. I don't know why but * Note. a beql s7, x followed by a or y, x, r0 will count as this. I don't know why but
* GOAL does this on comparisons to false. * GOAL does this on comparisons to false.
*/ */
bool delay_slot_sets_false(BranchElement* branch) { bool delay_slot_sets_false(BranchElement* branch, SetVarOp& delay) {
if (branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::SET_REG_FALSE) { assert(branch->op()->likely());
assert(branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::NO_DELAY);
if (delay.src().is_identity() && delay.src().get_arg(0).is_sym_ptr() &&
delay.src().get_arg(0).get_str() == "#f") {
return true; return true;
} }
if (branch->op()->condition().kind() == IR2_Condition::Kind::FALSE && if (branch->op()->condition().kind() == IR2_Condition::Kind::FALSE) {
branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::SET_REG_REG) { if (delay.src().is_identity() && delay.src().get_arg(0).is_var()) {
auto& cond = branch->op()->condition(); auto src_var = delay.src().get_arg(0).var();
auto& delay = branch->op()->branch_delay(); auto& cond = branch->op()->condition();
auto cond_reg = cond.src(0).var().reg(); auto cond_reg = cond.src(0).var().reg();
auto src_reg = delay.var(1).reg(); return cond_reg == src_var.reg();
return cond_reg == src_reg; }
} }
return false; return false;
// if (branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::SET_REG_FALSE) {
// return true;
// }
//
// if (branch->op()->condition().kind() == IR2_Condition::Kind::FALSE &&
// branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::SET_REG_REG) {
// auto& cond = branch->op()->condition();
// auto& delay = branch->op()->branch_delay();
// auto cond_reg = cond.src(0).var().reg();
// auto src_reg = delay.var(1).reg();
// return cond_reg == src_reg;
// }
//
// return false;
} }
/*! /*!
@ -194,43 +213,61 @@ bool delay_slot_sets_false(BranchElement* branch) {
* or form branch? Either it explicitly sets #t, or it tests the value for being not false, * or form branch? Either it explicitly sets #t, or it tests the value for being not false,
* then uses that * then uses that
*/ */
bool delay_slot_sets_truthy(BranchElement* branch) { bool delay_slot_sets_truthy(BranchElement* branch, SetVarOp& delay) {
if (branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::SET_REG_TRUE) { assert(branch->op()->likely());
assert(branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::NO_DELAY);
if (delay.src().is_identity() && delay.src().get_arg(0).is_sym_ptr() &&
delay.src().get_arg(0).get_str() == "#t") {
return true; return true;
} }
if (branch->op()->condition().kind() == IR2_Condition::Kind::TRUTHY && if (branch->op()->condition().kind() == IR2_Condition::Kind::TRUTHY) {
branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::SET_REG_REG) { if (delay.src().is_identity() && delay.src().get_arg(0).is_var()) {
auto& cond = branch->op()->condition(); auto src_var = delay.src().get_arg(0).var();
auto& delay = branch->op()->branch_delay(); auto& cond = branch->op()->condition();
auto cond_reg = cond.src(0).var().reg(); auto cond_reg = cond.src(0).var().reg();
auto src_reg = delay.var(1).reg(); return cond_reg == src_var.reg();
return cond_reg == src_reg; }
} }
// if (branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::SET_REG_TRUE) {
// return true;
// }
//
// if (branch->op()->condition().kind() == IR2_Condition::Kind::TRUTHY &&
// branch->op()->branch_delay().kind() == IR2_BranchDelay::Kind::SET_REG_REG) {
// auto& cond = branch->op()->condition();
// auto& delay = branch->op()->branch_delay();
// auto cond_reg = cond.src(0).var().reg();
// auto src_reg = delay.var(1).reg();
// return cond_reg == src_reg;
// }
return false; return false;
} }
/*! /*!
* Try to convert a short circuit to an and. * Try to convert a short circuit to an and.
*/ */
bool try_clean_up_sc_as_and(FormPool& pool, const Function& func, ShortCircuitElement* ir) { bool try_clean_up_sc_as_and(FormPool& pool, Function& func, ShortCircuitElement* ir) {
Register destination; Register destination;
Variable ir_dest; Variable ir_dest;
for (int i = 0; i < int(ir->entries.size()) - 1; i++) { for (int i = 0; i < int(ir->entries.size()) - 1; i++) {
auto branch = get_condition_branch(ir->entries.at(i).condition); auto branch = get_condition_branch(ir->entries.at(i).condition);
assert(branch.first); assert(branch.first);
if (!delay_slot_sets_false(branch.first)) { assert(ir->entries.at(i).branch_delay.has_value());
if (!delay_slot_sets_false(branch.first, *ir->entries.at(i).branch_delay)) {
return false; return false;
} }
if (i == 0) { if (i == 0) {
// first case, remember the destination // first case, remember the destination
ir_dest = branch.first->op()->branch_delay().var(0); ir_dest = ir->entries.at(i).branch_delay->dst();
destination = ir_dest.reg(); destination = ir_dest.reg();
} else { } else {
// check destination against the first case. // check destination against the first case.
if (destination != branch.first->op()->branch_delay().var(0).reg()) { if (destination != ir->entries.at(i).branch_delay->dst().reg()) {
return false; return false;
} }
} }
@ -247,7 +284,14 @@ bool try_clean_up_sc_as_and(FormPool& pool, const Function& func, ShortCircuitEl
assert(branch.first); assert(branch.first);
if (func.ir2.env.has_reg_use()) { if (func.ir2.env.has_reg_use()) {
auto& branch_info = func.ir2.env.reg_use().op.at(branch.first->op()->op_id()); auto delay_id = ir->entries.at(i).branch_delay->dst().idx();
auto& delay_info = func.ir2.env.reg_use().op.at(delay_id);
auto branch_id = branch.first->op()->op_id();
auto& branch_info = func.ir2.env.reg_use().op.at(branch_id);
for (auto x : delay_info.consumes) {
branch_info.consumes.insert(x);
}
if (i == 0) { if (i == 0) {
live_out_result = (branch_info.written_and_unused.find(ir_dest.reg()) == live_out_result = (branch_info.written_and_unused.find(ir_dest.reg()) ==
@ -276,22 +320,23 @@ bool try_clean_up_sc_as_and(FormPool& pool, const Function& func, ShortCircuitEl
* Try to convert a short circuit to an or. * Try to convert a short circuit to an or.
* Note - this will convert an and to a very strange or, so always use the try as and first. * Note - this will convert an and to a very strange or, so always use the try as and first.
*/ */
bool try_clean_up_sc_as_or(FormPool& pool, const Function& func, ShortCircuitElement* ir) { bool try_clean_up_sc_as_or(FormPool& pool, Function& func, ShortCircuitElement* ir) {
Register destination; Register destination;
Variable ir_dest; Variable ir_dest;
for (int i = 0; i < int(ir->entries.size()) - 1; i++) { for (int i = 0; i < int(ir->entries.size()) - 1; i++) {
auto branch = get_condition_branch(ir->entries.at(i).condition); auto branch = get_condition_branch(ir->entries.at(i).condition);
assert(branch.first); assert(branch.first);
if (!delay_slot_sets_truthy(branch.first)) { assert(ir->entries.at(i).branch_delay.has_value());
if (!delay_slot_sets_truthy(branch.first, *ir->entries.at(i).branch_delay)) {
return false; return false;
} }
if (i == 0) { if (i == 0) {
// first case, remember the destination // first case, remember the destination
ir_dest = branch.first->op()->branch_delay().var(0); ir_dest = ir->entries.at(i).branch_delay->dst();
destination = ir_dest.reg(); destination = ir_dest.reg();
} else { } else {
// check destination against the first case. // check destination against the first case.
if (destination != branch.first->op()->branch_delay().var(0).reg()) { if (destination != ir->entries.at(i).branch_delay->dst().reg()) {
return false; return false;
} }
} }
@ -307,14 +352,21 @@ bool try_clean_up_sc_as_or(FormPool& pool, const Function& func, ShortCircuitEle
assert(branch.first); assert(branch.first);
if (func.ir2.env.has_reg_use()) { if (func.ir2.env.has_reg_use()) {
auto& branch_info = func.ir2.env.reg_use().op.at(branch.first->op()->op_id()); auto delay_id = ir->entries.at(i).branch_delay->dst().idx();
auto& delay_info = func.ir2.env.reg_use().op.at(delay_id);
auto branch_id = branch.first->op()->op_id();
auto& branch_info = func.ir2.env.reg_use().op.at(branch_id);
for (auto x : delay_info.consumes) {
branch_info.consumes.insert(x);
}
if (i == 0) { if (i == 0) {
live_out_result = (branch_info.written_and_unused.find(ir_dest.reg()) == live_out_result = (delay_info.written_and_unused.find(ir_dest.reg()) ==
branch_info.written_and_unused.end()); delay_info.written_and_unused.end());
} else { } else {
bool this_live_out = (branch_info.written_and_unused.find(ir_dest.reg()) == bool this_live_out = (delay_info.written_and_unused.find(ir_dest.reg()) ==
branch_info.written_and_unused.end()); delay_info.written_and_unused.end());
assert(live_out_result == this_live_out); assert(live_out_result == this_live_out);
} }
} }
@ -327,7 +379,7 @@ bool try_clean_up_sc_as_or(FormPool& pool, const Function& func, ShortCircuitEle
return true; return true;
} }
void clean_up_sc(FormPool& pool, const Function& func, ShortCircuitElement* ir); void clean_up_sc(FormPool& pool, Function& func, ShortCircuitElement* ir);
/*! /*!
* A form like (and x (or y z)) will be recognized as a single SC Vertex by the CFG pass. * A form like (and x (or y z)) will be recognized as a single SC Vertex by the CFG pass.
@ -338,11 +390,12 @@ void clean_up_sc(FormPool& pool, const Function& func, ShortCircuitElement* ir);
* (and x (or y (and a b)) c d (or z)) * (and x (or y (and a b)) c d (or z))
* will work correctly. This may require doing more splitting on both sections! * will work correctly. This may require doing more splitting on both sections!
*/ */
bool try_splitting_nested_sc(FormPool& pool, const Function& func, ShortCircuitElement* ir) { bool try_splitting_nested_sc(FormPool& pool, Function& func, ShortCircuitElement* ir) {
auto first_branch = get_condition_branch(ir->entries.front().condition); auto first_branch = get_condition_branch(ir->entries.front().condition);
assert(first_branch.first); assert(first_branch.first);
bool first_is_and = delay_slot_sets_false(first_branch.first); assert(ir->entries.front().branch_delay.has_value());
bool first_is_or = delay_slot_sets_truthy(first_branch.first); bool first_is_and = delay_slot_sets_false(first_branch.first, *ir->entries.front().branch_delay);
bool first_is_or = delay_slot_sets_truthy(first_branch.first, *ir->entries.front().branch_delay);
assert(first_is_and != first_is_or); // one or the other but not both! assert(first_is_and != first_is_or); // one or the other but not both!
int first_different = -1; // the index of the first one that's different. int first_different = -1; // the index of the first one that's different.
@ -350,8 +403,9 @@ bool try_splitting_nested_sc(FormPool& pool, const Function& func, ShortCircuitE
for (int i = 1; i < int(ir->entries.size()) - 1; i++) { for (int i = 1; i < int(ir->entries.size()) - 1; i++) {
auto branch = get_condition_branch(ir->entries.at(i).condition); auto branch = get_condition_branch(ir->entries.at(i).condition);
assert(branch.first); assert(branch.first);
bool is_and = delay_slot_sets_false(branch.first); assert(ir->entries.at(i).branch_delay.has_value());
bool is_or = delay_slot_sets_truthy(branch.first); bool is_and = delay_slot_sets_false(branch.first, *ir->entries.at(i).branch_delay);
bool is_or = delay_slot_sets_truthy(branch.first, *ir->entries.at(i).branch_delay);
assert(is_and != is_or); assert(is_and != is_or);
if (first_different == -1) { if (first_different == -1) {
@ -395,7 +449,7 @@ bool try_splitting_nested_sc(FormPool& pool, const Function& func, ShortCircuitE
* Try to clean up a single short circuit IR. It may get split up into nested IR_ShortCircuits * Try to clean up a single short circuit IR. It may get split up into nested IR_ShortCircuits
* if there is a case like (and a (or b c)) * if there is a case like (and a (or b c))
*/ */
void clean_up_sc(FormPool& pool, const Function& func, ShortCircuitElement* ir) { void clean_up_sc(FormPool& pool, Function& func, ShortCircuitElement* ir) {
assert(ir->entries.size() > 1); assert(ir->entries.size() > 1);
if (!try_clean_up_sc_as_and(pool, func, ir)) { if (!try_clean_up_sc_as_and(pool, func, ir)) {
if (!try_clean_up_sc_as_or(pool, func, ir)) { if (!try_clean_up_sc_as_or(pool, func, ir)) {
@ -630,6 +684,98 @@ bool is_op_3(FormElement* ir,
return true; return true;
} }
bool is_op_3(AtomicOp* op,
MatchParam<SimpleExpression::Kind> kind,
MatchParam<Register> dst,
MatchParam<Register> src0,
MatchParam<Register> src1,
Register* dst_out = nullptr,
Register* src0_out = nullptr,
Register* src1_out = nullptr) {
// should be a set reg to int math 2 ir
auto set = dynamic_cast<SetVarOp*>(op);
if (!set) {
return false;
}
// destination should be a register
auto dest = set->dst();
if (dst != dest.reg()) {
return false;
}
auto math = set->src();
if (kind != math.kind()) {
return false;
}
if (get_simple_expression_arg_count(math.kind()) != 2) {
return false;
}
auto arg0 = math.get_arg(0);
auto arg1 = math.get_arg(1);
if (!arg0.is_var() || src0 != arg0.var().reg() || !arg1.is_var() || src1 != arg1.var().reg()) {
return false;
}
// it's a match!
if (dst_out) {
*dst_out = dest.reg();
}
if (src0_out) {
*src0_out = arg0.var().reg();
}
if (src1_out) {
*src1_out = arg1.var().reg();
}
return true;
}
bool is_op_2(AtomicOp* op,
MatchParam<SimpleExpression::Kind> kind,
MatchParam<Register> dst,
MatchParam<Register> src0,
Register* dst_out = nullptr,
Register* src0_out = nullptr) {
// should be a set reg to int math 2 ir
auto set = dynamic_cast<SetVarOp*>(op);
if (!set) {
return false;
}
// destination should be a register
auto dest = set->dst();
if (dst != dest.reg()) {
return false;
}
auto math = set->src();
if (kind != math.kind()) {
return false;
}
auto arg = math.get_arg(0);
if (!arg.is_var() || src0 != arg.var().reg()) {
return false;
}
// it's a match!
if (dst_out) {
*dst_out = dest.reg();
}
if (src0_out) {
*src0_out = arg.var().reg();
}
return true;
}
bool is_op_2(FormElement* ir, bool is_op_2(FormElement* ir,
MatchParam<SimpleExpression::Kind> kind, MatchParam<SimpleExpression::Kind> kind,
MatchParam<Register> dst, MatchParam<Register> dst,
@ -675,17 +821,18 @@ bool is_op_2(FormElement* ir,
* Try to convert this SC Vertex into an abs (integer). * Try to convert this SC Vertex into an abs (integer).
* Will return a converted abs IR if successful, or nullptr if its not possible * Will return a converted abs IR if successful, or nullptr if its not possible
*/ */
Form* try_sc_as_abs(FormPool& pool, const Function& f, const ShortCircuit* vtx) { Form* try_sc_as_abs(FormPool& pool, Function& f, const ShortCircuit* vtx) {
if (vtx->entries.size() != 1) { if (vtx->entries.size() != 1) {
return nullptr; return nullptr;
} }
auto b0 = dynamic_cast<BlockVtx*>(vtx->entries.at(0)); auto b0_c = vtx->entries.at(0).condition;
if (!b0) { auto b0_d = dynamic_cast<BlockVtx*>(vtx->entries.at(0).likely_delay);
if (!b0_c || !b0_d) {
return nullptr; return nullptr;
} }
auto b0_ptr = cfg_to_ir(pool, f, b0); auto b0_ptr = cfg_to_ir(pool, f, b0_c);
// auto b0_ir = dynamic_cast<IR_Begin*>(b0_ptr.get()); // auto b0_ir = dynamic_cast<IR_Begin*>(b0_ptr.get());
BranchElement* branch = dynamic_cast<BranchElement*>(b0_ptr->back()); BranchElement* branch = dynamic_cast<BranchElement*>(b0_ptr->back());
@ -694,19 +841,27 @@ Form* try_sc_as_abs(FormPool& pool, const Function& f, const ShortCircuit* vtx)
return nullptr; return nullptr;
} }
auto delay_start = f.ir2.atomic_ops->block_id_to_first_atomic_op.at(b0_d->block_id);
auto delay_end = f.ir2.atomic_ops->block_id_to_end_atomic_op.at(b0_d->block_id);
if (delay_end - delay_start != 1) {
return nullptr;
}
auto& delay_op = f.ir2.atomic_ops->ops.at(delay_start);
auto* delay = dynamic_cast<SetVarOp*>(delay_op.get());
// check the branch instruction // check the branch instruction
if (!branch->op()->likely() || if (!branch->op()->likely() ||
branch->op()->condition().kind() != IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED || branch->op()->condition().kind() != IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED ||
branch->op()->branch_delay().kind() != IR2_BranchDelay::Kind::NEGATE) { !is_op_2(delay, SimpleExpression::Kind::NEG, {}, {})) {
// todo - if there was an abs(unsigned), it would be missed here. // todo - if there was an abs(unsigned), it would be missed here.
return nullptr; return nullptr;
} }
auto input = branch->op()->condition().src(0); auto input = branch->op()->condition().src(0);
auto output = branch->op()->branch_delay().var(0); auto output = delay->dst();
assert(input.is_var()); assert(input.is_var());
assert(input.var().reg() == branch->op()->branch_delay().var(1).reg()); assert(input.var().reg() == delay->src().get_arg(0).var().reg());
// remove the branch // remove the branch
b0_ptr->pop_back(); b0_ptr->pop_back();
@ -731,22 +886,32 @@ Form* try_sc_as_abs(FormPool& pool, const Function& f, const ShortCircuit* vtx)
* GOAL's shift function accepts positive/negative numbers to determine the direction * GOAL's shift function accepts positive/negative numbers to determine the direction
* of the shift. * of the shift.
*/ */
Form* try_sc_as_ash(FormPool& pool, const Function& f, const ShortCircuit* vtx) { Form* try_sc_as_ash(FormPool& pool, Function& f, const ShortCircuit* vtx) {
if (vtx->entries.size() != 2) { if (vtx->entries.size() != 2) {
return nullptr; return nullptr;
} }
// todo, I think b0 could possibly be something more complicated, depending on how we order. // todo, I think b0 could possibly be something more complicated, depending on how we order.
auto b0 = dynamic_cast<CfgVtx*>(vtx->entries.at(0)); auto b0_c = vtx->entries.at(0).condition;
auto b1 = dynamic_cast<BlockVtx*>(vtx->entries.at(1)); auto b0_d = dynamic_cast<BlockVtx*>(vtx->entries.at(0).likely_delay);
if (!b0 || !b1) { auto b1 = dynamic_cast<BlockVtx*>(vtx->entries.at(1).condition);
if (!b0_c || !b0_d || !b1 || vtx->entries.at(1).likely_delay) {
return nullptr; return nullptr;
} }
auto b0_ptr = cfg_to_ir(pool, f, b0); auto b0_c_ptr = cfg_to_ir(pool, f, b0_c);
auto b1_ptr = cfg_to_ir(pool, f, b1); auto b1_ptr = cfg_to_ir(pool, f, b1);
auto branch = dynamic_cast<BranchElement*>(b0_ptr->back()); auto delay_start = f.ir2.atomic_ops->block_id_to_first_atomic_op.at(b0_d->block_id);
auto delay_end = f.ir2.atomic_ops->block_id_to_end_atomic_op.at(b0_d->block_id);
if (delay_end - delay_start != 1) {
return nullptr;
}
auto& delay_op = f.ir2.atomic_ops->ops.at(delay_start);
auto* delay = dynamic_cast<SetVarOp*>(delay_op.get());
auto branch = dynamic_cast<BranchElement*>(b0_c_ptr->back());
if (!branch || b1_ptr->size() != 2) { if (!branch || b1_ptr->size() != 2) {
return nullptr; return nullptr;
} }
@ -754,7 +919,7 @@ Form* try_sc_as_ash(FormPool& pool, const Function& f, const ShortCircuit* vtx)
// check the branch instruction // check the branch instruction
if (!branch->op()->likely() || if (!branch->op()->likely() ||
branch->op()->condition().kind() != IR2_Condition::Kind::GEQ_ZERO_SIGNED || branch->op()->condition().kind() != IR2_Condition::Kind::GEQ_ZERO_SIGNED ||
branch->op()->branch_delay().kind() != IR2_BranchDelay::Kind::DSLLV) { !is_op_3(delay, SimpleExpression::Kind::LEFT_SHIFT, {}, {}, {})) {
return nullptr; return nullptr;
} }
@ -768,9 +933,9 @@ Form* try_sc_as_ash(FormPool& pool, const Function& f, const ShortCircuit* vtx)
auto sa_in = branch->op()->condition().src(0); auto sa_in = branch->op()->condition().src(0);
assert(sa_in.is_var()); assert(sa_in.is_var());
auto result = branch->op()->branch_delay().var(0); auto result = delay->dst();
auto value_in = branch->op()->branch_delay().var(1); auto value_in = delay->src().get_arg(0).var();
auto sa_in2 = branch->op()->branch_delay().var(2); auto sa_in2 = delay->src().get_arg(1).var();
assert(sa_in.var().reg() == sa_in2.reg()); assert(sa_in.var().reg() == sa_in2.reg());
auto dsubu_candidate = b1_ptr->at(0); auto dsubu_candidate = b1_ptr->at(0);
@ -798,7 +963,7 @@ Form* try_sc_as_ash(FormPool& pool, const Function& f, const ShortCircuit* vtx)
clobber_ir = dsubu_set->dst(); clobber_ir = dsubu_set->dst();
} }
Variable dest_ir = branch->op()->branch_delay().var(0); Variable dest_ir = result;
SimpleAtom shift_ir = branch->op()->condition().src(0); SimpleAtom shift_ir = branch->op()->condition().src(0);
auto value_ir = auto value_ir =
dynamic_cast<const SimpleExpressionElement*>(dsrav_set->src()->try_as_single_element()) dynamic_cast<const SimpleExpressionElement*>(dsrav_set->src()->try_as_single_element())
@ -806,7 +971,7 @@ Form* try_sc_as_ash(FormPool& pool, const Function& f, const ShortCircuit* vtx)
.get_arg(0); .get_arg(0);
// remove the branch // remove the branch
b0_ptr->pop_back(); b0_c_ptr->pop_back();
auto& info = f.ir2.env.reg_use(); auto& info = f.ir2.env.reg_use();
auto final_op_idx = value_ir.var().idx(); auto final_op_idx = value_ir.var().idx();
@ -824,16 +989,32 @@ Form* try_sc_as_ash(FormPool& pool, const Function& f, const ShortCircuit* vtx)
auto ash_form = pool.alloc_single_element_form<AshElement>( auto ash_form = pool.alloc_single_element_form<AshElement>(
nullptr, shift_ir.var(), value_ir.var(), clobber_ir, is_arith, consumed); nullptr, shift_ir.var(), value_ir.var(), clobber_ir, is_arith, consumed);
auto set_form = pool.alloc_element<SetVarElement>(dest_ir, ash_form, true); auto set_form = pool.alloc_element<SetVarElement>(dest_ir, ash_form, true);
b0_ptr->push_back(set_form); b0_c_ptr->push_back(set_form);
return b0_ptr; return b0_c_ptr;
}
bool is_set_symbol_value(SetVarOp& op, const std::string& name) {
return op.src().is_identity() && op.src().get_arg(0).is_sym_val() &&
op.src().get_arg(0).get_str() == name;
}
SetVarOp get_delay_op(const Function& f, const BlockVtx* vtx) {
auto delay_start = f.ir2.atomic_ops->block_id_to_first_atomic_op.at(vtx->block_id);
auto delay_end = f.ir2.atomic_ops->block_id_to_end_atomic_op.at(vtx->block_id);
if (delay_end - delay_start != 1) {
assert(false);
}
auto& delay_op = f.ir2.atomic_ops->ops.at(delay_start);
auto* delay = dynamic_cast<SetVarOp*>(delay_op.get());
return *delay;
} }
/*! /*!
* Try to convert a short circuiting expression into a "type-of" expression. * Try to convert a short circuiting expression into a "type-of" expression.
* We do this before attempting the normal and/or expressions. * We do this before attempting the normal and/or expressions.
*/ */
Form* try_sc_as_type_of(FormPool& pool, const Function& f, const ShortCircuit* vtx) { Form* try_sc_as_type_of(FormPool& pool, Function& f, const ShortCircuit* vtx) {
// the assembly looks like this: // the assembly looks like this:
/* /*
dsll32 v1, a0, 29 ;; (set! v1 (shl a0 61)) dsll32 v1, a0, 29 ;; (set! v1 (shl a0 61))
@ -853,23 +1034,25 @@ Form* try_sc_as_type_of(FormPool& pool, const Function& f, const ShortCircuit* v
return nullptr; return nullptr;
} }
auto b0 = dynamic_cast<CfgVtx*>(vtx->entries.at(0)); auto b0_c = dynamic_cast<CfgVtx*>(vtx->entries.at(0).condition);
auto b1 = dynamic_cast<BlockVtx*>(vtx->entries.at(1)); auto b0_d = dynamic_cast<BlockVtx*>(vtx->entries.at(0).likely_delay);
auto b2 = dynamic_cast<BlockVtx*>(vtx->entries.at(2)); auto b1_c = dynamic_cast<BlockVtx*>(vtx->entries.at(1).condition);
auto b1_d = dynamic_cast<BlockVtx*>(vtx->entries.at(1).likely_delay);
auto b2_c = dynamic_cast<BlockVtx*>(vtx->entries.at(2).condition);
if (!b0 || !b1 || !b2) { if (!b0_c || !b0_d || !b1_c || !b1_d || !b2_c || vtx->entries.at(2).likely_delay) {
return nullptr; return nullptr;
} }
auto b0_ptr = cfg_to_ir(pool, f, b0); // should be begin. auto b0_ptr = cfg_to_ir(pool, f, b0_c); // should be begin.
if (b0_ptr->size() <= 1) { if (b0_ptr->size() <= 1) {
return nullptr; return nullptr;
} }
auto b1_ptr = cfg_to_ir(pool, f, b1); auto b1_ptr = cfg_to_ir(pool, f, b1_c);
auto b1_ir = dynamic_cast<BranchElement*>(b1_ptr->try_as_single_element()); auto b1_ir = dynamic_cast<BranchElement*>(b1_ptr->try_as_single_element());
auto b2_ptr = cfg_to_ir(pool, f, b2); auto b2_ptr = cfg_to_ir(pool, f, b2_c);
auto b2_ir = dynamic_cast<SetVarElement*>(b2_ptr->try_as_single_element()); auto b2_ir = dynamic_cast<SetVarElement*>(b2_ptr->try_as_single_element());
if (!b1_ir || !b2_ir) { if (!b1_ir || !b2_ir) {
return nullptr; return nullptr;
@ -896,25 +1079,25 @@ Form* try_sc_as_type_of(FormPool& pool, const Function& f, const ShortCircuit* v
auto second_branch = b1_ir; auto second_branch = b1_ir;
auto else_case = b2_ir; auto else_case = b2_ir;
if (!first_branch || auto b0_delay_op = get_delay_op(f, b0_d);
first_branch->op()->branch_delay().kind() != IR2_BranchDelay::Kind::SET_BINTEGER || if (!first_branch || !is_set_symbol_value(b0_delay_op, "binteger") ||
first_branch->op()->condition().kind() != IR2_Condition::Kind::ZERO || first_branch->op()->condition().kind() != IR2_Condition::Kind::ZERO ||
!first_branch->op()->likely()) { !first_branch->op()->likely()) {
return nullptr; return nullptr;
} }
auto temp_reg = first_branch->op()->condition().src(0).var(); auto temp_reg = first_branch->op()->condition().src(0).var();
assert(temp_reg.reg() == temp_reg0.reg()); assert(temp_reg.reg() == temp_reg0.reg());
auto dst_reg = first_branch->op()->branch_delay().var(0); auto dst_reg = b0_delay_op.dst();
if (!second_branch || auto b1_delay_op = get_delay_op(f, b1_d);
second_branch->op()->branch_delay().kind() != IR2_BranchDelay::Kind::SET_PAIR || if (!second_branch || !is_set_symbol_value(b1_delay_op, "pair") ||
second_branch->op()->condition().kind() != IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED || second_branch->op()->condition().kind() != IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED ||
!second_branch->op()->likely()) { !second_branch->op()->likely()) {
return nullptr; return nullptr;
} }
// check we agree on destination register. // check we agree on destination register.
auto dst_reg2 = second_branch->op()->branch_delay().var(0); auto dst_reg2 = b1_delay_op.dst();
assert(dst_reg2.reg() == dst_reg.reg()); assert(dst_reg2.reg() == dst_reg.reg());
// else case is a lwu to grab the type from a basic // else case is a lwu to grab the type from a basic
@ -960,7 +1143,7 @@ Form* try_sc_as_type_of(FormPool& pool, const Function& f, const ShortCircuit* v
} }
Form* merge_cond_else_with_sc_cond(FormPool& pool, Form* merge_cond_else_with_sc_cond(FormPool& pool,
const Function& f, Function& f,
const CondWithElse* cwe, const CondWithElse* cwe,
Form* else_ir) { Form* else_ir) {
if (else_ir->size() != 2) { if (else_ir->size() != 2) {
@ -998,7 +1181,7 @@ Form* merge_cond_else_with_sc_cond(FormPool& pool,
} }
void insert_cfg_into_list(FormPool& pool, void insert_cfg_into_list(FormPool& pool,
const Function& f, Function& f,
const CfgVtx* vtx, const CfgVtx* vtx,
std::vector<FormElement*>* output) { std::vector<FormElement*>* output) {
auto as_sequence = dynamic_cast<const SequenceVtx*>(vtx); auto as_sequence = dynamic_cast<const SequenceVtx*>(vtx);
@ -1023,7 +1206,7 @@ void insert_cfg_into_list(FormPool& pool,
} }
} }
Form* cfg_to_ir(FormPool& pool, const Function& f, const CfgVtx* vtx) { Form* cfg_to_ir(FormPool& pool, Function& f, const CfgVtx* vtx) {
if (dynamic_cast<const BlockVtx*>(vtx)) { if (dynamic_cast<const BlockVtx*>(vtx)) {
auto* bv = dynamic_cast<const BlockVtx*>(vtx); auto* bv = dynamic_cast<const BlockVtx*>(vtx);
@ -1140,7 +1323,19 @@ Form* cfg_to_ir(FormPool& pool, const Function& f, const CfgVtx* vtx) {
std::vector<ShortCircuitElement::Entry> entries; std::vector<ShortCircuitElement::Entry> entries;
for (auto& x : svtx->entries) { for (auto& x : svtx->entries) {
ShortCircuitElement::Entry e; ShortCircuitElement::Entry e;
e.condition = cfg_to_ir(pool, f, x); e.condition = cfg_to_ir(pool, f, x.condition);
if (x.likely_delay) {
auto delay = dynamic_cast<BlockVtx*>(x.likely_delay);
assert(delay);
auto delay_start = f.ir2.atomic_ops->block_id_to_first_atomic_op.at(delay->block_id);
auto delay_end = f.ir2.atomic_ops->block_id_to_end_atomic_op.at(delay->block_id);
assert(delay_end - delay_start == 1);
auto& op = f.ir2.atomic_ops->ops.at(delay_start);
auto op_as_expr = dynamic_cast<SetVarOp*>(op.get());
assert(op_as_expr);
e.branch_delay = *op_as_expr;
}
entries.push_back(e); entries.push_back(e);
} }
auto result = pool.alloc_single_element_form<ShortCircuitElement>(nullptr, entries); auto result = pool.alloc_single_element_form<ShortCircuitElement>(nullptr, entries);

View file

@ -8,12 +8,12 @@
[121, ["gp", "(array uint8)"]], [121, ["gp", "(array uint8)"]],
[141, ["gp", "(array int16)"]], [141, ["gp", "(array int16)"]],
[161, ["gp", "(array uint16)"]], [161, ["gp", "(array uint16)"]],
[185, ["gp", "(array uint128)"]], [186, ["gp", "(array uint128)"]],
[203, ["gp", "(array int32)"]], [204, ["gp", "(array int32)"]],
[222, ["gp", "(array float)"]], [223, ["gp", "(array float)"]],
[231, ["gp", "(array float)"]], [232, ["gp", "(array float)"]],
[248, ["gp", "(array basic)"]], [249, ["gp", "(array basic)"]],
[257, ["gp", "(array basic)"]] [258, ["gp", "(array basic)"]]
], ],
"(method 3 array)":[ "(method 3 array)":[

View file

@ -170,14 +170,14 @@ TEST(DecompilerAtomicOpBuilder, ANDI) {
} }
TEST(DecompilerAtomicOpBuilder, BEQL_SLL) { TEST(DecompilerAtomicOpBuilder, BEQL_SLL) {
test_case(assembly_from_list({"L100:", "beql a0, a1, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "beql a0, a1, L100"}),
{"(bl! (= a0 a1) L100 (nop!))"}, {{}}, {{"a0", "a1"}}, {{}}); {"(bl! (= a0 a1) L100 (no-delay!))"}, {{}}, {{"a0", "a1"}}, {{}});
test_case(assembly_from_list({"L100:", "beql r0, r0, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "beql r0, r0, L100"}), {"(bl! #t L100 (no-delay!))"}, {{}},
{"(bl! #t L100 (nop!))"}, {{}}, {{}}, {{}}); {{}}, {{}});
test_case(assembly_from_list({"L100:", "beql a0, r0, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "beql a0, r0, L100"}),
{"(bl! (zero? a0) L100 (nop!))"}, {{}}, {{"a0"}}, {{}}); {"(bl! (zero? a0) L100 (no-delay!))"}, {{}}, {{"a0"}}, {{}});
test_case(assembly_from_list({"L100:", "beql s7, a0, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "beql s7, a0, L100"}), {"(bl! (not a0) L100 (no-delay!))"},
{"(bl! (not a0) L100 (nop!))"}, {{}}, {{"a0"}}, {{}}); {{}}, {{"a0"}}, {{}});
} }
TEST(DecompilerAtomicOpBuilder, BEQ_SLL) { TEST(DecompilerAtomicOpBuilder, BEQ_SLL) {
@ -192,27 +192,27 @@ TEST(DecompilerAtomicOpBuilder, BEQ_SLL) {
} }
TEST(DecompilerAtomicOpBuilder, BGEZL_SLL) { TEST(DecompilerAtomicOpBuilder, BGEZL_SLL) {
test_case(assembly_from_list({"L100:", "bgezl a0, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "bgezl a0, L100"}), {"(bl! (>=0.si a0) L100 (no-delay!))"},
{"(bl! (>=0.si a0) L100 (nop!))"}, {{}}, {{"a0"}}, {{}}); {{}}, {{"a0"}}, {{}});
} }
TEST(DecompilerAtomicOpBuilder, BGTZL_SLL) { TEST(DecompilerAtomicOpBuilder, BGTZL_SLL) {
test_case(assembly_from_list({"L100:", "bgtzl a0, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "bgtzl a0, L100"}), {"(bl! (>0.si a0) L100 (no-delay!))"},
{"(bl! (>0.si a0) L100 (nop!))"}, {{}}, {{"a0"}}, {{}}); {{}}, {{"a0"}}, {{}});
} }
TEST(DecompilerAtomicOpBuilder, BLTZL_SLL) { TEST(DecompilerAtomicOpBuilder, BLTZL_SLL) {
test_case(assembly_from_list({"L100:", "bltzl a0, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "bltzl a0, L100"}), {"(bl! (<0.si a0) L100 (no-delay!))"},
{"(bl! (<0.si a0) L100 (nop!))"}, {{}}, {{"a0"}}, {{}}); {{}}, {{"a0"}}, {{}});
} }
TEST(DecompilerAtomicOpBuilder, BNEL_SLL) { TEST(DecompilerAtomicOpBuilder, BNEL_SLL) {
test_case(assembly_from_list({"L100:", "bnel a1, a2, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "bnel a1, a2, L100"}),
{"(bl! (!= a1 a2) L100 (nop!))"}, {{}}, {{"a1", "a2"}}, {{}}); {"(bl! (!= a1 a2) L100 (no-delay!))"}, {{}}, {{"a1", "a2"}}, {{}});
test_case(assembly_from_list({"L100:", "bnel a1, r0, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "bnel a1, r0, L100"}),
{"(bl! (nonzero? a1) L100 (nop!))"}, {{}}, {{"a1"}}, {{}}); {"(bl! (nonzero? a1) L100 (no-delay!))"}, {{}}, {{"a1"}}, {{}});
test_case(assembly_from_list({"L100:", "bnel s7, a1, L100", "sll r0, r0, 0"}), test_case(assembly_from_list({"L100:", "bnel s7, a1, L100"}),
{"(bl! (truthy a1) L100 (nop!))"}, {{}}, {{"a1"}}, {{}}); {"(bl! (truthy a1) L100 (no-delay!))"}, {{}}, {{"a1"}}, {{}});
} }
TEST(DecompilerAtomicOpBuilder, BNE_DADDIU) { TEST(DecompilerAtomicOpBuilder, BNE_DADDIU) {

View file

@ -114,7 +114,7 @@ TEST_F(FormRegressionTest, Abs) {
" jr ra\n" " jr ra\n"
" daddu sp, sp, r0"; " daddu sp, sp, r0";
std::string type = "(function int int)"; std::string type = "(function int int)";
std::string expected = "(begin (set! v0-0 a0-0) (set! v0-1 (abs v0-0)) (ret-value v0-1))"; std::string expected = "(begin (set! v0-0 a0-0) (set! v0-0 (abs v0-0)) (ret-value v0-0))";
test_no_expr(func, type, expected); test_no_expr(func, type, expected);
} }
@ -736,7 +736,7 @@ TEST_F(FormRegressionTest, NestedAndOr) {
" )\n" " )\n"
" a0-2\n" // false or >0 " a0-2\n" // false or >0
" )\n" " )\n"
" (begin (set! a0-3 '#t) (set! v1-2 (!= v1-2 a0-3)))\n" // not #t " (begin (set! a0-3 '#t) (set! v1-2 (!= v1-1 a0-3)))\n" // not #t
" )\n" " )\n"
" v1-2\n" // (and (or false >0) (not #t)) " v1-2\n" // (and (or false >0) (not #t))
" )\n" " )\n"

View file

@ -1600,7 +1600,7 @@ TEST_F(FormRegressionTest, ExprSort) {
" (set! s1-0 (car (cdr s3-0)))\n" " (set! s1-0 (car (cdr s3-0)))\n"
" (set! v1-1 (s5-0 s2-0 s1-0))\n" " (set! v1-1 (s5-0 s2-0 s1-0))\n"
" (when\n" " (when\n"
" (and (or (not v1-1) (> (the-as int v1-1) 0)) (!= v1-2 (quote #t)))\n" " (and (or (not v1-1) (> (the-as int v1-1) 0)) (!= v1-1 (quote #t)))\n"
" (set! s4-0 (+ s4-0 1))\n" " (set! s4-0 (+ s4-0 1))\n"
" (set! (car s3-0) s1-0)\n" " (set! (car s3-0) s1-0)\n"
" (set! (car (cdr s3-0)) s2-0)\n" " (set! (car (cdr s3-0)) s2-0)\n"
@ -2337,10 +2337,10 @@ TEST_F(FormRegressionTest, ExprPrintName) {
" (string= a0-0 a1-0)\n" " (string= a0-0 a1-0)\n"
" )\n" " )\n"
" ((and (= (-> a0-0 type) string) (= (-> a1-0 type) symbol))\n" " ((and (= (-> a0-0 type) string) (= (-> a1-0 type) symbol))\n"
" (string= a0-0 (-> (+ 65336 (the-as int (the-as symbol a1-0))) 0))\n" " (string= a0-0 (-> (+ 65336 (the-as int a1-0)) 0))\n"
" )\n" " )\n"
" ((and (= (-> a1-0 type) string) (= (-> a0-0 type) symbol))\n" " ((and (= (-> a1-0 type) string) (= (-> a0-0 type) symbol))\n"
" (string= a1-0 (-> (+ 65336 (the-as int (the-as symbol a0-0))) 0))\n" " (string= a1-0 (-> (+ 65336 (the-as int a0-0)) 0))\n"
" )\n" " )\n"
" )"; " )";
test_with_expr(func, type, expected, false, "", {}, test_with_expr(func, type, expected, false, "", {},

View file

@ -657,7 +657,7 @@ TEST_F(FormRegressionTest, ExprArrayMethod2) {
" (quote #f)\n" " (quote #f)\n"
" )\n" " )\n"
" (else\n" " (else\n"
" (set! v1-40 (or (= v1-1 (quote uint128)) (= v1-40 (quote int128))))\n" " (set! v1-40 (or (= v1-1 (quote uint128)) (= v1-1 (quote int128))))\n"
" (cond\n" " (cond\n"
" (v1-40\n" " (v1-40\n"
" (set! s5-8 0)\n" " (set! s5-8 0)\n"
@ -755,12 +755,12 @@ TEST_F(FormRegressionTest, ExprArrayMethod2) {
"\t\t[121, [\"gp\", \"(array uint8)\"]],\n" "\t\t[121, [\"gp\", \"(array uint8)\"]],\n"
"\t\t[141, [\"gp\", \"(array int16)\"]],\n" "\t\t[141, [\"gp\", \"(array int16)\"]],\n"
"\t\t[161, [\"gp\", \"(array uint16)\"]],\n" "\t\t[161, [\"gp\", \"(array uint16)\"]],\n"
"\t\t[185, [\"gp\", \"(array uint128)\"]],\n" "\t\t[186, [\"gp\", \"(array uint128)\"]],\n"
"\t\t[203, [\"gp\", \"(array int32)\"]],\n" "\t\t[204, [\"gp\", \"(array int32)\"]],\n"
"\t\t[222, [\"gp\", \"(array float)\"]],\n" "\t\t[223, [\"gp\", \"(array float)\"]],\n"
"\t\t[231, [\"gp\", \"(array float)\"]],\n" "\t\t[232, [\"gp\", \"(array float)\"]],\n"
"\t\t[248, [\"gp\", \"(array basic)\"]],\n" "\t\t[249, [\"gp\", \"(array basic)\"]],\n"
"\t\t[257, [\"gp\", \"(array basic)\"]]]")); "\t\t[258, [\"gp\", \"(array basic)\"]]]"));
} }
TEST_F(FormRegressionTest, ExprArrayMethod3) { TEST_F(FormRegressionTest, ExprArrayMethod3) {
@ -1309,7 +1309,7 @@ TEST_F(FormRegressionTest, ExprArrayMethod3) {
" (quote #f)\n" " (quote #f)\n"
" )\n" " )\n"
" (else\n" " (else\n"
" (set! v1-40 (or (= v1-1 (quote int128)) (= v1-40 (quote uint128))))\n" " (set! v1-40 (or (= v1-1 (quote int128)) (= v1-1 (quote uint128))))\n"
" (cond\n" " (cond\n"
" (v1-40\n" " (v1-40\n"
" (set! s5-8 0)\n" " (set! s5-8 0)\n"
@ -1393,10 +1393,10 @@ TEST_F(FormRegressionTest, ExprArrayMethod3) {
"\t\t[132, [\"gp\", \"(array int8)\"]],\n" "\t\t[132, [\"gp\", \"(array int8)\"]],\n"
"\t\t[150, [\"gp\", \"(array int16)\"]],\n" "\t\t[150, [\"gp\", \"(array int16)\"]],\n"
"\t\t[168, [\"gp\", \"(array uint16)\"]],\n" "\t\t[168, [\"gp\", \"(array uint16)\"]],\n"
"\t\t[190, [\"gp\", \"(array uint128)\"]],\n" "\t\t[191, [\"gp\", \"(array uint128)\"]],\n"
"\t\t[203, [\"gp\", \"(array int32)\"]],\n" "\t\t[204, [\"gp\", \"(array int32)\"]],\n"
"\t\t[225, [\"gp\", \"(array float)\"]],\n" "\t\t[226, [\"gp\", \"(array float)\"]],\n"
"\t\t[242, [\"gp\", \"(array basic)\"]]]")); "\t\t[243, [\"gp\", \"(array basic)\"]]]"));
} }
TEST_F(FormRegressionTest, ExprValid) { TEST_F(FormRegressionTest, ExprValid) {