diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index e77b784d54..1cb9af2b21 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -58,7 +58,6 @@ add_library( VuDisasm/VuDisassembler.cpp VuDisasm/VuInstruction.cpp - VuDisasm/VuProgram.cpp config.cpp) diff --git a/decompiler/Function/CfgVtx.cpp b/decompiler/Function/CfgVtx.cpp index 3a8723e8e0..2054bf9830 100644 --- a/decompiler/Function/CfgVtx.cpp +++ b/decompiler/Function/CfgVtx.cpp @@ -146,6 +146,10 @@ goos::Object BlockVtx::to_form() const { return pretty_print::to_symbol("b" + std::to_string(block_id)); } +int BlockVtx::get_first_block_id() const { + return block_id; +} + std::string SequenceVtx::to_string() const { assert(!seq.empty()); // todo - this is not a great way to print it. Maybe sequences should have an ID or name? @@ -163,6 +167,10 @@ goos::Object SequenceVtx::to_form() const { return pretty_print::build_list(forms); } +int SequenceVtx::get_first_block_id() const { + return seq.at(0)->get_first_block_id(); +} + std::string EntryVtx::to_string() const { return "ENTRY"; } @@ -171,6 +179,11 @@ goos::Object EntryVtx::to_form() const { return pretty_print::to_symbol("entry"); } +int EntryVtx::get_first_block_id() const { + assert(false); + return -1; +} + std::string ExitVtx::to_string() const { return "EXIT"; } @@ -179,6 +192,11 @@ goos::Object ExitVtx::to_form() const { return pretty_print::to_symbol("exit"); } +int ExitVtx::get_first_block_id() const { + assert(false); + return -1; +} + std::string CondWithElse::to_string() const { return "CONDWE" + std::to_string(uid); } @@ -195,6 +213,10 @@ goos::Object CondWithElse::to_form() const { return pretty_print::build_list(forms); } +int CondWithElse::get_first_block_id() const { + return entries.at(0).condition->get_first_block_id(); +} + std::string CondNoElse::to_string() const { return "CONDNE" + std::to_string(uid); } @@ -209,6 +231,10 @@ goos::Object CondNoElse::to_form() const { return pretty_print::build_list(forms); } +int CondNoElse::get_first_block_id() const { + return entries.at(0).condition->get_first_block_id(); +} + std::string WhileLoop::to_string() const { return "WHL" + std::to_string(uid); } @@ -219,6 +245,11 @@ goos::Object WhileLoop::to_form() const { return pretty_print::build_list(forms); } +int WhileLoop::get_first_block_id() const { + assert(false); + return -1; +} + std::string UntilLoop::to_string() const { return "UNTL" + std::to_string(uid); } @@ -229,6 +260,10 @@ goos::Object UntilLoop::to_form() const { return pretty_print::build_list(forms); } +int UntilLoop::get_first_block_id() const { + return condition->get_first_block_id(); +} + std::string UntilLoop_single::to_string() const { return "UNTLS" + std::to_string(uid); } @@ -238,6 +273,11 @@ goos::Object UntilLoop_single::to_form() const { return pretty_print::build_list(forms); } +int UntilLoop_single::get_first_block_id() const { + assert(false); + return -1; +} + std::string InfiniteLoopBlock::to_string() const { return "INFL" + std::to_string(uid); } @@ -247,6 +287,11 @@ goos::Object InfiniteLoopBlock::to_form() const { return pretty_print::build_list(forms); } +int InfiniteLoopBlock::get_first_block_id() const { + assert(false); + return -1; +} + std::string ShortCircuit::to_string() const { return "SC" + std::to_string(uid); } @@ -264,6 +309,10 @@ goos::Object ShortCircuit::to_form() const { return pretty_print::build_list(forms); } +int ShortCircuit::get_first_block_id() const { + return entries.at(0).condition->get_first_block_id(); +} + std::string GotoEnd::to_string() const { return "goto_end" + std::to_string(uid); } @@ -274,17 +323,25 @@ goos::Object GotoEnd::to_form() const { return pretty_print::build_list(forms); } +int GotoEnd::get_first_block_id() const { + return body->get_first_block_id(); +} + std::string Break::to_string() const { return "goto" + std::to_string(uid); } goos::Object Break::to_form() const { std::vector forms = {pretty_print::to_symbol("break"), - pretty_print::to_symbol(std::to_string(dest_block)), + pretty_print::to_symbol(std::to_string(dest_block_id)), body->to_form(), unreachable_block->to_form()}; return pretty_print::build_list(forms); } +int Break::get_first_block_id() const { + return body->get_first_block_id(); +} + std::string EmptyVtx::to_string() const { return "empty"; } @@ -293,6 +350,11 @@ goos::Object EmptyVtx::to_form() const { return pretty_print::build_list("empty"); } +int EmptyVtx::get_first_block_id() const { + assert(false); + return -1; +} + ControlFlowGraph::ControlFlowGraph() { // allocate the entry and exit vertices. m_entry = alloc(); @@ -796,9 +858,11 @@ bool ControlFlowGraph::find_goto_not_end() { replaced = true; auto* new_goto = alloc(); + m_has_break = true; new_goto->body = b0; new_goto->unreachable_block = b1; - // todo set block number + new_goto->dest_block_id = b0->succ_branch->get_first_block_id(); + m_blocks.at(new_goto->dest_block_id)->needs_label = true; for (auto* new_pred : b0->pred) { // printf("fix up pred %s of %s\n", new_pred->to_string().c_str(), diff --git a/decompiler/Function/CfgVtx.h b/decompiler/Function/CfgVtx.h index e7350bd6c9..7567d75fb9 100644 --- a/decompiler/Function/CfgVtx.h +++ b/decompiler/Function/CfgVtx.h @@ -67,6 +67,7 @@ class CfgVtx { public: virtual std::string to_string() const = 0; // convert to a single line string for debugging virtual goos::Object to_form() const = 0; // recursive print as LISP form. + virtual int get_first_block_id() const = 0; virtual ~CfgVtx() = default; CfgVtx* parent = nullptr; // parent structure, or nullptr if top level @@ -77,6 +78,8 @@ class CfgVtx { std::vector pred; // all vertices which have us as succ_branch or succ_ft int uid = -1; + bool needs_label = false; + enum class DelaySlotKind { NO_BRANCH, SET_REG_FALSE, SET_REG_TRUE, NOP, OTHER, NO_DELAY }; struct { @@ -134,6 +137,7 @@ class EntryVtx : public CfgVtx { EntryVtx() = default; goos::Object to_form() const override; std::string to_string() const override; + int get_first_block_id() const override; }; /*! @@ -143,6 +147,7 @@ class ExitVtx : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; }; /*! @@ -153,6 +158,7 @@ class BlockVtx : public CfgVtx { explicit BlockVtx(int id) : block_id(id) {} std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; int block_id = -1; // which block are we? bool is_early_exit_block = false; // are we an empty block at the end for early exits to jump to? }; @@ -165,6 +171,7 @@ class SequenceVtx : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; std::vector seq; }; @@ -177,6 +184,7 @@ class CondWithElse : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; struct Entry { Entry() = default; @@ -198,6 +206,7 @@ class CondNoElse : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; struct Entry { Entry() = default; @@ -213,6 +222,7 @@ class WhileLoop : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; CfgVtx* condition = nullptr; CfgVtx* body = nullptr; @@ -222,6 +232,7 @@ class UntilLoop : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; CfgVtx* condition = nullptr; CfgVtx* body = nullptr; @@ -231,6 +242,7 @@ class UntilLoop_single : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; CfgVtx* block = nullptr; }; @@ -239,6 +251,7 @@ class ShortCircuit : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; struct Entry { CfgVtx* condition = nullptr; CfgVtx* likely_delay = nullptr; // will be nullptr on last case @@ -250,6 +263,7 @@ class InfiniteLoopBlock : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; CfgVtx* block; }; @@ -257,6 +271,7 @@ class GotoEnd : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; CfgVtx* body = nullptr; CfgVtx* unreachable_block = nullptr; }; @@ -265,15 +280,17 @@ class Break : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; - int dest_block = -1; + int get_first_block_id() const override; CfgVtx* body = nullptr; CfgVtx* unreachable_block = nullptr; + int dest_block_id = -1; }; class EmptyVtx : public CfgVtx { public: std::string to_string() const override; goos::Object to_form() const override; + int get_first_block_id() const override; }; struct BasicBlock; @@ -292,6 +309,7 @@ class ControlFlowGraph { int get_top_level_vertices_count(); bool is_fully_resolved(); CfgVtx* get_single_top_level(); + bool contains_break() const { return m_has_break; } void flag_early_exit(const std::vector& blocks); @@ -359,6 +377,7 @@ class ControlFlowGraph { EntryVtx* m_entry; // the entry vertex ExitVtx* m_exit; // the exit vertex int m_uid = 0; + bool m_has_break = false; }; class LinkedObjectFile; diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index c9db63123b..7cb51b3963 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -308,6 +308,19 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, case Kind::RIGHT_SHIFT_ARITH: case Kind::RIGHT_SHIFT_LOGIC: { bool is_unsigned = m_kind == Kind::RIGHT_SHIFT_LOGIC; + + if (m_args[1].is_int()) { + auto bf = dynamic_cast(dts.ts.lookup_type(arg0_type.typespec())); + if (bf) { + int shift_size = 64; + int size = shift_size - m_args[1].get_int(); + int start_bit = shift_size - size; + auto field = find_field(dts.ts, bf, start_bit, size, is_unsigned); + return TP_Type::make_from_ts(field.type()); + // auto field = find_field(arg0_type.typespec(), bf, 64) + } + } + if (arg0_type.kind == TP_Type::Kind::LEFT_SHIFTED_BITFIELD && m_args[1].is_int()) { // second op in left/right shift combo int end_bit = 64 - arg0_type.get_left_shift(); diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp index 68f28d7448..e2a6315b2e 100644 --- a/decompiler/IR2/Form.cpp +++ b/decompiler/IR2/Form.cpp @@ -767,41 +767,56 @@ void ReturnElement::get_modified_regs(RegSet& regs) const { // BreakElement ///////////////////////////// -BreakElement::BreakElement(Form* _return_code, Form* _dead_code) - : return_code(_return_code), dead_code(_dead_code) { +BreakElement::BreakElement(Form* _return_code, Form* _dead_code, int _lid) + : return_code(_return_code), dead_code(_dead_code), lid(_lid) { return_code->parent_element = this; - dead_code->parent_element = this; + if (dead_code) { + dead_code->parent_element = this; + } } goos::Object BreakElement::to_form_internal(const Env& env) const { std::vector forms; - forms.push_back(pretty_print::to_symbol("break")); - forms.push_back(pretty_print::build_list(return_code->to_form(env))); - forms.push_back(pretty_print::build_list(dead_code->to_form(env))); + if (dead_code) { + forms.push_back(pretty_print::to_symbol("break")); + forms.push_back(pretty_print::build_list(return_code->to_form(env))); + forms.push_back(pretty_print::build_list(dead_code->to_form(env))); + } else { + forms.push_back(pretty_print::to_symbol("begin")); + return_code->inline_forms(forms, env); + forms.push_back(pretty_print::build_list(fmt::format("goto cfg-{}", lid))); + } return pretty_print::build_list(forms); } void BreakElement::apply(const std::function& f) { f(this); return_code->apply(f); - dead_code->apply(f); + if (dead_code) { + dead_code->apply(f); + } } void BreakElement::apply_form(const std::function& f) { return_code->apply_form(f); - dead_code->apply_form(f); + if (dead_code) { + dead_code->apply_form(f); + } } void BreakElement::collect_vars(RegAccessSet& vars, bool recursive) const { if (recursive) { return_code->collect_vars(vars, recursive); - dead_code->collect_vars(vars, recursive); + if (dead_code) { + dead_code->collect_vars(vars, recursive); + } } } void BreakElement::get_modified_regs(RegSet& regs) const { - for (auto x : {return_code, dead_code}) { - x->get_modified_regs(regs); + return_code->get_modified_regs(regs); + if (dead_code) { + dead_code->get_modified_regs(regs); } } @@ -1503,6 +1518,8 @@ std::string fixed_operator_to_string(FixedOperatorKind kind) { return "none"; case FixedOperatorKind::PCPYLD: return "make-u128"; + case FixedOperatorKind::SYMBOL_TO_STRING: + return "symbol->string"; default: assert(false); return ""; @@ -2369,6 +2386,55 @@ void MethodOfTypeElement::collect_vars(RegAccessSet& vars, bool) const { void MethodOfTypeElement::get_modified_regs(RegSet&) const {} +//////////////////////////////// +// LabelElement +/////////////////////////////// + +LabelElement::LabelElement(int lid) : m_lid(lid) {} + +goos::Object LabelElement::to_form_internal(const Env&) const { + return pretty_print::build_list(fmt::format("label cfg-{}", m_lid)); +} + +void LabelElement::apply(const std::function& f) { + f(this); +} + +void LabelElement::apply_form(const std::function&) {} +void LabelElement::collect_vars(RegAccessSet&, bool) const {} +void LabelElement::get_modified_regs(RegSet&) const {} + +//////////////////////////////// +// GetSymbolStringPointer +////////////////////////////// + +GetSymbolStringPointer::GetSymbolStringPointer(Form* src) : m_src(src) { + m_src->parent_element = this; +} + +goos::Object GetSymbolStringPointer::to_form_internal(const Env& env) const { + return pretty_print::build_list("sym->str-ptr", m_src->to_form(env)); +} + +void GetSymbolStringPointer::apply(const std::function& f) { + f(this); + m_src->apply(f); +} + +void GetSymbolStringPointer::apply_form(const std::function& f) { + m_src->apply_form(f); +} + +void GetSymbolStringPointer::collect_vars(RegAccessSet& vars, bool recursive) const { + if (recursive) { + m_src->collect_vars(vars, recursive); + } +} + +void GetSymbolStringPointer::get_modified_regs(RegSet& regs) const { + return m_src->get_modified_regs(regs); +} + //////////////////////////////// // Utilities //////////////////////////////// diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index d1c2fbc79f..070b2245d0 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -590,12 +590,14 @@ class BreakElement : public FormElement { public: Form* return_code = nullptr; Form* dead_code = nullptr; - BreakElement(Form* _return_code, Form* _dead_code); + int lid = -1; + BreakElement(Form* _return_code, Form* _dead_code, int _lid); goos::Object to_form_internal(const Env& env) const override; void apply(const std::function& f) override; void apply_form(const std::function& f) override; void collect_vars(RegAccessSet& vars, bool recursive) const override; void get_modified_regs(RegSet& regs) const override; + void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override; }; /*! @@ -1015,6 +1017,8 @@ class DerefToken { return m_kind == Kind::FIELD_NAME && m_name == name; } + bool is_int(int x) const { return m_kind == Kind::INTEGER_CONSTANT && m_int_constant == x; } + Kind kind() const { return m_kind; } const std::string& field_name() const { assert(m_kind == Kind::FIELD_NAME); @@ -1409,6 +1413,39 @@ class MethodOfTypeElement : public FormElement { MethodInfo m_method_info; }; +class LabelElement : public FormElement { + public: + LabelElement(int lid); + goos::Object to_form_internal(const Env& env) const override; + void apply(const std::function& f) override; + void apply_form(const std::function& f) override; + void collect_vars(RegAccessSet& vars, bool recursive) const override; + void get_modified_regs(RegSet& regs) const override; + void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override; + + private: + int m_lid = -1; +}; + +class GetSymbolStringPointer : public FormElement { + public: + GetSymbolStringPointer(Form* src); + goos::Object to_form_internal(const Env& env) const override; + void apply(const std::function& f) override; + void apply_form(const std::function& f) override; + void collect_vars(RegAccessSet& vars, bool recursive) const override; + void get_modified_regs(RegSet& regs) const override; + void update_from_stack(const Env& env, + FormPool& pool, + FormStack& stack, + std::vector* result, + bool allow_side_effects) override; + Form* src() { return m_src; } + + private: + Form* m_src = nullptr; +}; + /*! * A Form is a wrapper around one or more FormElements. * This is done for two reasons: diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index d3c260e2e1..d342c0487c 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -629,6 +629,15 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, // lookup types. auto arg1_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg()); auto arg0_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg()); + + // try to find symbol to string stuff + auto arg0_int = get_goal_integer_constant(args.at(0), env); + if (arg0_int && (*arg0_int == SYM_INFO_OFFSET + 4) && + arg1_type.typespec() == TypeSpec("symbol")) { + result->push_back(pool.alloc_element(args.at(1))); + return; + } + if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR) { // try to see if this is valid, from the type system. FieldReverseLookupInput input; @@ -1981,6 +1990,14 @@ void DerefElement::update_from_stack(const Env& env, // todo - update var tokens from stack? m_base->update_children_from_stack(env, pool, stack, allow_side_effects); + // look for sym->str-ptr + auto sym_str = m_base->try_as_element(); + if (sym_str && m_tokens.size() == 1 && m_tokens.at(0).is_int(0)) { + result->push_back(pool.alloc_element( + GenericOperator::make_fixed(FixedOperatorKind::SYMBOL_TO_STRING), sym_str->src())); + return; + } + // merge nested ->'s inline_nested(); @@ -3470,4 +3487,34 @@ void StackSpillValueElement::update_from_stack(const Env&, result->push_back(this); } +void GetSymbolStringPointer::update_from_stack(const Env&, + FormPool&, + FormStack&, + std::vector* result, + bool) { + mark_popped(); + result->push_back(this); +} + +void LabelElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { + mark_popped(); + stack.push_form_element(this, true); +} + +void BreakElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { + mark_popped(); + + FormStack temp_stack(false); + for (auto& elt : return_code->elts()) { + elt->push_to_stack(env, pool, temp_stack); + } + + auto new_entries = temp_stack.rewrite(pool, env); + return_code->clear(); + for (auto e : new_entries) { + return_code->push_back(e); + } + stack.push_form_element(this, true); +} + } // namespace decompiler diff --git a/decompiler/IR2/IR2_common.h b/decompiler/IR2/IR2_common.h index 353a4a721b..5a27eb7ad2 100644 --- a/decompiler/IR2/IR2_common.h +++ b/decompiler/IR2/IR2_common.h @@ -144,6 +144,7 @@ enum class FixedOperatorKind { PAIRP, NONE, PCPYLD, + SYMBOL_TO_STRING, INVALID }; diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index 1099d7a35a..39ec8389e8 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -242,13 +242,17 @@ void ObjectFileDB::ir2_stack_spill_slot_pass() { if (!func.cfg_ok) { return; } - auto spill_map = build_spill_map(func.instructions, {func.prologue_end, func.epilogue_start}); - auto map_size = spill_map.size(); - if (map_size) { - functions_with_spills++; - total_slots += map_size; + try { + auto spill_map = build_spill_map(func.instructions, {func.prologue_end, func.epilogue_start}); + auto map_size = spill_map.size(); + if (map_size) { + functions_with_spills++; + total_slots += map_size; + } + func.ir2.env.set_stack_spills(spill_map); + } catch (std::exception& e) { + func.warnings.general_warning("stack spill failed: {}", e.what()); } - func.ir2.env.set_stack_spills(spill_map); }); lg::info("Analyzed stack spills: found {} functions with spills (total {} vars), took {:.2f} ms", functions_with_spills, total_slots, timer.getMs()); diff --git a/decompiler/VuDisasm/VuProgram.cpp b/decompiler/VuDisasm/VuProgram.cpp deleted file mode 100644 index 1d1e78272b..0000000000 --- a/decompiler/VuDisasm/VuProgram.cpp +++ /dev/null @@ -1,3 +0,0 @@ - - -#include "VuProgram.h" diff --git a/decompiler/analysis/cfg_builder.cpp b/decompiler/analysis/cfg_builder.cpp index f2cd360492..f8dd50d2e9 100644 --- a/decompiler/analysis/cfg_builder.cpp +++ b/decompiler/analysis/cfg_builder.cpp @@ -192,6 +192,32 @@ void clean_up_break(FormPool& pool, BreakElement* ir) { } } +void clean_up_break_final(const Function& f, BreakElement* ir) { + SetVarElement* dead = dynamic_cast(ir->dead_code->try_as_single_element()); + if (!dead) { + dead = dynamic_cast(ir->dead_code->elts().front()); + for (int i = 1; i < ir->dead_code->size(); i++) { + if (!dynamic_cast(ir->dead_code->at(i))) { + dead = nullptr; + break; + } + } + } + + if (!dead) { + lg::error("failed to recognize dead code after break, got {}", + ir->dead_code->to_string(f.ir2.env)); + throw std::runtime_error("failed to recognize dead code"); + } + assert(dead); + auto src = dynamic_cast(dead->src()->try_as_single_element()); + assert(src); + if (src->expr().is_identity() && src->expr().get_arg(0).is_int() && + src->expr().get_arg(0).get_int() == 0) { + ir->dead_code = nullptr; + } +} + /*! * 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 @@ -1412,10 +1438,16 @@ void insert_cfg_into_list(FormPool& pool, auto as_block = dynamic_cast(vtx); if (as_sequence) { // inline the sequence. + if (as_sequence->needs_label) { + output->push_back(pool.alloc_element(vtx->get_first_block_id())); + } for (auto& x : as_sequence->seq) { insert_cfg_into_list(pool, f, x, output); } } else if (as_block) { + if (as_block->needs_label) { + output->push_back(pool.alloc_element(vtx->get_first_block_id())); + } convert_and_inline(pool, f, as_block, output); } else { auto ir = cfg_to_ir(pool, f, vtx); @@ -1425,7 +1457,7 @@ void insert_cfg_into_list(FormPool& pool, } } -Form* cfg_to_ir(FormPool& pool, Function& f, const CfgVtx* vtx) { +Form* cfg_to_ir_helper(FormPool& pool, Function& f, const CfgVtx* vtx) { if (dynamic_cast(vtx)) { auto* bv = dynamic_cast(vtx); Form* output = pool.alloc_empty_form(); @@ -1588,7 +1620,8 @@ Form* cfg_to_ir(FormPool& pool, Function& f, const CfgVtx* vtx) { } else if (dynamic_cast(vtx)) { auto* cvtx = dynamic_cast(vtx); auto result = pool.alloc_single_element_form( - nullptr, cfg_to_ir(pool, f, cvtx->body), cfg_to_ir(pool, f, cvtx->unreachable_block)); + nullptr, cfg_to_ir(pool, f, cvtx->body), cfg_to_ir(pool, f, cvtx->unreachable_block), + cvtx->dest_block_id); clean_up_break(pool, dynamic_cast(result->try_as_single_element())); return result; } else if (dynamic_cast(vtx)) { @@ -1599,6 +1632,15 @@ Form* cfg_to_ir(FormPool& pool, Function& f, const CfgVtx* vtx) { return nullptr; } +Form* cfg_to_ir(FormPool& pool, Function& f, const CfgVtx* vtx) { + Form* result = cfg_to_ir_helper(pool, f, vtx); + if (vtx->needs_label) { + result->elts().insert(result->elts().begin(), + pool.alloc_element(vtx->get_first_block_id())); + } + return result; +} + /*! * Post processing pass to clean up while loops - annoyingly the block before a while loop * has a jump to the condition branch that we need to remove. This currently happens after all @@ -1666,6 +1708,11 @@ void build_initial_forms(Function& function) { if (as_return) { clean_up_return_final(function, as_return); } + + auto as_break = dynamic_cast(form); + if (as_break) { + clean_up_break_final(function, as_break); + } }); function.ir2.top_form = result; diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index 9f56e639ae..14055448a2 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -8562,7 +8562,7 @@ (dummy-16 (_type_ int int int int) none 16) (dummy-17 (_type_ int int) res-lump 17) (dummy-18 (_type_ int int) none 18) - (dummy-19 (_type_ symbol symbol int) int 19) + (lookup-tag-idx (_type_ symbol symbol float) int 19) (dummy-20 (_type_ int int) none 20) (dummy-21 (_type_ int int int int int) none 21) ) diff --git a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc index 066c8dad44..47f0a5d056 100644 --- a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc @@ -440,5 +440,9 @@ "trajectory": [ ["L18", "uint64", true] + ], + + "res": [ + ["L150", "uint64", true] ] } diff --git a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc index 49a1e3ebe3..909d1acec3 100644 --- a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc @@ -98,8 +98,8 @@ // GSTRING "name=": [ - [24, "a1", "symbol"], - [39, "a0", "symbol"] + [26, "a1", "symbol"], + [42, "a0", "symbol"] ], "string-cat-to-last-char": [ diff --git a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc index d4ae9f7f5c..0f85088808 100644 --- a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc @@ -849,7 +849,7 @@ "args": ["allocation", "type-to-make", "psm", "w", "h", "ztest", "zpsm"], "vars": { "gp-0": "obj" } }, - + "(method 0 ripple-control)": { "vars": { "v0-0": "obj" } }, @@ -1200,87 +1200,87 @@ "s0-0": "upload-chunk-idx", "sv-52": "current-dest-chunk", "sv-56": "allow-cached", - "gp-0":"total-upload-size", - "a0-21":["dma", "dma-packet"], - "a0-23":["gif", "gs-gif-tag"], - "v1-55":["dma-end", "dma-packet"] + "gp-0": "total-upload-size", + "a0-21": ["dma", "dma-packet"], + "a0-23": ["gif", "gs-gif-tag"], + "v1-55": ["dma-end", "dma-packet"] } }, "texture-page-near-allocate-0": { "args": ["pool", "page", "heap", "mode"], "vars": { - "s3-0":"common-dest", - "s2-0":"page-seg-idx", - "a1-5":"page-seg-2-size", - "v1-15":"after-seg-2-data", - "a0-8":"seg-2-data" + "s3-0": "common-dest", + "s2-0": "page-seg-idx", + "a1-5": "page-seg-2-size", + "v1-15": "after-seg-2-data", + "a0-8": "seg-2-data" } }, "texture-page-near-allocate-1": { "args": ["pool", "page", "heap", "mode"], "vars": { - "s4-0":"seg2-size", - "a1-1":"seg2-dest", - "s2-0":"common-dest", - "s1-0":"page-seg-idx" + "s4-0": "seg2-size", + "a1-1": "seg2-dest", + "s2-0": "common-dest", + "s1-0": "page-seg-idx" } }, "texture-page-level-allocate": { "args": ["pool", "page", "heap", "mode"], "vars": { - "s2-0":"common-id", - "v1-6":"level-idx" + "s2-0": "common-id", + "v1-6": "level-idx" } }, "texture-page-size-check": { "args": ["pool", "level", "hide-prints"], "vars": { - "gp-0":"oversize", - "s3-0":"tfrag-page", - "v1-0":"tfrag-mip0-size", - "v1-3":"pris-page", - "v1-5":"shrub-page", - "v1-7":"alpha-page", - "v1-9":"water-page" + "gp-0": "oversize", + "s3-0": "tfrag-page", + "v1-0": "tfrag-mip0-size", + "v1-3": "pris-page", + "v1-5": "shrub-page", + "v1-7": "alpha-page", + "v1-9": "water-page" } }, "(method 13 texture-pool)": { - "args":["obj", "level", "max-page-kind", "id-array"], + "args": ["obj", "level", "max-page-kind", "id-array"], "vars": { - "v1-0":"page-idx", - "v1-5":"tfrag-dir-entry", - "v1-7":"pris-dir-entry", - "v1-9":"shrub-dir-entry", - "v1-11":"alpha-dir-entry", - "v1-13":"water-dir-entry", - "a2-7":"overflow-bits" + "v1-0": "page-idx", + "v1-5": "tfrag-dir-entry", + "v1-7": "pris-dir-entry", + "v1-9": "shrub-dir-entry", + "v1-11": "alpha-dir-entry", + "v1-13": "water-dir-entry", + "a2-7": "overflow-bits" } }, "(method 14 texture-pool)": { - "args":["obj", "level", "tex-page-kind"], + "args": ["obj", "level", "tex-page-kind"], "vars": { - "s3-0":"tfrag-page", - "s2-0":"tfrag-bucket", - "f30-0":"distance", - "a2-4":"pris-page", - "a3-3":"pris-bucket", - "a2-5":"shrub-page", - "f0-5":"shrub-closest", - "t0-4":"shrub-bucket", - "a3-4":"shrub-mode", - "s3-1":"alpha-page", - "f0-6":"alpha-closest", - "s2-1":"alpha-bucket", - "s1-3":"alpha-mode", - "s0-0":"alpha-dest-chunk", - "a2-7":"water-page", - "a3-6":"water-bucket" + "s3-0": "tfrag-page", + "s2-0": "tfrag-bucket", + "f30-0": "distance", + "a2-4": "pris-page", + "a3-3": "pris-bucket", + "a2-5": "shrub-page", + "f0-5": "shrub-closest", + "t0-4": "shrub-bucket", + "a3-4": "shrub-mode", + "s3-1": "alpha-page", + "f0-6": "alpha-closest", + "s2-1": "alpha-bucket", + "s1-3": "alpha-mode", + "s0-0": "alpha-dest-chunk", + "a2-7": "water-page", + "a3-6": "water-bucket" } }, @@ -1377,11 +1377,11 @@ }, "move-target-from-pad": { - "args":["trans", "pad-idx"], + "args": ["trans", "pad-idx"], "vars": { - "s4-0":"local-trans", - "a0-5":"inv-cam-rot", - "s3-0":"cam-rot-mat" + "s4-0": "local-trans", + "a0-5": "inv-cam-rot", + "s3-0": "cam-rot-mat" } }, @@ -1565,10 +1565,18 @@ "args": ["obj", "name", "cmd-idx"], "vars": { "v1-1": "cmd-lst" } }, - + // SHADOW-CPU-H "(method 0 shadow-control)": { - "args": ["allocation", "type-to-make", "bottom-offset", "top-offset", "dir", "center", "fade"], + "args": [ + "allocation", + "type-to-make", + "bottom-offset", + "top-offset", + "dir", + "center", + "fade" + ], "vars": { "v0-0": "obj" } }, @@ -1584,85 +1592,104 @@ }, "(method 0 align-control)": { - "vars":{"v0-0": ["obj", "align-control"]} + "vars": { "v0-0": ["obj", "align-control"] } }, "str-load": { - "args":["name", "chunk-id", "address", "len"], - "vars":{"s2-0":["cmd", "load-chunk-msg"]} + "args": ["name", "chunk-id", "address", "len"], + "vars": { "s2-0": ["cmd", "load-chunk-msg"] } }, "str-load-status": { - "args":["length-out"], - "vars":{"v1-7":"response"} + "args": ["length-out"], + "vars": { "v1-7": "response" } }, "str-play-async": { "args": ["name", "addr"], - "vars":{"s4-0":"cmd"} + "vars": { "s4-0": "cmd" } }, "str-play-stop": { "args": ["name"], - "vars":{"s5-0":"cmd"} + "vars": { "s5-0": "cmd" } }, "str-play-queue": { "args": ["name"], - "vars":{"s5-0":"cmd"} + "vars": { "s5-0": "cmd" } }, "str-ambient-play": { "args": ["name"], - "vars":{"s5-0":"cmd"} + "vars": { "s5-0": "cmd" } }, "str-ambient-stop": { "args": ["name"], - "vars":{"s5-0":"cmd"} + "vars": { "s5-0": "cmd" } }, "string->sound-name": { - "args":["str"], - "vars":{"v1-0":"snd-name", "a1-0":["out-ptr", "(pointer uint8)"], "a2-0":"in-ptr"} + "args": ["str"], + "vars": { + "v1-0": "snd-name", + "a1-0": ["out-ptr", "(pointer uint8)"], + "a2-0": "in-ptr" + } }, "dgo-load-begin": { - "args":["name", "buffer1", "buffer2", "current-heap"], - "vars":{"s2-0":"cmd"} + "args": ["name", "buffer1", "buffer2", "current-heap"], + "vars": { "s2-0": "cmd" } }, "dgo-load-get-next": { - "args":["last-object"], - "vars":{"gp-0":["load-location", "pointer"], "v1-5":"response"} + "args": ["last-object"], + "vars": { "gp-0": ["load-location", "pointer"], "v1-5": "response" } }, "dgo-load-continue": { - "args":["current-heap"], - "vars":{"gp-0":"cmd"} + "args": ["current-heap"], + "vars": { "gp-0": "cmd" } }, "dgo-load-cancel": { - "vars":{"a2-0":"cmd"} + "vars": { "a2-0": "cmd" } }, "find-temp-buffer": { - "args":["size"], - "vars":{"gp-0":"qwc"} + "args": ["size"], + "vars": { "gp-0": "qwc" } }, "dgo-load-link": { - "args":["obj-file", "heap", "print-login", "last-object"], - "vars":{"s4-0":"obj-data"} + "args": ["obj-file", "heap", "print-login", "last-object"], + "vars": { "s4-0": "obj-data" } }, "ramdisk-load": { - "args":["file-id", "offset", "length", "buffer"], - "vars":{"v1-1":"cmd"} + "args": ["file-id", "offset", "length", "buffer"], + "vars": { "v1-1": "cmd" } }, "show-mc-info": { - "args":["dma-buf"], - "vars":{"s5-0":"info", "s4-0":"slot-idx"} - } + "args": ["dma-buf"], + "vars": { "s5-0": "info", "s4-0": "slot-idx" } + }, - + "(method 19 res-lump)": { + "args": ["obj", "name-sym", "mode", "time"], + "vars": { + "t2-4": "type-chars", + "t3-1": "max-search", + "t4-0": "min-search", + "t6-5": "diff", + "t5-2": "check-idx", + "t4-1": "tag-idx", + "t3-13": "interp-tag-idx", + "t4-4": "tag-ptr", + "t0-6": "lo-tag-idx-out", + "v1-14": "hi-tag-idx-out", + "t1-0": "most-recent-invalid-time-idx" + } + } } diff --git a/goal_src/engine/data/res-h.gc b/goal_src/engine/data/res-h.gc index 33573ff1e2..cdfa65a8bf 100644 --- a/goal_src/engine/data/res-h.gc +++ b/goal_src/engine/data/res-h.gc @@ -43,7 +43,7 @@ (dummy-16 (_type_ int int int int) none 16) (dummy-17 (_type_ int int) res-lump 17) (dummy-18 (_type_ int int) none 18) - (dummy-19 (_type_ symbol symbol int) int 19) + (lookup-tag-idx (_type_ symbol symbol float) int 19) (dummy-20 (_type_ int int) none 20) (dummy-21 (_type_ int int int int int) none 21) ) diff --git a/goal_src/engine/data/res.gc b/goal_src/engine/data/res.gc index 784567277b..277d3a53b6 100644 --- a/goal_src/engine/data/res.gc +++ b/goal_src/engine/data/res.gc @@ -121,3 +121,179 @@ (-> obj data-size))) ) +(defmethod lookup-tag-idx res-lump ((obj res-lump) (name-sym symbol) (mode symbol) (time float)) + "Look up the index of the tag containing with the given name and timestamp. + This will actually return two tags: one in the lower 32 bits and one in the upper 32 bits. + Depending on the mode, they may be the same, or they may be two tags that you should interpolate + between, if the exact time was not found. + + name-sym should be the name of the thing you want. + time is for the timestamp you want. + If mode = 'base, then both the indices are the same and the timestamp is ignored. + If mode = 'interp, then it tries to get closest below/closest above (or both the same, if exact match found) + If mode = 'exact, then it requires an exact timestamp match and both indices are the same. + If things go wrong, returns a negative number" + (local-vars (tag-idx int)) + ;; for some unknown reason, these names cannot be looked up with this method. + (when (or + (= name-sym 'id) + (= name-sym 'aid) + (= name-sym 'trans) + (= name-sym 'rot) + (= name-sym 'nav-mesh) + (= name-sym 'process-type) + (= name-sym 'task) + ) + (crash!) + ) + + ;; check that we are valid. + (if (or (not obj) (zero? obj) (<= (-> obj length) 0)) + (return -1) + ) + + ;; these are the outputs of the function. + (let ((hi-tag-idx-out -1) + (lo-tag-idx-out -1) + ) + ;; this value is the index of the most recently passed tag with an "invalid" timestamp + (let ((most-recent-invalid-time-idx -1) + ;; read 8 chars of the name we want + (type-chars (-> (the-as (pointer uint64) (-> (symbol->string name-sym) data)) 0)) + ) + + ;; now we will do a binary search. The names are stored in ascending order if you + ;; treat the first 8 chars as an integer + ;; min/max are inclusive. + (let ((max-search (+ (-> obj length) -1)) + (min-search 0) + ) + ;; inclusive, so >= is correct + (while (>= max-search min-search) + ;; check in the middle of the range to bisect it. + (let* ((check-idx (+ min-search (/ (- max-search min-search) 2))) + ;; subtract the two words. The sign of this tells us if we are too high or too low + (diff (- type-chars + (-> (the-as (pointer uint64) (-> (symbol->string (-> (-> obj tag) check-idx name)) data)) 0) + ) + ) + ) + (cond + ((zero? diff) + ;; perfect match! we are done, set the tag-idx and get out of here. + (begin + (set! tag-idx check-idx) + (goto cfg-32) + ) + ) + (else + ;; didn't match. pick the appropriate half of the remaining tags + (if (< (the-as int diff) 0) + (set! max-search (+ check-idx -1)) + (set! min-search (+ check-idx 1)) + ) + ) + ) + ) + ) + ) + ;; got to the end of the loop without finding the answer. Set a negative tag + (set! tag-idx -1) + + (label cfg-32) + (if (< tag-idx 0) + (return tag-idx) + ) + + ;; if there are multiple tags with the same name and different timesteps, we can't be sure which we ended on. + ;; this loop brings us to the first tag with the correct name. + (while (and (> tag-idx 0) + (= type-chars + (-> (the-as (pointer uint64) (-> (symbol->string (-> (-> obj tag) (+ tag-idx -1) name)) data)) 0) + ) + ) + (+! tag-idx -1) + ) + + ;; in 'base mode, we just want the earliest tag with the right name. + ;; tags are in increasing timestamps, so we found it! + (if (= mode 'base) + (begin + ;; both lo and hi are the same + (set! lo-tag-idx-out tag-idx) + (set! hi-tag-idx-out tag-idx) + (goto cfg-73) + ) + ) + + ;; next we will iterate through tags with the right name and find the one(s) with the right timestamp + ;; interp-tag-idx is the index of tag-ptr always + ;; (the fact that they keep a pointer to a tag and not actually load the tag makes it seem like + ;; they ran into some 128-bit integer issues here...) + (let ((interp-tag-idx tag-idx) + (tag-ptr (&-> (-> obj tag) tag-idx)) + ) + ;; loop, until we reach another name or the end of the table + (while (not (or + (>= interp-tag-idx (-> obj length)) + ;; < is correct here because we are incrementing and names get larger + (< type-chars + (-> (the-as (pointer uint64) (-> (symbol->string (-> tag-ptr 0 name)) data)) 0) + ) + ) + ) + (cond + ;; The checks above only made sure that the first 8 chars were correct. + ;; This skips items with matching first 8 chars, but differences after that. + ((!= name-sym (-> tag-ptr 0 name)) + ) + + ;; check for exact match + ((= (-> tag-ptr 0 key-frame) time) + ;; in all cases, just return the exact match. + (begin + (set! lo-tag-idx-out interp-tag-idx) + (set! hi-tag-idx-out interp-tag-idx) + (goto cfg-73) + ) + ) + + ;; check for being not far enough + ((and (>= time (-> tag-ptr 0 key-frame)) (!= mode 'exact)) + ;; in all cases, except for exact, we'll want to remember this. + ;; just in case there are no more tags + (set! lo-tag-idx-out interp-tag-idx) + (set! hi-tag-idx-out interp-tag-idx) + ;; also remember if we hit an invalid one + (if (= (-> tag-ptr 0 key-frame) -1000000000.0) + (set! most-recent-invalid-time-idx interp-tag-idx) + ) + ) + + ;; check for being too far (passed the time) + ((< time (-> tag-ptr 0 key-frame)) + (begin + ;; if we're interpolation mode and valid, set the high one. + ;; not sure what the invalid time thing is about. + (if (and (!= lo-tag-idx-out most-recent-invalid-time-idx) (= mode 'interp)) + (set! hi-tag-idx-out interp-tag-idx) + ) + (goto cfg-73) + ) + ) + ) + + ;; advance to next tag + (+! interp-tag-idx 1) + (set! tag-ptr (&-> tag-ptr 1)) + ) + ) + ) + (label cfg-73) + ;; end: return the tags. + (logior + (logand #xffffffff (the-as uint lo-tag-idx-out)) + (the-as uint (shl hi-tag-idx-out 32)) + ) + ) + ) diff --git a/goal_src/kernel/gcommon.gc b/goal_src/kernel/gcommon.gc index e31ae57c8f..67c8e1b011 100644 --- a/goal_src/kernel/gcommon.gc +++ b/goal_src/kernel/gcommon.gc @@ -49,8 +49,8 @@ (defconstant PAIR_OFFSET 2) (defconstant BASIC_OFFSET 4) -(defmacro symbol-to-string (sym) - ;; "Convert a symbol to a goal string." +(defmacro symbol->string (sym) + "Convert a symbol to a goal string." `(-> (the-as (pointer string) (+ SYM_TO_STRING_OFFSET (the-as int ,sym)))) ) diff --git a/goal_src/kernel/gstring.gc b/goal_src/kernel/gstring.gc index d0f63f53bb..13ecdfa001 100644 --- a/goal_src/kernel/gstring.gc +++ b/goal_src/kernel/gstring.gc @@ -106,10 +106,10 @@ (string= (the-as string arg0) (the-as string arg1)) ) ((and (= (-> arg0 type) string) (= (-> arg1 type) symbol)) - (string= (the-as string arg0) (symbol-to-string arg1)) + (string= (the-as string arg0) (symbol->string arg1)) ) ((and (= (-> arg1 type) string) (= (-> arg0 type) symbol)) - (string= (the-as string arg1) (symbol-to-string arg0)) + (string= (the-as string arg1) (symbol->string arg0)) ) ) ) diff --git a/test/decompiler/reference/all_forward_declarations.gc b/test/decompiler/reference/all_forward_declarations.gc index 05d445d80c..64ec9ee587 100644 --- a/test/decompiler/reference/all_forward_declarations.gc +++ b/test/decompiler/reference/all_forward_declarations.gc @@ -659,3 +659,8 @@ ) (define-extern add-debug-line (function symbol int vector vector rgba symbol int int)) +(defconstant SYM_TO_STRING_OFFSET #xff38) +(defmacro symbol->string (sym) + "Convert a symbol to a goal string." + `(-> (the-as (pointer string) (+ SYM_TO_STRING_OFFSET (the-as int ,sym)))) + ) diff --git a/test/decompiler/reference/kernel/gstring_REF.gc b/test/decompiler/reference/kernel/gstring_REF.gc index 7c69692023..81db96a730 100644 --- a/test/decompiler/reference/kernel/gstring_REF.gc +++ b/test/decompiler/reference/kernel/gstring_REF.gc @@ -114,16 +114,10 @@ (string= (the-as string arg0) (the-as string arg1)) ) ((and (= (-> arg0 type) string) (= (-> arg1 type) symbol)) - (string= - (the-as string arg0) - (the-as string (-> (the-as (pointer uint32) (+ #xff38 (the-as int arg1))))) - ) + (string= (the-as string arg0) (symbol->string (the-as symbol arg1))) ) ((and (= (-> arg1 type) string) (= (-> arg0 type) symbol)) - (string= - (the-as string arg1) - (the-as string (-> (the-as (pointer uint32) (+ #xff38 (the-as int arg0))))) - ) + (string= (the-as string arg1) (symbol->string (the-as symbol arg0))) ) ) ) diff --git a/test/decompiler/test_FormExpressionBuildLong.cpp b/test/decompiler/test_FormExpressionBuildLong.cpp index a6ad032227..29c85b88de 100644 --- a/test/decompiler/test_FormExpressionBuildLong.cpp +++ b/test/decompiler/test_FormExpressionBuildLong.cpp @@ -2441,3 +2441,509 @@ TEST_F(FormRegressionTest, ExprStringToInt) { " )"; test_final_function(func, type, expected); } + +TEST_F(FormRegressionTest, Method19ResTag) { + std::string func = + "sll r0, r0, 0\n" + "L101:\n" + " daddiu sp, sp, -16\n" + " sd fp, 8(sp)\n" + " or fp, t9, r0\n" + " daddiu v1, s7, id\n" + " dsubu v1, a1, v1\n" + " daddiu t0, s7, 8\n" + " movn t0, s7, v1\n" + " bnel s7, t0, L102\n" + " or v1, t0, r0\n" + "\n" + " daddiu v1, s7, aid\n" + " dsubu v1, a1, v1\n" + " daddiu t0, s7, 8\n" + " movn t0, s7, v1\n" + " bnel s7, t0, L102\n" + " or v1, t0, r0\n" + "\n" + " daddiu v1, s7, trans\n" + " dsubu v1, a1, v1\n" + " daddiu t0, s7, 8\n" + " movn t0, s7, v1\n" + " bnel s7, t0, L102\n" + " or v1, t0, r0\n" + "\n" + " daddiu v1, s7, rot\n" + " dsubu v1, a1, v1\n" + " daddiu t0, s7, 8\n" + " movn t0, s7, v1\n" + " bnel s7, t0, L102\n" + " or v1, t0, r0\n" + "\n" + " daddiu v1, s7, nav-mesh\n" + " dsubu v1, a1, v1\n" + " daddiu t0, s7, 8\n" + " movn t0, s7, v1\n" + " bnel s7, t0, L102\n" + " or v1, t0, r0\n" + "\n" + " daddiu v1, s7, process-type\n" + " dsubu v1, a1, v1\n" + " daddiu t0, s7, 8\n" + " movn t0, s7, v1\n" + " bnel s7, t0, L102\n" + " or v1, t0, r0\n" + "\n" + " daddiu v1, s7, task\n" + " dsubu t0, a1, v1\n" + " daddiu v1, s7, 8\n" + " movn v1, s7, t0\n" + "L102:\n" + " beq s7, v1, L103\n" + " or v1, s7, r0\n" + "\n" + " sd r0, 2(r0)\n" + " or v1, r0, r0\n" + "L103:\n" + " beql s7, a0, L104\n" + " daddiu v1, s7, 8\n" + "\n" + " beql a0, r0, L104\n" + " daddiu v1, s7, 8\n" + "\n" + " lw v1, 0(a0)\n" + " slt t0, r0, v1\n" + " daddiu v1, s7, 8\n" + " movn v1, s7, t0\n" + "L104:\n" + " beq s7, v1, L105\n" + " or v1, s7, r0\n" + "\n" + " addiu v0, r0, -1\n" + " beq r0, r0, L129\n" + " sll r0, r0, 0\n" + "\n" + " or v1, r0, r0\n" + "L105:\n" + " addiu v1, r0, -1\n" + " addiu t0, r0, -1\n" + " addiu t1, r0, -1\n" + " ori t2, r0, 65336\n" + " daddu t2, t2, a1\n" + "\n" + " lwu t2, 0(t2)\n" + " daddiu t2, t2, 4\n" + " ld t2, 0(t2)\n" + "\n" + " lw t3, 0(a0)\n" + " daddiu t3, t3, -1\n" + " addiu t4, r0, 0\n" + " beq r0, r0, L109\n" + " sll r0, r0, 0\n" + "\n" + "L106:\n" + " dsubu t5, t3, t4\n" + "\n" + " dsra t5, t5, 1\n" + " daddu t5, t4, t5\n" + " ori t6, r0, 65336\n" + " lwu t7, 24(a0)\n" + " dsll t8, t5, 4\n" + " daddu t7, t7, t8\n" + "\n" + " lq t7, 0(t7)\n" + " dsll32 t7, t7, 0\n" + " dsrl32 t7, t7, 0\n" + " daddu t6, t6, t7\n" + "\n" + " lwu t6, 0(t6)\n" + " daddiu t6, t6, 4\n" + " ld t6, 0(t6)\n" + "\n" + " dsubu t6, t2, t6\n" + " bne t6, r0, L107\n" + " sll r0, r0, 0\n" + "\n" + " or t4, t5, r0\n" + " beq r0, r0, L110\n" + " sll r0, r0, 0\n" + "\n" + " or v1, r0, r0\n" + " beq r0, r0, L109\n" + " sll r0, r0, 0\n" + "\n" + "L107:\n" + " slt t6, t6, r0\n" + " beq t6, r0, L108\n" + " sll r0, r0, 0\n" + "\n" + " daddiu t3, t5, -1\n" + " or t5, t3, r0\n" + " beq r0, r0, L109\n" + " sll r0, r0, 0\n" + "\n" + "L108:\n" + " daddiu t4, t5, 1\n" + " or t5, t4, r0\n" + "L109:\n" + " slt t5, t3, t4\n" + "\n" + " beq t5, r0, L106\n" + " sll r0, r0, 0\n" + "\n" + " or t3, s7, r0\n" + " addiu t4, r0, -1\n" + "L110:\n" + " slt t3, t4, r0\n" + " beq t3, r0, L111\n" + " or t3, s7, r0\n" + "\n" + " or v0, t4, r0\n" + " beq r0, r0, L129\n" + " sll r0, r0, 0\n" + "\n" + " or v1, r0, r0\n" + "L111:\n" + " beq r0, r0, L113\n" + " sll r0, r0, 0\n" + "\n" + "L112:\n" + " daddiu t4, t4, -1\n" + "L113:\n" + " slt t3, r0, t4\n" + " daddiu t5, s7, 8\n" + " movz t5, s7, t3\n" + " beql s7, t5, L114\n" + " or t3, t5, r0\n" + "\n" + " ori t3, r0, 65336\n" + " lwu t5, 24(a0)\n" + " daddiu t6, t4, -1\n" + " dsll t6, t6, 4\n" + " daddu t5, t5, t6\n" + "\n" + " lq t5, 0(t5)\n" + " dsll32 t5, t5, 0\n" + " dsrl32 t5, t5, 0\n" + " daddu t3, t3, t5\n" + "\n" + " lwu t3, 0(t3)\n" + " daddiu t3, t3, 4\n" + " ld t3, 0(t3)\n" + "\n" + " dsubu t5, t2, t3\n" + " daddiu t3, s7, 8\n" + " movn t3, s7, t5\n" + "L114:\n" + " bne s7, t3, L112\n" + " sll r0, r0, 0\n" + "\n" + " or t3, s7, r0\n" + " daddiu t3, s7, base\n" + " bne a2, t3, L115\n" + " or t3, s7, r0\n" + "\n" + " or t0, t4, r0\n" + " or v1, t4, r0\n" + " or a0, s7, r0\n" + " beq r0, r0, L128\n" + " sll r0, r0, 0\n" + "\n" + " or v1, r0, r0\n" + "L115:\n" + " or t3, t4, r0\n" + " dsll t4, t4, 4\n" + " daddu t4, r0, t4\n" + " lwu t5, 24(a0)\n" + " daddu t4, t4, t5\n" + "\n" + " beq r0, r0, L126\n" + " sll r0, r0, 0\n" + "\n" + "L116:\n" + " lq t5, 0(t4)\n" + " dsll32 t5, t5, 0\n" + " dsrl32 t5, t5, 0\n" + " beq a1, t5, L117\n" + " or t5, s7, r0\n" + "\n" + " or t5, s7, r0\n" + " beq r0, r0, L125\n" + " sll r0, r0, 0\n" + "\n" + "L117:\n" + " lq t5, 0(t4)\n" + " dsra32 t5, t5, 0\n" + " mtc1 f0, t5\n" + " mtc1 f1, a3\n" + " c.eq.s f0, f1\n" + " bc1f L118\n" + " or t5, s7, r0\n" + "\n" + " or t0, t3, r0\n" + " or v1, t3, r0\n" + " or a0, s7, r0\n" + " beq r0, r0, L128\n" + " sll r0, r0, 0\n" + "\n" + " or v1, r0, r0\n" + " beq r0, r0, L125\n" + " sll r0, r0, 0\n" + "\n" + "L118:\n" + " mtc1 f0, a3\n" + " lq t5, 0(t4)\n" + " dsra32 t5, t5, 0\n" + " mtc1 f1, t5\n" + " c.lt.s f0, f1\n" + " bc1f L119\n" + " daddiu t5, s7, 8\n" + "\n" + " or t5, s7, r0\n" + "L119:\n" + " beql s7, t5, L120\n" + " or t5, t5, r0\n" + "\n" + " daddiu t5, s7, exact\n" + " dsubu t6, a2, t5\n" + " daddiu t5, s7, 8\n" + " movz t5, s7, t6\n" + "L120:\n" + " beq s7, t5, L122\n" + " or t5, s7, r0\n" + "\n" + " or t0, t3, r0\n" + " or v1, t3, r0\n" + " lq t5, 0(t4)\n" + " dsra32 t5, t5, 0\n" + " mtc1 f0, t5\n" + //" lwc1 f1, L147(fp)\n" + " mtc1 f1, r0\n" + " c.eq.s f0, f1\n" + " bc1f L121\n" + " or t5, s7, r0\n" + "\n" + " or t1, t3, r0\n" + " or t5, t1, r0\n" + "L121:\n" + " beq r0, r0, L125\n" + " sll r0, r0, 0\n" + "\n" + "L122:\n" + " mtc1 f0, a3\n" + " lq t5, 0(t4)\n" + " dsra32 t5, t5, 0\n" + " mtc1 f1, t5\n" + " c.lt.s f0, f1\n" + " bc1f L125\n" + " or t5, s7, r0\n" + "\n" + " dsubu a0, t0, t1\n" + "\n" + " daddiu a1, s7, 8\n" + " movz a1, s7, a0\n" + " beql s7, a1, L123\n" + " or a0, a1, r0\n" + "\n" + " daddiu a0, s7, interp\n" + " dsubu a1, a2, a0\n" + " daddiu a0, s7, 8\n" + " movn a0, s7, a1\n" + "L123:\n" + " beq s7, a0, L124\n" + " or a0, s7, r0\n" + "\n" + " or v1, t3, r0\n" + " or a0, v1, r0\n" + "L124:\n" + " or a0, s7, r0\n" + " beq r0, r0, L128\n" + " sll r0, r0, 0\n" + "\n" + " or v1, r0, r0\n" + "L125:\n" + " daddiu t3, t3, 1\n" + " daddiu t4, t4, 16\n" + "L126:\n" + " lw t5, 0(a0)\n" + " slt t5, t3, t5\n" + " daddiu t6, s7, 8\n" + " movn t6, s7, t5\n" + " bnel s7, t6, L127\n" + " or t5, t6, r0\n" + "\n" + " ori t5, r0, 65336\n" + " lq t6, 0(t4)\n" + " dsll32 t6, t6, 0\n" + " dsrl32 t6, t6, 0\n" + " daddu t5, t5, t6\n" + "\n" + " lwu t5, 0(t5)\n" + " daddiu t5, t5, 4\n" + " ld t5, 0(t5)\n" + "\n" + " sltu t6, t2, t5\n" + " daddiu t5, s7, 8\n" + " movz t5, s7, t6\n" + "L127:\n" + " beq s7, t5, L116\n" + " sll r0, r0, 0\n" + "\n" + " or a0, s7, r0\n" + " or a0, s7, r0\n" + "L128:\n" + //" ld a0, L150(fp)\n" + " or a0, r0, r0\n" + " and a0, a0, t0\n" + " dsll32 v1, v1, 0\n" + " or v0, a0, v1\n" + "\n" + "L129:\n" + " ld fp, 8(sp)\n" + " jr ra\n" + " daddiu sp, sp, 16"; + std::string type = "(function res-lump symbol symbol float int)"; + std::string expected = + "(begin\n" + " (when\n" + " (or\n" + " (= arg1 'id)\n" + " (= arg1 'aid)\n" + " (= arg1 'trans)\n" + " (= arg1 'rot)\n" + " (= arg1 'nav-mesh)\n" + " (= arg1 'process-type)\n" + " (= arg1 'task)\n" + " )\n" + " (crash!)\n" + " (let ((v1-9 0))\n" + " )\n" + " )\n" + " (if (or (not arg0) (zero? arg0) (<= (-> arg0 length) 0))\n" + " (return -1)\n" + " )\n" + " (let ((v1-14 -1)\n" + " (t0-6 -1)\n" + " )\n" + " (let ((t1-0 -1)\n" + " (t2-4 (-> (the-as (pointer uint64) (-> (symbol->string arg1) data)) 0))\n" + " )\n" + " (let ((t3-1 (+ (-> arg0 length) -1))\n" + " (t4-0 0)\n" + " )\n" + " (while (>= t3-1 t4-0)\n" + " (let* ((t5-2 (+ t4-0 (/ (- t3-1 t4-0) 2)))\n" + " (t6-5\n" + " (-\n" + " t2-4\n" + " (->\n" + " (the-as\n" + " (pointer uint64)\n" + " (-> (symbol->string (-> (-> arg0 tag) t5-2 name)) data)\n" + " )\n" + " 0\n" + " )\n" + " )\n" + " )\n" + " )\n" + " (cond\n" + " ((zero? t6-5)\n" + " (begin\n" + " (set! t4-1 t5-2)\n" + " (goto cfg-32)\n" + " )\n" + " )\n" + " (else\n" + " (if (< (the-as int t6-5) 0)\n" + " (set! t3-1 (+ t5-2 -1))\n" + " (set! t4-0 (+ t5-2 1))\n" + " )\n" + " )\n" + " )\n" + " )\n" + " )\n" + " )\n" + " (set! t4-1 -1)\n" + " (label cfg-32)\n" + " (if (< t4-1 0)\n" + " (return t4-1)\n" + " )\n" + " (while\n" + " (and\n" + " (> t4-1 0)\n" + " (=\n" + " t2-4\n" + " (->\n" + " (the-as\n" + " (pointer uint64)\n" + " (-> (symbol->string (-> (-> arg0 tag) (+ t4-1 -1) name)) data)\n" + " )\n" + " 0\n" + " )\n" + " )\n" + " )\n" + " (+! t4-1 -1)\n" + " )\n" + " (if (= arg2 'base)\n" + " (begin\n" + " (set! t0-6 t4-1)\n" + " (set! v1-14 t4-1)\n" + " (goto cfg-73)\n" + " )\n" + " )\n" + " (let ((t3-13 t4-1)\n" + " (t4-4 (&-> (-> arg0 tag) t4-1))\n" + " )\n" + " (while\n" + " (not\n" + " (or\n" + " (>= t3-13 (-> arg0 length))\n" + " (<\n" + " t2-4\n" + " (->\n" + " (the-as (pointer uint64) (-> (symbol->string (-> t4-4 0 name)) data))\n" + " 0\n" + " )\n" + " )\n" + " )\n" + " )\n" + " (cond\n" + " ((!= arg1 (-> t4-4 0 name))\n" + " )\n" + " ((= (-> t4-4 0 key-frame) arg3)\n" + " (begin\n" + " (set! t0-6 t3-13)\n" + " (set! v1-14 t3-13)\n" + " (goto cfg-73)\n" + " )\n" + " )\n" + " ((and (>= arg3 (-> t4-4 0 key-frame)) (!= arg2 'exact))\n" + " (set! t0-6 t3-13)\n" + " (set! v1-14 t3-13)\n" + " (if (= (-> t4-4 0 key-frame) 0.0)\n" + " (set! t1-0 t3-13)\n" + " )\n" + " )\n" + " ((< arg3 (-> t4-4 0 key-frame))\n" + " (begin\n" + " (if (and (!= t0-6 t1-0) (= arg2 'interp))\n" + " (set! v1-14 t3-13)\n" + " )\n" + " (goto cfg-73)\n" + " )\n" + " )\n" + " )\n" + " (+! t3-13 1)\n" + " (set! t4-4 (&-> t4-4 1))\n" + " )\n" + " )\n" + " )\n" + " (label cfg-73)\n" + " (logior (logand 0 t0-6) (shl v1-14 32))\n" + " )\n" + " )"; + test_with_expr(func, type, expected, false, "", {}, + "[\n" + " [46, \"t2\", \"(pointer uint64)\"],\n" + " [100, \"t3\", \"(pointer uint64)\"],\n" + " [184, \"t5\", \"(pointer uint64)\"],\n" + " [64, \"t6\", \"(pointer uint64)\"]\n" + " ]"); +} \ No newline at end of file