diff --git a/common/goos/TextDB.cpp b/common/goos/TextDB.cpp index 3c5fc18561..3a274741df 100644 --- a/common/goos/TextDB.cpp +++ b/common/goos/TextDB.cpp @@ -156,7 +156,9 @@ void TextDb::inherit_info(const Object& parent, const Object& child) { while (!children.empty()) { auto top = children.back(); children.pop_back(); - map[top->heap_obj] = parent_kv->second; + if (map.find(top->heap_obj) == map.end()) { + map[top->heap_obj] = parent_kv->second; + } if (top->as_pair()->car.is_pair()) { children.push_back(&top->as_pair()->car); } diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index aa5c8138dd..25dafabba7 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -437,6 +437,37 @@ MethodInfo TypeSystem::lookup_method(const std::string& type_name, throw std::runtime_error("lookup_method failed"); } +bool TypeSystem::try_lookup_method(const std::string& type_name, + const std::string& method_name, + MethodInfo* info) const { + auto kv = m_types.find(type_name); + if (kv == m_types.end()) { + return false; + } + + auto* iter_type = kv->second.get(); + // look up the method + while (true) { + if (method_name == "new") { + if (iter_type->get_my_new_method(info)) { + return true; + } + } else { + if (iter_type->get_my_method(method_name, info)) { + return true; + } + } + + if (iter_type->has_parent()) { + iter_type = lookup_type(iter_type->get_parent()); + } else { + // couldn't find method. + break; + } + } + return false; +} + /*! * Like lookup_method, but won't throw or print an error when things go wrong. */ diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index 2d3fb339c6..cc558c7f85 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -128,6 +128,9 @@ class TypeSystem { MethodInfo add_new_method(Type* type, const TypeSpec& ts); MethodInfo lookup_method(const std::string& type_name, const std::string& method_name) const; MethodInfo lookup_method(const std::string& type_name, int method_id) const; + bool try_lookup_method(const std::string& type_name, + const std::string& method_name, + MethodInfo* info) const; bool try_lookup_method(const std::string& type_name, int method_id, MethodInfo* info) const; MethodInfo lookup_new_method(const std::string& type_name) const; void assert_method_id(const std::string& type_name, const std::string& method_name, int id); diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index 8064c30ae0..3a5411a72f 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -10,6 +10,7 @@ add_library( analysis/inline_asm_rewrite.cpp analysis/insert_lets.cpp analysis/reg_usage.cpp + analysis/stack_spill.cpp analysis/type_analysis.cpp analysis/variable_naming.cpp diff --git a/decompiler/Disasm/Instruction.h b/decompiler/Disasm/Instruction.h index 36edab264a..6034c5de23 100644 --- a/decompiler/Disasm/Instruction.h +++ b/decompiler/Disasm/Instruction.h @@ -6,9 +6,6 @@ * Can print itself (within the context of a LinkedObjectFile). */ -#ifndef NEXT_INSTRUCTION_H -#define NEXT_INSTRUCTION_H - #include #include "OpcodeInfo.h" #include "Register.h" @@ -112,4 +109,3 @@ class Instruction { int cop2_dest_mask_intel() const; }; } // namespace decompiler -#endif // NEXT_INSTRUCTION_H diff --git a/decompiler/IR2/AtomicOp.cpp b/decompiler/IR2/AtomicOp.cpp index 37dafe3900..f8da73b146 100644 --- a/decompiler/IR2/AtomicOp.cpp +++ b/decompiler/IR2/AtomicOp.cpp @@ -1639,4 +1639,94 @@ void FunctionEndOp::collect_vars(RegAccessSet& vars) const { vars.insert(m_return_reg); } } + +///////////////////////////// +// StackSpillStoreOp +///////////////////////////// + +StackSpillStoreOp::StackSpillStoreOp(RegisterAccess value, int size, int offset, int my_idx) + : AtomicOp(my_idx), m_value(value), m_size(size), m_offset(offset) { + assert(m_value.mode() == AccessMode::READ); +} + +goos::Object StackSpillStoreOp::to_form(const std::vector&, const Env& env) const { + return pretty_print::build_list( + fmt::format("stack-store {} :offset {} :sz {}", m_value.to_string(env), m_offset, m_size)); +} + +bool StackSpillStoreOp::operator==(const AtomicOp& other) const { + if (typeid(StackSpillStoreOp) != typeid(other)) { + return false; + } + + auto po = dynamic_cast(&other); + assert(po); + return m_size == po->m_size && m_value == po->m_value && m_offset == po->m_offset; +} + +bool StackSpillStoreOp::is_sequence_point() const { + return true; // this might not be totally true, but it seems kind of scary to allow it to + // reorder. +} + +void StackSpillStoreOp::update_register_info() { + m_read_regs.push_back(m_value.reg()); +} + +void StackSpillStoreOp::collect_vars(RegAccessSet& vars) const { + vars.insert(m_value); +} + +RegisterAccess StackSpillStoreOp::get_set_destination() const { + throw std::runtime_error("StackSpillStoreOp cannot be treated as a set! operation"); +} + +///////////////////////////// +// StackSpillLoadOp +///////////////////////////// + +StackSpillLoadOp::StackSpillLoadOp(RegisterAccess dst, + int size, + int offset, + bool is_signed, + int my_idx) + : AtomicOp(my_idx), m_dst(dst), m_size(size), m_offset(offset), m_is_signed(is_signed) { + assert(m_dst.mode() == AccessMode::WRITE); +} + +goos::Object StackSpillLoadOp::to_form(const std::vector&, const Env& env) const { + return pretty_print::build_list(fmt::format("stack-load {} :offset {} :sz {} :sext #{}", + m_dst.to_string(env), m_offset, m_size, + m_is_signed ? 't' : 'f')); +} + +bool StackSpillLoadOp::operator==(const AtomicOp& other) const { + if (typeid(StackSpillStoreOp) != typeid(other)) { + return false; + } + + auto po = dynamic_cast(&other); + assert(po); + return m_size == po->m_size && m_dst == po->m_dst && m_offset == po->m_offset && + m_is_signed == po->m_is_signed; +} + +bool StackSpillLoadOp::is_sequence_point() const { + return true; // this might not be totally true, but it seems kind of scary to allow it to + // reorder. +} + +void StackSpillLoadOp::update_register_info() { + m_write_regs.push_back(m_dst.reg()); +} + +void StackSpillLoadOp::collect_vars(RegAccessSet& vars) const { + vars.insert(m_dst); +} + +RegisterAccess StackSpillLoadOp::get_set_destination() const { + // todo! + throw std::runtime_error("StackSpillLoadOp cannot be treated as a set! operation"); +} + } // namespace decompiler diff --git a/decompiler/IR2/AtomicOp.h b/decompiler/IR2/AtomicOp.h index dbc38408c8..8f3aaf44ad 100644 --- a/decompiler/IR2/AtomicOp.h +++ b/decompiler/IR2/AtomicOp.h @@ -697,8 +697,7 @@ struct IR2_RegOffset { class FunctionEndOp : public AtomicOp { public: explicit FunctionEndOp(int my_idx); - virtual goos::Object to_form(const std::vector& labels, - const Env& env) const override; + goos::Object to_form(const std::vector& labels, const Env& env) const override; bool operator==(const AtomicOp& other) const override; bool is_sequence_point() const override; RegisterAccess get_set_destination() const override; @@ -719,5 +718,52 @@ class FunctionEndOp : public AtomicOp { RegisterAccess m_return_reg; }; +/*! + * An operation to store a variable from the stack. + */ +class StackSpillStoreOp : public AtomicOp { + public: + StackSpillStoreOp(RegisterAccess value, int size, int offset, int my_idx); + goos::Object to_form(const std::vector& labels, const Env& env) const override; + bool operator==(const AtomicOp& other) const override; + bool is_sequence_point() const override; + FormElement* get_as_form(FormPool& pool, const Env& env) const override; + RegisterAccess get_set_destination() const override; + void update_register_info() override; + TypeState propagate_types_internal(const TypeState& input, + const Env& env, + DecompilerTypeSystem& dts) override; + void collect_vars(RegAccessSet& vars) const override; + + private: + RegisterAccess m_value; + int m_size; + int m_offset; +}; + +/*! + * An operation to load a variable from the stack. + */ +class StackSpillLoadOp : public AtomicOp { + public: + StackSpillLoadOp(RegisterAccess dst, int size, int offset, bool is_signed, int my_idx); + goos::Object to_form(const std::vector& labels, const Env& env) const override; + bool operator==(const AtomicOp& other) const override; + bool is_sequence_point() const override; + FormElement* get_as_form(FormPool& pool, const Env& env) const override; + RegisterAccess get_set_destination() const override; + void update_register_info() override; + TypeState propagate_types_internal(const TypeState& input, + const Env& env, + DecompilerTypeSystem& dts) override; + void collect_vars(RegAccessSet& vars) const override; + + private: + RegisterAccess m_dst; + int m_size; + int m_offset; + bool m_is_signed; +}; + bool get_as_reg_offset(const SimpleExpression& expr, IR2_RegOffset* out); } // namespace decompiler diff --git a/decompiler/IR2/AtomicOpForm.cpp b/decompiler/IR2/AtomicOpForm.cpp index d28799ab51..89a1d963c9 100644 --- a/decompiler/IR2/AtomicOpForm.cpp +++ b/decompiler/IR2/AtomicOpForm.cpp @@ -723,4 +723,29 @@ FormElement* FunctionEndOp::get_as_form(FormPool& pool, const Env&) const { FormElement* AsmBranchOp::get_as_form(FormPool& pool, const Env&) const { return pool.alloc_element(this); } + +FormElement* StackSpillLoadOp::get_as_form(FormPool& pool, const Env& env) const { + TypeSpec type("object"); + auto kv = env.stack_slot_entries.find(m_offset); + if (kv != env.stack_slot_entries.end()) { + type = kv->second.typespec; + } + return pool.alloc_element(m_dst, + pool.alloc_single_element_form( + nullptr, m_size, m_offset, m_is_signed), + true, type); +} + +FormElement* StackSpillStoreOp::get_as_form(FormPool& pool, const Env& env) const { + auto& slot_type = env.stack_slot_entries.at(m_offset).typespec; + auto src_type = env.get_types_before_op(m_my_idx).get(m_value.reg()).typespec(); + std::optional cast_type; + + if (!env.dts->ts.tc(slot_type, src_type)) { + // we fail the typecheck for a normal set!, so add a cast. + cast_type = slot_type; + } + + return pool.alloc_element(m_value, m_size, m_offset, cast_type); +} } // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index 91ef380023..8fa14e2aa7 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -3,6 +3,7 @@ #include "common/log/log.h" #include "AtomicOp.h" #include "decompiler/util/DecompilerTypeSystem.h" +#include "decompiler/IR2/bitfields.h" namespace decompiler { @@ -284,7 +285,13 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, if (m_args[1].is_int() && is_int_or_uint(dts, arg0_type)) { assert(m_args[1].get_int() >= 0); assert(m_args[1].get_int() < 64); - return TP_Type::make_from_product(1ull << m_args[1].get_int(), is_signed(dts, arg0_type)); + // this could be a bitfield access or a multiply. + // we pick bitfield access if the parent is a bitfield. + if (dynamic_cast(dts.ts.lookup_type(arg0_type.typespec()))) { + return TP_Type::make_from_left_shift_bitfield(arg0_type.typespec(), m_args[1].get_int()); + } else { + return TP_Type::make_from_product(1ull << m_args[1].get_int(), is_signed(dts, arg0_type)); + } } if (m_args[1].is_int() && dts.ts.tc(TypeSpec("pointer"), arg0_type.typespec())) { @@ -293,6 +300,27 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, } break; + case Kind::RIGHT_SHIFT_ARITH: + case Kind::RIGHT_SHIFT_LOGIC: { + bool is_unsigned = m_kind == Kind::RIGHT_SHIFT_LOGIC; + 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(); + + int size = 64 - m_args[1].get_int(); + int start_bit = end_bit - size; + if (start_bit < 0) { + throw std::runtime_error("Bad bitfield start bit"); + } + + auto type = dts.ts.lookup_type(arg0_type.get_bitfield_type()); + auto as_bitfield = dynamic_cast(type); + assert(as_bitfield); + auto field = find_field(dts.ts, as_bitfield, start_bit, size, is_unsigned); + return TP_Type::make_from_ts(field.type()); + } + } break; + case Kind::MUL_SIGNED: { if (arg0_type.is_integer_constant() && is_int_or_uint(dts, arg1_type)) { return TP_Type::make_from_product(arg0_type.get_integer_constant(), @@ -1023,4 +1051,39 @@ TypeState AsmBranchOp::propagate_types_internal(const TypeState& input, return output; } +TypeState StackSpillLoadOp::propagate_types_internal(const TypeState& input, + const Env& env, + DecompilerTypeSystem&) { + // stack slot load + auto info = env.stack_spills().lookup(m_offset); + if (info.size != m_size) { + throw std::runtime_error(fmt::format( + "Stack slot load mismatch: defined as size {}, got size {}\n", info.size, m_size)); + } + + if (info.is_signed != m_is_signed) { + throw std::runtime_error("Stack slot signed mismatch"); + } + + auto& loaded_type = input.get_slot(m_offset); + auto result = input; + result.get(m_dst.reg()) = loaded_type; + return result; +} + +TypeState StackSpillStoreOp::propagate_types_internal(const TypeState& input, + const Env& env, + DecompilerTypeSystem&) { + auto info = env.stack_spills().lookup(m_offset); + if (info.size != m_size) { + throw std::runtime_error(fmt::format( + "Stack slot load mismatch: defined as size {}, got size {}\n", info.size, m_size)); + } + + auto& stored_type = input.get(m_value.reg()); + auto result = input; + result.spill_slots[m_offset] = stored_type; + return result; +} + } // namespace decompiler diff --git a/decompiler/IR2/Env.cpp b/decompiler/IR2/Env.cpp index dfca92a0a2..defeef4efa 100644 --- a/decompiler/IR2/Env.cpp +++ b/decompiler/IR2/Env.cpp @@ -91,12 +91,17 @@ const std::string& Env::remapped_name(const std::string& name) const { } goos::Object Env::get_variable_name_with_cast(const RegisterAccess& access) const { - return get_variable_name_with_cast(access.reg(), access.idx(), access.mode()); + auto result = get_variable_and_cast(access); + if (result.cast) { + return pretty_print::build_list("the-as", result.cast->print(), result.name); + } else { + return pretty_print::to_symbol(result.name); + } } -goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const { - if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR) { - auto& var_info = m_var_names.lookup(reg, atomic_idx, mode); +VariableWithCast Env::get_variable_and_cast(const RegisterAccess& access) const { + if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) { + auto& var_info = m_var_names.lookup(access.reg(), access.idx(), access.mode()); // this is a bit of a confusing process. The first step is to grab the auto-generated name: std::string original_name = var_info.name(); auto lookup_name = original_name; @@ -118,27 +123,32 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce } // next, we insert type casts that make enforce the user override. - auto type_kv = m_typecasts.find(atomic_idx); + auto type_kv = m_typecasts.find(access.idx()); if (type_kv != m_typecasts.end()) { for (auto& x : type_kv->second) { - if (x.reg == reg) { + if (x.reg == access.reg()) { // let's make sure the above claim is true TypeSpec type_in_reg; - if (has_type_analysis() && mode == AccessMode::READ) { - type_in_reg = get_types_for_op_mode(atomic_idx, AccessMode::READ).get(reg).typespec(); + if (has_type_analysis() && access.mode() == AccessMode::READ) { + type_in_reg = get_types_for_op_mode(access.idx(), AccessMode::READ) + .get(access.reg()) + .typespec(); if (type_in_reg.print() != x.type_name) { lg::error( "Decompiler type consistency error. There was a typecast for reg {} at idx {} " "(var {}) to type {}, but the actual type is {} ({})", - reg.to_charp(), atomic_idx, lookup_name, x.type_name, type_in_reg.print(), - type_in_reg.print()); + access.reg().to_charp(), access.idx(), lookup_name, x.type_name, + type_in_reg.print(), type_in_reg.print()); assert(false); } } if (type_of_var != type_in_reg) { // TODO - use the when possible? - return pretty_print::build_list("the-as", x.type_name, lookup_name); + VariableWithCast result; + result.cast = TypeSpec(x.type_name); + result.name = lookup_name; + return result; } } } @@ -146,8 +156,9 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce // type analysis stuff runs before variable types, so we insert casts that account // for the changing types due to the lca(uses) that is used to generate variable types. - auto type_of_reg = get_types_for_op_mode(atomic_idx, mode).get(reg).typespec(); - if (mode == AccessMode::READ) { + auto type_of_reg = + get_types_for_op_mode(access.idx(), access.mode()).get(access.reg()).typespec(); + if (access.mode() == AccessMode::READ) { // note - this may be stricter than needed. but that's ok. if (type_of_var != type_of_reg) { @@ -156,7 +167,10 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce // {}\n ", // lookup_name, reg.to_charp(), atomic_idx, type_of_reg.print(), // var_info.type.typespec().print(), type_of_var.print()); - return pretty_print::build_list("the-as", type_of_reg.print(), lookup_name); + VariableWithCast result; + result.cast = type_of_reg; + result.name = lookup_name; + return result; } } else { // if we're setting a variable, we are a little less strict. @@ -170,12 +184,21 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce } } - return pretty_print::to_symbol(lookup_name); + VariableWithCast result; + result.name = lookup_name; + return result; + } else { - return pretty_print::to_symbol(reg.to_charp()); + VariableWithCast result; + result.name = access.reg().to_charp(); + return result; } } +goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const { + return get_variable_name_with_cast(RegisterAccess(mode, reg, atomic_idx, true)); +} + std::optional Env::get_user_cast_for_access(const RegisterAccess& access) const { if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) { auto& var_info = m_var_names.lookup(access.reg(), access.idx(), access.mode()); @@ -420,9 +443,22 @@ goos::Object Env::local_var_type_list(const Form* top_level_form, } count++; - elts.push_back(pretty_print::build_list(lookup_name, x.type.typespec().print())); } + + // sort in increasing offset. + // it looks like this is the order the GOAL compiler itself used. + std::vector spills; + for (auto& x : stack_slot_entries) { + spills.push_back(x.second); + } + std::sort(spills.begin(), spills.end(), + [](const StackSpillEntry& a, const StackSpillEntry& b) { return a.offset < b.offset; }); + for (auto& x : spills) { + elts.push_back(pretty_print::build_list(x.name(), x.typespec.print())); + count++; + } + if (count_out) { *count_out = count; } diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h index 9d5fe88c8b..24901ecf21 100644 --- a/decompiler/IR2/Env.h +++ b/decompiler/IR2/Env.h @@ -5,6 +5,7 @@ #include #include #include "decompiler/util/TP_Type.h" +#include "decompiler/util/StackSpillMap.h" #include "decompiler/Disasm/Register.h" #include "decompiler/IR2/IR2_common.h" #include "decompiler/analysis/reg_usage.h" @@ -16,12 +17,31 @@ class Form; class DecompilerTypeSystem; struct FunctionAtomicOps; +struct VariableWithCast { + std::string name; + std::optional cast; +}; + struct StackVarEntry { StackVariableHint hint; TypeSpec ref_type; // the actual type of the address. int size = -1; }; +struct StackSpillEntry { + TP_Type tp_type; + TypeSpec typespec; + int offset; + std::optional name_override; + std::string name() const { + if (name_override) { + return *name_override; + } else { + return fmt::format("sv-{}", offset); + } + } +}; + /*! * An "environment" for a single function. * This contains data for an entire function, like which registers are live when, the types of @@ -34,17 +54,16 @@ class Env { bool has_local_vars() const { return m_has_local_vars; } bool has_type_analysis() const { return m_has_types; } bool has_reg_use() const { return m_has_reg_use; } + const RegUsageInfo& reg_use() const { + assert(m_has_reg_use); + return m_reg_use; + } void set_reg_use(const RegUsageInfo& info) { m_reg_use = info; m_has_reg_use = true; } - const RegUsageInfo& reg_use() const { - assert(m_has_reg_use); - return m_reg_use; - } - RegUsageInfo& reg_use() { assert(m_has_reg_use); return m_reg_use; @@ -54,6 +73,7 @@ class Env { goos::Object get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const; goos::Object get_variable_name_with_cast(const RegisterAccess& access) const; std::string get_variable_name(const RegisterAccess& access) const; + VariableWithCast get_variable_and_cast(const RegisterAccess& access) const; std::optional get_user_cast_for_access(const RegisterAccess& access) const; TypeSpec get_variable_type(const RegisterAccess& access, bool using_user_var_types) const; @@ -158,9 +178,24 @@ class Env { void set_retype_map(const std::unordered_map& map) { m_var_retype = map; } + void set_stack_spills(const StackSpillMap& map) { m_stack_spill_map = map; } + const StackSpillMap& stack_spills() const { return m_stack_spill_map; } + // todo - remove these hacks at some point. LinkedObjectFile* file = nullptr; DecompilerTypeSystem* dts = nullptr; + std::unordered_map stack_slot_entries; + + std::string get_spill_slot_var_name(int offset) const { + auto kv = stack_slot_entries.find(offset); + if (kv == stack_slot_entries.end()) { + return fmt::format("sv-{}", offset); + } else { + return kv->second.name(); + } + } + + const std::unordered_map& var_remap_map() const { return m_var_remap; } private: RegisterAccess m_end_var; @@ -186,5 +221,7 @@ class Env { std::unordered_set m_vars_defined_in_let; std::optional m_type_analysis_return_type; + + StackSpillMap m_stack_spill_map; }; } // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp index 98b6a95d7f..2352dc85b4 100644 --- a/decompiler/IR2/Form.cpp +++ b/decompiler/IR2/Form.cpp @@ -2291,6 +2291,52 @@ void VectorFloatLoadStoreElement::collect_vf_regs(RegSet& regs) const { regs.insert(m_vf_reg); } +//////////////////////////////// +// StackSpillStoreElement +//////////////////////////////// + +StackSpillStoreElement::StackSpillStoreElement(RegisterAccess value, + int size, + int stack_offset, + const std::optional& cast_type) + : m_value(value), m_size(size), m_stack_offset(stack_offset), m_cast_type(cast_type) {} + +goos::Object StackSpillStoreElement::to_form_internal(const Env& env) const { + return pretty_print::build_list( + fmt::format("set! {}", env.get_spill_slot_var_name(m_stack_offset)), m_value.to_form(env)); +} + +void StackSpillStoreElement::apply(const std::function& f) { + f(this); +} + +void StackSpillStoreElement::apply_form(const std::function&) {} + +void StackSpillStoreElement::collect_vars(RegAccessSet& vars, bool) const { + vars.insert(m_value); +} + +void StackSpillStoreElement::get_modified_regs(RegSet&) const {} + +//////////////////////////////// +// StackSpillValueElement +//////////////////////////////// + +StackSpillValueElement::StackSpillValueElement(int size, int stack_offset, bool is_signed) + : m_size(size), m_stack_offset(stack_offset), m_is_signed(is_signed) {} + +goos::Object StackSpillValueElement::to_form_internal(const Env& env) const { + return pretty_print::to_symbol(env.get_spill_slot_var_name(m_stack_offset)); +} + +void StackSpillValueElement::apply(const std::function& f) { + f(this); +} + +void StackSpillValueElement::apply_form(const std::function&) {} +void StackSpillValueElement::collect_vars(RegAccessSet&, bool) const {} +void StackSpillValueElement::get_modified_regs(RegSet&) const {} + //////////////////////////////// // Utilities //////////////////////////////// diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index 675541c585..b63b2c3ddb 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -1316,6 +1316,48 @@ class VectorFloatLoadStoreElement : public FormElement { bool m_is_load = false; }; +class StackSpillStoreElement : public FormElement { + public: + StackSpillStoreElement(RegisterAccess value, + int size, + int stack_offset, + const std::optional& cast_type); + 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; + const std::optional& cast_type() const { return m_cast_type; } + + private: + RegisterAccess m_value; + int m_size = -1; + int m_stack_offset = -1; + std::optional m_cast_type; +}; + +// the value from a stack load. +class StackSpillValueElement : public FormElement { + public: + StackSpillValueElement(int size, int stack_offset, bool is_signed); + 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; + + private: + int m_size = -1; + int m_stack_offset = -1; + bool m_is_signed = false; +}; + /*! * 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 308bd194ed..920d531ccf 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -308,8 +308,8 @@ bool is_uint_type(const Env& env, int my_idx, RegisterAccess var) { bool is_ptr_or_child(const Env& env, int my_idx, RegisterAccess var, bool) { // Now that decompiler types are synced up properly, we don't want this. - // auto type = as_var ? env.get_variable_type(var, true).base_type() - // : env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type(); + // auto type = as_var ? env.get_variable_type(var, true).base_type() + // : env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type(); auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type(); return type == "pointer"; } @@ -603,7 +603,7 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, args.push_back(pool.alloc_single_element_form(nullptr, m_expr.get_arg(1))); } - bool arg0_ptr = is_ptr_or_child(env, m_my_idx, m_expr.get_arg(0).var(), is_var(args.at(0))); + bool arg0_ptr = is_ptr_or_child(env, m_my_idx, m_expr.get_arg(0).var(), true); // Look for getting an address inside of an object. // (+ process). array style access with a stride of 1. @@ -943,7 +943,7 @@ void SimpleExpressionElement::update_from_stack_logor_or_logand(const Env& env, } BitfieldManip step(manip_kind, m_expr.get_arg(1).get_int()); - auto other = read_elt->push_step(step, env.dts->ts, pool); + auto other = read_elt->push_step(step, env.dts->ts, pool, env); if (other) { result->push_back(other); } else { @@ -983,7 +983,7 @@ void SimpleExpressionElement::update_from_stack_logor_or_logand(const Env& env, assert(false); } BitfieldManip step(manip_kind, arg1_atom->get_int()); - auto other = read_elt->push_step(step, env.dts->ts, pool); + auto other = read_elt->push_step(step, env.dts->ts, pool, env); assert(!other); // shouldn't be complete. result->push_back(read_elt); return; @@ -997,7 +997,7 @@ void SimpleExpressionElement::update_from_stack_logor_or_logand(const Env& env, assert(false); } auto step = BitfieldManip::from_form(manip_kind, stripped_arg1); - auto other = read_elt->push_step(step, env.dts->ts, pool); + auto other = read_elt->push_step(step, env.dts->ts, pool, env); if (other) { result->push_back(other); } else { @@ -1066,7 +1066,7 @@ void SimpleExpressionElement::update_from_stack_left_shift(const Env& env, auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::LEFT_SHIFT, m_expr.get_arg(1).get_int()); - auto other = read_elt->push_step(step, env.dts->ts, pool); + auto other = read_elt->push_step(step, env.dts->ts, pool, env); assert(!other); // shouldn't be complete. result->push_back(read_elt); } else { @@ -1087,7 +1087,7 @@ void SimpleExpressionElement::update_from_stack_right_shift_logic(const Env& env auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL, m_expr.get_arg(1).get_int()); - auto other = read_elt->push_step(step, env.dts->ts, pool); + auto other = read_elt->push_step(step, env.dts->ts, pool, env); assert(other); // should be a high field. result->push_back(other); } else { @@ -1100,7 +1100,7 @@ void SimpleExpressionElement::update_from_stack_right_shift_logic(const Env& env if (as_bitfield_access) { BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL, m_expr.get_arg(1).get_int()); - auto next = as_bitfield_access->push_step(step, env.dts->ts, pool); + auto next = as_bitfield_access->push_step(step, env.dts->ts, pool, env); if (next) { result->push_back(next); } else { @@ -1139,7 +1139,7 @@ void SimpleExpressionElement::update_from_stack_right_shift_arith(const Env& env auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_ARITH, m_expr.get_arg(1).get_int()); - auto other = read_elt->push_step(step, env.dts->ts, pool); + auto other = read_elt->push_step(step, env.dts->ts, pool, env); assert(other); // should be a high field. result->push_back(other); } else { @@ -2068,6 +2068,12 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac form->push_back(stack.pop_back(pool)); } else { FormStack temp_stack(false); + if (form == entry.body) { + auto as_setvar = dynamic_cast(form->elts().back()); + if (as_setvar && as_setvar->is_dead_set() && as_setvar->src_type() != TypeSpec("float")) { + rewrite_as_set = false; + } + } for (auto& elt : form->elts()) { elt->push_to_stack(env, pool, temp_stack); } @@ -2119,8 +2125,9 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac source_types.push_back(last_in_body->src_type()); } last_var = last_in_body->dst(); - } // For now, I am fine with letting this fail. For example, if the set is eliminated by a - // coloring move. If this makes really ugly code later on, we could use this to disable + } + // For now, I am fine with letting this fail. For example, if the set is eliminated by a + // coloring move. If this makes really ugly code later on, we could use this to disable // write as set. } @@ -2320,7 +2327,7 @@ FormElement* ConditionElement::make_nonzero_check_generic(const Env& env, dynamic_cast(source_forms.at(0)->try_as_single_element()); if (as_bitfield_op) { bitfield_compare = as_bitfield_op->push_step( - BitfieldManip(BitfieldManip::Kind::NONZERO_COMPARE, 0), env.dts->ts, pool); + BitfieldManip(BitfieldManip::Kind::NONZERO_COMPARE, 0), env.dts->ts, pool, env); } if (bitfield_compare) { @@ -2698,7 +2705,7 @@ void push_asm_srl_to_stack(const AsmOp* op, auto base = pop_to_forms({*var}, env, pool, stack, true).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL_32BIT, integer); - auto other = read_elt->push_step(step, env.dts->ts, pool); + auto other = read_elt->push_step(step, env.dts->ts, pool, env); assert(other); // should be a high field. stack.push_value_to_reg(*dst, pool.alloc_single_form(nullptr, other), true, env.get_variable_type(*dst, true)); @@ -2740,6 +2747,7 @@ void AtomicOpElement::push_to_stack(const Env& env, FormPool& pool, FormStack& s } return; } + throw std::runtime_error("Can't push atomic op to stack: " + m_op->to_string(env)); } @@ -3079,6 +3087,20 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool, true, TypeSpec("symbol")); } +/////////////////////////// +// StackSpillStoreElement +/////////////////////////// +void StackSpillStoreElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { + mark_popped(); + auto src = pop_to_forms({m_value}, env, pool, stack, true).at(0); + auto dst = pool.alloc_single_element_form( + nullptr, env.get_spill_slot_var_name(m_stack_offset)); + if (m_cast_type) { + src = cast_form(src, *m_cast_type, pool, env); + } + stack.push_form_element(pool.alloc_element(dst, src), true); +} + void VectorFloatLoadStoreElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { mark_popped(); stack.push_form_element(this, true); @@ -3147,4 +3169,13 @@ void StackVarDefElement::update_from_stack(const Env&, result->push_back(this); } +void StackSpillValueElement::update_from_stack(const Env&, + FormPool&, + FormStack&, + std::vector* result, + bool) { + mark_popped(); + result->push_back(this); +} + } // namespace decompiler diff --git a/decompiler/IR2/bitfields.cpp b/decompiler/IR2/bitfields.cpp index 1174caa4d0..0fad82f8aa 100644 --- a/decompiler/IR2/bitfields.cpp +++ b/decompiler/IR2/bitfields.cpp @@ -143,7 +143,6 @@ void BitfieldAccessElement::get_modified_regs(RegSet& regs) const { m_base->get_modified_regs(regs); } -namespace { const BitField& find_field(const TypeSystem& ts, const BitFieldType* type, int start_bit, @@ -172,6 +171,7 @@ const BitField& find_field(const TypeSystem& ts, !looking_for_unsigned, type->get_name())); } +namespace { std::optional find_field_from_mask(const TypeSystem& ts, const BitFieldType* type, uint64_t mask) { @@ -227,7 +227,8 @@ std::optional get_bitfield_initial_set(Form* form, */ FormElement* BitfieldAccessElement::push_step(const BitfieldManip step, const TypeSystem& ts, - FormPool& pool) { + FormPool& pool, + const Env& env) { if (m_steps.empty() && step.kind == BitfieldManip::Kind::LEFT_SHIFT) { // for left/right shift combo to get a field. m_steps.push_back(step); @@ -351,8 +352,6 @@ FormElement* BitfieldAccessElement::push_step(const BitfieldManip step, return pool.alloc_element(m_type, m_base, std::vector{*val}); - - // todo check that the mask and the set are compatible with eachother } throw std::runtime_error("Unknown state in BitfieldReadElement"); diff --git a/decompiler/IR2/bitfields.h b/decompiler/IR2/bitfields.h index 7b9c4abb7e..488d459391 100644 --- a/decompiler/IR2/bitfields.h +++ b/decompiler/IR2/bitfields.h @@ -66,7 +66,10 @@ class BitfieldAccessElement : public FormElement { 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; - FormElement* push_step(const BitfieldManip step, const TypeSystem& ts, FormPool& pool); + FormElement* push_step(const BitfieldManip step, + const TypeSystem& ts, + FormPool& pool, + const Env& env); private: Form* m_base = nullptr; @@ -151,4 +154,10 @@ Form* cast_to_int_enum(const EnumType* type_info, Form* in); std::optional get_goal_integer_constant(Form* in, const Env&); + +const BitField& find_field(const TypeSystem& ts, + const BitFieldType* type, + int start_bit, + int size, + std::optional looking_for_unsigned); } // namespace decompiler diff --git a/decompiler/ObjectFile/ObjectFileDB.h b/decompiler/ObjectFile/ObjectFileDB.h index a9dae48ae7..c824c293b1 100644 --- a/decompiler/ObjectFile/ObjectFileDB.h +++ b/decompiler/ObjectFile/ObjectFileDB.h @@ -67,6 +67,7 @@ class ObjectFileDB { void analyze_functions_ir1(); void analyze_functions_ir2(const std::string& output_dir); void ir2_top_level_pass(); + void ir2_stack_spill_slot_pass(); void ir2_basic_block_pass(); void ir2_atomic_op_pass(); void ir2_type_analysis_pass(); diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index 77c05f63be..46de8bdb5e 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -17,6 +17,7 @@ #include "decompiler/analysis/final_output.h" #include "decompiler/analysis/expression_build.h" #include "decompiler/analysis/inline_asm_rewrite.h" +#include "decompiler/analysis/stack_spill.h" #include "decompiler/analysis/anonymous_function_def.h" #include "common/goos/PrettyPrinter.h" #include "decompiler/IR2/Form.h" @@ -34,6 +35,8 @@ void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir) { ir2_top_level_pass(); lg::info("Processing basic blocks and control flow graph..."); ir2_basic_block_pass(); + lg::info("Finding stack spills..."); + ir2_stack_spill_slot_pass(); lg::info("Converting to atomic ops..."); ir2_atomic_op_pass(); lg::info("Running type analysis..."); @@ -239,6 +242,23 @@ void ObjectFileDB::ir2_basic_block_pass() { 100.f * inspect_methods / total_functions); } +void ObjectFileDB::ir2_stack_spill_slot_pass() { + Timer timer; + int functions_with_spills = 0; + int total_slots = 0; + for_each_function_def_order([&](Function& func, int, ObjectFileData&) { + 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); + }); + lg::info("Analyzed stack spills: found {} functions will spills (total {} vars), took {:.2f} ms", + functions_with_spills, total_slots, timer.getMs()); +} + /*! * Conversion of MIPS instructions into AtomicOps. The AtomicOps represent what we * think are IR of the original GOAL compiler. diff --git a/decompiler/analysis/atomic_op_builder.cpp b/decompiler/analysis/atomic_op_builder.cpp index 793ed39857..3e7ff56c96 100644 --- a/decompiler/analysis/atomic_op_builder.cpp +++ b/decompiler/analysis/atomic_op_builder.cpp @@ -41,6 +41,10 @@ Register rv0() { return make_gpr(Reg::V0); } +Register rsp() { + return make_gpr(Reg::SP); +} + ///////////////////////// // Variable Helpers ///////////////////////// @@ -149,6 +153,12 @@ std::unique_ptr make_standard_load(const Instruction& i0, if (i0.get_src(0).is_label() && i0.get_src(1).is_reg(rfp())) { // it's an FP relative load. src = SimpleAtom::make_static_address(i0.get_src(0).get_label()).as_expr(); + } else if (i0.get_src(0).is_imm() && i0.get_src(1).is_reg(rsp()) && + (kind == LoadVarOp::Kind::SIGNED || kind == LoadVarOp::Kind::UNSIGNED)) { + // it's a stack spill. + return std::make_unique(make_dst_var(i0, idx), load_size, + i0.get_src(0).get_imm(), + kind == LoadVarOp::Kind::SIGNED, idx); } else if (i0.get_src(0).is_imm() && i0.get_src(0).get_imm() == 0) { // the offset is 0 src = make_src_atom(i0.get_src(1).get_reg(), idx).as_expr(); @@ -165,8 +175,17 @@ std::unique_ptr make_standard_store(const Instruction& i0, int idx, int store_size, StoreOp::Kind kind) { - if (i0.get_src(2).is_reg(Register(Reg::GPR, Reg::SP))) { - return std::make_unique(i0, idx); + if (i0.get_src(2).is_reg(Register(Reg::GPR, Reg::SP)) && kind == StoreOp::Kind::INTEGER) { + if (kind == StoreOp::Kind::INTEGER && store_size == 4 && i0.get_src(1).get_imm() == 0) { + // this is a bit of a hack. enter-state does a sw onto the stack that's not a spill, but + // instead manipulates the stores "ra" register that will later be restored. + // I believe sw is never used for stack spills, and no stack variable is ever located at + // sp + 0, so this should be safe. + return std::make_unique(i0, idx); + } + // it's a stack spill. + return std::make_unique(make_src_var(i0.get_src(0).get_reg(), idx), + store_size, i0.get_src(1).get_imm(), idx); } SimpleAtom val; SimpleExpression dst; diff --git a/decompiler/analysis/expression_build.cpp b/decompiler/analysis/expression_build.cpp index 3a4ae7760b..242dd2c0d2 100644 --- a/decompiler/analysis/expression_build.cpp +++ b/decompiler/analysis/expression_build.cpp @@ -34,8 +34,18 @@ bool convert_to_expressions( // get variable names from the user. f.ir2.env.map_args_from_config(arg_names, var_override_map); - // override variable types from the user. + // convert to typespec + for (auto& info : f.ir2.env.stack_slot_entries) { + auto rename = f.ir2.env.var_remap_map().find(info.second.name()); + if (rename != f.ir2.env.var_remap_map().end()) { + info.second.name_override = rename->second; + } + // debug + // fmt::print("STACK {} : {} ({})\n", info.first, info.second.typespec.print(), + // info.second.tp_type.print()); + } + // override variable types from the user. std::unordered_map retype; for (auto& remap : var_override_map) { if (remap.second.type) { diff --git a/decompiler/analysis/stack_spill.cpp b/decompiler/analysis/stack_spill.cpp new file mode 100644 index 0000000000..41f9c93dbf --- /dev/null +++ b/decompiler/analysis/stack_spill.cpp @@ -0,0 +1,97 @@ +#include +#include "third-party/fmt/core.h" +#include "stack_spill.h" +#include "decompiler/Disasm/DecompilerLabel.h" + +namespace decompiler { + +std::string StackSpillSlot::print() const { + return fmt::format("[{:3d}] {}{}", offset, is_signed ? 's' : 'u', size * 8); +} + +void StackSpillMap::add_access(const StackSpillSlot& access) { + auto existing = m_slot_map.find(access.offset); + if (existing != m_slot_map.end()) { + if (access != existing->second) { + throw std::runtime_error(fmt::format("Inconsistent stack access:\n{}\n{}\n", + existing->second.print(), access.print())); + } + } else { + m_slot_map.insert({access.offset, access}); + } +} + +const StackSpillSlot& StackSpillMap::lookup(int offset) const { + auto result = m_slot_map.find(offset); + if (result == m_slot_map.end()) { + throw std::runtime_error(fmt::format("unknown stack spill slot at offset {}", offset)); + } + return result->second; +} + +void StackSpillMap::finalize() { + // how many variables exist at each byte. should be 1 or 0. + int max_offset = 0; + for (auto& slot : m_slot_map) { + max_offset = std::max(max_offset, slot.second.offset + slot.second.size); + } + + assert(max_offset < 4096); // just a sanity check here + std::vector var_count(max_offset, 0); + + for (auto& slot : m_slot_map) { + for (int i = 0; i < slot.second.size; i++) { + var_count.at(slot.second.offset + i)++; + } + } + + for (size_t i = 0; i < var_count.size(); i++) { + if (var_count[i] > 1) { + throw std::runtime_error( + fmt::format("There are {} variables at stack offset {}", var_count[i], i)); + } + } +} + +int StackSpillMap::size() const { + return m_slot_map.size(); +} + +namespace { +struct StackInstrInfo { + InstructionKind kind; + bool is_load; + int size; + bool is_signed; +}; + +constexpr StackInstrInfo stack_instrs[] = {{InstructionKind::SQ, false, 16, false}, + {InstructionKind::LQ, true, 16, false}}; +} // namespace + +StackSpillMap build_spill_map(const std::vector& instructions, Range range) { + StackSpillMap map; + + for (auto idx : range) { + auto& instr = instructions.at(idx); + + for (auto& instr_template : stack_instrs) { + if (instr.kind == instr_template.kind) { + // we are the right kind. + auto src_reg = instr.get_src(instr_template.is_load ? 1 : 2).get_reg(); + if (src_reg == Register(Reg::GPR, Reg::SP)) { + StackSpillSlot slot; + slot.offset = instr.get_src(instr_template.is_load ? 0 : 1).get_imm(); + slot.size = instr_template.size; + slot.is_signed = instr_template.is_signed; + map.add_access(slot); + } + break; + } + } + } + + map.finalize(); + return map; +} +} // namespace decompiler \ No newline at end of file diff --git a/decompiler/analysis/stack_spill.h b/decompiler/analysis/stack_spill.h new file mode 100644 index 0000000000..451f48884a --- /dev/null +++ b/decompiler/analysis/stack_spill.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include "decompiler/Disasm/Instruction.h" +#include "common/util/Range.h" +#include "decompiler/util/StackSpillMap.h" + +namespace decompiler { + +/*! + * Given the instructions for a function, build a StackSpillMap containing all memory used to + * spill register variables. The range should be the non-prologue/non-epilogue instruction range. + */ +StackSpillMap build_spill_map(const std::vector& instructions, Range range); + +} // namespace decompiler \ No newline at end of file diff --git a/decompiler/analysis/type_analysis.cpp b/decompiler/analysis/type_analysis.cpp index 1126d52b1e..ed9820580b 100644 --- a/decompiler/analysis/type_analysis.cpp +++ b/decompiler/analysis/type_analysis.cpp @@ -2,7 +2,7 @@ namespace decompiler { namespace { -TypeState construct_initial_typestate(const TypeSpec& f_ts) { +TypeState construct_initial_typestate(const TypeSpec& f_ts, const Env& env) { TypeState result; int goal_args[] = {Reg::A0, Reg::A1, Reg::A2, Reg::A3, Reg::T0, Reg::T1, Reg::T2, Reg::T3}; assert(f_ts.base_type() == "function"); @@ -11,11 +11,16 @@ TypeState construct_initial_typestate(const TypeSpec& f_ts) { for (int i = 0; i < int(f_ts.arg_count()) - 1; i++) { auto reg_id = goal_args[i]; auto reg_type = f_ts.get_arg(i); - result.gpr_types[reg_id] = TP_Type::make_from_ts(reg_type); + result.get(Register(Reg::GPR, reg_id)) = TP_Type::make_from_ts(reg_type); } // todo, more specific process types for behaviors. - result.gpr_types[Reg::S6] = TP_Type::make_from_ts(TypeSpec("process")); + result.get(Register(Reg::GPR, Reg::S6)) = TP_Type::make_from_ts(TypeSpec("process")); + + // initialize stack slots as uninitialized + for (auto slot_info : env.stack_spills().map()) { + result.spill_slots.insert({slot_info.first, {}}); + } return result; } @@ -80,7 +85,7 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F op_types.resize(func.ir2.atomic_ops->ops.size()); auto& aop = func.ir2.atomic_ops; - // STEP 1 - topologocial sort the blocks. This gives us an order where we: + // STEP 1 - topological sort the blocks. This gives us an order where we: // - never visit unreachable blocks (we can't type propagate these) // - always visit at least one predecessor of a block before that block auto order = func.bb_topo_sort(); @@ -88,7 +93,7 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F assert(order.vist_order.front() == 0); // STEP 2 - initialize type state for the first block to the function argument types. - block_init_types.at(0) = construct_initial_typestate(my_type); + block_init_types.at(0) = construct_initial_typestate(my_type, func.ir2.env); // STEP 3 - propagate types until the result stops changing bool run_again = true; @@ -109,7 +114,8 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F try { op_types.at(op_id) = op->propagate_types(*init_types, func.ir2.env, dts); } catch (std::runtime_error& e) { - lg::warn("Function {} failed type prop: {}", func.guessed_name.to_string(), e.what()); + lg::warn("Function {} failed type prop at op {}: {}", func.guessed_name.to_string(), + op_id, e.what()); func.warnings.type_prop_warning("{}", e.what()); func.ir2.env.set_types(block_init_types, op_types, *func.ir2.atomic_ops, my_type); return false; @@ -162,6 +168,35 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F } } + // figure out the types of stack spill variables: + auto& env = func.ir2.env; + bool changed; + for (auto& type_info : op_types) { + for (auto& spill : type_info.spill_slots) { + auto& slot_info = env.stack_slot_entries[spill.first]; + slot_info.tp_type = + dts.tp_lca(env.stack_slot_entries[spill.first].tp_type, spill.second, &changed); + slot_info.offset = spill.first; + } + } + + for (auto& type_info : block_init_types) { + for (auto& spill : type_info.spill_slots) { + auto& slot_info = env.stack_slot_entries[spill.first]; + slot_info.tp_type = + dts.tp_lca(env.stack_slot_entries[spill.first].tp_type, spill.second, &changed); + slot_info.offset = spill.first; + } + } + + // convert to typespec + for (auto& info : env.stack_slot_entries) { + info.second.typespec = info.second.tp_type.typespec(); + // debug + // fmt::print("STACK {} : {} ({})\n", info.first, info.second.typespec.print(), + // info.second.tp_type.print()); + } + func.ir2.env.set_types(block_init_types, op_types, *func.ir2.atomic_ops, my_type); return true; diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index 16862c76d0..096b0740e2 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -2444,6 +2444,7 @@ (unpack-v4-16 109) (unpack-v4-8 110) (unpack-v4-5 111) + (cmd-mask 239) ;; not sure what this is ) (defenum vif-cmd-32 @@ -2772,12 +2773,12 @@ ) (define-extern *vif-disasm-table* (array vif-disasm-element)) ;; unknown type -;;(define-extern disasm-vif-tag (function (pointer uint32) int symbol int symbol)) -(define-extern disasm-dma-tag (function dma-tag symbol int)) +(define-extern disasm-vif-tag (function (pointer vif-tag) int symbol symbol int)) +(define-extern disasm-dma-tag (function dma-tag symbol none)) (define-extern disasm-vif-details (function symbol (pointer uint8) vif-cmd int symbol)) (define-extern vif-disasm-element type) (define-extern *dma-disasm* symbol) -(define-extern disasm-dma-list function) +(define-extern disasm-dma-list (function dma-packet symbol symbol symbol int symbol)) ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc index 6d32caedee..719ee97dc0 100644 --- a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc @@ -181,6 +181,36 @@ [[202, 225], "s3", "(pointer uint16)"] ], + "disasm-vif-tag": [ + [[81, 85], "t1", "vif-stcycl-imm"], + [242, "a0", "vif-unpack-imm"] + ], + + "disasm-dma-list": [ + [25, "v1", "dma-tag"], + + [153, "v1", "dma-packet"], + [189, "v1", "dma-packet"], + [229, "v1", "dma-packet"], + [258, "v1", "dma-packet"], + [302, "v1", "dma-packet"], + [308, "v1", "dma-packet"], + + //[133, "v1", "(pointer uint64)"], + [152, "v1", "(pointer uint64)"], + + [167, "v1", "(pointer uint64)"], + [176, "v1", "(pointer uint64)"], + [198, "v1", "(pointer uint64)"], + [207, "v1", "(pointer uint64)"], + [238, "v1", "(pointer uint64)"], + [247, "v1", "(pointer uint64)"], + [282, "v1", "(pointer uint64)"], + [291, "v1", "(pointer uint64)"], + [324, "v1", "(pointer uint64)"], + [334, "v1", "(pointer uint64)"] + ], + // LEVEL "lookup-level-info": [ [3, "a1", "symbol"], diff --git a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc index 4f3ab40df4..7764133e98 100644 --- a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc @@ -687,6 +687,42 @@ "vars": { "s4-0": "count2", "s3-0": "data-ptr", "s2-0": "i" } }, + "disasm-vif-tag": { + "args": ["data", "words", "stream", "details"], + "vars": { + "gp-0": "byte-idx", + "v1-0": "cmd-template-idx", + "a0-12": "print-kind", + "s1-0": "first-tag", + "s0-0": "packet-size", + "t1-1": ["stcycl-imm", "vif-stcycl-imm"], + "sv-16": "cmd", + "sv-32": "data-ptr", + "sv-48": "data-idx", + "sv-64": "unpack-imm" + } + }, + + "disasm-dma-list": { + "args": ["data", "mode", "verbose", "stream", "expected-size"], + "vars": { + "sv-16": "addr", + "sv-32": "data-2", + "sv-48": "qwc", + "sv-64": "ra-1", + "sv-80": "ra-2", + "sv-96": "call-depth", + "sv-112": "current-tag", + "s2-0": "mode-2", + "s3-0": "verbose-2", + "gp-0": "stream-2", + "s1-0": "expected-size-2", + "s0-0": "end-condition", + "s4-0": "total-qwc", + "s5-0": "total-tags" + } + }, + "cpad-invalid!": { "args": ["pad"] }, diff --git a/decompiler/util/DecompilerTypeSystem.cpp b/decompiler/util/DecompilerTypeSystem.cpp index 470b7bf1dc..70dd711eff 100644 --- a/decompiler/util/DecompilerTypeSystem.cpp +++ b/decompiler/util/DecompilerTypeSystem.cpp @@ -367,10 +367,28 @@ bool DecompilerTypeSystem::tp_lca(TypeState* combined, const TypeState& add) { } } + for (auto& x : add.spill_slots) { + // auto existing = combined->spill_slots.find(x.first); + // if (existing == combined->spill_slots.end()) { + // result = true; + // combined->spill_slots.insert({existing->first, existing->second}); + // } + bool diff = false; + auto new_type = tp_lca(combined->spill_slots[x.first], x.second, &diff); + if (diff) { + result = true; + combined->spill_slots[x.first] = new_type; + } + } + return result; } int DecompilerTypeSystem::get_format_arg_count(const std::string& str) const { + // temporary hack, remove this. + if (str == "ERROR: dma tag has data in reserved bits ~X~%") { + return 0; + } int arg_count = 0; for (size_t i = 0; i < str.length(); i++) { if (str.at(i) == '~') { diff --git a/decompiler/util/StackSpillMap.h b/decompiler/util/StackSpillMap.h new file mode 100644 index 0000000000..7ebe8a6c68 --- /dev/null +++ b/decompiler/util/StackSpillMap.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace decompiler { +/*! + * Memory on the stack used to spill a register. + * This just has a size/is_signed? and doesn't know anything about types. + */ +struct StackSpillSlot { + int offset = -1; // relative to sp + int size = -1; // bytes + bool is_signed = false; // set to false for quadwords/doublewords + + bool operator==(const StackSpillSlot& other) const { + return offset == other.offset && size == other.size && is_signed == other.is_signed; + } + + bool operator!=(const StackSpillSlot& other) const { return !((*this) == other); } + + std::string print() const; +}; + +/*! + * Map of StackSpillSlots for a function. + */ +class StackSpillMap { + public: + void add_access(const StackSpillSlot& access); + void finalize(); + const StackSpillSlot& lookup(int offset) const; + int size() const; + const std::unordered_map& map() const { return m_slot_map; } + + private: + std::unordered_map m_slot_map; +}; +} // namespace decompiler \ No newline at end of file diff --git a/decompiler/util/TP_Type.cpp b/decompiler/util/TP_Type.cpp index e6f60fcfba..38c330eedf 100644 --- a/decompiler/util/TP_Type.cpp +++ b/decompiler/util/TP_Type.cpp @@ -59,6 +59,8 @@ std::string TP_Type::print() const { return fmt::format("", m_ts.print()); case Kind::NON_VIRTUAL_METHOD: return fmt::format("", m_ts.print()); + case Kind::LEFT_SHIFTED_BITFIELD: + return fmt::format("(<{}> << {})", m_ts.print(), m_int); case Kind::INVALID: default: assert(false); @@ -105,6 +107,8 @@ bool TP_Type::operator==(const TP_Type& other) const { case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT: return m_int == other.m_int && m_ts == other.m_ts && m_extra_multiplier == other.m_extra_multiplier; + case Kind::LEFT_SHIFTED_BITFIELD: + return m_int == other.m_int && m_ts == other.m_ts; case Kind::INVALID: default: assert(false); @@ -157,6 +161,8 @@ TypeSpec TP_Type::typespec() const { return m_ts; case Kind::NON_VIRTUAL_METHOD: return m_ts; + case Kind::LEFT_SHIFTED_BITFIELD: + return TypeSpec("int"); // ideally this is never used. case Kind::INVALID: default: assert(false); diff --git a/decompiler/util/TP_Type.h b/decompiler/util/TP_Type.h index 0e89f15134..2b9feedfb0 100644 --- a/decompiler/util/TP_Type.h +++ b/decompiler/util/TP_Type.h @@ -32,6 +32,7 @@ class TP_Type { DYNAMIC_METHOD_ACCESS, // partial access into a VIRTUAL_METHOD, NON_VIRTUAL_METHOD, + LEFT_SHIFTED_BITFIELD, // (bitfield << some-constant) INVALID } kind = Kind::UNINITIALIZED; TP_Type() = default; @@ -57,6 +58,7 @@ class TP_Type { case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT: case Kind::VIRTUAL_METHOD: case Kind::NON_VIRTUAL_METHOD: + case Kind::LEFT_SHIFTED_BITFIELD: return false; case Kind::UNINITIALIZED: case Kind::OBJECT_NEW_METHOD: @@ -209,6 +211,14 @@ class TP_Type { return result; } + static TP_Type make_from_left_shift_bitfield(const TypeSpec& ts, int amount) { + TP_Type result; + result.kind = Kind::LEFT_SHIFTED_BITFIELD; + result.m_ts = ts; + result.m_int = amount; + return result; + } + const TypeSpec& get_objects_typespec() const { assert(kind == Kind::TYPESPEC || kind == Kind::INTEGER_CONSTANT_PLUS_VAR); return m_ts; @@ -249,6 +259,16 @@ class TP_Type { return m_extra_multiplier; } + int get_left_shift() const { + assert(kind == Kind::LEFT_SHIFTED_BITFIELD); + return m_int; + } + + const TypeSpec& get_bitfield_type() const { + assert(kind == Kind::LEFT_SHIFTED_BITFIELD); + return m_ts; + } + private: TypeSpec m_ts; std::string m_str; @@ -260,6 +280,7 @@ class TP_Type { struct TypeState { TP_Type gpr_types[32]; TP_Type fpr_types[32]; + std::unordered_map spill_slots; std::string print_gpr_masked(u32 mask) const; TP_Type& get(const Register& r) { @@ -285,6 +306,16 @@ struct TypeState { throw std::runtime_error("TP_Type::get failed"); } } + + const TP_Type& get_slot(int offset) const { + auto result = spill_slots.find(offset); + if (result == spill_slots.end()) { + throw std::runtime_error("TP_Type::get_slot failed: " + std::to_string(offset)); + } + return result->second; + } + + TP_Type& get_slot(int offset) { return spill_slots[offset]; } }; u32 regs_to_gpr_mask(const std::vector& regs); diff --git a/goal_src/engine/dma/dma-disasm.gc b/goal_src/engine/dma/dma-disasm.gc index 4b01102255..3fbdd31f35 100644 --- a/goal_src/engine/dma/dma-disasm.gc +++ b/goal_src/engine/dma/dma-disasm.gc @@ -56,3 +56,600 @@ (new 'static 'vif-disasm-element :print #x8))) + +(defun disasm-vif-details ((stream symbol) (data (pointer uint8)) (kind vif-cmd) (count int)) + (cond + ((= kind (vif-cmd unpack-v4-8)) + (let ((data-ptr (&-> data 4))) + (dotimes (i count) + (format stream " #x~X: #x~2X #x~2X #x~2X #x~2X~%" + (+ (+ (shl i 2) 4) (the-as int data)) + (-> data-ptr (shl i 2)) + (-> data-ptr (+ (shl i 2) 1)) + (-> data-ptr (+ (shl i 2) 2)) + (-> data-ptr (+ (shl i 2) 3)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-s-8)) + (let ((s3-1 (&-> data 4))) + (dotimes (s2-1 count) + ;; this is messed up and I think somebody put a parenthesis in + ;; the wrong spot. I believe the format below only has one + ;; format argument but should have 2. + (format stream " #x~X: #x~2x~%" + (+ (+ s2-1 4) (the-as int data)) + count + ) + ;; the actual assembly is very strange here. + (let ((v1-21 (-> s3-1 (* 3 s2-1)))) + ) + (let ((v1-26 (-> s3-1 (+ (* 3 s2-1) 1)))) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v4-32)) + (let ((s3-2 (the-as (pointer uint32) (&-> data 4)))) + (dotimes (s2-2 count) + (format stream " #x~X: #x~8x #x~8x #x~8x #x~8x~%" + (+ (+ (shl s2-2 4) 4) (the-as int data)) + (-> s3-2 (shl s2-2 2)) + (-> s3-2 (+ (shl s2-2 2) 1)) + (-> s3-2 (+ (shl s2-2 2) 2)) + (-> s3-2 (+ (shl s2-2 2) 3)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v4-16)) + (let ((s3-3 (the-as (pointer uint16) (&-> data 4)))) + (dotimes (s2-3 count) + (format stream " #x~X: #x~4x #x~4x #x~4x #x~4x~%" + (+ (+ (shl s2-3 3) 4) (the-as int data)) + (-> s3-3 (shl s2-3 2)) + (-> s3-3 (+ (shl s2-3 2) 1)) + (-> s3-3 (+ (shl s2-3 2) 2)) + (-> s3-3 (+ (shl s2-3 2) 3)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v3-32)) + (let ((s3-4 (the-as (pointer uint32) (&-> data 4)))) + (dotimes (s2-4 count) + (format stream " #x~X: #x~8x #x~8x #x~8x~%" + (+ (+ (* 12 s2-4) 4) (the-as int data)) + (-> (&+ s3-4 (* 12 s2-4)) 0) + (-> s3-4 (+ (* 3 s2-4) 1)) + (-> s3-4 (+ (* 3 s2-4) 2)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v3-16)) + (let ((s3-5 (the-as (pointer uint16) (&-> data 4)))) + (dotimes (s2-5 count) + (format stream " #x~X: #x~4x #x~4x #x~4x~%" + (+ (+ (* 6 s2-5) 4) (the-as int data)) + (-> (&+ s3-5 (* 6 s2-5)) 0) + (-> s3-5 (+ (* 3 s2-5) 1)) + (-> s3-5 (+ (* 3 s2-5) 2)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v2-16)) + (let ((s3-6 (the-as (pointer uint16) (&-> data 4)))) + (dotimes (s2-6 count) + (format stream " #x~X: #x~4x #x~4x~%" + (+ (+ (shl s2-6 2) 4) (the-as int data)) + (-> (&+ s3-6 (* 6 s2-6)) 0) + (-> s3-6 (+ (* 3 s2-6) 1)) + ) + ) + ) + ) + (else + (format stream " #x~X: Data format #b~b not yet supported, add it for yourself!~%" + (&-> data 4) + kind + ) + ) + ) + #f + ) + +(defun disasm-vif-tag ((data (pointer vif-tag)) (words int) (stream symbol) (details symbol)) + "Print out a vif code and the immediate data. + Will print stuff until the number of words has been reached. + Returns how many bytes we overshot by." + (local-vars + (cmd vif-cmd) + (data-ptr (pointer vif-tag)) + (data-idx int) + (unpack-imm vif-unpack-imm) + ) + (let ((byte-idx 0)) + (while (< byte-idx (shl words 2)) + (let ((packet-size 4)) ;; default packet size is 32-bits. + (let ((first-tag (-> data 0))) + ;; print the packet's address. + (format stream " #x~X:" data) + ;; iterate through the disasm table, looking for a match + (dotimes (cmd-template-idx (-> *vif-disasm-table* length)) + (set! cmd (-> first-tag cmd)) + ;; check the command against the table's mask and tag + (when (= (logand cmd (the-as uint (-> *vif-disasm-table* cmd-template-idx mask))) + (-> *vif-disasm-table* cmd-template-idx tag) + ) + (let* ((print-kind (-> *vif-disasm-table* cmd-template-idx print)) + (v0-1 (cond + ((zero? print-kind) + ;; just the name and irq bit. + (format stream " (~s :irq ~D)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + ) + ) + ((= print-kind 1) + ;; name and immediate register value. + (format stream " (~s :irq ~D :~s #x~X)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> *vif-disasm-table* cmd-template-idx string2) + (-> first-tag imm) + ) + ) + ((= print-kind 2) + ;; name and stcycl immediate + (let ((stcycl-imm (the-as vif-stcycl-imm (-> first-tag imm)))) + (format stream " (~s :irq ~D :wl ~D :cl ~D)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> stcycl-imm wl) + (-> stcycl-imm cl) + ) + ) + ) + ((= print-kind 3) + ;; name and a single word of extra data + (set! packet-size 8) ;; 4 + 4 = 8 byte packet. + (format stream " (~s :irq ~D :~s #x~X)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> *vif-disasm-table* cmd-template-idx string2) + (-> data 1) + ) + ) + ((= print-kind 4) + ;; 4x 1 word extra data + (set! packet-size 20) ;; 4 + 16 = 20 byte packet. + (format stream " (~s :irq ~D :~s " + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> *vif-disasm-table* cmd-template-idx string2) + ) + (format stream "#x~X #x~X #x~X #x~X)~%" + (-> data 1) + (-> data 2) + (-> data 3) + (-> data 4) + ) + ) + ((= print-kind 5) + (format stream " (~s :irq ~D :instructions #x~D :addr #x~X)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> first-tag num) + (-> first-tag imm) + ) + ) + ((= print-kind 6) + ;; imm is quadword count. + ;; This packet size calculation is wrong. + ;; this doesn't seem to be a decompiler error, this matches + ;; the assembly, but makes no sense. + (set! packet-size + (the-as int (if (-> first-tag imm) + #x100000 + (shl (-> first-tag imm) 4) + ) + ) + ) + (format stream " (~s :irq ~D :qwc #x~D)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> first-tag imm) + ) + + ;; loop over data and print it. + (set! data-ptr (&-> data 1)) + (set! data-idx 0) + (while (< data-idx (the-as int (-> first-tag imm))) + (format stream " #x~X: #x~8x #x~8x #x~8x #x~8x~%" + (+ (+ (shl data-idx 4) 4) (the-as int data)) + (-> data-ptr (shl data-idx 2)) + (-> data-ptr (+ (shl data-idx 2) 1)) + (-> data-ptr (+ (shl data-idx 2) 2)) + (-> data-ptr (+ (shl data-idx 2) 3)) + ) + (set! data-idx (+ data-idx 1)) + ) + #f + ) + ((= print-kind 7) + (set! packet-size + (the-as int + (+ + (logand + -4 + (+ (* (-> *vif-disasm-table* cmd-template-idx val) + (the-as uint (-> first-tag num))) + 3 + ) + ) + 4 + ) + ) + ) + (set! unpack-imm (the-as vif-unpack-imm (-> first-tag imm))) + (format stream " (~s :irq ~D :num ~D :addr #x~X " + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> first-tag num) + (-> unpack-imm addr) + ) + (format stream ":msk ~D :flg ~D :usn ~D [skip ~d])~%" + (-> first-tag msk) + (-> unpack-imm flg) + (-> unpack-imm usn) + (the-as uint packet-size) + ) + (if details + (disasm-vif-details + stream + (the-as (pointer uint8) data) + (logand cmd (vif-cmd cmd-mask)) + (the-as int (-> first-tag num)) + ) + ) + ) + ((= print-kind 8) + (format stream " (*unknown* vif-tag #x~X)~%" + (-> first-tag cmd) + ) + ) + ) + ) + ) + ) + ;; we matched, skip to the end. + (set! cmd-template-idx (-> *vif-disasm-table* length)) + ) + ) + ) + ;; increment counters. + (+! byte-idx packet-size) + (&+! data packet-size) + ) + ) + (- byte-idx (shl words 2)) + ) + ) + +(defun disasm-dma-tag ((arg0 dma-tag) (arg1 symbol)) + (format arg1 "(dma-tag ") + ;; this is a case statement. + (let ((t9-1 format) + (a0-2 arg1) + (a1-2 "~s") + (v1-1 (-> arg0 id)) + ) + (t9-1 a0-2 a1-2 (cond + ((= v1-1 (dma-tag-id end)) + "end" + ) + ((= v1-1 (dma-tag-id ret)) + "ret" + ) + ((= v1-1 (dma-tag-id call)) + "call" + ) + ((= v1-1 (dma-tag-id refs)) + "refs" + ) + ((= v1-1 (dma-tag-id ref)) + "ref" + ) + ((= v1-1 (dma-tag-id next)) + "next" + ) + ((= v1-1 (dma-tag-id cnt)) + "cnt" + ) + ((zero? v1-1) + "refe" + ) + (else + "*unknown*" + ) + ) + ) + ) + (if (> (the-as uint (-> arg0 addr)) 0) + (format arg1 " :addr #x~8x" (-> arg0 addr)) + ) + (if (> (the-as uint (-> arg0 qwc)) 0) + (format arg1 " :qwc ~d" (-> arg0 qwc)) + ) + (if (> (the-as uint (-> arg0 spr)) 0) + (format arg1 " :spr ~d" (-> arg0 spr)) + ) + (if (> (the-as uint (-> arg0 irq)) 0) + (format arg1 " :irq ~d" (-> arg0 irq)) + ) + (if (> (the-as uint (-> arg0 pce)) 0) + (format arg1 " :pce ~d" (-> arg0 pce)) + ) + (format arg1 ")~%") + (none) + ) + +;; this is unused. +(define *dma-disasm* #t) + +(defun disasm-dma-list ((data dma-packet) (mode symbol) (verbose symbol) (stream symbol) (expected-size int)) + "Disassemble a dma list, starting from the given packet." + (local-vars + (addr object) + (data-2 dma-packet) + (qwc int) + (ra-1 object) + (ra-2 object) + (call-depth int) + (current-tag dma-tag) + ) + ;; this is a little messed up because of stack spills. + (set! data-2 data) + (let ((mode-2 mode) + (verbose-2 verbose) + (stream-2 stream) + (expected-size-2 expected-size) + ) + (if verbose-2 + (format stream-2 "~%--- ~X -----------------------------~%" data-2) + ) + ;; the end-condition will get set to #t when the end of the chain is reached, + ;; or 'error if invalid data is found. + (let ((end-condition #f)) + ;; statistics + (let ((total-qwc 0) + (total-tags 0) + ) + (set! addr 0) + (set! qwc 0) + ;; for the "call" feature + (set! ra-1 0) + (set! ra-2 1) + (set! call-depth -1) + ;; the tag we're currently exploring. + (set! current-tag (new 'static 'dma-tag)) + ;; loop until tag is done + (while (not end-condition) + ;; first, we should verify that the data pointer is valid so we don't crash + (cond + ((not (valid? data-2 (the-as type #f) "dma-list tag pointer" #t stream-2)) + (format stream-2 "ERROR: dma-list tag pointer invalid~%") + (set! end-condition 'error) + ) + (else + ;; load the tag + (set! current-tag (-> data-2 dma)) + ;; check the address. it is unset on the first pass so we skip this check then. + (when (not (or (zero? total-tags) + (valid? addr (the-as type #f) "dma-list data pointer" #t stream-2) + ) + ) + (format stream-2 "ERROR: dma-list data pointer invalid~%") + (set! end-condition 'error) + ) + ;; check that the tag's value makes sense. + (when (nonzero? (logand #x3ff0000 (the-as int current-tag))) + (format stream-2 "ERROR: dma tag has data in reserved bits ~X~%") + (set! end-condition 'error) + ) + ) + ) + + ;; next, disassembly the dma-tag. + ;; only do it if verbose is set, or we have encountered an error. + (when (or verbose-2 (= end-condition 'error)) + (format stream-2 "#x~8x: " data-2) + (cond + ((zero? call-depth) + (format stream-2 " ") + ) + ((= call-depth 1) + (format stream-2 " ") + ) + ) + (disasm-dma-tag current-tag stream-2) + ) + + ;; now the dma data. + (if end-condition + (none) ;; do nothing if we want to end. + (cond + ;; check if we are an addr in addr field dma tag. + ((or (zero? (+ (the-as uint (-> current-tag id)) (the-as uint -3))) ;; ref + (zero? (+ (the-as uint (-> current-tag id)) (the-as uint -4))) ;; refs + (zero? (-> current-tag id)) ;; refe + ) + ;; set addresss and qwc from the tag. + (set! addr (-> current-tag addr)) + (set! qwc (the-as int (-> current-tag qwc))) + ;; optionally disassemble vif tags. + (if mode-2 + ;; I don't quite understand this. The first thing is for the tag transferred due to tte. + ;; but I don't understand what the v0-9 offset is. + (let ((v0-9 (disasm-vif-tag (&-> data-2 vif0) 2 stream-2 (= mode-2 'details)))) + (disasm-vif-tag + (the-as (pointer vif-tag) (+ (the-as uint addr) (the-as uint v0-9))) + (the-as int (- (shl (the-as int qwc) 2) (the-as uint (sar v0-9 2)))) + stream-2 + (= mode-2 'details) + ) + ) + ) + ;; move on to next dma-packet. it is adjacent in memory for these modes. + (set! data-2 (the-as dma-packet (&-> (the-as (pointer uint64) data-2) 2))) + ;; if we were a refe, it is now the end! + (if (zero? (-> current-tag id)) ;; check refe. + (set! end-condition #t) + ) + ) + (else + (cond + ((= (-> current-tag id) (dma-tag-id cnt)) + ;; cnt: address is after tag, next tag is after data. + ;; get the address from after the tag + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + ;; qwc from the tag + (set! qwc (the-as int (-> current-tag qwc))) + ;; disassemble vif. + (if mode-2 + (disasm-vif-tag + (the-as (pointer vif-tag) (&-> (the-as (pointer uint64) data-2) 1)) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + ;; next data is after vif data. + (set! data-2 (the-as dma-packet (+ (the-as uint data-2) + (the-as uint (shl (the-as int (+ (the-as uint qwc) + (the-as uint 1))) + 4) + ) + ) + ) + ) + ) + ((= (-> current-tag id) (dma-tag-id next)) + ;; address after tag and qwc in tag. + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + (set! qwc (the-as int (-> current-tag qwc))) + (if mode-2 + (disasm-vif-tag + (the-as (pointer vif-tag) (&-> (the-as (pointer uint64) data-2) 1)) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + ;; addr is the next tag. check for infinite loop before continuing. + (when (= data-2 (-> current-tag addr)) + (format stream-2 "ERROR: next tag creates infinite loop.~%") + (set! end-condition 'error) + ) + (set! data-2 (the-as dma-packet (-> current-tag addr))) + ) + (else + (cond + ((= (-> current-tag id) (dma-tag-id call)) + ;; this "calls" a DMA chain, which should then return to here. + ;; the stack is only two deep. + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + (set! qwc (the-as int (-> current-tag qwc))) + (if mode-2 + (disasm-vif-tag (the-as (pointer vif-tag) (&-> (the-as (pointer uint64) data-2) 1)) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + (set! data-2 (the-as dma-packet (-> current-tag addr))) + ;; increment stack + (set! call-depth (+ call-depth 1)) + ;; and store the return. + (cond + ((zero? call-depth) + (set! ra-1 (&+ (the pointer addr) qwc)) + ) + (else + (set! ra-2 (&+ (the pointer addr) qwc)) + ) + ) + ) + ((= (-> current-tag id) (dma-tag-id ret)) + ;; return from a "called" dma chain. + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + (set! qwc (the-as int (-> current-tag qwc))) + (if mode-2 + (disasm-vif-tag (the-as (pointer vif-tag) (&-> (the-as (pointer uint64) data-2) 1)) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + + ;; restore the address from the stack + ;; likely a case. + (let ((v1-123 call-depth)) + (cond + ((zero? v1-123) + (set! data-2 (the-as dma-packet ra-1)) + ) + ((= v1-123 1) + (set! data-2 (the-as dma-packet ra-2)) + ) + (else + (set! end-condition #t) + ) + ) + ) + (set! call-depth (+ call-depth -1)) + ) + ((= (-> current-tag id) (dma-tag-id end)) + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + (set! qwc (the-as int (-> current-tag qwc))) + (set! end-condition #t) + (if mode-2 + (disasm-vif-tag (the-as (pointer vif-tag) (&-> (the-as (pointer uint64) data-2) 1)) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + + ) + (else + (format stream-2 "ERROR: Unknown DMA TAG command.~%") + (set! end-condition 'error) + ) + ) + ) + ) + ) + ) + ) + ;; increment stats + (+! total-qwc qwc) + (+! total-tags 1) + ;; end, if we hit the tag limit. + (if (and (>= expected-size-2 0) (>= total-tags expected-size-2)) + (set! end-condition #t) + ) + ) + (when (or verbose-2 (= end-condition 'error)) + (format stream-2 "NOTICE: Total tags: ~d~%" total-tags) + (format stream-2 "NOTICE: Total QWC: ~d~%" total-qwc) + (format stream-2 "--------------------------------~%~%") + ) + ) + (!= end-condition 'error) + ) + ) + ) diff --git a/goal_src/engine/dma/dma-h.gc b/goal_src/engine/dma/dma-h.gc index 42c1592817..26a80f4611 100644 --- a/goal_src/engine/dma/dma-h.gc +++ b/goal_src/engine/dma/dma-h.gc @@ -294,6 +294,7 @@ (unpack-v4-16 109) (unpack-v4-8 110) (unpack-v4-5 111) + (cmd-mask 239) ;; not sure what this is. ) ;; this makes a copy of the above type, but uses a uint32. diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index a7cfd79185..87f5a36082 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -270,7 +270,8 @@ class Compiler { RegVal* compile_get_method_of_object(const goos::Object& form, RegVal* object, const std::string& method_name, - Env* env); + Env* env, + bool error_message_function_or_method = false); Val* compile_define_constant(const goos::Object& form, const goos::Object& rest, Env* env, diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index cd5bea4291..a559440694 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -472,9 +472,10 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en if (eval_args.empty()) { throw_compiler_error(form, "Unrecognized symbol {} as head of form.", uneval_head.print()); } + // get the method function pointer - head = compile_get_method_of_object(form, eval_args.front(), symbol_string(uneval_head), env); - fmt::format("method of object {} {}\n", head->print(), head->type().print()); + head = compile_get_method_of_object(form, eval_args.front(), symbol_string(uneval_head), env, + true); } // convert the head to a GPR (if function, this is already done) diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index 75be7c5751..0db43d47a3 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -53,9 +53,20 @@ RegVal* Compiler::compile_get_method_of_type(const goos::Object& form, RegVal* Compiler::compile_get_method_of_object(const goos::Object& form, RegVal* object, const std::string& method_name, - Env* env) { + Env* env, + bool error_message_function_or_method) { auto& compile_time_type = object->type(); - auto method_info = m_ts.lookup_method(compile_time_type.base_type(), method_name); + MethodInfo method_info; + if (!m_ts.try_lookup_method(compile_time_type.base_type(), method_name, &method_info)) { + if (error_message_function_or_method) { + throw_compiler_error(form, "No method or function named {} for type {}", method_name, + compile_time_type.print()); + } else { + throw_compiler_error(form, "Type {} has no method {}", compile_time_type.print(), + method_name); + } + } + method_info.type = method_info.type.substitute_for_method_call(compile_time_type.base_type()); auto fe = get_parent_env_of_type(env); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fcd6944b36..648f018964 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable(goalc-test ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_gkernel_decomp.cpp ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_math_decomp.cpp ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_DataParser.cpp + ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_DisasmVifDecompile.cpp ${GOALC_TEST_FRAMEWORK_SOURCES} ${GOALC_TEST_CASES}) diff --git a/test/decompiler/FormRegressionTest.cpp b/test/decompiler/FormRegressionTest.cpp index 335f43b478..2c9202e877 100644 --- a/test/decompiler/FormRegressionTest.cpp +++ b/test/decompiler/FormRegressionTest.cpp @@ -6,6 +6,7 @@ #include "decompiler/analysis/cfg_builder.h" #include "decompiler/analysis/expression_build.h" #include "decompiler/analysis/final_output.h" +#include "decompiler/analysis/stack_spill.h" #include "decompiler/analysis/insert_lets.h" #include "decompiler/util/config_parsers.h" #include "common/goos/PrettyPrinter.h" @@ -153,6 +154,11 @@ std::unique_ptr FormRegressionTest::make_function( fmt::print("CFG:\n{}\n", test->func.cfg->to_dot()); } + // find stack spill slots + auto spill_map = build_spill_map(test->func.instructions, + {test->func.prologue_end, test->func.epilogue_start}); + test->func.ir2.env.set_stack_spills(spill_map); + // convert instruction to atomic ops DecompWarnings warnings; auto ops = convert_function_to_atomic_ops(test->func, program.labels, warnings); diff --git a/test/decompiler/reference/all_forward_declarations.gc b/test/decompiler/reference/all_forward_declarations.gc index b35b4a08ff..b7442b855a 100644 --- a/test/decompiler/reference/all_forward_declarations.gc +++ b/test/decompiler/reference/all_forward_declarations.gc @@ -3,6 +3,7 @@ (define-extern fact (function int int)) (define-extern max (function int int int)) (define-extern min (function int int int)) +(define-extern valid? (function object type basic basic object symbol)) ;; KERNEL (declare-type process basic) @@ -82,6 +83,10 @@ '(none) ) +(defmacro empty-form () + '(none) + ) + ;; math (define-extern fabs (function float float)) @@ -172,6 +177,7 @@ (unpack-v4-16 109) (unpack-v4-8 110) (unpack-v4-5 111) + (cmd-mask 239) ) (defenum vif-cmd-32 diff --git a/test/decompiler/reference/dma-disasm_REF.gc b/test/decompiler/reference/dma-disasm_REF.gc new file mode 100644 index 0000000000..f43773879e --- /dev/null +++ b/test/decompiler/reference/dma-disasm_REF.gc @@ -0,0 +1,917 @@ +;;-*-Lisp-*- +(in-package goal) + +;; this file is debug only +(when *debug-segment* +;; definition of type vif-disasm-element +(deftype vif-disasm-element (structure) + ((mask uint32 :offset-assert 0) + (tag vif-cmd-32 :offset-assert 4) + (val uint32 :offset-assert 8) + (print uint32 :offset-assert 12) + (string1 string :offset-assert 16) + (string2 string :offset-assert 20) + ) + :method-count-assert 9 + :size-assert #x18 + :flag-assert #x900000018 + ) + +;; definition for method 3 of type vif-disasm-element +(defmethod inspect vif-disasm-element ((obj vif-disasm-element)) + (format #t "[~8x] ~A~%" obj 'vif-disasm-element) + (format #t "~Tmask: ~D~%" (-> obj mask)) + (format #t "~Ttag: ~D~%" (-> obj tag)) + (format #t "~Tval: ~D~%" (-> obj val)) + (format #t "~Tprint: ~D~%" (-> obj print)) + (format #t "~Tstring1: ~A~%" (-> obj string1)) + (format #t "~Tstring2: ~A~%" (-> obj string2)) + obj + ) + +;; definition for symbol *vif-disasm-table*, type (array vif-disasm-element) +(define + *vif-disasm-table* + (the-as (array vif-disasm-element) + (new + 'static + 'boxed-array + vif-disasm-element + 34 + (new 'static 'vif-disasm-element :mask #x7f :string1 "nop") + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 stcycl) + :print #x2 + :string1 "stcycl" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 offset) + :print #x1 + :string1 "offset" + :string2 "offset" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 base) + :print #x1 + :string1 "base" + :string2 "base" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 itop) + :print #x1 + :string1 "itop" + :string2 "addr" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 stmod) + :print #x1 + :string1 "stmod" + :string2 "mode" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 mskpath3) + :print #x1 + :string1 "mskpath3" + :string2 "mask" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 mark) + :print #x1 + :string1 "mark" + :string2 "mark" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 flushe) + :string1 "flushe" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 flush) + :string1 "flush" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 flusha) + :string1 "flusha" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 mscal) + :print #x1 + :string1 "mscal" + :string2 "addr" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 mscnt) + :string1 "mscnt" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 mscalf) + :print #x1 + :string1 "mscalf" + :string2 "addr" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 stmask) + :print #x3 + :string1 "stmask" + :string2 "mask" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 strow) + :print #x4 + :string1 "strow" + :string2 "row" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 stcol) + :print #x4 + :string1 "stcol" + :string2 "col" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 mpg) + :print #x5 + :string1 "mpg" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 direct) + :print #x6 + :string1 "direct" + ) + (new 'static 'vif-disasm-element + :mask #x7f + :tag (vif-cmd-32 directhl) + :print #x6 + :string1 "directhl" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-s-32) + :val #x10 + :print #x7 + :string1 "unpack-s-32" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-s-16) + :val #x8 + :print #x7 + :string1 "unpack-s-16" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-s-8) + :val #x4 + :print #x7 + :string1 "unpack-s-8" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v2-32) + :val #x8 + :print #x7 + :string1 "unpack-v2-32" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v2-16) + :val #x4 + :print #x7 + :string1 "unpack-v2-16" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v2-8) + :val #x2 + :print #x7 + :string1 "unpack-v2-8" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v3-32) + :val #xc + :print #x7 + :string1 "unpack-v3-32" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v3-16) + :val #x6 + :print #x7 + :string1 "unpack-v3-16" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v3-8) + :val #x3 + :print #x7 + :string1 "unpack-v3-8" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v4-32) + :val #x10 + :print #x7 + :string1 "unpack-v4-32" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v4-16) + :val #x8 + :print #x7 + :string1 "unpack-v4-16" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v4-8) + :val #x4 + :print #x7 + :string1 "unpack-v4-8" + ) + (new 'static 'vif-disasm-element + :mask #x6f + :tag (vif-cmd-32 unpack-v4-5) + :val #x2 + :print #x7 + :string1 "unpack-v4-5" + ) + (new 'static 'vif-disasm-element :print #x8) + ) + ) + ) + +;; definition for function disasm-vif-details +(defun + disasm-vif-details + ((stream symbol) (data (pointer uint8)) (kind vif-cmd) (count int)) + (let ((count2 count)) + (cond + ((= kind (vif-cmd unpack-v4-8)) + (let ((data-ptr (&-> data 4))) + (dotimes (i count2) + (format + stream + " #x~X: #x~2X #x~2X #x~2X #x~2X~%" + (+ (+ (shl i 2) 4) (the-as int data)) + (-> data-ptr (shl i 2)) + (-> data-ptr (+ (shl i 2) 1)) + (-> data-ptr (+ (shl i 2) 2)) + (-> data-ptr (+ (shl i 2) 3)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-s-8)) + (let ((s3-1 (&-> data 4))) + (dotimes (s2-1 count2) + (format + stream + " #x~X: #x~2x~%" + (+ (+ s2-1 4) (the-as int data)) + count + ) + (let ((v1-21 (-> s3-1 (* 3 s2-1)))) + ) + (let ((v1-26 (-> s3-1 (+ (* 3 s2-1) 1)))) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v4-32)) + (let ((s3-2 (the-as (pointer uint32) (&-> data 4)))) + (dotimes (s2-2 count2) + (format + stream + " #x~X: #x~8x #x~8x #x~8x #x~8x~%" + (+ (+ (shl s2-2 4) 4) (the-as int data)) + (-> s3-2 (shl s2-2 2)) + (-> s3-2 (+ (shl s2-2 2) 1)) + (-> s3-2 (+ (shl s2-2 2) 2)) + (-> s3-2 (+ (shl s2-2 2) 3)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v4-16)) + (let ((s3-3 (the-as (pointer uint16) (&-> data 4)))) + (dotimes (s2-3 count2) + (format + stream + " #x~X: #x~4x #x~4x #x~4x #x~4x~%" + (+ (+ (shl s2-3 3) 4) (the-as int data)) + (-> s3-3 (shl s2-3 2)) + (-> s3-3 (+ (shl s2-3 2) 1)) + (-> s3-3 (+ (shl s2-3 2) 2)) + (-> s3-3 (+ (shl s2-3 2) 3)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v3-32)) + (let ((s3-4 (the-as (pointer uint32) (&-> data 4)))) + (dotimes (s2-4 count2) + (format + stream + " #x~X: #x~8x #x~8x #x~8x~%" + (+ (+ (* 12 s2-4) 4) (the-as int data)) + (-> (&+ s3-4 (* 12 s2-4)) 0) + (-> s3-4 (+ (* 3 s2-4) 1)) + (-> s3-4 (+ (* 3 s2-4) 2)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v3-16)) + (let ((s3-5 (the-as (pointer uint16) (&-> data 4)))) + (dotimes (s2-5 count2) + (format + stream + " #x~X: #x~4x #x~4x #x~4x~%" + (+ (+ (* 6 s2-5) 4) (the-as int data)) + (-> (&+ s3-5 (* 6 s2-5)) 0) + (-> s3-5 (+ (* 3 s2-5) 1)) + (-> s3-5 (+ (* 3 s2-5) 2)) + ) + ) + ) + ) + ((= kind (vif-cmd unpack-v2-16)) + (let ((s3-6 (the-as (pointer uint16) (&-> data 4)))) + (dotimes (s2-6 count2) + (format + stream + " #x~X: #x~4x #x~4x~%" + (+ (+ (shl s2-6 2) 4) (the-as int data)) + (-> (&+ s3-6 (* 6 s2-6)) 0) + (-> s3-6 (+ (* 3 s2-6) 1)) + ) + ) + ) + ) + (else + (format + stream + " #x~X: Data format #b~b not yet supported, add it for yourself!~%" + (&-> data 4) + kind + ) + ) + ) + ) + #f + ) + +;; definition for function disasm-vif-tag +;; Used lq/sq +(defun + disasm-vif-tag + ((data (pointer vif-tag)) (words int) (stream symbol) (details symbol)) + (local-vars + (cmd vif-cmd) + (data-ptr (pointer vif-tag)) + (data-idx int) + (unpack-imm vif-unpack-imm) + ) + (let ((byte-idx 0)) + (while (< byte-idx (shl words 2)) + (let ((packet-size 4)) + (let ((first-tag (-> data 0))) + (format stream " #x~X:" data) + (dotimes (cmd-template-idx (-> *vif-disasm-table* length)) + (set! cmd (-> first-tag cmd)) + (when + (= + (logand + cmd + (the-as uint (-> *vif-disasm-table* cmd-template-idx mask)) + ) + (-> *vif-disasm-table* cmd-template-idx tag) + ) + (let* ((print-kind (-> *vif-disasm-table* cmd-template-idx print)) + (v0-1 (cond + ((zero? print-kind) + (format + stream + " (~s :irq ~D)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + ) + ) + ((= print-kind 1) + (format + stream + " (~s :irq ~D :~s #x~X)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> *vif-disasm-table* cmd-template-idx string2) + (-> first-tag imm) + ) + ) + ((= print-kind 2) + (let + ((stcycl-imm (the-as vif-stcycl-imm (-> first-tag imm))) + ) + (format + stream + " (~s :irq ~D :wl ~D :cl ~D)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> stcycl-imm wl) + (-> stcycl-imm cl) + ) + ) + ) + ((= print-kind 3) + (set! packet-size 8) + (format + stream + " (~s :irq ~D :~s #x~X)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> *vif-disasm-table* cmd-template-idx string2) + (-> data 1) + ) + ) + ((= print-kind 4) + (set! packet-size 20) + (format + stream + " (~s :irq ~D :~s " + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> *vif-disasm-table* cmd-template-idx string2) + ) + (format + stream + "#x~X #x~X #x~X #x~X)~%" + (-> data 1) + (-> data 2) + (-> data 3) + (-> data 4) + ) + ) + ((= print-kind 5) + (format + stream + " (~s :irq ~D :instructions #x~D :addr #x~X)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> first-tag num) + (-> first-tag imm) + ) + ) + ((= print-kind 6) + (if (-> first-tag imm) + (set! packet-size #x100000) + (set! + packet-size + (the-as int (shl (the-as int (-> first-tag imm)) 4)) + ) + ) + (format + stream + " (~s :irq ~D :qwc #x~D)~%" + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> first-tag imm) + ) + (set! data-ptr (&-> data 1)) + (set! data-idx 0) + (while (< data-idx (the-as int (-> first-tag imm))) + (format + stream + " #x~X: #x~8x #x~8x #x~8x #x~8x~%" + (+ (+ (shl data-idx 4) 4) (the-as int data)) + (-> data-ptr (shl data-idx 2)) + (-> data-ptr (+ (shl data-idx 2) 1)) + (-> data-ptr (+ (shl data-idx 2) 2)) + (-> data-ptr (+ (shl data-idx 2) 3)) + ) + (set! data-idx (+ data-idx 1)) + ) + #f + ) + ((= print-kind 7) + (set! + packet-size + (the-as + int + (+ + (logand + -4 + (the-as + int + (+ + (* + (-> *vif-disasm-table* cmd-template-idx val) + (the-as uint (-> first-tag num)) + ) + 3 + ) + ) + ) + 4 + ) + ) + ) + (set! + unpack-imm + (the-as vif-unpack-imm (-> first-tag imm)) + ) + (format + stream + " (~s :irq ~D :num ~D :addr #x~X " + (-> *vif-disasm-table* cmd-template-idx string1) + (-> first-tag irq) + (-> first-tag num) + (-> unpack-imm addr) + ) + (format + stream + ":msk ~D :flg ~D :usn ~D [skip ~d])~%" + (-> first-tag msk) + (-> unpack-imm flg) + (-> unpack-imm usn) + (the-as uint packet-size) + ) + (if details + (disasm-vif-details + stream + (the-as (pointer uint8) data) + (logand cmd (vif-cmd cmd-mask)) + (the-as int (-> first-tag num)) + ) + ) + ) + ((= print-kind 8) + (format + stream + " (*unknown* vif-tag #x~X)~%" + (-> first-tag cmd) + ) + ) + ) + ) + ) + ) + (set! cmd-template-idx (-> *vif-disasm-table* length)) + ) + ) + ) + (+! byte-idx packet-size) + (&+! data packet-size) + ) + ) + (- byte-idx (shl words 2)) + ) + ) + +;; definition for function disasm-dma-tag +;; INFO: Return type mismatch object vs none. +(defun disasm-dma-tag ((arg0 dma-tag) (arg1 symbol)) + (format arg1 "(dma-tag ") + (let ((t9-1 format) + (a0-2 arg1) + (a1-2 "~s") + (v1-1 (-> arg0 id)) + ) + (t9-1 a0-2 a1-2 (cond + ((= v1-1 (dma-tag-id end)) + "end" + ) + ((= v1-1 (dma-tag-id ret)) + "ret" + ) + ((= v1-1 (dma-tag-id call)) + "call" + ) + ((= v1-1 (dma-tag-id refs)) + "refs" + ) + ((= v1-1 (dma-tag-id ref)) + "ref" + ) + ((= v1-1 (dma-tag-id next)) + "next" + ) + ((= v1-1 (dma-tag-id cnt)) + "cnt" + ) + ((zero? v1-1) + "refe" + ) + (else + "*unknown*" + ) + ) + ) + ) + (if (> (the-as uint (-> arg0 addr)) 0) + (format arg1 " :addr #x~8x" (-> arg0 addr)) + ) + (if (> (the-as uint (-> arg0 qwc)) 0) + (format arg1 " :qwc ~d" (-> arg0 qwc)) + ) + (if (> (the-as uint (-> arg0 spr)) 0) + (format arg1 " :spr ~d" (-> arg0 spr)) + ) + (if (> (the-as uint (-> arg0 irq)) 0) + (format arg1 " :irq ~d" (-> arg0 irq)) + ) + (if (> (the-as uint (-> arg0 pce)) 0) + (format arg1 " :pce ~d" (-> arg0 pce)) + ) + (format arg1 ")~%") + (none) + ) + +;; definition for symbol *dma-disasm*, type symbol +(define *dma-disasm* #t) + +;; definition for function disasm-dma-list +;; WARN: Check prologue - tricky store of a0 +;; Used lq/sq +(defun + disasm-dma-list + ((data dma-packet) + (mode symbol) + (verbose symbol) + (stream symbol) + (expected-size int) + ) + (local-vars + (addr object) + (data-2 dma-packet) + (qwc int) + (ra-1 object) + (ra-2 object) + (call-depth int) + (current-tag dma-tag) + ) + (set! data-2 data) + (let ((mode-2 mode) + (verbose-2 verbose) + (stream-2 stream) + (expected-size-2 expected-size) + ) + (if verbose-2 + (format stream-2 "~%--- ~X -----------------------------~%" data-2) + ) + (let ((end-condition #f)) + (let ((total-qwc 0) + (total-tags 0) + ) + (set! addr 0) + (set! qwc 0) + (set! ra-1 0) + (set! ra-2 1) + (set! call-depth -1) + (set! current-tag (new 'static 'dma-tag)) + (while (not end-condition) + (cond + ((not + (valid? data-2 (the-as type #f) "dma-list tag pointer" #t stream-2) + ) + (format stream-2 "ERROR: dma-list tag pointer invalid~%") + (set! end-condition 'error) + ) + (else + (set! current-tag (-> data-2 dma)) + (when + (not + (or + (zero? total-tags) + (valid? addr (the-as type #f) "dma-list data pointer" #t stream-2) + ) + ) + (format stream-2 "ERROR: dma-list data pointer invalid~%") + (set! end-condition 'error) + ) + (when (nonzero? (logand #x3ff0000 (the-as int current-tag))) + (format stream-2 "ERROR: dma tag has data in reserved bits ~X~%") + (set! end-condition 'error) + ) + ) + ) + (when (or verbose-2 (= end-condition 'error)) + (format stream-2 "#x~8x: " data-2) + (cond + ((zero? call-depth) + (format stream-2 " ") + ) + ((= call-depth 1) + (format stream-2 " ") + ) + ) + (disasm-dma-tag current-tag stream-2) + ) + (if end-condition + (empty-form) + (cond + ((or + (zero? (+ (the-as uint (-> current-tag id)) (the-as uint -3))) + (zero? (+ (the-as uint (-> current-tag id)) (the-as uint -4))) + (zero? (-> current-tag id)) + ) + (set! addr (-> current-tag addr)) + (set! qwc (the-as int (-> current-tag qwc))) + (if mode-2 + (let + ((v0-9 + (disasm-vif-tag (&-> data-2 vif0) 2 stream-2 (= mode-2 'details)) + ) + ) + (disasm-vif-tag + (the-as (pointer vif-tag) (+ (the-as uint addr) (the-as uint v0-9))) + (the-as int (- (shl (the-as int qwc) 2) (the-as uint (sar v0-9 2)))) + stream-2 + (= mode-2 'details) + ) + ) + ) + (set! + data-2 + (the-as dma-packet (&-> (the-as (pointer uint64) data-2) 2)) + ) + (if (zero? (-> current-tag id)) + (set! end-condition #t) + ) + ) + (else + (cond + ((= (-> current-tag id) (dma-tag-id cnt)) + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + (set! qwc (the-as int (-> current-tag qwc))) + (if mode-2 + (disasm-vif-tag + (the-as (pointer vif-tag) (&-> (the-as (pointer uint64) data-2) 1)) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + (set! + data-2 + (the-as + dma-packet + (+ + (the-as uint data-2) + (the-as + uint + (shl (the-as int (+ (the-as uint qwc) (the-as uint 1))) 4) + ) + ) + ) + ) + (let ((v1-68 data-2)) + ) + ) + ((= (-> current-tag id) (dma-tag-id next)) + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + (set! qwc (the-as int (-> current-tag qwc))) + (if mode-2 + (disasm-vif-tag + (the-as (pointer vif-tag) (&-> (the-as (pointer uint64) data-2) 1)) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + (when (= data-2 (-> current-tag addr)) + (format stream-2 "ERROR: next tag creates infinite loop.~%") + (set! end-condition 'error) + ) + (set! data-2 (the-as dma-packet (-> current-tag addr))) + (let ((v1-88 data-2)) + ) + ) + (else + (cond + ((= (-> current-tag id) (dma-tag-id call)) + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + (set! qwc (the-as int (-> current-tag qwc))) + (if mode-2 + (disasm-vif-tag + (the-as + (pointer vif-tag) + (&-> (the-as (pointer uint64) data-2) 1) + ) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + (set! data-2 (the-as dma-packet (-> current-tag addr))) + (set! call-depth (+ call-depth 1)) + (cond + ((zero? call-depth) + (set! ra-1 (&+ addr qwc)) + (let ((v1-108 (the-as (pointer uint64) ra-1))) + ) + ) + (else + (set! ra-2 (&+ addr qwc)) + (let ((v1-111 (the-as (pointer uint64) ra-2))) + ) + ) + ) + ) + ((= (-> current-tag id) (dma-tag-id ret)) + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + (set! qwc (the-as int (-> current-tag qwc))) + (if mode-2 + (disasm-vif-tag + (the-as + (pointer vif-tag) + (&-> (the-as (pointer uint64) data-2) 1) + ) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + (let ((v1-123 call-depth)) + (cond + ((zero? v1-123) + (set! data-2 (the-as dma-packet ra-1)) + (let ((v1-125 data-2)) + ) + ) + ((= v1-123 1) + (set! data-2 (the-as dma-packet ra-2)) + (let ((v1-127 data-2)) + ) + ) + (else + (set! end-condition #t) + ) + ) + ) + (set! call-depth (+ call-depth -1)) + (let ((v1-131 call-depth)) + ) + ) + ((= (-> current-tag id) (dma-tag-id end)) + (set! addr (&-> (the-as (pointer uint64) data-2) 2)) + (set! qwc (the-as int (-> current-tag qwc))) + (set! end-condition #t) + (let ((v0-16 (if mode-2 + (disasm-vif-tag + (the-as + (pointer vif-tag) + (&-> (the-as (pointer uint64) data-2) 1) + ) + (the-as int (+ (shl (the-as int qwc) 2) 2)) + stream-2 + (= mode-2 'details) + ) + ) + ) + ) + ) + ) + (else + (format stream-2 "ERROR: Unknown DMA TAG command.~%") + (set! end-condition 'error) + ) + ) + ) + ) + ) + ) + ) + (+! total-qwc qwc) + (+! total-tags 1) + (if (and (>= expected-size-2 0) (>= total-tags expected-size-2)) + (set! end-condition #t) + ) + ) + (when (or verbose-2 (= end-condition 'error)) + (format stream-2 "NOTICE: Total tags: ~d~%" total-tags) + (format stream-2 "NOTICE: Total QWC: ~d~%" total-qwc) + (format stream-2 "--------------------------------~%~%") + ) + ) + (!= end-condition 'error) + ) + ) + ) + +) + diff --git a/test/decompiler/reference/gstring_REF.gc b/test/decompiler/reference/gstring_REF.gc index 9c4cc5fdc5..2c5efdcd60 100644 --- a/test/decompiler/reference/gstring_REF.gc +++ b/test/decompiler/reference/gstring_REF.gc @@ -562,22 +562,34 @@ (>= (the-as uint 102) (-> next-char-2 0)) ) ) - (set! - result - (if - (and - (>= (-> next-char-2 0) (the-as uint 65)) - (>= (the-as uint 70) (-> next-char-2 0)) - ) - (+ (+ (-> next-char-2 0) -55) (the-as uint (shl result 4))) + (if + (and + (>= (-> next-char-2 0) (the-as uint 65)) + (>= (the-as uint 70) (-> next-char-2 0)) + ) + (set! + result (the-as int - (if - (and - (>= (-> next-char-2 0) (the-as uint 97)) - (>= (the-as uint 102) (-> next-char-2 0)) - ) + (+ (+ (-> next-char-2 0) -55) (the-as uint (shl result 4))) + ) + ) + (if + (and + (>= (-> next-char-2 0) (the-as uint 97)) + (>= (the-as uint 102) (-> next-char-2 0)) + ) + (set! + result + (the-as + int (+ (+ (-> next-char-2 0) -87) (the-as uint (shl result 4))) + ) + ) + (set! + result + (the-as + int (+ (+ (-> next-char-2 0) -48) (the-as uint (shl result 4))) ) ) diff --git a/test/decompiler/test_DisasmVifDecompile.cpp b/test/decompiler/test_DisasmVifDecompile.cpp new file mode 100644 index 0000000000..24c0a89dbf --- /dev/null +++ b/test/decompiler/test_DisasmVifDecompile.cpp @@ -0,0 +1,710 @@ + +#include "gtest/gtest.h" +#include "FormRegressionTest.h" + +using namespace decompiler; + +TEST_F(FormRegressionTest, ExprDisasmVif) { + std::string func = + " sll r0, r0, 0\n" + "L53:\n" + " daddiu sp, sp, -192\n" + " sd ra, 0(sp)\n" + " sd fp, 8(sp)\n" + " or fp, t9, r0\n" + " sq s0, 80(sp)\n" + " sq s1, 96(sp)\n" + " sq s2, 112(sp)\n" + " sq s3, 128(sp)\n" + " sq s4, 144(sp)\n" + " sq s5, 160(sp)\n" + " sq gp, 176(sp)\n" + + " or s4, a0, r0\n" + " or s5, a1, r0\n" + " or s3, a2, r0\n" + " or s2, a3, r0\n" + " addiu gp, r0, 0\n" + " beq r0, r0, L72\n" + " sll r0, r0, 0\n" + + "L54:\n" + " addiu s0, r0, 4\n" + " lwu s1, 0(s4)\n" + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L139\n" + " or a2, s4, r0\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " addiu v1, r0, 0\n" + " beq r0, r0, L71\n" + " sll r0, r0, 0\n" + + "L55:\n" + " dsll32 a0, s1, 1\n" + " dsrl32 a0, a0, 25\n" + " sq a0, 16(sp)\n" + " dsll a0, v1, 2\n" + " lw a1, *vif-disasm-table*(s7)\n" + " daddu a0, a0, a1\n" + + " lwu a0, 12(a0)\n" + + " lwu a0, 4(a0)\n" + " lq a1, 16(sp)\n" + " dsll a2, v1, 2\n" + " lw a3, *vif-disasm-table*(s7)\n" + " daddu a2, a2, a3\n" + + " lwu a2, 12(a2)\n" + + " lwu a2, 0(a2)\n" + " and a1, a1, a2\n" + " bne a1, a0, L70\n" + " or a0, s7, r0\n" + + " dsll a0, v1, 2\n" + " lw a1, *vif-disasm-table*(s7)\n" + " daddu a0, a0, a1\n" + + " lwu a0, 12(a0)\n" + + " lwu a0, 12(a0)\n" + " bne a0, r0, L56\n" + " or a1, s7, r0\n" + + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L138\n" + " dsll v1, v1, 2\n" + " lw a2, *vif-disasm-table*(s7)\n" + " daddu v1, v1, a2\n" + + " lwu v1, 12(v1)\n" + + " lwu a2, 16(v1)\n" + " srl a3, s1, 31\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " beq r0, r0, L69\n" + " sll r0, r0, 0\n" + + "L56:\n" + " addiu a1, r0, 1\n" + " bne a0, a1, L57\n" + " or a1, s7, r0\n" + + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L137\n" + + " dsll a2, v1, 2\n" + " lw a3, *vif-disasm-table*(s7)\n" + " daddu a2, a2, a3\n" + + " lwu a2, 12(a2)\n" + + " lwu a2, 16(a2)\n" + " srl a3, s1, 31\n" + " dsll v1, v1, 2\n" + " lw t0, *vif-disasm-table*(s7)\n" + " daddu v1, v1, t0\n" + + " lwu v1, 12(v1)\n" + + " lwu t0, 20(v1)\n" + " dsll32 v1, s1, 16\n" + " dsrl32 t1, v1, 16\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " beq r0, r0, L69\n" + " sll r0, r0, 0\n" + + "L57:\n" + " addiu a1, r0, 2\n" + " bne a0, a1, L58\n" + " or a1, s7, r0\n" + + " dsll32 a0, s1, 16\n" + " dsrl32 t1, a0, 16\n" + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L136\n" + + " dsll v1, v1, 2\n" + " lw a2, *vif-disasm-table*(s7)\n" + " daddu v1, v1, a2\n" + + " lwu v1, 12(v1)\n" + + " lwu a2, 16(v1)\n" + " srl a3, s1, 31\n" + " dsll32 v1, t1, 16\n" + " dsrl32 t0, v1, 24\n" + " dsll32 v1, t1, 24\n" + " dsrl32 t1, v1, 24\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " beq r0, r0, L69\n" + " sll r0, r0, 0\n" + + "L58:\n" + " addiu a1, r0, 3\n" + " bne a0, a1, L59\n" + " or a1, s7, r0\n" + + " addiu s0, r0, 8\n" + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L137\n" + + " dsll a2, v1, 2\n" + " lw a3, *vif-disasm-table*(s7)\n" + " daddu a2, a2, a3\n" + + " lwu a2, 12(a2)\n" + + " lwu a2, 16(a2)\n" + " srl a3, s1, 31\n" + " dsll v1, v1, 2\n" + " lw t0, *vif-disasm-table*(s7)\n" + " daddu v1, v1, t0\n" + + " lwu v1, 12(v1)\n" + + " lwu t0, 20(v1)\n" + " lwu t1, 4(s4)\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " beq r0, r0, L69\n" + " sll r0, r0, 0\n" + + "L59:\n" + " addiu a1, r0, 4\n" + " bne a0, a1, L60\n" + " or a1, s7, r0\n" + + " addiu s0, r0, 20\n" + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L135\n" + " dsll a2, v1, 2\n" + " lw a3, *vif-disasm-table*(s7)\n" + " daddu a2, a2, a3\n" + + " lwu a2, 12(a2)\n" + + " lwu a2, 16(a2)\n" + " srl a3, s1, 31\n" + " dsll v1, v1, 2\n" + " lw t0, *vif-disasm-table*(s7)\n" + " daddu v1, v1, t0\n" + + " lwu v1, 12(v1)\n" + + " lwu t0, 20(v1)\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L134\n" + + " lwu a2, 4(s4)\n" + " lwu a3, 8(s4)\n" + " lwu t0, 12(s4)\n" + " lwu t1, 16(s4)\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " beq r0, r0, L69\n" + " sll r0, r0, 0\n" + + "L60:\n" + " addiu a1, r0, 5\n" + " bne a0, a1, L61\n" + " or a1, s7, r0\n" + + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L133\n" + + " dsll v1, v1, 2\n" + " lw a2, *vif-disasm-table*(s7)\n" + " daddu v1, v1, a2\n" + + " lwu v1, 12(v1)\n" + + " lwu a2, 16(v1)\n" + " srl a3, s1, 31\n" + " dsll32 v1, s1, 8\n" + " dsrl32 t0, v1, 24\n" + " dsll32 v1, s1, 16\n" + " dsrl32 t1, v1, 16\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " beq r0, r0, L69\n" + " sll r0, r0, 0\n" + + "L61:\n" + " addiu a1, r0, 6\n" + " bne a0, a1, L66\n" + " or a1, s7, r0\n" + + " dsll32 a0, s1, 16\n" + " dsrl32 a0, a0, 16\n" + " beq s7, a0, L62\n" + " sll r0, r0, 0\n" + + " lui s0, 16\n" + " or a0, s0, r0\n" + " beq r0, r0, L63\n" + " sll r0, r0, 0\n" + + "L62:\n" + " dsll32 a0, s1, 16\n" + " dsrl32 a0, a0, 16\n" + " dsll s0, a0, 4\n" + " or a0, s0, r0\n" + "L63:\n" + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L132\n" + + " dsll v1, v1, 2\n" + " lw a2, *vif-disasm-table*(s7)\n" + " daddu v1, v1, a2\n" + + " lwu v1, 12(v1)\n" + + " lwu a2, 16(v1)\n" + " srl a3, s1, 31\n" + " dsll32 v1, s1, 16\n" + " dsrl32 t0, v1, 16\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " daddiu v1, s4, 4\n" + " sq v1, 32(sp)\n" + " addiu v1, r0, 0\n" + " sq v1, 48(sp)\n" + " beq r0, r0, L65\n" + " sll r0, r0, 0\n" + + "L64:\n" + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L145\n" + + " lq v1, 48(sp)\n" + " dsll v1, v1, 4\n" + " daddiu v1, v1, 4\n" + " daddu a2, v1, s4\n" + + " lq v1, 32(sp)\n" + " lq a3, 48(sp)\n" + " dsll a3, a3, 2\n" + " dsll a3, a3, 2\n" + " daddu v1, v1, a3\n" + + " lwu a3, 0(v1)\n" + + " lq v1, 32(sp)\n" + " lq t0, 48(sp)\n" + " dsll t0, t0, 2\n" + " daddiu t0, t0, 1\n" + " dsll t0, t0, 2\n" + " daddu v1, v1, t0\n" + + " lwu t0, 0(v1)\n" + + " lq v1, 32(sp)\n" + " lq t1, 48(sp)\n" + " dsll t1, t1, 2\n" + " daddiu t1, t1, 2\n" + " dsll t1, t1, 2\n" + " daddu v1, v1, t1\n" + + " lwu t1, 0(v1)\n" + + " lq v1, 32(sp)\n" + " lq t2, 48(sp)\n" + " dsll t2, t2, 2\n" + " daddiu t2, t2, 3\n" + " dsll t2, t2, 2\n" + " daddu v1, v1, t2\n" + + " lwu t2, 0(v1)\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " lq v1, 48(sp)\n" + " daddiu v1, v1, 1\n" + " sq v1, 48(sp)\n" + "L65:\n" + " lq v1, 48(sp)\n" + " dsll32 a0, s1, 16\n" + " dsrl32 a0, a0, 16\n" + " slt v1, v1, a0\n" + " bne v1, r0, L64\n" + " sll r0, r0, 0\n" + + " or v1, s7, r0\n" + " or v1, s7, r0\n" + " or v0, v1, r0\n" + " beq r0, r0, L69\n" + " sll r0, r0, 0\n" + + "L66:\n" + " addiu a1, r0, 7\n" + " bne a0, a1, L68\n" + " or a1, s7, r0\n" + + " addiu a0, r0, -4\n" + " dsll a1, v1, 2\n" + " lw a2, *vif-disasm-table*(s7)\n" + " daddu a1, a1, a2\n" + + " lwu a1, 12(a1)\n" + + " lwu a1, 8(a1)\n" + " dsll32 a2, s1, 8\n" + " dsrl32 a2, a2, 24\n" + " multu3 a1, a1, a2\n" + " daddiu a1, a1, 3\n" + " and a0, a0, a1\n" + + " daddiu s0, a0, 4\n" + " dsll32 a0, s1, 16\n" + " dsrl32 a0, a0, 16\n" + " sq a0, 64(sp)\n" + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L131\n" + + " dsll v1, v1, 2\n" + " lw a2, *vif-disasm-table*(s7)\n" + " daddu v1, v1, a2\n" + + " lwu v1, 12(v1)\n" + + " lwu a2, 16(v1)\n" + " srl a3, s1, 31\n" + " dsll32 v1, s1, 8\n" + " dsrl32 t0, v1, 24\n" + " lq v1, 64(sp)\n" + " dsll32 v1, v1, 22\n" + " dsrl32 t1, v1, 22\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L130\n" + + " dsll32 v1, s1, 3\n" + " dsrl32 a2, v1, 31\n" + " lq v1, 64(sp)\n" + " dsll32 v1, v1, 16\n" + " dsrl32 a3, v1, 31\n" + " lq v1, 64(sp)\n" + " dsll32 v1, v1, 17\n" + " dsrl32 t0, v1, 31\n" + " or t1, s0, r0\n" + + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + " beq s7, s2, L67\n" + " or v0, s7, r0\n" + + " lw t9, disasm-vif-details(s7)\n" + + " or a0, s3, r0\n" + " or a1, s4, r0\n" + " lq v1, 16(sp)\n" + " andi a2, v1, 239\n" + " dsll32 v1, s1, 8\n" + " dsrl32 a3, v1, 24\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + "L67:\n" + " or v1, v0, r0\n" + " or v0, v1, r0\n" + " beq r0, r0, L69\n" + " sll r0, r0, 0\n" + + "L68:\n" + " addiu v1, r0, 8\n" + " bne a0, v1, L69\n" + " or v0, s7, r0\n" + + " lw t9, format(s7)\n" + " or a0, s3, r0\n" + " daddiu a1, fp, L129\n" + + " dsll32 v1, s1, 1\n" + " dsrl32 a2, v1, 25\n" + " jalr ra, t9\n" + + " sll v0, ra, 0\n" + + "L69:\n" + " or v1, v0, r0\n" + " lw v1, *vif-disasm-table*(s7)\n" + " lw v1, 0(v1)\n" + " or a0, v1, r0\n" + "L70:\n" + " daddiu v1, v1, 1\n" + + "L71:\n" + " lw a0, *vif-disasm-table*(s7)\n" + " lw a0, 0(a0)\n" + " slt a0, v1, a0\n" + " bne a0, r0, L55\n" + " sll r0, r0, 0\n" + + " or v1, s7, r0\n" + " or v1, s7, r0\n" + " daddu gp, gp, s0\n" + " daddu s4, s4, s0\n" + + " or v1, s4, r0\n" + + "L72:\n" + " dsll v1, s5, 2\n" + " slt v1, gp, v1\n" + " bne v1, r0, L54\n" + " sll r0, r0, 0\n" + + " or v1, s7, r0\n" + " dsll v1, s5, 2\n" + " dsubu v0, gp, v1\n" + " ld ra, 0(sp)\n" + " ld fp, 8(sp)\n" + " lq gp, 176(sp)\n" + " lq s5, 160(sp)\n" + " lq s4, 144(sp)\n" + " lq s3, 128(sp)\n" + " lq s2, 112(sp)\n" + " lq s1, 96(sp)\n" + " lq s0, 80(sp)\n" + " jr ra\n" + " 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 (shl arg1 2))\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 (the-as uint (-> *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" + " (v0-1 (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 (the-as int t1-1) 48) 56)\n" + " (shr (shl (the-as int 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!\n" + " s0-0\n" + " (the-as int (shl (the-as int (-> s1-0 imm)) 4))\n" + " )\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" + " (+ (+ (shl sv-48 4) 4) (the-as int arg0))\n" + " (-> sv-32 (shl sv-48 2))\n" + " (-> sv-32 (+ (shl sv-48 2) 1))\n" + " (-> sv-32 (+ (shl sv-48 2) 2))\n" + " (-> sv-32 (+ (shl sv-48 2) 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" + " (the-as\n" + " int\n" + " (+\n" + " (*\n" + " (-> *vif-disasm-table* v1-0 val)\n" + " (the-as uint (-> s1-0 num))\n" + " )\n" + " 3\n" + " )\n" + " )\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 (the-as int 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 (the-as int sv-64) 48) 63)\n" + " (shr (shl (the-as int 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" + " )\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 (shl arg1 2))\n" + " )"; + test_with_expr(func, type, expected, false, "", + {{"L139", " #x~X:"}, + {"L138", " (~s :irq ~D)~%"}, + {"L137", " (~s :irq ~D :~s #x~X)~%"}, + {"L136", " (~s :irq ~D :wl ~D :cl ~D)~%"}, + {"L135", " (~s :irq ~D :~s "}, + {"L134", "#x~X #x~X #x~X #x~X)~%"}, + {"L133", " (~s :irq ~D :instructions #x~D :addr #x~X)~%"}, + {"L132", " (~s :irq ~D :qwc #x~D)~%"}, + {"L145", " #x~X: #x~8x #x~8x #x~8x #x~8x~%"}, + {"L131", " (~s :irq ~D :num ~D :addr #x~X "}, + {"L130", ":msk ~D :flg ~D :usn ~D [skip ~d])~%"}, + {"L129", " (*unknown* vif-tag #x~X)~%"}}); +} \ No newline at end of file diff --git a/test/decompiler/test_FormExpressionBuildLong.cpp b/test/decompiler/test_FormExpressionBuildLong.cpp index 366a67ecdb..29652a75bf 100644 --- a/test/decompiler/test_FormExpressionBuildLong.cpp +++ b/test/decompiler/test_FormExpressionBuildLong.cpp @@ -2373,24 +2373,27 @@ TEST_F(FormRegressionTest, ExprStringToInt) { " (>= (the-as uint 102) (-> a0-3 0))\n" " )\n" " )\n" - " (set!\n" - " v0-0\n" + " (if\n" + " (and\n" + " (>= (-> a0-3 0) (the-as uint 65))\n" + " (>= (the-as uint 70) (-> a0-3 0))\n" + " )\n" + " (set!\n" + " v0-0\n" + " (the-as int (+ (+ (-> a0-3 0) -55) (the-as uint (shl v0-0 4))))\n" + " )\n" " (if\n" " (and\n" - " (>= (-> a0-3 0) (the-as uint 65))\n" - " (>= (the-as uint 70) (-> a0-3 0))\n" + " (>= (-> a0-3 0) (the-as uint 97))\n" + " (>= (the-as uint 102) (-> a0-3 0))\n" " )\n" - " (+ (+ (-> a0-3 0) -55) (the-as uint (shl v0-0 4)))\n" - " (the-as\n" - " int\n" - " (if\n" - " (and\n" - " (>= (-> a0-3 0) (the-as uint 97))\n" - " (>= (the-as uint 102) (-> a0-3 0))\n" - " )\n" - " (+ (+ (-> a0-3 0) -87) (the-as uint (shl v0-0 4)))\n" - " (+ (+ (-> a0-3 0) -48) (the-as uint (shl v0-0 4)))\n" - " )\n" + " (set!\n" + " v0-0\n" + " (the-as int (+ (+ (-> a0-3 0) -87) (the-as uint (shl v0-0 4))))\n" + " )\n" + " (set!\n" + " v0-0\n" + " (the-as int (+ (+ (-> a0-3 0) -48) (the-as uint (shl v0-0 4))))\n" " )\n" " )\n" " )\n" diff --git a/test/offline/offline_test_main.cpp b/test/offline/offline_test_main.cpp index 5dbbae0461..6266f2e772 100644 --- a/test/offline/offline_test_main.cpp +++ b/test/offline/offline_test_main.cpp @@ -17,7 +17,7 @@ const std::unordered_set g_object_files_to_decompile = { "trigonometry-h", /* transformq-h */ "matrix", "transform", "quaternion", "euler", /* geometry, trigonometry, */ "gsound-h", "timer-h", "timer", "vif-h", "dma-h", "video-h", "vu1-user-h", "dma", "dma-buffer", - "dma-bucket", + "dma-bucket", "dma-disasm", /* gap */ "bounding-box", /* gap */ @@ -32,7 +32,7 @@ const std::vector g_object_files_to_check_against_reference = { /* transformq-h, */ "matrix", "transform", "quaternion", "euler", /* geometry, trigonometry */ "gsound-h", "timer-h", /* timer, */ "vif-h", "dma-h", "video-h", "vu1-user-h", "dma", - "dma-buffer", "dma-bucket", + "dma-buffer", "dma-bucket", "dma-disasm", /* gap */ "bounding-box", /* gap */ "sync-info-h", "sync-info"}; @@ -117,6 +117,9 @@ const std::unordered_set skip_in_compiling = { "(method 3 profile-frame)", // double definition. + // dma-disasm + "disasm-dma-list", + // sync-info "(method 15 sync-info)", // needs display stuff first "(method 15 sync-info-eased)", // needs display stuff first @@ -389,9 +392,9 @@ TEST_F(OfflineDecompilation, Reference) { std::string src = db->ir2_final_out(obj_l.at(0)); - // if (file == "gstate") { - // fmt::print("{}\n", src); - // } + /* if (file == "gstring") { + fmt::print("{}\n", src); + }*/ auto reference = file_util::read_text_file(file_util::get_file_path( {"test", "decompiler", "reference", fmt::format("{}_REF.gc", file)}));