mirror of
https://github.com/open-goal/jak-project
synced 2026-08-05 01:30:22 -04:00
Merge remote-tracking branch 'upstream/w/cfg_2_ir' into logging
This commit is contained in:
@@ -752,6 +752,10 @@ std::shared_ptr<IR> try_bnel(Instruction& instr, Instruction& next_instr, int id
|
||||
return std::make_shared<IR_Branch>(
|
||||
Condition(Condition::TRUTHY, make_reg(instr.get_src(1).get_reg(), idx), nullptr, nullptr),
|
||||
instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true);
|
||||
} else if (instr.kind == InstructionKind::BNEL && instr.get_src(1).is_reg(make_gpr(Reg::R0))) {
|
||||
return std::make_shared<IR_Branch>(
|
||||
Condition(Condition::NONZERO, make_reg(instr.get_src(0).get_reg(), idx), nullptr, nullptr),
|
||||
instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true);
|
||||
} else if (instr.kind == InstructionKind::BNEL) {
|
||||
// return std::make_shared<IR_Branch2>(IR_Branch2::NOT_EQUAL, instr.get_src(2).get_label(),
|
||||
// make_reg(instr.get_src(0).get_reg(), idx),
|
||||
|
||||
@@ -6,6 +6,17 @@
|
||||
#include "decompiler/Function/Function.h"
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
|
||||
/*!
|
||||
* TODO
|
||||
* - fix "right aligned" nested and/or or or/ands
|
||||
* - can either fix in here, or maybe in cfgvertex? not sure...
|
||||
* - check for missing inverts
|
||||
* - finish cleaning up and/or. There may be some extra work to invert the final condition.
|
||||
* if it turns out this is needed. Or just wrap it in a giant "not" and figure it out later on?
|
||||
* - store the destination of things when possible (cond else, short circuits)
|
||||
* - revisit weird destinations in conds.
|
||||
*/
|
||||
|
||||
namespace {
|
||||
|
||||
std::shared_ptr<IR> cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx);
|
||||
@@ -107,6 +118,7 @@ void clean_up_cond_with_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
|
||||
assert(jump_to_next.first->branch_delay.kind == BranchDelay::NOP);
|
||||
// patch the jump to next with a condition.
|
||||
auto replacement = std::make_shared<IR_Compare>(jump_to_next.first->condition);
|
||||
replacement->condition.invert();
|
||||
*(jump_to_next.second) = replacement;
|
||||
|
||||
// patch the jump at the end of a block.
|
||||
@@ -133,6 +145,130 @@ void clean_up_cond_with_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Does the instruction in the delay slot set a register to false?
|
||||
* Note. a beql s7, x followed by a or y, x, r0 will count as this. I don't know why but
|
||||
* GOAL does this on comparisons to false.
|
||||
*/
|
||||
bool delay_slot_sets_false(IR_Branch* branch) {
|
||||
if (branch->branch_delay.kind == BranchDelay::SET_REG_FALSE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (branch->condition.kind == Condition::FALSE &&
|
||||
branch->branch_delay.kind == BranchDelay::SET_REG_REG) {
|
||||
auto reg_check = dynamic_cast<IR_Register*>(branch->condition.src0.get());
|
||||
assert(reg_check);
|
||||
auto reg_read = dynamic_cast<IR_Register*>(branch->branch_delay.source.get());
|
||||
assert(reg_read);
|
||||
return reg_check->reg == reg_read->reg;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Does the instruction in the delay slot set a register to a truthy value, like in a GOAL
|
||||
* or form branch? Either it explicitly sets #t, or it tests the value for being not false,
|
||||
* then uses that
|
||||
*/
|
||||
bool delay_slot_sets_truthy(IR_Branch* branch) {
|
||||
if (branch->branch_delay.kind == BranchDelay::SET_REG_TRUE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (branch->condition.kind == Condition::TRUTHY &&
|
||||
branch->branch_delay.kind == BranchDelay::SET_REG_REG) {
|
||||
auto reg_check = dynamic_cast<IR_Register*>(branch->condition.src0.get());
|
||||
assert(reg_check);
|
||||
auto reg_read = dynamic_cast<IR_Register*>(branch->branch_delay.source.get());
|
||||
assert(reg_read);
|
||||
return reg_check->reg == reg_read->reg;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Try to convert a short circuit to an and.
|
||||
*/
|
||||
bool try_clean_up_sc_as_and(std::shared_ptr<IR_ShortCircuit>& ir, LinkedObjectFile& file) {
|
||||
Register destination;
|
||||
std::shared_ptr<IR> ir_dest = nullptr;
|
||||
for (int i = 0; i < int(ir->entries.size()) - 1; i++) {
|
||||
auto branch = get_condition_branch(&ir->entries.at(i).condition);
|
||||
assert(branch.first);
|
||||
if (!delay_slot_sets_false(branch.first)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
ir_dest = branch.first->branch_delay.destination;
|
||||
destination = dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get())->reg;
|
||||
} else {
|
||||
if (destination !=
|
||||
dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get())->reg) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ir->kind = IR_ShortCircuit::AND;
|
||||
ir->final_result = ir_dest;
|
||||
|
||||
// now get rid of the branches
|
||||
for (int i = 0; i < int(ir->entries.size()) - 1; i++) {
|
||||
auto branch = get_condition_branch(&ir->entries.at(i).condition);
|
||||
assert(branch.first);
|
||||
auto replacement = std::make_shared<IR_Compare>(branch.first->condition);
|
||||
replacement->condition.invert();
|
||||
*(branch.second) = replacement;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Try to convert a short circuit to an or.
|
||||
* Note - this will convert an and to a very strange or, so always use the try as and first.
|
||||
*/
|
||||
bool try_clean_up_sc_as_or(std::shared_ptr<IR_ShortCircuit>& ir, LinkedObjectFile& file) {
|
||||
Register destination;
|
||||
for (int i = 0; i < int(ir->entries.size()) - 1; i++) {
|
||||
auto branch = get_condition_branch(&ir->entries.at(i).condition);
|
||||
assert(branch.first);
|
||||
if (!delay_slot_sets_truthy(branch.first)) {
|
||||
printf("reject %s\n", branch.first->print(file).c_str());
|
||||
return false;
|
||||
}
|
||||
assert(dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get()));
|
||||
|
||||
if (i == 0) {
|
||||
destination = dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get())->reg;
|
||||
} else {
|
||||
if (destination !=
|
||||
dynamic_cast<IR_Register*>(branch.first->branch_delay.destination.get())->reg) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ir->kind = IR_ShortCircuit::OR;
|
||||
|
||||
// todo write the destination somewhere...
|
||||
return true;
|
||||
}
|
||||
|
||||
void clean_up_sc(std::shared_ptr<IR_ShortCircuit>& ir, LinkedObjectFile& file) {
|
||||
(void)file;
|
||||
assert(ir->entries.size() > 1);
|
||||
if (!try_clean_up_sc_as_and(ir, file)) {
|
||||
if (!try_clean_up_sc_as_or(ir, file)) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* A GOAL comparison which produces a boolean is recognized as a cond-no-else by the CFG analysis.
|
||||
* But it should not be decompiled as a branching statement.
|
||||
@@ -153,11 +289,6 @@ void convert_cond_no_else_to_compare(std::shared_ptr<IR>* ir) {
|
||||
|
||||
auto condition_as_single = dynamic_cast<IR_Branch*>(cne->entries.front().condition.get());
|
||||
if (condition_as_single) {
|
||||
// as far as I can tell this is totally valid but just happens to not appear?
|
||||
// if this case is ever hit in the future it's fine and we just need to implement this.
|
||||
// but leaving empty for now so there's fewer things to test.
|
||||
// assert(false);
|
||||
|
||||
auto replacement = std::make_shared<IR_Set>(
|
||||
IR_Set::REG_64, dst, std::make_shared<IR_Compare>(condition.first->condition));
|
||||
*ir = replacement;
|
||||
@@ -212,6 +343,7 @@ void clean_up_cond_no_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
|
||||
}
|
||||
|
||||
auto replacement = std::make_shared<IR_Compare>(jump_to_next.first->condition);
|
||||
replacement->condition.invert();
|
||||
*(jump_to_next.second) = replacement;
|
||||
e.cleaned = true;
|
||||
|
||||
@@ -282,12 +414,19 @@ bool is_int_math_3(IR* ir,
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Are these IR's both the same register? False if either is not a register.
|
||||
*/
|
||||
bool is_same_reg(IR* a, IR* b) {
|
||||
auto ar = dynamic_cast<IR_Register*>(a);
|
||||
auto br = dynamic_cast<IR_Register*>(b);
|
||||
return ar && br && ar->reg == br->reg;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Try to convert this SC Vertex into an abs (integer).
|
||||
* Will return a converted abs IR if successful, or nullptr if its not possible
|
||||
*/
|
||||
std::shared_ptr<IR> try_sc_as_abs(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) {
|
||||
if (vtx->entries.size() != 1) {
|
||||
return nullptr;
|
||||
@@ -698,6 +837,7 @@ std::shared_ptr<IR> cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx)
|
||||
entries.push_back(e);
|
||||
}
|
||||
auto result = std::make_shared<IR_ShortCircuit>(entries);
|
||||
clean_up_sc(result, file);
|
||||
// todo clean these into real and/or.
|
||||
return result;
|
||||
} else if (dynamic_cast<CondNoElse*>(vtx)) {
|
||||
|
||||
@@ -407,8 +407,10 @@ int Condition::num_args() const {
|
||||
case GREATER_THAN_ZERO_SIGNED:
|
||||
case GEQ_ZERO_SIGNED:
|
||||
case LESS_THAN_ZERO:
|
||||
case LEQ_ZERO_SIGNED:
|
||||
return 1;
|
||||
case ALWAYS:
|
||||
case NEVER:
|
||||
return 0;
|
||||
default:
|
||||
assert(false);
|
||||
@@ -425,6 +427,85 @@ void Condition::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
}
|
||||
}
|
||||
|
||||
void Condition::invert() {
|
||||
switch (kind) {
|
||||
case NOT_EQUAL:
|
||||
kind = EQUAL;
|
||||
break;
|
||||
case EQUAL:
|
||||
kind = NOT_EQUAL;
|
||||
break;
|
||||
case LESS_THAN_SIGNED:
|
||||
kind = GEQ_SIGNED;
|
||||
break;
|
||||
case GREATER_THAN_SIGNED:
|
||||
kind = LEQ_SIGNED;
|
||||
break;
|
||||
case LEQ_SIGNED:
|
||||
kind = GREATER_THAN_SIGNED;
|
||||
break;
|
||||
case GEQ_SIGNED:
|
||||
kind = LESS_THAN_SIGNED;
|
||||
break;
|
||||
case GREATER_THAN_ZERO_SIGNED:
|
||||
kind = LEQ_ZERO_SIGNED;
|
||||
break;
|
||||
case LEQ_ZERO_SIGNED:
|
||||
kind = GREATER_THAN_ZERO_SIGNED;
|
||||
break;
|
||||
case LESS_THAN_ZERO:
|
||||
kind = GEQ_ZERO_SIGNED;
|
||||
break;
|
||||
case GEQ_ZERO_SIGNED:
|
||||
kind = LESS_THAN_ZERO;
|
||||
break;
|
||||
case LESS_THAN_UNSIGNED:
|
||||
kind = GEQ_UNSIGNED;
|
||||
break;
|
||||
case GREATER_THAN_UNSIGNED:
|
||||
kind = LEQ_UNSIGNED;
|
||||
break;
|
||||
case LEQ_UNSIGNED:
|
||||
kind = GREATER_THAN_UNSIGNED;
|
||||
break;
|
||||
case GEQ_UNSIGNED:
|
||||
kind = LESS_THAN_UNSIGNED;
|
||||
break;
|
||||
case ZERO:
|
||||
kind = NONZERO;
|
||||
break;
|
||||
case NONZERO:
|
||||
kind = ZERO;
|
||||
break;
|
||||
case FALSE:
|
||||
kind = TRUTHY;
|
||||
break;
|
||||
case TRUTHY:
|
||||
kind = FALSE;
|
||||
break;
|
||||
case ALWAYS:
|
||||
kind = NEVER;
|
||||
break;
|
||||
case NEVER:
|
||||
kind = ALWAYS;
|
||||
break;
|
||||
case FLOAT_EQUAL:
|
||||
kind = FLOAT_NOT_EQUAL;
|
||||
break;
|
||||
case FLOAT_NOT_EQUAL:
|
||||
kind = FLOAT_EQUAL;
|
||||
break;
|
||||
case FLOAT_LESS_THAN:
|
||||
kind = FLOAT_GEQ;
|
||||
break;
|
||||
case FLOAT_GEQ:
|
||||
kind = FLOAT_LESS_THAN;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object Condition::to_form(const LinkedObjectFile& file) const {
|
||||
int nargs = num_args();
|
||||
std::string condtion_operator;
|
||||
@@ -474,6 +555,9 @@ goos::Object Condition::to_form(const LinkedObjectFile& file) const {
|
||||
case ALWAYS:
|
||||
condtion_operator = "'#t";
|
||||
break;
|
||||
case NEVER:
|
||||
condtion_operator = "'#f";
|
||||
break;
|
||||
case FLOAT_EQUAL:
|
||||
condtion_operator = "=.f";
|
||||
break;
|
||||
@@ -495,6 +579,9 @@ goos::Object Condition::to_form(const LinkedObjectFile& file) const {
|
||||
case LESS_THAN_ZERO:
|
||||
condtion_operator = "<0.si";
|
||||
break;
|
||||
case LEQ_ZERO_SIGNED:
|
||||
condtion_operator = "<=0.si";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
@@ -648,6 +735,8 @@ goos::Object IR_Cond::to_form(const LinkedObjectFile& file) const {
|
||||
return pretty_print::build_list(list);
|
||||
} else if (entries.size() == 1) {
|
||||
// turn into a when if the body requires multiple forms
|
||||
// todo check to see if the condition starts with a NOT and this can be simplified to an
|
||||
// unless.
|
||||
std::vector<goos::Object> list;
|
||||
list.push_back(pretty_print::to_symbol("when"));
|
||||
list.push_back(entries.front().condition->to_form(file));
|
||||
|
||||
@@ -196,6 +196,7 @@ struct Condition {
|
||||
LEQ_SIGNED,
|
||||
GEQ_SIGNED,
|
||||
GREATER_THAN_ZERO_SIGNED,
|
||||
LEQ_ZERO_SIGNED,
|
||||
LESS_THAN_ZERO,
|
||||
GEQ_ZERO_SIGNED,
|
||||
LESS_THAN_UNSIGNED,
|
||||
@@ -207,6 +208,7 @@ struct Condition {
|
||||
FALSE,
|
||||
TRUTHY,
|
||||
ALWAYS,
|
||||
NEVER,
|
||||
FLOAT_EQUAL,
|
||||
FLOAT_NOT_EQUAL,
|
||||
FLOAT_LESS_THAN,
|
||||
@@ -232,6 +234,7 @@ struct Condition {
|
||||
goos::Object to_form(const LinkedObjectFile& file) const;
|
||||
std::shared_ptr<IR> src0, src1, clobber;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const;
|
||||
void invert();
|
||||
};
|
||||
|
||||
class IR_Branch : public IR {
|
||||
@@ -343,6 +346,8 @@ class IR_ShortCircuit : public IR {
|
||||
|
||||
enum Kind { UNKNOWN, AND, OR } kind = UNKNOWN;
|
||||
|
||||
std::shared_ptr<IR> final_result = nullptr; // the register that the final result goes in.
|
||||
|
||||
std::vector<Entry> entries;
|
||||
explicit IR_ShortCircuit(std::vector<Entry> _entries) : entries(std::move(_entries)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"master-is-hopeful-better?",
|
||||
|
||||
// fails for unknown reason
|
||||
"target-falling-anim-trans",
|
||||
"target-falling-anim-trans", "change-brother",
|
||||
|
||||
// these are all valid, but use short circuiting branches in strange ways. There's probably a few compiler uses that we're not
|
||||
"(method 21 actor-link-info)","(method 20 actor-link-info)","(method 28 collide-shape-prim-mesh)", "(method 35 collide-shape)",
|
||||
@@ -52,6 +52,7 @@
|
||||
// real asm
|
||||
"cspace<-parented-transformq-joint!", "blerc-a-fragment", "render-boundary-tri", "render-boundary-quad",
|
||||
"(method 19 collide-shape-prim-sphere)","vector-segment-distance-point!", "exp", "(method 11 collide-mesh-cache)",
|
||||
"(method 13 collide-edge-work)", "ambient-inspect",
|
||||
|
||||
"(method 11 cpu-thread)", "atan0", "sincos!", "sincos-rad!", "disasm-dma-list", "vblank-handler", "vif1-handler",
|
||||
"vif1-handler-debug", "entity-actor-count", "decompress-frame-data-pair-to-accumulator",
|
||||
@@ -60,7 +61,7 @@
|
||||
"generic-tie-decompress", "matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!", "generic-prepare-dma-single",
|
||||
"(method 13 collide-shape-prim-sphere)", "(method 14 collide-shape-prim-sphere)", "(method 12 collide-shape-prim-sphere)",
|
||||
"adgif-shader<-texture-with-update!", "generic-interp-dproc", "sprite-draw-distorters", "draw-bones", "(method 9 collide-mesh-cache)",
|
||||
"(method 18 collide-shape-prim-sphere)",
|
||||
"(method 18 collide-shape-prim-sphere)","birth-pickup-at-point",
|
||||
|
||||
"collide-do-primitives", "draw-bones-check-longest-edge-asm",
|
||||
"sp-launch-particles-var", "(method 15 collide-shape-prim-mesh)", "(method 15 collide-shape-prim-sphere)",
|
||||
|
||||
Reference in New Issue
Block a user