From f70ae6dad38898980f8d796b6cd1af33f9ca3d7e Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Oct 2020 11:37:47 -0400 Subject: [PATCH] add abs --- decompiler/IR/BasicOpBuilder.cpp | 18 ++++ decompiler/IR/CfgBuilder.cpp | 87 ++++++++++++++----- decompiler/IR/IR.cpp | 10 +++ decompiler/IR/IR.h | 4 +- decompiler/config/jak1_ntsc_black_label.jsonc | 10 ++- 5 files changed, 104 insertions(+), 25 deletions(-) diff --git a/decompiler/IR/BasicOpBuilder.cpp b/decompiler/IR/BasicOpBuilder.cpp index 9d6dfb2d10..8010fca21e 100644 --- a/decompiler/IR/BasicOpBuilder.cpp +++ b/decompiler/IR/BasicOpBuilder.cpp @@ -723,6 +723,11 @@ BranchDelay get_branch_delay(Instruction& i, int 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; + } else if (is_gpr_3(i, InstructionKind::DSUBU, {}, make_gpr(Reg::R0), {})) { + BranchDelay b(BranchDelay::NEGATE); + b.destination = make_reg(i.get_dst(0).get_reg(), idx); + b.source = make_reg(i.get_src(1).get_reg(), idx); + return b; } BranchDelay b(BranchDelay::UNKNOWN); return b; @@ -819,6 +824,16 @@ std::shared_ptr try_bgezl(Instruction& instr, Instruction& next_instr, int i return nullptr; } +std::shared_ptr try_bltzl(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::BLTZL) { + return std::make_shared( + Condition(Condition::LESS_THAN_ZERO, 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)) { @@ -1279,6 +1294,9 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec case InstructionKind::BGEZL: result = try_bgezl(i, next, instr); break; + case InstructionKind::BLTZL: + result = try_bltzl(i, next, instr); + break; case InstructionKind::BEQL: result = try_beql(i, next, instr); break; diff --git a/decompiler/IR/CfgBuilder.cpp b/decompiler/IR/CfgBuilder.cpp index 51d6b23176..bffa16626d 100644 --- a/decompiler/IR/CfgBuilder.cpp +++ b/decompiler/IR/CfgBuilder.cpp @@ -133,25 +133,6 @@ void clean_up_cond_with_else(std::shared_ptr* ir, LinkedObjectFile& file) { } } -bool try_clean_up_sc_as_and(const std::shared_ptr& ir) { - 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 - auto branch = get_condition_branch(&e.condition); - assert(branch.first); - } - } - // 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); -} - /*! * 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. @@ -251,6 +232,9 @@ void clean_up_cond_no_else(std::shared_ptr* ir, LinkedObjectFile& file) { } } +/*! + * Match for a (set! reg (math reg reg)) form + */ bool is_int_math_3(IR* ir, MatchParam kind, MatchParam dst, @@ -298,6 +282,62 @@ bool is_int_math_3(IR* ir, return true; } +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; +} + +std::shared_ptr try_sc_as_abs(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) { + if (vtx->entries.size() != 1) { + return nullptr; + } + + auto b0 = dynamic_cast(vtx->entries.at(0)); + if (!b0) { + return nullptr; + } + + // todo, seems possible to be a single op instead of a begin here. + auto b0_ptr = cfg_to_ir(f, file, b0); + auto b0_ir = dynamic_cast(b0_ptr.get()); + + auto branch = dynamic_cast(b0_ir->forms.back().get()); + if (!branch) { + return nullptr; + } + + // check the branch instruction + if (!branch->likely || branch->condition.kind != Condition::LESS_THAN_ZERO || + branch->branch_delay.kind != BranchDelay::NEGATE) { + return nullptr; + } + + auto input = branch->condition.src0; + auto output = branch->branch_delay.destination; + + assert(is_same_reg(input.get(), branch->branch_delay.source.get())); + + 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, output, std::make_shared(IR_IntMath1::ABS, input))); + return b0_ptr; + } + + return nullptr; +} + +/*! + * Attempt to convert a short circuit expression into an arithmetic shift. + * GOAL's shift function accepts positive/negative numbers to determine the direction + * of the shift. + */ std::shared_ptr try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) { if (vtx->entries.size() != 2) { return nullptr; @@ -310,6 +350,7 @@ std::shared_ptr try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCirc return nullptr; } + // todo, seems possible to be a single op instead of a begin... auto b0_ptr = cfg_to_ir(f, file, b0); auto b0_ir = dynamic_cast(b0_ptr.get()); @@ -644,8 +685,12 @@ std::shared_ptr cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx) if (as_ash) { return as_ash; } - // now try as a normal and/or + auto as_abs = try_sc_as_abs(f, file, svtx); + if (as_abs) { + return as_abs; + } + // now try as a normal and/or std::vector entries; for (auto& x : svtx->entries) { IR_ShortCircuit::Entry e; @@ -653,7 +698,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); + // todo clean these into real and/or. 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 1ac422c651..35746bc254 100644 --- a/decompiler/IR/IR.cpp +++ b/decompiler/IR/IR.cpp @@ -270,6 +270,9 @@ std::shared_ptr
IR_IntMath1::to_form(const LinkedObjectFile& file) const { case NOT: math_operator = "lognot"; break; + case ABS: + math_operator = "abs.si"; + break; default: assert(false); } @@ -340,6 +343,9 @@ std::shared_ptr BranchDelay::to_form(const LinkedObjectFile& file) const { case DSLLV: return buildList(toForm("set!"), destination->to_form(file), buildList("shl", source->to_form(file), source2->to_form(file))); + case NEGATE: + return buildList(toForm("set!"), destination->to_form(file), + buildList("-", source->to_form(file))); case UNKNOWN: return buildList("unknown-branch-delay"); default: @@ -389,6 +395,7 @@ int Condition::num_args() const { case TRUTHY: case GREATER_THAN_ZERO_SIGNED: case GEQ_ZERO_SIGNED: + case LESS_THAN_ZERO: return 1; case ALWAYS: return 0; @@ -474,6 +481,9 @@ std::shared_ptr Condition::to_form(const LinkedObjectFile& file) const { case GEQ_ZERO_SIGNED: condtion_operator = ">=0.si"; break; + case LESS_THAN_ZERO: + condtion_operator = "<0.si"; + break; default: assert(false); } diff --git a/decompiler/IR/IR.h b/decompiler/IR/IR.h index f362537eb9..aed60dc4bc 100644 --- a/decompiler/IR/IR.h +++ b/decompiler/IR/IR.h @@ -147,7 +147,7 @@ class IR_IntMath2 : public IR { class IR_IntMath1 : public IR { public: - enum Kind { NOT } kind; + enum Kind { NOT, ABS } kind; IR_IntMath1(Kind _kind, std::shared_ptr _arg) : kind(_kind), arg(std::move(_arg)) {} std::shared_ptr arg; std::shared_ptr to_form(const LinkedObjectFile& file) const override; @@ -178,6 +178,7 @@ struct BranchDelay { SET_BINTEGER, SET_PAIR, DSLLV, + NEGATE, UNKNOWN } kind; std::shared_ptr destination = nullptr, source = nullptr, source2 = nullptr; @@ -195,6 +196,7 @@ struct Condition { LEQ_SIGNED, GEQ_SIGNED, GREATER_THAN_ZERO_SIGNED, + LESS_THAN_ZERO, GEQ_ZERO_SIGNED, LESS_THAN_UNSIGNED, GREATER_THAN_UNSIGNED, diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index 9e9bec5d96..aee6288697 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 - "abs", "min", "max", "(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!", + "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", @@ -42,12 +42,16 @@ // this one fails due to false compaction where an else case has only a not expression in it. "master-is-hopeful-better?", - // temp remove me + // fails for unknown reason "target-falling-anim-trans", + // 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)", + "debug-menu-item-var-render", "(method 14 level)","add-blue-motion","anim-tester-add-newobj","(method 27 orb-cache-top)", + // 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 19 collide-shape-prim-sphere)","vector-segment-distance-point!", "exp", "(method 11 collide-mesh-cache)", "(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",