[Decompiler] Stack Variables (#338)

* clean up type analysis

* get everything set up

* basic stack variables working

* partial load fix

* most of matrix

* add offline tests
This commit is contained in:
water111 2021-03-27 15:18:59 -04:00 committed by GitHub
parent 7fac11ddf5
commit 64c35ca453
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 4223 additions and 387 deletions

View file

@ -10,6 +10,7 @@ add_library(
analysis/inline_asm_rewrite.cpp
analysis/insert_lets.cpp
analysis/reg_usage.cpp
analysis/type_analysis.cpp
analysis/variable_naming.cpp
data/game_count.cpp
@ -27,7 +28,6 @@ add_library(
Function/BasicBlocks.cpp
Function/CfgVtx.cpp
Function/Function.cpp
Function/TypeAnalysis.cpp
Function/TypeInspector.cpp
IR/BasicOpBuilder.cpp
@ -48,6 +48,7 @@ add_library(
ObjectFile/ObjectFileDB.cpp
ObjectFile/ObjectFileDB_IR2.cpp
util/config_parsers.cpp
util/data_decompile.cpp
util/DataParser.cpp
util/DecompilerTypeSystem.cpp

View file

@ -90,12 +90,6 @@ class Function {
const AtomicOp& get_atomic_op_at_instr(int idx);
int get_basic_op_count();
int get_failed_basic_op_count();
bool run_type_analysis_ir2(const TypeSpec& my_type,
DecompilerTypeSystem& dts,
LinkedObjectFile& file,
const std::unordered_map<int, std::vector<TypeCast>>& casts,
const std::unordered_map<std::string, LabelType>& label_types);
BlockTopologicalSort bb_topo_sort();
TypeSpec type;

View file

@ -963,38 +963,47 @@ void SetVarConditionOp::collect_vars(RegAccessSet& vars) const {
// StoreOp
/////////////////////////////
StoreOp::StoreOp(int size, bool is_float, SimpleExpression addr, SimpleAtom value, int my_idx)
StoreOp::StoreOp(int size, Kind kind, SimpleExpression addr, SimpleAtom value, int my_idx)
: AtomicOp(my_idx),
m_size(size),
m_is_float(is_float),
m_kind(kind),
m_addr(std::move(addr)),
m_value(std::move(value)) {}
goos::Object StoreOp::to_form(const std::vector<DecompilerLabel>& labels, const Env& env) const {
std::string store_name;
if (m_is_float) {
assert(m_size == 4);
store_name = "s.f!";
} else {
switch (m_size) {
case 1:
store_name = "s.b!";
break;
case 2:
store_name = "s.h!";
break;
case 4:
store_name = "s.w!";
break;
case 8:
store_name = "s.d!";
break;
case 16:
store_name = "s.q!";
break;
default:
assert(false);
}
switch (m_kind) {
case Kind::INTEGER:
switch (m_size) {
case 1:
store_name = "s.b!";
break;
case 2:
store_name = "s.h!";
break;
case 4:
store_name = "s.w!";
break;
case 8:
store_name = "s.d!";
break;
case 16:
store_name = "s.q!";
break;
default:
assert(false);
}
break;
case Kind::FLOAT:
assert(m_size == 4);
store_name = "s.f!";
break;
case Kind::VECTOR_FLOAT:
assert(m_size == 16);
store_name = "s.vf!";
break;
default:
assert(false);
}
return pretty_print::build_list(pretty_print::to_symbol(store_name), m_addr.to_form(labels, env),
@ -1081,6 +1090,11 @@ goos::Object LoadVarOp::to_form(const std::vector<DecompilerLabel>& labels, cons
assert(false);
}
break;
case Kind::VECTOR_FLOAT:
assert(m_size == 16);
forms.push_back(pretty_print::build_list("l.vf", m_src.to_form(labels, env)));
break;
default:
assert(false);
}

View file

@ -420,12 +420,14 @@ class SetVarConditionOp : public AtomicOp {
*/
class StoreOp : public AtomicOp {
public:
StoreOp(int size, bool is_float, SimpleExpression addr, SimpleAtom value, int my_idx);
enum class Kind { INTEGER, FLOAT, VECTOR_FLOAT, INVALID };
StoreOp(int size, Kind kind, SimpleExpression addr, SimpleAtom value, 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;
RegisterAccess get_set_destination() const override;
FormElement* get_as_form(FormPool& pool, const Env& env) const override;
FormElement* get_vf_store_as_form(FormPool& pool, const Env& env) const;
void update_register_info() override;
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
@ -436,7 +438,7 @@ class StoreOp : public AtomicOp {
private:
int m_size;
bool m_is_float;
Kind m_kind = Kind::INVALID;
SimpleExpression m_addr;
SimpleAtom m_value;
};
@ -447,13 +449,14 @@ class StoreOp : public AtomicOp {
*/
class LoadVarOp : public AtomicOp {
public:
enum class Kind { UNSIGNED, SIGNED, FLOAT };
enum class Kind { UNSIGNED, SIGNED, FLOAT, VECTOR_FLOAT };
LoadVarOp(Kind kind, int size, RegisterAccess dst, SimpleExpression src, 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;
RegisterAccess get_set_destination() const override;
FormElement* get_as_form(FormPool& pool, const Env& env) const override;
Form* get_load_src(FormPool& pool, const Env& env) const;
void update_register_info() override;
TypeState propagate_types_internal(const TypeState& input,
const Env& env,

View file

@ -40,28 +40,40 @@ ConditionElement* IR2_Condition::get_as_form(FormPool& pool, const Env& env, int
FormElement* SetVarOp::get_as_form(FormPool& pool, const Env& env) const {
if (env.has_type_analysis() && m_src.args() == 2 && m_src.get_arg(1).is_int() &&
m_src.get_arg(0).is_var() && m_src.kind() == SimpleExpression::Kind::ADD) {
auto arg0_type = env.get_types_before_op(m_my_idx).get(m_src.get_arg(0).var().reg());
if (arg0_type.kind == TP_Type::Kind::TYPESPEC) {
// access a field.
FieldReverseLookupInput rd_in;
rd_in.deref = std::nullopt;
rd_in.stride = 0;
rd_in.offset = m_src.get_arg(1).get_int();
rd_in.base_type = arg0_type.typespec();
auto rd = env.dts->ts.reverse_field_lookup(rd_in);
if (rd.success) {
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(
nullptr, SimpleAtom::make_var(m_src.get_arg(0).var()).as_expr(), m_my_idx);
std::vector<DerefToken> tokens;
for (auto& x : rd.tokens) {
tokens.push_back(to_token(x));
if (m_src.get_arg(0).var().reg() == Register(Reg::GPR, Reg::SP)) {
// get a stack variable.
for (auto& var : env.stack_var_hints()) {
if (var.hint.stack_offset == m_src.get_arg(1).get_int()) {
// match!
return pool.alloc_element<SetVarElement>(
m_dst, pool.alloc_single_element_form<StackVarDefElement>(nullptr, var), true,
var.ref_type);
}
auto load =
pool.alloc_single_element_form<DerefElement>(nullptr, source, rd.addr_of, tokens);
}
} else {
// access a field
auto arg0_type = env.get_types_before_op(m_my_idx).get(m_src.get_arg(0).var().reg());
if (arg0_type.kind == TP_Type::Kind::TYPESPEC) {
FieldReverseLookupInput rd_in;
rd_in.deref = std::nullopt;
rd_in.stride = 0;
rd_in.offset = m_src.get_arg(1).get_int();
rd_in.base_type = arg0_type.typespec();
auto rd = env.dts->ts.reverse_field_lookup(rd_in);
return pool.alloc_element<SetVarElement>(m_dst, load, true,
m_source_type.value_or(TypeSpec("object")));
if (rd.success) {
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(
nullptr, SimpleAtom::make_var(m_src.get_arg(0).var()).as_expr(), m_my_idx);
std::vector<DerefToken> tokens;
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,
m_source_type.value_or(TypeSpec("object")));
}
}
}
}
@ -115,6 +127,9 @@ std::optional<TypeSpec> get_typecast_for_atom(const SimpleAtom& atom,
auto type_info = env.dts->ts.lookup_type(expected_type);
switch (atom.get_kind()) {
case SimpleAtom::Kind::VARIABLE: {
if (atom.var().reg().get_kind() == Reg::VF) {
return {}; // no casts needed for VF registers.
}
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())) {
@ -162,7 +177,48 @@ std::optional<TypeSpec> get_typecast_for_atom(const SimpleAtom& atom,
}
} // namespace
FormElement* StoreOp::get_vf_store_as_form(FormPool& pool, const Env& env) const {
assert(m_value.is_var() && m_value.var().reg().get_kind() == Reg::VF);
if (env.has_type_analysis()) {
IR2_RegOffset ro;
if (get_as_reg_offset(m_addr, &ro)) {
auto& input_type = env.get_types_before_op(m_my_idx).get(ro.reg);
FieldReverseLookupInput rd_in;
DerefKind dk;
dk.is_store = true;
dk.reg_kind = get_reg_kind(ro.reg);
dk.size = m_size;
rd_in.deref = dk;
rd_in.base_type = input_type.typespec();
rd_in.stride = 0;
rd_in.offset = ro.offset;
auto rd = env.dts->ts.reverse_field_lookup(rd_in);
if (rd.success) {
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(
nullptr, SimpleAtom::make_var(ro.var).as_expr(), m_my_idx);
std::vector<DerefToken> tokens;
for (auto& x : rd.tokens) {
tokens.push_back(to_token(x));
}
assert(!rd.addr_of); // we'll change this to true because .svf uses an address.
auto addr = pool.alloc_single_element_form<DerefElement>(nullptr, source, true, tokens);
return pool.alloc_element<VectorFloatLoadStoreElement>(m_value.var().reg(), addr, false);
}
}
}
// nothing worked.
throw std::runtime_error("NYI get_vf_store_as_form fallback");
}
FormElement* StoreOp::get_as_form(FormPool& pool, const Env& env) const {
if (m_kind == Kind::VECTOR_FLOAT) {
return get_vf_store_as_form(pool, env);
}
if (env.has_type_analysis()) {
if (m_addr.is_identity() && m_addr.get_arg(0).is_sym_val()) {
// we are storing a value in a global symbol. This is something like sw rx, offset(s7)
@ -353,7 +409,7 @@ FormElement* StoreOp::get_as_form(FormPool& pool, const Env& env) const {
return pool.alloc_element<StoreElement>(this);
}
FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
Form* LoadVarOp::get_load_src(FormPool& pool, const Env& env) const {
if (env.has_type_analysis()) {
IR2_RegOffset ro;
if (get_as_reg_offset(m_src, &ro)) {
@ -372,9 +428,7 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
tokens.push_back(DerefToken::make_field_name(method_info.name));
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(
nullptr, SimpleAtom::make_var(ro.var).as_expr(), m_my_idx);
auto load = pool.alloc_single_element_form<DerefElement>(nullptr, source, false, tokens);
return pool.alloc_element<SetVarElement>(m_dst, load, true,
m_type.value_or(TypeSpec("object")));
return pool.alloc_single_element_form<DerefElement>(nullptr, source, false, tokens);
}
// todo structure method
@ -385,9 +439,7 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
if (input_type.kind == TP_Type::Kind::DYNAMIC_METHOD_ACCESS && ro.offset == 16) {
// access method vtable. The input is type + (4 * method), and the 16 is the offset
// of method 0.
auto load = pool.alloc_single_element_form<DynamicMethodAccess>(nullptr, ro.var);
return pool.alloc_element<SetVarElement>(m_dst, load, true,
m_type.value_or(TypeSpec("object")));
return pool.alloc_single_element_form<DynamicMethodAccess>(nullptr, ro.var);
}
if (input_type.kind == TP_Type::Kind::OBJECT_PLUS_PRODUCT_WITH_CONSTANT) {
@ -412,10 +464,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
// we pass along the register offset because code generation seems to be a bit
// different in different cases.
auto load = pool.alloc_single_element_form<ArrayFieldAccess>(
return pool.alloc_single_element_form<ArrayFieldAccess>(
nullptr, ro.var, tokens, input_type.get_multiplier(), ro.offset);
return pool.alloc_element<SetVarElement>(m_dst, load, true,
m_type.value_or(TypeSpec("object")));
}
}
@ -427,20 +477,15 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
if (ro.offset == 2) {
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(
nullptr, SimpleAtom::make_var(ro.var).as_expr(), m_my_idx);
auto load = pool.alloc_single_element_form<GenericElement>(
return pool.alloc_single_element_form<GenericElement>(
nullptr, GenericOperator::make_fixed(FixedOperatorKind::CDR), source);
// cdr = another pair.
return pool.alloc_element<SetVarElement>(m_dst, load, true,
m_type.value_or(TypeSpec("object")));
} else if (ro.offset == -2) {
// car = some object.
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(
nullptr, SimpleAtom::make_var(ro.var).as_expr(), m_my_idx);
auto load = pool.alloc_single_element_form<GenericElement>(
return pool.alloc_single_element_form<GenericElement>(
nullptr, GenericOperator::make_fixed(FixedOperatorKind::CAR), source);
// cdr = another pair.
return pool.alloc_element<SetVarElement>(m_dst, load, true,
m_type.value_or(TypeSpec("object")));
}
}
@ -467,10 +512,7 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
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,
m_type.value_or(TypeSpec("object")));
return pool.alloc_single_element_form<DerefElement>(nullptr, source, rd.addr_of, tokens);
}
if (input_type.typespec() == TypeSpec("pointer") ||
@ -505,10 +547,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
nullptr, SimpleAtom::make_var(ro.var).as_expr(), m_my_idx);
auto cast_dest = pool.alloc_single_element_form<CastElement>(
nullptr, TypeSpec("pointer", {TypeSpec(cast_type)}), dest);
auto deref = pool.alloc_single_element_form<DerefElement>(nullptr, cast_dest, false,
std::vector<DerefToken>());
return pool.alloc_element<SetVarElement>(m_dst, deref, true,
m_type.value_or(TypeSpec("object")));
return pool.alloc_single_element_form<DerefElement>(nullptr, cast_dest, false,
std::vector<DerefToken>());
}
}
}
@ -527,9 +567,7 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
assert(word.kind == LinkedWord::PLAIN_DATA);
float value;
memcpy(&value, &word.data, 4);
auto float_elt = pool.alloc_single_element_form<ConstantFloatElement>(nullptr, value);
return pool.alloc_element<SetVarElement>(m_dst, float_elt, true,
m_type.value_or(TypeSpec("object")));
return pool.alloc_single_element_form<ConstantFloatElement>(nullptr, value);
} else if (hint->second.type_name == "uint64" && m_kind != Kind::FLOAT && m_size == 8) {
assert((label.offset % 8) == 0);
auto word0 = env.file->words_by_seg.at(label.target_segment).at(label.offset / 4);
@ -540,18 +578,41 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
memcpy(&value, &word0.data, 4);
memcpy(((u8*)&value) + 4, &word1.data, 4);
auto val_elt = pool.alloc_single_element_form<ConstantTokenElement>(
nullptr, fmt::format("#x{:x}", value));
return pool.alloc_element<SetVarElement>(m_dst, val_elt, true,
m_type.value_or(TypeSpec("object")));
return pool.alloc_single_element_form<ConstantTokenElement>(nullptr,
fmt::format("#x{:x}", value));
}
}
}
}
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(nullptr, m_src, m_my_idx);
auto load = pool.alloc_single_element_form<LoadSourceElement>(nullptr, source, m_size, m_kind);
return pool.alloc_element<SetVarElement>(m_dst, load, true, m_type.value_or(TypeSpec("object")));
return pool.alloc_single_element_form<LoadSourceElement>(nullptr, source, m_size, m_kind);
}
FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
auto src = get_load_src(pool, env);
if (m_kind == Kind::VECTOR_FLOAT) {
assert(m_dst.reg().get_kind() == Reg::VF);
auto src_as_deref = dynamic_cast<DerefElement*>(src->try_as_single_element());
if (src_as_deref) {
assert(!src_as_deref->is_addr_of());
src_as_deref->set_addr_of(true);
return pool.alloc_element<VectorFloatLoadStoreElement>(m_dst.reg(), src, true);
}
auto src_as_unrecognized = dynamic_cast<LoadSourceElement*>(src->try_as_single_element());
if (src_as_unrecognized) {
return pool.alloc_element<VectorFloatLoadStoreElement>(m_dst.reg(),
src_as_unrecognized->location(), true);
}
throw std::runtime_error("VF unknown load");
} else {
assert(m_dst.reg().get_kind() != Reg::VF);
return pool.alloc_element<SetVarElement>(m_dst, src, true, m_type.value_or(TypeSpec("object")));
}
}
FormElement* BranchOp::get_as_form(FormPool& pool, const Env&) const {

View file

@ -223,6 +223,48 @@ TP_Type SimpleExpression::get_type_int1(const TypeState& input,
to_form(env.file->labels, env).print() + " " + arg_type.print());
}
namespace {
/*!
* Get the type of sp + offset.
*/
TP_Type get_stack_type_at_constant_offset(int offset,
const Env& env,
const DecompilerTypeSystem& dts) {
(void)dts;
for (auto& var : env.stack_var_hints()) {
if (offset < var.hint.stack_offset || offset >= (var.hint.stack_offset + var.size)) {
continue; // reject, it isn't in this variable
}
if (offset == var.hint.stack_offset) {
// special case just getting the variable
if (var.hint.container_type == StackVariableHint::ContainerType::NONE) {
return TP_Type::make_from_ts(coerce_to_reg_type(var.ref_type));
}
}
// Note: GOAL doesn't seem to constant propagate memory access on the stack, so the code
// below should never be needed.
/*
// yes, it is in the variable!
FieldReverseLookupInput rd_in;
rd_in.deref = std::nullopt; // not a deref
rd_in.stride = 0; // not a strided access
rd_in.offset = offset - var.hint.stack_offset; // offset into this var
rd_in.base_type = var.ref_type; // use ref type for ptr.
auto rd = dts.ts.reverse_field_lookup(rd_in);
if (rd.success) {
auto result = TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type));
fmt::print("Matched a stack variable! {}\n", result.print());
return result;
}
*/
// if we fail, keep trying others. This lets us have overlays in stack memory.
}
throw std::runtime_error(fmt::format("Failed to find a stack variable at offset {}", offset));
}
} // namespace
/*!
* Special case for "integer math".
*/
@ -267,6 +309,12 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
} break;
case Kind::ADD:
// get stack address:
if (m_args[0].is_var() && m_args[0].var().reg() == Register(Reg::GPR, Reg::SP) &&
m_args[1].is_int()) {
return get_stack_type_at_constant_offset(m_args[1].get_int(), env, dts);
}
if (arg0_type.is_product_with(4) && tc(dts, TypeSpec("type"), arg1_type)) {
// dynamic access into the method array with shift, add, offset-load
// no need to track the type because we don't know the method index anyway.
@ -726,7 +774,7 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input,
}
// rd failed, try as pair.
if (dts.type_prop_settings.allow_pair) {
if (env.allow_sloppy_pair_typing()) {
// we are strict here - only permit pair-type loads from object or pair.
// object is permitted for stuff like association lists where the car is also a pair.
if (m_kind == Kind::SIGNED && m_size == 4 &&
@ -755,11 +803,17 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input,
TypeState LoadVarOp::propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) {
TypeState result = input;
auto load_type = get_src_type(input, env, dts);
result.get(m_dst.reg()) = load_type;
m_type = load_type.typespec();
return result;
if (m_dst.reg().get_kind() == Reg::FPR || m_dst.reg().get_kind() == Reg::GPR) {
TypeState result = input;
auto load_type = get_src_type(input, env, dts);
result.get(m_dst.reg()) = load_type;
m_type = load_type.typespec();
return result;
} else {
// vector float loads show up as LoadVarOps, but we don't want to track types in the
// vector float registers.
return input;
}
}
TypeState BranchOp::propagate_types_internal(const TypeState& input,

View file

@ -6,6 +6,7 @@
#include "Form.h"
#include "decompiler/analysis/atomic_op_builder.h"
#include "common/goos/PrettyPrinter.h"
#include "common/util/math_util.h"
namespace decompiler {
void Env::set_remap_for_function(int nargs) {
@ -441,4 +442,45 @@ const UseDefInfo& Env::get_use_def_info(const RegisterAccess& ra) const {
auto var_id = get_program_var_id(ra);
return m_var_names.use_def_info.at(var_id);
}
/*!
* Set the stack hints. This must be done before type analysis.
* This actually parses the types, so it should be done after the dts is set up.
*/
void Env::set_stack_var_hints(const std::vector<StackVariableHint>& hints) {
for (auto& hint : hints) {
StackVarEntry entry;
entry.hint = hint;
// parse the type spec.
TypeSpec base_typespec = dts->parse_type_spec(hint.element_type);
auto type_info = dts->ts.lookup_type(base_typespec);
switch (hint.container_type) {
case StackVariableHint::ContainerType::NONE:
// just a plain object on the stack.
if (!type_info->is_reference()) {
throw std::runtime_error(
fmt::format("Stack variable type {} is not a reference and cannot be stored directly "
"on the stack. Use an array instead.",
base_typespec.print()));
}
entry.ref_type = base_typespec;
entry.size = type_info->get_size_in_memory();
// sanity check the alignment
if (align(entry.hint.stack_offset, type_info->get_in_memory_alignment()) !=
entry.hint.stack_offset) {
lg::error("Misaligned stack variable of type {} offset {} required align {}\n",
entry.ref_type.print(), entry.hint.stack_offset,
type_info->get_in_memory_alignment());
}
break;
default:
assert(false);
}
m_stack_vars.push_back(entry);
}
}
} // namespace decompiler

View file

@ -16,6 +16,12 @@ class Form;
class DecompilerTypeSystem;
struct FunctionAtomicOps;
struct StackVarEntry {
StackVariableHint hint;
TypeSpec ref_type; // the actual type of the address.
int size = -1;
};
/*!
* An "environment" for a single function.
* This contains data for an entire function, like which registers are live when, the types of
@ -108,6 +114,8 @@ class Env {
m_typecasts = casts;
}
const std::unordered_map<int, std::vector<TypeCast>>& casts() const { return m_typecasts; }
void set_remap_for_function(int nargs);
void set_remap_for_method(int nargs);
void set_remap_for_new_method(int nargs);
@ -129,6 +137,9 @@ class Env {
m_label_types = types;
}
void set_stack_var_hints(const std::vector<StackVariableHint>& hints);
const std::vector<StackVarEntry>& stack_var_hints() const { return m_stack_vars; }
const UseDefInfo& get_use_def_info(const RegisterAccess& ra) const;
void disable_use(const RegisterAccess& access) {
if (has_local_vars()) {
@ -146,6 +157,7 @@ class Env {
void set_retype_map(const std::unordered_map<std::string, TypeSpec>& map) { m_var_retype = map; }
// todo - remove these hacks at some point.
LinkedObjectFile* file = nullptr;
DecompilerTypeSystem* dts = nullptr;
@ -166,6 +178,7 @@ class Env {
bool m_allow_sloppy_pair_typing = false;
std::unordered_map<int, std::vector<TypeCast>> m_typecasts;
std::vector<StackVarEntry> m_stack_vars;
std::unordered_map<std::string, std::string> m_var_remap;
std::unordered_map<std::string, TypeSpec> m_var_retype;
std::unordered_map<std::string, LabelType> m_label_types;

View file

@ -211,6 +211,8 @@ goos::Object LoadSourceElement::to_form_internal(const Env& env) const {
return pretty_print::build_list("l.wu", m_addr->to_form(env));
case 8:
return pretty_print::build_list("l.d", m_addr->to_form(env));
case 16:
return pretty_print::build_list("l.q", m_addr->to_form(env));
default:
assert(false);
return {};
@ -229,6 +231,10 @@ goos::Object LoadSourceElement::to_form_internal(const Env& env) const {
return {};
}
break;
case LoadVarOp::Kind::VECTOR_FLOAT:
assert(m_size == 16);
return pretty_print::build_list("l.vf", m_addr->to_form(env));
break;
default:
assert(false);
return {};
@ -2213,6 +2219,75 @@ void LambdaDefinitionElement::collect_vars(RegAccessSet&, bool) const {}
void LambdaDefinitionElement::get_modified_regs(RegSet&) const {}
/////////////////////////////
// StackVarDefElement
/////////////////////////////
StackVarDefElement::StackVarDefElement(const StackVarEntry& entry) : m_entry(entry) {}
goos::Object StackVarDefElement::to_form_internal(const Env&) const {
switch (m_entry.hint.container_type) {
case StackVariableHint::ContainerType::NONE:
return pretty_print::build_list(fmt::format("new 'stack '{}", m_entry.ref_type.print()));
default:
assert(false);
}
}
void StackVarDefElement::apply_form(const std::function<void(Form*)>&) {}
void StackVarDefElement::apply(const std::function<void(FormElement*)>& f) {
f(this);
}
void StackVarDefElement::collect_vars(RegAccessSet&, bool) const {}
void StackVarDefElement::get_modified_regs(RegSet&) const {}
////////////////////////////////
// VectorFloatLoadStoreElement
////////////////////////////////
VectorFloatLoadStoreElement::VectorFloatLoadStoreElement(Register vf_reg,
Form* location,
bool is_load)
: m_vf_reg(vf_reg), m_location(location), m_is_load(is_load) {
location->parent_element = this;
}
goos::Object VectorFloatLoadStoreElement::to_form_internal(const Env& env) const {
if (m_is_load) {
return pretty_print::build_list(".lvf", pretty_print::to_symbol(m_vf_reg.to_charp()),
m_location->to_form(env));
} else {
return pretty_print::build_list(".svf", m_location->to_form(env),
pretty_print::to_symbol(m_vf_reg.to_charp()));
}
}
void VectorFloatLoadStoreElement::apply(const std::function<void(FormElement*)>& f) {
f(this);
m_location->apply(f);
}
void VectorFloatLoadStoreElement::apply_form(const std::function<void(Form*)>& f) {
m_location->apply_form(f);
}
void VectorFloatLoadStoreElement::collect_vars(RegAccessSet& vars, bool recursive) const {
if (recursive) {
m_location->collect_vars(vars, recursive);
}
}
void VectorFloatLoadStoreElement::get_modified_regs(RegSet&) const {
// vf's dont count
}
void VectorFloatLoadStoreElement::collect_vf_regs(RegSet& regs) const {
regs.insert(m_vf_reg);
}
std::optional<SimpleAtom> form_as_atom(const Form* f) {
auto as_single = f->try_as_single_element();
auto as_atom = dynamic_cast<SimpleAtomElement*>(as_single);

View file

@ -199,6 +199,7 @@ class LoadSourceElement : public FormElement {
int size() const { return m_size; }
LoadVarOp::Kind kind() const { return m_kind; }
const Form* location() const { return m_addr; }
Form* location() { return m_addr; }
void update_from_stack(const Env& env,
FormPool& pool,
FormStack& stack,
@ -1001,6 +1002,7 @@ class DerefElement : public FormElement {
Form* base() { return m_base; }
const std::vector<DerefToken>& tokens() const { return m_tokens; }
void set_base(Form* new_base);
void set_addr_of(bool is_addr_of) { m_is_addr_of = is_addr_of; }
private:
Form* m_base = nullptr;
@ -1247,6 +1249,41 @@ class LambdaDefinitionElement : public FormElement {
goos::Object m_def;
};
class StackVarDefElement : public FormElement {
public:
StackVarDefElement(const StackVarEntry& entry);
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;
void collect_vars(RegAccessSet& vars, bool recursive) const override;
void get_modified_regs(RegSet& regs) const override;
void update_from_stack(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects) override;
private:
StackVarEntry m_entry;
};
class VectorFloatLoadStoreElement : public FormElement {
public:
VectorFloatLoadStoreElement(Register vf_reg, Form* location, bool is_load);
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;
void collect_vars(RegAccessSet& vars, bool recursive) const override;
void get_modified_regs(RegSet& regs) const override;
void push_to_stack(const Env& env, FormPool& pool, FormStack& stack) override;
void collect_vf_regs(RegSet& regs) const;
private:
Register m_vf_reg;
Form* m_location = nullptr;
bool m_is_load = false;
};
/*!
* A Form is a wrapper around one or more FormElements.
* This is done for two reasons:

View file

@ -2600,6 +2600,11 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool,
true, TypeSpec("symbol"));
}
void VectorFloatLoadStoreElement::push_to_stack(const Env&, FormPool&, FormStack& stack) {
mark_popped();
stack.push_form_element(this, true);
}
void SimpleAtomElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
@ -2654,4 +2659,13 @@ void ConstantFloatElement::update_from_stack(const Env&,
result->push_back(this);
}
void StackVarDefElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
} // namespace decompiler

View file

@ -13,9 +13,6 @@ const std::map<InstructionKind, OpenGOALAsm::Function> MIPS_ASM_TO_OPEN_GOAL_FUN
{InstructionKind::PSRAW, {"TODO.PSRAW", {}}},
{InstructionKind::PSUBW, {"TODO.PSUBW", {}}},
{InstructionKind::LQ, {"TODO.LQ", {MOD::OFFSET}}},
{InstructionKind::SQ, {"TODO.SQ", {MOD::OFFSET}}},
// NOTE - depending on how this is used, this may case issues! Be Warned!
// lots of implicit logic in OpenGOAL depending on argument types!
{InstructionKind::MFC1, {".mov", {}}},

View file

@ -9,6 +9,7 @@
#include "common/util/Timer.h"
#include "common/util/FileUtil.h"
#include "decompiler/Function/TypeInspector.h"
#include "decompiler/analysis/type_analysis.h"
#include "decompiler/analysis/reg_usage.h"
#include "decompiler/analysis/insert_lets.h"
#include "decompiler/analysis/variable_naming.h"
@ -296,10 +297,17 @@ void ObjectFileDB::ir2_type_analysis_pass() {
func.type = ts;
attempted_functions++;
// try type analysis here.
auto hints =
get_config().type_casts_by_function_by_atomic_op_idx[func.guessed_name.to_string()];
auto func_name = func.guessed_name.to_string();
auto casts = get_config().type_casts_by_function_by_atomic_op_idx[func_name];
auto label_types = get_config().label_types[data.to_unique_name()];
if (func.run_type_analysis_ir2(ts, dts, data.linked_data, hints, label_types)) {
func.ir2.env.set_type_casts(casts);
func.ir2.env.set_label_types(label_types);
if (get_config().pair_functions_by_name.find(func_name) !=
get_config().pair_functions_by_name.end()) {
func.ir2.env.set_sloppy_pair_typing();
}
func.ir2.env.set_stack_var_hints(get_config().stack_var_hints_by_function[func_name]);
if (run_type_analysis_ir2(ts, dts, func)) {
successful_functions++;
func.ir2.env.types_succeeded = true;
} else {

View file

@ -164,17 +164,17 @@ std::unique_ptr<AtomicOp> make_standard_load(const Instruction& i0,
std::unique_ptr<AtomicOp> make_standard_store(const Instruction& i0,
int idx,
int store_size,
bool is_float) {
StoreOp::Kind kind) {
if (i0.get_src(2).is_reg(Register(Reg::GPR, Reg::SP))) {
return std::make_unique<AsmOp>(i0, idx);
}
SimpleAtom val;
SimpleExpression dst;
if (i0.get_src(0).is_reg(rs7())) {
assert(!is_float);
assert(kind == StoreOp::Kind::INTEGER);
val = SimpleAtom::make_sym_val("#f");
} else if (i0.get_src(0).is_reg(rr0())) {
assert(!is_float);
assert(kind == StoreOp::Kind::INTEGER);
val = SimpleAtom::make_int_constant(0);
} else {
val = make_src_atom(i0.get_src(0).get_reg(), idx);
@ -189,7 +189,7 @@ std::unique_ptr<AtomicOp> make_standard_store(const Instruction& i0,
SimpleAtom::make_int_constant(offset));
}
return std::make_unique<StoreOp>(store_size, is_float, dst, val, idx);
return std::make_unique<StoreOp>(store_size, kind, dst, val, idx);
}
std::unique_ptr<AtomicOp> make_asm_op(const Instruction& i0, int idx) {
@ -277,8 +277,6 @@ std::unique_ptr<AtomicOp> make_asm_op(const Instruction& i0, int idx) {
// Moves / Loads / Stores
case InstructionKind::CTC2:
case InstructionKind::CFC2:
case InstructionKind::SQC2:
case InstructionKind::LQC2:
case InstructionKind::LDR:
case InstructionKind::LDL:
case InstructionKind::QMTC2:
@ -593,9 +591,10 @@ std::unique_ptr<AtomicOp> convert_sw_1(const Instruction& i0, int idx) {
// store a register.
val = make_src_atom(i0.get_src(0).get_reg(), idx);
}
return std::make_unique<StoreOp>(4, false, SimpleAtom::make_sym_val(name).as_expr(), val, idx);
return std::make_unique<StoreOp>(4, StoreOp::Kind::INTEGER,
SimpleAtom::make_sym_val(name).as_expr(), val, idx);
} else {
return make_standard_store(i0, idx, 4, false);
return make_standard_store(i0, idx, 4, StoreOp::Kind::INTEGER);
}
}
@ -603,7 +602,7 @@ std::unique_ptr<AtomicOp> convert_sd_1(const Instruction& i0, int idx) {
if (i0.get_src(0).is_reg(rr0()) && i0.get_src(1).is_imm(2) && i0.get_src(2).is_reg(rr0())) {
return std::make_unique<SpecialOp>(SpecialOp::Kind::CRASH, idx);
} else {
return make_standard_store(i0, idx, 8, false);
return make_standard_store(i0, idx, 8, StoreOp::Kind::INTEGER);
}
}
@ -719,6 +718,8 @@ std::unique_ptr<AtomicOp> convert_1(const Instruction& i0, int idx) {
return make_standard_load(i0, idx, 8, LoadVarOp::Kind::UNSIGNED);
case InstructionKind::LQ:
return make_standard_load(i0, idx, 16, LoadVarOp::Kind::UNSIGNED);
case InstructionKind::LQC2:
return make_standard_load(i0, idx, 16, LoadVarOp::Kind::VECTOR_FLOAT);
case InstructionKind::DSLL:
return make_2reg_1imm_op(i0, SimpleExpression::Kind::LEFT_SHIFT, idx);
case InstructionKind::DSLL32:
@ -774,17 +775,19 @@ std::unique_ptr<AtomicOp> convert_1(const Instruction& i0, int idx) {
case InstructionKind::DSLLV:
return make_3reg_op(i0, SimpleExpression::Kind::LEFT_SHIFT, idx);
case InstructionKind::SB:
return make_standard_store(i0, idx, 1, false);
return make_standard_store(i0, idx, 1, StoreOp::Kind::INTEGER);
case InstructionKind::SH:
return make_standard_store(i0, idx, 2, false);
return make_standard_store(i0, idx, 2, StoreOp::Kind::INTEGER);
case InstructionKind::SW:
return convert_sw_1(i0, idx);
case InstructionKind::SD:
return convert_sd_1(i0, idx);
case InstructionKind::SQ:
return make_standard_store(i0, idx, 16, false);
return make_standard_store(i0, idx, 16, StoreOp::Kind::INTEGER);
case InstructionKind::SQC2:
return make_standard_store(i0, idx, 16, StoreOp::Kind::VECTOR_FLOAT);
case InstructionKind::SWC1:
return make_standard_store(i0, idx, 4, true);
return make_standard_store(i0, idx, 4, StoreOp::Kind::FLOAT);
case InstructionKind::CVTWS: // float to int
return make_2reg_op(i0, SimpleExpression::Kind::FLOAT_TO_INT, idx);
case InstructionKind::CVTSW: // int to float

View file

@ -26,6 +26,10 @@ bool rewrite_inline_asm_instructions(Form* top_level_form,
// operations
AsmOpElement* elem = dynamic_cast<AsmOpElement*>(entry);
if (!elem) {
auto as_load_store = dynamic_cast<VectorFloatLoadStoreElement*>(entry);
if (as_load_store) {
as_load_store->collect_vf_regs(vf_regs);
}
new_entries.push_back(entry);
continue;
}

View file

@ -296,6 +296,10 @@ Form* insert_cast_for_let(RegisterAccess dst,
return src;
}
bool register_can_hold_var(const Register& reg) {
return reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR;
}
} // namespace
LetStats insert_lets(const Function& func, Env& env, FormPool& pool, Form* top_level_form) {
@ -325,7 +329,7 @@ LetStats insert_lets(const Function& func, Env& env, FormPool& pool, Form* top_l
// and add it.
for (auto& access : reg_accesses) {
if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) {
if (register_can_hold_var(access.reg())) {
auto name = env.get_variable_name(access);
var_info[name].elts_using_var.insert(elt);
var_info[name].var_name = name;
@ -406,7 +410,7 @@ LetStats insert_lets(const Function& func, Env& env, FormPool& pool, Form* top_l
for (auto& info : sorted_info) {
auto first_form = info.lca_form->at(info.start_idx);
auto first_form_as_set = dynamic_cast<SetVarElement*>(first_form);
if (first_form_as_set &&
if (first_form_as_set && register_can_hold_var(first_form_as_set->dst().reg()) &&
env.get_variable_name(first_form_as_set->dst()) == env.get_variable_name(info.access) &&
!first_form_as_set->info().is_eliminated_coloring_move) {
bool allowed = true;

View file

@ -1,7 +1,4 @@
#include "decompiler/Function/Function.h"
#include "decompiler/IR/IR.h"
#include "third-party/fmt/core.h"
#include "decompiler/config.h"
#include "type_analysis.h"
namespace decompiler {
namespace {
@ -66,59 +63,32 @@ void try_modify_input_types_for_casts(
}
} // namespace
bool Function::run_type_analysis_ir2(
const TypeSpec& my_type,
DecompilerTypeSystem& dts,
LinkedObjectFile& file,
const std::unordered_map<int, std::vector<TypeCast>>& casts,
const std::unordered_map<std::string, LabelType>& label_types) {
(void)file;
ir2.env.set_type_casts(casts);
ir2.env.set_label_types(label_types);
bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, Function& func) {
// STEP 0 - set decompiler type system settings for this function. In config we can manually
// specify some settings for type propagation to reduce the strictness of type propagation.
// TODO - this is kinda hacky so that it works in both unit tests and actual decompilation.
// it would be better if this setting came 100% from the IR2 env.
if (!dts.type_prop_settings.locked) {
dts.type_prop_settings.reset();
if (get_config().pair_functions_by_name.find(guessed_name.to_string()) !=
get_config().pair_functions_by_name.end()) {
dts.type_prop_settings.allow_pair = true;
ir2.env.set_sloppy_pair_typing();
}
} else {
if (dts.type_prop_settings.allow_pair) {
ir2.env.set_sloppy_pair_typing();
}
}
if (guessed_name.kind == FunctionName::FunctionKind::METHOD) {
dts.type_prop_settings.current_method_type = guessed_name.type_name;
if (func.guessed_name.kind == FunctionName::FunctionKind::METHOD) {
dts.type_prop_settings.current_method_type = func.guessed_name.type_name;
}
if (my_type.last_arg() == TypeSpec("none")) {
auto as_end = dynamic_cast<FunctionEndOp*>(ir2.atomic_ops->ops.back().get());
auto as_end = dynamic_cast<FunctionEndOp*>(func.ir2.atomic_ops->ops.back().get());
assert(as_end);
as_end->mark_function_as_no_return_value();
}
std::vector<TypeState> block_init_types, op_types;
block_init_types.resize(basic_blocks.size());
op_types.resize(ir2.atomic_ops->ops.size());
auto& aop = ir2.atomic_ops;
block_init_types.resize(func.basic_blocks.size());
op_types.resize(func.ir2.atomic_ops->ops.size());
auto& aop = func.ir2.atomic_ops;
// STEP 1 - topologocial sort the blocks. This gives us an order where we:
// - never visit unreachable blocks (we can't type propagate these)
// - always visit at least one predecessor of a block before that block
auto order = bb_topo_sort();
auto order = func.bb_topo_sort();
assert(!order.vist_order.empty());
assert(order.vist_order.front() == 0);
// STEP 2 - initialize type state for the first block to the function argument types.
block_init_types.at(0) = construct_initial_typestate(my_type);
// and add hints from config
// TODO - this should be moved into the construct_initial_typestate
// try_apply_hints(0, hints, &block_init_types.at(0), dts);
// STEP 3 - propagate types until the result stops changing
bool run_again = true;
@ -126,25 +96,22 @@ bool Function::run_type_analysis_ir2(
run_again = false;
// do each block in the topological sort order:
for (auto block_id : order.vist_order) {
auto& block = basic_blocks.at(block_id);
auto& block = func.basic_blocks.at(block_id);
TypeState* init_types = &block_init_types.at(block_id);
for (int op_id = aop->block_id_to_first_atomic_op.at(block_id);
op_id < aop->block_id_to_end_atomic_op.at(block_id); op_id++) {
// apply type hints only if we are not the first op.
std::unordered_map<Register, TP_Type, Register::hash> restore_cast_types;
// if (op_id != aop->block_id_to_first_atomic_op.at(block_id)) {
try_modify_input_types_for_casts(op_id, casts, init_types, &restore_cast_types, dts);
//}
try_modify_input_types_for_casts(op_id, func.ir2.env.casts(), init_types,
&restore_cast_types, dts);
auto& op = aop->ops.at(op_id);
try {
op_types.at(op_id) = op->propagate_types(*init_types, ir2.env, dts);
op_types.at(op_id) = op->propagate_types(*init_types, func.ir2.env, dts);
} 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, my_type);
lg::warn("Function {} failed type prop: {}", func.guessed_name.to_string(), e.what());
func.warnings.type_prop_warning("{}", e.what());
func.ir2.env.set_types(block_init_types, op_types, *func.ir2.atomic_ops, my_type);
return false;
}
@ -165,11 +132,6 @@ bool Function::run_type_analysis_ir2(
// propagate the types: for each possible succ
for (auto succ_block_id : {block.succ_ft, block.succ_branch}) {
if (succ_block_id != -1) {
// apply hint
// try_apply_hints(aop->block_id_to_first_atomic_op.at(succ_block_id), hints,
// init_types,
// dts);
// set types to LCA (current, new)
if (dts.tp_lca(&block_init_types.at(succ_block_id), *init_types)) {
// if something changed, run again!
@ -182,7 +144,8 @@ bool Function::run_type_analysis_ir2(
auto last_type = op_types.back().get(Register(Reg::GPR, Reg::V0)).typespec();
if (last_type != my_type.last_arg()) {
warnings.info("Return type mismatch {} vs {}.", last_type.print(), my_type.last_arg().print());
func.warnings.info("Return type mismatch {} vs {}.", last_type.print(),
my_type.last_arg().print());
}
// and apply final casts:
@ -190,15 +153,16 @@ bool Function::run_type_analysis_ir2(
for (int op_id = aop->block_id_to_first_atomic_op.at(block_id);
op_id < aop->block_id_to_end_atomic_op.at(block_id); op_id++) {
if (op_id == aop->block_id_to_first_atomic_op.at(block_id)) {
try_modify_input_types_for_casts(op_id, casts, &block_init_types.at(block_id), nullptr,
dts);
try_modify_input_types_for_casts(op_id, func.ir2.env.casts(),
&block_init_types.at(block_id), nullptr, dts);
} else {
try_modify_input_types_for_casts(op_id, casts, &op_types.at(op_id - 1), nullptr, dts);
try_modify_input_types_for_casts(op_id, func.ir2.env.casts(), &op_types.at(op_id - 1),
nullptr, dts);
}
}
}
ir2.env.set_types(block_init_types, op_types, *ir2.atomic_ops, my_type);
func.ir2.env.set_types(block_init_types, op_types, *func.ir2.atomic_ops, my_type);
return true;
}

View file

@ -0,0 +1,13 @@
#pragma once
#include <vector>
#include <unordered_map>
#include <string>
#include "common/type_system/TypeSpec.h"
#include "decompiler/config.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
namespace decompiler {
bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, Function& func);
}

View file

@ -3,6 +3,7 @@
#include "third-party/fmt/core.h"
#include "common/util/FileUtil.h"
#include "common/util/json_util.h"
#include "decompiler/util/config_parsers.h"
namespace decompiler {
Config gConfig;
@ -151,5 +152,13 @@ void set_config(const std::string& path_to_config_file) {
}
}
}
auto stack_vars_json = read_json_file_from_config(cfg, "stack_vars_file");
for (auto& kv : stack_vars_json.items()) {
auto& func_name = kv.key();
auto& stack_vars = kv.value();
gConfig.stack_var_hints_by_function[func_name] = parse_stack_var_hints(stack_vars);
}
}
} // namespace decompiler

View file

@ -27,6 +27,21 @@ struct LocalVarOverride {
std::optional<std::string> type;
};
/*!
* Information about a variable pointing to some data on the stack.
*/
struct StackVariableHint {
std::string element_type; // type of the thing stored
// todo - is boxed array on the stack supported?
enum class ContainerType {
NONE, // just store the plain thing.
ARRAY, // for refs, array of refs. For values, array of values.
INLINE_ARRAY // for refs, array of values, for values, invalid
} container_type = ContainerType::NONE;
int container_size = -1; // if container other than NONE, the number of elements.
int stack_offset = 0; // where it's located on the stack (relative to sp after prologue)
};
struct Config {
int game_version = -1;
std::vector<std::string> dgo_names;
@ -60,11 +75,12 @@ struct Config {
std::unordered_map<std::string, std::vector<std::string>> function_arg_names;
std::unordered_map<std::string, std::unordered_map<std::string, LocalVarOverride>>
function_var_overrides;
std::unordered_map<std::string, std::unordered_map<std::string, LabelType>> label_types;
std::unordered_map<std::string, std::vector<StackVariableHint>> stack_var_hints_by_function;
bool run_ir2 = false;
};
Config& get_config();
void set_config(const std::string& path_to_config_file);
} // namespace decompiler

View file

@ -1409,49 +1409,44 @@
(define-extern matrixp*! (function matrix matrix matrix matrix))
(define-extern vector-matrix*! (function vector vector matrix vector))
(define-extern vector-rotate*! (function vector vector matrix vector))
(define-extern vector3s-matrix*! function)
(define-extern vector3s-rotate*! function)
(define-extern vector3s-matrix*! (function vector3s vector3s matrix vector3s))
(define-extern vector3s-rotate*! (function vector3s vector3s matrix vector3s))
(define-extern matrix-inverse-of-rot-trans! function)
(define-extern matrix-3x3-determinant function)
(define-extern matrix-3x3-inverse-transpose! function)
(define-extern matrix-scale! function)
(define-extern column-scale-matrix! function)
(define-extern matrix-rotate-yx! function)
(define-extern matrix-y-angle function)
(define-extern matrix-3x3-inverse! function)
(define-extern matrix-rotate-z! function)
;;(define-extern *identity-matrix* object) ;; unknown type
(define-extern matrix-axis-angle! function)
(define-extern matrix-transpose! (function matrix matrix matrix))
(define-extern matrix-inverse-of-rot-trans! (function matrix matrix matrix))
(define-extern matrix-4x4-inverse! (function matrix matrix matrix))
(define-extern matrix-translate! (function matrix vector matrix))
(define-extern matrix-translate+! (function matrix matrix vector matrix))
(define-extern matrix-scale! (function matrix vector matrix))
(define-extern scale-matrix! (function matrix vector matrix matrix))
(define-extern matrix-inv-scale! (function matrix vector matrix))
(define-extern column-scale-matrix! (function matrix vector matrix matrix))
(define-extern matrix-rotate-x! (function matrix float matrix))
(define-extern matrix-rotate-y! (function matrix float matrix))
(define-extern matrix-rotate-z! (function matrix float matrix))
(define-extern matrix-rotate-zyx! (function matrix vector matrix))
(define-extern matrix-rotate-x! function)
(define-extern matrix-rotate-xyz! (function matrix vector matrix))
(define-extern matrix-rotate-zxy! (function matrix vector matrix))
(define-extern matrix-rotate-yxz! (function matrix vector matrix))
(define-extern matrix-rotate-yzx! (function matrix vector matrix))
(define-extern matrix-rotate-yxy! (function matrix vector matrix))
(define-extern matrix-rotate-yx! (function matrix float float matrix))
(define-extern matrix-lerp! (function matrix matrix matrix float matrix))
(define-extern matrix-3x3-determinant (function matrix float))
(define-extern matrix3-determinant (function matrix float))
(define-extern matrix-3x3-inverse! (function matrix matrix matrix))
(define-extern matrix-3x3-inverse-transpose! (function matrix matrix matrix))
(define-extern matrix3-inverse-transpose! (function matrix matrix matrix))
(define-extern matrix-4x4-determinant (function matrix float))
(define-extern matrix-4x4-inverse-transpose! (function matrix matrix matrix))
(define-extern matrix-y-angle (function matrix float))
(define-extern matrix-rotate-yxy! function)
(define-extern *identity-matrix* matrix)
(define-extern matrix-axis-sin-cos! (function matrix vector float float none))
(define-extern matrix-axis-angle! (function matrix vector float none))
(define-extern matrix-4x4-determinant function)
(define-extern matrix-inv-scale! function)
(define-extern matrix-rotate-yzx! function)
(define-extern matrix-axis-sin-cos! function)
(define-extern matrix-lerp! function)
(define-extern matrix-rotate-y! function)
(define-extern matrix-rotate-xyz! function)
(define-extern scale-matrix! function)
(define-extern matrix-rotate-yxz! function)
(define-extern matrix-rotate-zxy! function)
(define-extern matrix3-determinant function)
(define-extern matrix3-inverse-transpose! function)
(define-extern matrix-translate+! function)
(define-extern matrix-transpose! function)
(define-extern matrix-4x4-inverse-transpose! function)
(define-extern matrix-axis-sin-cos-vu! function)
(define-extern matrix-translate! function)
(define-extern matrix-4x4-inverse! function)
(define-extern trs-matrix-calc! function)
(define-extern transform-matrix-parent-calc! function)
(define-extern transform-matrix-calc! function)

View file

@ -57,6 +57,7 @@
"anonymous_function_types_file":"decompiler/config/jak1_ntsc_black_label/anonymous_function_types.jsonc",
"var_names_file":"decompiler/config/jak1_ntsc_black_label/var_names.jsonc",
"label_types_file":"decompiler/config/jak1_ntsc_black_label/label_types.jsonc",
"stack_vars_file":"decompiler/config/jak1_ntsc_black_label/stack_vars.jsonc",
"analyze_functions":true,
"analyze_expressions":true,

View file

@ -45,6 +45,10 @@
["L2", "_auto_", true]
],
"matrix":[
["L60", "float", true]
],
"trigonometry": [
["L143", "float", true],
["L144", "float", true],

View file

@ -0,0 +1,42 @@
{
"matrixp*!": [[16, "matrix"]],
"vector3s-matrix*!": [[16, "vector"]],
"vector3s-rotate*!": [[16, "vector"]],
"matrix-rotate-zyx!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-rotate-xyz!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-rotate-zxy!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-rotate-yxz!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-rotate-yzx!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-rotate-yxy!":[
[16, "vector"],
[32, "vector"],
[48, "vector"]
],
"matrix-rotate-yx!":[
[16, "matrix"]
]
}

View file

@ -44,14 +44,10 @@ class DecompilerTypeSystem {
int get_format_arg_count(const TP_Type& type) const;
TypeSpec lookup_symbol_type(const std::string& name) const;
// todo - totally eliminate this.
struct {
bool locked = false;
bool allow_pair;
std::string current_method_type;
void reset() {
allow_pair = false;
current_method_type.clear();
}
void reset() { current_method_type.clear(); }
} type_prop_settings;
private:

View file

@ -280,7 +280,6 @@ struct TypeState {
case Reg::FPR:
return fpr_types[r.get_fpr()];
default:
lg::die("Cannot use register {} with TypeState.", r.to_charp());
assert(false);
throw std::runtime_error("TP_Type::get failed");
}

View file

@ -0,0 +1,52 @@
#include "config_parsers.h"
#include "common/util/json_util.h"
namespace decompiler {
std::vector<StackVariableHint> parse_stack_var_hints(const nlohmann::json& json) {
std::vector<StackVariableHint> result;
for (auto& stack_var : json) {
StackVariableHint hint;
hint.stack_offset = stack_var.at(0).get<int>();
auto& type_info = stack_var.at(1);
if (type_info.is_array()) {
auto container_type = type_info.at(0).get<std::string>();
if (container_type == "array") {
hint.container_type = StackVariableHint::ContainerType::ARRAY;
} else if (container_type == "inline-array") {
hint.container_type = StackVariableHint::ContainerType::INLINE_ARRAY;
} else {
throw std::runtime_error("Container type is invalid: " + container_type);
}
hint.element_type = type_info.at(1).get<std::string>();
hint.container_size = type_info.at(2).get<int>();
} else if (type_info.is_string()) {
hint.container_type = StackVariableHint::ContainerType::NONE;
hint.container_size = -1;
hint.element_type = type_info.get<std::string>();
} else {
throw std::runtime_error("Invalid stack var override");
}
result.push_back(hint);
}
return result;
}
std::unordered_map<int, std::vector<decompiler::TypeCast>> parse_cast_hints(
const nlohmann::json& casts) {
std::unordered_map<int, std::vector<decompiler::TypeCast>> out;
for (auto& cast : casts) {
auto idx_range = parse_json_optional_integer_range(cast.at(0));
for (auto idx : idx_range) {
TypeCast type_cast;
type_cast.atomic_op_idx = idx;
type_cast.reg = Register(cast.at(1));
type_cast.type_name = cast.at(2).get<std::string>();
out[idx].push_back(type_cast);
}
}
return out;
}
} // namespace decompiler

View file

@ -0,0 +1,10 @@
#pragma once
#include <vector>
#include "decompiler/config.h"
#include "third-party/json.hpp"
namespace decompiler {
std::vector<StackVariableHint> parse_stack_var_hints(const nlohmann::json& json);
std::unordered_map<int, std::vector<decompiler::TypeCast>> parse_cast_hints(
const nlohmann::json& casts);
} // namespace decompiler

File diff suppressed because it is too large Load diff

View file

@ -5,4 +5,9 @@
;; name in dgo: trigonometry-h
;; dgos: GAME, ENGINE
;; this file generates no code.
;; this file generates no code.
(defun-extern sin float float)
(defun-extern cos float float)
(defun-extern vector-sincos! vector vector vector int)
(defun-extern atan float float float)

View file

@ -22,6 +22,7 @@ add_executable(goalc-test
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_AtomicOpBuilder.cpp
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_FormBeforeExpressions.cpp
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_FormExpressionBuild.cpp
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_FormExpressionBuild2.cpp
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_FormExpressionBuildLong.cpp
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_InstructionDecode.cpp
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_InstructionParser.cpp

View file

@ -1,11 +1,13 @@
#include "FormRegressionTest.h"
#include "decompiler/analysis/type_analysis.h"
#include "decompiler/analysis/variable_naming.h"
#include "decompiler/analysis/reg_usage.h"
#include "decompiler/analysis/cfg_builder.h"
#include "decompiler/analysis/expression_build.h"
#include "decompiler/analysis/final_output.h"
#include "decompiler/analysis/insert_lets.h"
#include "decompiler/util/config_parsers.h"
#include "common/goos/PrettyPrinter.h"
#include "common/util/json_util.h"
#include "decompiler/IR2/Form.h"
@ -108,55 +110,78 @@ parse_var_json(const std::string& str) {
std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
const std::string& code,
const TypeSpec& function_type,
bool do_expressions,
bool allow_pairs,
const std::string& method_name,
const std::vector<std::pair<std::string, std::string>>& strings,
const std::unordered_map<int, std::vector<TypeCast>>& casts,
const std::string& var_map_json) {
dts->type_prop_settings.locked = true;
const TestSettings& settings) {
// Set up decompiler type system
dts->type_prop_settings.reset();
dts->type_prop_settings.allow_pair = allow_pairs;
dts->type_prop_settings.current_method_type = method_name;
dts->type_prop_settings.current_method_type = settings.method_name;
// set up label names for string constants
std::vector<std::string> string_label_names;
for (auto& x : strings) {
for (auto& x : settings.strings) {
string_label_names.push_back(x.first);
}
// parse the assembly
auto program = parser->parse_program(code, string_label_names);
// printf("prg:\n%s\n\n", program.print().c_str());
// create the test data collection
auto test = std::make_unique<TestData>(program.instructions.size());
// populate the LinkedObjectFile
test->file.words_by_seg.resize(3);
test->file.labels = program.labels;
// Set up the environment
test->func.ir2.env.file = &test->file;
test->func.ir2.env.dts = dts.get();
// Set up the function
test->func.instructions = program.instructions;
test->func.guessed_name.set_as_global("test-function");
test->func.type = function_type;
for (auto& str : strings) {
// set up string constants in the data
for (auto& str : settings.strings) {
test->add_string_at_label(str.first, str.second);
}
// find basic blocks
test->func.basic_blocks = find_blocks_in_function(test->file, 0, test->func);
// analyze function prologue/epilogue
test->func.analyze_prologue(test->file);
// build control flow graph
test->func.cfg = build_cfg(test->file, 0, test->func);
EXPECT_TRUE(test->func.cfg->is_fully_resolved());
if (!test->func.cfg->is_fully_resolved()) {
fmt::print("CFG:\n{}\n", test->func.cfg->to_dot());
}
// convert instruction to atomic ops
DecompWarnings warnings;
auto ops = convert_function_to_atomic_ops(test->func, program.labels, warnings);
test->func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops));
test->func.ir2.atomic_ops_succeeded = true;
test->func.ir2.env.set_end_var(test->func.ir2.atomic_ops->end_op().return_var());
EXPECT_TRUE(test->func.run_type_analysis_ir2(function_type, *dts, test->file, casts, {}));
// set up type settings
if (!settings.casts_json.empty()) {
test->func.ir2.env.set_type_casts(parse_cast_hints(nlohmann::json::parse(settings.casts_json)));
}
if (settings.allow_pairs) {
test->func.ir2.env.set_sloppy_pair_typing();
}
if (!settings.stack_var_json.empty()) {
auto stack_hints = parse_stack_var_hints(nlohmann::json::parse(settings.stack_var_json));
test->func.ir2.env.set_stack_var_hints(stack_hints);
}
// analyze types
EXPECT_TRUE(run_type_analysis_ir2(function_type, *dts, test->func));
test->func.ir2.env.types_succeeded = true;
// analyze registers
test->func.ir2.env.set_reg_use(analyze_ir2_register_usage(test->func));
// split to variables
auto result = run_variable_renaming(test->func, test->func.ir2.env.reg_use(),
*test->func.ir2.atomic_ops, *dts);
if (result.has_value()) {
@ -165,16 +190,18 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
EXPECT_TRUE(false);
}
// structure
build_initial_forms(test->func);
EXPECT_TRUE(test->func.ir2.top_form);
// for now, just test that this can at least be called.
if (test->func.ir2.top_form) {
// just make sure this doesn't crash
RegAccessSet vars;
test->func.ir2.top_form->collect_vars(vars, true);
if (do_expressions) {
auto config = parse_var_json(var_map_json);
if (settings.do_expressions) {
auto config = parse_var_json(settings.var_map_json);
// build expressions (most of the fancy decompilation happens here)
bool success = convert_to_expressions(test->func.ir2.top_form, *test->func.ir2.form_pool,
test->func, config.first, config.second, *dts);
@ -182,6 +209,7 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
if (!success) {
return nullptr;
}
// move variables into lets.
insert_lets(test->func, test->func.ir2.env, *test->func.ir2.form_pool,
test->func.ir2.top_form);
}
@ -205,15 +233,9 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
void FormRegressionTest::test(const std::string& code,
const std::string& type,
const std::string& expected,
bool do_expressions,
bool allow_pairs,
const std::string& method_name,
const std::vector<std::pair<std::string, std::string>>& strings,
const std::unordered_map<int, std::vector<TypeCast>>& casts,
const std::string& var_map_json) {
const TestSettings& settings) {
auto ts = dts->parse_type_spec(type);
auto test = make_function(code, ts, do_expressions, allow_pairs, method_name, strings, casts,
var_map_json);
auto test = make_function(code, ts, settings);
ASSERT_TRUE(test);
auto expected_form =
pretty_print::get_pretty_printer_reader().read_from_string(expected, false).as_pair()->car;
@ -237,10 +259,16 @@ void FormRegressionTest::test_final_function(
const std::string& expected,
bool allow_pairs,
const std::vector<std::pair<std::string, std::string>>& strings,
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts,
const std::string& cast_json,
const std::string& var_map_json) {
auto ts = dts->parse_type_spec(type);
auto test = make_function(code, ts, true, allow_pairs, "", strings, casts, var_map_json);
TestSettings settings;
settings.allow_pairs = allow_pairs;
settings.strings = strings;
settings.casts_json = cast_json;
settings.var_map_json = var_map_json;
settings.do_expressions = true;
auto test = make_function(code, ts, settings);
ASSERT_TRUE(test);
auto expected_form =
pretty_print::get_pretty_printer_reader().read_from_string(expected, false).as_pair()->car;
@ -256,23 +284,18 @@ void FormRegressionTest::test_final_function(
EXPECT_TRUE(expected_form == actual_form);
}
std::unordered_map<int, std::vector<decompiler::TypeCast>> FormRegressionTest::parse_cast_json(
const std::string& in) {
std::unordered_map<int, std::vector<decompiler::TypeCast>> out;
auto casts = nlohmann::json::parse(in);
for (auto& cast : casts) {
auto idx_range = parse_json_optional_integer_range(cast.at(0));
for (auto idx : idx_range) {
TypeCast type_cast;
type_cast.atomic_op_idx = idx;
type_cast.reg = Register(cast.at(1));
type_cast.type_name = cast.at(2).get<std::string>();
out[idx].push_back(type_cast);
}
}
return out;
void FormRegressionTest::test_with_stack_vars(const std::string& code,
const std::string& type,
const std::string& expected,
const std::string& stack_map_json,
const std::string& cast_json,
const std::string& var_map_json) {
TestSettings settings;
settings.do_expressions = true;
settings.stack_var_json = stack_map_json;
settings.var_map_json = var_map_json;
settings.casts_json = cast_json;
test(code, type, expected, settings);
}
std::unique_ptr<InstructionParser> FormRegressionTest::parser;

View file

@ -11,6 +11,16 @@ namespace decompiler {
struct TypeCast;
}
struct TestSettings {
bool do_expressions = false;
bool allow_pairs = false;
std::string method_name;
std::vector<std::pair<std::string, std::string>> strings;
std::string casts_json;
std::string var_map_json;
std::string stack_var_json;
};
class FormRegressionTest : public ::testing::Test {
protected:
static std::unique_ptr<decompiler::InstructionParser> parser;
@ -27,25 +37,22 @@ class FormRegressionTest : public ::testing::Test {
void add_string_at_label(const std::string& label_name, const std::string& data);
};
std::unique_ptr<TestData> make_function(
const std::string& code,
const TypeSpec& function_type,
bool do_expressions,
bool allow_pairs = false,
const std::string& method_name = "",
const std::vector<std::pair<std::string, std::string>>& strings = {},
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
const std::string& var_map_json = "");
std::unique_ptr<TestData> make_function(const std::string& code,
const TypeSpec& function_type,
const TestSettings& settings);
void test(const std::string& code,
const std::string& type,
const std::string& expected,
bool do_expressions,
bool allow_pairs = false,
const std::string& method_name = "",
const std::vector<std::pair<std::string, std::string>>& strings = {},
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
const std::string& var_map_json = "");
const TestSettings& settings);
void test_final_function(const std::string& code,
const std::string& type,
const std::string& expected,
bool allow_pairs = false,
const std::vector<std::pair<std::string, std::string>>& strings = {},
const std::string& cast_json = "",
const std::string& var_map_json = "");
void test_no_expr(const std::string& code,
const std::string& type,
@ -53,9 +60,16 @@ class FormRegressionTest : public ::testing::Test {
bool allow_pairs = false,
const std::string& method_name = "",
const std::vector<std::pair<std::string, std::string>>& strings = {},
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
const std::string& cast_json = "",
const std::string& var_map_json = "") {
test(code, type, expected, false, allow_pairs, method_name, strings, casts, var_map_json);
TestSettings settings;
settings.allow_pairs = allow_pairs;
settings.method_name = method_name;
settings.strings = strings;
settings.casts_json = cast_json;
settings.var_map_json = var_map_json;
settings.do_expressions = false;
test(code, type, expected, settings);
}
void test_with_expr(const std::string& code,
@ -64,19 +78,22 @@ class FormRegressionTest : public ::testing::Test {
bool allow_pairs = false,
const std::string& method_name = "",
const std::vector<std::pair<std::string, std::string>>& strings = {},
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
const std::string& cast_json = "",
const std::string& var_map_json = "") {
test(code, type, expected, true, allow_pairs, method_name, strings, casts, var_map_json);
TestSettings settings;
settings.allow_pairs = allow_pairs;
settings.method_name = method_name;
settings.strings = strings;
settings.casts_json = cast_json;
settings.var_map_json = var_map_json;
settings.do_expressions = true;
test(code, type, expected, settings);
}
void test_final_function(
const std::string& code,
const std::string& type,
const std::string& expected,
bool allow_pairs = false,
const std::vector<std::pair<std::string, std::string>>& strings = {},
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
const std::string& var_map_json = "");
std::unordered_map<int, std::vector<decompiler::TypeCast>> parse_cast_json(const std::string& in);
void test_with_stack_vars(const std::string& code,
const std::string& type,
const std::string& expected,
const std::string& stack_map_json,
const std::string& cast_json = "",
const std::string& var_map_json = "");
};

View file

@ -87,4 +87,14 @@
(define-extern fabs (function float float))
(define-extern abs (function int int))
(define-extern rand-vu-init (function float none))
(define-extern rand-vu (function float))
(define-extern rand-vu (function float))
;; matrix
(declare-type matrix structure)
(declare-type vector structure)
(define-extern matrix-transpose! (function matrix matrix matrix))
(define-extern sin (function float float))
(define-extern cos (function float float))
(define-extern vector-sincos! (function vector vector vector int))
(define-extern matrix-axis-sin-cos! (function matrix vector float float none))
(define-extern atan (function float float float))

View file

@ -45,8 +45,8 @@
(.max.vf vf2 vf4 vf5)
(.mov.vf vf1 vf0 :mask #b1000)
(.mov.vf vf2 vf0 :mask #b1000)
(.svf obj vf1)
(.svf obj vf2 :offset 16)
(.svf (&-> obj min quad) vf1)
(.svf (&-> obj max quad) vf2)
0
)
)
@ -57,13 +57,13 @@
(vf2 :class vf)
(vf3 :class vf)
)
(.lvf vf1 obj)
(.lvf vf2 obj :offset 16)
(.lvf vf1 (&-> obj min quad))
(.lvf vf2 (&-> obj max quad))
(.lvf vf3 arg0)
(.min.vf vf1 vf1 vf3)
(.max.vf vf2 vf2 vf3)
(.svf obj vf1)
(.svf obj vf2 :offset 16)
(.svf (&-> obj min quad) vf1)
(.svf (&-> obj max quad) vf2)
0
)
)
@ -75,14 +75,14 @@
(vf3 :class vf)
(vf4 :class vf)
)
(.lvf vf1 obj)
(.lvf vf2 obj :offset 16)
(.lvf vf3 arg0)
(.lvf vf4 arg0 :offset 16)
(.lvf vf1 (&-> obj min quad))
(.lvf vf2 (&-> obj max quad))
(.lvf vf3 (&-> arg0 min quad))
(.lvf vf4 (&-> arg0 max quad))
(.min.vf vf1 vf1 vf3)
(.max.vf vf2 vf2 vf4)
(.svf obj vf1)
(.svf obj vf2 :offset 16)
(.svf (&-> obj min quad) vf1)
(.svf (&-> obj max quad) vf2)
0
)
)
@ -111,8 +111,8 @@
(.sub.x.vf vf2 vf2 vf1 :mask #b111)
(.mov.vf vf2 vf0 :mask #b1000)
(.mov.vf vf3 vf0 :mask #b1000)
(.svf obj vf2)
(.svf obj vf3 :offset 16)
(.svf (&-> obj min quad) vf2)
(.svf (&-> obj max quad) vf3)
0
)
)
@ -125,13 +125,13 @@
(vf3 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.lvf vf1 arg0)
(.lvf vf1 (&-> arg0 quad))
(.sub.w.vf vf2 vf1 vf1 :mask #b111)
(.add.w.vf vf3 vf1 vf1 :mask #b111)
(.mov.vf vf2 vf0 :mask #b1000)
(.mov.vf vf3 vf0 :mask #b1000)
(.svf obj vf2)
(.svf obj vf3 :offset 16)
(.svf (&-> obj min quad) vf2)
(.svf (&-> obj max quad) vf3)
0
)
)

View file

@ -16,6 +16,7 @@
)
;; definition for method 3 of type matrix
;; INFO: this function exists in multiple non-identical object files
(defmethod inspect matrix ((obj matrix))
(format #t "[~8x] ~A~%" obj 'matrix)
(format #t "~Tdata[16] @ #x~X~%" (-> obj data))
@ -36,6 +37,7 @@
)
;; definition for method 3 of type matrix3
;; INFO: this function exists in multiple non-identical object files
(defmethod inspect matrix3 ((obj matrix3))
(format #t "[~8x] ~A~%" obj 'matrix3)
(format #t "~Tdata[12] @ #x~X~%" (-> obj data))

File diff suppressed because it is too large Load diff

View file

@ -849,8 +849,8 @@
(rlet ((vf1 :class vf)
(vf2 :class vf)
)
(.lvf vf1 arg0)
(.lvf vf2 arg1)
(.lvf vf1 (&-> arg0 quad))
(.lvf vf2 (&-> arg1 quad))
(.mul.vf vf1 vf1 vf2)
(.add.y.vf vf1 vf1 vf1 :mask #b1)
(.add.z.vf vf1 vf1 vf1 :mask #b1)
@ -895,8 +895,8 @@
(vf3 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.lvf vf1 arg0)
(.lvf vf2 arg1)
(.lvf vf1 (&-> arg0 quad))
(.lvf vf2 (&-> arg1 quad))
(.mul.vf vf1 vf1 vf2)
(.add.w.vf vf3 vf0 vf0 :mask #b1)
(.mul.x.vf acc vf3 vf1 :mask #b1)
@ -917,10 +917,10 @@
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.mov.vf vf6 vf0 :mask #b1000)
(.lvf vf4 arg1)
(.lvf vf5 arg2)
(.lvf vf4 (&-> arg1 quad))
(.lvf vf5 (&-> arg2 quad))
(.add.vf vf6 vf4 vf5 :mask #b111)
(.svf arg0 vf6)
(.svf (&-> arg0 quad) vf6)
arg0
)
)
@ -933,11 +933,11 @@
(vf6 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.lvf vf4 arg1)
(.lvf vf5 arg2)
(.lvf vf4 (&-> arg1 quad))
(.lvf vf5 (&-> arg2 quad))
(.mov.vf vf6 vf0 :mask #b1000)
(.sub.vf vf6 vf4 vf5 :mask #b111)
(.svf arg0 vf6)
(.svf (&-> arg0 quad) vf6)
arg0
)
)
@ -953,7 +953,7 @@
(defun vector-reset! ((arg0 vector))
(rlet ((vf0 :class vf))
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.svf arg0 vf0)
(.svf (&-> arg0 quad) vf0)
arg0
)
)

View file

@ -11,8 +11,9 @@ TEST_F(FormRegressionTest, StringTest) {
"L101:\n"
" jr ra\n"
" daddu sp, sp, r0";
auto test = make_function(func, TypeSpec("function", {TypeSpec("none")}), false, false, "",
{{"L100", "testing-string"}, {"L101", "testing-string-2"}});
TestSettings settings;
settings.strings = {{"L100", "testing-string"}, {"L101", "testing-string-2"}};
auto test = make_function(func, TypeSpec("function", {TypeSpec("none")}), settings);
EXPECT_EQ(test->file.get_goal_string_by_label(test->file.get_label_by_name("L100")),
"testing-string");

View file

@ -2282,8 +2282,8 @@ TEST_F(FormRegressionTest, ExprPrintName) {
" )\n"
" )";
test_with_expr(func, type, expected, false, "", {},
parse_cast_json("[\t\t[24, \"a1\", \"symbol\"],\n"
"\t\t[39, \"a0\", \"symbol\"]]"));
"[\t\t[24, \"a1\", \"symbol\"],\n"
"\t\t[39, \"a0\", \"symbol\"]]");
}
TEST_F(FormRegressionTest, ExprProfileBarMethod9) {

View file

@ -0,0 +1,59 @@
#include "gtest/gtest.h"
#include "FormRegressionTest.h"
using namespace decompiler;
TEST_F(FormRegressionTest, MatrixPMult) {
std::string func =
"sll r0, r0, 0\n"
" daddiu sp, sp, -112\n"
" sd ra, 0(sp)\n"
" sq s5, 80(sp)\n"
" sq gp, 96(sp)\n"
" or gp, a0, r0\n"
" daddiu s5, sp, 16\n"
" sq r0, 0(s5)\n"
" sq r0, 16(s5)\n"
" sq r0, 32(s5)\n"
" sq r0, 48(s5)\n"
" lw t9, matrix*!(s7)\n"
" or a0, s5, r0\n"
" jalr ra, t9\n"
" sll v0, ra, 0\n"
" lq v1, 0(s5)\n"
" sq v1, 0(gp)\n"
" lq v1, 16(s5)\n"
" sq v1, 16(gp)\n"
" lq v1, 32(s5)\n"
" sq v1, 32(gp)\n"
" lq v1, 48(s5)\n"
" sq v1, 48(gp)\n"
" or v0, gp, r0\n"
" ld ra, 0(sp)\n"
" lq gp, 96(sp)\n"
" lq s5, 80(sp)\n"
" jr ra\n"
" daddiu sp, sp, 112";
std::string type = "(function matrix matrix matrix matrix)";
std::string expected =
"(begin\n"
" (let ((s5-0 (new (quote stack) (quote matrix))))\n"
" (set! (-> s5-0 vector 0 quad) (the-as uint128 0))\n"
" (set! (-> s5-0 vector 1 quad) (the-as uint128 0))\n"
" (set! (-> s5-0 vector 2 quad) (the-as uint128 0))\n"
" (set! (-> s5-0 vector 3 quad) (the-as uint128 0))\n"
" (matrix*! s5-0 arg1 arg2)\n"
" (set! (-> arg0 vector 0 quad) (-> s5-0 vector 0 quad))\n"
" (set! (-> arg0 vector 1 quad) (-> s5-0 vector 1 quad))\n"
" (set! (-> arg0 vector 2 quad) (-> s5-0 vector 2 quad))\n"
" (set! (-> arg0 vector 3 quad) (-> s5-0 vector 3 quad))\n"
" )\n"
" arg0\n"
" )";
test_with_stack_vars(func, type, expected,
"[\n"
" [16, \"matrix\"]\n"
" ]");
}

View file

@ -687,21 +687,21 @@ TEST_F(FormRegressionTest, ExprArrayMethod2) {
{"L336", "~A"},
{"L335", " ~A"},
{"L334", ")"}},
parse_cast_json("["
"\t\t[23, \"gp\", \"(array int32)\"],\n"
"\t\t[43, \"gp\", \"(array uint32)\"],\n"
"\t\t[63, \"gp\", \"(array int64)\"],\n"
"\t\t[83, \"gp\", \"(array uint64)\"],\n"
"\t\t[102, \"gp\", \"(array int8)\"],\n"
"\t\t[121, \"gp\", \"(array uint8)\"],\n"
"\t\t[141, \"gp\", \"(array int16)\"],\n"
"\t\t[161, \"gp\", \"(array uint16)\"],\n"
"\t\t[186, \"gp\", \"(array uint128)\"],\n"
"\t\t[204, \"gp\", \"(array int32)\"],\n"
"\t\t[223, \"gp\", \"(array float)\"],\n"
"\t\t[232, \"gp\", \"(array float)\"],\n"
"\t\t[249, \"gp\", \"(array basic)\"],\n"
"\t\t[258, \"gp\", \"(array basic)\"]]"));
"["
"\t\t[23, \"gp\", \"(array int32)\"],\n"
"\t\t[43, \"gp\", \"(array uint32)\"],\n"
"\t\t[63, \"gp\", \"(array int64)\"],\n"
"\t\t[83, \"gp\", \"(array uint64)\"],\n"
"\t\t[102, \"gp\", \"(array int8)\"],\n"
"\t\t[121, \"gp\", \"(array uint8)\"],\n"
"\t\t[141, \"gp\", \"(array int16)\"],\n"
"\t\t[161, \"gp\", \"(array uint16)\"],\n"
"\t\t[186, \"gp\", \"(array uint128)\"],\n"
"\t\t[204, \"gp\", \"(array int32)\"],\n"
"\t\t[223, \"gp\", \"(array float)\"],\n"
"\t\t[232, \"gp\", \"(array float)\"],\n"
"\t\t[249, \"gp\", \"(array basic)\"],\n"
"\t\t[258, \"gp\", \"(array basic)\"]]");
}
TEST_F(FormRegressionTest, ExprArrayMethod3) {
@ -1266,18 +1266,18 @@ TEST_F(FormRegressionTest, ExprArrayMethod3) {
{"L327", "~T [~D] #x~X~%"},
{"L326", "~T [~D] ~f~%"},
{"L325", "~T [~D] ~A~%"}},
parse_cast_json("[\t\t[44, \"gp\", \"(array int32)\"],\n"
"\t\t[62, \"gp\", \"(array uint32)\"],\n"
"\t\t[80, \"gp\", \"(array int64)\"],\n"
"\t\t[98, \"gp\", \"(array uint64)\"],\n"
"\t\t[115, \"gp\", \"(array int8)\"],\n"
"\t\t[132, \"gp\", \"(array int8)\"],\n"
"\t\t[150, \"gp\", \"(array int16)\"],\n"
"\t\t[168, \"gp\", \"(array uint16)\"],\n"
"\t\t[191, \"gp\", \"(array uint128)\"],\n"
"\t\t[207, \"gp\", \"(array int32)\"],\n"
"\t\t[226, \"gp\", \"(array float)\"],\n"
"\t\t[243, \"gp\", \"(array basic)\"]]"));
"[\t\t[44, \"gp\", \"(array int32)\"],\n"
"\t\t[62, \"gp\", \"(array uint32)\"],\n"
"\t\t[80, \"gp\", \"(array int64)\"],\n"
"\t\t[98, \"gp\", \"(array uint64)\"],\n"
"\t\t[115, \"gp\", \"(array int8)\"],\n"
"\t\t[132, \"gp\", \"(array int8)\"],\n"
"\t\t[150, \"gp\", \"(array int16)\"],\n"
"\t\t[168, \"gp\", \"(array uint16)\"],\n"
"\t\t[191, \"gp\", \"(array uint128)\"],\n"
"\t\t[207, \"gp\", \"(array int32)\"],\n"
"\t\t[226, \"gp\", \"(array float)\"],\n"
"\t\t[243, \"gp\", \"(array basic)\"]]");
}
TEST_F(FormRegressionTest, ExprValid) {

View file

@ -371,7 +371,7 @@ TEST_F(FormRegressionTest, ExprMethod0Thread) {
" (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\"]]"),
"[[[13, 28], \"v0\", \"cpu-thread\"]]",
"{\"vars\":{\"v0-0\":[\"obj\", \"cpu-thread\"]}}");
}
@ -686,8 +686,8 @@ TEST_F(FormRegressionTest, ExprMethod0Process) {
" (the-as process v0-0)\n"
" )";
test_with_expr(func, type, expected, false, "process", {},
parse_cast_json("[\t\t[12, \"a0\", \"int\"],\n"
"\t\t[[13, 43], \"v0\", \"process\"]]"));
"[\t\t[12, \"a0\", \"int\"],\n"
"\t\t[[13, 43], \"v0\", \"process\"]]");
}
TEST_F(FormRegressionTest, ExprInspectProcessHeap) {
@ -751,8 +751,8 @@ TEST_F(FormRegressionTest, ExprInspectProcessHeap) {
" #f\n"
" )";
test_with_expr(func, type, expected, false, "", {},
parse_cast_json("[\t\t[[4,11], \"s5\", \"basic\"],\n"
"\t\t[[17,20], \"s5\", \"pointer\"]]"),
"[\t\t[[4,11], \"s5\", \"basic\"],\n"
"\t\t[[17,20], \"s5\", \"pointer\"]]",
"{\"vars\":{\"s5-0\":[\"obj\", \"pointer\"]}}");
}
@ -1123,8 +1123,8 @@ TEST_F(FormRegressionTest, ExprMethod14DeadPool) {
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_cast_json("[\t\t[24, \"v1\", \"(pointer process-tree)\"],\n"
"\t\t[[30,39], \"s4\", \"(pointer process-tree)\"]]"));
"[\t\t[24, \"v1\", \"(pointer process-tree)\"],\n"
"\t\t[[30,39], \"s4\", \"(pointer process-tree)\"]]");
}
TEST_F(FormRegressionTest, ExprMethod15DeadPool) {
@ -1299,11 +1299,10 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) {
" (set! (-> obj heap top-base) (-> obj heap top))\n"
" obj\n"
" )";
test_with_expr(
func, type, expected, false, "dead-pool-heap", {},
parse_cast_json("[\t\t[60, \"v0\", \"int\"],\n"
"\t\t[61, \"a0\", \"pointer\"], [61, \"v0\", \"dead-pool-heap\"]]"),
"{\"vars\":{\"v0-0\":[\"obj\", \"dead-pool-heap\"]}}");
test_with_expr(func, type, expected, false, "dead-pool-heap", {},
"[\t\t[60, \"v0\", \"int\"],\n"
"\t\t[61, \"a0\", \"pointer\"], [61, \"v0\", \"dead-pool-heap\"]]",
"{\"vars\":{\"v0-0\":[\"obj\", \"dead-pool-heap\"]}}");
}
TEST_F(FormRegressionTest, ExprMethod22DeadPoolHeap) {
@ -1426,9 +1425,9 @@ TEST_F(FormRegressionTest, ExprMethod21DeadPoolHeap) {
" )\n"
" )";
test_with_expr(func, type, expected, false, "", {},
parse_cast_json("[\t\t[5, \"v1\", \"pointer\"],\n"
"\t\t[13, \"a0\", \"pointer\"],\n"
"\t\t[25, \"v1\", \"pointer\"]]"));
"[\t\t[5, \"v1\", \"pointer\"],\n"
"\t\t[13, \"a0\", \"pointer\"],\n"
"\t\t[25, \"v1\", \"pointer\"]]");
}
TEST_F(FormRegressionTest, ExprMethod3DeadPoolHeap) {
@ -1623,7 +1622,7 @@ TEST_F(FormRegressionTest, ExprMethod5DeadPoolHeap) {
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\"]]"));
"[[3, \"v1\", \"int\"], [3, \"a0\", \"int\"]]");
}
TEST_F(FormRegressionTest, ExprMethod19DeadPoolHeap) {

View file

@ -14,30 +14,18 @@ const std::unordered_set<std::string> g_object_files_to_decompile = {
"gcommon", "gstring-h", "gkernel-h", "gkernel",
/*"pskernel",*/ "gstring", "dgo-h", "gstate", "types-h", "vu1-macros", "math", "vector-h",
"bounding-box-h", "matrix-h", "quaternion-h", "euler-h", "transform-h", "geometry-h",
"trigonometry-h",
"trigonometry-h", /* transformq-h */ "matrix",
/* gap */
"bounding-box"};
// 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",
"dgo-h",
"gstate",
"types-h",
"vu1-macros",
"math",
"vector-h",
"bounding-box-h",
"matrix-h",
"quaternion-h",
"euler-h",
"transform-h",
"geometry-h",
"trigonometry-h",
"gstring-h", "gkernel-h", "gkernel", "gstring", "dgo-h", "gstate", "types-h", "vu1-macros",
"math", "vector-h", "bounding-box-h", "matrix-h", "quaternion-h", "euler-h", "transform-h",
"geometry-h", "trigonometry-h",
/* transformq-h, */
"matrix",
/* gap */ "bounding-box"};
// the functions we expect the decompiler to skip
@ -66,6 +54,10 @@ const std::unordered_set<std::string> expected_skip_in_decompiler = {
// bounding-box
"(method 9 bounding-box)", // handwritten asm loop
"(method 14 bounding-box)", // handwritten asm loop
// matrix
"(method 9 matrix)", // handwritten asm loop
"matrix-axis-sin-cos!",
"matrix-axis-sin-cos-vu!",
};
const std::unordered_set<std::string> skip_in_compiling = {
@ -100,6 +92,10 @@ const std::unordered_set<std::string> skip_in_compiling = {
"(method 3 vector)", // this function appears twice, which confuses the compiler.
"vector-dot", // fpu acc
"vector4-dot", // fpu acc
/// MATRIX
"matrix-transpose!", // unsupported asm ops
"matrix-4x4-inverse!", // compiler fails to regalloc this...
};
// default location for the data. It can be changed with a command line argument.