[Decompiler] WIP: Stack Spills (#382)

* set up types

* cleaned up type analysis and got things working through atomic ops

* expression working, need types

* improved types and names

* getting close

* finish up dma-disasm

* fix
This commit is contained in:
water111
2021-04-25 14:48:54 -04:00
committed by GitHub
parent 54ccc9db97
commit 2002db359a
43 changed files with 3187 additions and 99 deletions
+1
View File
@@ -10,6 +10,7 @@ add_library(
analysis/inline_asm_rewrite.cpp
analysis/insert_lets.cpp
analysis/reg_usage.cpp
analysis/stack_spill.cpp
analysis/type_analysis.cpp
analysis/variable_naming.cpp
-4
View File
@@ -6,9 +6,6 @@
* Can print itself (within the context of a LinkedObjectFile).
*/
#ifndef NEXT_INSTRUCTION_H
#define NEXT_INSTRUCTION_H
#include <vector>
#include "OpcodeInfo.h"
#include "Register.h"
@@ -112,4 +109,3 @@ class Instruction {
int cop2_dest_mask_intel() const;
};
} // namespace decompiler
#endif // NEXT_INSTRUCTION_H
+90
View File
@@ -1639,4 +1639,94 @@ void FunctionEndOp::collect_vars(RegAccessSet& vars) const {
vars.insert(m_return_reg);
}
}
/////////////////////////////
// StackSpillStoreOp
/////////////////////////////
StackSpillStoreOp::StackSpillStoreOp(RegisterAccess value, int size, int offset, int my_idx)
: AtomicOp(my_idx), m_value(value), m_size(size), m_offset(offset) {
assert(m_value.mode() == AccessMode::READ);
}
goos::Object StackSpillStoreOp::to_form(const std::vector<DecompilerLabel>&, const Env& env) const {
return pretty_print::build_list(
fmt::format("stack-store {} :offset {} :sz {}", m_value.to_string(env), m_offset, m_size));
}
bool StackSpillStoreOp::operator==(const AtomicOp& other) const {
if (typeid(StackSpillStoreOp) != typeid(other)) {
return false;
}
auto po = dynamic_cast<const StackSpillStoreOp*>(&other);
assert(po);
return m_size == po->m_size && m_value == po->m_value && m_offset == po->m_offset;
}
bool StackSpillStoreOp::is_sequence_point() const {
return true; // this might not be totally true, but it seems kind of scary to allow it to
// reorder.
}
void StackSpillStoreOp::update_register_info() {
m_read_regs.push_back(m_value.reg());
}
void StackSpillStoreOp::collect_vars(RegAccessSet& vars) const {
vars.insert(m_value);
}
RegisterAccess StackSpillStoreOp::get_set_destination() const {
throw std::runtime_error("StackSpillStoreOp cannot be treated as a set! operation");
}
/////////////////////////////
// StackSpillLoadOp
/////////////////////////////
StackSpillLoadOp::StackSpillLoadOp(RegisterAccess dst,
int size,
int offset,
bool is_signed,
int my_idx)
: AtomicOp(my_idx), m_dst(dst), m_size(size), m_offset(offset), m_is_signed(is_signed) {
assert(m_dst.mode() == AccessMode::WRITE);
}
goos::Object StackSpillLoadOp::to_form(const std::vector<DecompilerLabel>&, const Env& env) const {
return pretty_print::build_list(fmt::format("stack-load {} :offset {} :sz {} :sext #{}",
m_dst.to_string(env), m_offset, m_size,
m_is_signed ? 't' : 'f'));
}
bool StackSpillLoadOp::operator==(const AtomicOp& other) const {
if (typeid(StackSpillStoreOp) != typeid(other)) {
return false;
}
auto po = dynamic_cast<const StackSpillLoadOp*>(&other);
assert(po);
return m_size == po->m_size && m_dst == po->m_dst && m_offset == po->m_offset &&
m_is_signed == po->m_is_signed;
}
bool StackSpillLoadOp::is_sequence_point() const {
return true; // this might not be totally true, but it seems kind of scary to allow it to
// reorder.
}
void StackSpillLoadOp::update_register_info() {
m_write_regs.push_back(m_dst.reg());
}
void StackSpillLoadOp::collect_vars(RegAccessSet& vars) const {
vars.insert(m_dst);
}
RegisterAccess StackSpillLoadOp::get_set_destination() const {
// todo!
throw std::runtime_error("StackSpillLoadOp cannot be treated as a set! operation");
}
} // namespace decompiler
+48 -2
View File
@@ -697,8 +697,7 @@ struct IR2_RegOffset {
class FunctionEndOp : public AtomicOp {
public:
explicit FunctionEndOp(int my_idx);
virtual goos::Object to_form(const std::vector<DecompilerLabel>& labels,
const Env& env) const override;
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;
@@ -719,5 +718,52 @@ class FunctionEndOp : public AtomicOp {
RegisterAccess m_return_reg;
};
/*!
* An operation to store a variable from the stack.
*/
class StackSpillStoreOp : public AtomicOp {
public:
StackSpillStoreOp(RegisterAccess value, int size, int offset, 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;
FormElement* get_as_form(FormPool& pool, const Env& env) const override;
RegisterAccess get_set_destination() const override;
void update_register_info() override;
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void collect_vars(RegAccessSet& vars) const override;
private:
RegisterAccess m_value;
int m_size;
int m_offset;
};
/*!
* An operation to load a variable from the stack.
*/
class StackSpillLoadOp : public AtomicOp {
public:
StackSpillLoadOp(RegisterAccess dst, int size, int offset, bool is_signed, 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;
FormElement* get_as_form(FormPool& pool, const Env& env) const override;
RegisterAccess get_set_destination() const override;
void update_register_info() override;
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void collect_vars(RegAccessSet& vars) const override;
private:
RegisterAccess m_dst;
int m_size;
int m_offset;
bool m_is_signed;
};
bool get_as_reg_offset(const SimpleExpression& expr, IR2_RegOffset* out);
} // namespace decompiler
+25
View File
@@ -723,4 +723,29 @@ FormElement* FunctionEndOp::get_as_form(FormPool& pool, const Env&) const {
FormElement* AsmBranchOp::get_as_form(FormPool& pool, const Env&) const {
return pool.alloc_element<AtomicOpElement>(this);
}
FormElement* StackSpillLoadOp::get_as_form(FormPool& pool, const Env& env) const {
TypeSpec type("object");
auto kv = env.stack_slot_entries.find(m_offset);
if (kv != env.stack_slot_entries.end()) {
type = kv->second.typespec;
}
return pool.alloc_element<SetVarElement>(m_dst,
pool.alloc_single_element_form<StackSpillValueElement>(
nullptr, m_size, m_offset, m_is_signed),
true, type);
}
FormElement* StackSpillStoreOp::get_as_form(FormPool& pool, const Env& env) const {
auto& slot_type = env.stack_slot_entries.at(m_offset).typespec;
auto src_type = env.get_types_before_op(m_my_idx).get(m_value.reg()).typespec();
std::optional<TypeSpec> cast_type;
if (!env.dts->ts.tc(slot_type, src_type)) {
// we fail the typecheck for a normal set!, so add a cast.
cast_type = slot_type;
}
return pool.alloc_element<StackSpillStoreElement>(m_value, m_size, m_offset, cast_type);
}
} // namespace decompiler
+64 -1
View File
@@ -3,6 +3,7 @@
#include "common/log/log.h"
#include "AtomicOp.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/IR2/bitfields.h"
namespace decompiler {
@@ -284,7 +285,13 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
if (m_args[1].is_int() && is_int_or_uint(dts, arg0_type)) {
assert(m_args[1].get_int() >= 0);
assert(m_args[1].get_int() < 64);
return TP_Type::make_from_product(1ull << m_args[1].get_int(), is_signed(dts, arg0_type));
// this could be a bitfield access or a multiply.
// we pick bitfield access if the parent is a bitfield.
if (dynamic_cast<BitFieldType*>(dts.ts.lookup_type(arg0_type.typespec()))) {
return TP_Type::make_from_left_shift_bitfield(arg0_type.typespec(), m_args[1].get_int());
} else {
return TP_Type::make_from_product(1ull << m_args[1].get_int(), is_signed(dts, arg0_type));
}
}
if (m_args[1].is_int() && dts.ts.tc(TypeSpec("pointer"), arg0_type.typespec())) {
@@ -293,6 +300,27 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
}
break;
case Kind::RIGHT_SHIFT_ARITH:
case Kind::RIGHT_SHIFT_LOGIC: {
bool is_unsigned = m_kind == Kind::RIGHT_SHIFT_LOGIC;
if (arg0_type.kind == TP_Type::Kind::LEFT_SHIFTED_BITFIELD && m_args[1].is_int()) {
// second op in left/right shift combo
int end_bit = 64 - arg0_type.get_left_shift();
int size = 64 - m_args[1].get_int();
int start_bit = end_bit - size;
if (start_bit < 0) {
throw std::runtime_error("Bad bitfield start bit");
}
auto type = dts.ts.lookup_type(arg0_type.get_bitfield_type());
auto as_bitfield = dynamic_cast<BitFieldType*>(type);
assert(as_bitfield);
auto field = find_field(dts.ts, as_bitfield, start_bit, size, is_unsigned);
return TP_Type::make_from_ts(field.type());
}
} break;
case Kind::MUL_SIGNED: {
if (arg0_type.is_integer_constant() && is_int_or_uint(dts, arg1_type)) {
return TP_Type::make_from_product(arg0_type.get_integer_constant(),
@@ -1023,4 +1051,39 @@ TypeState AsmBranchOp::propagate_types_internal(const TypeState& input,
return output;
}
TypeState StackSpillLoadOp::propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem&) {
// stack slot load
auto info = env.stack_spills().lookup(m_offset);
if (info.size != m_size) {
throw std::runtime_error(fmt::format(
"Stack slot load mismatch: defined as size {}, got size {}\n", info.size, m_size));
}
if (info.is_signed != m_is_signed) {
throw std::runtime_error("Stack slot signed mismatch");
}
auto& loaded_type = input.get_slot(m_offset);
auto result = input;
result.get(m_dst.reg()) = loaded_type;
return result;
}
TypeState StackSpillStoreOp::propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem&) {
auto info = env.stack_spills().lookup(m_offset);
if (info.size != m_size) {
throw std::runtime_error(fmt::format(
"Stack slot load mismatch: defined as size {}, got size {}\n", info.size, m_size));
}
auto& stored_type = input.get(m_value.reg());
auto result = input;
result.spill_slots[m_offset] = stored_type;
return result;
}
} // namespace decompiler
+53 -17
View File
@@ -91,12 +91,17 @@ const std::string& Env::remapped_name(const std::string& name) const {
}
goos::Object Env::get_variable_name_with_cast(const RegisterAccess& access) const {
return get_variable_name_with_cast(access.reg(), access.idx(), access.mode());
auto result = get_variable_and_cast(access);
if (result.cast) {
return pretty_print::build_list("the-as", result.cast->print(), result.name);
} else {
return pretty_print::to_symbol(result.name);
}
}
goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const {
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR) {
auto& var_info = m_var_names.lookup(reg, atomic_idx, mode);
VariableWithCast Env::get_variable_and_cast(const RegisterAccess& access) const {
if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) {
auto& var_info = m_var_names.lookup(access.reg(), access.idx(), access.mode());
// this is a bit of a confusing process. The first step is to grab the auto-generated name:
std::string original_name = var_info.name();
auto lookup_name = original_name;
@@ -118,27 +123,32 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce
}
// next, we insert type casts that make enforce the user override.
auto type_kv = m_typecasts.find(atomic_idx);
auto type_kv = m_typecasts.find(access.idx());
if (type_kv != m_typecasts.end()) {
for (auto& x : type_kv->second) {
if (x.reg == reg) {
if (x.reg == access.reg()) {
// let's make sure the above claim is true
TypeSpec type_in_reg;
if (has_type_analysis() && mode == AccessMode::READ) {
type_in_reg = get_types_for_op_mode(atomic_idx, AccessMode::READ).get(reg).typespec();
if (has_type_analysis() && access.mode() == AccessMode::READ) {
type_in_reg = get_types_for_op_mode(access.idx(), AccessMode::READ)
.get(access.reg())
.typespec();
if (type_in_reg.print() != x.type_name) {
lg::error(
"Decompiler type consistency error. There was a typecast for reg {} at idx {} "
"(var {}) to type {}, but the actual type is {} ({})",
reg.to_charp(), atomic_idx, lookup_name, x.type_name, type_in_reg.print(),
type_in_reg.print());
access.reg().to_charp(), access.idx(), lookup_name, x.type_name,
type_in_reg.print(), type_in_reg.print());
assert(false);
}
}
if (type_of_var != type_in_reg) {
// TODO - use the when possible?
return pretty_print::build_list("the-as", x.type_name, lookup_name);
VariableWithCast result;
result.cast = TypeSpec(x.type_name);
result.name = lookup_name;
return result;
}
}
}
@@ -146,8 +156,9 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce
// type analysis stuff runs before variable types, so we insert casts that account
// for the changing types due to the lca(uses) that is used to generate variable types.
auto type_of_reg = get_types_for_op_mode(atomic_idx, mode).get(reg).typespec();
if (mode == AccessMode::READ) {
auto type_of_reg =
get_types_for_op_mode(access.idx(), access.mode()).get(access.reg()).typespec();
if (access.mode() == AccessMode::READ) {
// note - this may be stricter than needed. but that's ok.
if (type_of_var != type_of_reg) {
@@ -156,7 +167,10 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce
// {}\n ",
// lookup_name, reg.to_charp(), atomic_idx, type_of_reg.print(),
// var_info.type.typespec().print(), type_of_var.print());
return pretty_print::build_list("the-as", type_of_reg.print(), lookup_name);
VariableWithCast result;
result.cast = type_of_reg;
result.name = lookup_name;
return result;
}
} else {
// if we're setting a variable, we are a little less strict.
@@ -170,12 +184,21 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce
}
}
return pretty_print::to_symbol(lookup_name);
VariableWithCast result;
result.name = lookup_name;
return result;
} else {
return pretty_print::to_symbol(reg.to_charp());
VariableWithCast result;
result.name = access.reg().to_charp();
return result;
}
}
goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const {
return get_variable_name_with_cast(RegisterAccess(mode, reg, atomic_idx, true));
}
std::optional<TypeSpec> Env::get_user_cast_for_access(const RegisterAccess& access) const {
if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) {
auto& var_info = m_var_names.lookup(access.reg(), access.idx(), access.mode());
@@ -420,9 +443,22 @@ goos::Object Env::local_var_type_list(const Form* top_level_form,
}
count++;
elts.push_back(pretty_print::build_list(lookup_name, x.type.typespec().print()));
}
// sort in increasing offset.
// it looks like this is the order the GOAL compiler itself used.
std::vector<StackSpillEntry> spills;
for (auto& x : stack_slot_entries) {
spills.push_back(x.second);
}
std::sort(spills.begin(), spills.end(),
[](const StackSpillEntry& a, const StackSpillEntry& b) { return a.offset < b.offset; });
for (auto& x : spills) {
elts.push_back(pretty_print::build_list(x.name(), x.typespec.print()));
count++;
}
if (count_out) {
*count_out = count;
}
+42 -5
View File
@@ -5,6 +5,7 @@
#include <cassert>
#include <common/goos/Object.h>
#include "decompiler/util/TP_Type.h"
#include "decompiler/util/StackSpillMap.h"
#include "decompiler/Disasm/Register.h"
#include "decompiler/IR2/IR2_common.h"
#include "decompiler/analysis/reg_usage.h"
@@ -16,12 +17,31 @@ class Form;
class DecompilerTypeSystem;
struct FunctionAtomicOps;
struct VariableWithCast {
std::string name;
std::optional<TypeSpec> cast;
};
struct StackVarEntry {
StackVariableHint hint;
TypeSpec ref_type; // the actual type of the address.
int size = -1;
};
struct StackSpillEntry {
TP_Type tp_type;
TypeSpec typespec;
int offset;
std::optional<std::string> name_override;
std::string name() const {
if (name_override) {
return *name_override;
} else {
return fmt::format("sv-{}", offset);
}
}
};
/*!
* An "environment" for a single function.
* This contains data for an entire function, like which registers are live when, the types of
@@ -34,17 +54,16 @@ class Env {
bool has_local_vars() const { return m_has_local_vars; }
bool has_type_analysis() const { return m_has_types; }
bool has_reg_use() const { return m_has_reg_use; }
const RegUsageInfo& reg_use() const {
assert(m_has_reg_use);
return m_reg_use;
}
void set_reg_use(const RegUsageInfo& info) {
m_reg_use = info;
m_has_reg_use = true;
}
const RegUsageInfo& reg_use() const {
assert(m_has_reg_use);
return m_reg_use;
}
RegUsageInfo& reg_use() {
assert(m_has_reg_use);
return m_reg_use;
@@ -54,6 +73,7 @@ class Env {
goos::Object get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const;
goos::Object get_variable_name_with_cast(const RegisterAccess& access) const;
std::string get_variable_name(const RegisterAccess& access) const;
VariableWithCast get_variable_and_cast(const RegisterAccess& access) const;
std::optional<TypeSpec> get_user_cast_for_access(const RegisterAccess& access) const;
TypeSpec get_variable_type(const RegisterAccess& access, bool using_user_var_types) const;
@@ -158,9 +178,24 @@ class Env {
void set_retype_map(const std::unordered_map<std::string, TypeSpec>& map) { m_var_retype = map; }
void set_stack_spills(const StackSpillMap& map) { m_stack_spill_map = map; }
const StackSpillMap& stack_spills() const { return m_stack_spill_map; }
// todo - remove these hacks at some point.
LinkedObjectFile* file = nullptr;
DecompilerTypeSystem* dts = nullptr;
std::unordered_map<int, StackSpillEntry> stack_slot_entries;
std::string get_spill_slot_var_name(int offset) const {
auto kv = stack_slot_entries.find(offset);
if (kv == stack_slot_entries.end()) {
return fmt::format("sv-{}", offset);
} else {
return kv->second.name();
}
}
const std::unordered_map<std::string, std::string>& var_remap_map() const { return m_var_remap; }
private:
RegisterAccess m_end_var;
@@ -186,5 +221,7 @@ class Env {
std::unordered_set<std::string> m_vars_defined_in_let;
std::optional<TypeSpec> m_type_analysis_return_type;
StackSpillMap m_stack_spill_map;
};
} // namespace decompiler
+46
View File
@@ -2291,6 +2291,52 @@ void VectorFloatLoadStoreElement::collect_vf_regs(RegSet& regs) const {
regs.insert(m_vf_reg);
}
////////////////////////////////
// StackSpillStoreElement
////////////////////////////////
StackSpillStoreElement::StackSpillStoreElement(RegisterAccess value,
int size,
int stack_offset,
const std::optional<TypeSpec>& cast_type)
: m_value(value), m_size(size), m_stack_offset(stack_offset), m_cast_type(cast_type) {}
goos::Object StackSpillStoreElement::to_form_internal(const Env& env) const {
return pretty_print::build_list(
fmt::format("set! {}", env.get_spill_slot_var_name(m_stack_offset)), m_value.to_form(env));
}
void StackSpillStoreElement::apply(const std::function<void(FormElement*)>& f) {
f(this);
}
void StackSpillStoreElement::apply_form(const std::function<void(Form*)>&) {}
void StackSpillStoreElement::collect_vars(RegAccessSet& vars, bool) const {
vars.insert(m_value);
}
void StackSpillStoreElement::get_modified_regs(RegSet&) const {}
////////////////////////////////
// StackSpillValueElement
////////////////////////////////
StackSpillValueElement::StackSpillValueElement(int size, int stack_offset, bool is_signed)
: m_size(size), m_stack_offset(stack_offset), m_is_signed(is_signed) {}
goos::Object StackSpillValueElement::to_form_internal(const Env& env) const {
return pretty_print::to_symbol(env.get_spill_slot_var_name(m_stack_offset));
}
void StackSpillValueElement::apply(const std::function<void(FormElement*)>& f) {
f(this);
}
void StackSpillValueElement::apply_form(const std::function<void(Form*)>&) {}
void StackSpillValueElement::collect_vars(RegAccessSet&, bool) const {}
void StackSpillValueElement::get_modified_regs(RegSet&) const {}
////////////////////////////////
// Utilities
////////////////////////////////
+42
View File
@@ -1316,6 +1316,48 @@ class VectorFloatLoadStoreElement : public FormElement {
bool m_is_load = false;
};
class StackSpillStoreElement : public FormElement {
public:
StackSpillStoreElement(RegisterAccess value,
int size,
int stack_offset,
const std::optional<TypeSpec>& cast_type);
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;
const std::optional<TypeSpec>& cast_type() const { return m_cast_type; }
private:
RegisterAccess m_value;
int m_size = -1;
int m_stack_offset = -1;
std::optional<TypeSpec> m_cast_type;
};
// the value from a stack load.
class StackSpillValueElement : public FormElement {
public:
StackSpillValueElement(int size, int stack_offset, bool is_signed);
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:
int m_size = -1;
int m_stack_offset = -1;
bool m_is_signed = false;
};
/*!
* A Form is a wrapper around one or more FormElements.
* This is done for two reasons:
+45 -14
View File
@@ -308,8 +308,8 @@ bool is_uint_type(const Env& env, int my_idx, RegisterAccess var) {
bool is_ptr_or_child(const Env& env, int my_idx, RegisterAccess var, bool) {
// Now that decompiler types are synced up properly, we don't want this.
// auto type = as_var ? env.get_variable_type(var, true).base_type()
// : env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type();
// auto type = as_var ? env.get_variable_type(var, true).base_type()
// : env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type();
auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type();
return type == "pointer";
}
@@ -603,7 +603,7 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env,
args.push_back(pool.alloc_single_element_form<SimpleAtomElement>(nullptr, m_expr.get_arg(1)));
}
bool arg0_ptr = is_ptr_or_child(env, m_my_idx, m_expr.get_arg(0).var(), is_var(args.at(0)));
bool arg0_ptr = is_ptr_or_child(env, m_my_idx, m_expr.get_arg(0).var(), true);
// Look for getting an address inside of an object.
// (+ <integer 108 + int> process). array style access with a stride of 1.
@@ -943,7 +943,7 @@ void SimpleExpressionElement::update_from_stack_logor_or_logand(const Env& env,
}
BitfieldManip step(manip_kind, m_expr.get_arg(1).get_int());
auto other = read_elt->push_step(step, env.dts->ts, pool);
auto other = read_elt->push_step(step, env.dts->ts, pool, env);
if (other) {
result->push_back(other);
} else {
@@ -983,7 +983,7 @@ void SimpleExpressionElement::update_from_stack_logor_or_logand(const Env& env,
assert(false);
}
BitfieldManip step(manip_kind, arg1_atom->get_int());
auto other = read_elt->push_step(step, env.dts->ts, pool);
auto other = read_elt->push_step(step, env.dts->ts, pool, env);
assert(!other); // shouldn't be complete.
result->push_back(read_elt);
return;
@@ -997,7 +997,7 @@ void SimpleExpressionElement::update_from_stack_logor_or_logand(const Env& env,
assert(false);
}
auto step = BitfieldManip::from_form(manip_kind, stripped_arg1);
auto other = read_elt->push_step(step, env.dts->ts, pool);
auto other = read_elt->push_step(step, env.dts->ts, pool, env);
if (other) {
result->push_back(other);
} else {
@@ -1066,7 +1066,7 @@ void SimpleExpressionElement::update_from_stack_left_shift(const Env& env,
auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0);
auto read_elt = pool.alloc_element<BitfieldAccessElement>(base, arg0_type);
BitfieldManip step(BitfieldManip::Kind::LEFT_SHIFT, m_expr.get_arg(1).get_int());
auto other = read_elt->push_step(step, env.dts->ts, pool);
auto other = read_elt->push_step(step, env.dts->ts, pool, env);
assert(!other); // shouldn't be complete.
result->push_back(read_elt);
} else {
@@ -1087,7 +1087,7 @@ void SimpleExpressionElement::update_from_stack_right_shift_logic(const Env& env
auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0);
auto read_elt = pool.alloc_element<BitfieldAccessElement>(base, arg0_type);
BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL, m_expr.get_arg(1).get_int());
auto other = read_elt->push_step(step, env.dts->ts, pool);
auto other = read_elt->push_step(step, env.dts->ts, pool, env);
assert(other); // should be a high field.
result->push_back(other);
} else {
@@ -1100,7 +1100,7 @@ void SimpleExpressionElement::update_from_stack_right_shift_logic(const Env& env
if (as_bitfield_access) {
BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL, m_expr.get_arg(1).get_int());
auto next = as_bitfield_access->push_step(step, env.dts->ts, pool);
auto next = as_bitfield_access->push_step(step, env.dts->ts, pool, env);
if (next) {
result->push_back(next);
} else {
@@ -1139,7 +1139,7 @@ void SimpleExpressionElement::update_from_stack_right_shift_arith(const Env& env
auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0);
auto read_elt = pool.alloc_element<BitfieldAccessElement>(base, arg0_type);
BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_ARITH, m_expr.get_arg(1).get_int());
auto other = read_elt->push_step(step, env.dts->ts, pool);
auto other = read_elt->push_step(step, env.dts->ts, pool, env);
assert(other); // should be a high field.
result->push_back(other);
} else {
@@ -2068,6 +2068,12 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac
form->push_back(stack.pop_back(pool));
} else {
FormStack temp_stack(false);
if (form == entry.body) {
auto as_setvar = dynamic_cast<SetVarElement*>(form->elts().back());
if (as_setvar && as_setvar->is_dead_set() && as_setvar->src_type() != TypeSpec("float")) {
rewrite_as_set = false;
}
}
for (auto& elt : form->elts()) {
elt->push_to_stack(env, pool, temp_stack);
}
@@ -2119,8 +2125,9 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac
source_types.push_back(last_in_body->src_type());
}
last_var = last_in_body->dst();
} // For now, I am fine with letting this fail. For example, if the set is eliminated by a
// coloring move. If this makes really ugly code later on, we could use this to disable
}
// For now, I am fine with letting this fail. For example, if the set is eliminated by a
// coloring move. If this makes really ugly code later on, we could use this to disable
// write as set.
}
@@ -2320,7 +2327,7 @@ FormElement* ConditionElement::make_nonzero_check_generic(const Env& env,
dynamic_cast<BitfieldAccessElement*>(source_forms.at(0)->try_as_single_element());
if (as_bitfield_op) {
bitfield_compare = as_bitfield_op->push_step(
BitfieldManip(BitfieldManip::Kind::NONZERO_COMPARE, 0), env.dts->ts, pool);
BitfieldManip(BitfieldManip::Kind::NONZERO_COMPARE, 0), env.dts->ts, pool, env);
}
if (bitfield_compare) {
@@ -2698,7 +2705,7 @@ void push_asm_srl_to_stack(const AsmOp* op,
auto base = pop_to_forms({*var}, env, pool, stack, true).at(0);
auto read_elt = pool.alloc_element<BitfieldAccessElement>(base, arg0_type);
BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL_32BIT, integer);
auto other = read_elt->push_step(step, env.dts->ts, pool);
auto other = read_elt->push_step(step, env.dts->ts, pool, env);
assert(other); // should be a high field.
stack.push_value_to_reg(*dst, pool.alloc_single_form(nullptr, other), true,
env.get_variable_type(*dst, true));
@@ -2740,6 +2747,7 @@ void AtomicOpElement::push_to_stack(const Env& env, FormPool& pool, FormStack& s
}
return;
}
throw std::runtime_error("Can't push atomic op to stack: " + m_op->to_string(env));
}
@@ -3079,6 +3087,20 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool,
true, TypeSpec("symbol"));
}
///////////////////////////
// StackSpillStoreElement
///////////////////////////
void StackSpillStoreElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
mark_popped();
auto src = pop_to_forms({m_value}, env, pool, stack, true).at(0);
auto dst = pool.alloc_single_element_form<ConstantTokenElement>(
nullptr, env.get_spill_slot_var_name(m_stack_offset));
if (m_cast_type) {
src = cast_form(src, *m_cast_type, pool, env);
}
stack.push_form_element(pool.alloc_element<SetFormFormElement>(dst, src), true);
}
void VectorFloatLoadStoreElement::push_to_stack(const Env&, FormPool&, FormStack& stack) {
mark_popped();
stack.push_form_element(this, true);
@@ -3147,4 +3169,13 @@ void StackVarDefElement::update_from_stack(const Env&,
result->push_back(this);
}
void StackSpillValueElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
} // namespace decompiler
+3 -4
View File
@@ -143,7 +143,6 @@ void BitfieldAccessElement::get_modified_regs(RegSet& regs) const {
m_base->get_modified_regs(regs);
}
namespace {
const BitField& find_field(const TypeSystem& ts,
const BitFieldType* type,
int start_bit,
@@ -172,6 +171,7 @@ const BitField& find_field(const TypeSystem& ts,
!looking_for_unsigned, type->get_name()));
}
namespace {
std::optional<BitField> find_field_from_mask(const TypeSystem& ts,
const BitFieldType* type,
uint64_t mask) {
@@ -227,7 +227,8 @@ std::optional<BitFieldDef> get_bitfield_initial_set(Form* form,
*/
FormElement* BitfieldAccessElement::push_step(const BitfieldManip step,
const TypeSystem& ts,
FormPool& pool) {
FormPool& pool,
const Env& env) {
if (m_steps.empty() && step.kind == BitfieldManip::Kind::LEFT_SHIFT) {
// for left/right shift combo to get a field.
m_steps.push_back(step);
@@ -351,8 +352,6 @@ FormElement* BitfieldAccessElement::push_step(const BitfieldManip step,
return pool.alloc_element<ModifiedCopyBitfieldElement>(m_type, m_base,
std::vector<BitFieldDef>{*val});
// todo check that the mask and the set are compatible with eachother
}
throw std::runtime_error("Unknown state in BitfieldReadElement");
+10 -1
View File
@@ -66,7 +66,10 @@ class BitfieldAccessElement : public FormElement {
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;
FormElement* push_step(const BitfieldManip step, const TypeSystem& ts, FormPool& pool);
FormElement* push_step(const BitfieldManip step,
const TypeSystem& ts,
FormPool& pool,
const Env& env);
private:
Form* m_base = nullptr;
@@ -151,4 +154,10 @@ Form* cast_to_int_enum(const EnumType* type_info,
Form* in);
std::optional<u64> get_goal_integer_constant(Form* in, const Env&);
const BitField& find_field(const TypeSystem& ts,
const BitFieldType* type,
int start_bit,
int size,
std::optional<bool> looking_for_unsigned);
} // namespace decompiler
+1
View File
@@ -67,6 +67,7 @@ class ObjectFileDB {
void analyze_functions_ir1();
void analyze_functions_ir2(const std::string& output_dir);
void ir2_top_level_pass();
void ir2_stack_spill_slot_pass();
void ir2_basic_block_pass();
void ir2_atomic_op_pass();
void ir2_type_analysis_pass();
@@ -17,6 +17,7 @@
#include "decompiler/analysis/final_output.h"
#include "decompiler/analysis/expression_build.h"
#include "decompiler/analysis/inline_asm_rewrite.h"
#include "decompiler/analysis/stack_spill.h"
#include "decompiler/analysis/anonymous_function_def.h"
#include "common/goos/PrettyPrinter.h"
#include "decompiler/IR2/Form.h"
@@ -34,6 +35,8 @@ void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir) {
ir2_top_level_pass();
lg::info("Processing basic blocks and control flow graph...");
ir2_basic_block_pass();
lg::info("Finding stack spills...");
ir2_stack_spill_slot_pass();
lg::info("Converting to atomic ops...");
ir2_atomic_op_pass();
lg::info("Running type analysis...");
@@ -239,6 +242,23 @@ void ObjectFileDB::ir2_basic_block_pass() {
100.f * inspect_methods / total_functions);
}
void ObjectFileDB::ir2_stack_spill_slot_pass() {
Timer timer;
int functions_with_spills = 0;
int total_slots = 0;
for_each_function_def_order([&](Function& func, int, ObjectFileData&) {
auto spill_map = build_spill_map(func.instructions, {func.prologue_end, func.epilogue_start});
auto map_size = spill_map.size();
if (map_size) {
functions_with_spills++;
total_slots += map_size;
}
func.ir2.env.set_stack_spills(spill_map);
});
lg::info("Analyzed stack spills: found {} functions will spills (total {} vars), took {:.2f} ms",
functions_with_spills, total_slots, timer.getMs());
}
/*!
* Conversion of MIPS instructions into AtomicOps. The AtomicOps represent what we
* think are IR of the original GOAL compiler.
+21 -2
View File
@@ -41,6 +41,10 @@ Register rv0() {
return make_gpr(Reg::V0);
}
Register rsp() {
return make_gpr(Reg::SP);
}
/////////////////////////
// Variable Helpers
/////////////////////////
@@ -149,6 +153,12 @@ std::unique_ptr<AtomicOp> make_standard_load(const Instruction& i0,
if (i0.get_src(0).is_label() && i0.get_src(1).is_reg(rfp())) {
// it's an FP relative load.
src = SimpleAtom::make_static_address(i0.get_src(0).get_label()).as_expr();
} else if (i0.get_src(0).is_imm() && i0.get_src(1).is_reg(rsp()) &&
(kind == LoadVarOp::Kind::SIGNED || kind == LoadVarOp::Kind::UNSIGNED)) {
// it's a stack spill.
return std::make_unique<StackSpillLoadOp>(make_dst_var(i0, idx), load_size,
i0.get_src(0).get_imm(),
kind == LoadVarOp::Kind::SIGNED, idx);
} else if (i0.get_src(0).is_imm() && i0.get_src(0).get_imm() == 0) {
// the offset is 0
src = make_src_atom(i0.get_src(1).get_reg(), idx).as_expr();
@@ -165,8 +175,17 @@ std::unique_ptr<AtomicOp> make_standard_store(const Instruction& i0,
int idx,
int store_size,
StoreOp::Kind kind) {
if (i0.get_src(2).is_reg(Register(Reg::GPR, Reg::SP))) {
return std::make_unique<AsmOp>(i0, idx);
if (i0.get_src(2).is_reg(Register(Reg::GPR, Reg::SP)) && kind == StoreOp::Kind::INTEGER) {
if (kind == StoreOp::Kind::INTEGER && store_size == 4 && i0.get_src(1).get_imm() == 0) {
// this is a bit of a hack. enter-state does a sw onto the stack that's not a spill, but
// instead manipulates the stores "ra" register that will later be restored.
// I believe sw is never used for stack spills, and no stack variable is ever located at
// sp + 0, so this should be safe.
return std::make_unique<AsmOp>(i0, idx);
}
// it's a stack spill.
return std::make_unique<StackSpillStoreOp>(make_src_var(i0.get_src(0).get_reg(), idx),
store_size, i0.get_src(1).get_imm(), idx);
}
SimpleAtom val;
SimpleExpression dst;
+11 -1
View File
@@ -34,8 +34,18 @@ bool convert_to_expressions(
// get variable names from the user.
f.ir2.env.map_args_from_config(arg_names, var_override_map);
// override variable types from the user.
// convert to typespec
for (auto& info : f.ir2.env.stack_slot_entries) {
auto rename = f.ir2.env.var_remap_map().find(info.second.name());
if (rename != f.ir2.env.var_remap_map().end()) {
info.second.name_override = rename->second;
}
// debug
// fmt::print("STACK {} : {} ({})\n", info.first, info.second.typespec.print(),
// info.second.tp_type.print());
}
// override variable types from the user.
std::unordered_map<std::string, TypeSpec> retype;
for (auto& remap : var_override_map) {
if (remap.second.type) {
+97
View File
@@ -0,0 +1,97 @@
#include <stdexcept>
#include "third-party/fmt/core.h"
#include "stack_spill.h"
#include "decompiler/Disasm/DecompilerLabel.h"
namespace decompiler {
std::string StackSpillSlot::print() const {
return fmt::format("[{:3d}] {}{}", offset, is_signed ? 's' : 'u', size * 8);
}
void StackSpillMap::add_access(const StackSpillSlot& access) {
auto existing = m_slot_map.find(access.offset);
if (existing != m_slot_map.end()) {
if (access != existing->second) {
throw std::runtime_error(fmt::format("Inconsistent stack access:\n{}\n{}\n",
existing->second.print(), access.print()));
}
} else {
m_slot_map.insert({access.offset, access});
}
}
const StackSpillSlot& StackSpillMap::lookup(int offset) const {
auto result = m_slot_map.find(offset);
if (result == m_slot_map.end()) {
throw std::runtime_error(fmt::format("unknown stack spill slot at offset {}", offset));
}
return result->second;
}
void StackSpillMap::finalize() {
// how many variables exist at each byte. should be 1 or 0.
int max_offset = 0;
for (auto& slot : m_slot_map) {
max_offset = std::max(max_offset, slot.second.offset + slot.second.size);
}
assert(max_offset < 4096); // just a sanity check here
std::vector<int> var_count(max_offset, 0);
for (auto& slot : m_slot_map) {
for (int i = 0; i < slot.second.size; i++) {
var_count.at(slot.second.offset + i)++;
}
}
for (size_t i = 0; i < var_count.size(); i++) {
if (var_count[i] > 1) {
throw std::runtime_error(
fmt::format("There are {} variables at stack offset {}", var_count[i], i));
}
}
}
int StackSpillMap::size() const {
return m_slot_map.size();
}
namespace {
struct StackInstrInfo {
InstructionKind kind;
bool is_load;
int size;
bool is_signed;
};
constexpr StackInstrInfo stack_instrs[] = {{InstructionKind::SQ, false, 16, false},
{InstructionKind::LQ, true, 16, false}};
} // namespace
StackSpillMap build_spill_map(const std::vector<Instruction>& instructions, Range<int> range) {
StackSpillMap map;
for (auto idx : range) {
auto& instr = instructions.at(idx);
for (auto& instr_template : stack_instrs) {
if (instr.kind == instr_template.kind) {
// we are the right kind.
auto src_reg = instr.get_src(instr_template.is_load ? 1 : 2).get_reg();
if (src_reg == Register(Reg::GPR, Reg::SP)) {
StackSpillSlot slot;
slot.offset = instr.get_src(instr_template.is_load ? 0 : 1).get_imm();
slot.size = instr_template.size;
slot.is_signed = instr_template.is_signed;
map.add_access(slot);
}
break;
}
}
}
map.finalize();
return map;
}
} // namespace decompiler
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <unordered_map>
#include <vector>
#include <string>
#include "decompiler/Disasm/Instruction.h"
#include "common/util/Range.h"
#include "decompiler/util/StackSpillMap.h"
namespace decompiler {
/*!
* Given the instructions for a function, build a StackSpillMap containing all memory used to
* spill register variables. The range should be the non-prologue/non-epilogue instruction range.
*/
StackSpillMap build_spill_map(const std::vector<Instruction>& instructions, Range<int> range);
} // namespace decompiler
+41 -6
View File
@@ -2,7 +2,7 @@
namespace decompiler {
namespace {
TypeState construct_initial_typestate(const TypeSpec& f_ts) {
TypeState construct_initial_typestate(const TypeSpec& f_ts, const Env& env) {
TypeState result;
int goal_args[] = {Reg::A0, Reg::A1, Reg::A2, Reg::A3, Reg::T0, Reg::T1, Reg::T2, Reg::T3};
assert(f_ts.base_type() == "function");
@@ -11,11 +11,16 @@ TypeState construct_initial_typestate(const TypeSpec& f_ts) {
for (int i = 0; i < int(f_ts.arg_count()) - 1; i++) {
auto reg_id = goal_args[i];
auto reg_type = f_ts.get_arg(i);
result.gpr_types[reg_id] = TP_Type::make_from_ts(reg_type);
result.get(Register(Reg::GPR, reg_id)) = TP_Type::make_from_ts(reg_type);
}
// todo, more specific process types for behaviors.
result.gpr_types[Reg::S6] = TP_Type::make_from_ts(TypeSpec("process"));
result.get(Register(Reg::GPR, Reg::S6)) = TP_Type::make_from_ts(TypeSpec("process"));
// initialize stack slots as uninitialized
for (auto slot_info : env.stack_spills().map()) {
result.spill_slots.insert({slot_info.first, {}});
}
return result;
}
@@ -80,7 +85,7 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F
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:
// STEP 1 - topological 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 = func.bb_topo_sort();
@@ -88,7 +93,7 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F
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);
block_init_types.at(0) = construct_initial_typestate(my_type, func.ir2.env);
// STEP 3 - propagate types until the result stops changing
bool run_again = true;
@@ -109,7 +114,8 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F
try {
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: {}", func.guessed_name.to_string(), e.what());
lg::warn("Function {} failed type prop at op {}: {}", func.guessed_name.to_string(),
op_id, 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;
@@ -162,6 +168,35 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F
}
}
// figure out the types of stack spill variables:
auto& env = func.ir2.env;
bool changed;
for (auto& type_info : op_types) {
for (auto& spill : type_info.spill_slots) {
auto& slot_info = env.stack_slot_entries[spill.first];
slot_info.tp_type =
dts.tp_lca(env.stack_slot_entries[spill.first].tp_type, spill.second, &changed);
slot_info.offset = spill.first;
}
}
for (auto& type_info : block_init_types) {
for (auto& spill : type_info.spill_slots) {
auto& slot_info = env.stack_slot_entries[spill.first];
slot_info.tp_type =
dts.tp_lca(env.stack_slot_entries[spill.first].tp_type, spill.second, &changed);
slot_info.offset = spill.first;
}
}
// convert to typespec
for (auto& info : env.stack_slot_entries) {
info.second.typespec = info.second.tp_type.typespec();
// debug
// fmt::print("STACK {} : {} ({})\n", info.first, info.second.typespec.print(),
// info.second.tp_type.print());
}
func.ir2.env.set_types(block_init_types, op_types, *func.ir2.atomic_ops, my_type);
return true;
+4 -3
View File
@@ -2444,6 +2444,7 @@
(unpack-v4-16 109)
(unpack-v4-8 110)
(unpack-v4-5 111)
(cmd-mask 239) ;; not sure what this is
)
(defenum vif-cmd-32
@@ -2772,12 +2773,12 @@
)
(define-extern *vif-disasm-table* (array vif-disasm-element)) ;; unknown type
;;(define-extern disasm-vif-tag (function (pointer uint32) int symbol int symbol))
(define-extern disasm-dma-tag (function dma-tag symbol int))
(define-extern disasm-vif-tag (function (pointer vif-tag) int symbol symbol int))
(define-extern disasm-dma-tag (function dma-tag symbol none))
(define-extern disasm-vif-details (function symbol (pointer uint8) vif-cmd int symbol))
(define-extern vif-disasm-element type)
(define-extern *dma-disasm* symbol)
(define-extern disasm-dma-list function)
(define-extern disasm-dma-list (function dma-packet symbol symbol symbol int symbol))
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -181,6 +181,36 @@
[[202, 225], "s3", "(pointer uint16)"]
],
"disasm-vif-tag": [
[[81, 85], "t1", "vif-stcycl-imm"],
[242, "a0", "vif-unpack-imm"]
],
"disasm-dma-list": [
[25, "v1", "dma-tag"],
[153, "v1", "dma-packet"],
[189, "v1", "dma-packet"],
[229, "v1", "dma-packet"],
[258, "v1", "dma-packet"],
[302, "v1", "dma-packet"],
[308, "v1", "dma-packet"],
//[133, "v1", "(pointer uint64)"],
[152, "v1", "(pointer uint64)"],
[167, "v1", "(pointer uint64)"],
[176, "v1", "(pointer uint64)"],
[198, "v1", "(pointer uint64)"],
[207, "v1", "(pointer uint64)"],
[238, "v1", "(pointer uint64)"],
[247, "v1", "(pointer uint64)"],
[282, "v1", "(pointer uint64)"],
[291, "v1", "(pointer uint64)"],
[324, "v1", "(pointer uint64)"],
[334, "v1", "(pointer uint64)"]
],
// LEVEL
"lookup-level-info": [
[3, "a1", "symbol"],
@@ -687,6 +687,42 @@
"vars": { "s4-0": "count2", "s3-0": "data-ptr", "s2-0": "i" }
},
"disasm-vif-tag": {
"args": ["data", "words", "stream", "details"],
"vars": {
"gp-0": "byte-idx",
"v1-0": "cmd-template-idx",
"a0-12": "print-kind",
"s1-0": "first-tag",
"s0-0": "packet-size",
"t1-1": ["stcycl-imm", "vif-stcycl-imm"],
"sv-16": "cmd",
"sv-32": "data-ptr",
"sv-48": "data-idx",
"sv-64": "unpack-imm"
}
},
"disasm-dma-list": {
"args": ["data", "mode", "verbose", "stream", "expected-size"],
"vars": {
"sv-16": "addr",
"sv-32": "data-2",
"sv-48": "qwc",
"sv-64": "ra-1",
"sv-80": "ra-2",
"sv-96": "call-depth",
"sv-112": "current-tag",
"s2-0": "mode-2",
"s3-0": "verbose-2",
"gp-0": "stream-2",
"s1-0": "expected-size-2",
"s0-0": "end-condition",
"s4-0": "total-qwc",
"s5-0": "total-tags"
}
},
"cpad-invalid!": {
"args": ["pad"]
},
+18
View File
@@ -367,10 +367,28 @@ bool DecompilerTypeSystem::tp_lca(TypeState* combined, const TypeState& add) {
}
}
for (auto& x : add.spill_slots) {
// auto existing = combined->spill_slots.find(x.first);
// if (existing == combined->spill_slots.end()) {
// result = true;
// combined->spill_slots.insert({existing->first, existing->second});
// }
bool diff = false;
auto new_type = tp_lca(combined->spill_slots[x.first], x.second, &diff);
if (diff) {
result = true;
combined->spill_slots[x.first] = new_type;
}
}
return result;
}
int DecompilerTypeSystem::get_format_arg_count(const std::string& str) const {
// temporary hack, remove this.
if (str == "ERROR: dma tag has data in reserved bits ~X~%") {
return 0;
}
int arg_count = 0;
for (size_t i = 0; i < str.length(); i++) {
if (str.at(i) == '~') {
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <unordered_map>
#include <vector>
#include <string>
namespace decompiler {
/*!
* Memory on the stack used to spill a register.
* This just has a size/is_signed? and doesn't know anything about types.
*/
struct StackSpillSlot {
int offset = -1; // relative to sp
int size = -1; // bytes
bool is_signed = false; // set to false for quadwords/doublewords
bool operator==(const StackSpillSlot& other) const {
return offset == other.offset && size == other.size && is_signed == other.is_signed;
}
bool operator!=(const StackSpillSlot& other) const { return !((*this) == other); }
std::string print() const;
};
/*!
* Map of StackSpillSlots for a function.
*/
class StackSpillMap {
public:
void add_access(const StackSpillSlot& access);
void finalize();
const StackSpillSlot& lookup(int offset) const;
int size() const;
const std::unordered_map<int, StackSpillSlot>& map() const { return m_slot_map; }
private:
std::unordered_map<int, StackSpillSlot> m_slot_map;
};
} // namespace decompiler
+6
View File
@@ -59,6 +59,8 @@ std::string TP_Type::print() const {
return fmt::format("<vmethod {}>", m_ts.print());
case Kind::NON_VIRTUAL_METHOD:
return fmt::format("<method {}>", m_ts.print());
case Kind::LEFT_SHIFTED_BITFIELD:
return fmt::format("(<{}> << {})", m_ts.print(), m_int);
case Kind::INVALID:
default:
assert(false);
@@ -105,6 +107,8 @@ bool TP_Type::operator==(const TP_Type& other) const {
case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT:
return m_int == other.m_int && m_ts == other.m_ts &&
m_extra_multiplier == other.m_extra_multiplier;
case Kind::LEFT_SHIFTED_BITFIELD:
return m_int == other.m_int && m_ts == other.m_ts;
case Kind::INVALID:
default:
assert(false);
@@ -157,6 +161,8 @@ TypeSpec TP_Type::typespec() const {
return m_ts;
case Kind::NON_VIRTUAL_METHOD:
return m_ts;
case Kind::LEFT_SHIFTED_BITFIELD:
return TypeSpec("int"); // ideally this is never used.
case Kind::INVALID:
default:
assert(false);
+31
View File
@@ -32,6 +32,7 @@ class TP_Type {
DYNAMIC_METHOD_ACCESS, // partial access into a
VIRTUAL_METHOD,
NON_VIRTUAL_METHOD,
LEFT_SHIFTED_BITFIELD, // (bitfield << some-constant)
INVALID
} kind = Kind::UNINITIALIZED;
TP_Type() = default;
@@ -57,6 +58,7 @@ class TP_Type {
case Kind::INTEGER_CONSTANT_PLUS_VAR_MULT:
case Kind::VIRTUAL_METHOD:
case Kind::NON_VIRTUAL_METHOD:
case Kind::LEFT_SHIFTED_BITFIELD:
return false;
case Kind::UNINITIALIZED:
case Kind::OBJECT_NEW_METHOD:
@@ -209,6 +211,14 @@ class TP_Type {
return result;
}
static TP_Type make_from_left_shift_bitfield(const TypeSpec& ts, int amount) {
TP_Type result;
result.kind = Kind::LEFT_SHIFTED_BITFIELD;
result.m_ts = ts;
result.m_int = amount;
return result;
}
const TypeSpec& get_objects_typespec() const {
assert(kind == Kind::TYPESPEC || kind == Kind::INTEGER_CONSTANT_PLUS_VAR);
return m_ts;
@@ -249,6 +259,16 @@ class TP_Type {
return m_extra_multiplier;
}
int get_left_shift() const {
assert(kind == Kind::LEFT_SHIFTED_BITFIELD);
return m_int;
}
const TypeSpec& get_bitfield_type() const {
assert(kind == Kind::LEFT_SHIFTED_BITFIELD);
return m_ts;
}
private:
TypeSpec m_ts;
std::string m_str;
@@ -260,6 +280,7 @@ class TP_Type {
struct TypeState {
TP_Type gpr_types[32];
TP_Type fpr_types[32];
std::unordered_map<int, TP_Type> spill_slots;
std::string print_gpr_masked(u32 mask) const;
TP_Type& get(const Register& r) {
@@ -285,6 +306,16 @@ struct TypeState {
throw std::runtime_error("TP_Type::get failed");
}
}
const TP_Type& get_slot(int offset) const {
auto result = spill_slots.find(offset);
if (result == spill_slots.end()) {
throw std::runtime_error("TP_Type::get_slot failed: " + std::to_string(offset));
}
return result->second;
}
TP_Type& get_slot(int offset) { return spill_slots[offset]; }
};
u32 regs_to_gpr_mask(const std::vector<Register>& regs);