From 4b90634e93b8a67b81853239049840fd13ad8545 Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Oct 2020 10:44:28 -0400 Subject: [PATCH] fix ash showing up as sc --- decompiler/IR/BasicOpBuilder.cpp | 33 ++++ decompiler/IR/CfgBuilder.cpp | 162 ++++++++++++++++-- decompiler/IR/IR.cpp | 28 ++- decompiler/IR/IR.h | 27 ++- decompiler/config/jak1_ntsc_black_label.jsonc | 2 +- 5 files changed, 232 insertions(+), 20 deletions(-) diff --git a/decompiler/IR/BasicOpBuilder.cpp b/decompiler/IR/BasicOpBuilder.cpp index bf3eaacaad..9d6dfb2d10 100644 --- a/decompiler/IR/BasicOpBuilder.cpp +++ b/decompiler/IR/BasicOpBuilder.cpp @@ -515,6 +515,17 @@ std::shared_ptr try_sll(Instruction& instr, int idx) { return nullptr; } +std::shared_ptr try_dsrav(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::DSRAV, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::RIGHT_SHIFT_ARITH, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + std::shared_ptr try_sw(Instruction& instr, int idx) { if (instr.kind == InstructionKind::SW && instr.get_src(1).is_sym() && instr.get_src(2).is_reg(make_gpr(Reg::S7))) { @@ -706,6 +717,12 @@ BranchDelay get_branch_delay(Instruction& i, int idx) { b.destination = make_reg(i.get_dst(0).get_reg(), idx); return b; } + } else if (i.kind == InstructionKind::DSLLV) { + BranchDelay b(BranchDelay::DSLLV); + b.destination = make_reg(i.get_dst(0).get_reg(), idx); + b.source = make_reg(i.get_src(0).get_reg(), idx); + b.source2 = make_reg(i.get_src(1).get_reg(), idx); + return b; } BranchDelay b(BranchDelay::UNKNOWN); return b; @@ -792,6 +809,16 @@ std::shared_ptr try_bgtzl(Instruction& instr, Instruction& next_instr, int i return nullptr; } +std::shared_ptr try_bgezl(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::BGEZL) { + return std::make_shared( + Condition(Condition::GEQ_ZERO_SIGNED, make_reg(instr.get_src(0).get_reg(), idx), nullptr, + nullptr), + instr.get_src(1).get_label(), get_branch_delay(next_instr, idx), true); + } + return nullptr; +} + std::shared_ptr try_daddiu(Instruction& i0, Instruction& i1, int idx) { if (i0.kind == InstructionKind::DADDIU && i1.kind == InstructionKind::MOVN && i0.get_src(0).get_reg() == make_gpr(Reg::S7)) { @@ -1249,6 +1276,9 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec case InstructionKind::BGTZL: result = try_bgtzl(i, next, instr); break; + case InstructionKind::BGEZL: + result = try_bgezl(i, next, instr); + break; case InstructionKind::BEQL: result = try_beql(i, next, instr); break; @@ -1419,6 +1449,9 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec case InstructionKind::CVTSW: result = try_cvtsw(i, instr); break; + case InstructionKind::DSRAV: + result = try_dsrav(i, instr); + break; default: result = nullptr; } diff --git a/decompiler/IR/CfgBuilder.cpp b/decompiler/IR/CfgBuilder.cpp index efb875b2c1..51d6b23176 100644 --- a/decompiler/IR/CfgBuilder.cpp +++ b/decompiler/IR/CfgBuilder.cpp @@ -1,7 +1,10 @@ +#include "third-party/fmt/format.h" #include +#include "common/util/MatchParam.h" #include "CfgBuilder.h" #include "decompiler/Function/CfgVtx.h" #include "decompiler/Function/Function.h" +#include "decompiler/Disasm/InstructionMatching.h" namespace { @@ -130,11 +133,8 @@ void clean_up_cond_with_else(std::shared_ptr* ir, LinkedObjectFile& file) { } } -/* - * before we do this we'll have to to recognize ash... - bool try_clean_up_sc_as_and(const std::shared_ptr& ir) { - for(size_t i = 0; i < ir->entries.size(); i++) { + for (size_t i = 0; i < ir->entries.size(); i++) { auto& e = ir->entries.at(i); if (i < ir->entries.size() - 1) { // check we load the delay slot with false @@ -142,16 +142,15 @@ bool try_clean_up_sc_as_and(const std::shared_ptr& ir) { assert(branch.first); } } -// ir->kind = IR_ShortCircuit::AND; -// return true; + // ir->kind = IR_ShortCircuit::AND; + // return true; return false; } void clean_up_sc(std::shared_ptr ir) { assert(ir->entries.size() > 1); - //try_clean_up_sc_as_and(ir); + // try_clean_up_sc_as_and(ir); } - */ /*! * A GOAL comparison which produces a boolean is recognized as a cond-no-else by the CFG analysis. @@ -252,6 +251,143 @@ void clean_up_cond_no_else(std::shared_ptr* ir, LinkedObjectFile& file) { } } +bool is_int_math_3(IR* ir, + MatchParam kind, + MatchParam dst, + MatchParam src0, + MatchParam 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(ir); + if (!set) { + return false; + } + + // destination should be a register + auto dest = dynamic_cast(set->dst.get()); + if (!dest || dst != dest->reg) { + return false; + } + + auto math = dynamic_cast(set->src.get()); + if (!math || kind != math->kind) { + return false; + } + + auto arg0 = dynamic_cast(math->arg0.get()); + auto arg1 = dynamic_cast(math->arg1.get()); + + if (!arg0 || src0 != arg0->reg || !arg1 || src1 != arg1->reg) { + return false; + } + + // it's a match! + if (dst_out) { + *dst_out = dest->reg; + } + + if (src0_out) { + *src0_out = arg0->reg; + } + + if (src1_out) { + *src1_out = arg1->reg; + } + return true; +} + +std::shared_ptr try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) { + if (vtx->entries.size() != 2) { + return nullptr; + } + + // todo, I think b0 could possibly be something more complicated, depending on how we order. + auto b0 = dynamic_cast(vtx->entries.at(0)); + auto b1 = dynamic_cast(vtx->entries.at(1)); + if (!b0 || !b1) { + return nullptr; + } + + auto b0_ptr = cfg_to_ir(f, file, b0); + auto b0_ir = dynamic_cast(b0_ptr.get()); + + auto b1_ptr = cfg_to_ir(f, file, b1); + auto b1_ir = dynamic_cast(b1_ptr.get()); + + if (!b0_ir || !b1_ir) { + return nullptr; + } + + auto branch = dynamic_cast(b0_ir->forms.back().get()); + if (!branch || b1_ir->forms.size() != 2) { + return nullptr; + } + + // check the branch instruction + if (!branch->likely || branch->condition.kind != Condition::GEQ_ZERO_SIGNED || + branch->branch_delay.kind != BranchDelay::DSLLV) { + return nullptr; + } + + /* + * bgezl s5, L109 ; s5 is the shift amount + dsllv a0, a0, s5 ; a0 is both input and output here + + dsubu a1, r0, s5 ; a1 is a temp here + dsrav a0, a0, a1 ; a0 is both input and output here + */ + + auto sa_in = dynamic_cast(branch->condition.src0.get()); + assert(sa_in); + auto result = dynamic_cast(branch->branch_delay.destination.get()); + auto value_in = dynamic_cast(branch->branch_delay.source.get()); + auto sa_in2 = dynamic_cast(branch->branch_delay.source2.get()); + assert(result && value_in && sa_in2); + assert(sa_in->reg == sa_in2->reg); + + auto dsubu_candidate = b1_ir->forms.at(0); + auto dsrav_candidate = b1_ir->forms.at(1); + + Register clobber; + if (!is_int_math_3(dsubu_candidate.get(), IR_IntMath2::SUB, {}, make_gpr(Reg::R0), sa_in->reg, + &clobber)) { + return nullptr; + } + + assert(result); + assert(value_in); + if (!is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_ARITH, result->reg, + value_in->reg, clobber)) { + return nullptr; + } + + std::shared_ptr clobber_ir = nullptr; + auto dsubu_set = dynamic_cast(dsubu_candidate.get()); + auto dsrav_set = dynamic_cast(dsrav_candidate.get()); + if (clobber != result->reg) { + clobber_ir = dsubu_set->dst; + } + + std::shared_ptr dest_ir = branch->branch_delay.destination; + std::shared_ptr shift_ir = branch->condition.src0; + std::shared_ptr value_ir = dynamic_cast(dsrav_set->src.get())->arg0; + if (b0_ir->forms.size() == 1) { + // this is probably fine but happens to not occur in anything we try yet. + assert(false); + } else { + // remove the branch + b0_ir->forms.pop_back(); + // add the ash + b0_ir->forms.push_back(std::make_shared( + IR_Set::REG_64, dest_ir, std::make_shared(shift_ir, value_ir, clobber_ir))); + return b0_ptr; + } + + return nullptr; +} + /*! * Try to convert a short circuiting expression into a "type-of" expression. * We do this before attempting the normal and/or expressions. @@ -363,7 +499,7 @@ std::shared_ptr try_sc_as_type_of(Function& f, LinkedObjectFile& file, Short assert(offset->value == -4); std::shared_ptr clobber = nullptr; - if (temp_reg->reg != src_reg->reg && temp_reg->reg != dst_reg->reg) { + if (temp_reg->reg != dst_reg->reg) { clobber = first_branch->condition.src0; } if (b0_ir->forms.size() == 2) { @@ -503,7 +639,13 @@ std::shared_ptr cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx) if (as_type_of) { return as_type_of; } + + auto as_ash = try_sc_as_ash(f, file, svtx); + if (as_ash) { + return as_ash; + } // now try as a normal and/or + std::vector entries; for (auto& x : svtx->entries) { IR_ShortCircuit::Entry e; @@ -511,7 +653,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); + // clean_up_sc(result); return result; } else if (dynamic_cast(vtx)) { auto* cvtx = dynamic_cast(vtx); diff --git a/decompiler/IR/IR.cpp b/decompiler/IR/IR.cpp index 3fc59f7b00..1ac422c651 100644 --- a/decompiler/IR/IR.cpp +++ b/decompiler/IR/IR.cpp @@ -19,12 +19,6 @@ std::vector> IR::get_all_ir(LinkedObjectFile& file) const { last_checked = end_of_check; } - // Todo, remove this check which is just for debugging. - std::unordered_set> unique_ir; - for (auto& x : result) { - unique_ir.insert(x); - } - assert(unique_ir.size() == result.size()); return result; } @@ -343,6 +337,9 @@ std::shared_ptr
BranchDelay::to_form(const LinkedObjectFile& file) const { return buildList(toForm("set!"), destination->to_form(file), "binteger"); case SET_PAIR: return buildList(toForm("set!"), destination->to_form(file), "pair"); + case DSLLV: + return buildList(toForm("set!"), destination->to_form(file), + buildList("shl", source->to_form(file), source2->to_form(file))); case UNKNOWN: return buildList("unknown-branch-delay"); default: @@ -358,6 +355,10 @@ void BranchDelay::get_children(std::vector>* output) const { if (source) { output->push_back(source); } + + if (source2) { + output->push_back(source2); + } } std::shared_ptr IR_Nop::to_form(const LinkedObjectFile& file) const { @@ -387,6 +388,7 @@ int Condition::num_args() const { case FALSE: case TRUTHY: case GREATER_THAN_ZERO_SIGNED: + case GEQ_ZERO_SIGNED: return 1; case ALWAYS: return 0; @@ -467,7 +469,10 @@ std::shared_ptr Condition::to_form(const LinkedObjectFile& file) const { condtion_operator = ">=.f"; break; case GREATER_THAN_ZERO_SIGNED: - condtion_operator = ">0.s"; + condtion_operator = ">0.si"; + break; + case GEQ_ZERO_SIGNED: + condtion_operator = ">=0.si"; break; default: assert(false); @@ -675,3 +680,12 @@ void IR_ShortCircuit::get_children(std::vector>* output) con } } } + +std::shared_ptr IR_Ash::to_form(const LinkedObjectFile& file) const { + return buildList("ash", value->to_form(file), shift_amount->to_form(file)); +} + +void IR_Ash::get_children(std::vector>* output) const { + output->push_back(value); + output->push_back(shift_amount); +} \ No newline at end of file diff --git a/decompiler/IR/IR.h b/decompiler/IR/IR.h index 0b51f2df4b..f362537eb9 100644 --- a/decompiler/IR/IR.h +++ b/decompiler/IR/IR.h @@ -170,8 +170,17 @@ class IR_IntegerConstant : public IR { }; struct BranchDelay { - enum Kind { NOP, SET_REG_FALSE, SET_REG_TRUE, SET_REG_REG, SET_BINTEGER, SET_PAIR, UNKNOWN } kind; - std::shared_ptr destination = nullptr, source = nullptr; + enum Kind { + NOP, + SET_REG_FALSE, + SET_REG_TRUE, + SET_REG_REG, + SET_BINTEGER, + SET_PAIR, + DSLLV, + UNKNOWN + } kind; + std::shared_ptr destination = nullptr, source = nullptr, source2 = nullptr; explicit BranchDelay(Kind _kind) : kind(_kind) {} std::shared_ptr to_form(const LinkedObjectFile& file) const; void get_children(std::vector>* output) const; @@ -186,6 +195,7 @@ struct Condition { LEQ_SIGNED, GEQ_SIGNED, GREATER_THAN_ZERO_SIGNED, + GEQ_ZERO_SIGNED, LESS_THAN_UNSIGNED, GREATER_THAN_UNSIGNED, LEQ_UNSIGNED, @@ -337,4 +347,17 @@ class IR_ShortCircuit : public IR { void get_children(std::vector>* output) const override; }; +class IR_Ash : public IR { + public: + std::shared_ptr shift_amount, value, clobber; + IR_Ash(std::shared_ptr _shift_amount, + std::shared_ptr _value, + std::shared_ptr _clobber) + : shift_amount(std::move(_shift_amount)), + value(std::move(_value)), + clobber(std::move(_clobber)) {} + std::shared_ptr to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* output) const override; +}; + #endif // JAK_IR_H diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index 666119a079..9e9bec5d96 100644 --- a/decompiler/config/jak1_ntsc_black_label.jsonc +++ b/decompiler/config/jak1_ntsc_black_label.jsonc @@ -30,7 +30,7 @@ "asm_functions_by_name":[ // gcommon - "ash", "abs", "min", "max", "(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!", + "abs", "min", "max", "(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!", // pskernel "resend-exception", "kernel-set-interrupt-vector", "kernel-set-exception-vector", "return-from-exception",