mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 00:57:44 -04:00
[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:
parent
7fac11ddf5
commit
64c35ca453
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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", {}}},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
13
decompiler/analysis/type_analysis.h
Normal file
13
decompiler/analysis/type_analysis.h
Normal 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);
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -45,6 +45,10 @@
|
|||
["L2", "_auto_", true]
|
||||
],
|
||||
|
||||
"matrix":[
|
||||
["L60", "float", true]
|
||||
],
|
||||
|
||||
"trigonometry": [
|
||||
["L143", "float", true],
|
||||
["L144", "float", true],
|
||||
|
|
42
decompiler/config/jak1_ntsc_black_label/stack_vars.jsonc
Normal file
42
decompiler/config/jak1_ntsc_black_label/stack_vars.jsonc
Normal 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"]
|
||||
]
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
52
decompiler/util/config_parsers.cpp
Normal file
52
decompiler/util/config_parsers.cpp
Normal 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
|
10
decompiler/util/config_parsers.h
Normal file
10
decompiler/util/config_parsers.h
Normal 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
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = "");
|
||||
};
|
|
@ -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))
|
|
@ -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
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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))
|
||||
|
|
1741
test/decompiler/reference/matrix_REF.gc
Normal file
1741
test/decompiler/reference/matrix_REF.gc
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
59
test/decompiler/test_FormExpressionBuild2.cpp
Normal file
59
test/decompiler/test_FormExpressionBuild2.cpp
Normal 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"
|
||||
" ]");
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue