From 3cfa4e37454182129fd56edfbc59fc3e708d5370 Mon Sep 17 00:00:00 2001 From: water111 Date: Mon, 13 Apr 2026 16:08:59 -0400 Subject: [PATCH] more fixes --- decompiler/IR2/Env.cpp | 72 +++++ decompiler/IR2/Env.h | 4 + decompiler/IR2/Form.h | 1 + decompiler/IR2/FormExpressionAnalysis.cpp | 119 +++++++- decompiler/IR2/FormStack.cpp | 69 ++++- test/decompiler/test_DisasmVifDecompile.cpp | 303 +++++++++----------- 6 files changed, 379 insertions(+), 189 deletions(-) diff --git a/decompiler/IR2/Env.cpp b/decompiler/IR2/Env.cpp index a355113b42..55f95610f7 100644 --- a/decompiler/IR2/Env.cpp +++ b/decompiler/IR2/Env.cpp @@ -4,6 +4,7 @@ #include #include +#include "AtomicOp.h" #include "Form.h" #include "common/goos/PrettyPrinter.h" @@ -546,6 +547,16 @@ FunctionVariableDefinitions Env::local_var_type_list(const Form* top_level_form, int nargs_to_ignore) const { ASSERT(nargs_to_ignore <= 8); auto vars = extract_visible_variables(top_level_form); + std::unordered_set visible_stack_slots; + if (top_level_form) { + RegAccessSet var_set; + top_level_form->collect_vars(var_set, true); + for (const auto& var : var_set) { + if (is_stack_slot_access(var)) { + visible_stack_slots.insert(get_stack_slot_offset_from_access(var)); + } + } + } FunctionVariableDefinitions result; std::vector elts; @@ -580,6 +591,9 @@ FunctionVariableDefinitions Env::local_var_type_list(const Form* top_level_form, // it looks like this is the order the GOAL compiler itself used. std::vector spills; for (auto& x : stack_slot_entries) { + if (top_level_form && !visible_stack_slots.count(x.first)) { + continue; + } spills.push_back(x.second); } std::sort(spills.begin(), spills.end(), @@ -608,17 +622,32 @@ std::unordered_set Env::get_ssa_var(const RegAccessSet& vars } RegId Env::get_program_var_id(const RegisterAccess& var) const { + if (is_stack_slot_access(var)) { + return RegId(Register(Reg::GPR, Reg::SP), get_stack_slot_offset_from_access(var)); + } return m_var_names.lookup(var.reg(), var.idx(), var.mode()).reg_id; } const UseDefInfo& Env::get_use_def_info(const RegisterAccess& ra) const { ASSERT(has_local_vars()); + if (is_stack_slot_access(ra)) { + return m_stack_slot_use_def_info.at(get_program_var_id(ra)); + } auto var_id = get_program_var_id(ra); return m_var_names.use_def_info.at(var_id); } void Env::disable_def(const RegisterAccess& access, DecompWarnings& warnings) { if (is_stack_slot_access(access)) { + auto& info = m_stack_slot_use_def_info.at(get_program_var_id(access)); + for (auto& def : info.defs) { + if (!def.disabled) { + def.disabled = true; + return; + } + } + warnings.warning("disable stack def twice: {}", + get_spill_slot_var_name(get_stack_slot_offset_from_access(access))); return; } if (has_local_vars()) { @@ -628,6 +657,16 @@ void Env::disable_def(const RegisterAccess& access, DecompWarnings& warnings) { void Env::disable_use(const RegisterAccess& access) { if (is_stack_slot_access(access)) { + auto& info = m_stack_slot_use_def_info.at(get_program_var_id(access)); + for (auto& use : info.uses) { + if (!use.disabled) { + use.disabled = true; + return; + } + } + throw std::runtime_error( + fmt::format("Invalid disable use on stack slot {}", + get_spill_slot_var_name(get_stack_slot_offset_from_access(access)))); return; } if (has_local_vars()) { @@ -635,6 +674,39 @@ void Env::disable_use(const RegisterAccess& access) { } } +void Env::rebuild_stack_slot_use_def_info() { + m_stack_slot_use_def_info.clear(); + + if (!func || !func->ir2.atomic_ops) { + return; + } + + const auto& ops = *func->ir2.atomic_ops; + std::vector op_to_block(ops.ops.size(), -1); + for (int block_id = 0; block_id < (int)ops.block_id_to_first_atomic_op.size(); block_id++) { + for (int op_id = ops.block_id_to_first_atomic_op.at(block_id); + op_id < ops.block_id_to_end_atomic_op.at(block_id); op_id++) { + op_to_block.at(op_id) = block_id; + } + } + + for (int op_id = 0; op_id < (int)ops.ops.size(); op_id++) { + const auto* op = ops.ops.at(op_id).get(); + if (auto* store = dynamic_cast(op)) { + auto var_id = RegId(Register(Reg::GPR, Reg::SP), store->offset()); + auto& info = m_stack_slot_use_def_info[var_id]; + info.defs.push_back({op_id, op_to_block.at(op_id), AccessMode::WRITE, false}); + info.ssa_vars.insert(store->offset()); + } + if (auto* load = dynamic_cast(op)) { + auto var_id = RegId(Register(Reg::GPR, Reg::SP), load->offset()); + auto& info = m_stack_slot_use_def_info[var_id]; + info.uses.push_back({op_id, op_to_block.at(op_id), AccessMode::READ, false}); + info.ssa_vars.insert(load->offset()); + } + } +} + /*! * Set the stack hints. This must be done before type analysis. * This actually parses the types, so it should be done after the dts is set up. diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h index 167f02bb99..2dcb75ca73 100644 --- a/decompiler/IR2/Env.h +++ b/decompiler/IR2/Env.h @@ -146,6 +146,7 @@ class Env { void set_local_vars(const VariableNames& names) { m_var_names = names; m_has_local_vars = true; + rebuild_stack_slot_use_def_info(); } void set_end_var(RegisterAccess var) { m_end_var = var; } @@ -248,6 +249,8 @@ class Env { bool pp_mapped_by_behavior() const { return m_pp_mapped_by_behavior; } private: + void rebuild_stack_slot_use_def_info(); + RegisterAccess m_end_var; bool m_has_reg_use = false; @@ -255,6 +258,7 @@ class Env { bool m_has_local_vars = false; VariableNames m_var_names; + std::unordered_map m_stack_slot_use_def_info; bool m_has_types = false; bool m_pp_mapped_by_behavior = false; diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index 2cfdd8d640..af6cca590d 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -1568,6 +1568,7 @@ class StackSpillValueElement : public FormElement { int stack_offset, bool is_signed, std::optional read_type = std::nullopt); + int stack_offset() const { return m_stack_offset; } 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; diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index 4adeca1d71..d146e2d677 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -87,6 +87,35 @@ Form* cast_stack_slot_var_if_needed(Form* in, return in; } +Form* cast_inlined_function_symbol_if_needed(Form* in, + const TypeSpec& desired_type, + FormPool& pool, + const Env& env) { + if (!env.has_type_analysis() || desired_type.base_type() != "function") { + return in; + } + + auto existing_cast = in->try_as_element(); + if (existing_cast && existing_cast->type() == desired_type) { + return in; + } + + auto obj = in->to_form(env); + if (!obj.is_symbol()) { + return in; + } + + try { + auto symbol_type = env.dts->lookup_symbol_type(obj.as_symbol().name_ptr); + if (symbol_type.base_type() == "function" && symbol_type != desired_type) { + return pool.form(desired_type, in); + } + } catch (const std::runtime_error&) { + } + + return in; +} + Form* strip_pcypld_64(Form* in) { auto m = match(Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::PCPYLD), {Matcher::integer(0), Matcher::any(0)}), @@ -402,19 +431,35 @@ void pop_helper(const std::vector& vars, const std::optional& consumes = std::nullopt, const std::vector& times_used = {}) { // to submit to stack to attempt popping - std::vector submit_regs; - // submit_reg[i] is for var submit_reg_to_var[i] - std::vector submit_reg_to_var; + std::vector submit_vars; + // submit_vars[i] is for var submit_var_to_var[i] + std::vector submit_var_to_var; // build submission for stack std::unordered_map reg_counts; + std::unordered_map stack_var_counts; for (auto& v : vars) { - reg_counts[v.reg()]++; + if (is_stack_slot_access(v)) { + stack_var_counts[env.get_variable_name_name_only(v)]++; + } else { + reg_counts[v.reg()]++; + } } for (size_t var_idx = 0; var_idx < vars.size(); var_idx++) { const auto& var = vars.at(var_idx); if (is_stack_slot_access(var)) { + int times = 1; + if (!times_used.empty()) { + times = times_used.at(var_idx); + } + + auto& use_def = env.get_use_def_info(var); + if (stack_var_counts.at(env.get_variable_name_name_only(var)) == 1 && + use_def.use_count() == times && use_def.def_count() == 1) { + submit_var_to_var.push_back(var_idx); + submit_vars.push_back(var); + } continue; } auto& ri = env.reg_use().op.at(var.idx()); @@ -430,8 +475,8 @@ void pop_helper(const std::vector& vars, auto& use_def = env.get_use_def_info(var); if (use_def.use_count() == times && use_def.def_count() == 1) { - submit_reg_to_var.push_back(var_idx); - submit_regs.push_back(var.reg()); + submit_var_to_var.push_back(var_idx); + submit_vars.push_back(var); } else { // auto var_id = env.get_program_var_id(var); // lg::print( @@ -457,9 +502,9 @@ void pop_helper(const std::vector& vars, // submit and get a result! If the stack has nothing to pop, the result here may be nullptr. std::vector pop_result; // loop in reverse (later vals first) - for (size_t i = submit_regs.size(); i-- > 0;) { + for (size_t i = submit_vars.size(); i-- > 0;) { // figure out what var we are: - auto var_idx = submit_reg_to_var.at(i); + auto var_idx = submit_var_to_var.at(i); // anything _less_ than this should be unmodified by the pop // it's fine to modify yourself in your pop. @@ -470,7 +515,7 @@ void pop_helper(const std::vector& vars, // do the pop, with the barrier to prevent out-of-sequence popping. pop_result.push_back( - stack.pop_reg(submit_regs.at(i), pop_barrier_regs, env, allow_side_effects)); + stack.pop_reg(submit_vars.at(i), pop_barrier_regs, env, allow_side_effects)); } // now flip back to the source order for making the final result std::reverse(pop_result.begin(), pop_result.end()); @@ -480,9 +525,9 @@ void pop_helper(const std::vector& vars, forms.resize(vars.size(), nullptr); if (!pop_result.empty()) { // success! - for (size_t i = 0; i < submit_regs.size(); i++) { + for (size_t i = 0; i < submit_vars.size(); i++) { // fill out vars from our submission - forms.at(submit_reg_to_var.at(i)) = pop_result.at(i); + forms.at(submit_var_to_var.at(i)) = pop_result.at(i); } } @@ -2671,6 +2716,13 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta // if we are a reg-reg move that consumes the original, push it without popping from stack. // it is the Stack's responsibility to untangle these later on. if (m_src->is_single_element()) { + auto spill_value = dynamic_cast(m_src->back()); + if (spill_value) { + stack.push_non_seq_reg_to_reg(m_dst, make_stack_slot_access(spill_value->stack_offset()), + m_src, m_src_type, m_var_info); + return; + } + auto src_as_se = dynamic_cast(m_src->back()); if (src_as_se) { if (src_as_se->expr().kind() == SimpleExpression::Kind::IDENTITY && @@ -2683,7 +2735,10 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta auto var = src_as_se->expr().get_arg(0).var(); bool is_consumed_reg_move = false; - if (!is_stack_slot_access(var)) { + if (is_stack_slot_access(var)) { + auto& use_def = env.get_use_def_info(var); + is_consumed_reg_move = use_def.use_count() == 1 && use_def.def_count() == 1; + } else { auto& info = env.reg_use().op.at(var.idx()); is_consumed_reg_move = info.consumes.find(var.reg()) != info.consumes.end(); } @@ -4023,7 +4078,9 @@ void FunctionCallElement::update_from_stack(const Env& env, } } - new_form = pool.alloc_element(GenericOperator::make_function(unstacked.at(0)), + auto called_function = + cast_inlined_function_symbol_if_needed(unstacked.at(0), function_type, pool, env); + new_form = pool.alloc_element(GenericOperator::make_function(called_function), arg_forms); { @@ -4085,7 +4142,7 @@ void FunctionCallElement::update_from_stack(const Env& env, if (match_result.matched) { auto& alloc = match_result.maps.strings.at(allocation); if (alloc != "global" && alloc != "debug" && alloc != "process" && - alloc != "loading-level") { + alloc != "loading-level" && alloc != "process-level-heap") { throw std::runtime_error("Unrecognized heap symbol for new: " + alloc); } auto type_2 = match_result.maps.strings.at(type_for_arg); @@ -6987,6 +7044,40 @@ void StackSpillStoreElement::push_to_stack(const Env& env, FormPool& pool, FormS stack_type = *m_cast_type; } + auto src_as_generic = src->try_as_element(); + if (src_as_generic && !src_as_generic->op().is_func()) { + using InplaceOpInfo = std::pair; + const static std::array in_place_ops = { + InplaceOpInfo{FixedOperatorKind::ADDITION, FixedOperatorKind::ADDITION_IN_PLACE}, + InplaceOpInfo{FixedOperatorKind::ADDITION_PTR, FixedOperatorKind::ADDITION_PTR_IN_PLACE}, + InplaceOpInfo{FixedOperatorKind::LOGAND, FixedOperatorKind::LOGAND_IN_PLACE}, + InplaceOpInfo{FixedOperatorKind::LOGIOR, FixedOperatorKind::LOGIOR_IN_PLACE}, + InplaceOpInfo{FixedOperatorKind::LOGCLEAR, FixedOperatorKind::LOGCLEAR_IN_PLACE}, + InplaceOpInfo{FixedOperatorKind::LOGXOR, FixedOperatorKind::LOGXOR_IN_PLACE}, + }; + + auto dst_var = make_stack_slot_access(m_stack_offset); + auto dst_form = pool.form(SimpleAtom::make_var(dst_var))->to_form(env); + for (const auto& [kind, inplace_kind] : in_place_ops) { + if (!src_as_generic->op().is_fixed(kind)) { + continue; + } + + for (int inplace_arg : {0, 1}) { + if (src_as_generic->elts().at(inplace_arg)->to_form(env) != dst_form) { + continue; + } + + if (inplace_arg != 0) { + std::swap(src_as_generic->elts().at(0), src_as_generic->elts().at(1)); + } + src_as_generic->op() = GenericOperator::make_fixed(inplace_kind); + stack.push_form_element(src_as_generic, true); + return; + } + } + } + stack.push_value_to_reg(make_stack_slot_access(m_stack_offset), src, true, stack_type); } diff --git a/decompiler/IR2/FormStack.cpp b/decompiler/IR2/FormStack.cpp index 97c97ce84c..cb5e1c1ab3 100644 --- a/decompiler/IR2/FormStack.cpp +++ b/decompiler/IR2/FormStack.cpp @@ -9,6 +9,17 @@ #include "decompiler/util/DecompilerTypeSystem.h" namespace decompiler { +namespace { +bool nonempty_intersection(const RegSet& a, const RegSet& b) { + for (auto x : a) { + if (b.find(x) != b.end()) { + return true; + } + } + return false; +} +} // namespace + std::string FormStack::StackEntry::print(const Env& env) const { if (destination.has_value()) { ASSERT(source && !elt); @@ -116,19 +127,59 @@ Form* FormStack::pop_reg(const RegisterAccess& var, const Env& env, bool allow_side_effects, int begin_idx) { - return pop_reg(var.reg(), barrier, env, allow_side_effects, begin_idx); -} + RegSet modified; + size_t begin = m_stack.size(); + if (begin_idx >= 0) { + begin = begin_idx; + } + for (size_t i = begin; i-- > 0;) { + auto& entry = m_stack.at(i); + if (entry.active) { + if (entry.destination.has_value() && same_expression_var(*entry.destination, var)) { + entry.source->get_modified_regs(modified); + if (!allow_side_effects && entry.source->has_side_effects()) { + return nullptr; + } + if (nonempty_intersection(modified, barrier)) { + return nullptr; + } + entry.active = false; + ASSERT(entry.source); + if (entry.non_seq_source.has_value()) { + ASSERT(entry.sequence_point == false); + auto result = pop_reg(*entry.non_seq_source, barrier, env, allow_side_effects, i); + if (result) { + return result; + } + } -namespace { -bool nonempty_intersection(const RegSet& a, const RegSet& b) { - for (auto x : a) { - if (b.find(x) != b.end()) { - return true; + return entry.source; + } else { + if (entry.sequence_point) { + return nullptr; + } + if (entry.source) { + ASSERT(!entry.elt); + entry.source->get_modified_regs(modified); + if (!allow_side_effects) { + return nullptr; + } + } else { + ASSERT(entry.elt); + entry.elt->get_modified_regs(modified); + if (!allow_side_effects && entry.elt->has_side_effects()) { + return nullptr; + } + } + } + } else { + if (entry.destination.has_value() && same_expression_var(*entry.destination, var)) { + return nullptr; + } } } - return false; + return nullptr; } -} // namespace Form* FormStack::pop_reg(Register reg, const RegSet& barrier, diff --git a/test/decompiler/test_DisasmVifDecompile.cpp b/test/decompiler/test_DisasmVifDecompile.cpp index 93f6e7be9c..9d2742ae55 100644 --- a/test/decompiler/test_DisasmVifDecompile.cpp +++ b/test/decompiler/test_DisasmVifDecompile.cpp @@ -515,172 +515,143 @@ TEST_F(FormRegressionTestJak1, ExprDisasmVif) { " daddiu sp, sp, 192"; std::string type = "(function (pointer vif-tag) int symbol symbol int)"; std::string expected = - "(let ((gp-0 0))\n" - " (while (< gp-0 (* arg1 4))\n" - " (let ((s0-0 4))\n" - " (let ((s1-0 (-> arg0 0)))\n" - " (format arg2 \" #x~X:\" arg0)\n" - " (dotimes (v1-0 (-> *vif-disasm-table* length))\n" - " (set! sv-16 (-> s1-0 cmd))\n" - " (when\n" - " (=\n" - " (logand sv-16 (-> *vif-disasm-table* v1-0 mask))\n" - " (-> *vif-disasm-table* v1-0 tag)\n" - " )\n" - " (let ((a0-12 (-> *vif-disasm-table* v1-0 print)))\n" - " (cond\n" - " ((zero? a0-12)\n" - " (format\n" - " arg2\n" - " \" (~s :irq ~D)~%\"\n" - " (-> *vif-disasm-table* v1-0 string1)\n" - " (-> s1-0 irq)\n" - " )\n" - " )\n" - " ((= a0-12 1)\n" - " (format\n" - " arg2\n" - " \" (~s :irq ~D :~s #x~X)~%\"\n" - " (-> *vif-disasm-table* v1-0 string1)\n" - " (-> s1-0 irq)\n" - " (-> *vif-disasm-table* v1-0 string2)\n" - " (-> s1-0 imm)\n" - " )\n" - " )\n" - " ((= a0-12 2)\n" - " (let ((t1-1 (-> s1-0 imm)))\n" - " (format\n" - " arg2\n" - " \" (~s :irq ~D :wl ~D :cl ~D)~%\"\n" - " (-> *vif-disasm-table* v1-0 string1)\n" - " (-> s1-0 irq)\n" - " (shr (shl t1-1 48) 56)\n" - " (shr (shl t1-1 56) 56)\n" - " )\n" - " )\n" - " )\n" - " ((= a0-12 3)\n" - " (set! s0-0 8)\n" - " (format\n" - " arg2\n" - " \" (~s :irq ~D :~s #x~X)~%\"\n" - " (-> *vif-disasm-table* v1-0 string1)\n" - " (-> s1-0 irq)\n" - " (-> *vif-disasm-table* v1-0 string2)\n" - " (-> arg0 1)\n" - " )\n" - " )\n" - " ((= a0-12 4)\n" - " (set! s0-0 20)\n" - " (format\n" - " arg2\n" - " \" (~s :irq ~D :~s \"\n" - " (-> *vif-disasm-table* v1-0 string1)\n" - " (-> s1-0 irq)\n" - " (-> *vif-disasm-table* v1-0 string2)\n" - " )\n" - " (format\n" - " arg2\n" - " \"#x~X #x~X #x~X #x~X)~%\"\n" - " (-> arg0 1)\n" - " (-> arg0 2)\n" - " (-> arg0 3)\n" - " (-> arg0 4)\n" - " )\n" - " )\n" - " ((= a0-12 5)\n" - " (format\n" - " arg2\n" - " \" (~s :irq ~D :instructions #x~D :addr #x~X)~%\"\n" - " (-> *vif-disasm-table* v1-0 string1)\n" - " (-> s1-0 irq)\n" - " (-> s1-0 num)\n" - " (-> s1-0 imm)\n" - " )\n" - " )\n" - " ((= a0-12 6)\n" - " (if (-> s1-0 imm)\n" - " (set! s0-0 #x100000)\n" - " (set! s0-0 (the-as int (* (-> s1-0 imm) 16)))\n" - " )\n" - " (format\n" - " arg2\n" - " \" (~s :irq ~D :qwc #x~D)~%\"\n" - " (-> *vif-disasm-table* v1-0 string1)\n" - " (-> s1-0 irq)\n" - " (-> s1-0 imm)\n" - " )\n" - " (set! sv-32 (&-> arg0 1))\n" - " (set! sv-48 0)\n" - " (while (< sv-48 (the-as int (-> s1-0 imm)))\n" - " (format\n" - " arg2\n" - " \" #x~X: #x~8x #x~8x #x~8x #x~8x~%\"\n" - " (+ (+ (* sv-48 16) 4) (the-as int arg0))\n" - " (-> sv-32 (* sv-48 4))\n" - " (-> sv-32 (+ (* sv-48 4) 1))\n" - " (-> sv-32 (+ (* sv-48 4) 2))\n" - " (-> sv-32 (+ (* sv-48 4) 3))\n" - " )\n" - " (set! sv-48 (+ sv-48 1))\n" - " )\n" - " #f\n" - " )\n" - " ((= a0-12 7)\n" - " (set!\n" - " s0-0\n" - " (the-as\n" - " int\n" - " (+\n" - " (logand\n" - " -4\n" - " (+ (* (-> *vif-disasm-table* v1-0 val) (-> s1-0 num)) 3)\n" - " )\n" - " 4\n" - " )\n" - " )\n" - " )\n" - " (set! sv-64 (-> s1-0 imm))\n" - " (format\n" - " arg2\n" - " \" (~s :irq ~D :num ~D :addr #x~X \"\n" - " (-> *vif-disasm-table* v1-0 string1)\n" - " (-> s1-0 irq)\n" - " (-> s1-0 num)\n" - " (shr (shl sv-64 54) 54)\n" - " )\n" - " (format\n" - " arg2\n" - " \":msk ~D :flg ~D :usn ~D [skip ~d])~%\"\n" - " (-> s1-0 msk)\n" - " (shr (shl sv-64 48) 63)\n" - " (shr (shl sv-64 49) 63)\n" - " (the-as uint s0-0)\n" - " )\n" - " (if arg3\n" - " (disasm-vif-details\n" - " arg2\n" - " (the-as (pointer uint8) arg0)\n" - " (logand sv-16 (vif-cmd cmd-mask))\n" - " (the-as int (-> s1-0 num))\n" - " )\n" - " )\n" - " )\n" - " ((= a0-12 8)\n" - " (format arg2 \" (*unknown* vif-tag #x~X)~%\" (-> s1-0 cmd))\n" - " )\n" - " )\n" - " )\n" - " (set! v1-0 (-> *vif-disasm-table* length))\n" - " )\n" - " )\n" - " )\n" - " (+! gp-0 s0-0)\n" - " (&+! arg0 s0-0)\n" - " )\n" - " )\n" - " (- gp-0 (* arg1 4))\n" - " )"; + R"((let ((gp-0 0)) + (while (< gp-0 (* arg1 4)) + (let ((s0-0 4)) + (let ((s1-0 (-> arg0 0))) + (format arg2 " #x~X:" arg0) + (dotimes (v1-0 (-> *vif-disasm-table* length)) + (let ((sv-16 (-> s1-0 cmd))) + (when (= (logand sv-16 (-> *vif-disasm-table* v1-0 mask)) (-> *vif-disasm-table* v1-0 tag)) + (let ((a0-12 (-> *vif-disasm-table* v1-0 print))) + (cond + ((zero? a0-12) + (format arg2 " (~s :irq ~D)~%" (-> *vif-disasm-table* v1-0 string1) (-> s1-0 irq)) + ) + ((= a0-12 1) + (format + arg2 + " (~s :irq ~D :~s #x~X)~%" + (-> *vif-disasm-table* v1-0 string1) + (-> s1-0 irq) + (-> *vif-disasm-table* v1-0 string2) + (-> s1-0 imm) + ) + ) + ((= a0-12 2) + (let ((t1-1 (-> s1-0 imm))) + (format + arg2 + " (~s :irq ~D :wl ~D :cl ~D)~%" + (-> *vif-disasm-table* v1-0 string1) + (-> s1-0 irq) + (shr (shl t1-1 48) 56) + (shr (shl t1-1 56) 56) + ) + ) + ) + ((= a0-12 3) + (set! s0-0 8) + (format + arg2 + " (~s :irq ~D :~s #x~X)~%" + (-> *vif-disasm-table* v1-0 string1) + (-> s1-0 irq) + (-> *vif-disasm-table* v1-0 string2) + (-> arg0 1) + ) + ) + ((= a0-12 4) + (set! s0-0 20) + (format + arg2 + " (~s :irq ~D :~s " + (-> *vif-disasm-table* v1-0 string1) + (-> s1-0 irq) + (-> *vif-disasm-table* v1-0 string2) + ) + (format arg2 "#x~X #x~X #x~X #x~X)~%" (-> arg0 1) (-> arg0 2) (-> arg0 3) (-> arg0 4)) + ) + ((= a0-12 5) + (format + arg2 + " (~s :irq ~D :instructions #x~D :addr #x~X)~%" + (-> *vif-disasm-table* v1-0 string1) + (-> s1-0 irq) + (-> s1-0 num) + (-> s1-0 imm) + ) + ) + ((= a0-12 6) + (if (-> s1-0 imm) + (set! s0-0 #x100000) + (set! s0-0 (the-as int (* (-> s1-0 imm) 16))) + ) + (format arg2 " (~s :irq ~D :qwc #x~D)~%" (-> *vif-disasm-table* v1-0 string1) (-> s1-0 irq) (-> s1-0 imm)) + (let ((sv-32 (&-> arg0 1)) + (sv-48 0) + ) + (while (< sv-48 (the-as int (-> s1-0 imm))) + (format + arg2 + " #x~X: #x~8x #x~8x #x~8x #x~8x~%" + (+ (+ (* sv-48 16) 4) (the-as int arg0)) + (-> sv-32 (* sv-48 4)) + (-> sv-32 (+ (* sv-48 4) 1)) + (-> sv-32 (+ (* sv-48 4) 2)) + (-> sv-32 (+ (* sv-48 4) 3)) + ) + (+! sv-48 1) + ) + ) + #f + ) + ((= a0-12 7) + (set! s0-0 (the-as int (+ (logand -4 (+ (* (-> *vif-disasm-table* v1-0 val) (-> s1-0 num)) 3)) 4))) + (let ((sv-64 (-> s1-0 imm))) + (format + arg2 + " (~s :irq ~D :num ~D :addr #x~X " + (-> *vif-disasm-table* v1-0 string1) + (-> s1-0 irq) + (-> s1-0 num) + (shr (shl sv-64 54) 54) + ) + (format + arg2 + ":msk ~D :flg ~D :usn ~D [skip ~d])~%" + (-> s1-0 msk) + (shr (shl sv-64 48) 63) + (shr (shl sv-64 49) 63) + (the-as uint s0-0) + ) + ) + (if arg3 + (disasm-vif-details + arg2 + (the-as (pointer uint8) arg0) + (logand sv-16 (vif-cmd cmd-mask)) + (the-as int (-> s1-0 num)) + ) + ) + ) + ((= a0-12 8) + (format arg2 " (*unknown* vif-tag #x~X)~%" (-> s1-0 cmd)) + ) + ) + ) + (set! v1-0 (-> *vif-disasm-table* length)) + ) + ) + ) + ) + (+! gp-0 s0-0) + (&+! arg0 s0-0) + ) + ) + (- gp-0 (* arg1 4)) + ) +)"; test_with_expr(func, type, expected, false, "", {{"L139", " #x~X:"}, {"L138", " (~s :irq ~D)~%"},