[decompiler] gkernel offline test (#321)

* working on pointer math

* bug fixes

* gkernel passing with no anon functions

* update tests
This commit is contained in:
water111 2021-03-14 16:11:42 -04:00 committed by GitHub
parent 814480f9e5
commit e93d97dd07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2576 additions and 278 deletions

View File

@ -144,7 +144,7 @@ bool Function::run_type_analysis_ir2(
} catch (std::runtime_error& e) {
lg::warn("Function {} failed type prop: {}", guessed_name.to_string(), e.what());
warnings.type_prop_warning("{}", e.what());
ir2.env.set_types(block_init_types, op_types, *ir2.atomic_ops);
ir2.env.set_types(block_init_types, op_types, *ir2.atomic_ops, my_type);
return false;
}
@ -198,7 +198,7 @@ bool Function::run_type_analysis_ir2(
}
}
ir2.env.set_types(block_init_types, op_types, *ir2.atomic_ops);
ir2.env.set_types(block_init_types, op_types, *ir2.atomic_ops, my_type);
return true;
}

View File

@ -107,6 +107,61 @@ FormElement* SetVarConditionOp::get_as_form(FormPool& pool, const Env& env) cons
is_sequence_point(), TypeSpec("symbol"));
}
namespace {
std::optional<TypeSpec> get_typecast_for_atom(const SimpleAtom& atom,
const Env& env,
const TypeSpec& expected_type,
int my_idx) {
auto type_info = env.dts->ts.lookup_type(expected_type);
switch (atom.get_kind()) {
case SimpleAtom::Kind::VARIABLE: {
auto src_type = env.get_types_before_op(my_idx).get(atom.var().reg());
if (src_type.requires_cast() || !env.dts->ts.tc(expected_type, src_type.typespec())) {
// we fail the typecheck for a normal set!, so add a cast.
return expected_type;
} else {
return {};
}
} break;
case SimpleAtom::Kind::INTEGER_CONSTANT: {
std::optional<TypeSpec> cast_for_set, cast_for_define;
bool sym_int_or_uint = env.dts->ts.tc(TypeSpec("integer"), expected_type);
bool sym_uint = env.dts->ts.tc(TypeSpec("uinteger"), expected_type);
bool sym_int = sym_int_or_uint && !sym_uint;
if (sym_int) {
// do nothing for set.
return {};
} else {
// for uint or other
return expected_type;
}
} break;
case SimpleAtom::Kind::SYMBOL_PTR:
case SimpleAtom::Kind::SYMBOL_VAL: {
assert(atom.get_str() == "#f");
if (expected_type != TypeSpec("symbol")) {
// explicitly cast if we're not using a reference type, including pointers.
// otherwise, we allow setting references to #f.
if (!type_info->is_reference()) {
return expected_type;
}
return {};
}
} break;
default:
assert(false);
}
return {};
}
} // namespace
FormElement* StoreOp::get_as_form(FormPool& pool, const Env& env) const {
if (env.has_type_analysis()) {
if (m_addr.is_identity() && m_addr.get_arg(0).is_sym_val()) {
@ -248,8 +303,10 @@ FormElement* StoreOp::get_as_form(FormPool& pool, const Env& env) const {
}
assert(!rd.addr_of);
auto addr = pool.alloc_element<DerefElement>(source, rd.addr_of, tokens);
return pool.alloc_element<StorePlainDeref>(addr, m_value.as_expr(), m_my_idx, ro.var,
std::nullopt);
return pool.alloc_element<StorePlainDeref>(
addr, m_value.as_expr(), m_my_idx, ro.var, std::nullopt,
get_typecast_for_atom(m_value, env, coerce_to_reg_type(rd.result_type), m_my_idx));
}
std::string cast_type;
@ -285,7 +342,8 @@ FormElement* StoreOp::get_as_form(FormPool& pool, const Env& env) const {
auto deref =
pool.alloc_element<DerefElement>(cast_source, false, std::vector<DerefToken>());
return pool.alloc_element<StorePlainDeref>(deref, m_value.as_expr(), m_my_idx, ro.var,
TypeSpec("pointer", {TypeSpec(cast_type)}));
TypeSpec("pointer", {TypeSpec(cast_type)}),
std::nullopt);
}
}
}
@ -412,7 +470,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
m_type.value_or(TypeSpec("object")));
}
if (input_type.typespec() == TypeSpec("pointer")) {
if (input_type.typespec() == TypeSpec("pointer") ||
input_type.kind == TP_Type::Kind::OBJECT_PLUS_PRODUCT_WITH_CONSTANT) {
std::string cast_type;
switch (m_size) {
case 1:

View File

@ -672,7 +672,8 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input,
return TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type));
}
if (input_type.typespec() == TypeSpec("pointer")) {
if (input_type.typespec() == TypeSpec("pointer") ||
input_type.kind == TP_Type::Kind::OBJECT_PLUS_PRODUCT_WITH_CONSTANT) {
// we got a plain pointer. let's just assume we're loading an integer.
// perhaps we should disable this feature by default on 4-byte loads if we're getting
// lots of false positives for loading pointers from plain pointers.

View File

@ -168,6 +168,39 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce
}
}
std::optional<TypeSpec> Env::get_user_cast_for_access(const RegisterAccess& access) const {
if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) {
auto& var_info = m_var_names.lookup(access.reg(), access.idx(), access.mode());
std::string original_name = var_info.name();
auto type_kv = m_typecasts.find(access.idx());
if (type_kv != m_typecasts.end()) {
for (auto& x : type_kv->second) {
if (x.reg == access.reg()) {
// let's make sure the above claim is true
TypeSpec type_in_reg;
if (has_type_analysis() && access.mode() == AccessMode::READ) {
type_in_reg =
get_types_for_op_mode(access.idx(), AccessMode::READ).get(access.reg()).typespec();
if (type_in_reg.print() != x.type_name) {
lg::error(
"Decompiler type consistency error. There was a typecast for reg {} at idx {} "
"(var {}) to type {}, but the actual type is {} ({})",
access.reg().to_charp(), access.idx(), original_name, x.type_name,
type_in_reg.print(), type_in_reg.print());
assert(false);
}
}
auto cast_type = dts->parse_type_spec(x.type_name);
return cast_type;
}
}
}
}
return {};
}
std::string Env::get_variable_name(const RegisterAccess& access) const {
if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) {
std::string lookup_name = m_var_names.lookup(access.reg(), access.idx(), access.mode()).name();
@ -186,15 +219,17 @@ std::string Env::get_variable_name(const RegisterAccess& access) const {
* NOTE: this is _NOT_ the most specific type known to the decompiler, but instead the type
* of the variable.
*/
TypeSpec Env::get_variable_type(const RegisterAccess& access) const {
TypeSpec Env::get_variable_type(const RegisterAccess& access, bool using_user_var_types) const {
if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) {
auto& var_info = m_var_names.lookup(access.reg(), access.idx(), access.mode());
std::string original_name = var_info.name();
auto type_of_var = var_info.type.typespec();
auto retype_kv = m_var_retype.find(original_name);
if (retype_kv != m_var_retype.end()) {
type_of_var = retype_kv->second;
if (using_user_var_types) {
auto retype_kv = m_var_retype.find(original_name);
if (retype_kv != m_var_retype.end()) {
type_of_var = retype_kv->second;
}
}
return type_of_var;
@ -208,7 +243,8 @@ TypeSpec Env::get_variable_type(const RegisterAccess& access) const {
*/
void Env::set_types(const std::vector<TypeState>& block_init_types,
const std::vector<TypeState>& op_end_types,
const FunctionAtomicOps& atomic_ops) {
const FunctionAtomicOps& atomic_ops,
const TypeSpec& my_type) {
m_block_init_types = block_init_types;
m_op_end_types = op_end_types;
@ -230,6 +266,16 @@ void Env::set_types(const std::vector<TypeState>& block_init_types,
}
m_has_types = true;
// check the actual return type:
if (my_type.last_arg() != TypeSpec("none")) {
auto as_end = dynamic_cast<const FunctionEndOp*>(atomic_ops.ops.back().get());
if (as_end) {
m_type_analysis_return_type = get_types_before_op((int)atomic_ops.ops.size() - 1)
.get(Register(Reg::GPR, Reg::V0))
.typespec();
}
}
}
std::string Env::print_local_var_types(const Form* top_level_form) const {

View File

@ -46,7 +46,8 @@ class Env {
// TODO - remove this.
goos::Object get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const;
std::string get_variable_name(const RegisterAccess& access) const;
TypeSpec get_variable_type(const RegisterAccess& access) const;
std::optional<TypeSpec> get_user_cast_for_access(const RegisterAccess& access) const;
TypeSpec get_variable_type(const RegisterAccess& access, bool using_user_var_types) const;
/*!
* Get the types in registers _after_ the given operation has completed.
@ -80,7 +81,8 @@ class Env {
void set_types(const std::vector<TypeState>& block_init_types,
const std::vector<TypeState>& op_end_types,
const FunctionAtomicOps& atomic_ops);
const FunctionAtomicOps& atomic_ops,
const TypeSpec& my_type);
void set_local_vars(const VariableNames& names) {
m_var_names = names;
@ -168,5 +170,6 @@ class Env {
std::unordered_map<std::string, LabelType> m_label_types;
std::unordered_set<std::string> m_vars_defined_in_let;
std::optional<TypeSpec> m_type_analysis_return_type;
};
} // namespace decompiler

View File

@ -1415,6 +1415,8 @@ std::string fixed_operator_to_string(FixedOperatorKind kind) {
return "&+!";
case FixedOperatorKind::SUBTRACTION:
return "-";
case FixedOperatorKind::SUBTRACTION_PTR:
return "&-";
case FixedOperatorKind::MULTIPLICATION:
return "*";
case FixedOperatorKind::SQRT:
@ -1481,6 +1483,8 @@ std::string fixed_operator_to_string(FixedOperatorKind kind) {
return "null?";
case FixedOperatorKind::PAIRP:
return "pair?";
case FixedOperatorKind::NONE:
return "none";
default:
assert(false);
return "";
@ -1950,22 +1954,35 @@ StorePlainDeref::StorePlainDeref(DerefElement* dst,
SimpleExpression expr,
int my_idx,
RegisterAccess base_var,
std::optional<TypeSpec> cast_type)
std::optional<TypeSpec> dst_cast_type,
std::optional<TypeSpec> src_cast_type)
: m_dst(dst),
m_expr(std::move(expr)),
m_my_idx(my_idx),
m_base_var(std::move(base_var)),
m_cast_type(cast_type) {}
m_base_var(base_var),
m_dst_cast_type(std::move(dst_cast_type)),
m_src_cast_type(std::move(src_cast_type)) {}
goos::Object StorePlainDeref::to_form_internal(const Env& env) const {
if (!m_cast_type.has_value()) {
return pretty_print::build_list("set!", m_dst->to_form(env),
m_expr.to_form(env.file->labels, env));
std::vector<goos::Object> lst = {pretty_print::to_symbol("set!")};
if (m_dst_cast_type) {
lst.push_back(
pretty_print::build_list("the-as", m_dst_cast_type->print(), m_dst->to_form(env)));
} else {
return pretty_print::build_list(
"set!", pretty_print::build_list("the-as", m_cast_type->print(), m_dst->to_form(env)),
m_expr.to_form(env.file->labels, env));
lst.push_back(m_dst->to_form(env));
}
if (m_src_cast_type) {
lst.push_back(pretty_print::build_list("the-as", m_src_cast_type->print(),
m_expr.to_form(env.file->labels, env)));
} else {
lst.push_back(m_expr.to_form(env.file->labels, env));
}
return pretty_print::build_list(lst);
}
void StorePlainDeref::apply(const std::function<void(FormElement*)>& f) {
f(this);
m_dst->apply(f);

View File

@ -1136,7 +1136,8 @@ class StorePlainDeref : public FormElement {
SimpleExpression expr,
int my_idx,
RegisterAccess base_var,
std::optional<TypeSpec> cast_type);
std::optional<TypeSpec> dst_cast_type,
std::optional<TypeSpec> src_cast_type);
goos::Object to_form_internal(const Env& env) const override;
void apply(const std::function<void(FormElement*)>& f) override;
@ -1150,7 +1151,7 @@ class StorePlainDeref : public FormElement {
SimpleExpression m_expr;
int m_my_idx = -1;
RegisterAccess m_base_var;
std::optional<TypeSpec> m_cast_type;
std::optional<TypeSpec> m_dst_cast_type, m_src_cast_type;
};
class StoreArrayAccess : public FormElement {

View File

@ -228,6 +228,20 @@ std::vector<Form*> pop_to_forms(const std::vector<RegisterAccess>& vars,
for (auto& x : forms_out) {
forms.push_back(pool.alloc_sequence_form(nullptr, x));
}
// add casts, if needed.
assert(vars.size() == forms.size());
for (size_t i = 0; i < vars.size(); i++) {
auto atom = form_as_atom(forms[i]);
bool is_var = atom && atom->is_var();
auto cast = env.get_user_cast_for_access(vars[i]);
// only cast if we didn't get a var (compacting expressions).
// there is a separate system for casting variables that will do a better job.
if (cast && !is_var) {
forms[i] = pool.alloc_single_element_form<CastElement>(nullptr, *cast, forms[i]);
}
}
return forms;
}
@ -250,6 +264,11 @@ bool is_int_type(const Env& env, int my_idx, RegisterAccess var) {
return type == TypeSpec("int");
}
bool is_pointer_type(const Env& env, int my_idx, RegisterAccess var) {
auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec();
return type.base_type() == "pointer";
}
/*!
* type == uint (exactly)?
*/
@ -258,10 +277,19 @@ bool is_uint_type(const Env& env, int my_idx, RegisterAccess var) {
return type == TypeSpec("uint");
}
bool is_ptr_or_child(const Env& env, int my_idx, RegisterAccess var) {
auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type();
bool is_ptr_or_child(const Env& env, int my_idx, RegisterAccess var, bool as_var) {
auto type = as_var ? env.get_variable_type(var, true).base_type()
: env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type();
return type == "pointer";
}
bool is_var(Form* form) {
auto atom = form_as_atom(form);
if (atom) {
return atom->is_var();
}
return false;
}
} // namespace
/*!
@ -511,7 +539,6 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env,
bool allow_side_effects) {
auto arg0_i = is_int_type(env, m_my_idx, m_expr.get_arg(0).var());
auto arg0_u = is_uint_type(env, m_my_idx, m_expr.get_arg(0).var());
bool arg0_ptr = is_ptr_or_child(env, m_my_idx, m_expr.get_arg(0).var());
bool arg1_reg = m_expr.get_arg(1).is_var();
bool arg1_i = true;
@ -531,6 +558,8 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env,
args.push_back(pool.alloc_single_element_form<SimpleAtomElement>(nullptr, m_expr.get_arg(1)));
}
bool arg0_ptr = is_ptr_or_child(env, m_my_idx, m_expr.get_arg(0).var(), is_var(args.at(0)));
// Look for getting an address inside of an object.
// (+ <integer 108 + int> process). array style access with a stride of 1.
// in the case, both are vars.
@ -802,6 +831,10 @@ void SimpleExpressionElement::update_from_stack_copy_first_int_2(const Env& env,
} else {
auto cast = pool.alloc_single_element_form<CastElement>(
nullptr, TypeSpec(arg0_i ? "int" : "uint"), args.at(1));
if (kind == FixedOperatorKind::SUBTRACTION &&
is_pointer_type(env, m_my_idx, m_expr.get_arg(0).var())) {
kind = FixedOperatorKind::SUBTRACTION_PTR;
}
auto new_form =
pool.alloc_element<GenericElement>(GenericOperator::make_fixed(kind), args.at(0), cast);
result->push_back(new_form);
@ -1107,38 +1140,39 @@ void StoreInPairElement::push_to_stack(const Env& env, FormPool& pool, FormStack
}
}
namespace {
Form* make_optional_cast(const std::optional<TypeSpec>& cast_type, Form* in, FormPool& pool) {
if (cast_type) {
return pool.alloc_single_element_form<CastElement>(nullptr, *cast_type, in);
} else {
return in;
}
}
} // namespace
void StorePlainDeref::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
mark_popped();
if (m_expr.is_var()) {
auto vars = std::vector<RegisterAccess>({m_expr.var(), m_base_var});
auto popped = pop_to_forms(vars, env, pool, stack, true);
if (m_cast_type.has_value()) {
m_dst->set_base(
pool.alloc_single_element_form<CastElement>(nullptr, *m_cast_type, popped.at(1)));
} else {
m_dst->set_base(popped.at(1));
}
m_dst->set_base(make_optional_cast(m_dst_cast_type, popped.at(1), pool));
m_dst->mark_popped();
m_dst->inline_nested();
auto fr = pool.alloc_element<SetFormFormElement>(pool.alloc_single_form(nullptr, m_dst),
popped.at(0));
auto fr = pool.alloc_element<SetFormFormElement>(
pool.alloc_single_form(nullptr, m_dst),
make_optional_cast(m_src_cast_type, popped.at(0), pool));
fr->mark_popped();
stack.push_form_element(fr, true);
} else {
auto vars = std::vector<RegisterAccess>({m_base_var});
auto popped = pop_to_forms(vars, env, pool, stack, true);
if (m_cast_type.has_value()) {
m_dst->set_base(
pool.alloc_single_element_form<CastElement>(nullptr, *m_cast_type, popped.at(1)));
} else {
m_dst->set_base(popped.at(0));
}
m_dst->set_base(make_optional_cast(m_dst_cast_type, popped.at(0), pool));
m_dst->mark_popped();
m_dst->inline_nested();
auto val = pool.alloc_single_element_form<SimpleExpressionElement>(nullptr, m_expr, m_my_idx);
val->mark_popped();
auto fr = pool.alloc_element<SetFormFormElement>(pool.alloc_single_form(nullptr, m_dst), val);
auto fr = pool.alloc_element<SetFormFormElement>(
pool.alloc_single_form(nullptr, m_dst), make_optional_cast(m_src_cast_type, val, pool));
fr->mark_popped();
stack.push_form_element(fr, true);
}
@ -1232,19 +1266,11 @@ void FunctionCallElement::update_from_stack(const Env& env,
function_type = tp_type.typespec();
}
// assert(is_method == m_op->is_method());
if (is_virtual_method != m_op->is_method()) {
lg::error("Disagreement on method!");
throw std::runtime_error("Disagreement on method");
}
// if method, don't pop the obj arg.
// Variable method_obj_var;
// if (is_method) {
// method_obj_var = all_pop_vars.at(1);
// all_pop_vars.erase(all_pop_vars.begin() + 1);
// }
if (tp_type.kind == TP_Type::Kind::NON_VIRTUAL_METHOD) {
std::swap(all_pop_vars.at(0), all_pop_vars.at(1));
}
@ -1257,20 +1283,39 @@ void FunctionCallElement::update_from_stack(const Env& env,
std::vector<Form*> arg_forms;
for (size_t arg_id = 0; arg_id < nargs; arg_id++) {
auto val = unstacked.at(arg_id + 1); // first is the function itself.
auto& var = all_pop_vars.at(arg_id + 1);
if (env.has_type_analysis() && function_type.arg_count() == nargs + 1) {
auto actual_arg_type = env.get_types_before_op(var.idx()).get(var.reg()).typespec();
auto desired_arg_type = function_type.get_arg(arg_id);
if (!env.dts->ts.tc(desired_arg_type, actual_arg_type)) {
arg_forms.push_back(
pool.alloc_single_element_form<CastElement>(nullptr, desired_arg_type, val));
if (is_virtual_method) {
for (size_t arg_id = 0; arg_id < nargs; arg_id++) {
auto val = unstacked.at(arg_id + 1); // first is the function itself.
auto& var = all_pop_vars.at(arg_id + 1);
if (env.has_type_analysis() && function_type.arg_count() == nargs + 2) {
auto actual_arg_type = env.get_types_before_op(var.idx()).get(var.reg()).typespec();
auto desired_arg_type = function_type.get_arg(arg_id + 1);
if (!env.dts->ts.tc(desired_arg_type, actual_arg_type)) {
arg_forms.push_back(
pool.alloc_single_element_form<CastElement>(nullptr, desired_arg_type, val));
} else {
arg_forms.push_back(val);
}
} else {
arg_forms.push_back(val);
}
}
} else {
for (size_t arg_id = 0; arg_id < nargs; arg_id++) {
auto val = unstacked.at(arg_id + 1); // first is the function itself.
auto& var = all_pop_vars.at(arg_id + 1);
if (env.has_type_analysis() && function_type.arg_count() == nargs + 1) {
auto actual_arg_type = env.get_types_before_op(var.idx()).get(var.reg()).typespec();
auto desired_arg_type = function_type.get_arg(arg_id);
if (!env.dts->ts.tc(desired_arg_type, actual_arg_type)) {
arg_forms.push_back(
pool.alloc_single_element_form<CastElement>(nullptr, desired_arg_type, val));
} else {
arg_forms.push_back(val);
}
} else {
arg_forms.push_back(val);
}
} else {
arg_forms.push_back(val);
}
}
@ -1650,7 +1695,7 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack&
if (used_as_value) {
// TODO - is this wrong?
stack.push_value_to_reg(final_destination, pool.alloc_single_form(nullptr, this), true,
env.get_variable_type(final_destination));
env.get_variable_type(final_destination, false));
} else {
stack.push_form_element(this, true);
}
@ -1775,7 +1820,7 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac
stack.push_form_element(this, true);
} else {
stack.push_value_to_reg(*last_var, pool.alloc_single_form(nullptr, this), true,
env.get_variable_type(*last_var));
env.get_variable_type(*last_var, false));
}
} else {
stack.push_form_element(this, true);
@ -1834,7 +1879,7 @@ void ShortCircuitElement::push_to_stack(const Env& env, FormPool& pool, FormStac
assert(used_as_value.has_value());
stack.push_value_to_reg(final_result, pool.alloc_single_form(nullptr, this), true,
env.get_variable_type(final_result));
env.get_variable_type(final_result, false));
already_rewritten = true;
}
}

View File

@ -232,7 +232,7 @@ FormElement* FormStack::pop_back(FormPool& pool) {
namespace {
bool is_op_in_place(SetVarElement* elt,
FixedOperatorKind op,
const Env&,
const Env& env,
RegisterAccess* base_out,
Form** val_out) {
auto matcher = Matcher::op(GenericOpMatcher::fixed(op), {Matcher::any_reg(0), Matcher::any(1)});
@ -240,6 +240,7 @@ bool is_op_in_place(SetVarElement* elt,
if (result.matched) {
auto first = result.maps.regs.at(0);
assert(first.has_value());
if (first->reg() != elt->dst().reg()) {
return false;
}
@ -248,6 +249,14 @@ bool is_op_in_place(SetVarElement* elt,
return false;
}
auto src_var = env.get_variable_name(*first);
auto dst_var = env.get_variable_name(elt->dst());
if (src_var != dst_var) {
// something like daddu a1-1, a1-0, v0 isn't safe to turn into an in-place, but will pass
// the previous two checks.
return false;
}
*val_out = result.maps.forms.at(1);
*base_out = first.value();
return true;

View File

@ -105,6 +105,7 @@ enum class FixedOperatorKind {
ADDITION_IN_PLACE,
ADDITION_PTR_IN_PLACE,
SUBTRACTION,
SUBTRACTION_PTR,
MULTIPLICATION,
SQRT,
ARITH_SHIFT,
@ -138,6 +139,7 @@ enum class FixedOperatorKind {
METHOD_OF_OBJECT,
NULLP,
PAIRP,
NONE,
INVALID
};

View File

@ -19,6 +19,31 @@ bool convert_to_expressions(
const DecompilerTypeSystem& dts) {
assert(top_level_form);
// set argument names to some reasonable defaults. these will be used if the user doesn't
// give us anything more specific.
if (f.guessed_name.kind == FunctionName::FunctionKind::GLOBAL) {
f.ir2.env.set_remap_for_function(f.type.arg_count() - 1);
} else if (f.guessed_name.kind == FunctionName::FunctionKind::METHOD) {
if (f.guessed_name.method_id == GOAL_NEW_METHOD) {
f.ir2.env.set_remap_for_new_method(f.type.arg_count() - 1);
} else {
f.ir2.env.set_remap_for_method(f.type.arg_count() - 1);
}
}
// get variable names from the user.
f.ir2.env.map_args_from_config(arg_names, var_override_map);
// override variable types from the user.
std::unordered_map<std::string, TypeSpec> retype;
for (auto& remap : var_override_map) {
if (remap.second.type) {
retype[remap.first] = dts.parse_type_spec(*remap.second.type);
}
}
f.ir2.env.set_retype_map(retype);
try {
// create the root expression stack for the function
FormStack stack(true);
@ -45,6 +70,8 @@ bool convert_to_expressions(
} else {
// or just get all the expressions
new_entries = stack.rewrite(pool, f.ir2.env);
new_entries.push_back(
pool.alloc_element<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::NONE)));
}
// if we are a totally empty function, insert a placeholder so we don't have to handle
@ -70,31 +97,6 @@ bool convert_to_expressions(
return false;
}
// set argument names to some reasonable defaults. these will be used if the user doesn't
// give us anything more specific.
if (f.guessed_name.kind == FunctionName::FunctionKind::GLOBAL) {
f.ir2.env.set_remap_for_function(f.type.arg_count() - 1);
} else if (f.guessed_name.kind == FunctionName::FunctionKind::METHOD) {
if (f.guessed_name.method_id == GOAL_NEW_METHOD) {
f.ir2.env.set_remap_for_new_method(f.type.arg_count() - 1);
} else {
f.ir2.env.set_remap_for_method(f.type.arg_count() - 1);
}
}
// get variable names from the user.
f.ir2.env.map_args_from_config(arg_names, var_override_map);
// override variable types from the user.
std::unordered_map<std::string, TypeSpec> retype;
for (auto& remap : var_override_map) {
if (remap.second.type) {
retype[remap.first] = dts.parse_type_spec(*remap.second.type);
}
}
f.ir2.env.set_retype_map(retype);
return true;
}
} // namespace decompiler

View File

@ -222,7 +222,7 @@ Form* insert_cast_for_let(RegisterAccess dst,
Form* src,
FormPool& pool,
const Env& env) {
auto dst_type = env.get_variable_type(dst);
auto dst_type = env.get_variable_type(dst, true);
if (src_type != dst_type) {
// fmt::print("inserting let cast because {} != {}\n", dst_type.print(), src_type.print());

View File

@ -2507,7 +2507,7 @@
;;(define-extern dma-sync object) ;; unknown type
;;(define-extern dma-packet-array object) ;; unknown type
(define-extern dma-buffer-inplace-new (function dma-buffer int dma-buffer))
(define-extern dma-buffer-length (function dma-buffer uint))
(define-extern dma-buffer-length (function dma-buffer int))
(define-extern dma-buffer-free (function dma-buffer int))
;;(define-extern dma-gif-packet object) ;; unknown type

View File

@ -111,6 +111,7 @@
"(method 0 catch-frame)",
"throw-dispatch",
"set-to-run-bootstrap",
"run-function-in-process", // not asm, but it uses the stack.
// pskernel
"return-from-exception", // F: eret

View File

@ -5,7 +5,13 @@
],
"gkernel": [
["L345", "_auto_", true]
["L345", "_auto_", true],
["L344", "_auto_", true],
["L346", "float", true],
["L347", "float", true],
["L348", "float", true],
["L289", "_auto_", true],
["L282", "_auto_", true]
],
"math": [

View File

@ -64,7 +64,7 @@
"(method 0 dead-pool-heap)": [
[60, "v0", "int"], // a lie, actually the 115 is an align16 constant propagated on addr of heap start.
[63, "a0", "pointer"],
//[63, "a0", "pointer"],
[[61, 73], "v0", "dead-pool-heap"]
],
@ -89,13 +89,13 @@
"(method 9 process)": [[43, "s5", "process"]],
"(method 14 dead-pool)": [
[[24, 26], "v1", "(pointer process-tree)"],
[[24, 25], "v1", "(pointer process-tree)"],
[[30, 39], "s4", "(pointer process-tree)"]
],
"inspect-process-heap": [
[[4, 11], "s5", "basic"],
[17, "s5", "int"]
[17, "s5", "pointer"]
],
"name=": [

View File

@ -191,6 +191,16 @@
"vars":{"v1-1":"in-goal-mem"}
},
// GKERNEL
"(method 0 cpu-thread)":{
"vars":{"v0-0":["obj", "cpu-thread"]}
},
"inspect-process-heap":{
"vars":{"s5-0":["obj", "pointer"]}
},
"(method 23 dead-pool-heap)":{
"args":["this", "rec"]
},

View File

@ -130,10 +130,13 @@ TypeSpec TP_Type::typespec() const {
case Kind::PRODUCT_WITH_CONSTANT:
return m_ts;
case Kind::OBJECT_PLUS_PRODUCT_WITH_CONSTANT:
if (m_ts.base_type() == "pointer") {
return TypeSpec("pointer");
}
// this can be part of an array access, so we don't really know the type.
// probably not a good idea to try to do anything with this as a typespec
// so let's be very vague
return TypeSpec("pointer");
return TypeSpec("int");
case Kind::OBJECT_NEW_METHOD:
// similar to previous case, being more vague than we need to be because we don't
// want to assume the return type incorrectly and you shouldn't try to do anything with

View File

@ -40,6 +40,35 @@ class TP_Type {
bool operator!=(const TP_Type& other) const;
TypeSpec typespec() const;
/*!
* Returns true if the expression with this type should always be wrapped in a cast.
*/
bool requires_cast() const {
switch (kind) {
case Kind::TYPESPEC:
case Kind::TYPE_OF_TYPE_OR_CHILD:
case Kind::TYPE_OF_TYPE_NO_VIRTUAL:
case Kind::FALSE_AS_NULL: // if we want all #f's for references to be cast, move this.
case Kind::PRODUCT_WITH_CONSTANT:
case Kind::STRING_CONSTANT:
case Kind::FORMAT_STRING:
case Kind::INTEGER_CONSTANT:
case Kind::INTEGER_CONSTANT_PLUS_VAR:
case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT:
case Kind::VIRTUAL_METHOD:
case Kind::NON_VIRTUAL_METHOD:
return false;
case Kind::UNINITIALIZED:
case Kind::OBJECT_PLUS_PRODUCT_WITH_CONSTANT:
case Kind::OBJECT_NEW_METHOD:
case Kind::DYNAMIC_METHOD_ACCESS:
return true;
case Kind::INVALID:
default:
assert(false);
}
}
bool is_constant_string() const { return kind == Kind::STRING_CONSTANT; }
bool is_integer_constant() const { return kind == Kind::INTEGER_CONSTANT; }
bool is_integer_constant(int64_t value) const { return is_integer_constant() && m_int == value; }

View File

@ -722,7 +722,7 @@ goos::Object decompile_pair(const DecompilerLabel& label,
auto cdr_word = words.at(to_print.target_segment).at((to_print.offset + 2) / 4);
// if empty
if (cdr_word.kind == LinkedWord::EMPTY_PTR) {
return pretty_print::build_list(list_tokens);
return pretty_print::build_list("quote", pretty_print::build_list(list_tokens));
}
// if pointer
if (cdr_word.kind == LinkedWord::PTR) {
@ -736,7 +736,7 @@ goos::Object decompile_pair(const DecompilerLabel& label,
"could not find a test case yet.");
list_tokens.push_back(pretty_print::to_symbol("."));
list_tokens.push_back(decompile_pair_elt(cdr_word, labels, words, ts));
return pretty_print::build_list(list_tokens);
return pretty_print::build_list("quote", pretty_print::build_list(list_tokens));
} else {
if ((to_print.offset % 4) != 0) {
throw std::runtime_error(
@ -755,7 +755,7 @@ goos::Object decompile_pair(const DecompilerLabel& label,
list_tokens.push_back(pretty_print::to_symbol("."));
list_tokens.push_back(decompile_pair_elt(
words.at(to_print.target_segment).at(to_print.offset / 4), labels, words, ts));
return pretty_print::build_list(list_tokens);
return pretty_print::build_list("quote", pretty_print::build_list(list_tokens));
}
}
}

View File

@ -172,6 +172,11 @@
`(/ 0 0)
)
(defmacro break! ()
"A breakpoint. Todo - should we use int3 instead?"
`(/ 0 0)
)
;;;;;;;;;;;;;;;;;;;
;; GOAL Syntax
;;;;;;;;;;;;;;;;;;;
@ -379,7 +384,7 @@
)
(defmacro &- (a b)
`(- (the-as uint ,a) (the-as uint ,b))
`(- (the-as int ,a) (the-as int ,b))
)
(defmacro &-> (&rest args)

View File

@ -0,0 +1,71 @@
;; GCOMMON
(define-extern name= (function basic basic symbol))
(define-extern fact (function int int))
;; KERNEL
(declare-type process basic)
(declare-type stack-frame basic)
(declare-type state basic)
(declare-type cpu-thread basic)
(declare-type dead-pool basic)
(declare-type event-message-block structure)
(declare-type thread basic)
(deftype process (process-tree)
((pool dead-pool :offset-assert #x20)
(status basic :offset-assert #x24)
(pid int32 :offset-assert #x28)
(main-thread cpu-thread :offset-assert #x2c)
(top-thread thread :offset-assert #x30)
(entity basic :offset-assert #x34)
(state state :offset-assert #x38)
(trans-hook function :offset-assert #x3c)
(post-hook function :offset-assert #x40)
(event-hook (function basic int basic event-message-block object) :offset-assert #x44)
(allocated-length int32 :offset-assert #x48)
(next-state state :offset-assert #x4c)
(heap-base pointer :offset-assert #x50)
(heap-top pointer :offset-assert #x54)
(heap-cur pointer :offset-assert #x58)
(stack-frame-top stack-frame :offset-assert #x5c)
(connection-list connectable :inline :offset-assert #x60)
(stack uint8 :dynamic :offset-assert #x70)
)
(:methods
(new (symbol type basic int) _type_ 0)
(activate (_type_ process-tree basic pointer) process-tree 9)
(deactivate (process) none 10)
(dummy-method-11 () none 11)
(run-logic? (process) symbol 12)
(dummy-method () none 13)
)
:size-assert #x70
:method-count-assert 14
:no-runtime-type ;; already defined by kscheme. Don't do it again.
)
(declare-type dead-pool-heap basic)
(define-extern *debug-dead-pool* dead-pool-heap)
(define-extern change-parent (function process-tree process-tree process-tree))
(define-extern *null-process* process)
(define-extern *vis-boot* basic)
(define-extern *stdcon* string)
(declare-type kernel-context basic)
(define-extern iterate-process-tree (function process-tree (function object object) kernel-context object))
(define-extern execute-process-tree (function process-tree (function object object) kernel-context object))
(define-extern search-process-tree (function process-tree (function process-tree object) process-tree))
(define-extern *listener-process* process)
(define-extern *active-pool* process-tree)
(define-extern reset-and-call (function thread function object))
(define-extern ash (function int int int))
(define-extern inspect-process-tree (function process-tree int int symbol process-tree))
(define-extern set-to-run-bootstrap (function none))
(define-extern dead-state state)
(define-extern *display-pool* process-tree)
(define-extern *camera-pool* process-tree)
(define-extern *target-pool* process-tree)
(define-extern *entity-pool* process-tree)
(define-extern *default-pool* process-tree)

View File

@ -918,11 +918,12 @@
;; WARN: Inline assembly instruction marked with TODO - [TODO.LQ]
;; WARN: Inline assembly instruction marked with TODO - [TODO.SQ]
(defun qmem-copy->! ((dst pointer) (src pointer) (size int))
(local-vars (src-ptr pointer) (dst-ptr pointer) (value int))
(local-vars (value int))
(let ((result dst))
(let ((qwc (sar (+ size 15) 4)))
(&+! dst (shl qwc 4))
(&+! src (shl qwc 4))
(let* ((qwc (sar (+ size 15) 4))
(src-ptr (&+ dst (shl qwc 4)))
(dst-ptr (&+ src (shl qwc 4)))
)
(while (nonzero? qwc)
(+! qwc -1)
(&+! src-ptr -16)
@ -1317,4 +1318,7 @@
;; failed to figure out what this is:
(let ((v0-3 0))
)
)
;; failed to figure out what this is:
(none)

View File

@ -379,6 +379,5 @@
(let ((v0-11 0))
)
;; failed to figure out what this is:
(none)

File diff suppressed because it is too large Load Diff

View File

@ -4,3 +4,6 @@
;; failed to figure out what this is:
(let ((v0-0 0))
)
;; failed to figure out what this is:
(none)

View File

@ -315,7 +315,7 @@ TEST_F(DataDecompTest, ContinuePoint) {
" -0.1328\n"
" 0.5831\n"
" )\n"
" :load-commands (('special \"citb-exit-plat-4\" #t))\n"
" :load-commands '('('special \"citb-exit-plat-4\" #t))\n"
" :vis-nick 'fin\n"
" :lev0 'finalboss\n"
" :disp0 'display\n"

View File

@ -2372,7 +2372,7 @@ TEST_F(FormRegressionTest, ExprCopyStringString) {
" (set! a1-1 (&-> a1-1 1))\n"
" )\n"
" )\n"
" (set! (-> v1-0 0) 0)\n"
" (set! (-> v1-0 0) (the-as uint 0))\n"
" )\n"
" arg0\n"
" )";
@ -2575,4 +2575,54 @@ TEST_F(FormRegressionTest, MoveFalse) {
std::string expected = "(nonzero? (logand (+ arg0 12) 1))";
test_with_expr(func, type, expected, false, "", {{"L17", "A ~A"}});
}
// Good for testing that in-place ops (+!) check the _variable_ is the same.
TEST_F(FormRegressionTest, QMemCpy) {
std::string func =
"sll r0, r0, 0\n"
"L78:\n"
" or v0, a0, r0\n"
" daddiu v1, a2, 15\n"
" dsra v1, v1, 4\n"
" dsll a2, v1, 4\n"
" daddu a0, a0, a2\n"
" dsll a2, v1, 4\n"
" daddu a1, a1, a2\n"
" beq r0, r0, L80\n"
" sll r0, r0, 0\n"
"L79:\n"
" daddiu v1, v1, -1\n"
" daddiu a0, a0, -16\n"
" daddiu a1, a1, -16\n"
" lq a2, 0(a1)\n"
" sq a2, 0(a0)\n"
"L80:\n"
" bne v1, r0, L79\n"
" sll r0, r0, 0\n"
" or v1, s7, r0\n"
" or v1, s7, r0\n"
" jr ra\n"
" daddu sp, sp, r0\n";
std::string type = "(function pointer pointer int pointer)";
std::string expected =
"(let ((v0-0 arg0))\n"
" (let* ((v1-1 (sar (+ arg2 15) 4))\n"
" (a0-1 (&+ arg0 (shl v1-1 4)))\n"
" (a1-1 (&+ arg1 (shl v1-1 4)))\n"
" )\n"
" (while (nonzero? v1-1)\n"
" (+! v1-1 -1)\n"
" (&+! a0-1 -16)\n"
" (&+! a1-1 -16)\n"
" (.lq a2-3 0 a1-1)\n"
" (.sq a2-3 0 a0-1)\n"
" )\n"
" )\n"
" v0-0\n"
" )";
test_with_expr(func, type, expected);
}

View File

@ -134,6 +134,7 @@ TEST_F(FormRegressionTest, ExprMethod1Thread) {
"(begin\n"
" (when (= arg0 (-> arg0 process main-thread)) (break!) (let ((v1-3 0))))\n"
" (set! (-> arg0 process top-thread) (-> arg0 previous))\n"
" (none)\n"
" )";
test_with_expr(func, type, expected, false);
}
@ -257,11 +258,13 @@ TEST_F(FormRegressionTest, ExprMethod9Thread) {
std::string type = "(function thread int none)";
std::string expected =
"(begin\n"
" (let\n"
" ((a2-0 (-> arg0 process)))\n"
" (let ((a2-0 (-> arg0 process)))\n"
" (cond\n"
" ((!= arg0 (-> a2-0 main-thread)) (format 0 \"1 ~A ~%\" a2-0))\n"
" ((= (-> arg0 stack-size) arg1))\n"
" ((!= arg0 (-> a2-0 main-thread))\n"
" (format 0 \"1 ~A ~%\" a2-0)\n"
" )\n"
" ((= (-> arg0 stack-size) arg1)\n"
" )\n"
" ((=\n"
" (-> a2-0 heap-cur)\n"
" (+\n"
@ -271,14 +274,21 @@ TEST_F(FormRegressionTest, ExprMethod9Thread) {
" )\n"
" (set!\n"
" (-> a2-0 heap-cur)\n"
" (+ (+ (+ arg1 -4) (the-as int (-> arg0 type size))) (the-as int arg0))\n"
" (the-as\n"
" pointer\n"
" (+ (+ (+ arg1 -4) (the-as int (-> arg0 type size))) (the-as int arg0))\n"
" )\n"
" )\n"
" (set! (-> arg0 stack-size) arg1)\n"
" )\n"
" (else (format 0 \"2 ~A ~%\" a2-0))\n"
" (else\n"
" (format 0 \"2 ~A ~%\" a2-0)\n"
" )\n"
" )\n"
" )\n"
" (let ((v0-2 0)))\n"
" (let ((v0-2 0))\n"
" )\n"
" (none)\n"
" )";
test_with_expr(func, type, expected, false, "", {{"L342", "1 ~A ~%"}, {"L341", "2 ~A ~%"}});
}
@ -325,35 +335,44 @@ TEST_F(FormRegressionTest, ExprMethod0Thread) {
" daddu sp, sp, r0";
std::string type = "(function symbol type process symbol int pointer cpu-thread)";
std::string expected =
"(let\n"
" ((v0-0\n"
" (if\n"
" (-> arg2 top-thread)\n"
" (&+ arg5 -7164)\n"
" (let\n"
" ((v1-2 (logand -16 (the-as int (&+ (-> arg2 heap-cur) 15)))))\n"
" (set! (-> arg2 heap-cur) (+ (+ v1-2 (the-as int (-> arg1 size))) arg4))\n"
" (+ v1-2 4)\n"
"(let ((obj (the-as cpu-thread (if (-> arg2 top-thread)\n"
" (&+ arg5 -7164)\n"
" (let\n"
" ((v1-2\n"
" (logand\n"
" -16\n"
" (the-as int (&+ (-> arg2 heap-cur) 15))\n"
" )\n"
" )\n"
" )\n"
" (set!\n"
" (-> arg2 heap-cur)\n"
" (the-as\n"
" pointer\n"
" (+ (+ v1-2 (the-as int (-> arg1 size))) arg4)\n"
" )\n"
" )\n"
" (+ v1-2 4)\n"
" )\n"
" )\n"
" )\n"
" )\n"
" )\n"
" )\n"
" )\n"
" (set! (-> (the-as cpu-thread v0-0) type) arg1)\n"
" (set! (-> (the-as cpu-thread v0-0) name) arg3)\n"
" (set! (-> (the-as cpu-thread v0-0) process) arg2)\n"
" (set! (-> (the-as cpu-thread v0-0) sp) arg5)\n"
" (set! (-> (the-as cpu-thread v0-0) stack-top) arg5)\n"
" (set! (-> (the-as cpu-thread v0-0) previous) (-> arg2 top-thread))\n"
" (set! (-> arg2 top-thread) (the-as cpu-thread v0-0))\n"
" (set! (-> (the-as cpu-thread v0-0) suspend-hook) (method-of-object (the-as cpu-thread "
"v0-0) thread-suspend))\n"
" (set! (-> (the-as cpu-thread v0-0) resume-hook) (method-of-object (the-as cpu-thread "
"v0-0) thread-resume))\n"
" (set! (-> (the-as cpu-thread v0-0) stack-size) arg4)\n"
" (the-as cpu-thread v0-0)\n"
" (set! (-> obj type) arg1)\n"
" (set! (-> obj name) arg3)\n"
" (set! (-> obj process) arg2)\n"
" (set! (-> obj sp) arg5)\n"
" (set! (-> obj stack-top) arg5)\n"
" (set! (-> obj previous) (-> arg2 top-thread))\n"
" (set! (-> arg2 top-thread) obj)\n"
" (set! (-> obj suspend-hook) (method-of-object obj thread-suspend))\n"
" (set! (-> obj resume-hook) (method-of-object obj thread-resume))\n"
" (set! (-> obj stack-size) arg4)\n"
" (the-as cpu-thread (the-as object obj))\n"
" )";
test_with_expr(func, type, expected, false, "cpu-thread", {},
parse_cast_json("[[[13, 28], \"v0\", \"cpu-thread\"]]"));
parse_cast_json("[[[13, 28], \"v0\", \"cpu-thread\"]]"),
"{\"vars\":{\"v0-0\":[\"obj\", \"cpu-thread\"]}}");
}
TEST_F(FormRegressionTest, ExprMethod5CpuThread) {
@ -433,10 +452,10 @@ TEST_F(FormRegressionTest, RemoveMethod0ProcessTree) {
"(let\n"
" ((v0-0 (object-new arg0 arg1 (the-as int (-> arg1 size)))))\n"
" (set! (-> v0-0 name) arg2)\n"
" (set! (-> v0-0 mask) 256)\n"
" (set! (-> v0-0 parent) #f)\n"
" (set! (-> v0-0 brother) #f)\n"
" (set! (-> v0-0 child) #f)\n"
" (set! (-> v0-0 mask) (the-as uint 256))\n"
" (set! (-> v0-0 parent) (the-as (pointer process-tree) #f))\n"
" (set! (-> v0-0 brother) (the-as (pointer process-tree) #f))\n"
" (set! (-> v0-0 child) (the-as (pointer process-tree) #f))\n"
" (set! (-> v0-0 self) v0-0)\n"
" (set! (-> v0-0 ppointer) (&-> v0-0 self))\n"
" v0-0\n"
@ -650,7 +669,8 @@ TEST_F(FormRegressionTest, ExprMethod0Process) {
" )\n"
" (set! (-> (the-as process v0-0) heap-top) (&-> (the-as process v0-0) stack (-> (the-as "
"process v0-0) allocated-length)))\n"
" (set! (-> (the-as process v0-0) stack-frame-top) (-> (the-as process v0-0) heap-top))\n"
" (set! (-> (the-as process v0-0) stack-frame-top) (the-as stack-frame (-> (the-as process "
"v0-0) heap-top)))\n"
" (set! (-> (the-as process v0-0) stack-frame-top) #f)\n"
" (set! (-> (the-as process v0-0) state) #f)\n"
" (set! (-> (the-as process v0-0) next-state) #f)\n"
@ -658,9 +678,9 @@ TEST_F(FormRegressionTest, ExprMethod0Process) {
" (set! (-> (the-as process v0-0) trans-hook) #f)\n"
" (set! (-> (the-as process v0-0) post-hook) #f)\n"
" (set! (-> (the-as process v0-0) event-hook) #f)\n"
" (set! (-> (the-as process v0-0) parent) #f)\n"
" (set! (-> (the-as process v0-0) brother) #f)\n"
" (set! (-> (the-as process v0-0) child) #f)\n"
" (set! (-> (the-as process v0-0) parent) (the-as (pointer process-tree) #f))\n"
" (set! (-> (the-as process v0-0) brother) (the-as (pointer process-tree) #f))\n"
" (set! (-> (the-as process v0-0) child) (the-as (pointer process-tree) #f))\n"
" (set! (-> (the-as process v0-0) self) (the-as process v0-0))\n"
" (set! (-> (the-as process v0-0) ppointer) (&-> (the-as process v0-0) self))\n"
" (the-as process v0-0)\n"
@ -722,20 +742,18 @@ TEST_F(FormRegressionTest, ExprInspectProcessHeap) {
std::string type = "(function process symbol)";
std::string expected =
"(begin\n"
" (let\n"
" ((obj (the-as basic (&+ (-> arg0 heap-base) 4))))\n"
" (while\n"
" (< (the-as int obj) (the-as int (-> arg0 heap-cur)))\n"
" (inspect obj)\n"
" (+! (the-as int obj) (logand -16 (+ (asize-of obj) 15)))\n"
" (let ((obj (&+ (-> arg0 heap-base) 4)))\n"
" (while (< (the-as int obj) (the-as int (-> arg0 heap-cur)))\n"
" (inspect (the-as basic obj))\n"
" (&+! obj (logand -16 (+ (asize-of (the-as basic obj)) 15)))\n"
" )\n"
" )\n"
" #f\n"
" )";
test_with_expr(func, type, expected, false, "", {},
parse_cast_json("[\t\t[[4,11], \"s5\", \"basic\"],\n"
"\t\t[[17,20], \"s5\", \"int\"]]"),
"{\"vars\":{\"s5-0\":[\"obj\", \"basic\"]}}");
"\t\t[[17,20], \"s5\", \"pointer\"]]"),
"{\"vars\":{\"s5-0\":[\"obj\", \"pointer\"]}}");
}
// note: skipped method 3 process
@ -822,11 +840,11 @@ TEST_F(FormRegressionTest, ExprMethod2Process) {
" (format\n"
" #t\n"
" \":stack ~D/~D :heap ~D/~D @ #x~X>\"\n"
" (- (-> arg0 top-thread stack-top) (the-as uint (-> arg0 top-thread sp)))\n"
" (&- (-> arg0 top-thread stack-top) (the-as uint (-> arg0 top-thread sp)))\n"
" (-> arg0 main-thread stack-size)\n"
" (-\n"
" (-> arg0 allocated-length)\n"
" (- (-> arg0 heap-top) (the-as uint (-> arg0 heap-cur)))\n"
" (&- (-> arg0 heap-top) (the-as uint (-> arg0 heap-cur)))\n"
" )\n"
" (-> arg0 allocated-length)\n"
" arg0\n"
@ -930,10 +948,10 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPool) {
"(let\n"
" ((s3-0 (object-new arg0 arg1 (the-as int (-> arg1 size)))))\n"
" (set! (-> s3-0 name) arg4)\n"
" (set! (-> s3-0 mask) 256)\n"
" (set! (-> s3-0 parent) #f)\n"
" (set! (-> s3-0 brother) #f)\n"
" (set! (-> s3-0 child) #f)\n"
" (set! (-> s3-0 mask) (the-as uint 256))\n"
" (set! (-> s3-0 parent) (the-as (pointer process-tree) #f))\n"
" (set! (-> s3-0 brother) (the-as (pointer process-tree) #f))\n"
" (set! (-> s3-0 child) (the-as (pointer process-tree) #f))\n"
" (set! (-> s3-0 self) s3-0)\n"
" (set! (-> s3-0 ppointer) (&-> s3-0 self))\n"
" (dotimes\n"
@ -1126,7 +1144,7 @@ TEST_F(FormRegressionTest, ExprMethod15DeadPool) {
" jr ra\n"
" daddiu sp, sp, 16";
std::string type = "(function dead-pool process none)";
std::string expected = "(change-parent arg1 arg0)";
std::string expected = "(begin (change-parent arg1 arg0) (none))";
test_with_expr(func, type, expected);
}
@ -1245,11 +1263,11 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) {
" )\n"
" )\n"
" (set! (-> obj name) arg2)\n"
" (set! (-> obj mask) 256)\n"
" (set! (-> obj mask) (the-as uint 256))\n"
" (set! (-> obj allocated-length) arg3)\n"
" (set! (-> obj parent) #f)\n"
" (set! (-> obj brother) #f)\n"
" (set! (-> obj child) #f)\n"
" (set! (-> obj parent) (the-as (pointer process-tree) #f))\n"
" (set! (-> obj brother) (the-as (pointer process-tree) #f))\n"
" (set! (-> obj child) (the-as (pointer process-tree) #f))\n"
" (set! (-> obj self) obj)\n"
" (set! (-> obj ppointer) (&-> obj self))\n"
" (let\n"
@ -1264,7 +1282,7 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) {
" )\n"
" )\n"
" )\n"
" (set! (-> obj dead-list next) (-> obj process-list))\n"
" (set! (-> obj dead-list next) (the-as dead-pool-heap-rec (-> obj process-list)))\n"
" (set! (-> obj alive-list process) #f)\n"
" (set! (-> obj process-list (+ arg3 -1) next) #f)\n"
" (set! (-> obj alive-list prev) (-> obj alive-list))\n"
@ -1274,7 +1292,7 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) {
" (set! (-> obj first-shrink) #f)\n"
" (set!\n"
" (-> obj heap base)\n"
" (logand -16 (+ (+ (the-as int obj) 115) (* 12 arg3)))\n"
" (the-as pointer (logand -16 (+ (+ (the-as int obj) 115) (* 12 arg3))))\n"
" )\n"
" (set! (-> obj heap current) (-> obj heap base))\n"
" (set! (-> obj heap top) (&+ (-> obj heap base) arg4))\n"
@ -1314,13 +1332,16 @@ TEST_F(FormRegressionTest, ExprMethod22DeadPoolHeap) {
" 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"
"(the-as pointer (if (-> arg1 process)\n"
" (+\n"
" (+\n"
" (+ (-> arg1 process allocated-length) -4)\n"
" (the-as int (-> process size))\n"
" )\n"
" (the-as int (-> arg1 process))\n"
" )\n"
" (-> arg0 heap base)\n"
" )\n"
" )";
test_with_expr(func, type, expected);
}
@ -1382,26 +1403,26 @@ TEST_F(FormRegressionTest, ExprMethod21DeadPoolHeap) {
" daddu sp, sp, r0";
std::string type = "(function dead-pool-heap dead-pool-heap-rec int)";
std::string expected =
"(if\n"
" (-> arg1 process)\n"
"(if (-> arg1 process)\n"
" (let\n"
" ((v1-3\n"
" (&+\n"
" (&+ (-> arg1 process) (-> process size))\n"
" (&+ (the-as pointer (-> arg1 process)) (-> process size))\n"
" (-> arg1 process allocated-length)\n"
" )\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 4)))\n"
" (if (-> arg1 next)\n"
" (&- (the-as pointer (-> arg1 next process)) (the-as uint v1-3))\n"
" (&- (-> arg0 heap top) (the-as uint (&+ v1-3 4)))\n"
" )\n"
" )\n"
" (if\n"
" (-> arg1 next)\n"
" (- (-> arg1 next process) (the-as uint (&+ (-> arg0 heap base) 4)))\n"
" (- (-> arg0 heap top) (the-as uint (-> arg0 heap base)))\n"
" (if (-> arg1 next)\n"
" (&-\n"
" (the-as pointer (-> arg1 next process))\n"
" (the-as uint (&+ (-> arg0 heap base) 4))\n"
" )\n"
" (&- (-> arg0 heap top) (the-as uint (-> arg0 heap base)))\n"
" )\n"
" )";
test_with_expr(func, type, expected, false, "", {},
@ -1539,7 +1560,7 @@ TEST_F(FormRegressionTest, ExprMethod3DeadPoolHeap) {
std::string expected =
"(begin\n"
" (let*\n"
" ((s5-0 (- (-> arg0 heap top) (the-as uint (-> arg0 heap base))))\n"
" ((s5-0 (&- (-> arg0 heap top) (the-as uint (-> arg0 heap base))))\n"
" (v1-3\n"
" (if\n"
" (-> arg0 alive-list prev)\n"
@ -1599,7 +1620,8 @@ TEST_F(FormRegressionTest, ExprMethod5DeadPoolHeap) {
" 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))";
std::string expected =
"(+ (the-as int (- -4 (the-as int arg0))) (the-as int (-> arg0 heap top)))";
test_with_expr(func, type, expected, false, "", {},
parse_cast_json("[[3, \"v1\", \"int\"], [3, \"a0\", \"int\"]]"));
}
@ -1665,7 +1687,7 @@ TEST_F(FormRegressionTest, ExprMethod20DeadPoolHeap) {
" 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)))";
std::string expected = "(&- (-> arg0 heap top) (the-as uint (-> arg0 heap base)))";
test_with_expr(func, type, expected);
}
@ -1706,7 +1728,7 @@ TEST_F(FormRegressionTest, ExprMethod25DeadPoolHeap) {
" (if\n"
" (-> arg0 alive-list prev)\n"
" (gap-size arg0 (-> arg0 alive-list prev))\n"
" (- v1-0 (the-as uint (-> arg0 heap base)))\n"
" (&- v1-0 (the-as uint (-> arg0 heap base)))\n"
" )\n"
" )";
test_with_expr(func, type, expected);
@ -1987,7 +2009,7 @@ TEST_F(FormRegressionTest, ExprMethod14DeadPoolHeap) {
"(let\n"
" ((s4-0 (-> arg0 dead-list next)) (s3-0 (the-as process #f)))\n"
" (let\n"
" ((s1-0 (find-gap-by-size arg0 (+ (-> process size) (the-as uint arg2)))))\n"
" ((s1-0 (find-gap-by-size arg0 (the-as int (+ (-> process size) (the-as uint arg2))))))\n"
" (cond\n"
" ((and s4-0 s1-0)\n"
" (set! (-> arg0 dead-list next) (-> s4-0 next))\n"
@ -2193,8 +2215,7 @@ TEST_F(FormRegressionTest, ExprMethod15DeadPoolHeap) {
// NOTE: has wrong types for s5-1, but it's okay
std::string expected =
"(begin\n"
" (if\n"
" (!= arg0 (-> arg1 pool))\n"
" (if (!= arg0 (-> arg1 pool))\n"
" (format\n"
" 0\n"
" \"ERROR: process ~A does not belong to dead-pool-heap ~A.~%\"\n"
@ -2203,35 +2224,36 @@ TEST_F(FormRegressionTest, ExprMethod15DeadPoolHeap) {
" )\n"
" )\n"
" (change-parent arg1 arg0)\n"
" (set! (-> arg0 child) #f)\n"
" (let\n"
" ((s5-1 (-> arg1 ppointer)))\n"
" (set! (-> arg0 child) (the-as (pointer process-tree) #f))\n"
" (let ((s5-1 (-> arg1 ppointer)))\n"
" (if\n"
" (or\n"
" (= (-> arg0 first-gap) s5-1)\n"
" (<\n"
" (the-as int (gap-location arg0 s5-1))\n"
" (the-as int (gap-location arg0 (the-as dead-pool-heap-rec s5-1)))\n"
" (the-as int (gap-location arg0 (-> arg0 first-gap)))\n"
" )\n"
" )\n"
" (set! (-> arg0 first-gap) (-> s5-1 1))\n"
" (set! (-> arg0 first-gap) (the-as dead-pool-heap-rec (-> s5-1 1)))\n"
" )\n"
" (when\n"
" (= (-> arg0 first-shrink) s5-1)\n"
" (set! (-> arg0 first-shrink) (-> s5-1 1))\n"
" (when (not (-> arg0 first-shrink process)) (set! (-> arg0 first-shrink) #f))\n"
" (when (= (-> arg0 first-shrink) s5-1)\n"
" (set! (-> arg0 first-shrink) (the-as dead-pool-heap-rec (-> s5-1 1)))\n"
" (when (not (-> arg0 first-shrink process))\n"
" (set! (-> arg0 first-shrink) #f)\n"
" )\n"
" )\n"
" (set! (-> s5-1 1 parent) (-> s5-1 2))\n"
" (if\n"
" (-> s5-1 2)\n"
" (set! (-> s5-1 2 mask) (-> s5-1 1))\n"
" (set! (-> arg0 alive-list prev) (-> s5-1 1))\n"
" (set! (-> s5-1 1 parent) (the-as (pointer process-tree) (-> s5-1 2)))\n"
" (if (-> s5-1 2)\n"
" (set! (-> s5-1 2 mask) (the-as uint (-> s5-1 1)))\n"
" (set! (-> arg0 alive-list prev) (the-as dead-pool-heap-rec (-> s5-1 1)))\n"
" )\n"
" (set! (-> s5-1 2) (-> arg0 dead-list next))\n"
" (set! (-> arg0 dead-list next) s5-1)\n"
" (set! (-> s5-1 2) (the-as process-tree (-> arg0 dead-list next)))\n"
" (set! (-> arg0 dead-list next) (the-as dead-pool-heap-rec s5-1))\n"
" (set! (-> s5-1 0) *null-process*)\n"
" )\n"
" (let ((v0-4 0)))\n"
" (let ((v0-4 0))\n"
" )\n"
" (none)\n"
" )";
test_with_expr(func, type, expected, false, "",
{{"L297", "ERROR: process ~A does not belong to dead-pool-heap ~A.~%"}});
@ -2326,35 +2348,41 @@ TEST_F(FormRegressionTest, ExprMethod17DeadPoolHeap) {
" daddiu sp, sp, 64";
std::string type = "(function dead-pool-heap process dead-pool-heap)";
// NOTE - this has bad types.
std::string expected =
"(begin\n"
" (if\n"
" arg1\n"
" (let\n"
" ((s5-0 (-> arg1 ppointer)))\n"
" (when\n"
" (not\n"
" (or\n"
" (nonzero? (logand (-> arg1 mask) 512))\n"
" (and (not (-> arg1 next-state)) (not (-> arg1 state)))\n"
" )\n"
" )\n"
" (set!\n"
" (-> arg1 allocated-length)\n"
" (- (-> arg1 heap-cur) (the-as uint (-> arg1 stack)))\n"
" )\n"
" (set! (-> arg1 heap-top) (&-> arg1 stack (-> arg1 allocated-length)))\n"
" (if\n"
" (< (the-as int arg1) (the-as int (gap-location arg0 (-> arg0 first-gap))))\n"
" (set! (-> arg0 first-gap) (find-gap arg0 s5-0))\n"
" )\n"
" (set! (-> arg1 mask) (logior (-> arg1 mask) 512))\n"
" )\n"
" (if\n"
" (= (-> arg0 first-shrink) s5-0)\n"
" (set! (-> arg0 first-shrink) (-> s5-0 2))\n"
" )\n"
" )\n"
" (if arg1 (let ((s5-0 (-> arg1 ppointer)))\n"
" (when\n"
" (not\n"
" (or\n"
" (nonzero? (logand (-> arg1 mask) 512))\n"
" (and (not (-> arg1 next-state)) (not (-> arg1 state)))\n"
" )\n"
" )\n"
" (set!\n"
" (-> arg1 allocated-length)\n"
" (&- (-> arg1 heap-cur) (the-as uint (-> arg1 stack)))\n"
" )\n"
" (set!\n"
" (-> arg1 heap-top)\n"
" (&-> arg1 stack (-> arg1 allocated-length))\n"
" )\n"
" (if\n"
" (<\n"
" (the-as int arg1)\n"
" (the-as int (gap-location arg0 (-> arg0 first-gap)))\n"
" )\n"
" (set! (-> arg0 first-gap) (find-gap arg0 (the-as dead-pool-heap-rec s5-0)))\n"
" )\n"
" (set! (-> arg1 mask) (logior (-> arg1 mask) 512))\n"
" )\n"
" (if (= (-> arg0 first-shrink) s5-0)\n"
" (set!\n"
" (-> arg0 first-shrink)\n"
" (the-as dead-pool-heap-rec (-> s5-0 2))\n"
" )\n"
" )\n"
" )\n"
" )\n"
" arg0\n"
" )";
@ -2570,8 +2598,8 @@ TEST_F(FormRegressionTest, ExprMethod16DeadPoolHeap) {
" ((< f0-2 (l.f L348)) (set! arg1 (shl arg1 1)) (let ((v1-12 arg1))))\n"
" )\n"
" )\n"
" (set! (-> arg0 compact-count-targ) arg1)\n"
" (set! (-> arg0 compact-count) 0)\n"
" (set! (-> arg0 compact-count-targ) (the-as uint arg1))\n"
" (set! (-> arg0 compact-count) (the-as uint 0))\n"
" (while\n"
" (nonzero? arg1)\n"
" (+! arg1 -1)\n"
@ -2604,6 +2632,7 @@ TEST_F(FormRegressionTest, ExprMethod16DeadPoolHeap) {
" )\n"
" )\n"
" (let ((v0-8 0)))\n"
" (none)\n"
" )";
test_with_expr(func, type, expected, false, "", {{"L296", "~3LLow Actor Memory~%~0L"}});
}
@ -2798,7 +2827,7 @@ TEST_F(FormRegressionTest, ExprMethod18DeadPoolHeap) {
" (-> s4-0 process)\n"
" (relocate\n"
" (-> s4-0 process)\n"
" (- (gap-location arg0 a1-3) (the-as uint (&-> (-> s4-0 process) type)))\n"
" (&- (gap-location arg0 a1-3) (the-as uint (&-> (-> s4-0 process) type)))\n"
" )\n"
" )\n"
" )\n"
@ -2806,6 +2835,7 @@ TEST_F(FormRegressionTest, ExprMethod18DeadPoolHeap) {
" )\n"
" )\n"
" (let ((v0-4 0)))\n"
" (none)\n"
" )";
test_with_expr(func, type, expected);
}

View File

@ -15,8 +15,7 @@ const std::unordered_set<std::string> g_object_files_to_decompile = {"gcommon",
// the object files to check against a reference in test/decompiler/reference
const std::vector<std::string> g_object_files_to_check_against_reference = {
"gcommon", // NOTE: this file needs work, but adding it for now just to test the framework.
"gstring-h", "gkernel-h",
/*"gkernel"*/};
"gstring-h", "gkernel-h", "gkernel"};
// the functions we expect the decompiler to skip
const std::unordered_set<std::string> expected_skip_in_decompiler = {
@ -47,25 +46,15 @@ const std::unordered_set<std::string> skip_in_compiling = {
//////////////////////
// these functions are not implemented by the compiler in OpenGOAL, but are in GOAL.
"abs",
"ash",
"min",
"max",
"lognor",
"abs", "ash", "min", "max", "lognor",
// weird PS2 specific debug registers:
"breakpoint-range-set!",
// these require 128-bit integers. We want these eventually, but disabling for now to focus
// on more important issues.
"(method 3 vec4s)",
"(method 2 vec4s)",
"qmem-copy<-!",
"qmem-copy->!",
"(method 2 array)",
"(method 3 vec4s)", "(method 2 vec4s)", "qmem-copy<-!", "qmem-copy->!", "(method 2 array)",
"(method 3 array)",
// does weird stuff with the type system.
"print",
"printl",
"inspect",
"print", "printl", "inspect",
// inline assembly
"valid?",
@ -74,23 +63,18 @@ const std::unordered_set<std::string> skip_in_compiling = {
//////////////////////
// bitfields, possibly inline assembly
"(method 2 handle)",
};
// The decompiler does not attempt to insert forward definitions, as this would be part of an
// unimplemented full-program type analysis pass. For now, we manually specify all functions
// that should have a forward definition here.
const std::string g_forward_type_defs =
// used out of order
"(define-extern name= (function basic basic symbol))\n"
// recursive
"(define-extern fact (function int int))\n"
// gkernel-h
"(declare-type process basic)\n"
"(declare-type stack-frame basic)\n"
"(declare-type state basic)\n"
"(declare-type cpu-thread basic)\n"
"(declare-type dead-pool basic)\n"
"(declare-type event-message-block structure)\n";
//////////////////////
// GKERNEL
//////////////////////
// these refer to anonymous functions, which aren't yet implemented.
"process-by-name", "process-not-name", "process-count", "kill-by-type", "kill-not-type",
"kill-by-name", "kill-not-name", "kernel-dispatcher",
// asm
"(method 10 process)"
};
// default location for the data. It can be changed with a command line argument.
std::string g_iso_data_path = "";
@ -358,6 +342,10 @@ TEST_F(OfflineDecompilation, Reference) {
std::string src = db->ir2_final_out(obj_l.at(0));
// if (file == "gkernel") {
// fmt::print("{}\n", src);
// }
auto reference = file_util::read_text_file(file_util::get_file_path(
{"test", "decompiler", "reference", fmt::format("{}_REF.gc", file)}));
@ -371,7 +359,8 @@ TEST_F(OfflineDecompilation, Reference) {
TEST_F(OfflineDecompilation, Compile) {
Compiler compiler;
compiler.run_front_end_on_string(g_forward_type_defs);
compiler.run_front_end_on_string(file_util::read_text_file(file_util::get_file_path(
{"test", "decompiler", "reference", "all_forward_declarations.gc"})));
for (auto& file : g_object_files_to_check_against_reference) {
auto& obj_l = db->obj_files_by_name.at(file);