[Decompiler] Expressions (Part 3) (#213)

* before inserting bonus instruction

* first part of refactor for return values

* find parent method working
This commit is contained in:
water111 2021-01-25 22:08:58 -05:00 committed by GitHub
parent 2f722e6379
commit b59e33c005
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 782 additions and 81 deletions

View file

@ -36,6 +36,7 @@ add_library(
IR2/Form.cpp IR2/Form.cpp
IR2/FormExpressionAnalysis.cpp IR2/FormExpressionAnalysis.cpp
IR2/FormStack.cpp IR2/FormStack.cpp
IR2/GenericElementMatcher.cpp
ObjectFile/LinkedObjectFile.cpp ObjectFile/LinkedObjectFile.cpp
ObjectFile/LinkedObjectFileCreation.cpp ObjectFile/LinkedObjectFileCreation.cpp

View file

@ -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; dts.type_prop_settings.current_method_type = guessed_name.type_name;
} }
if (my_type.last_arg() == TypeSpec("none")) {
auto as_end = dynamic_cast<FunctionEndOp*>(ir2.atomic_ops->ops.back().get());
assert(as_end);
as_end->mark_function_as_no_return_value();
}
std::vector<TypeState> block_init_types, op_types; std::vector<TypeState> block_init_types, op_types;
block_init_types.resize(basic_blocks.size()); block_init_types.resize(basic_blocks.size());
op_types.resize(ir2.atomic_ops->ops.size()); op_types.resize(ir2.atomic_ops->ops.size());

View file

@ -1307,4 +1307,47 @@ bool get_as_reg_offset(const SimpleExpression& expr, IR2_RegOffset* out) {
} }
return false; 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<DecompilerLabel>&, 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<const FunctionEndOp*>(&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 } // namespace decompiler

View file

@ -38,6 +38,7 @@ class DecompilerTypeSystem;
* SetVarConditionOp * SetVarConditionOp
* AsmOp * AsmOp
* SetVarExprOp * SetVarExprOp
* FunctionEndOp
*/ */
class AtomicOp { class AtomicOp {
public: public:
@ -553,7 +554,7 @@ class SpecialOp : public AtomicOp {
*/ */
class CallOp : public AtomicOp { class CallOp : public AtomicOp {
public: public:
CallOp(int my_idx); explicit CallOp(int my_idx);
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override; goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
bool operator==(const AtomicOp& other) const override; bool operator==(const AtomicOp& other) const override;
bool is_sequence_point() const override; bool is_sequence_point() const override;
@ -612,5 +613,37 @@ struct IR2_RegOffset {
Variable var; Variable var;
int offset; 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<DecompilerLabel>& 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); bool get_as_reg_offset(const SimpleExpression& expr, IR2_RegOffset* out);
} // namespace decompiler } // namespace decompiler

View file

@ -77,7 +77,13 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
// todo pointer // todo pointer
// todo product trick // todo product trick
// todo type of basic fallback // 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<DynamicMethodAccess>(nullptr, ro.var);
return pool.alloc_element<SetVarElement>(m_dst, load, true);
}
// Assume we're accessing a field of an object. // Assume we're accessing a field of an object.
FieldReverseLookupInput rd_in; FieldReverseLookupInput rd_in;
@ -150,4 +156,7 @@ FormElement* ConditionalMoveFalseOp::get_as_form(FormPool& pool, const Env&) con
return pool.alloc_element<ConditionalMoveFalseElement>(m_dst, source, m_on_zero); return pool.alloc_element<ConditionalMoveFalseElement>(m_dst, source, m_on_zero);
} }
FormElement* FunctionEndOp::get_as_form(FormPool& pool, const Env&) const {
return pool.alloc_element<AtomicOpElement>(this);
}
} // namespace decompiler } // namespace decompiler

View file

@ -773,4 +773,14 @@ TypeState ConditionalMoveFalseOp::propagate_types_internal(const TypeState& inpu
return result; 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 } // namespace decompiler

View file

@ -69,6 +69,9 @@ class Env {
m_has_local_vars = true; 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::string print_local_var_types(const Form* top_level_form) const;
std::unordered_set<RegId, RegId::hash> get_ssa_var(const VariableSet& vars) const; std::unordered_set<RegId, RegId::hash> get_ssa_var(const VariableSet& vars) const;
@ -78,6 +81,8 @@ class Env {
DecompilerTypeSystem* dts = nullptr; DecompilerTypeSystem* dts = nullptr;
private: private:
Variable m_end_var;
bool m_has_reg_use = false; bool m_has_reg_use = false;
RegUsageInfo m_reg_use; RegUsageInfo m_reg_use;

View file

@ -344,8 +344,8 @@ void BranchElement::collect_vars(VariableSet& vars) const {
goos::Object ReturnElement::to_form(const Env& env) const { goos::Object ReturnElement::to_form(const Env& env) const {
std::vector<goos::Object> forms; std::vector<goos::Object> forms;
forms.push_back(pretty_print::to_symbol("return")); forms.push_back(pretty_print::to_symbol("return"));
forms.push_back(pretty_print::build_list(return_code->to_form(env))); forms.push_back(return_code->to_form(env));
forms.push_back(pretty_print::build_list(dead_code->to_form(env))); forms.push_back(dead_code->to_form(env));
return pretty_print::build_list(forms); return pretty_print::build_list(forms);
} }
@ -820,6 +820,26 @@ void GenericOperator::apply_form(const std::function<void(Form*)>& 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) { std::string fixed_operator_to_string(FixedOperatorKind kind) {
switch (kind) { switch (kind) {
case FixedOperatorKind::GPR_TO_FPR: 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<void(FormElement*)>& f) {
f(this);
}
void DynamicMethodAccess::apply_form(const std::function<void(Form*)>&) {}
void DynamicMethodAccess::collect_vars(VariableSet& vars) const {
vars.insert(m_source);
}
} // namespace decompiler } // namespace decompiler

View file

@ -167,6 +167,7 @@ class SimpleAtomElement : public FormElement {
void apply(const std::function<void(FormElement*)>& f) override; void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override; void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(VariableSet& vars) const 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; // void push_to_stack(const Env& env, FormStack& stack) override;
private: private:
@ -206,6 +207,8 @@ class AtomicOpElement : public FormElement {
void apply(const std::function<void(FormElement*)>& f) override; void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override; void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(VariableSet& vars) const 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: private:
const AtomicOp* m_op; const AtomicOp* m_op;
@ -230,6 +233,10 @@ class ConditionElement : public FormElement {
void apply_form(const std::function<void(Form*)>& f) override; void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(VariableSet& vars) const override; void collect_vars(VariableSet& vars) const override;
void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) 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<FormElement*>* result) override;
void invert(); void invert();
const RegSet& consume() const { return m_consumed; } const RegSet& consume() const { return m_consumed; }
@ -436,6 +443,7 @@ class ShortCircuitElement : public FormElement {
void apply(const std::function<void(FormElement*)>& f) override; void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override; void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(VariableSet& vars) const 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; goos::Object to_form(const Env& env) const;
void apply(const std::function<void(FormElement*)>& f); void apply(const std::function<void(FormElement*)>& f);
void apply_form(const std::function<void(Form*)>& f); void apply_form(const std::function<void(Form*)>& f);
bool operator==(const GenericOperator& other) const;
bool operator!=(const GenericOperator& other) const;
private: private:
Kind m_kind = Kind::INVALID; Kind m_kind = Kind::INVALID;
@ -588,6 +598,8 @@ class GenericElement : public FormElement {
void apply(const std::function<void(FormElement*)>& f) override; void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override; void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(VariableSet& vars) const override; void collect_vars(VariableSet& vars) const override;
const GenericOperator& op() const { return m_head; }
const std::vector<Form*>& elts() const { return m_elts; }
private: private:
GenericOperator m_head; GenericOperator m_head;
@ -601,6 +613,8 @@ class CastElement : public FormElement {
void apply(const std::function<void(FormElement*)>& f) override; void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override; void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(VariableSet& vars) const override; void collect_vars(VariableSet& vars) const override;
const TypeSpec& type() const { return m_type; }
const Form* source() const { return m_source; }
private: private:
TypeSpec m_type; TypeSpec m_type;
@ -609,7 +623,12 @@ class CastElement : public FormElement {
class DerefToken { class DerefToken {
public: 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_constant(s64 int_constant);
static DerefToken make_int_expr(Form* expr); static DerefToken make_int_expr(Form* expr);
static DerefToken make_field_name(const std::string& name); static DerefToken make_field_name(const std::string& name);
@ -645,6 +664,22 @@ class DerefElement : public FormElement {
std::vector<DerefToken> m_tokens; std::vector<DerefToken> 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<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(VariableSet& vars) const override;
void update_from_stack(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result) override;
private:
Variable m_source;
};
/*! /*!
* A Form is a wrapper around one or more FormElements. * A Form is a wrapper around one or more FormElements.
* This is done for two reasons: * This is done for two reasons:

View file

@ -1,10 +1,21 @@
#include "Form.h" #include "Form.h"
#include "FormStack.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 decompiler {
namespace { namespace {
Form* var_to_form(const Variable& var, FormPool& pool) {
return pool.alloc_single_element_form<SimpleAtomElement>(nullptr, SimpleAtom::make_var(var));
}
void update_var_from_stack_helper(int my_idx, void update_var_from_stack_helper(int my_idx,
Variable input, Variable input,
FormPool& pool, FormPool& pool,
@ -529,6 +540,41 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack&
stack.push_form_element(this, true); 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<FormElement*> 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 // ConditionElement
/////////////////// ///////////////////
@ -546,8 +592,78 @@ void ConditionElement::push_to_stack(const Env&, FormPool& pool, FormStack& stac
true); true);
} }
void ReturnElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { void ConditionElement::update_from_stack(const Env&,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result) {
std::vector<Form*> 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<GenericElement>(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<FormElement*> 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); stack.push_form_element(this, true);
} }
void AtomicOpElement::push_to_stack(const Env& env, FormPool&, FormStack&) {
auto as_end = dynamic_cast<const FunctionEndOp*>(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<FormElement*>* 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<DerefElement>(
var_to_form(base.value(), pool), false,
std::vector<DerefToken>{DerefToken::make_field_name("methods"),
DerefToken::make_int_expr(var_to_form(idx.value(), pool))});
result->push_back(deref);
}
} // namespace decompiler } // namespace decompiler

View file

@ -49,11 +49,11 @@ void FormStack::push_form_element(FormElement* elt, bool sequence_point) {
m_stack.push_back(entry); 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;) { for (size_t i = m_stack.size(); i-- > 0;) {
auto& entry = m_stack.at(i); auto& entry = m_stack.at(i);
if (entry.active) { if (entry.active) {
if (entry.destination->reg() == var.reg()) { if (entry.destination->reg() == reg) {
entry.active = false; entry.active = false;
assert(entry.source); assert(entry.source);
return entry.source; return entry.source;
@ -70,6 +70,10 @@ Form* FormStack::pop_reg(const Variable& var) {
return nullptr; return nullptr;
} }
Form* FormStack::pop_reg(const Variable& var) {
return pop_reg(var.reg());
}
std::vector<FormElement*> FormStack::rewrite(FormPool& pool) { std::vector<FormElement*> FormStack::rewrite(FormPool& pool) {
std::vector<FormElement*> result; std::vector<FormElement*> result;
@ -89,9 +93,9 @@ std::vector<FormElement*> FormStack::rewrite(FormPool& pool) {
return result; return result;
} }
std::vector<FormElement*> FormStack::rewrite_to_get_reg(FormPool& pool, std::vector<FormElement*> FormStack::rewrite_to_get_var(FormPool& pool,
Register reg, const Variable& var,
const Env& env) { const Env&) {
// first, rewrite as normal. // first, rewrite as normal.
auto default_result = rewrite(pool); auto default_result = rewrite(pool);
@ -99,7 +103,7 @@ std::vector<FormElement*> FormStack::rewrite_to_get_reg(FormPool& pool,
// value in the given register. // value in the given register.
auto last_op_as_set = dynamic_cast<SetVarElement*>(default_result.back()); auto last_op_as_set = dynamic_cast<SetVarElement*>(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(); default_result.pop_back();
for (auto form : last_op_as_set->src()->elts()) { for (auto form : last_op_as_set->src()->elts()) {
form->parent_form = nullptr; // will get set later, this makes it obvious if I forget. form->parent_form = nullptr; // will get set later, this makes it obvious if I forget.
@ -107,8 +111,8 @@ std::vector<FormElement*> FormStack::rewrite_to_get_reg(FormPool& pool,
} }
return default_result; return default_result;
} else { } else {
throw std::runtime_error( default_result.push_back(pool.alloc_element<SimpleAtomElement>(SimpleAtom::make_var(var)));
fmt::format("Couldn't rewrite form to get result {}:\n{}\n\n", reg.to_charp(), print(env))); return default_result;
} }
} }
} // namespace decompiler } // namespace decompiler

View file

@ -16,9 +16,10 @@ class FormStack {
void push_value_to_reg(Variable var, Form* value, bool sequence_point); void push_value_to_reg(Variable var, Form* value, bool sequence_point);
void push_form_element(FormElement* elt, bool sequence_point); void push_form_element(FormElement* elt, bool sequence_point);
Form* pop_reg(const Variable& var); Form* pop_reg(const Variable& var);
Form* pop_reg(Register reg);
bool is_single_expression(); bool is_single_expression();
std::vector<FormElement*> rewrite(FormPool& pool); std::vector<FormElement*> rewrite(FormPool& pool);
std::vector<FormElement*> rewrite_to_get_reg(FormPool& pool, Register reg, const Env& env); std::vector<FormElement*> rewrite_to_get_var(FormPool& pool, const Variable& var, const Env& env);
std::string print(const Env& env); std::string print(const Env& env);
private: private:

View file

@ -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<Matcher>& 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<Matcher>& 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<Matcher>& 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<int> 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<SimpleAtomElement*>(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<SimpleExpressionElement*>(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<GenericElement*>(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<CastElement*>(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<SimpleAtomElement*>(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<SimpleExpressionElement*>(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

View file

@ -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<std::optional<Variable>> regs;
} maps;
};
class Matcher {
public:
static Matcher any_reg(int match_id = -1);
static Matcher op(GenericOperator op, const std::vector<Matcher>& args);
static Matcher fixed_op(FixedOperatorKind op, const std::vector<Matcher>& args);
static Matcher match_or(const std::vector<Matcher>& args);
static Matcher cast(const std::string& type, Matcher value);
static Matcher any();
static Matcher integer(std::optional<int> 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<Matcher> m_sub_matchers;
Kind m_kind = Kind::INVALID;
int m_reg_out_id = -1;
std::optional<int> m_int_match;
std::string m_str;
};
MatchResult match(const Matcher& spec, const Form* input);
} // namespace decompiler

View file

@ -239,6 +239,7 @@ void ObjectFileDB::ir2_atomic_op_pass() {
auto ops = convert_function_to_atomic_ops(func, data.linked_data.labels); auto ops = convert_function_to_atomic_ops(func, data.linked_data.labels);
func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops)); func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops));
func.ir2.atomic_ops_succeeded = true; func.ir2.atomic_ops_succeeded = true;
func.ir2.env.set_end_var(func.ir2.atomic_ops->end_op().return_var());
successful++; successful++;
} catch (std::exception& e) { } catch (std::exception& e) {
lg::warn("Function {} from {} could not be converted to atomic ops: {}", lg::warn("Function {} from {} could not be converted to atomic ops: {}",

View file

@ -1407,15 +1407,32 @@ FunctionAtomicOps convert_function_to_atomic_ops(const Function& func,
FunctionAtomicOps result; FunctionAtomicOps result;
int last_op = 0; 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: // we should only consider the blocks which actually have instructions:
if (block.end_word > block.start_word) { if (block.end_word > block.start_word) {
auto begin = func.instructions.begin() + block.start_word; auto begin = func.instructions.begin() + block.start_word;
auto end = func.instructions.begin() + block.end_word; auto end = func.instructions.begin() + block.end_word;
last_op = convert_block_to_atomic_ops(block.start_word, begin, end, labels, &result); 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<FunctionEndOp>(int(result.ops.size())));
result.ops.back()->update_register_info();
// add to block.
result.block_id_to_end_atomic_op.back()++;
}
} else { } else {
result.block_id_to_first_atomic_op.push_back(last_op); if (i == int(func.basic_blocks.size()) - 1) {
result.block_id_to_end_atomic_op.push_back(last_op); // we're the last block. insert the function end op.
result.ops.push_back(std::make_unique<FunctionEndOp>(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);
}
} }
} }

View file

@ -14,6 +14,13 @@ struct FunctionAtomicOps {
// the actual ops, store in the correct order // the actual ops, store in the correct order
std::vector<std::unique_ptr<AtomicOp>> ops; std::vector<std::unique_ptr<AtomicOp>> ops;
FunctionEndOp& end_op() const {
assert(!ops.empty());
auto end = dynamic_cast<FunctionEndOp*>(ops.back().get());
assert(end);
return *end;
}
// mappings from instructions to atomic ops and back // mappings from instructions to atomic ops and back
std::unordered_map<int, int> instruction_to_atomic_op; std::unordered_map<int, int> instruction_to_atomic_op;
std::unordered_map<int, int> atomic_op_to_instruction; std::unordered_map<int, int> atomic_op_to_instruction;

View file

@ -5,6 +5,31 @@
#include "decompiler/util/DecompilerTypeSystem.h" #include "decompiler/util/DecompilerTypeSystem.h"
namespace decompiler { namespace decompiler {
void clean_up_ifs(Form* top_level_form) {
top_level_form->apply([&](FormElement* elt) {
auto as_cne = dynamic_cast<CondNoElseElement*>(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, bool convert_to_expressions(Form* top_level_form,
FormPool& pool, FormPool& pool,
const Function& f, const Function& f,
@ -40,9 +65,10 @@ bool convert_to_expressions(Form* top_level_form,
} }
std::vector<FormElement*> new_entries; std::vector<FormElement*> new_entries;
if (f.type.last_arg() != TypeSpec("none")) { if (f.type.last_arg() != TypeSpec("none")) {
auto v0 = Register(Reg::GPR, Reg::V0); auto return_var = f.ir2.atomic_ops->end_op().return_var();
new_entries = stack.rewrite_to_get_reg(pool, v0, f.ir2.env); 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(v0); 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)) { if (!dts.ts.typecheck(f.type.last_arg(), reg_return_type.typespec(), "", false, false)) {
// we need to cast the final value. // we need to cast the final value.
auto to_cast = new_entries.back(); auto to_cast = new_entries.back();
@ -59,10 +85,15 @@ bool convert_to_expressions(Form* top_level_form,
for (auto x : new_entries) { for (auto x : new_entries) {
top_level_form->push_back(x); top_level_form->push_back(x);
} }
// fix up stuff
clean_up_ifs(top_level_form);
} catch (std::exception& e) { } catch (std::exception& e) {
lg::warn("Expression building failed: {}", e.what()); lg::warn("Expression building failed: {}", e.what());
return false; return false;
} }
return true; return true;
} }
} // namespace decompiler } // namespace decompiler

View file

@ -12,27 +12,14 @@ bool in_set(RegSet& set, const Register& obj) {
return set.find(obj) != set.end(); return set.find(obj) != set.end();
} }
void phase1(const FunctionAtomicOps& ops, void phase1(const FunctionAtomicOps& ops, int block_id, RegUsageInfo* out) {
int block_id,
RegUsageInfo* out,
bool insert_v0_read_instruction_at_end) {
int end_op = ops.block_id_to_end_atomic_op.at(block_id); 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 start_op = ops.block_id_to_first_atomic_op.at(block_id);
int loop_end = end_op; for (int i = end_op; i-- > start_op;) {
if (insert_v0_read_instruction_at_end) { const auto& instr = ops.ops.at(i);
loop_end++; auto read = instr->read_regs();
} auto write = instr->write_regs();
for (int i = loop_end; i-- > start_op;) {
std::vector<Register> read;
std::vector<Register> 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();
}
auto& lv = out->op.at(i).live; auto& lv = out->op.at(i).live;
auto& dd = out->op.at(i).dead; auto& dd = out->op.at(i).dead;
@ -114,8 +101,7 @@ bool phase2(const std::vector<BasicBlock>& blocks, int block_id, RegUsageInfo* i
void phase3(const FunctionAtomicOps& ops, void phase3(const FunctionAtomicOps& ops,
const std::vector<BasicBlock>& blocks, const std::vector<BasicBlock>& blocks,
int block_id, int block_id,
RegUsageInfo* info, RegUsageInfo* info) {
bool insert_v0_read_instruction_at_end) {
RegSet live_local; RegSet live_local;
const auto& block_obj = blocks.at(block_id); const auto& block_obj = blocks.at(block_id);
for (auto s : {block_obj.succ_branch, block_obj.succ_ft}) { 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 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 start_op = ops.block_id_to_first_atomic_op.at(block_id);
int loop_end = end_op; for (int i = end_op; i-- > start_op;) {
if (insert_v0_read_instruction_at_end) {
loop_end++;
}
for (int i = loop_end; i-- > start_op;) {
auto& lv = info->op.at(i).live; auto& lv = info->op.at(i).live;
auto& dd = info->op.at(i).dead; auto& dd = info->op.at(i).dead;
@ -149,12 +130,6 @@ void phase3(const FunctionAtomicOps& ops,
live_local = new_live; live_local = new_live;
} }
} }
bool should_insert_v0_read(const std::vector<BasicBlock>& blocks, const Function& function, int i) {
return i == int(blocks.size()) - 1 && function.type.arg_count() > 0 &&
function.type.last_arg() != TypeSpec("none");
}
} // namespace } // namespace
RegUsageInfo analyze_ir2_register_usage(const Function& function) { 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); RegUsageInfo result(blocks.size(), ops->ops.size() + 1);
for (int i = 0; i < int(blocks.size()); i++) { 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; bool changed = false;
@ -177,7 +152,7 @@ RegUsageInfo analyze_ir2_register_usage(const Function& function) {
} while (changed); } while (changed);
for (int i = 0; i < int(blocks.size()); i++) { 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. // we want to know if an op "consumes" a register.

View file

@ -98,6 +98,7 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
auto ops = convert_function_to_atomic_ops(test->func, program.labels); auto ops = convert_function_to_atomic_ops(test->func, program.labels);
test->func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops)); test->func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops));
test->func.ir2.atomic_ops_succeeded = true; 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, {})); EXPECT_TRUE(test->func.run_type_analysis_ir2(function_type, *dts, test->file, {}));

View file

@ -27,7 +27,7 @@ TEST_F(FormRegressionTest, SimplestTest) {
" jr ra\n" " jr ra\n"
" daddu sp, sp, r0"; " daddu sp, sp, r0";
std::string type = "(function object object)"; 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); test_no_expr(func, type, expected);
} }
@ -52,6 +52,7 @@ TEST_F(FormRegressionTest, FloatingPointBasic) {
" (set! f1-0 (gpr->fpr a0-0))\n" " (set! f1-0 (gpr->fpr a0-0))\n"
" (set! f0-1 (/.s f0-0 f1-0))\n" " (set! f0-1 (/.s f0-0 f1-0))\n"
" (set! v0-0 (fpr->gpr f0-1))\n" " (set! v0-0 (fpr->gpr f0-1))\n"
" (ret-value v0-0)\n"
" )"; " )";
test_no_expr(func, type, expected); test_no_expr(func, type, expected);
} }
@ -64,7 +65,7 @@ TEST_F(FormRegressionTest, Op3) {
" jr ra\n" " jr ra\n"
" daddu sp, sp, r0"; " daddu sp, sp, r0";
std::string type = "(function int int int)"; 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); test_no_expr(func, type, expected);
} }
@ -77,7 +78,7 @@ TEST_F(FormRegressionTest, Division) {
" jr ra\n" " jr ra\n"
" daddu sp, sp, r0"; " daddu sp, sp, r0";
std::string type = "(function int int int)"; 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); test_no_expr(func, type, expected);
} }
@ -97,7 +98,7 @@ TEST_F(FormRegressionTest, Ash) {
" sll r0, r0, 0\n" " sll r0, r0, 0\n"
" sll r0, r0, 0"; " sll r0, r0, 0";
std::string type = "(function int int int)"; 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); test_no_expr(func, type, expected);
} }
@ -113,7 +114,7 @@ TEST_F(FormRegressionTest, Abs) {
" jr ra\n" " jr ra\n"
" daddu sp, sp, r0"; " daddu sp, sp, r0";
std::string type = "(function int int)"; 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); test_no_expr(func, type, expected);
} }
@ -127,7 +128,13 @@ TEST_F(FormRegressionTest, Min) {
" jr ra\n" " jr ra\n"
" daddu sp, sp, r0"; " daddu sp, sp, r0";
std::string type = "(function int int int)"; 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); test_no_expr(func, type, expected);
} }
@ -142,7 +149,13 @@ TEST_F(FormRegressionTest, Max) {
" jr ra\n" " jr ra\n"
" daddu sp, sp, r0"; " daddu sp, sp, r0";
std::string type = "(function int int int)"; 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); test_no_expr(func, type, expected);
} }
@ -182,6 +195,7 @@ TEST_F(FormRegressionTest, FormatString) {
" (set! a2-0 (fpr->gpr f0-0))\n" " (set! a2-0 (fpr->gpr f0-0))\n"
" (call! a0-1 a1-0 a2-0)\n" // #t, "~f", the float " (call! a0-1 a1-0 a2-0)\n" // #t, "~f", the float
" (set! v0-1 gp-0)\n" " (set! v0-1 gp-0)\n"
" (ret-value v0-1)\n"
" )"; " )";
test_no_expr(func, type, expected, false, "", {{"L343", "~f"}}); 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" " (begin (set! v1-0 (-> v1-0 parent)) (= v1-0 a0-1))\n"
" (if\n" " (if\n"
" (= v1-0 a1-0)\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"
" )\n" " )\n"
" (set! v0-0 '#f)\n" " (set! v0-0 '#f)\n"
" (ret-value v0-0)\n"
" )"; " )";
test_no_expr(func, type, expected); test_no_expr(func, type, expected);
} }
@ -285,10 +300,11 @@ TEST_F(FormRegressionTest, Or) {
" )\n" " )\n"
" (if\n" " (if\n"
" (= a0-0 a1-0)\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"
" )\n" " )\n"
" (set! v0-0 '#f)\n" " (set! v0-0 '#f)\n"
" (ret-value v0-0)\n"
" )"; " )";
test_no_expr(func, type, expected); test_no_expr(func, type, expected);
} }
@ -343,29 +359,30 @@ TEST_F(FormRegressionTest, DynamicMethodAccess) {
"(begin\n" "(begin\n"
" (set! v1-0 (sll a1-0 2))\n" " (set! v1-0 (sll a1-0 2))\n"
" (set! v1-1 (+ v1-0 a0-0))\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" " (until\n"
" (!= v0-0 v1-2)\n" // actually goes after the body, so it's fine to refer to v1-2 " (!= v0-0 v1-2)\n" // actually goes after the body, so it's fine to refer to v1-2
" (if\n" " (if\n"
" (begin\n" " (begin\n"
" (if\n" " (if\n"
" (begin (set! a2-0 object) (= a0-0 a2-0))\n" // if we reached the top " (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 " (return (begin (set! v1-3 nothing) (set! v0-0 v1-3)) (set! v1-2 0))\n" // return
// nothing. // nothing.
" )\n" " )\n"
" (set! a0-0 (-> a0-0 parent))\n" // get next parent type " (set! a0-0 (-> a0-0 parent))\n" // get next parent type
" (set! a2-2 (sll a1-0 2))\n" // fancy access " (set! a2-2 (sll a1-0 2))\n" // fancy access
" (set! a2-3 (+ a2-2 a0-0))\n" " (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 " (set! v0-0 (dyn-method-access a2-3))\n" // get method (in v0-1, the same var as loop
// condition) // condition)
" (zero? v0-0)\n" // is it defined? " (zero? v0-0)\n" // is it defined?
" )\n" " )\n"
" (return ((begin (set! v1-4 nothing) (set! v0-0 v1-4))) ((set! v1-2 0)))\n" // also " (return (begin (set! v1-4 nothing) (set! v0-0 v1-4)) (set! v1-2 0))\n" // also
// return // return
// nothing. // nothing.
" )\n" " )\n"
" )\n" " )\n"
" (set! v1-5 '#f)\n" " (set! v1-5 '#f)\n"
" (ret-value v0-0)\n"
" )"; " )";
test_no_expr(func, type, expected); test_no_expr(func, type, expected);
} }
@ -409,6 +426,7 @@ TEST_F(FormRegressionTest, SimpleLoopMergeCheck) {
" (set! v1-1 '#f)\n" " (set! v1-1 '#f)\n"
" (set! v1-2 '#f)\n" " (set! v1-2 '#f)\n"
" (set! v0-0 (l.w (+ a0-0 -2)))\n" " (set! v0-0 (l.w (+ a0-0 -2)))\n"
" (ret-value v0-0)\n"
" )"; " )";
test_no_expr(func, type, expected, true); test_no_expr(func, type, expected, true);
} }
@ -460,6 +478,7 @@ TEST_F(FormRegressionTest, And) {
" daddu sp, sp, r0"; " daddu sp, sp, r0";
std::string type = "(function pair int)"; std::string type = "(function pair int)";
std::string expected = std::string expected =
"(begin\n"
"(cond\n" "(cond\n"
" ((begin (set! v1-0 '()) (= a0-0 v1-0)) (set! v0-0 0))\n" // should be a case, not a return " ((begin (set! v1-0 '()) (= a0-0 v1-0)) (set! v0-0 0))\n" // should be a case, not a return
" (else\n" " (else\n"
@ -478,7 +497,8 @@ TEST_F(FormRegressionTest, And) {
" )\n" " )\n"
" (set! v1-2 '#f)\n" // while's false, I think. " (set! v1-2 '#f)\n" // while's false, I think.
" )\n" " )\n"
" )"; " )"
"(ret-value v0-0))\n";
test_no_expr(func, type, expected, true); test_no_expr(func, type, expected, true);
} }
@ -536,7 +556,7 @@ TEST_F(FormRegressionTest, FunctionCall) {
" daddiu sp, sp, 48"; " daddiu sp, sp, 48";
std::string type = "(function basic object object)"; std::string type = "(function basic object object)";
std::string expected = std::string expected =
"(if\n" // this if needs regrouping. "(begin (if\n" // this if needs regrouping.
" (begin\n" " (begin\n"
" (set! s5-0 a0-0)\n" // s5-0 is the thing to check " (set! s5-0 a0-0)\n" // s5-0 is the thing to check
" (set! gp-0 a1-0)\n" // gp-0 is the list " (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 " (!= gp-0 v1-3)\n" // IF CONDITION
" )\n" " )\n"
" (set! v0-1 gp-0)\n" // not empty, so return the result " (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); test_no_expr(func, type, expected, true);
} }
@ -732,6 +753,7 @@ TEST_F(FormRegressionTest, NestedAndOr) {
" )\n" " )\n"
" (set! v1-12 '#f)\n" " (set! v1-12 '#f)\n"
" (set! v0-1 gp-0)\n" " (set! v0-1 gp-0)\n"
" (ret-value v0-1)\n"
" )"; " )";
test_no_expr(func, type, expected, true); test_no_expr(func, type, expected, true);
} }
@ -771,7 +793,7 @@ TEST_F(FormRegressionTest, NewMethod) {
" daddiu sp, sp, 32"; " daddiu sp, sp, 32";
std::string type = "(function symbol type int inline-array-class)"; std::string type = "(function symbol type int inline-array-class)";
std::string expected = std::string expected =
"(when\n" "(begin (when\n"
" (begin\n" " (begin\n"
" (set! gp-0 a2-0)\n" // gp-0 is size " (set! gp-0 a2-0)\n" // gp-0 is size
" (set! v1-0 object)\n" " (set! v1-0 object)\n"
@ -787,7 +809,8 @@ TEST_F(FormRegressionTest, NewMethod) {
" )\n" " )\n"
" (s.w! v0-0 gp-0)\n" // store size " (s.w! v0-0 gp-0)\n" // store size
" (s.w! (+ v0-0 4) gp-0)\n" " (s.w! (+ v0-0 4) gp-0)\n"
" )"; " )"
" (ret-value v0-0))\n";
test_no_expr(func, type, expected, false, "inline-array-class"); test_no_expr(func, type, expected, false, "inline-array-class");
} }
@ -823,7 +846,7 @@ TEST_F(FormRegressionTest, Recursive) {
" daddiu sp, sp, 32"; " daddiu sp, sp, 32";
std::string type = "(function int int)"; std::string type = "(function int int)";
std::string expected = 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 " ((begin (set! gp-0 a0-0) (set! v1-0 1) (= gp-0 v1-0)) (set! v0-0 1))\n" // base
" (else\n" " (else\n"
" (set! t9-0 fact)\n" // recurse! " (set! t9-0 fact)\n" // recurse!
@ -831,7 +854,8 @@ TEST_F(FormRegressionTest, Recursive) {
" (set! v0-1 (call! a0-1))\n" " (set! v0-1 (call! a0-1))\n"
" (set! v0-0 (*.si gp-0 v0-1))\n" // not quite a tail call... " (set! v0-0 (*.si gp-0 v0-1))\n" // not quite a tail call...
" )\n" " )\n"
" )"; " )"
" (ret-value v0-0))\n";
test_no_expr(func, type, expected, false); test_no_expr(func, type, expected, false);
} }
@ -866,6 +890,7 @@ TEST_F(FormRegressionTest, TypeOf) {
" (set! v1-1 (type-of a0-0))\n" " (set! v1-1 (type-of a0-0))\n"
" (set! t9-0 (-> v1-1 method-table 2))\n" // print method. " (set! t9-0 (-> v1-1 method-table 2))\n" // print method.
" (set! v0-0 (call! a0-0))\n" " (set! v0-0 (call! a0-0))\n"
" (ret-value v0-0)\n"
" )"; " )";
test_no_expr(func, type, expected, false); test_no_expr(func, type, expected, false);
} }

View file

@ -462,10 +462,129 @@ TEST_F(FormRegressionTest, ExprBasicTypeP) {
// don't plan on supporting this. // don't plan on supporting this.
" (if\n" " (if\n"
" (= v1-0 a1-0)\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"
" )\n" " )\n"
" '#f\n" " '#f\n"
" )"; " )";
test_with_expr(func, type, expected); 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, "");
}