diff --git a/decompiler/IR/BasicOpBuilder.cpp b/decompiler/IR/BasicOpBuilder.cpp index 63ac38dc1b..2ac138c610 100644 --- a/decompiler/IR/BasicOpBuilder.cpp +++ b/decompiler/IR/BasicOpBuilder.cpp @@ -752,6 +752,10 @@ std::shared_ptr try_bnel(Instruction& instr, Instruction& next_instr, int id return std::make_shared( 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( + 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::NOT_EQUAL, instr.get_src(2).get_label(), // make_reg(instr.get_src(0).get_reg(), idx), diff --git a/decompiler/IR/CfgBuilder.cpp b/decompiler/IR/CfgBuilder.cpp index bffa16626d..adb4a132c5 100644 --- a/decompiler/IR/CfgBuilder.cpp +++ b/decompiler/IR/CfgBuilder.cpp @@ -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 cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx); @@ -107,6 +118,7 @@ void clean_up_cond_with_else(std::shared_ptr* 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(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, 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(branch->condition.src0.get()); + assert(reg_check); + auto reg_read = dynamic_cast(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(branch->condition.src0.get()); + assert(reg_check); + auto reg_read = dynamic_cast(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, LinkedObjectFile& file) { + Register destination; + std::shared_ptr 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(branch.first->branch_delay.destination.get())->reg; + } else { + if (destination != + dynamic_cast(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(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, 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(branch.first->branch_delay.destination.get())); + + if (i == 0) { + destination = dynamic_cast(branch.first->branch_delay.destination.get())->reg; + } else { + if (destination != + dynamic_cast(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, 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) { auto condition_as_single = dynamic_cast(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::REG_64, dst, std::make_shared(condition.first->condition)); *ir = replacement; @@ -212,6 +343,7 @@ void clean_up_cond_no_else(std::shared_ptr* ir, LinkedObjectFile& file) { } auto replacement = std::make_shared(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(a); auto br = dynamic_cast(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 try_sc_as_abs(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) { if (vtx->entries.size() != 1) { return nullptr; @@ -698,6 +837,7 @@ std::shared_ptr cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx) entries.push_back(e); } auto result = std::make_shared(entries); + clean_up_sc(result, file); // todo clean these into real and/or. return result; } else if (dynamic_cast(vtx)) { diff --git a/decompiler/IR/IR.cpp b/decompiler/IR/IR.cpp index 8635f8e8a3..f2bce71428 100644 --- a/decompiler/IR/IR.cpp +++ b/decompiler/IR/IR.cpp @@ -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>* 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 list; list.push_back(pretty_print::to_symbol("when")); list.push_back(entries.front().condition->to_form(file)); diff --git a/decompiler/IR/IR.h b/decompiler/IR/IR.h index 44fbe0545e..7d358bd379 100644 --- a/decompiler/IR/IR.h +++ b/decompiler/IR/IR.h @@ -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 src0, src1, clobber; void get_children(std::vector>* 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 final_result = nullptr; // the register that the final result goes in. + std::vector entries; explicit IR_ShortCircuit(std::vector _entries) : entries(std::move(_entries)) {} goos::Object to_form(const LinkedObjectFile& file) const override; diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index 0247b6ca38..3697fcaab8 100644 --- a/decompiler/config/jak1_ntsc_black_label.jsonc +++ b/decompiler/config/jak1_ntsc_black_label.jsonc @@ -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)",