From b59e33c005d8bfdeb4f24e79012e158cc46f7bb5 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Mon, 25 Jan 2021 22:08:58 -0500 Subject: [PATCH] [Decompiler] Expressions (Part 3) (#213) * before inserting bonus instruction * first part of refactor for return values * find parent method working --- decompiler/CMakeLists.txt | 1 + decompiler/Function/TypeAnalysis.cpp | 6 + decompiler/IR2/AtomicOp.cpp | 43 +++++ decompiler/IR2/AtomicOp.h | 35 +++- decompiler/IR2/AtomicOpForm.cpp | 11 +- decompiler/IR2/AtomicOpTypeAnalysis.cpp | 10 ++ decompiler/IR2/Env.h | 5 + decompiler/IR2/Form.cpp | 44 ++++- decompiler/IR2/Form.h | 37 +++- decompiler/IR2/FormExpressionAnalysis.cpp | 118 +++++++++++- decompiler/IR2/FormStack.cpp | 20 ++- decompiler/IR2/FormStack.h | 3 +- decompiler/IR2/GenericElementMatcher.cpp | 169 ++++++++++++++++++ decompiler/IR2/GenericElementMatcher.h | 53 ++++++ decompiler/ObjectFile/ObjectFileDB_IR2.cpp | 1 + decompiler/analysis/atomic_op_builder.cpp | 23 ++- decompiler/analysis/atomic_op_builder.h | 7 + decompiler/analysis/expression_build.cpp | 37 +++- decompiler/analysis/reg_usage.cpp | 43 +---- test/decompiler/FormRegressionTest.cpp | 1 + .../decompiler/test_FormBeforeExpressions.cpp | 75 +++++--- test/decompiler/test_FormExpressionBuild.cpp | 121 ++++++++++++- 22 files changed, 782 insertions(+), 81 deletions(-) create mode 100644 decompiler/IR2/GenericElementMatcher.cpp create mode 100644 decompiler/IR2/GenericElementMatcher.h diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index d38a67772..34f69419f 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -36,6 +36,7 @@ add_library( IR2/Form.cpp IR2/FormExpressionAnalysis.cpp IR2/FormStack.cpp + IR2/GenericElementMatcher.cpp ObjectFile/LinkedObjectFile.cpp ObjectFile/LinkedObjectFileCreation.cpp diff --git a/decompiler/Function/TypeAnalysis.cpp b/decompiler/Function/TypeAnalysis.cpp index 04fc84230..6c0eaec64 100644 --- a/decompiler/Function/TypeAnalysis.cpp +++ b/decompiler/Function/TypeAnalysis.cpp @@ -63,6 +63,12 @@ bool Function::run_type_analysis_ir2(const TypeSpec& my_type, dts.type_prop_settings.current_method_type = guessed_name.type_name; } + if (my_type.last_arg() == TypeSpec("none")) { + auto as_end = dynamic_cast(ir2.atomic_ops->ops.back().get()); + assert(as_end); + as_end->mark_function_as_no_return_value(); + } + std::vector block_init_types, op_types; block_init_types.resize(basic_blocks.size()); op_types.resize(ir2.atomic_ops->ops.size()); diff --git a/decompiler/IR2/AtomicOp.cpp b/decompiler/IR2/AtomicOp.cpp index 4258c3043..2a825a38a 100644 --- a/decompiler/IR2/AtomicOp.cpp +++ b/decompiler/IR2/AtomicOp.cpp @@ -1307,4 +1307,47 @@ bool get_as_reg_offset(const SimpleExpression& expr, IR2_RegOffset* out) { } return false; } + +///////////////////////////// +// FunctionEndOp +///////////////////////////// + +FunctionEndOp::FunctionEndOp(int my_idx) + : AtomicOp(my_idx), m_return_reg(VariableMode::READ, Register(Reg::GPR, Reg::V0), my_idx) {} + +goos::Object FunctionEndOp::to_form(const std::vector&, const Env* env) const { + if (m_function_has_return_value) { + return pretty_print::build_list("ret-value", m_return_reg.to_string(env)); + } else { + return pretty_print::build_list("ret-none"); + } +} + +bool FunctionEndOp::operator==(const AtomicOp& other) const { + if (typeid(FunctionEndOp) != typeid(other)) { + return false; + } + + auto po = dynamic_cast(&other); + assert(po); + return m_function_has_return_value == po->m_function_has_return_value; +} + +bool FunctionEndOp::is_sequence_point() const { + return true; +} + +Variable FunctionEndOp::get_set_destination() const { + throw std::runtime_error("FunctionEndOp cannot be treated as a set! operation"); +} + +void FunctionEndOp::update_register_info() { + m_read_regs.push_back(Register(Reg::GPR, Reg::V0)); +} + +void FunctionEndOp::collect_vars(VariableSet& vars) const { + if (m_function_has_return_value) { + vars.insert(m_return_reg); + } +} } // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/AtomicOp.h b/decompiler/IR2/AtomicOp.h index 3600db48d..9d41fce3f 100644 --- a/decompiler/IR2/AtomicOp.h +++ b/decompiler/IR2/AtomicOp.h @@ -38,6 +38,7 @@ class DecompilerTypeSystem; * SetVarConditionOp * AsmOp * SetVarExprOp + * FunctionEndOp */ class AtomicOp { public: @@ -553,7 +554,7 @@ class SpecialOp : public AtomicOp { */ class CallOp : public AtomicOp { public: - CallOp(int my_idx); + explicit CallOp(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; @@ -612,5 +613,37 @@ struct IR2_RegOffset { Variable var; int offset; }; + +/*! + * An extra operation inserted at the very end of a function. + * It "reads" the return register V0. + * During type analysis, call "mark_function_as_no_return_value" to update the register info if + * we learn that this function does not return a value. + */ +class FunctionEndOp : public AtomicOp { + public: + explicit FunctionEndOp(int my_idx); + virtual 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; + Variable get_set_destination() const override; + FormElement* get_as_form(FormPool& pool, const Env& env) const override; + void update_register_info() override; + TypeState propagate_types_internal(const TypeState& input, + const Env& env, + DecompilerTypeSystem& dts) override; + void collect_vars(VariableSet& vars) const override; + void mark_function_as_no_return_value(); + const Variable& return_var() const { + assert(m_function_has_return_value); + return m_return_reg; + } + + private: + bool m_function_has_return_value = true; + Variable m_return_reg; +}; + bool get_as_reg_offset(const SimpleExpression& expr, IR2_RegOffset* out); } // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/AtomicOpForm.cpp b/decompiler/IR2/AtomicOpForm.cpp index cdcc3b3aa..a5fc11b43 100644 --- a/decompiler/IR2/AtomicOpForm.cpp +++ b/decompiler/IR2/AtomicOpForm.cpp @@ -77,7 +77,13 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { // todo pointer // todo product trick // todo type of basic fallback - // todo dynamic method id access + + if (input_type.kind == TP_Type::Kind::DYNAMIC_METHOD_ACCESS && ro.offset == 16) { + // access method vtable. The input is type + (4 * method), and the 16 is the offset + // of method 0. + auto load = pool.alloc_single_element_form(nullptr, ro.var); + return pool.alloc_element(m_dst, load, true); + } // Assume we're accessing a field of an object. FieldReverseLookupInput rd_in; @@ -150,4 +156,7 @@ FormElement* ConditionalMoveFalseOp::get_as_form(FormPool& pool, const Env&) con return pool.alloc_element(m_dst, source, m_on_zero); } +FormElement* FunctionEndOp::get_as_form(FormPool& pool, const Env&) const { + return pool.alloc_element(this); +} } // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index 849cef7a2..e59d2e76c 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -773,4 +773,14 @@ TypeState ConditionalMoveFalseOp::propagate_types_internal(const TypeState& inpu return result; } +TypeState FunctionEndOp::propagate_types_internal(const TypeState& input, + const Env&, + DecompilerTypeSystem&) { + return input; +} + +void FunctionEndOp::mark_function_as_no_return_value() { + m_function_has_return_value = false; +} + } // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h index 2a7821bde..9d3ebdb3b 100644 --- a/decompiler/IR2/Env.h +++ b/decompiler/IR2/Env.h @@ -69,6 +69,9 @@ class Env { m_has_local_vars = true; } + void set_end_var(Variable var) { m_end_var = var; } + const Variable& end_var() const { return m_end_var; } + std::string print_local_var_types(const Form* top_level_form) const; std::unordered_set get_ssa_var(const VariableSet& vars) const; @@ -78,6 +81,8 @@ class Env { DecompilerTypeSystem* dts = nullptr; private: + Variable m_end_var; + bool m_has_reg_use = false; RegUsageInfo m_reg_use; diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp index 130d548f8..6fb72d342 100644 --- a/decompiler/IR2/Form.cpp +++ b/decompiler/IR2/Form.cpp @@ -344,8 +344,8 @@ void BranchElement::collect_vars(VariableSet& vars) const { goos::Object ReturnElement::to_form(const Env& env) const { std::vector forms; forms.push_back(pretty_print::to_symbol("return")); - forms.push_back(pretty_print::build_list(return_code->to_form(env))); - forms.push_back(pretty_print::build_list(dead_code->to_form(env))); + forms.push_back(return_code->to_form(env)); + forms.push_back(dead_code->to_form(env)); return pretty_print::build_list(forms); } @@ -820,6 +820,26 @@ void GenericOperator::apply_form(const std::function& f) { } } +bool GenericOperator::operator==(const GenericOperator& other) const { + if (m_kind != other.m_kind) { + return false; + } + switch (m_kind) { + case Kind::FIXED_OPERATOR: + return m_fixed_kind == other.m_fixed_kind; + case Kind::CONDITION_OPERATOR: + return m_condition_kind == other.m_condition_kind; + case Kind::FUNCTION_EXPR: + return false; + default: + assert(false); + } +} + +bool GenericOperator::operator!=(const GenericOperator& other) const { + return !((*this) == other); +} + std::string fixed_operator_to_string(FixedOperatorKind kind) { switch (kind) { case FixedOperatorKind::GPR_TO_FPR: @@ -1034,4 +1054,24 @@ void DerefElement::collect_vars(VariableSet& vars) const { } } +///////////////////////////// +// DynamicMethodAccess +///////////////////////////// + +DynamicMethodAccess::DynamicMethodAccess(Variable source) : m_source(source) {} + +goos::Object DynamicMethodAccess::to_form(const Env& env) const { + return pretty_print::build_list("dyn-method-access", m_source.to_string(&env)); +} + +void DynamicMethodAccess::apply(const std::function& f) { + f(this); +} + +void DynamicMethodAccess::apply_form(const std::function&) {} + +void DynamicMethodAccess::collect_vars(VariableSet& vars) const { + vars.insert(m_source); +} + } // namespace decompiler diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index d01c2e06a..2d0368555 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -167,6 +167,7 @@ class SimpleAtomElement : public FormElement { void apply(const std::function& f) override; void apply_form(const std::function& f) override; void collect_vars(VariableSet& vars) const override; + const SimpleAtom& atom() const { return m_atom; } // void push_to_stack(const Env& env, FormStack& stack) override; private: @@ -206,6 +207,8 @@ class AtomicOpElement : public FormElement { void apply(const std::function& f) override; void apply_form(const std::function& f) override; void collect_vars(VariableSet& vars) const override; + void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override; + const AtomicOp* op() const { return m_op; } private: const AtomicOp* m_op; @@ -230,6 +233,10 @@ class ConditionElement : public FormElement { void apply_form(const std::function& f) override; void collect_vars(VariableSet& vars) const override; void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override; + void update_from_stack(const Env& env, + FormPool& pool, + FormStack& stack, + std::vector* result) override; void invert(); const RegSet& consume() const { return m_consumed; } @@ -436,6 +443,7 @@ class ShortCircuitElement : public FormElement { void apply(const std::function& f) override; void apply_form(const std::function& f) override; void collect_vars(VariableSet& vars) const override; + void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override; }; /*! @@ -569,6 +577,8 @@ class GenericOperator { goos::Object to_form(const Env& env) const; void apply(const std::function& f); void apply_form(const std::function& f); + bool operator==(const GenericOperator& other) const; + bool operator!=(const GenericOperator& other) const; private: Kind m_kind = Kind::INVALID; @@ -588,6 +598,8 @@ class GenericElement : public FormElement { void apply(const std::function& f) override; void apply_form(const std::function& f) override; void collect_vars(VariableSet& vars) const override; + const GenericOperator& op() const { return m_head; } + const std::vector& elts() const { return m_elts; } private: GenericOperator m_head; @@ -601,6 +613,8 @@ class CastElement : public FormElement { void apply(const std::function& f) override; void apply_form(const std::function& f) override; void collect_vars(VariableSet& vars) const override; + const TypeSpec& type() const { return m_type; } + const Form* source() const { return m_source; } private: TypeSpec m_type; @@ -609,7 +623,12 @@ class CastElement : public FormElement { class DerefToken { public: - enum class Kind { INTEGER_CONSTANT, INTEGER_EXPRESSION, FIELD_NAME, INVALID }; + enum class Kind { + INTEGER_CONSTANT, + INTEGER_EXPRESSION, // some form which evaluates to an integer index. Not offset, index. + FIELD_NAME, + INVALID + }; static DerefToken make_int_constant(s64 int_constant); static DerefToken make_int_expr(Form* expr); static DerefToken make_field_name(const std::string& name); @@ -645,6 +664,22 @@ class DerefElement : public FormElement { std::vector m_tokens; }; +class DynamicMethodAccess : public FormElement { + public: + explicit DynamicMethodAccess(Variable source); + goos::Object to_form(const Env& env) const override; + void apply(const std::function& f) override; + void apply_form(const std::function& f) override; + void collect_vars(VariableSet& vars) const override; + void update_from_stack(const Env& env, + FormPool& pool, + FormStack& stack, + std::vector* result) override; + + private: + Variable m_source; +}; + /*! * 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 e9596e7c6..f00a1823f 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -1,10 +1,21 @@ #include "Form.h" #include "FormStack.h" +#include "GenericElementMatcher.h" + +/* + * TODO + * - use var_to_form over expressions for vars + * - check out if we can push/pop variables instead of registers? + */ namespace decompiler { namespace { +Form* var_to_form(const Variable& var, FormPool& pool) { + return pool.alloc_single_element_form(nullptr, SimpleAtom::make_var(var)); +} + void update_var_from_stack_helper(int my_idx, Variable input, FormPool& pool, @@ -529,6 +540,41 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack.push_form_element(this, true); } +/////////////////// +// ShortCircuitElement +/////////////////// + +void ShortCircuitElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { + if (!used_as_value.value_or(false)) { + throw std::runtime_error( + "ShortCircuitElement::push_to_stack not implemented for result not used case."); + + stack.push_form_element(this, true); + } else { + for (int i = 0; i < int(entries.size()); i++) { + auto& entry = entries.at(i); + FormStack temp_stack; + for (auto& elt : entry.condition->elts()) { + elt->push_to_stack(env, pool, temp_stack); + } + + std::vector new_entries; + if (i == int(entries.size()) - 1) { + new_entries = temp_stack.rewrite_to_get_var(pool, final_result, env); + } else { + new_entries = temp_stack.rewrite(pool); + } + + entry.condition->clear(); + for (auto e : new_entries) { + entry.condition->push_back(e); + } + } + assert(used_as_value.has_value()); + stack.push_value_to_reg(final_result, pool.alloc_single_form(nullptr, this), true); + } +} + /////////////////// // ConditionElement /////////////////// @@ -546,8 +592,78 @@ void ConditionElement::push_to_stack(const Env&, FormPool& pool, FormStack& stac true); } -void ReturnElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { +void ConditionElement::update_from_stack(const Env&, + FormPool& pool, + FormStack& stack, + std::vector* result) { + std::vector source_forms; + + for (int i = 0; i < get_condition_num_args(m_kind); i++) { + source_forms.push_back(update_var_from_stack_to_form(m_src[i]->var().idx(), m_src[i]->var(), + m_consumed, pool, stack)); + } + + result->push_back( + pool.alloc_element(GenericOperator::make_compare(m_kind), source_forms)); +} + +void ReturnElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { + FormStack temp_stack; + for (auto& elt : return_code->elts()) { + elt->push_to_stack(env, pool, temp_stack); + } + + std::vector new_entries; + new_entries = temp_stack.rewrite_to_get_var(pool, env.end_var(), env); + + return_code->clear(); + for (auto e : new_entries) { + return_code->push_back(e); + } stack.push_form_element(this, true); } +void AtomicOpElement::push_to_stack(const Env& env, FormPool&, FormStack&) { + auto as_end = dynamic_cast(m_op); + if (as_end) { + // we don't want to push this to the stack (for now at least) + return; + } + throw std::runtime_error("Can't push atomic op to stack: " + m_op->to_string(env)); +} + +//////////////////////// +// DynamicMethodAccess +//////////////////////// + +void DynamicMethodAccess::update_from_stack(const Env& env, + FormPool& pool, + FormStack& stack, + std::vector* result) { + auto new_val = stack.pop_reg(m_source); + auto reg0_matcher = + Matcher::match_or({Matcher::any_reg(0), Matcher::cast("uint", Matcher::any_reg(0))}); + auto reg1_matcher = + Matcher::match_or({Matcher::any_reg(1), Matcher::cast("int", Matcher::any_reg(1))}); + + // (+ (sll (the-as uint a1-0) 2) (the-as int a0-0)) + auto sll_matcher = Matcher::fixed_op(FixedOperatorKind::SLL, {reg0_matcher, Matcher::integer(2)}); + auto matcher = Matcher::fixed_op(FixedOperatorKind::ADDITION, {sll_matcher, reg1_matcher}); + auto match_result = match(matcher, new_val); + if (!match_result.matched) { + throw std::runtime_error("Couldn't match DynamicMethodAccess values: " + + new_val->to_string(env)); + } + + auto idx = match_result.maps.regs.at(0); + auto base = match_result.maps.regs.at(1); + assert(idx.has_value() && base.has_value()); + + auto deref = pool.alloc_element( + var_to_form(base.value(), pool), false, + std::vector{DerefToken::make_field_name("methods"), + DerefToken::make_int_expr(var_to_form(idx.value(), pool))}); + result->push_back(deref); +} + } // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/FormStack.cpp b/decompiler/IR2/FormStack.cpp index ab351ef11..0e6977805 100644 --- a/decompiler/IR2/FormStack.cpp +++ b/decompiler/IR2/FormStack.cpp @@ -49,11 +49,11 @@ void FormStack::push_form_element(FormElement* elt, bool sequence_point) { m_stack.push_back(entry); } -Form* FormStack::pop_reg(const Variable& var) { +Form* FormStack::pop_reg(Register reg) { for (size_t i = m_stack.size(); i-- > 0;) { auto& entry = m_stack.at(i); if (entry.active) { - if (entry.destination->reg() == var.reg()) { + if (entry.destination->reg() == reg) { entry.active = false; assert(entry.source); return entry.source; @@ -70,6 +70,10 @@ Form* FormStack::pop_reg(const Variable& var) { return nullptr; } +Form* FormStack::pop_reg(const Variable& var) { + return pop_reg(var.reg()); +} + std::vector FormStack::rewrite(FormPool& pool) { std::vector result; @@ -89,9 +93,9 @@ std::vector FormStack::rewrite(FormPool& pool) { return result; } -std::vector FormStack::rewrite_to_get_reg(FormPool& pool, - Register reg, - const Env& env) { +std::vector FormStack::rewrite_to_get_var(FormPool& pool, + const Variable& var, + const Env&) { // first, rewrite as normal. auto default_result = rewrite(pool); @@ -99,7 +103,7 @@ std::vector FormStack::rewrite_to_get_reg(FormPool& pool, // value in the given register. auto last_op_as_set = dynamic_cast(default_result.back()); - if (last_op_as_set && last_op_as_set->dst().reg() == reg) { + if (last_op_as_set && last_op_as_set->dst().reg() == var.reg()) { default_result.pop_back(); for (auto form : last_op_as_set->src()->elts()) { form->parent_form = nullptr; // will get set later, this makes it obvious if I forget. @@ -107,8 +111,8 @@ std::vector FormStack::rewrite_to_get_reg(FormPool& pool, } return default_result; } else { - throw std::runtime_error( - fmt::format("Couldn't rewrite form to get result {}:\n{}\n\n", reg.to_charp(), print(env))); + default_result.push_back(pool.alloc_element(SimpleAtom::make_var(var))); + return default_result; } } } // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/FormStack.h b/decompiler/IR2/FormStack.h index 2f2afee62..4e761472f 100644 --- a/decompiler/IR2/FormStack.h +++ b/decompiler/IR2/FormStack.h @@ -16,9 +16,10 @@ class FormStack { void push_value_to_reg(Variable var, Form* value, bool sequence_point); void push_form_element(FormElement* elt, bool sequence_point); Form* pop_reg(const Variable& var); + Form* pop_reg(Register reg); bool is_single_expression(); std::vector rewrite(FormPool& pool); - std::vector rewrite_to_get_reg(FormPool& pool, Register reg, const Env& env); + std::vector rewrite_to_get_var(FormPool& pool, const Variable& var, const Env& env); std::string print(const Env& env); private: diff --git a/decompiler/IR2/GenericElementMatcher.cpp b/decompiler/IR2/GenericElementMatcher.cpp new file mode 100644 index 000000000..30338d348 --- /dev/null +++ b/decompiler/IR2/GenericElementMatcher.cpp @@ -0,0 +1,169 @@ +#include "GenericElementMatcher.h" + +namespace decompiler { +Matcher Matcher::any_reg(int match_id) { + Matcher m; + m.m_kind = Kind::ANY_REG; + m.m_reg_out_id = match_id; + return m; +} + +Matcher Matcher::op(GenericOperator op, const std::vector& args) { + Matcher m; + m.m_kind = Kind::GENERIC_OP; + m.m_gen_op = op; + m.m_sub_matchers = args; + return m; +} + +Matcher Matcher::fixed_op(FixedOperatorKind op, const std::vector& args) { + Matcher m; + m.m_kind = Kind::GENERIC_OP; + m.m_gen_op = GenericOperator::make_fixed(op); + m.m_sub_matchers = args; + return m; +} + +Matcher Matcher::match_or(const std::vector& args) { + Matcher m; + m.m_kind = Kind::OR; + m.m_sub_matchers = args; + return m; +} + +Matcher Matcher::cast(const std::string& type, Matcher value) { + Matcher m; + m.m_kind = Kind::CAST; + m.m_str = type; + m.m_sub_matchers = {value}; + return m; +} + +Matcher Matcher::any() { + Matcher m; + m.m_kind = Kind::ANY; + return m; +} + +Matcher Matcher::integer(std::optional value) { + Matcher m; + m.m_kind = Kind::INT; + m.m_int_match = value; + return m; +} + +bool Matcher::do_match(const Form* input, MatchResult::Maps* maps_out) const { + switch (m_kind) { + case Kind::ANY_REG: { + bool got = false; + Variable result; + + auto as_simple_atom = dynamic_cast(input->try_as_single_element()); + if (as_simple_atom) { + if (as_simple_atom->atom().is_var()) { + got = true; + result = as_simple_atom->atom().var(); + } + } + + auto as_expr = dynamic_cast(input->try_as_single_element()); + if (as_expr && as_expr->expr().is_identity()) { + auto atom = as_expr->expr().get_arg(0); + if (atom.is_var()) { + got = true; + result = atom.var(); + } + } + + if (got) { + if (m_reg_out_id != -1) { + maps_out->regs.resize(std::max(size_t(m_reg_out_id + 1), maps_out->regs.size())); + maps_out->regs.at(m_reg_out_id) = result; + } + return true; + } else { + return false; + } + } break; + + case Kind::GENERIC_OP: { + auto as_generic = dynamic_cast(input->try_as_single_element()); + if (as_generic) { + if (as_generic->op() != m_gen_op) { + return false; + } + + if (as_generic->elts().size() != m_sub_matchers.size()) { + return false; + } + + for (size_t i = 0; i < m_sub_matchers.size(); i++) { + if (!m_sub_matchers.at(i).do_match(as_generic->elts().at(i), maps_out)) { + return false; + } + } + return true; + } + return false; + } break; + + case Kind::OR: { + for (auto& matcher : m_sub_matchers) { + if (matcher.do_match(input, maps_out)) { + return true; + } + } + return false; + } break; + + case Kind::CAST: { + auto as_cast = dynamic_cast(input->try_as_single_element()); + if (as_cast) { + if (as_cast->type().print() == m_str) { + return m_sub_matchers.at(0).do_match(as_cast->source(), maps_out); + } + } + return false; + } break; + + case Kind::INT: { + auto as_simple_atom = dynamic_cast(input->try_as_single_element()); + if (as_simple_atom) { + if (as_simple_atom->atom().is_int()) { + if (!m_int_match.has_value()) { + return true; + } + return as_simple_atom->atom().get_int() == *m_int_match; + } + } + + auto as_expr = dynamic_cast(input->try_as_single_element()); + if (as_expr && as_expr->expr().is_identity()) { + auto atom = as_expr->expr().get_arg(0); + if (atom.is_int()) { + if (!m_int_match.has_value()) { + return true; + } + return atom.get_int() == *m_int_match; + } + } + + return false; + } + + default: + assert(false); + } +} + +Matcher Matcher::any_reg_cast_to_int_or_uint(int match_id) { + return match_or( + {any_reg(match_id), cast("uint", any_reg(match_id)), cast("int", any_reg(match_id))}); +} + +MatchResult match(const Matcher& spec, const Form* input) { + MatchResult result; + result.matched = spec.do_match(input, &result.maps); + return result; +} +} // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/GenericElementMatcher.h b/decompiler/IR2/GenericElementMatcher.h new file mode 100644 index 000000000..d6dd4110a --- /dev/null +++ b/decompiler/IR2/GenericElementMatcher.h @@ -0,0 +1,53 @@ +/*! + * @file GenericElementMatcher.h + * + * The Matcher is supposed to match up forms to templates, and extract the variables actually used. + */ + +#pragma once +#include "Form.h" + +namespace decompiler { + +struct MatchResult { + bool matched = false; + struct Maps { + std::vector> regs; + } maps; +}; + +class Matcher { + public: + static Matcher any_reg(int match_id = -1); + static Matcher op(GenericOperator op, const std::vector& args); + static Matcher fixed_op(FixedOperatorKind op, const std::vector& args); + static Matcher match_or(const std::vector& args); + static Matcher cast(const std::string& type, Matcher value); + static Matcher any(); + static Matcher integer(std::optional value); + static Matcher any_reg_cast_to_int_or_uint(int match_id = -1); + + enum class Kind { + ANY_REG, // matching any register + GENERIC_OP, // matching + OR, + CAST, + ANY, + INT, + INVALID + }; + + bool do_match(const Form* input, MatchResult::Maps* maps_out) const; + + private: + GenericOperator m_gen_op; + std::vector m_sub_matchers; + Kind m_kind = Kind::INVALID; + int m_reg_out_id = -1; + std::optional m_int_match; + std::string m_str; +}; + +MatchResult match(const Matcher& spec, const Form* input); + +} // namespace decompiler \ No newline at end of file diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index 8158b3d6c..cb40a864a 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -239,6 +239,7 @@ void ObjectFileDB::ir2_atomic_op_pass() { auto ops = convert_function_to_atomic_ops(func, data.linked_data.labels); func.ir2.atomic_ops = std::make_shared(std::move(ops)); func.ir2.atomic_ops_succeeded = true; + func.ir2.env.set_end_var(func.ir2.atomic_ops->end_op().return_var()); successful++; } catch (std::exception& e) { lg::warn("Function {} from {} could not be converted to atomic ops: {}", diff --git a/decompiler/analysis/atomic_op_builder.cpp b/decompiler/analysis/atomic_op_builder.cpp index daa2da40e..93d3abcd7 100644 --- a/decompiler/analysis/atomic_op_builder.cpp +++ b/decompiler/analysis/atomic_op_builder.cpp @@ -1407,15 +1407,32 @@ FunctionAtomicOps convert_function_to_atomic_ops(const Function& func, FunctionAtomicOps result; int last_op = 0; - for (const auto& block : func.basic_blocks) { + for (int i = 0; i < int(func.basic_blocks.size()); i++) { + const auto& block = func.basic_blocks.at(i); // we should only consider the blocks which actually have instructions: if (block.end_word > block.start_word) { auto begin = func.instructions.begin() + block.start_word; auto end = func.instructions.begin() + block.end_word; last_op = convert_block_to_atomic_ops(block.start_word, begin, end, labels, &result); + if (i == int(func.basic_blocks.size()) - 1) { + // we're the last block. insert the function end op. + result.ops.push_back(std::make_unique(int(result.ops.size()))); + result.ops.back()->update_register_info(); + // add to block. + result.block_id_to_end_atomic_op.back()++; + } } else { - result.block_id_to_first_atomic_op.push_back(last_op); - result.block_id_to_end_atomic_op.push_back(last_op); + if (i == int(func.basic_blocks.size()) - 1) { + // we're the last block. insert the function end op. + result.ops.push_back(std::make_unique(int(result.ops.size()))); + result.ops.back()->update_register_info(); + // add block (no longer a zero-size block) + result.block_id_to_first_atomic_op.push_back(last_op); + result.block_id_to_end_atomic_op.push_back(last_op + 1); + } else { + result.block_id_to_first_atomic_op.push_back(last_op); + result.block_id_to_end_atomic_op.push_back(last_op); + } } } diff --git a/decompiler/analysis/atomic_op_builder.h b/decompiler/analysis/atomic_op_builder.h index 7826da1e2..95404dcf0 100644 --- a/decompiler/analysis/atomic_op_builder.h +++ b/decompiler/analysis/atomic_op_builder.h @@ -14,6 +14,13 @@ struct FunctionAtomicOps { // the actual ops, store in the correct order std::vector> ops; + FunctionEndOp& end_op() const { + assert(!ops.empty()); + auto end = dynamic_cast(ops.back().get()); + assert(end); + return *end; + } + // mappings from instructions to atomic ops and back std::unordered_map instruction_to_atomic_op; std::unordered_map atomic_op_to_instruction; diff --git a/decompiler/analysis/expression_build.cpp b/decompiler/analysis/expression_build.cpp index 6a728fef9..5aa4b21dc 100644 --- a/decompiler/analysis/expression_build.cpp +++ b/decompiler/analysis/expression_build.cpp @@ -5,6 +5,31 @@ #include "decompiler/util/DecompilerTypeSystem.h" namespace decompiler { +void clean_up_ifs(Form* top_level_form) { + top_level_form->apply([&](FormElement* elt) { + auto as_cne = dynamic_cast(elt); + if (!as_cne) { + return; + } + + auto top_condition = as_cne->entries.front().condition; + if (!top_condition->is_single_element() && elt->parent_form) { + auto real_condition = top_condition->back(); + top_condition->pop_back(); + + auto& parent_vector = elt->parent_form->elts(); + // find us in the parent vector + auto me = std::find_if(parent_vector.begin(), parent_vector.end(), + [&](FormElement* x) { return x == elt; }); + assert(me != parent_vector.end()); + + // now insert the fake condition + parent_vector.insert(me, top_condition->elts().begin(), top_condition->elts().end()); + top_condition->elts() = {real_condition}; + } + }); +} + bool convert_to_expressions(Form* top_level_form, FormPool& pool, const Function& f, @@ -40,9 +65,10 @@ bool convert_to_expressions(Form* top_level_form, } std::vector new_entries; if (f.type.last_arg() != TypeSpec("none")) { - auto v0 = Register(Reg::GPR, Reg::V0); - new_entries = stack.rewrite_to_get_reg(pool, v0, f.ir2.env); - auto reg_return_type = f.ir2.env.get_types_after_op(f.ir2.atomic_ops->ops.size() - 1).get(v0); + auto return_var = f.ir2.atomic_ops->end_op().return_var(); + new_entries = stack.rewrite_to_get_var(pool, return_var, f.ir2.env); + auto reg_return_type = + f.ir2.env.get_types_after_op(f.ir2.atomic_ops->ops.size() - 1).get(return_var.reg()); if (!dts.ts.typecheck(f.type.last_arg(), reg_return_type.typespec(), "", false, false)) { // we need to cast the final value. auto to_cast = new_entries.back(); @@ -59,10 +85,15 @@ bool convert_to_expressions(Form* top_level_form, for (auto x : new_entries) { top_level_form->push_back(x); } + + // fix up stuff + clean_up_ifs(top_level_form); + } catch (std::exception& e) { lg::warn("Expression building failed: {}", e.what()); return false; } + return true; } } // namespace decompiler diff --git a/decompiler/analysis/reg_usage.cpp b/decompiler/analysis/reg_usage.cpp index 154d561b7..c9e5d716d 100644 --- a/decompiler/analysis/reg_usage.cpp +++ b/decompiler/analysis/reg_usage.cpp @@ -12,27 +12,14 @@ bool in_set(RegSet& set, const Register& obj) { return set.find(obj) != set.end(); } -void phase1(const FunctionAtomicOps& ops, - int block_id, - RegUsageInfo* out, - bool insert_v0_read_instruction_at_end) { +void phase1(const FunctionAtomicOps& ops, int block_id, RegUsageInfo* out) { int end_op = ops.block_id_to_end_atomic_op.at(block_id); int start_op = ops.block_id_to_first_atomic_op.at(block_id); - int loop_end = end_op; - if (insert_v0_read_instruction_at_end) { - loop_end++; - } - for (int i = loop_end; i-- > start_op;) { - std::vector read; - std::vector write; - if (i == end_op) { - read = {Register(Reg::GPR, Reg::V0)}; - } else { - const auto& instr = ops.ops.at(i); - read = instr->read_regs(); - write = instr->write_regs(); - } + for (int i = end_op; i-- > start_op;) { + const auto& instr = ops.ops.at(i); + auto read = instr->read_regs(); + auto write = instr->write_regs(); auto& lv = out->op.at(i).live; auto& dd = out->op.at(i).dead; @@ -114,8 +101,7 @@ bool phase2(const std::vector& blocks, int block_id, RegUsageInfo* i void phase3(const FunctionAtomicOps& ops, const std::vector& blocks, int block_id, - RegUsageInfo* info, - bool insert_v0_read_instruction_at_end) { + RegUsageInfo* info) { RegSet live_local; const auto& block_obj = blocks.at(block_id); for (auto s : {block_obj.succ_branch, block_obj.succ_ft}) { @@ -130,12 +116,7 @@ void phase3(const FunctionAtomicOps& ops, int end_op = ops.block_id_to_end_atomic_op.at(block_id); int start_op = ops.block_id_to_first_atomic_op.at(block_id); - int loop_end = end_op; - if (insert_v0_read_instruction_at_end) { - loop_end++; - } - - for (int i = loop_end; i-- > start_op;) { + for (int i = end_op; i-- > start_op;) { auto& lv = info->op.at(i).live; auto& dd = info->op.at(i).dead; @@ -149,12 +130,6 @@ void phase3(const FunctionAtomicOps& ops, live_local = new_live; } } - -bool should_insert_v0_read(const std::vector& blocks, const Function& function, int i) { - return i == int(blocks.size()) - 1 && function.type.arg_count() > 0 && - function.type.last_arg() != TypeSpec("none"); -} - } // namespace RegUsageInfo analyze_ir2_register_usage(const Function& function) { @@ -163,7 +138,7 @@ RegUsageInfo analyze_ir2_register_usage(const Function& function) { RegUsageInfo result(blocks.size(), ops->ops.size() + 1); for (int i = 0; i < int(blocks.size()); i++) { - phase1(*ops, i, &result, should_insert_v0_read(blocks, function, i)); + phase1(*ops, i, &result); } bool changed = false; @@ -177,7 +152,7 @@ RegUsageInfo analyze_ir2_register_usage(const Function& function) { } while (changed); for (int i = 0; i < int(blocks.size()); i++) { - phase3(*ops, blocks, i, &result, should_insert_v0_read(blocks, function, i)); + phase3(*ops, blocks, i, &result); } // we want to know if an op "consumes" a register. diff --git a/test/decompiler/FormRegressionTest.cpp b/test/decompiler/FormRegressionTest.cpp index 5b11b81d6..1ffa33f89 100644 --- a/test/decompiler/FormRegressionTest.cpp +++ b/test/decompiler/FormRegressionTest.cpp @@ -98,6 +98,7 @@ std::unique_ptr FormRegressionTest::make_function( auto ops = convert_function_to_atomic_ops(test->func, program.labels); test->func.ir2.atomic_ops = std::make_shared(std::move(ops)); test->func.ir2.atomic_ops_succeeded = true; + test->func.ir2.env.set_end_var(test->func.ir2.atomic_ops->end_op().return_var()); EXPECT_TRUE(test->func.run_type_analysis_ir2(function_type, *dts, test->file, {})); diff --git a/test/decompiler/test_FormBeforeExpressions.cpp b/test/decompiler/test_FormBeforeExpressions.cpp index f5f7b641c..10e5406c9 100644 --- a/test/decompiler/test_FormBeforeExpressions.cpp +++ b/test/decompiler/test_FormBeforeExpressions.cpp @@ -27,7 +27,7 @@ TEST_F(FormRegressionTest, SimplestTest) { " jr ra\n" " daddu sp, sp, r0"; std::string type = "(function object object)"; - std::string expected = "(set! v0-0 a0-0)"; + std::string expected = "(begin (set! v0-0 a0-0) (ret-value v0-0))"; test_no_expr(func, type, expected); } @@ -52,6 +52,7 @@ TEST_F(FormRegressionTest, FloatingPointBasic) { " (set! f1-0 (gpr->fpr a0-0))\n" " (set! f0-1 (/.s f0-0 f1-0))\n" " (set! v0-0 (fpr->gpr f0-1))\n" + " (ret-value v0-0)\n" " )"; test_no_expr(func, type, expected); } @@ -64,7 +65,7 @@ TEST_F(FormRegressionTest, Op3) { " jr ra\n" " daddu sp, sp, r0"; std::string type = "(function int int int)"; - std::string expected = "(set! v0-0 (*.si a0-0 a1-0))"; + std::string expected = "(begin (set! v0-0 (*.si a0-0 a1-0)) (ret-value v0-0))"; test_no_expr(func, type, expected); } @@ -77,7 +78,7 @@ TEST_F(FormRegressionTest, Division) { " jr ra\n" " daddu sp, sp, r0"; std::string type = "(function int int int)"; - std::string expected = "(set! v0-0 (/.si a0-0 a1-0))"; + std::string expected = "(begin (set! v0-0 (/.si a0-0 a1-0)) (ret-value v0-0))"; test_no_expr(func, type, expected); } @@ -97,7 +98,7 @@ TEST_F(FormRegressionTest, Ash) { " sll r0, r0, 0\n" " sll r0, r0, 0"; std::string type = "(function int int int)"; - std::string expected = "(begin (set! v1-0 a0-0) (set! v0-0 (ash.si v1-0 a1-0)))"; + std::string expected = "(begin (set! v1-0 a0-0) (set! v0-0 (ash.si v1-0 a1-0)) (ret-value v0-0))"; test_no_expr(func, type, expected); } @@ -113,7 +114,7 @@ TEST_F(FormRegressionTest, Abs) { " jr ra\n" " daddu sp, sp, r0"; std::string type = "(function int int)"; - std::string expected = "(begin (set! v0-0 a0-0) (set! v0-1 (abs v0-0)))"; + std::string expected = "(begin (set! v0-0 a0-0) (set! v0-1 (abs v0-0)) (ret-value v0-1))"; test_no_expr(func, type, expected); } @@ -127,7 +128,13 @@ TEST_F(FormRegressionTest, Min) { " jr ra\n" " daddu sp, sp, r0"; std::string type = "(function int int int)"; - std::string expected = "(begin (set! v0-0 a0-0) (set! v1-0 a1-0) (set! v0-1 (min.si v0-0 v1-0)))"; + std::string expected = + "(begin\n" + " (set! v0-0 a0-0)\n" + " (set! v1-0 a1-0)\n" + " (set! v0-1 (min.si v0-0 v1-0))\n" + " (ret-value v0-1)\n" + " )"; test_no_expr(func, type, expected); } @@ -142,7 +149,13 @@ TEST_F(FormRegressionTest, Max) { " jr ra\n" " daddu sp, sp, r0"; std::string type = "(function int int int)"; - std::string expected = "(begin (set! v0-0 a0-0) (set! v1-0 a1-0) (set! v0-1 (max.si v0-0 v1-0)))"; + std::string expected = + "(begin\n" + " (set! v0-0 a0-0)\n" + " (set! v1-0 a1-0)\n" + " (set! v0-1 (max.si v0-0 v1-0))\n" + " (ret-value v0-1)\n" + " )"; test_no_expr(func, type, expected); } @@ -182,6 +195,7 @@ TEST_F(FormRegressionTest, FormatString) { " (set! a2-0 (fpr->gpr f0-0))\n" " (call! a0-1 a1-0 a2-0)\n" // #t, "~f", the float " (set! v0-1 gp-0)\n" + " (ret-value v0-1)\n" " )"; test_no_expr(func, type, expected, false, "", {{"L343", "~f"}}); } @@ -220,10 +234,11 @@ TEST_F(FormRegressionTest, WhileLoop) { " (begin (set! v1-0 (-> v1-0 parent)) (= v1-0 a0-1))\n" " (if\n" " (= v1-0 a1-0)\n" - " (return ((begin (set! v1-1 '#t) (set! v0-0 v1-1))) ((set! v1-0 0)))\n" + " (return (begin (set! v1-1 '#t) (set! v0-0 v1-1)) (set! v1-0 0))\n" " )\n" " )\n" " (set! v0-0 '#f)\n" + " (ret-value v0-0)\n" " )"; test_no_expr(func, type, expected); } @@ -285,10 +300,11 @@ TEST_F(FormRegressionTest, Or) { " )\n" " (if\n" " (= a0-0 a1-0)\n" - " (return ((begin (set! v1-1 '#t) (set! v0-0 v1-1))) ((set! v1-0 0)))\n" + " (return (begin (set! v1-1 '#t) (set! v0-0 v1-1)) (set! v1-0 0))\n" " )\n" " )\n" " (set! v0-0 '#f)\n" + " (ret-value v0-0)\n" " )"; test_no_expr(func, type, expected); } @@ -343,29 +359,30 @@ TEST_F(FormRegressionTest, DynamicMethodAccess) { "(begin\n" " (set! v1-0 (sll a1-0 2))\n" " (set! v1-1 (+ v1-0 a0-0))\n" - " (set! v1-2 (l.wu (+ v1-1 16)))\n" // get the method of the given type. + " (set! v1-2 (dyn-method-access v1-1))\n" // get the method of the given type. " (until\n" " (!= v0-0 v1-2)\n" // actually goes after the body, so it's fine to refer to v1-2 " (if\n" " (begin\n" " (if\n" " (begin (set! a2-0 object) (= a0-0 a2-0))\n" // if we reached the top - " (return ((begin (set! v1-3 nothing) (set! v0-0 v1-3))) ((set! v1-2 0)))\n" // return - // nothing. + " (return (begin (set! v1-3 nothing) (set! v0-0 v1-3)) (set! v1-2 0))\n" // return + // nothing. " )\n" " (set! a0-0 (-> a0-0 parent))\n" // get next parent type " (set! a2-2 (sll a1-0 2))\n" // fancy access " (set! a2-3 (+ a2-2 a0-0))\n" - " (set! v0-0 (l.wu (+ a2-3 16)))\n" // get method (in v0-1, the same var as loop - // condition) - " (zero? v0-0)\n" // is it defined? + " (set! v0-0 (dyn-method-access a2-3))\n" // get method (in v0-1, the same var as loop + // condition) + " (zero? v0-0)\n" // is it defined? " )\n" - " (return ((begin (set! v1-4 nothing) (set! v0-0 v1-4))) ((set! v1-2 0)))\n" // also - // return - // nothing. + " (return (begin (set! v1-4 nothing) (set! v0-0 v1-4)) (set! v1-2 0))\n" // also + // return + // nothing. " )\n" " )\n" " (set! v1-5 '#f)\n" + " (ret-value v0-0)\n" " )"; test_no_expr(func, type, expected); } @@ -409,6 +426,7 @@ TEST_F(FormRegressionTest, SimpleLoopMergeCheck) { " (set! v1-1 '#f)\n" " (set! v1-2 '#f)\n" " (set! v0-0 (l.w (+ a0-0 -2)))\n" + " (ret-value v0-0)\n" " )"; test_no_expr(func, type, expected, true); } @@ -460,6 +478,7 @@ TEST_F(FormRegressionTest, And) { " daddu sp, sp, r0"; std::string type = "(function pair int)"; std::string expected = + "(begin\n" "(cond\n" " ((begin (set! v1-0 '()) (= a0-0 v1-0)) (set! v0-0 0))\n" // should be a case, not a return " (else\n" @@ -478,7 +497,8 @@ TEST_F(FormRegressionTest, And) { " )\n" " (set! v1-2 '#f)\n" // while's false, I think. " )\n" - " )"; + " )" + "(ret-value v0-0))\n"; test_no_expr(func, type, expected, true); } @@ -536,7 +556,7 @@ TEST_F(FormRegressionTest, FunctionCall) { " daddiu sp, sp, 48"; std::string type = "(function basic object object)"; std::string expected = - "(if\n" // this if needs regrouping. + "(begin (if\n" // this if needs regrouping. " (begin\n" " (set! s5-0 a0-0)\n" // s5-0 is the thing to check " (set! gp-0 a1-0)\n" // gp-0 is the list @@ -561,7 +581,8 @@ TEST_F(FormRegressionTest, FunctionCall) { " (!= gp-0 v1-3)\n" // IF CONDITION " )\n" " (set! v0-1 gp-0)\n" // not empty, so return the result - " )"; // the (set! v0 #f) from the if is added later. + " )" // the (set! v0 #f) from the if is added later. + " (ret-value v0-1))\n"; test_no_expr(func, type, expected, true); } @@ -732,6 +753,7 @@ TEST_F(FormRegressionTest, NestedAndOr) { " )\n" " (set! v1-12 '#f)\n" " (set! v0-1 gp-0)\n" + " (ret-value v0-1)\n" " )"; test_no_expr(func, type, expected, true); } @@ -771,7 +793,7 @@ TEST_F(FormRegressionTest, NewMethod) { " daddiu sp, sp, 32"; std::string type = "(function symbol type int inline-array-class)"; std::string expected = - "(when\n" + "(begin (when\n" " (begin\n" " (set! gp-0 a2-0)\n" // gp-0 is size " (set! v1-0 object)\n" @@ -787,7 +809,8 @@ TEST_F(FormRegressionTest, NewMethod) { " )\n" " (s.w! v0-0 gp-0)\n" // store size " (s.w! (+ v0-0 4) gp-0)\n" - " )"; + " )" + " (ret-value v0-0))\n"; test_no_expr(func, type, expected, false, "inline-array-class"); } @@ -823,7 +846,7 @@ TEST_F(FormRegressionTest, Recursive) { " daddiu sp, sp, 32"; std::string type = "(function int int)"; std::string expected = - "(cond\n" + "(begin (cond\n" " ((begin (set! gp-0 a0-0) (set! v1-0 1) (= gp-0 v1-0)) (set! v0-0 1))\n" // base " (else\n" " (set! t9-0 fact)\n" // recurse! @@ -831,7 +854,8 @@ TEST_F(FormRegressionTest, Recursive) { " (set! v0-1 (call! a0-1))\n" " (set! v0-0 (*.si gp-0 v0-1))\n" // not quite a tail call... " )\n" - " )"; + " )" + " (ret-value v0-0))\n"; test_no_expr(func, type, expected, false); } @@ -866,6 +890,7 @@ TEST_F(FormRegressionTest, TypeOf) { " (set! v1-1 (type-of a0-0))\n" " (set! t9-0 (-> v1-1 method-table 2))\n" // print method. " (set! v0-0 (call! a0-0))\n" + " (ret-value v0-0)\n" " )"; test_no_expr(func, type, expected, false); } \ No newline at end of file diff --git a/test/decompiler/test_FormExpressionBuild.cpp b/test/decompiler/test_FormExpressionBuild.cpp index 9850fbeaf..2ea1f8c63 100644 --- a/test/decompiler/test_FormExpressionBuild.cpp +++ b/test/decompiler/test_FormExpressionBuild.cpp @@ -462,10 +462,129 @@ TEST_F(FormRegressionTest, ExprBasicTypeP) { // don't plan on supporting this. " (if\n" " (= v1-0 a1-0)\n" - " (return ((begin (set! v1-1 '#t) (set! v0-0 v1-1))) ((set! v1-0 0)))\n" + " (return '#t (set! v1-0 0))\n" " )\n" " )\n" " '#f\n" " )"; test_with_expr(func, type, expected); +} + +TEST_F(FormRegressionTest, ExprTypeTypep) { + std::string func = + " sll r0, r0, 0\n" + "L280:\n" + " lw v1, object(s7)\n" + + "L281:\n" + " bne a0, a1, L282\n" + " or a2, s7, r0\n" + + " daddiu v1, s7, #t\n" + " or v0, v1, r0\n" + " beq r0, r0, L284\n" + " sll r0, r0, 0\n" + + " or v1, r0, r0\n" + + "L282:\n" + " lwu a0, 4(a0)\n" + " dsubu a2, a0, v1\n" + " daddiu a3, s7, 8\n" + " movn a3, s7, a2\n" + " bnel s7, a3, L283\n" + " or a2, a3, r0\n" + + " daddiu a2, s7, 8\n" + " movn a2, s7, a0\n" + + "L283:\n" + " beq s7, a2, L281\n" + " sll r0, r0, 0\n" + + " or v0, s7, r0\n" + + "L284:\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function type type symbol)"; + + std::string expected = + "(begin\n" + " (set! v1-0 object)\n" + " (until\n" + " (truthy\n" + " (or\n" + " (begin (set! a0-0 (-> a0-0 parent)) (truthy (= a0-0 v1-0)))\n" // set! as value. + " (zero? a0-0)\n" + " )\n" + " )\n" + " (if (= a0-0 a1-0) (return '#t (set! v1-0 0)))\n" + " )\n" + " '#f\n" + " )"; + test_with_expr(func, type, expected, false, ""); +} + +TEST_F(FormRegressionTest, ExprFindParentMethod) { + std::string func = + " sll r0, r0, 0\n" + "L275:\n" + " dsll v1, a1, 2\n" + " daddu v1, v1, a0\n" + " lwu v1, 16(v1)\n" + + "L276:\n" + " lw a2, object(s7)\n" + " bne a0, a2, L277\n" + " or a2, s7, r0\n" + + " lw v1, nothing(s7)\n" + " or v0, v1, r0\n" + " beq r0, r0, L279\n" + " sll r0, r0, 0\n" + + " or v1, r0, r0\n" + + "L277:\n" + " lwu a0, 4(a0)\n" + " dsll a2, a1, 2\n" + " daddu a2, a2, a0\n" + + " lwu v0, 16(a2)\n" + " bne v0, r0, L278\n" + " or a2, s7, r0\n" + + " lw v1, nothing(s7)\n" + " or v0, v1, r0\n" + " beq r0, r0, L279\n" + " sll r0, r0, 0\n" + + " or v1, r0, r0\n" + + "L278:\n" + " beq v0, v1, L276\n" + " sll r0, r0, 0\n" + + " or v1, s7, r0\n" + + "L279:\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function type int function)"; + + std::string expected = + "(begin\n" + " (set! v1-2 (-> a0-0 methods a1-0))\n" + " (until\n" + " (!= v0-0 v1-2)\n" + " (if (= a0-0 object) (return nothing (set! v1-2 0)))\n" + " (set! a0-0 (-> a0-0 parent))\n" + " (set! v0-0 (-> a0-0 methods a1-0))\n" + " (if (zero? v0-0) (return nothing (set! v1-2 0)))\n" + " )\n" + " (set! v1-5 '#f)\n" + " v0-0\n" + " )"; + test_with_expr(func, type, expected, false, ""); } \ No newline at end of file