[Decompiler] More fixes for gkernel (#261)

* fix bugs and more address of support

* support for zero checks

* fix tests
This commit is contained in:
water111 2021-02-14 18:50:45 -05:00 committed by GitHub
parent d01ecf0a9e
commit 14d602c594
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1030 additions and 241 deletions

View file

@ -12,7 +12,7 @@ namespace versions {
constexpr s32 GOAL_VERSION_MAJOR = 0;
constexpr s32 GOAL_VERSION_MINOR = 6;
constexpr int DECOMPILER_VERSION = 2;
constexpr int DECOMPILER_VERSION = 4;
// these versions are from the game
constexpr u32 ART_FILE_VERSION = 6;

View file

@ -1266,14 +1266,18 @@ void CallOp::collect_vars(VariableSet& vars) const {
// ConditionalMoveFalseOp
/////////////////////////////
ConditionalMoveFalseOp::ConditionalMoveFalseOp(Variable dst, Variable src, bool on_zero, int my_idx)
: AtomicOp(my_idx), m_dst(dst), m_src(src), m_on_zero(on_zero) {}
ConditionalMoveFalseOp::ConditionalMoveFalseOp(Variable dst,
Variable src,
Variable old_value,
bool on_zero,
int my_idx)
: AtomicOp(my_idx), m_dst(dst), m_src(src), m_old_value(old_value), m_on_zero(on_zero) {}
goos::Object ConditionalMoveFalseOp::to_form(const std::vector<DecompilerLabel>& labels,
const Env& env) const {
(void)labels;
return pretty_print::build_list(m_on_zero ? "cmove-#f-zero" : "cmove-#f-nonzero",
m_dst.to_form(env), m_src.to_form(env));
m_dst.to_form(env), m_src.to_form(env), m_old_value.to_form(env));
}
bool ConditionalMoveFalseOp::operator==(const AtomicOp& other) const {
@ -1283,7 +1287,8 @@ bool ConditionalMoveFalseOp::operator==(const AtomicOp& other) const {
auto po = dynamic_cast<const ConditionalMoveFalseOp*>(&other);
assert(po);
return m_dst == po->m_dst && m_src == po->m_src && m_on_zero == po->m_on_zero;
return m_dst == po->m_dst && m_src == po->m_src && m_on_zero == po->m_on_zero &&
m_old_value == po->m_old_value;
}
bool ConditionalMoveFalseOp::is_sequence_point() const {
@ -1297,11 +1302,13 @@ Variable ConditionalMoveFalseOp::get_set_destination() const {
void ConditionalMoveFalseOp::update_register_info() {
m_write_regs.push_back(m_dst.reg());
m_read_regs.push_back(m_src.reg());
m_read_regs.push_back(m_old_value.reg());
}
void ConditionalMoveFalseOp::collect_vars(VariableSet& vars) const {
vars.insert(m_dst);
vars.insert(m_src);
vars.insert(m_old_value);
}
bool get_as_reg_offset(const SimpleExpression& expr, IR2_RegOffset* out) {

View file

@ -611,7 +611,7 @@ class CallOp : public AtomicOp {
*/
class ConditionalMoveFalseOp : public AtomicOp {
public:
ConditionalMoveFalseOp(Variable dst, Variable src, bool on_zero, int my_idx);
ConditionalMoveFalseOp(Variable dst, Variable src, Variable old_value, bool on_zero, int my_idx);
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env& env) const override;
bool operator==(const AtomicOp& other) const override;
bool is_sequence_point() const override;
@ -624,7 +624,7 @@ class ConditionalMoveFalseOp : public AtomicOp {
void collect_vars(VariableSet& vars) const override;
private:
Variable m_dst, m_src;
Variable m_dst, m_src, m_old_value;
bool m_on_zero;
};

View file

@ -339,6 +339,7 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
for (auto& x : rd.tokens) {
tokens.push_back(to_token(x));
}
auto load =
pool.alloc_single_element_form<DerefElement>(nullptr, source, rd.addr_of, tokens);
return pool.alloc_element<SetVarElement>(m_dst, load, true);
@ -414,9 +415,7 @@ FormElement* CallOp::get_as_form(FormPool& pool, const Env& env) const {
}
FormElement* ConditionalMoveFalseOp::get_as_form(FormPool& pool, const Env&) const {
auto source =
pool.alloc_single_element_form<SimpleAtomElement>(nullptr, SimpleAtom::make_var(m_src));
return pool.alloc_element<ConditionalMoveFalseElement>(m_dst, source, m_on_zero);
return pool.alloc_element<ConditionalMoveFalseElement>(m_dst, m_old_value, m_src, m_on_zero);
}
FormElement* FunctionEndOp::get_as_form(FormPool& pool, const Env&) const {

View file

@ -258,6 +258,12 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
return TP_Type::make_partial_dyanmic_vtable_access();
}
if (arg1_type.is_integer_constant() &&
arg0_type.kind == TP_Type::Kind::PRODUCT_WITH_CONSTANT) {
return TP_Type::make_from_integer_constant_plus_product(
arg1_type.get_integer_constant(), arg0_type.typespec(), arg0_type.get_multiplier());
}
if (arg1_type.is_integer_constant() && is_int_or_uint(dts, arg0_type)) {
return TP_Type::make_from_integer_constant_plus_var(arg1_type.get_integer_constant(),
arg0_type.typespec());
@ -268,6 +274,28 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
break;
}
if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR_MULT && m_kind == Kind::ADD) {
FieldReverseLookupInput rd_in;
rd_in.offset = arg0_type.get_add_int_constant();
rd_in.stride = arg0_type.get_mult_int_constant();
rd_in.base_type = arg1_type.typespec();
auto out = env.dts->ts.reverse_field_lookup(rd_in);
if (out.success) {
return TP_Type::make_from_ts(coerce_to_reg_type(out.result_type));
}
}
if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR && m_kind == Kind::ADD) {
FieldReverseLookupInput rd_in;
rd_in.offset = arg0_type.get_integer_constant();
rd_in.stride = 1;
rd_in.base_type = arg1_type.typespec();
auto out = env.dts->ts.reverse_field_lookup(rd_in);
if (out.success) {
return TP_Type::make_from_ts(coerce_to_reg_type(out.result_type));
}
}
if (arg0_type == arg1_type && is_int_or_uint(dts, arg0_type)) {
// both are the same type and both are int/uint, so we assume that we're doing integer math.
// we strip off any weird things like multiplication or integer constant.

View file

@ -971,29 +971,26 @@ void TypeOfElement::get_modified_regs(RegSet&) const {}
/////////////////////////////
ConditionalMoveFalseElement::ConditionalMoveFalseElement(Variable _dest,
Form* _source,
Variable _old_value,
Variable _source,
bool _on_zero)
: dest(_dest), source(_source), on_zero(_on_zero) {
source->parent_element = this;
}
: dest(_dest), old_value(_old_value), source(_source), on_zero(_on_zero) {}
goos::Object ConditionalMoveFalseElement::to_form_internal(const Env& env) const {
return pretty_print::build_list(on_zero ? "cmove-#f-zero" : "cmove-#f-nonzero", dest.to_form(env),
source->to_form(env));
source.to_form(env), old_value.to_form(env));
}
void ConditionalMoveFalseElement::apply(const std::function<void(FormElement*)>& f) {
f(this);
source->apply(f);
}
void ConditionalMoveFalseElement::apply_form(const std::function<void(Form*)>& f) {
source->apply_form(f);
}
void ConditionalMoveFalseElement::apply_form(const std::function<void(Form*)>&) {}
void ConditionalMoveFalseElement::collect_vars(VariableSet& vars) const {
vars.insert(dest);
source->collect_vars(vars);
vars.insert(old_value);
vars.insert(source);
}
void ConditionalMoveFalseElement::get_modified_regs(RegSet& regs) const {

View file

@ -719,9 +719,10 @@ class TypeOfElement : public FormElement {
class ConditionalMoveFalseElement : public FormElement {
public:
Variable dest;
Form* source = nullptr;
Variable old_value;
Variable source;
bool on_zero = false;
ConditionalMoveFalseElement(Variable _dest, Form* _source, bool _on_zero);
ConditionalMoveFalseElement(Variable _dest, Variable _old_value, Variable _source, bool _on_zero);
goos::Object to_form_internal(const Env& env) const override;
void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override;

View file

@ -465,6 +465,40 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env,
throw std::runtime_error("Failed to match for stride 1 address access with add.");
}
}
} else if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR_MULT) {
// try to see if this is valid, from the type system.
FieldReverseLookupInput input;
input.offset = arg0_type.get_add_int_constant();
input.stride = arg0_type.get_mult_int_constant();
input.base_type = arg1_type.typespec();
auto out = env.dts->ts.reverse_field_lookup(input);
if (out.success) {
// it is. now we have to modify things
// first, look for the index
auto arg0_matcher =
Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::ADDITION),
{Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION),
{Matcher::integer(input.stride), Matcher::any(0)}),
Matcher::integer(input.offset)});
auto match_result = match(arg0_matcher, args.at(0));
if (match_result.matched) {
bool used_index = false;
std::vector<DerefToken> tokens;
for (auto& tok : out.tokens) {
if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) {
assert(!used_index);
used_index = true;
tokens.push_back(DerefToken::make_int_expr(match_result.maps.forms.at(0)));
} else {
tokens.push_back(to_token(tok));
}
}
result->push_back(pool.alloc_element<DerefElement>(args.at(1), out.addr_of, tokens));
return;
} else {
throw std::runtime_error("Failed to match for stride (non power 2) with add");
}
}
}
}
@ -955,6 +989,16 @@ void FunctionCallElement::update_from_stack(const Env& env,
}
auto unsafe = stack.unsafe_peek(Register(Reg::GPR, Reg::A0));
if (!unsafe) {
if (!stack.is_root()) {
fmt::print("STACK:\n{}\n\n", stack.print(env));
throw std::runtime_error("Peek got to back and not root stack");
}
// failed to peek by reaching the end AND root stack, means we just take the function
// argument.
assert(false); // want to test this before enabling.
unsafe = mr.maps.forms.at(0);
}
if (unsafe) {
if (!unsafe->try_as_single_element()) {
throw std::runtime_error(
@ -1160,7 +1204,7 @@ void DerefElement::update_from_stack(const Env& env,
void UntilElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
for (auto form : {condition, body}) {
FormStack temp_stack;
FormStack temp_stack(false);
for (auto& entry : form->elts()) {
entry->push_to_stack(env, pool, temp_stack);
}
@ -1176,7 +1220,7 @@ void UntilElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stac
void WhileElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
for (auto form : {condition, body}) {
FormStack temp_stack;
FormStack temp_stack(false);
for (auto& entry : form->elts()) {
entry->push_to_stack(env, pool, temp_stack);
}
@ -1199,14 +1243,14 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack&
}
for (auto& entry : entries) {
for (auto form : {entry.condition, entry.body}) {
FormStack temp_stack;
FormStack temp_stack(false);
for (auto& elt : form->elts()) {
elt->push_to_stack(env, pool, temp_stack);
}
std::vector<FormElement*> new_entries;
if (form == entry.body && used_as_value) {
new_entries = temp_stack.rewrite_to_get_var(pool, final_destination, env);
new_entries = rewrite_to_get_var(temp_stack, pool, final_destination);
} else {
new_entries = temp_stack.rewrite(pool);
}
@ -1246,6 +1290,38 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac
std::optional<Variable> last_var;
bool rewrite_as_set = true;
// process conditions and bodies
for (auto& entry : entries) {
for (auto form : {entry.condition, entry.body}) {
FormStack temp_stack(false);
for (auto& elt : form->elts()) {
elt->push_to_stack(env, pool, temp_stack);
}
std::vector<FormElement*> new_entries;
new_entries = temp_stack.rewrite(pool);
form->clear();
for (auto e : new_entries) {
form->push_back(e);
}
}
}
// process else.
FormStack temp_stack(false);
for (auto& elt : else_ir->elts()) {
elt->push_to_stack(env, pool, temp_stack);
}
std::vector<FormElement*> new_entries;
new_entries = temp_stack.rewrite(pool);
else_ir->clear();
for (auto e : new_entries) {
else_ir->push_back(e);
}
// collect all forms which should write the output.
std::vector<Form*> write_output_forms;
for (const auto& entry : entries) {
@ -1280,44 +1356,12 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac
}
}
// process everything.
for (auto& entry : entries) {
for (auto form : {entry.condition, entry.body}) {
FormStack temp_stack;
for (auto& elt : form->elts()) {
elt->push_to_stack(env, pool, temp_stack);
}
std::vector<FormElement*> new_entries;
if (form == entry.body && rewrite_as_set && !set_unused) {
new_entries = temp_stack.rewrite_to_get_var(pool, *last_var, env);
} else {
new_entries = temp_stack.rewrite(pool);
}
form->clear();
for (auto e : new_entries) {
form->push_back(e);
}
}
}
// process else.
FormStack temp_stack;
for (auto& elt : else_ir->elts()) {
elt->push_to_stack(env, pool, temp_stack);
}
std::vector<FormElement*> new_entries;
// rewrite extra sets as needed.
if (rewrite_as_set && !set_unused) {
new_entries = temp_stack.rewrite_to_get_var(pool, *last_var, env);
} else {
new_entries = temp_stack.rewrite(pool);
}
else_ir->clear();
for (auto e : new_entries) {
else_ir->push_back(e);
for (auto& entry : entries) {
rewrite_to_get_var(entry.body->elts(), pool, *last_var);
}
rewrite_to_get_var(else_ir->elts(), pool, *last_var);
}
// raise expression.
@ -1360,14 +1404,14 @@ void ShortCircuitElement::push_to_stack(const Env& env, FormPool& pool, FormStac
}
for (int i = 0; i < int(entries.size()); i++) {
auto& entry = entries.at(i);
FormStack temp_stack;
FormStack temp_stack(false);
for (auto& elt : entry.condition->elts()) {
elt->push_to_stack(env, pool, temp_stack);
}
std::vector<FormElement*> new_entries;
if (i == int(entries.size()) - 1) {
new_entries = temp_stack.rewrite_to_get_var(pool, final_result, env);
new_entries = rewrite_to_get_var(temp_stack, pool, final_result);
} else {
new_entries = temp_stack.rewrite(pool);
}
@ -1406,14 +1450,14 @@ void ShortCircuitElement::update_from_stack(const Env& env,
}
for (int i = 0; i < int(entries.size()); i++) {
auto& entry = entries.at(i);
FormStack temp_stack;
FormStack temp_stack(false);
for (auto& elt : entry.condition->elts()) {
elt->push_to_stack(env, pool, temp_stack);
}
std::vector<FormElement*> new_entries;
if (i == int(entries.size()) - 1) {
new_entries = temp_stack.rewrite_to_get_var(pool, final_result, env);
new_entries = rewrite_to_get_var(temp_stack, pool, final_result);
} else {
new_entries = temp_stack.rewrite(pool);
}
@ -1629,13 +1673,13 @@ void ConditionElement::update_from_stack(const Env& env,
}
void ReturnElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
FormStack temp_stack;
FormStack temp_stack(false);
for (auto& elt : return_code->elts()) {
elt->push_to_stack(env, pool, temp_stack);
}
std::vector<FormElement*> new_entries;
new_entries = temp_stack.rewrite_to_get_var(pool, env.end_var(), env);
new_entries = rewrite_to_get_var(temp_stack, pool, env.end_var());
return_code->clear();
for (auto e : new_entries) {
@ -1865,7 +1909,33 @@ void ArrayFieldAccess::update_from_stack(const Env& env,
auto deref = pool.alloc_element<DerefElement>(base, false, tokens);
result->push_back(deref);
} else {
throw std::runtime_error("Not power of two case, not yet implemented (offset)");
// (+ v0-0 (the-as uint (* 12 (+ a3-0 -1))))
auto mult_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION),
{Matcher::integer(m_expected_stride), Matcher::any(0)});
mult_matcher = Matcher::match_or({Matcher::cast("uint", mult_matcher), mult_matcher});
auto add_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::ADDITION),
{Matcher::any(1), mult_matcher});
auto mr = match(add_matcher, new_val);
if (!mr.matched) {
throw std::runtime_error("Failed to match non-power of two case: " +
new_val->to_string(env));
}
auto base = mr.maps.forms.at(1);
auto idx = mr.maps.forms.at(0);
assert(idx && base);
std::vector<DerefToken> tokens = m_deref_tokens;
for (auto& x : tokens) {
if (x.kind() == DerefToken::Kind::EXPRESSION_PLACEHOLDER) {
x = DerefToken::make_int_expr(idx);
}
}
auto deref = pool.alloc_element<DerefElement>(base, false, tokens);
result->push_back(deref);
}
}
}
@ -1904,8 +1974,29 @@ void EmptyElement::push_to_stack(const Env&, FormPool&, FormStack& stack) {
stack.push_form_element(this, true);
}
void ConditionalMoveFalseElement::push_to_stack(const Env&, FormPool&, FormStack& stack) {
stack.push_form_element(this, true);
bool is_symbol_true(const Form* form) {
auto as_simple = dynamic_cast<SimpleExpressionElement*>(form->try_as_single_element());
if (as_simple && as_simple->expr().is_identity() && as_simple->expr().get_arg(0).is_sym_ptr() &&
as_simple->expr().get_arg(0).get_str() == "#t") {
return true;
}
return false;
}
void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
// pop the value and the original
auto popped = pop_to_forms({old_value, source}, env, pool, stack, true);
if (!is_symbol_true(popped.at(0))) {
throw std::runtime_error("Got unrecognized ConditionalMoveFalseElement original: " +
popped.at(0)->to_string(env));
}
stack.push_value_to_reg(dest,
pool.alloc_single_element_form<GenericElement>(
nullptr,
GenericOperator::make_compare(on_zero ? IR2_Condition::Kind::NONZERO
: IR2_Condition::Kind::ZERO),
std::vector<Form*>{popped.at(1)}),
true);
}
void SimpleAtomElement::push_to_stack(const Env&, FormPool&, FormStack& stack) {

View file

@ -75,8 +75,9 @@ void FormStack::push_form_element(FormElement* elt, bool sequence_point) {
Form* FormStack::pop_reg(const Variable& var,
const RegSet& barrier,
const Env& env,
bool allow_side_effects) {
return pop_reg(var.reg(), barrier, env, allow_side_effects);
bool allow_side_effects,
int begin_idx) {
return pop_reg(var.reg(), barrier, env, allow_side_effects, begin_idx);
}
namespace {
@ -91,10 +92,15 @@ bool nonempty_intersection(const RegSet& a, const RegSet& b) {
Form* FormStack::pop_reg(Register reg,
const RegSet& barrier,
const Env& env,
bool allow_side_effects) {
bool allow_side_effects,
int begin_idx) {
(void)env; // keep this for easy debugging.
RegSet modified;
for (size_t i = m_stack.size(); i-- > 0;) {
size_t begin = m_stack.size();
if (begin_idx >= 0) {
begin = begin_idx;
}
for (size_t i = begin; i-- > 0;) {
auto& entry = m_stack.at(i);
if (entry.active) {
if (entry.destination.has_value() && entry.destination->reg() == reg) {
@ -111,7 +117,7 @@ Form* FormStack::pop_reg(Register reg,
assert(entry.source);
if (entry.non_seq_source.has_value()) {
assert(entry.sequence_point == false);
auto result = pop_reg(entry.non_seq_source->reg(), barrier, env, allow_side_effects);
auto result = pop_reg(entry.non_seq_source->reg(), barrier, env, allow_side_effects, i);
if (result) {
return result;
}
@ -151,12 +157,12 @@ Form* FormStack::unsafe_peek(Register reg) {
for (size_t i = m_stack.size(); i-- > 0;) {
auto& entry = m_stack.at(i);
if (entry.active) {
return nullptr;
throw std::runtime_error("Failed to unsafe peek 1");
}
entry.source->get_modified_regs(modified);
if (modified.find(reg) != modified.end()) {
return nullptr;
throw std::runtime_error("Failed to unsafe peek 2");
}
if (entry.destination.has_value() && entry.destination->reg() == reg) {
@ -186,15 +192,9 @@ std::vector<FormElement*> FormStack::rewrite(FormPool& pool) {
return result;
}
std::vector<FormElement*> FormStack::rewrite_to_get_var(FormPool& pool,
const Variable& var,
const Env&) {
// first, rewrite as normal.
auto default_result = rewrite(pool);
// try a few different ways to "naturally" rewrite this so the value of the form is the
// value in the given register.
void rewrite_to_get_var(std::vector<FormElement*>& default_result,
FormPool& pool,
const Variable& var) {
auto last_op_as_set = dynamic_cast<SetVarElement*>(default_result.back());
if (last_op_as_set && last_op_as_set->dst().reg() == var.reg()) {
default_result.pop_back();
@ -202,10 +202,17 @@ std::vector<FormElement*> FormStack::rewrite_to_get_var(FormPool& pool,
form->parent_form = nullptr; // will get set later, this makes it obvious if I forget.
default_result.push_back(form);
}
return default_result;
} else {
default_result.push_back(pool.alloc_element<SimpleAtomElement>(SimpleAtom::make_var(var)));
return default_result;
}
}
std::vector<FormElement*> rewrite_to_get_var(FormStack& stack,
FormPool& pool,
const Variable& var) {
auto default_result = stack.rewrite(pool);
rewrite_to_get_var(default_result, pool, var);
return default_result;
}
} // namespace decompiler

View file

@ -12,7 +12,7 @@ class Form;
*/
class FormStack {
public:
FormStack() = default;
explicit FormStack(bool is_root_stack) : m_is_root_stack(is_root_stack) {}
void push_value_to_reg(Variable var,
Form* value,
bool sequence_point,
@ -25,13 +25,18 @@ class FormStack {
Form* pop_reg(const Variable& var,
const RegSet& barrier,
const Env& env,
bool allow_side_effects);
Form* pop_reg(Register reg, const RegSet& barrier, const Env& env, bool allow_side_effects);
bool allow_side_effects,
int begin_idx = -1);
Form* pop_reg(Register reg,
const RegSet& barrier,
const Env& env,
bool allow_side_effects,
int begin_idx = -1);
Form* unsafe_peek(Register reg);
bool is_single_expression();
std::vector<FormElement*> rewrite(FormPool& pool);
std::vector<FormElement*> rewrite_to_get_var(FormPool& pool, const Variable& var, const Env& env);
std::string print(const Env& env);
bool is_root() const { return m_is_root_stack; }
private:
struct StackEntry {
@ -49,5 +54,11 @@ class FormStack {
std::string print(const Env& env) const;
};
std::vector<StackEntry> m_stack;
bool m_is_root_stack = false;
};
void rewrite_to_get_var(std::vector<FormElement*>& default_result,
FormPool& pool,
const Variable& var);
std::vector<FormElement*> rewrite_to_get_var(FormStack& stack, FormPool& pool, const Variable& var);
} // namespace decompiler

View file

@ -603,9 +603,9 @@ std::unique_ptr<AtomicOp> convert_sd_1(const Instruction& i0, int idx) {
// movn or movz
std::unique_ptr<AtomicOp> convert_cmov_1(const Instruction& i0, int idx) {
if (i0.get_src(0).is_reg(rs7())) {
return std::make_unique<ConditionalMoveFalseOp>(make_dst_var(i0, idx),
make_src_var(i0.get_src(1).get_reg(), idx),
i0.kind == InstructionKind::MOVZ, idx);
return std::make_unique<ConditionalMoveFalseOp>(
make_dst_var(i0, idx), make_src_var(i0.get_src(1).get_reg(), idx),
make_src_var(i0.get_dst(0).get_reg(), idx), i0.kind == InstructionKind::MOVZ, idx);
} else {
return nullptr;
}

View file

@ -61,7 +61,7 @@ bool convert_to_expressions(Form* top_level_form,
// fmt::print("Before anything:\n{}\n",
// pretty_print::to_string(top_level_form->to_form(f.ir2.env)));
try {
FormStack stack;
FormStack stack(true);
for (auto& entry : top_level_form->elts()) {
// fmt::print("push {} to stack\n", entry->to_form(f.ir2.env).print());
entry->push_to_stack(f.ir2.env, pool, stack);
@ -70,7 +70,7 @@ bool convert_to_expressions(Form* top_level_form,
std::vector<FormElement*> new_entries;
if (f.type.last_arg() != TypeSpec("none")) {
auto return_var = f.ir2.atomic_ops->end_op().return_var();
new_entries = stack.rewrite_to_get_var(pool, return_var, f.ir2.env);
new_entries = rewrite_to_get_var(stack, pool, return_var);
auto reg_return_type =
f.ir2.env.get_types_after_op(f.ir2.atomic_ops->ops.size() - 1).get(return_var.reg());
if (!dts.ts.typecheck(f.type.last_arg(), reg_return_type.typespec(), "", false, false)) {

View file

@ -77,7 +77,7 @@
"(method 14 dead-pool)":[
// bug in game!
[25, ["v1", "(pointer process-tree)"]],
[24, ["v1", "(pointer process-tree)"]],
[30, ["s4", "(pointer process-tree)"]]
],

View file

@ -194,6 +194,10 @@
"vars":{"v1-1":"in-goal-mem"}
},
"(method 23 dead-pool-heap)":{
"args":["this", "rec"]
},
"seek":{
"args":["x", "target", "diff"],
"vars":{"f2-0":"err"}

View file

@ -272,6 +272,11 @@ TP_Type DecompilerTypeSystem::tp_lca(const TP_Type& existing,
return TP_Type::make_from_ts("int");
}
case TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR_MULT:
// a bit lazy here, but I don't think you can ever merge these.
*changed = true;
return TP_Type::make_from_ts("int");
case TP_Type::Kind::METHOD:
// never allow this to remain method
*changed = true;

View file

@ -51,6 +51,8 @@ std::string TP_Type::print() const {
return fmt::format("<integer {}>", m_int);
case Kind::INTEGER_CONSTANT_PLUS_VAR:
return fmt::format("<integer {} + {}>", m_int, m_ts.print());
case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT:
return fmt::format("<integer {} + ({} x {})>", m_int, m_extra_multiplier, m_ts.print());
case Kind::DYNAMIC_METHOD_ACCESS:
return fmt::format("<dynamic-method-access>");
case Kind::METHOD:
@ -95,6 +97,9 @@ bool TP_Type::operator==(const TP_Type& other) const {
return m_int == other.m_int && m_ts == other.m_ts;
case Kind::DYNAMIC_METHOD_ACCESS:
return true;
case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT:
return m_int == other.m_int && m_ts == other.m_ts &&
m_extra_multiplier == other.m_extra_multiplier;
case Kind::INVALID:
default:
assert(false);
@ -133,6 +138,7 @@ TypeSpec TP_Type::typespec() const {
case Kind::INTEGER_CONSTANT:
return TypeSpec("int");
case Kind::INTEGER_CONSTANT_PLUS_VAR:
case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT:
return m_ts;
case Kind::DYNAMIC_METHOD_ACCESS:
return TypeSpec("object");

View file

@ -26,9 +26,9 @@ class TP_Type {
STRING_CONSTANT, // a string that's part of the string pool
FORMAT_STRING, // a string with a given number of format arguments
INTEGER_CONSTANT, // a constant integer.
INTEGER_CONSTANT_PLUS_VAR, // constant + variable. used in stuff like (&-> obj inline-val-arr
// x)
DYNAMIC_METHOD_ACCESS, // partial access into a
INTEGER_CONSTANT_PLUS_VAR, // constant + variable. for dynamic addr of
INTEGER_CONSTANT_PLUS_VAR_MULT, // like var + 100 + 12 * var2
DYNAMIC_METHOD_ACCESS, // partial access into a
METHOD,
INVALID
} kind = Kind::UNINITIALIZED;
@ -130,6 +130,17 @@ class TP_Type {
return result;
}
static TP_Type make_from_integer_constant_plus_product(int64_t constant,
const TypeSpec& var_type,
int64_t multiplier) {
TP_Type result;
result.kind = Kind::INTEGER_CONSTANT_PLUS_VAR_MULT;
result.m_int = constant;
result.m_extra_multiplier = multiplier;
result.m_ts = var_type;
return result;
}
static TP_Type make_from_product(int64_t multiplier, bool is_signed) {
TP_Type result;
result.kind = Kind::PRODUCT_WITH_CONSTANT;
@ -189,10 +200,22 @@ class TP_Type {
return m_int;
}
u64 get_add_int_constant() const {
assert(kind == Kind::INTEGER_CONSTANT_PLUS_VAR_MULT);
return m_int;
}
u64 get_mult_int_constant() const {
assert(kind == Kind::INTEGER_CONSTANT_PLUS_VAR_MULT);
return m_extra_multiplier;
}
private:
TypeSpec m_ts;
std::string m_str;
int64_t m_int = 0;
int64_t m_extra_multiplier = 0;
};
struct TypeState {

View file

@ -23,4 +23,16 @@
- Fixed a bug where integer `abs` appeared instead of `fabs`.
- Some support for float -> integer conversions, but it is not 100% yet.
- Eliminate inserted coloring moves for function arguments that use `mtc1`.
- Support for `>=` for signed numbers.
- Support for `>=` for signed numbers.
## Version 4
- Fix bug in decoding of `vdiv`, `vsqrt`, and `vrsqrt` instructions
- Support for virtual method calls (may not recognize 100% of cases yet)
- Support for "weird" new calls
- Fixed a few "update from stack NYI" errors
- Array access recognized in more cases with power of two stride.
- Support for getting the address of something in an inline array with a stride that's not 1 or a power of 2.
- Fixed a bug in unscrambling coloring moves which sometimes caused the wrong values to be used.
- Improved nested cond rewriting to eliminate temporaries in more cases when used as a value
- Support `zero?` and `nonzero?` which are evaluated to GOAL booleans.
- Fix bug where method calls that "passed through" `a0` from the caller were not recognized.

View file

@ -530,8 +530,8 @@ TEST(DecompilerAtomicOpBuilder, MINS) {
}
TEST(DecompilerAtomicOpBuilder, MOVN) {
test_case(assembly_from_list({"movn a1, s7, a2"}), {"(cmove-#f-nonzero a1 a2)"}, {{"a1"}},
{{"a2"}}, {{}});
test_case(assembly_from_list({"movn a1, s7, a2"}), {"(cmove-#f-nonzero a1 a2 a1)"}, {{"a1"}},
{{"a2", "a1"}}, {{}});
}
TEST(DecompilerAtomicOpBuilder, MOVS) {
@ -539,8 +539,8 @@ TEST(DecompilerAtomicOpBuilder, MOVS) {
}
TEST(DecompilerAtomicOpBuilder, MOVZ) {
test_case(assembly_from_list({"movz a1, s7, a2"}), {"(cmove-#f-zero a1 a2)"}, {{"a1"}}, {{"a2"}},
{{}});
test_case(assembly_from_list({"movz a1, s7, a2"}), {"(cmove-#f-zero a1 a2 a1)"}, {{"a1"}},
{{"a2", "a1"}}, {{}});
}
TEST(DecompilerAtomicOpBuilder, MTC1) {

View file

@ -945,149 +945,747 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPool) {
test_with_expr(func, type, expected, false, "dead-pool");
}
// TODO - sketchy types, and likely a bug in the game.
// TEST_F(FormRegressionTest, ExprMethod14DeadPool) {
TEST_F(FormRegressionTest, ExprMethod14DeadPool) {
std::string func =
" sll r0, r0, 0\n"
" daddiu sp, sp, -64\n"
" sd ra, 0(sp)\n"
" sd fp, 8(sp)\n"
" or fp, t9, r0\n"
" sq s4, 16(sp)\n"
" sq s5, 32(sp)\n"
" sq gp, 48(sp)\n"
" or s5, a0, r0\n"
" or gp, a1, r0\n"
" lwu s4, 16(s5)\n"
" bnel s7, s4, L223\n"
" or v1, s7, r0\n"
" lw v1, *debug-segment*(s7)\n"
" beql s7, v1, L223\n"
" or v1, v1, r0\n"
" lw v1, *debug-dead-pool*(s7)\n"
" dsubu a0, s5, v1\n"
" daddiu v1, s7, 8\n"
" movz v1, s7, a0\n"
"L223:\n"
" beq s7, v1, L225\n"
" or v1, s7, r0\n"
" lw a0, *debug-dead-pool*(s7)\n"
" lwu v1, -4(a0)\n"
" lwu t9, 72(v1)\n"
" or a1, gp, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" or s4, v0, r0\n"
" beq s7, s4, L225\n"
" or v1, s7, r0\n"
" lw t9, format(s7)\n"
" addiu a0, r0, 0\n"
" daddiu a1, fp, L315\n"
" or a2, gp, r0\n"
" or v1, s4, r0\n"
" beq s7, v1, L224\n"
" or a3, s7, r0\n"
" lwu v1, 0(v1)\n"
" lwu a3, 24(v1)\n"
"L224:\n"
" lwu t0, 0(s5)\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
"\n"
" or v1, v0, r0\n"
"L225:\n"
" beq s7, s4, L226\n"
" sll r0, r0, 0\n"
" lwu v1, 0(s4)\n"
" sw gp, -4(v1)\n"
" lwu v0, 0(s4)\n"
" beq r0, r0, L228\n"
" sll r0, r0, 0\n"
"L226:\n"
" lw t9, format(s7)\n"
" addiu a0, r0, 0\n"
" daddiu a1, fp, L314\n"
" beq s7, s4, L227\n"
" or a3, s7, r0\n"
" lwu v1, 0(s4)\n"
" lwu a3, 24(v1)\n"
"L227:\n"
" lwu t0, 0(s5)\n"
" or a2, gp, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" or v0, s7, r0\n"
"L228:\n"
" ld ra, 0(sp)\n"
" ld fp, 8(sp)\n"
" lq gp, 48(sp)\n"
" lq s5, 32(sp)\n"
" lq s4, 16(sp)\n"
" jr ra\n"
" daddiu sp, sp, 64";
std::string type = "(function dead-pool type int process)";
std::string expected =
"(begin\n"
" (set! s4-0 (-> arg0 child))\n"
" (when\n"
" (and (not s4-0) *debug-segment* (!= arg0 *debug-dead-pool*))\n"
" (set! s4-0 (get-process *debug-dead-pool* arg1 arg2))\n"
" (when\n"
" s4-0\n"
" (set! t9-1 format)\n"
" (set! a0-2 0)\n"
" (set!\n"
" a1-2\n"
" \"WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%\"\n"
" )\n"
" (set! a2-1 arg1)\n"
" (set! v1-6 s4-0)\n"
" (t9-1\n"
" a0-2\n"
" a1-2\n"
" a2-1\n"
" (if v1-6 (-> (the-as (pointer process-tree) v1-6) 0 self))\n"
" (-> arg0 name)\n"
" )\n"
" )\n"
" )\n"
" (the-as\n"
" process\n"
" (cond\n"
" (s4-0\n"
" (set! (-> (the-as (pointer process-tree) s4-0) 0 type) arg1)\n"
" (-> s4-0 0)\n"
" )\n"
" (else\n"
" (format\n"
" 0\n"
" \"WARNING: ~A ~A could not be allocated, because ~A was empty.~%\"\n"
" arg1\n"
" (if s4-0 (-> s4-0 0 self))\n"
" (-> arg0 name)\n"
" )\n"
" (quote #f)\n"
" )\n"
" )\n"
" )\n"
" )";
// note - there's likely an actual bug here.
test_with_expr(
func, type, expected, false, "dead-pool",
{{"L315", "WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%"},
{"L314", "WARNING: ~A ~A could not be allocated, because ~A was empty.~%"}},
parse_hint_json("[\t\t[24, [\"v1\", \"(pointer process-tree)\"]],\n"
"\t\t[30, [\"s4\", \"(pointer process-tree)\"]]]"));
}
TEST_F(FormRegressionTest, ExprMethod15DeadPool) {
std::string func =
" sll r0, r0, 0\n"
" daddiu sp, sp, -16\n"
" sd ra, 0(sp)\n"
" lw t9, change-parent(s7)\n"
" or v1, a1, r0\n"
" or a1, a0, r0\n"
" or a0, v1, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" ld ra, 0(sp)\n"
" jr ra\n"
" daddiu sp, sp, 16";
std::string type = "(function dead-pool process none)";
std::string expected = "(change-parent arg1 arg0)";
test_with_expr(func, type, expected);
}
TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) {
std::string func =
" sll r0, r0, 0\n"
" daddiu sp, sp, -64\n"
" sd ra, 0(sp)\n"
" sq s4, 16(sp)\n"
" sq s5, 32(sp)\n"
" sq gp, 48(sp)\n"
" or s4, a2, r0\n"
" or s5, a3, r0\n"
" or gp, t0, r0\n"
" lw v1, object(s7)\n"
" lwu t9, 16(v1)\n"
" or v1, a1, r0\n"
" lhu a1, 8(a1)\n"
" addiu a2, r0, -16\n"
" addiu a3, r0, 12\n"
" mult3 a3, a3, s5\n"
" daddiu a3, a3, 15\n"
" and a2, a2, a3\n"
" daddu a1, a1, a2\n"
" daddu a2, a1, gp\n"
" or a1, v1, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" sw s4, 0(v0)\n"
" addiu v1, r0, 256\n"
" sw v1, 4(v0)\n"
" sw s5, 28(v0)\n"
" sw s7, 8(v0)\n"
" sw s7, 12(v0)\n"
" sw s7, 16(v0)\n"
" sw v0, 24(v0)\n"
" daddiu v1, v0, 24\n"
" sw v1, 20(v0)\n"
" or v1, s5, r0\n"
" beq r0, r0, L220\n"
" sll r0, r0, 0\n"
"L219:\n"
" daddiu v1, v1, -1\n"
" addiu a0, r0, 12\n"
" mult3 a0, a0, v1\n"
" daddiu a0, a0, 100\n"
" daddu a0, a0, v0\n"
" lw a1, *null-process*(s7)\n"
" sw a1, 0(a0)\n"
" addiu a1, r0, 12\n"
" daddiu a2, v1, 1\n"
" mult3 a1, a1, a2\n"
" daddiu a1, a1, 100\n"
" daddu a1, a1, v0\n"
" sw a1, 8(a0)\n"
"L220:\n"
" bne v1, r0, L219\n"
" sll r0, r0, 0\n"
" or v1, s7, r0\n"
" or v1, s7, r0\n"
" daddiu v1, v0, 100\n"
" sw v1, 96(v0)\n"
" sw s7, 76(v0)\n"
" addiu v1, r0, 12\n"
" daddiu a0, s5, -1\n"
" mult3 v1, v1, a0\n"
" daddu v1, v0, v1\n"
" sw s7, 108(v1)\n"
" daddiu v1, v0, 76\n"
" sw v1, 80(v0)\n"
" sw s7, 84(v0)\n"
" sw s7, 76(v0)\n"
" daddiu v1, v0, 76\n"
" sw v1, 48(v0)\n"
" sw s7, 52(v0)\n"
" addiu v1, r0, -16\n"
" daddiu a0, v0, 115\n"
" addiu a1, r0, 12\n"
" mult3 a1, a1, s5\n"
" daddu a0, a0, a1\n"
" and v1, v1, a0\n"
" sw v1, 60(v0)\n"
" lwu v1, 60(v0)\n"
" sw v1, 68(v0)\n"
" lwu v1, 60(v0)\n"
" daddu v1, v1, gp\n"
" sw v1, 64(v0)\n"
" lwu v1, 64(v0)\n"
" sw v1, 72(v0)\n"
" ld ra, 0(sp)\n"
" lq gp, 48(sp)\n"
" lq s5, 32(sp)\n"
" lq s4, 16(sp)\n"
" jr ra\n"
" daddiu sp, sp, 64";
std::string type = "(function symbol type basic int int dead-pool-heap)";
std::string expected =
"(begin\n"
" (set!\n"
" v0-0\n"
" (object-new\n"
" arg0\n"
" arg1\n"
" (the-as\n"
" int\n"
" (+\n"
" (+ (-> arg1 size) (the-as uint (logand -16 (+ (* 12 arg3) 15))))\n"
" (the-as uint arg4)\n"
" )\n"
" )\n"
" )\n"
" )\n"
" (set! (-> v0-0 name) arg2)\n"
" (set! (-> v0-0 mask) 256)\n"
" (set! (-> v0-0 allocated-length) arg3)\n"
" (set! (-> v0-0 parent) (quote #f))\n"
" (set! (-> v0-0 brother) (quote #f))\n"
" (set! (-> v0-0 child) (quote #f))\n"
" (set! (-> v0-0 self) v0-0)\n"
" (set! (-> v0-0 ppointer) (&-> v0-0 self))\n"
" (set! v1-4 arg3)\n"
" (while\n"
" (nonzero? v1-4)\n"
" (set! v1-4 (+ v1-4 -1))\n"
" (set! a0-4 (-> v0-0 process-list v1-4))\n"
" (set! (-> a0-4 process) *null-process*)\n"
" (set! (-> a0-4 next) (-> v0-0 process-list (+ v1-4 1)))\n"
" )\n"
" (set! (-> v0-0 dead-list next) (-> v0-0 process-list))\n"
" (set! (-> v0-0 alive-list process) (quote #f))\n"
" (set! (-> v0-0 process-list (+ arg3 -1) next) (quote #f))\n"
" (set! (-> v0-0 alive-list prev) (-> v0-0 alive-list))\n"
" (set! (-> v0-0 alive-list next) (quote #f))\n"
" (set! (-> v0-0 alive-list process) (quote #f))\n"
" (set! (-> v0-0 first-gap) (-> v0-0 alive-list))\n"
" (set! (-> v0-0 first-shrink) (quote #f))\n"
" (set!\n"
" (-> v0-0 heap base)\n"
" (logand\n"
" -16\n"
" (the-as int (+ (+ (the-as int v0-0) 115) (the-as uint (* 12 arg3))))\n"
" )\n"
" )\n"
" (set! (-> v0-0 heap current) (-> v0-0 heap base))\n"
" (set! (-> v0-0 heap top) (+ (-> v0-0 heap base) (the-as uint arg4)))\n"
" (set! (-> v0-0 heap top-base) (-> v0-0 heap top))\n"
" v0-0\n"
" )";
test_with_expr(func, type, expected, false, "dead-pool-heap", {},
parse_hint_json("[\t\t[60, [\"v0\", \"int\"]],\n"
"\t\t[61, [\"a0\", \"pointer\"], [\"v0\", \"dead-pool-heap\"]]]"));
}
TEST_F(FormRegressionTest, ExprMethod22DeadPoolHeap) {
std::string func =
" sll r0, r0, 0\n"
" lwu v1, 0(a1)\n"
" beq s7, v1, L216\n"
" sll r0, r0, 0\n"
" lwu v1, 0(a1)\n"
" lw v1, 68(v1)\n"
" daddiu v1, v1, -4\n"
" lw a0, process(s7)\n"
" lhu a0, 8(a0)\n"
" daddu v1, v1, a0\n"
" lwu a0, 0(a1)\n"
" daddu v0, v1, a0\n"
" beq r0, r0, L217\n"
" sll r0, r0, 0\n"
"L216:\n"
" lwu v0, 60(a0)\n"
"L217:\n"
" jr ra\n"
" daddu sp, sp, r0";
std::string type = "(function dead-pool-heap dead-pool-heap-rec pointer)";
std::string expected =
"(if\n"
" (-> arg1 process)\n"
" (+\n"
" (+ (+ (-> arg1 process allocated-length) -4) (the-as int (-> process size)))\n"
" (the-as int (-> arg1 process))\n"
" )\n"
" (-> arg0 heap base)\n"
" )";
test_with_expr(func, type, expected);
}
TEST_F(FormRegressionTest, ExprMethod21DeadPoolHeap) {
std::string func =
" sll r0, r0, 0\n"
"L209:\n"
" lwu v1, 0(a1)\n"
" beq s7, v1, L212\n"
" sll r0, r0, 0\n"
" lwu v1, 0(a1)\n"
" lw a2, process(s7)\n"
" lhu a2, 8(a2)\n"
" daddu v1, v1, a2\n"
" lwu a2, 0(a1)\n"
" lw a2, 68(a2)\n"
" daddu v1, v1, a2\n"
" lwu a2, 8(a1)\n"
" beq s7, a2, L210\n"
" sll r0, r0, 0\n"
" lwu a0, 8(a1)\n"
" lwu a0, 0(a0)\n"
" dsubu v0, a0, v1\n"
" beq r0, r0, L211\n"
" sll r0, r0, 0\n"
"L210:\n"
" lwu a0, 64(a0)\n"
" daddiu v1, v1, 4\n"
" dsubu v0, a0, v1\n"
"L211:\n"
" beq r0, r0, L214\n"
" sll r0, r0, 0\n"
"L212:\n"
" lwu v1, 8(a1)\n"
" beq s7, v1, L213\n"
" sll r0, r0, 0\n"
" lwu v1, 8(a1)\n"
" lwu v1, 0(v1)\n"
" lwu a0, 60(a0)\n"
" daddiu a0, a0, 4\n"
" dsubu v0, v1, a0\n"
" beq r0, r0, L214\n"
" sll r0, r0, 0\n"
"L213:\n"
" lwu v1, 64(a0)\n"
" lwu a0, 60(a0)\n"
" dsubu v0, v1, a0\n"
"L214:\n"
" jr ra\n"
" daddu sp, sp, r0";
std::string type = "(function dead-pool-heap dead-pool-heap-rec int)";
std::string expected =
"(cond\n"
" ((-> arg1 process)\n"
" (set!\n"
" v1-3\n"
" (+\n"
" (+ (-> arg1 process) (the-as uint (-> process size)))\n"
" (the-as uint (-> arg1 process allocated-length))\n"
" )\n"
" )\n"
" (if\n"
" (-> arg1 next)\n"
" (- (-> arg1 next process) (the-as uint v1-3))\n"
" (- (-> arg0 heap top) (the-as uint (+ v1-3 (the-as uint 4))))\n"
" )\n"
" )\n"
" (else\n"
" (if\n"
" (-> arg1 next)\n"
" (-\n"
" (-> arg1 next process)\n"
" (the-as uint (+ (-> arg0 heap base) (the-as uint 4)))\n"
" )\n"
" (- (-> arg0 heap top) (the-as uint (-> arg0 heap base)))\n"
" )\n"
" )\n"
" )";
test_with_expr(func, type, expected, false, "", {},
parse_hint_json("[\t\t[5, [\"v1\", \"pointer\"]],\n"
"\t\t[13, [\"a0\", \"pointer\"]],\n"
"\t\t[25, [\"v1\", \"pointer\"]]]"));
}
TEST_F(FormRegressionTest, ExprMethod3DeadPoolHeap) {
std::string func =
" sll r0, r0, 0\n"
" daddiu sp, sp, -128\n"
" sd ra, 0(sp)\n"
" sd fp, 8(sp)\n"
" or fp, t9, r0\n"
" sq s0, 16(sp)\n"
" sq s1, 32(sp)\n"
" sq s2, 48(sp)\n"
" sq s3, 64(sp)\n"
" sq s4, 80(sp)\n"
" sq s5, 96(sp)\n"
" sq gp, 112(sp)\n"
" or gp, a0, r0\n"
// CUT HERE
" lwu v1, 64(gp)\n"
" lwu a0, 60(gp)\n"
" dsubu s5, v1, a0\n"
" lwu v1, 80(gp)\n"
" beq s7, v1, L199\n"
" sll r0, r0, 0\n"
" or a0, gp, r0\n"
" lwu v1, -4(a0)\n"
" lwu t9, 100(v1)\n"
" lwu a1, 80(gp)\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" or v1, v0, r0\n"
" beq r0, r0, L200\n"
" sll r0, r0, 0\n"
"L199:\n"
" or v1, s5, r0\n"
"L200:\n"
" lw t9, format(s7)\n"
" daddiu a0, s7, #t\n"
" daddiu a1, fp, L300\n"
" daddiu a2, gp, 100\n"
" dsubu a3, s5, v1\n"
" or t0, s5, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" or v1, v0, r0\n"
" daddiu s5, gp, 76\n"
" addiu s4, r0, 0\n"
" beq r0, r0, L204\n"
" sll r0, r0, 0\n"
"L201:\n"
" lwu v1, 0(s5)\n"
" beq s7, v1, L202\n"
" or v1, s7, r0\n"
" lw t9, format(s7)\n"
" daddiu a0, s7, #t\n"
" daddiu a1, fp, L299\n"
" or a2, s4, r0\n"
" or a3, s5, r0\n"
" lwu t0, 0(s5)\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
"\n"
" or v1, v0, r0\n"
"L202:\n"
" or a0, gp, r0\n"
" lwu v1, -4(a0)\n"
" lwu t9, 100(v1)\n"
" or a1, s5, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
"\n"
" or s3, v0, r0\n"
" beq s3, r0, L203\n"
" or v1, s7, r0\n"
" lw s2, format(s7)\n"
" daddiu s1, s7, #t\n"
" daddiu s0, fp, L298\n"
" or a0, gp, r0\n"
" lwu v1, -4(a0)\n"
" lwu t9, 104(v1)\n"
" or a1, s5, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" or a3, v0, r0\n"
" or t9, s2, r0\n"
" or a0, s1, r0\n"
" or a1, s0, r0\n"
" or a2, s3, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
"\n"
" or v1, v0, r0\n"
"L203:\n"
" lwu s5, 8(s5)\n"
" daddiu s4, s4, 1\n"
"L204:\n"
" bne s7, s5, L201\n"
" sll r0, r0, 0\n"
" or v1, s7, r0\n"
" or v0, gp, r0\n"
" ld ra, 0(sp)\n"
" ld fp, 8(sp)\n"
" lq gp, 112(sp)\n"
" lq s5, 96(sp)\n"
" lq s4, 80(sp)\n"
" lq s3, 64(sp)\n"
" lq s2, 48(sp)\n"
" lq s1, 32(sp)\n"
" lq s0, 16(sp)\n"
" jr ra\n"
" daddiu sp, sp, 128";
std::string type = "(function dead-pool-heap dead-pool-heap)";
std::string expected =
"(begin\n"
" (set! s5-0 (- (-> arg0 heap top) (the-as uint (-> arg0 heap base))))\n"
" (set!\n"
" v1-3\n"
" (if (-> arg0 alive-list prev) (gap-size arg0 (-> arg0 alive-list prev)) s5-0)\n"
" )\n"
" (format\n"
" (quote #t)\n"
" \"~Tprocess-list[0] @ #x~X ~D/~D bytes used~%\"\n"
" (-> arg0 process-list)\n"
" (- s5-0 v1-3)\n"
" s5-0\n"
" )\n"
" (set! s5-1 (-> arg0 alive-list))\n"
" (set! s4-0 0)\n"
" (while\n"
" s5-1\n"
" (if\n"
" (-> s5-1 process)\n"
" (format\n"
" (quote #t)\n"
" \"~T [~3D] #<dead-pool-heap-rec @ #x~X> ~A~%\"\n"
" s4-0\n"
" s5-1\n"
" (-> s5-1 process)\n"
" )\n"
" )\n"
" (set! s3-0 (gap-size arg0 s5-1))\n"
" (if\n"
" (nonzero? s3-0)\n"
" (format\n"
" (quote #t)\n"
" \"~T gap: ~D bytes @ #x~X~%\"\n"
" s3-0\n"
" (gap-location arg0 s5-1)\n"
" )\n"
" )\n"
" (set! s5-1 (-> s5-1 next))\n"
" (set! s4-0 (+ s4-0 1))\n"
" )\n"
" arg0\n"
" )";
test_with_expr(func, type, expected, false, "",
{{"L300", "~Tprocess-list[0] @ #x~X ~D/~D bytes used~%"},
{"L299", "~T [~3D] #<dead-pool-heap-rec @ #x~X> ~A~%"},
{"L298", "~T gap: ~D bytes @ #x~X~%"}});
}
TEST_F(FormRegressionTest, ExprMethod5DeadPoolHeap) {
std::string func =
" sll r0, r0, 0\n"
" addiu v1, r0, -4\n"
" dsubu v1, v1, a0\n"
" lwu a0, 64(a0)\n"
" daddu v0, v1, a0\n"
" jr ra\n"
" daddu sp, sp, r0";
std::string type = "(function dead-pool-heap int)";
std::string expected = "(+ (- -4 (the-as int arg0)) (-> arg0 heap top))";
test_with_expr(func, type, expected, false, "", {},
parse_hint_json("[[3, [\"v1\", \"int\"], [\"a0\", \"int\"]]]"));
}
TEST_F(FormRegressionTest, ExprMethod19DeadPoolHeap) {
std::string func =
" sll r0, r0, 0\n"
"L194:\n"
" daddiu sp, sp, -48\n"
" sd ra, 0(sp)\n"
" sq s5, 16(sp)\n"
" sq gp, 32(sp)\n"
" or gp, a0, r0\n"
" lwu v1, 80(gp)\n"
" beq s7, v1, L195\n"
" sll r0, r0, 0\n"
" or a0, gp, r0\n"
" lwu v1, -4(a0)\n"
" lwu t9, 96(v1)\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
"\n"
" or s5, v0, r0\n"
" or a0, gp, r0\n"
" lwu v1, -4(a0)\n"
" lwu t9, 100(v1)\n"
" lwu a1, 80(gp)\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
"\n"
" or v1, v0, r0\n"
" dsubu v0, s5, v1\n"
" beq r0, r0, L196\n"
" sll r0, r0, 0\n"
"L195:\n"
" addiu v0, r0, 0\n"
"L196:\n"
" ld ra, 0(sp)\n"
" lq gp, 32(sp)\n"
" lq s5, 16(sp)\n"
" jr ra\n"
" daddiu sp, sp, 48";
std::string type = "(function dead-pool-heap int)";
std::string expected =
"(if\n"
" (-> arg0 alive-list prev)\n"
" (- (memory-total arg0) (gap-size arg0 (-> arg0 alive-list prev)))\n"
" 0\n"
" )";
test_with_expr(func, type, expected);
}
TEST_F(FormRegressionTest, ExprMethod20DeadPoolHeap) {
std::string func =
" sll r0, r0, 0\n"
" lwu v1, 64(a0)\n"
" lwu a0, 60(a0)\n"
" dsubu v0, v1, a0\n"
" jr ra\n"
" daddu sp, sp, r0";
std::string type = "(function dead-pool-heap int)";
std::string expected = "(- (-> arg0 heap top) (the-as uint (-> arg0 heap base)))";
test_with_expr(func, type, expected);
}
//
// TEST_F(FormRegressionTest, ExprMethod25DeadPoolHeap) {
// std::string func =
// " sll r0, r0, 0\n"
// " daddiu sp, sp, -64\n"
// " daddiu sp, sp, -16\n"
// " sd ra, 0(sp)\n"
// " sd fp, 8(sp)\n"
// " or fp, t9, r0\n"
// " sq s4, 16(sp)\n"
// " sq s5, 32(sp)\n"
// " sq gp, 48(sp)\n"
//
// " or s5, a0, r0\n"
// " or gp, a1, r0\n"
// " lwu s4, 16(s5)\n"
// " bnel s7, s4, L223\n"
//
// " or v1, s7, r0\n"
//
// " lw v1, *debug-segment*(s7)\n"
// " beql s7, v1, L223\n"
//
// " or v1, v1, r0\n"
//
// " lw v1, *debug-dead-pool*(s7)\n"
// " dsubu a0, s5, v1\n"
// " daddiu v1, s7, 8\n"
// " movz v1, s7, a0\n"
//
// "L223:\n"
// " beq s7, v1, L225\n"
// " or v1, s7, r0\n"
//
// " lw a0, *debug-dead-pool*(s7)\n"
// " lwu v1, -4(a0)\n"
// " lwu t9, 72(v1)\n"
// " or a1, gp, r0\n"
// " jalr ra, t9\n"
// " sll v0, ra, 0\n"
//
// " or s4, v0, r0\n"
// " beq s7, s4, L225\n"
// " or v1, s7, r0\n"
//
// " lw t9, format(s7)\n"
// " addiu a0, r0, 0\n"
// " daddiu a1, fp, L315\n"
// " or a2, gp, r0\n"
// " or v1, s4, r0\n"
// " beq s7, v1, L224\n"
// " or a3, s7, r0\n"
//
// " lwu v1, 0(v1)\n"
// " lwu a3, 24(v1)\n"
//
// "L224:\n"
// " lwu t0, 0(s5)\n"
// " jalr ra, t9\n"
// " sll v0, ra, 0\n"
// "\n"
// " or v1, v0, r0\n"
//
// "L225:\n"
// " beq s7, s4, L226\n"
// " lwu v1, 64(a0)\n"
// " lwu a1, 80(a0)\n"
// " beq s7, a1, L191\n"
// " sll r0, r0, 0\n"
//
// " lwu v1, 0(s4)\n"
// " sw gp, -4(v1)\n"
// " lwu v0, 0(s4)\n"
// " beq r0, r0, L228\n"
// " sll r0, r0, 0\n"
//
// "L226:\n"
// " lw t9, format(s7)\n"
// " addiu a0, r0, 0\n"
// " daddiu a1, fp, L314\n"
// " beq s7, s4, L227\n"
// " or a3, s7, r0\n"
//
// " lwu v1, 0(s4)\n"
// " lwu a3, 24(v1)\n"
//
// "L227:\n"
// " lwu t0, 0(s5)\n"
// " or a2, gp, r0\n"
// " or v1, a0, r0\n"
// " lwu a1, -4(v1)\n"
// " lwu t9, 100(a1)\n"
// " lwu a1, 80(a0)\n"
// " or a0, v1, r0\n" - is eliminated falsely because of the virtual method thing.
// " jalr ra, t9\n"
// " sll v0, ra, 0\n"
//
// " or v0, s7, r0\n"
// " beq r0, r0, L192\n"
// " sll r0, r0, 0\n"
//
// "L228:\n"
// "L191:\n"
// " lwu a0, 60(a0)\n"
// " dsubu v0, v1, a0\n"
//
// "L192:\n"
// " ld ra, 0(sp)\n"
// " ld fp, 8(sp)\n"
// " lq gp, 48(sp)\n"
// " lq s5, 32(sp)\n"
// " lq s4, 16(sp)\n"
// " jr ra\n"
// " daddiu sp, sp, 64";
// std::string type = "(function dead-pool type int process)";
// std::string expected =
// "(begin\n"
// " (set! s4-0 (-> arg0 child))\n"
// " (when\n"
// " (and (not s4-0) *debug-segment* (!= arg0 *debug-dead-pool*))\n"
// " (set! s4-0 (get-process *debug-dead-pool* arg1 arg2))\n"
// " (when\n"
// " s4-0\n"
// " (set! t9-1 format)\n"
// " (set! a0-2 0)\n"
// " (set!\n"
// " a1-2\n"
// " \"WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%\"\n"
// " )\n"
// " (set! a2-1 arg1)\n"
// " (set! v1-6 s4-0)\n"
// " (t9-1 a0-2 a1-2 a2-1 (if v1-6 (-> v1-6 name 6)) (-> arg0 name))\n"
// " )\n"
// " )\n"
// " (the-as\n"
// " process\n"
// " (cond\n"
// " (s4-0\n"
// " (set! (-> (the-as (pointer process-tree) s4-0) 0 type) arg1)\n"
// " (-> s4-0 0)\n"
// " )\n"
// " (else\n"
// " (format\n"
// " 0\n"
// " \"WARNING: ~A ~A could not be allocated, because ~A was empty.~%\"\n"
// " arg1\n"
// " (if s4-0 (-> s4-0 0 self))\n"
// " (-> arg0 name)\n"
// " )\n"
// " (quote #f)\n"
// " )\n"
// " )\n"
// " )\n"
// " )";
// test_with_expr(
// func, type, expected, false, "dead-pool",
// {{"L315", "WARNING: ~A ~A had to be allocated from the debug pool, because ~A was
// empty.~%"},
// {"L314", "WARNING: ~A ~A could not be allocated, because ~A was empty.~%"}},
// parse_hint_json("[\t\t[25, [\"v1\", \"(pointer process-tree)\"]],\n"
// "\t\t[30, [\"s4\", \"(pointer process-tree)\"]]]"));
// " daddiu sp, sp, 16";
// std::string type = "(function dead-pool-heap int)";
// std::string expected = "blah";
// test_with_expr(func, type, expected);
//}