diff --git a/common/versions.h b/common/versions.h index 52316f8275..e0342f17da 100644 --- a/common/versions.h +++ b/common/versions.h @@ -12,7 +12,7 @@ namespace versions { constexpr s32 GOAL_VERSION_MAJOR = 0; constexpr s32 GOAL_VERSION_MINOR = 6; -constexpr int DECOMPILER_VERSION = 2; +constexpr int DECOMPILER_VERSION = 4; // these versions are from the game constexpr u32 ART_FILE_VERSION = 6; diff --git a/decompiler/IR2/AtomicOp.cpp b/decompiler/IR2/AtomicOp.cpp index 1990be6b57..e95c6275d4 100644 --- a/decompiler/IR2/AtomicOp.cpp +++ b/decompiler/IR2/AtomicOp.cpp @@ -1266,14 +1266,18 @@ void CallOp::collect_vars(VariableSet& vars) const { // ConditionalMoveFalseOp ///////////////////////////// -ConditionalMoveFalseOp::ConditionalMoveFalseOp(Variable dst, Variable src, bool on_zero, int my_idx) - : AtomicOp(my_idx), m_dst(dst), m_src(src), m_on_zero(on_zero) {} +ConditionalMoveFalseOp::ConditionalMoveFalseOp(Variable dst, + Variable src, + Variable old_value, + bool on_zero, + int my_idx) + : AtomicOp(my_idx), m_dst(dst), m_src(src), m_old_value(old_value), m_on_zero(on_zero) {} goos::Object ConditionalMoveFalseOp::to_form(const std::vector& labels, const Env& env) const { (void)labels; return pretty_print::build_list(m_on_zero ? "cmove-#f-zero" : "cmove-#f-nonzero", - m_dst.to_form(env), m_src.to_form(env)); + m_dst.to_form(env), m_src.to_form(env), m_old_value.to_form(env)); } bool ConditionalMoveFalseOp::operator==(const AtomicOp& other) const { @@ -1283,7 +1287,8 @@ bool ConditionalMoveFalseOp::operator==(const AtomicOp& other) const { auto po = dynamic_cast(&other); assert(po); - return m_dst == po->m_dst && m_src == po->m_src && m_on_zero == po->m_on_zero; + return m_dst == po->m_dst && m_src == po->m_src && m_on_zero == po->m_on_zero && + m_old_value == po->m_old_value; } bool ConditionalMoveFalseOp::is_sequence_point() const { @@ -1297,11 +1302,13 @@ Variable ConditionalMoveFalseOp::get_set_destination() const { void ConditionalMoveFalseOp::update_register_info() { m_write_regs.push_back(m_dst.reg()); m_read_regs.push_back(m_src.reg()); + m_read_regs.push_back(m_old_value.reg()); } void ConditionalMoveFalseOp::collect_vars(VariableSet& vars) const { vars.insert(m_dst); vars.insert(m_src); + vars.insert(m_old_value); } bool get_as_reg_offset(const SimpleExpression& expr, IR2_RegOffset* out) { diff --git a/decompiler/IR2/AtomicOp.h b/decompiler/IR2/AtomicOp.h index e75eb0ebc6..4623571b05 100644 --- a/decompiler/IR2/AtomicOp.h +++ b/decompiler/IR2/AtomicOp.h @@ -611,7 +611,7 @@ class CallOp : public AtomicOp { */ class ConditionalMoveFalseOp : public AtomicOp { public: - ConditionalMoveFalseOp(Variable dst, Variable src, bool on_zero, int my_idx); + ConditionalMoveFalseOp(Variable dst, Variable src, Variable old_value, bool on_zero, 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; @@ -624,7 +624,7 @@ class ConditionalMoveFalseOp : public AtomicOp { void collect_vars(VariableSet& vars) const override; private: - Variable m_dst, m_src; + Variable m_dst, m_src, m_old_value; bool m_on_zero; }; diff --git a/decompiler/IR2/AtomicOpForm.cpp b/decompiler/IR2/AtomicOpForm.cpp index 714ac0bbf5..23168d97eb 100644 --- a/decompiler/IR2/AtomicOpForm.cpp +++ b/decompiler/IR2/AtomicOpForm.cpp @@ -339,6 +339,7 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { for (auto& x : rd.tokens) { tokens.push_back(to_token(x)); } + auto load = pool.alloc_single_element_form(nullptr, source, rd.addr_of, tokens); return pool.alloc_element(m_dst, load, true); @@ -414,9 +415,7 @@ FormElement* CallOp::get_as_form(FormPool& pool, const Env& env) const { } FormElement* ConditionalMoveFalseOp::get_as_form(FormPool& pool, const Env&) const { - auto source = - pool.alloc_single_element_form(nullptr, SimpleAtom::make_var(m_src)); - return pool.alloc_element(m_dst, source, m_on_zero); + return pool.alloc_element(m_dst, m_old_value, m_src, m_on_zero); } FormElement* FunctionEndOp::get_as_form(FormPool& pool, const Env&) const { diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index e8e0943e1b..02ff0e4a6c 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -258,6 +258,12 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, return TP_Type::make_partial_dyanmic_vtable_access(); } + if (arg1_type.is_integer_constant() && + arg0_type.kind == TP_Type::Kind::PRODUCT_WITH_CONSTANT) { + return TP_Type::make_from_integer_constant_plus_product( + arg1_type.get_integer_constant(), arg0_type.typespec(), arg0_type.get_multiplier()); + } + if (arg1_type.is_integer_constant() && is_int_or_uint(dts, arg0_type)) { return TP_Type::make_from_integer_constant_plus_var(arg1_type.get_integer_constant(), arg0_type.typespec()); @@ -268,6 +274,28 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, break; } + if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR_MULT && m_kind == Kind::ADD) { + FieldReverseLookupInput rd_in; + rd_in.offset = arg0_type.get_add_int_constant(); + rd_in.stride = arg0_type.get_mult_int_constant(); + rd_in.base_type = arg1_type.typespec(); + auto out = env.dts->ts.reverse_field_lookup(rd_in); + if (out.success) { + return TP_Type::make_from_ts(coerce_to_reg_type(out.result_type)); + } + } + + if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR && m_kind == Kind::ADD) { + FieldReverseLookupInput rd_in; + rd_in.offset = arg0_type.get_integer_constant(); + rd_in.stride = 1; + rd_in.base_type = arg1_type.typespec(); + auto out = env.dts->ts.reverse_field_lookup(rd_in); + if (out.success) { + return TP_Type::make_from_ts(coerce_to_reg_type(out.result_type)); + } + } + if (arg0_type == arg1_type && is_int_or_uint(dts, arg0_type)) { // both are the same type and both are int/uint, so we assume that we're doing integer math. // we strip off any weird things like multiplication or integer constant. diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp index 786d70cb09..a102f5c49d 100644 --- a/decompiler/IR2/Form.cpp +++ b/decompiler/IR2/Form.cpp @@ -971,29 +971,26 @@ void TypeOfElement::get_modified_regs(RegSet&) const {} ///////////////////////////// ConditionalMoveFalseElement::ConditionalMoveFalseElement(Variable _dest, - Form* _source, + Variable _old_value, + Variable _source, bool _on_zero) - : dest(_dest), source(_source), on_zero(_on_zero) { - source->parent_element = this; -} + : dest(_dest), old_value(_old_value), source(_source), on_zero(_on_zero) {} goos::Object ConditionalMoveFalseElement::to_form_internal(const Env& env) const { return pretty_print::build_list(on_zero ? "cmove-#f-zero" : "cmove-#f-nonzero", dest.to_form(env), - source->to_form(env)); + source.to_form(env), old_value.to_form(env)); } void ConditionalMoveFalseElement::apply(const std::function& f) { f(this); - source->apply(f); } -void ConditionalMoveFalseElement::apply_form(const std::function& f) { - source->apply_form(f); -} +void ConditionalMoveFalseElement::apply_form(const std::function&) {} void ConditionalMoveFalseElement::collect_vars(VariableSet& vars) const { vars.insert(dest); - source->collect_vars(vars); + vars.insert(old_value); + vars.insert(source); } void ConditionalMoveFalseElement::get_modified_regs(RegSet& regs) const { diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index 64134e932f..6c776016b2 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -719,9 +719,10 @@ class TypeOfElement : public FormElement { class ConditionalMoveFalseElement : public FormElement { public: Variable dest; - Form* source = nullptr; + Variable old_value; + Variable source; bool on_zero = false; - ConditionalMoveFalseElement(Variable _dest, Form* _source, bool _on_zero); + ConditionalMoveFalseElement(Variable _dest, Variable _old_value, Variable _source, bool _on_zero); goos::Object to_form_internal(const Env& env) const override; void apply(const std::function& f) override; void apply_form(const std::function& f) override; diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index c50d2f857e..a4d06e901e 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -465,6 +465,40 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, throw std::runtime_error("Failed to match for stride 1 address access with add."); } } + } else if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR_MULT) { + // try to see if this is valid, from the type system. + FieldReverseLookupInput input; + input.offset = arg0_type.get_add_int_constant(); + input.stride = arg0_type.get_mult_int_constant(); + input.base_type = arg1_type.typespec(); + auto out = env.dts->ts.reverse_field_lookup(input); + if (out.success) { + // it is. now we have to modify things + // first, look for the index + auto arg0_matcher = + Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), + {Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), + {Matcher::integer(input.stride), Matcher::any(0)}), + Matcher::integer(input.offset)}); + auto match_result = match(arg0_matcher, args.at(0)); + if (match_result.matched) { + bool used_index = false; + std::vector tokens; + for (auto& tok : out.tokens) { + if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) { + assert(!used_index); + used_index = true; + tokens.push_back(DerefToken::make_int_expr(match_result.maps.forms.at(0))); + } else { + tokens.push_back(to_token(tok)); + } + } + result->push_back(pool.alloc_element(args.at(1), out.addr_of, tokens)); + return; + } else { + throw std::runtime_error("Failed to match for stride (non power 2) with add"); + } + } } } @@ -955,6 +989,16 @@ void FunctionCallElement::update_from_stack(const Env& env, } auto unsafe = stack.unsafe_peek(Register(Reg::GPR, Reg::A0)); + if (!unsafe) { + if (!stack.is_root()) { + fmt::print("STACK:\n{}\n\n", stack.print(env)); + throw std::runtime_error("Peek got to back and not root stack"); + } + // failed to peek by reaching the end AND root stack, means we just take the function + // argument. + assert(false); // want to test this before enabling. + unsafe = mr.maps.forms.at(0); + } if (unsafe) { if (!unsafe->try_as_single_element()) { throw std::runtime_error( @@ -1160,7 +1204,7 @@ void DerefElement::update_from_stack(const Env& env, void UntilElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { for (auto form : {condition, body}) { - FormStack temp_stack; + FormStack temp_stack(false); for (auto& entry : form->elts()) { entry->push_to_stack(env, pool, temp_stack); } @@ -1176,7 +1220,7 @@ void UntilElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stac void WhileElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { for (auto form : {condition, body}) { - FormStack temp_stack; + FormStack temp_stack(false); for (auto& entry : form->elts()) { entry->push_to_stack(env, pool, temp_stack); } @@ -1199,14 +1243,14 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack& } for (auto& entry : entries) { for (auto form : {entry.condition, entry.body}) { - FormStack temp_stack; + FormStack temp_stack(false); for (auto& elt : form->elts()) { elt->push_to_stack(env, pool, temp_stack); } std::vector new_entries; if (form == entry.body && used_as_value) { - new_entries = temp_stack.rewrite_to_get_var(pool, final_destination, env); + new_entries = rewrite_to_get_var(temp_stack, pool, final_destination); } else { new_entries = temp_stack.rewrite(pool); } @@ -1246,6 +1290,38 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac std::optional last_var; bool rewrite_as_set = true; + // process conditions and bodies + for (auto& entry : entries) { + for (auto form : {entry.condition, entry.body}) { + FormStack temp_stack(false); + for (auto& elt : form->elts()) { + elt->push_to_stack(env, pool, temp_stack); + } + + std::vector new_entries; + new_entries = temp_stack.rewrite(pool); + + form->clear(); + for (auto e : new_entries) { + form->push_back(e); + } + } + } + + // process else. + FormStack temp_stack(false); + for (auto& elt : else_ir->elts()) { + elt->push_to_stack(env, pool, temp_stack); + } + + std::vector new_entries; + new_entries = temp_stack.rewrite(pool); + + else_ir->clear(); + for (auto e : new_entries) { + else_ir->push_back(e); + } + // collect all forms which should write the output. std::vector write_output_forms; for (const auto& entry : entries) { @@ -1280,44 +1356,12 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac } } - // process everything. - for (auto& entry : entries) { - for (auto form : {entry.condition, entry.body}) { - FormStack temp_stack; - for (auto& elt : form->elts()) { - elt->push_to_stack(env, pool, temp_stack); - } - - std::vector new_entries; - if (form == entry.body && rewrite_as_set && !set_unused) { - new_entries = temp_stack.rewrite_to_get_var(pool, *last_var, env); - } else { - new_entries = temp_stack.rewrite(pool); - } - - form->clear(); - for (auto e : new_entries) { - form->push_back(e); - } - } - } - - // process else. - FormStack temp_stack; - for (auto& elt : else_ir->elts()) { - elt->push_to_stack(env, pool, temp_stack); - } - - std::vector new_entries; + // rewrite extra sets as needed. if (rewrite_as_set && !set_unused) { - new_entries = temp_stack.rewrite_to_get_var(pool, *last_var, env); - } else { - new_entries = temp_stack.rewrite(pool); - } - - else_ir->clear(); - for (auto e : new_entries) { - else_ir->push_back(e); + for (auto& entry : entries) { + rewrite_to_get_var(entry.body->elts(), pool, *last_var); + } + rewrite_to_get_var(else_ir->elts(), pool, *last_var); } // raise expression. @@ -1360,14 +1404,14 @@ void ShortCircuitElement::push_to_stack(const Env& env, FormPool& pool, FormStac } for (int i = 0; i < int(entries.size()); i++) { auto& entry = entries.at(i); - FormStack temp_stack; + FormStack temp_stack(false); 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); + new_entries = rewrite_to_get_var(temp_stack, pool, final_result); } else { new_entries = temp_stack.rewrite(pool); } @@ -1406,14 +1450,14 @@ void ShortCircuitElement::update_from_stack(const Env& env, } for (int i = 0; i < int(entries.size()); i++) { auto& entry = entries.at(i); - FormStack temp_stack; + FormStack temp_stack(false); 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); + new_entries = rewrite_to_get_var(temp_stack, pool, final_result); } else { new_entries = temp_stack.rewrite(pool); } @@ -1629,13 +1673,13 @@ void ConditionElement::update_from_stack(const Env& env, } void ReturnElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { - FormStack temp_stack; + FormStack temp_stack(false); 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); + new_entries = rewrite_to_get_var(temp_stack, pool, env.end_var()); return_code->clear(); for (auto e : new_entries) { @@ -1865,7 +1909,33 @@ void ArrayFieldAccess::update_from_stack(const Env& env, auto deref = pool.alloc_element(base, false, tokens); result->push_back(deref); } else { - throw std::runtime_error("Not power of two case, not yet implemented (offset)"); + // (+ v0-0 (the-as uint (* 12 (+ a3-0 -1)))) + auto mult_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), + {Matcher::integer(m_expected_stride), Matcher::any(0)}); + mult_matcher = Matcher::match_or({Matcher::cast("uint", mult_matcher), mult_matcher}); + auto add_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), + {Matcher::any(1), mult_matcher}); + + auto mr = match(add_matcher, new_val); + if (!mr.matched) { + throw std::runtime_error("Failed to match non-power of two case: " + + new_val->to_string(env)); + } + + auto base = mr.maps.forms.at(1); + auto idx = mr.maps.forms.at(0); + + assert(idx && base); + + std::vector tokens = m_deref_tokens; + for (auto& x : tokens) { + if (x.kind() == DerefToken::Kind::EXPRESSION_PLACEHOLDER) { + x = DerefToken::make_int_expr(idx); + } + } + + auto deref = pool.alloc_element(base, false, tokens); + result->push_back(deref); } } } @@ -1904,8 +1974,29 @@ void EmptyElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { stack.push_form_element(this, true); } -void ConditionalMoveFalseElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { - stack.push_form_element(this, true); +bool is_symbol_true(const Form* form) { + auto as_simple = dynamic_cast(form->try_as_single_element()); + if (as_simple && as_simple->expr().is_identity() && as_simple->expr().get_arg(0).is_sym_ptr() && + as_simple->expr().get_arg(0).get_str() == "#t") { + return true; + } + return false; +} + +void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { + // pop the value and the original + auto popped = pop_to_forms({old_value, source}, env, pool, stack, true); + if (!is_symbol_true(popped.at(0))) { + throw std::runtime_error("Got unrecognized ConditionalMoveFalseElement original: " + + popped.at(0)->to_string(env)); + } + stack.push_value_to_reg(dest, + pool.alloc_single_element_form( + nullptr, + GenericOperator::make_compare(on_zero ? IR2_Condition::Kind::NONZERO + : IR2_Condition::Kind::ZERO), + std::vector{popped.at(1)}), + true); } void SimpleAtomElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { diff --git a/decompiler/IR2/FormStack.cpp b/decompiler/IR2/FormStack.cpp index ee59b63c94..dab8a3d4fa 100644 --- a/decompiler/IR2/FormStack.cpp +++ b/decompiler/IR2/FormStack.cpp @@ -75,8 +75,9 @@ void FormStack::push_form_element(FormElement* elt, bool sequence_point) { Form* FormStack::pop_reg(const Variable& var, const RegSet& barrier, const Env& env, - bool allow_side_effects) { - return pop_reg(var.reg(), barrier, env, allow_side_effects); + bool allow_side_effects, + int begin_idx) { + return pop_reg(var.reg(), barrier, env, allow_side_effects, begin_idx); } namespace { @@ -91,10 +92,15 @@ bool nonempty_intersection(const RegSet& a, const RegSet& b) { Form* FormStack::pop_reg(Register reg, const RegSet& barrier, const Env& env, - bool allow_side_effects) { + bool allow_side_effects, + int begin_idx) { (void)env; // keep this for easy debugging. RegSet modified; - for (size_t i = m_stack.size(); i-- > 0;) { + size_t begin = m_stack.size(); + if (begin_idx >= 0) { + begin = begin_idx; + } + for (size_t i = begin; i-- > 0;) { auto& entry = m_stack.at(i); if (entry.active) { if (entry.destination.has_value() && entry.destination->reg() == reg) { @@ -111,7 +117,7 @@ Form* FormStack::pop_reg(Register reg, assert(entry.source); if (entry.non_seq_source.has_value()) { assert(entry.sequence_point == false); - auto result = pop_reg(entry.non_seq_source->reg(), barrier, env, allow_side_effects); + auto result = pop_reg(entry.non_seq_source->reg(), barrier, env, allow_side_effects, i); if (result) { return result; } @@ -151,12 +157,12 @@ Form* FormStack::unsafe_peek(Register reg) { for (size_t i = m_stack.size(); i-- > 0;) { auto& entry = m_stack.at(i); if (entry.active) { - return nullptr; + throw std::runtime_error("Failed to unsafe peek 1"); } entry.source->get_modified_regs(modified); if (modified.find(reg) != modified.end()) { - return nullptr; + throw std::runtime_error("Failed to unsafe peek 2"); } if (entry.destination.has_value() && entry.destination->reg() == reg) { @@ -186,15 +192,9 @@ std::vector FormStack::rewrite(FormPool& pool) { return result; } -std::vector FormStack::rewrite_to_get_var(FormPool& pool, - const Variable& var, - const Env&) { - // first, rewrite as normal. - auto default_result = rewrite(pool); - - // try a few different ways to "naturally" rewrite this so the value of the form is the - // value in the given register. - +void rewrite_to_get_var(std::vector& default_result, + FormPool& pool, + const Variable& var) { auto last_op_as_set = dynamic_cast(default_result.back()); if (last_op_as_set && last_op_as_set->dst().reg() == var.reg()) { default_result.pop_back(); @@ -202,10 +202,17 @@ std::vector FormStack::rewrite_to_get_var(FormPool& pool, form->parent_form = nullptr; // will get set later, this makes it obvious if I forget. default_result.push_back(form); } - return default_result; } else { default_result.push_back(pool.alloc_element(SimpleAtom::make_var(var))); - return default_result; } } + +std::vector rewrite_to_get_var(FormStack& stack, + FormPool& pool, + const Variable& var) { + auto default_result = stack.rewrite(pool); + rewrite_to_get_var(default_result, pool, 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 cfa7482a67..b00134f8fe 100644 --- a/decompiler/IR2/FormStack.h +++ b/decompiler/IR2/FormStack.h @@ -12,7 +12,7 @@ class Form; */ class FormStack { public: - FormStack() = default; + explicit FormStack(bool is_root_stack) : m_is_root_stack(is_root_stack) {} void push_value_to_reg(Variable var, Form* value, bool sequence_point, @@ -25,13 +25,18 @@ class FormStack { Form* pop_reg(const Variable& var, const RegSet& barrier, const Env& env, - bool allow_side_effects); - Form* pop_reg(Register reg, const RegSet& barrier, const Env& env, bool allow_side_effects); + bool allow_side_effects, + int begin_idx = -1); + Form* pop_reg(Register reg, + const RegSet& barrier, + const Env& env, + bool allow_side_effects, + int begin_idx = -1); Form* unsafe_peek(Register reg); bool is_single_expression(); std::vector rewrite(FormPool& pool); - std::vector rewrite_to_get_var(FormPool& pool, const Variable& var, const Env& env); std::string print(const Env& env); + bool is_root() const { return m_is_root_stack; } private: struct StackEntry { @@ -49,5 +54,11 @@ class FormStack { std::string print(const Env& env) const; }; std::vector m_stack; + bool m_is_root_stack = false; }; + +void rewrite_to_get_var(std::vector& default_result, + FormPool& pool, + const Variable& var); +std::vector rewrite_to_get_var(FormStack& stack, FormPool& pool, const Variable& var); } // namespace decompiler diff --git a/decompiler/analysis/atomic_op_builder.cpp b/decompiler/analysis/atomic_op_builder.cpp index f9bfb5cc41..8f888ee9f4 100644 --- a/decompiler/analysis/atomic_op_builder.cpp +++ b/decompiler/analysis/atomic_op_builder.cpp @@ -603,9 +603,9 @@ std::unique_ptr convert_sd_1(const Instruction& i0, int idx) { // movn or movz std::unique_ptr convert_cmov_1(const Instruction& i0, int idx) { if (i0.get_src(0).is_reg(rs7())) { - return std::make_unique(make_dst_var(i0, idx), - make_src_var(i0.get_src(1).get_reg(), idx), - i0.kind == InstructionKind::MOVZ, idx); + return std::make_unique( + make_dst_var(i0, idx), make_src_var(i0.get_src(1).get_reg(), idx), + make_src_var(i0.get_dst(0).get_reg(), idx), i0.kind == InstructionKind::MOVZ, idx); } else { return nullptr; } diff --git a/decompiler/analysis/expression_build.cpp b/decompiler/analysis/expression_build.cpp index 3790e66411..8a8e05f13f 100644 --- a/decompiler/analysis/expression_build.cpp +++ b/decompiler/analysis/expression_build.cpp @@ -61,7 +61,7 @@ bool convert_to_expressions(Form* top_level_form, // fmt::print("Before anything:\n{}\n", // pretty_print::to_string(top_level_form->to_form(f.ir2.env))); try { - FormStack stack; + FormStack stack(true); for (auto& entry : top_level_form->elts()) { // fmt::print("push {} to stack\n", entry->to_form(f.ir2.env).print()); entry->push_to_stack(f.ir2.env, pool, stack); @@ -70,7 +70,7 @@ bool convert_to_expressions(Form* top_level_form, std::vector new_entries; if (f.type.last_arg() != TypeSpec("none")) { auto return_var = f.ir2.atomic_ops->end_op().return_var(); - new_entries = stack.rewrite_to_get_var(pool, return_var, f.ir2.env); + new_entries = rewrite_to_get_var(stack, pool, return_var); 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)) { diff --git a/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc b/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc index 193ec033e6..cccba30b1a 100644 --- a/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc @@ -77,7 +77,7 @@ "(method 14 dead-pool)":[ // bug in game! - [25, ["v1", "(pointer process-tree)"]], + [24, ["v1", "(pointer process-tree)"]], [30, ["s4", "(pointer process-tree)"]] ], diff --git a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc index 1e0687ab9f..9d9530b7f3 100644 --- a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc @@ -194,6 +194,10 @@ "vars":{"v1-1":"in-goal-mem"} }, + "(method 23 dead-pool-heap)":{ + "args":["this", "rec"] + }, + "seek":{ "args":["x", "target", "diff"], "vars":{"f2-0":"err"} diff --git a/decompiler/util/DecompilerTypeSystem.cpp b/decompiler/util/DecompilerTypeSystem.cpp index c2d991806e..36bc3815f5 100644 --- a/decompiler/util/DecompilerTypeSystem.cpp +++ b/decompiler/util/DecompilerTypeSystem.cpp @@ -272,6 +272,11 @@ TP_Type DecompilerTypeSystem::tp_lca(const TP_Type& existing, return TP_Type::make_from_ts("int"); } + case TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR_MULT: + // a bit lazy here, but I don't think you can ever merge these. + *changed = true; + return TP_Type::make_from_ts("int"); + case TP_Type::Kind::METHOD: // never allow this to remain method *changed = true; diff --git a/decompiler/util/TP_Type.cpp b/decompiler/util/TP_Type.cpp index c4c28aa237..2cace93b04 100644 --- a/decompiler/util/TP_Type.cpp +++ b/decompiler/util/TP_Type.cpp @@ -51,6 +51,8 @@ std::string TP_Type::print() const { return fmt::format("", m_int); case Kind::INTEGER_CONSTANT_PLUS_VAR: return fmt::format("", m_int, m_ts.print()); + case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT: + return fmt::format("", m_int, m_extra_multiplier, m_ts.print()); case Kind::DYNAMIC_METHOD_ACCESS: return fmt::format(""); case Kind::METHOD: @@ -95,6 +97,9 @@ bool TP_Type::operator==(const TP_Type& other) const { return m_int == other.m_int && m_ts == other.m_ts; case Kind::DYNAMIC_METHOD_ACCESS: return true; + 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::INVALID: default: assert(false); @@ -133,6 +138,7 @@ TypeSpec TP_Type::typespec() const { case Kind::INTEGER_CONSTANT: return TypeSpec("int"); case Kind::INTEGER_CONSTANT_PLUS_VAR: + case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT: return m_ts; case Kind::DYNAMIC_METHOD_ACCESS: return TypeSpec("object"); diff --git a/decompiler/util/TP_Type.h b/decompiler/util/TP_Type.h index 5fbe223a96..3de38aaa59 100644 --- a/decompiler/util/TP_Type.h +++ b/decompiler/util/TP_Type.h @@ -26,9 +26,9 @@ class TP_Type { STRING_CONSTANT, // a string that's part of the string pool FORMAT_STRING, // a string with a given number of format arguments INTEGER_CONSTANT, // a constant integer. - INTEGER_CONSTANT_PLUS_VAR, // constant + variable. used in stuff like (&-> obj inline-val-arr - // x) - DYNAMIC_METHOD_ACCESS, // partial access into a + INTEGER_CONSTANT_PLUS_VAR, // constant + variable. for dynamic addr of + INTEGER_CONSTANT_PLUS_VAR_MULT, // like var + 100 + 12 * var2 + DYNAMIC_METHOD_ACCESS, // partial access into a METHOD, INVALID } kind = Kind::UNINITIALIZED; @@ -130,6 +130,17 @@ class TP_Type { return result; } + static TP_Type make_from_integer_constant_plus_product(int64_t constant, + const TypeSpec& var_type, + int64_t multiplier) { + TP_Type result; + result.kind = Kind::INTEGER_CONSTANT_PLUS_VAR_MULT; + result.m_int = constant; + result.m_extra_multiplier = multiplier; + result.m_ts = var_type; + return result; + } + static TP_Type make_from_product(int64_t multiplier, bool is_signed) { TP_Type result; result.kind = Kind::PRODUCT_WITH_CONSTANT; @@ -189,10 +200,22 @@ class TP_Type { return m_int; } + u64 get_add_int_constant() const { + assert(kind == Kind::INTEGER_CONSTANT_PLUS_VAR_MULT); + return m_int; + } + + u64 get_mult_int_constant() const { + assert(kind == Kind::INTEGER_CONSTANT_PLUS_VAR_MULT); + return m_extra_multiplier; + } + private: TypeSpec m_ts; std::string m_str; int64_t m_int = 0; + + int64_t m_extra_multiplier = 0; }; struct TypeState { diff --git a/doc/decompiler_changelog.md b/doc/decompiler_changelog.md index a29e634448..dab79112cd 100644 --- a/doc/decompiler_changelog.md +++ b/doc/decompiler_changelog.md @@ -23,4 +23,16 @@ - Fixed a bug where integer `abs` appeared instead of `fabs`. - Some support for float -> integer conversions, but it is not 100% yet. - Eliminate inserted coloring moves for function arguments that use `mtc1`. -- Support for `>=` for signed numbers. \ No newline at end of file +- Support for `>=` for signed numbers. + +## Version 4 +- Fix bug in decoding of `vdiv`, `vsqrt`, and `vrsqrt` instructions +- Support for virtual method calls (may not recognize 100% of cases yet) +- Support for "weird" new calls +- Fixed a few "update from stack NYI" errors +- Array access recognized in more cases with power of two stride. +- Support for getting the address of something in an inline array with a stride that's not 1 or a power of 2. +- Fixed a bug in unscrambling coloring moves which sometimes caused the wrong values to be used. +- Improved nested cond rewriting to eliminate temporaries in more cases when used as a value +- Support `zero?` and `nonzero?` which are evaluated to GOAL booleans. +- Fix bug where method calls that "passed through" `a0` from the caller were not recognized. \ No newline at end of file diff --git a/test/decompiler/test_AtomicOpBuilder.cpp b/test/decompiler/test_AtomicOpBuilder.cpp index 4971403b03..4f468fe244 100644 --- a/test/decompiler/test_AtomicOpBuilder.cpp +++ b/test/decompiler/test_AtomicOpBuilder.cpp @@ -530,8 +530,8 @@ TEST(DecompilerAtomicOpBuilder, MINS) { } TEST(DecompilerAtomicOpBuilder, MOVN) { - test_case(assembly_from_list({"movn a1, s7, a2"}), {"(cmove-#f-nonzero a1 a2)"}, {{"a1"}}, - {{"a2"}}, {{}}); + test_case(assembly_from_list({"movn a1, s7, a2"}), {"(cmove-#f-nonzero a1 a2 a1)"}, {{"a1"}}, + {{"a2", "a1"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MOVS) { @@ -539,8 +539,8 @@ TEST(DecompilerAtomicOpBuilder, MOVS) { } TEST(DecompilerAtomicOpBuilder, MOVZ) { - test_case(assembly_from_list({"movz a1, s7, a2"}), {"(cmove-#f-zero a1 a2)"}, {{"a1"}}, {{"a2"}}, - {{}}); + test_case(assembly_from_list({"movz a1, s7, a2"}), {"(cmove-#f-zero a1 a2 a1)"}, {{"a1"}}, + {{"a2", "a1"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MTC1) { diff --git a/test/decompiler/test_gkernel_decomp.cpp b/test/decompiler/test_gkernel_decomp.cpp index b5ffb91d0f..1a4408b9dc 100644 --- a/test/decompiler/test_gkernel_decomp.cpp +++ b/test/decompiler/test_gkernel_decomp.cpp @@ -945,149 +945,747 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPool) { test_with_expr(func, type, expected, false, "dead-pool"); } -// TODO - sketchy types, and likely a bug in the game. -// TEST_F(FormRegressionTest, ExprMethod14DeadPool) { +TEST_F(FormRegressionTest, ExprMethod14DeadPool) { + std::string func = + " sll r0, r0, 0\n" + " daddiu sp, sp, -64\n" + " sd ra, 0(sp)\n" + " sd fp, 8(sp)\n" + " or fp, t9, r0\n" + " sq s4, 16(sp)\n" + " sq s5, 32(sp)\n" + " sq gp, 48(sp)\n" + + " or s5, a0, r0\n" + " or gp, a1, r0\n" + " lwu s4, 16(s5)\n" + " bnel s7, s4, L223\n" + + " or v1, s7, r0\n" + + " lw v1, *debug-segment*(s7)\n" + " beql s7, v1, L223\n" + + " or v1, v1, r0\n" + + " lw v1, *debug-dead-pool*(s7)\n" + " dsubu a0, s5, v1\n" + " daddiu v1, s7, 8\n" + " movz v1, s7, a0\n" + + "L223:\n" + " beq s7, v1, L225\n" + " or v1, s7, r0\n" + + " lw a0, *debug-dead-pool*(s7)\n" + " lwu v1, -4(a0)\n" + " lwu t9, 72(v1)\n" + " or a1, gp, r0\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " or s4, v0, r0\n" + " beq s7, s4, L225\n" + " or v1, s7, r0\n" + + " lw t9, format(s7)\n" + " addiu a0, r0, 0\n" + " daddiu a1, fp, L315\n" + " or a2, gp, r0\n" + " or v1, s4, r0\n" + " beq s7, v1, L224\n" + " or a3, s7, r0\n" + + " lwu v1, 0(v1)\n" + " lwu a3, 24(v1)\n" + + "L224:\n" + " lwu t0, 0(s5)\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + "\n" + " or v1, v0, r0\n" + + "L225:\n" + " beq s7, s4, L226\n" + " sll r0, r0, 0\n" + + " lwu v1, 0(s4)\n" + " sw gp, -4(v1)\n" + " lwu v0, 0(s4)\n" + " beq r0, r0, L228\n" + " sll r0, r0, 0\n" + + "L226:\n" + " lw t9, format(s7)\n" + " addiu a0, r0, 0\n" + " daddiu a1, fp, L314\n" + " beq s7, s4, L227\n" + " or a3, s7, r0\n" + + " lwu v1, 0(s4)\n" + " lwu a3, 24(v1)\n" + + "L227:\n" + " lwu t0, 0(s5)\n" + " or a2, gp, r0\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " or v0, s7, r0\n" + + "L228:\n" + " ld ra, 0(sp)\n" + " ld fp, 8(sp)\n" + " lq gp, 48(sp)\n" + " lq s5, 32(sp)\n" + " lq s4, 16(sp)\n" + " jr ra\n" + " daddiu sp, sp, 64"; + std::string type = "(function dead-pool type int process)"; + std::string expected = + "(begin\n" + " (set! s4-0 (-> arg0 child))\n" + " (when\n" + " (and (not s4-0) *debug-segment* (!= arg0 *debug-dead-pool*))\n" + " (set! s4-0 (get-process *debug-dead-pool* arg1 arg2))\n" + " (when\n" + " s4-0\n" + " (set! t9-1 format)\n" + " (set! a0-2 0)\n" + " (set!\n" + " a1-2\n" + " \"WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%\"\n" + " )\n" + " (set! a2-1 arg1)\n" + " (set! v1-6 s4-0)\n" + " (t9-1\n" + " a0-2\n" + " a1-2\n" + " a2-1\n" + " (if v1-6 (-> (the-as (pointer process-tree) v1-6) 0 self))\n" + " (-> arg0 name)\n" + " )\n" + " )\n" + " )\n" + " (the-as\n" + " process\n" + " (cond\n" + " (s4-0\n" + " (set! (-> (the-as (pointer process-tree) s4-0) 0 type) arg1)\n" + " (-> s4-0 0)\n" + " )\n" + " (else\n" + " (format\n" + " 0\n" + " \"WARNING: ~A ~A could not be allocated, because ~A was empty.~%\"\n" + " arg1\n" + " (if s4-0 (-> s4-0 0 self))\n" + " (-> arg0 name)\n" + " )\n" + " (quote #f)\n" + " )\n" + " )\n" + " )\n" + " )"; + + // note - there's likely an actual bug here. + test_with_expr( + func, type, expected, false, "dead-pool", + {{"L315", "WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%"}, + {"L314", "WARNING: ~A ~A could not be allocated, because ~A was empty.~%"}}, + parse_hint_json("[\t\t[24, [\"v1\", \"(pointer process-tree)\"]],\n" + "\t\t[30, [\"s4\", \"(pointer process-tree)\"]]]")); +} + +TEST_F(FormRegressionTest, ExprMethod15DeadPool) { + std::string func = + " sll r0, r0, 0\n" + " daddiu sp, sp, -16\n" + " sd ra, 0(sp)\n" + + " lw t9, change-parent(s7)\n" + " or v1, a1, r0\n" + " or a1, a0, r0\n" + " or a0, v1, r0\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " ld ra, 0(sp)\n" + " jr ra\n" + " daddiu sp, sp, 16"; + std::string type = "(function dead-pool process none)"; + std::string expected = "(change-parent arg1 arg0)"; + test_with_expr(func, type, expected); +} + +TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) { + std::string func = + " sll r0, r0, 0\n" + " daddiu sp, sp, -64\n" + " sd ra, 0(sp)\n" + " sq s4, 16(sp)\n" + " sq s5, 32(sp)\n" + " sq gp, 48(sp)\n" + + " or s4, a2, r0\n" + " or s5, a3, r0\n" + " or gp, t0, r0\n" + " lw v1, object(s7)\n" + " lwu t9, 16(v1)\n" + " or v1, a1, r0\n" + " lhu a1, 8(a1)\n" + " addiu a2, r0, -16\n" + " addiu a3, r0, 12\n" + " mult3 a3, a3, s5\n" + " daddiu a3, a3, 15\n" + " and a2, a2, a3\n" + " daddu a1, a1, a2\n" + " daddu a2, a1, gp\n" + " or a1, v1, r0\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " sw s4, 0(v0)\n" + " addiu v1, r0, 256\n" + " sw v1, 4(v0)\n" + " sw s5, 28(v0)\n" + " sw s7, 8(v0)\n" + " sw s7, 12(v0)\n" + " sw s7, 16(v0)\n" + " sw v0, 24(v0)\n" + " daddiu v1, v0, 24\n" + " sw v1, 20(v0)\n" + " or v1, s5, r0\n" + " beq r0, r0, L220\n" + " sll r0, r0, 0\n" + + "L219:\n" + " daddiu v1, v1, -1\n" + " addiu a0, r0, 12\n" + " mult3 a0, a0, v1\n" + " daddiu a0, a0, 100\n" + " daddu a0, a0, v0\n" + " lw a1, *null-process*(s7)\n" + " sw a1, 0(a0)\n" + " addiu a1, r0, 12\n" + " daddiu a2, v1, 1\n" + " mult3 a1, a1, a2\n" + " daddiu a1, a1, 100\n" + " daddu a1, a1, v0\n" + " sw a1, 8(a0)\n" + + "L220:\n" + " bne v1, r0, L219\n" + " sll r0, r0, 0\n" + + " or v1, s7, r0\n" + " or v1, s7, r0\n" + " daddiu v1, v0, 100\n" + " sw v1, 96(v0)\n" + " sw s7, 76(v0)\n" + " addiu v1, r0, 12\n" + " daddiu a0, s5, -1\n" + " mult3 v1, v1, a0\n" + " daddu v1, v0, v1\n" + " sw s7, 108(v1)\n" + " daddiu v1, v0, 76\n" + " sw v1, 80(v0)\n" + " sw s7, 84(v0)\n" + " sw s7, 76(v0)\n" + " daddiu v1, v0, 76\n" + " sw v1, 48(v0)\n" + " sw s7, 52(v0)\n" + " addiu v1, r0, -16\n" + " daddiu a0, v0, 115\n" + " addiu a1, r0, 12\n" + " mult3 a1, a1, s5\n" + " daddu a0, a0, a1\n" + " and v1, v1, a0\n" + " sw v1, 60(v0)\n" + " lwu v1, 60(v0)\n" + " sw v1, 68(v0)\n" + " lwu v1, 60(v0)\n" + " daddu v1, v1, gp\n" + " sw v1, 64(v0)\n" + " lwu v1, 64(v0)\n" + " sw v1, 72(v0)\n" + " ld ra, 0(sp)\n" + " lq gp, 48(sp)\n" + " lq s5, 32(sp)\n" + " lq s4, 16(sp)\n" + " jr ra\n" + " daddiu sp, sp, 64"; + std::string type = "(function symbol type basic int int dead-pool-heap)"; + std::string expected = + "(begin\n" + " (set!\n" + " v0-0\n" + " (object-new\n" + " arg0\n" + " arg1\n" + " (the-as\n" + " int\n" + " (+\n" + " (+ (-> arg1 size) (the-as uint (logand -16 (+ (* 12 arg3) 15))))\n" + " (the-as uint arg4)\n" + " )\n" + " )\n" + " )\n" + " )\n" + " (set! (-> v0-0 name) arg2)\n" + " (set! (-> v0-0 mask) 256)\n" + " (set! (-> v0-0 allocated-length) arg3)\n" + " (set! (-> v0-0 parent) (quote #f))\n" + " (set! (-> v0-0 brother) (quote #f))\n" + " (set! (-> v0-0 child) (quote #f))\n" + " (set! (-> v0-0 self) v0-0)\n" + " (set! (-> v0-0 ppointer) (&-> v0-0 self))\n" + " (set! v1-4 arg3)\n" + " (while\n" + " (nonzero? v1-4)\n" + " (set! v1-4 (+ v1-4 -1))\n" + " (set! a0-4 (-> v0-0 process-list v1-4))\n" + " (set! (-> a0-4 process) *null-process*)\n" + " (set! (-> a0-4 next) (-> v0-0 process-list (+ v1-4 1)))\n" + " )\n" + " (set! (-> v0-0 dead-list next) (-> v0-0 process-list))\n" + " (set! (-> v0-0 alive-list process) (quote #f))\n" + " (set! (-> v0-0 process-list (+ arg3 -1) next) (quote #f))\n" + " (set! (-> v0-0 alive-list prev) (-> v0-0 alive-list))\n" + " (set! (-> v0-0 alive-list next) (quote #f))\n" + " (set! (-> v0-0 alive-list process) (quote #f))\n" + " (set! (-> v0-0 first-gap) (-> v0-0 alive-list))\n" + " (set! (-> v0-0 first-shrink) (quote #f))\n" + " (set!\n" + " (-> v0-0 heap base)\n" + " (logand\n" + " -16\n" + " (the-as int (+ (+ (the-as int v0-0) 115) (the-as uint (* 12 arg3))))\n" + " )\n" + " )\n" + " (set! (-> v0-0 heap current) (-> v0-0 heap base))\n" + " (set! (-> v0-0 heap top) (+ (-> v0-0 heap base) (the-as uint arg4)))\n" + " (set! (-> v0-0 heap top-base) (-> v0-0 heap top))\n" + " v0-0\n" + " )"; + test_with_expr(func, type, expected, false, "dead-pool-heap", {}, + parse_hint_json("[\t\t[60, [\"v0\", \"int\"]],\n" + "\t\t[61, [\"a0\", \"pointer\"], [\"v0\", \"dead-pool-heap\"]]]")); +} + +TEST_F(FormRegressionTest, ExprMethod22DeadPoolHeap) { + std::string func = + " sll r0, r0, 0\n" + " lwu v1, 0(a1)\n" + " beq s7, v1, L216\n" + " sll r0, r0, 0\n" + + " lwu v1, 0(a1)\n" + " lw v1, 68(v1)\n" + " daddiu v1, v1, -4\n" + " lw a0, process(s7)\n" + " lhu a0, 8(a0)\n" + " daddu v1, v1, a0\n" + " lwu a0, 0(a1)\n" + " daddu v0, v1, a0\n" + " beq r0, r0, L217\n" + " sll r0, r0, 0\n" + + "L216:\n" + " lwu v0, 60(a0)\n" + + "L217:\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function dead-pool-heap dead-pool-heap-rec pointer)"; + std::string expected = + "(if\n" + " (-> arg1 process)\n" + " (+\n" + " (+ (+ (-> arg1 process allocated-length) -4) (the-as int (-> process size)))\n" + " (the-as int (-> arg1 process))\n" + " )\n" + " (-> arg0 heap base)\n" + " )"; + test_with_expr(func, type, expected); +} + +TEST_F(FormRegressionTest, ExprMethod21DeadPoolHeap) { + std::string func = + " sll r0, r0, 0\n" + "L209:\n" + " lwu v1, 0(a1)\n" + " beq s7, v1, L212\n" + " sll r0, r0, 0\n" + + " lwu v1, 0(a1)\n" + " lw a2, process(s7)\n" + " lhu a2, 8(a2)\n" + " daddu v1, v1, a2\n" + " lwu a2, 0(a1)\n" + " lw a2, 68(a2)\n" + " daddu v1, v1, a2\n" + " lwu a2, 8(a1)\n" + " beq s7, a2, L210\n" + " sll r0, r0, 0\n" + + " lwu a0, 8(a1)\n" + " lwu a0, 0(a0)\n" + " dsubu v0, a0, v1\n" + " beq r0, r0, L211\n" + " sll r0, r0, 0\n" + + "L210:\n" + " lwu a0, 64(a0)\n" + " daddiu v1, v1, 4\n" + " dsubu v0, a0, v1\n" + + "L211:\n" + " beq r0, r0, L214\n" + " sll r0, r0, 0\n" + + "L212:\n" + " lwu v1, 8(a1)\n" + " beq s7, v1, L213\n" + " sll r0, r0, 0\n" + + " lwu v1, 8(a1)\n" + " lwu v1, 0(v1)\n" + " lwu a0, 60(a0)\n" + " daddiu a0, a0, 4\n" + " dsubu v0, v1, a0\n" + " beq r0, r0, L214\n" + " sll r0, r0, 0\n" + + "L213:\n" + " lwu v1, 64(a0)\n" + " lwu a0, 60(a0)\n" + " dsubu v0, v1, a0\n" + + "L214:\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function dead-pool-heap dead-pool-heap-rec int)"; + std::string expected = + "(cond\n" + " ((-> arg1 process)\n" + " (set!\n" + " v1-3\n" + " (+\n" + " (+ (-> arg1 process) (the-as uint (-> process size)))\n" + " (the-as uint (-> arg1 process allocated-length))\n" + " )\n" + " )\n" + " (if\n" + " (-> arg1 next)\n" + " (- (-> arg1 next process) (the-as uint v1-3))\n" + " (- (-> arg0 heap top) (the-as uint (+ v1-3 (the-as uint 4))))\n" + " )\n" + " )\n" + " (else\n" + " (if\n" + " (-> arg1 next)\n" + " (-\n" + " (-> arg1 next process)\n" + " (the-as uint (+ (-> arg0 heap base) (the-as uint 4)))\n" + " )\n" + " (- (-> arg0 heap top) (the-as uint (-> arg0 heap base)))\n" + " )\n" + " )\n" + " )"; + test_with_expr(func, type, expected, false, "", {}, + parse_hint_json("[\t\t[5, [\"v1\", \"pointer\"]],\n" + "\t\t[13, [\"a0\", \"pointer\"]],\n" + "\t\t[25, [\"v1\", \"pointer\"]]]")); +} + +TEST_F(FormRegressionTest, ExprMethod3DeadPoolHeap) { + std::string func = + " sll r0, r0, 0\n" + " daddiu sp, sp, -128\n" + " sd ra, 0(sp)\n" + " sd fp, 8(sp)\n" + " or fp, t9, r0\n" + " sq s0, 16(sp)\n" + " sq s1, 32(sp)\n" + " sq s2, 48(sp)\n" + " sq s3, 64(sp)\n" + " sq s4, 80(sp)\n" + " sq s5, 96(sp)\n" + " sq gp, 112(sp)\n" + + " or gp, a0, r0\n" + + // CUT HERE + + " lwu v1, 64(gp)\n" + " lwu a0, 60(gp)\n" + " dsubu s5, v1, a0\n" + " lwu v1, 80(gp)\n" + " beq s7, v1, L199\n" + " sll r0, r0, 0\n" + + " or a0, gp, r0\n" + " lwu v1, -4(a0)\n" + " lwu t9, 100(v1)\n" + " lwu a1, 80(gp)\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " or v1, v0, r0\n" + " beq r0, r0, L200\n" + " sll r0, r0, 0\n" + + "L199:\n" + " or v1, s5, r0\n" + + "L200:\n" + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L300\n" + " daddiu a2, gp, 100\n" + " dsubu a3, s5, v1\n" + " or t0, s5, r0\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " or v1, v0, r0\n" + " daddiu s5, gp, 76\n" + " addiu s4, r0, 0\n" + " beq r0, r0, L204\n" + " sll r0, r0, 0\n" + + "L201:\n" + " lwu v1, 0(s5)\n" + " beq s7, v1, L202\n" + " or v1, s7, r0\n" + + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L299\n" + " or a2, s4, r0\n" + " or a3, s5, r0\n" + " lwu t0, 0(s5)\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + "\n" + " or v1, v0, r0\n" + + "L202:\n" + " or a0, gp, r0\n" + " lwu v1, -4(a0)\n" + " lwu t9, 100(v1)\n" + " or a1, s5, r0\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + "\n" + " or s3, v0, r0\n" + " beq s3, r0, L203\n" + " or v1, s7, r0\n" + + " lw s2, format(s7)\n" + " daddiu s1, s7, #t\n" + " daddiu s0, fp, L298\n" + " or a0, gp, r0\n" + " lwu v1, -4(a0)\n" + " lwu t9, 104(v1)\n" + " or a1, s5, r0\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " or a3, v0, r0\n" + " or t9, s2, r0\n" + " or a0, s1, r0\n" + " or a1, s0, r0\n" + " or a2, s3, r0\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + "\n" + " or v1, v0, r0\n" + + "L203:\n" + " lwu s5, 8(s5)\n" + " daddiu s4, s4, 1\n" + + "L204:\n" + " bne s7, s5, L201\n" + " sll r0, r0, 0\n" + + " or v1, s7, r0\n" + " or v0, gp, r0\n" + " ld ra, 0(sp)\n" + " ld fp, 8(sp)\n" + " lq gp, 112(sp)\n" + " lq s5, 96(sp)\n" + " lq s4, 80(sp)\n" + " lq s3, 64(sp)\n" + " lq s2, 48(sp)\n" + " lq s1, 32(sp)\n" + " lq s0, 16(sp)\n" + " jr ra\n" + " daddiu sp, sp, 128"; + std::string type = "(function dead-pool-heap dead-pool-heap)"; + std::string expected = + "(begin\n" + " (set! s5-0 (- (-> arg0 heap top) (the-as uint (-> arg0 heap base))))\n" + " (set!\n" + " v1-3\n" + " (if (-> arg0 alive-list prev) (gap-size arg0 (-> arg0 alive-list prev)) s5-0)\n" + " )\n" + " (format\n" + " (quote #t)\n" + " \"~Tprocess-list[0] @ #x~X ~D/~D bytes used~%\"\n" + " (-> arg0 process-list)\n" + " (- s5-0 v1-3)\n" + " s5-0\n" + " )\n" + " (set! s5-1 (-> arg0 alive-list))\n" + " (set! s4-0 0)\n" + " (while\n" + " s5-1\n" + " (if\n" + " (-> s5-1 process)\n" + " (format\n" + " (quote #t)\n" + " \"~T [~3D] # ~A~%\"\n" + " s4-0\n" + " s5-1\n" + " (-> s5-1 process)\n" + " )\n" + " )\n" + " (set! s3-0 (gap-size arg0 s5-1))\n" + " (if\n" + " (nonzero? s3-0)\n" + " (format\n" + " (quote #t)\n" + " \"~T gap: ~D bytes @ #x~X~%\"\n" + " s3-0\n" + " (gap-location arg0 s5-1)\n" + " )\n" + " )\n" + " (set! s5-1 (-> s5-1 next))\n" + " (set! s4-0 (+ s4-0 1))\n" + " )\n" + " arg0\n" + " )"; + test_with_expr(func, type, expected, false, "", + {{"L300", "~Tprocess-list[0] @ #x~X ~D/~D bytes used~%"}, + {"L299", "~T [~3D] # ~A~%"}, + {"L298", "~T gap: ~D bytes @ #x~X~%"}}); +} + +TEST_F(FormRegressionTest, ExprMethod5DeadPoolHeap) { + std::string func = + " sll r0, r0, 0\n" + " addiu v1, r0, -4\n" + " dsubu v1, v1, a0\n" + " lwu a0, 64(a0)\n" + " daddu v0, v1, a0\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function dead-pool-heap int)"; + std::string expected = "(+ (- -4 (the-as int arg0)) (-> arg0 heap top))"; + test_with_expr(func, type, expected, false, "", {}, + parse_hint_json("[[3, [\"v1\", \"int\"], [\"a0\", \"int\"]]]")); +} + +TEST_F(FormRegressionTest, ExprMethod19DeadPoolHeap) { + std::string func = + " sll r0, r0, 0\n" + "L194:\n" + " daddiu sp, sp, -48\n" + " sd ra, 0(sp)\n" + " sq s5, 16(sp)\n" + " sq gp, 32(sp)\n" + + " or gp, a0, r0\n" + " lwu v1, 80(gp)\n" + " beq s7, v1, L195\n" + " sll r0, r0, 0\n" + + " or a0, gp, r0\n" + " lwu v1, -4(a0)\n" + " lwu t9, 96(v1)\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + "\n" + " or s5, v0, r0\n" + " or a0, gp, r0\n" + " lwu v1, -4(a0)\n" + " lwu t9, 100(v1)\n" + " lwu a1, 80(gp)\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + "\n" + " or v1, v0, r0\n" + " dsubu v0, s5, v1\n" + " beq r0, r0, L196\n" + " sll r0, r0, 0\n" + + "L195:\n" + " addiu v0, r0, 0\n" + + "L196:\n" + " ld ra, 0(sp)\n" + " lq gp, 32(sp)\n" + " lq s5, 16(sp)\n" + " jr ra\n" + " daddiu sp, sp, 48"; + std::string type = "(function dead-pool-heap int)"; + std::string expected = + "(if\n" + " (-> arg0 alive-list prev)\n" + " (- (memory-total arg0) (gap-size arg0 (-> arg0 alive-list prev)))\n" + " 0\n" + " )"; + test_with_expr(func, type, expected); +} + +TEST_F(FormRegressionTest, ExprMethod20DeadPoolHeap) { + std::string func = + " sll r0, r0, 0\n" + " lwu v1, 64(a0)\n" + " lwu a0, 60(a0)\n" + " dsubu v0, v1, a0\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function dead-pool-heap int)"; + std::string expected = "(- (-> arg0 heap top) (the-as uint (-> arg0 heap base)))"; + test_with_expr(func, type, expected); +} +// +// TEST_F(FormRegressionTest, ExprMethod25DeadPoolHeap) { // std::string func = // " sll r0, r0, 0\n" -// " daddiu sp, sp, -64\n" +// " daddiu sp, sp, -16\n" // " sd ra, 0(sp)\n" -// " sd fp, 8(sp)\n" -// " or fp, t9, r0\n" -// " sq s4, 16(sp)\n" -// " sq s5, 32(sp)\n" -// " sq gp, 48(sp)\n" // -// " or s5, a0, r0\n" -// " or gp, a1, r0\n" -// " lwu s4, 16(s5)\n" -// " bnel s7, s4, L223\n" -// -// " or v1, s7, r0\n" -// -// " lw v1, *debug-segment*(s7)\n" -// " beql s7, v1, L223\n" -// -// " or v1, v1, r0\n" -// -// " lw v1, *debug-dead-pool*(s7)\n" -// " dsubu a0, s5, v1\n" -// " daddiu v1, s7, 8\n" -// " movz v1, s7, a0\n" -// -// "L223:\n" -// " beq s7, v1, L225\n" -// " or v1, s7, r0\n" -// -// " lw a0, *debug-dead-pool*(s7)\n" -// " lwu v1, -4(a0)\n" -// " lwu t9, 72(v1)\n" -// " or a1, gp, r0\n" -// " jalr ra, t9\n" -// " sll v0, ra, 0\n" -// -// " or s4, v0, r0\n" -// " beq s7, s4, L225\n" -// " or v1, s7, r0\n" -// -// " lw t9, format(s7)\n" -// " addiu a0, r0, 0\n" -// " daddiu a1, fp, L315\n" -// " or a2, gp, r0\n" -// " or v1, s4, r0\n" -// " beq s7, v1, L224\n" -// " or a3, s7, r0\n" -// -// " lwu v1, 0(v1)\n" -// " lwu a3, 24(v1)\n" -// -// "L224:\n" -// " lwu t0, 0(s5)\n" -// " jalr ra, t9\n" -// " sll v0, ra, 0\n" -// "\n" -// " or v1, v0, r0\n" -// -// "L225:\n" -// " beq s7, s4, L226\n" +// " lwu v1, 64(a0)\n" +// " lwu a1, 80(a0)\n" +// " beq s7, a1, L191\n" // " sll r0, r0, 0\n" // -// " lwu v1, 0(s4)\n" -// " sw gp, -4(v1)\n" -// " lwu v0, 0(s4)\n" -// " beq r0, r0, L228\n" -// " sll r0, r0, 0\n" -// -// "L226:\n" -// " lw t9, format(s7)\n" -// " addiu a0, r0, 0\n" -// " daddiu a1, fp, L314\n" -// " beq s7, s4, L227\n" -// " or a3, s7, r0\n" -// -// " lwu v1, 0(s4)\n" -// " lwu a3, 24(v1)\n" -// -// "L227:\n" -// " lwu t0, 0(s5)\n" -// " or a2, gp, r0\n" +// " or v1, a0, r0\n" +// " lwu a1, -4(v1)\n" +// " lwu t9, 100(a1)\n" +// " lwu a1, 80(a0)\n" +// " or a0, v1, r0\n" - is eliminated falsely because of the virtual method thing. // " jalr ra, t9\n" // " sll v0, ra, 0\n" // -// " or v0, s7, r0\n" +// " beq r0, r0, L192\n" +// " sll r0, r0, 0\n" // -// "L228:\n" +// "L191:\n" +// " lwu a0, 60(a0)\n" +// " dsubu v0, v1, a0\n" +// +// "L192:\n" // " ld ra, 0(sp)\n" -// " ld fp, 8(sp)\n" -// " lq gp, 48(sp)\n" -// " lq s5, 32(sp)\n" -// " lq s4, 16(sp)\n" // " jr ra\n" -// " daddiu sp, sp, 64"; -// std::string type = "(function dead-pool type int process)"; -// std::string expected = -// "(begin\n" -// " (set! s4-0 (-> arg0 child))\n" -// " (when\n" -// " (and (not s4-0) *debug-segment* (!= arg0 *debug-dead-pool*))\n" -// " (set! s4-0 (get-process *debug-dead-pool* arg1 arg2))\n" -// " (when\n" -// " s4-0\n" -// " (set! t9-1 format)\n" -// " (set! a0-2 0)\n" -// " (set!\n" -// " a1-2\n" -// " \"WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%\"\n" -// " )\n" -// " (set! a2-1 arg1)\n" -// " (set! v1-6 s4-0)\n" -// " (t9-1 a0-2 a1-2 a2-1 (if v1-6 (-> v1-6 name 6)) (-> arg0 name))\n" -// " )\n" -// " )\n" -// " (the-as\n" -// " process\n" -// " (cond\n" -// " (s4-0\n" -// " (set! (-> (the-as (pointer process-tree) s4-0) 0 type) arg1)\n" -// " (-> s4-0 0)\n" -// " )\n" -// " (else\n" -// " (format\n" -// " 0\n" -// " \"WARNING: ~A ~A could not be allocated, because ~A was empty.~%\"\n" -// " arg1\n" -// " (if s4-0 (-> s4-0 0 self))\n" -// " (-> arg0 name)\n" -// " )\n" -// " (quote #f)\n" -// " )\n" -// " )\n" -// " )\n" -// " )"; -// test_with_expr( -// func, type, expected, false, "dead-pool", -// {{"L315", "WARNING: ~A ~A had to be allocated from the debug pool, because ~A was -// empty.~%"}, -// {"L314", "WARNING: ~A ~A could not be allocated, because ~A was empty.~%"}}, -// parse_hint_json("[\t\t[25, [\"v1\", \"(pointer process-tree)\"]],\n" -// "\t\t[30, [\"s4\", \"(pointer process-tree)\"]]]")); +// " daddiu sp, sp, 16"; +// std::string type = "(function dead-pool-heap int)"; +// std::string expected = "blah"; +// test_with_expr(func, type, expected); //} \ No newline at end of file