mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 11:26:18 -04:00
[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:
parent
2f722e6379
commit
b59e33c005
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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:
|
||||||
|
|
169
decompiler/IR2/GenericElementMatcher.cpp
Normal file
169
decompiler/IR2/GenericElementMatcher.cpp
Normal 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
|
53
decompiler/IR2/GenericElementMatcher.h
Normal file
53
decompiler/IR2/GenericElementMatcher.h
Normal 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
|
|
@ -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: {}",
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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, {}));
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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, "");
|
||||||
|
}
|
Loading…
Reference in a new issue