mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-23 22:29:53 +00:00
[Decompiler] Get used variables, handle function calls better, and minor cleanup (#205)
* small fixes, implement getting used variables * improve printing of variables * missing include
This commit is contained in:
parent
af328ffa29
commit
679b17ae6f
@ -144,7 +144,7 @@ class Register {
|
||||
|
||||
bool operator==(const Register& other) const;
|
||||
bool operator!=(const Register& other) const;
|
||||
bool operator<(const Register& other) { return id < other.id; }
|
||||
bool operator<(const Register& other) const { return id < other.id; }
|
||||
|
||||
struct hash {
|
||||
auto operator()(const Register& x) const { return std::hash<uint16_t>()(x.id); }
|
||||
|
@ -166,9 +166,6 @@ class Function {
|
||||
bool atomic_ops_attempted = false;
|
||||
bool atomic_ops_succeeded = false;
|
||||
std::shared_ptr<FunctionAtomicOps> atomic_ops = nullptr;
|
||||
bool has_reg_use = false;
|
||||
RegUsageInfo reg_use;
|
||||
bool has_type_info = false;
|
||||
Env env;
|
||||
FormPool form_pool;
|
||||
Form* top_form = nullptr;
|
||||
|
@ -142,6 +142,12 @@ goos::Object SimpleAtom::to_form(const std::vector<DecompilerLabel>& labels, con
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleAtom::collect_vars(VariableSet& vars) const {
|
||||
if (is_var()) {
|
||||
vars.insert(var());
|
||||
}
|
||||
}
|
||||
|
||||
bool SimpleAtom::operator==(const SimpleAtom& other) const {
|
||||
if (other.m_kind != m_kind) {
|
||||
return false;
|
||||
@ -353,6 +359,12 @@ void SimpleExpression::get_regs(std::vector<Register>* out) const {
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleExpression::collect_vars(VariableSet& vars) const {
|
||||
for (int i = 0; i < args(); i++) {
|
||||
get_arg(i).collect_vars(vars);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// SetVarOp
|
||||
/////////////////////////////
|
||||
@ -395,6 +407,11 @@ void SetVarOp::update_register_info() {
|
||||
m_src.get_regs(&m_read_regs);
|
||||
}
|
||||
|
||||
void SetVarOp::collect_vars(VariableSet& vars) const {
|
||||
vars.insert(m_dst);
|
||||
m_src.collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// AsmOp
|
||||
/////////////////////////////
|
||||
@ -482,6 +499,17 @@ void AsmOp::update_register_info() {
|
||||
}
|
||||
}
|
||||
|
||||
void AsmOp::collect_vars(VariableSet& vars) const {
|
||||
if (m_dst.has_value()) {
|
||||
vars.insert(*m_dst);
|
||||
}
|
||||
|
||||
for (auto& x : m_src) {
|
||||
if (x.has_value()) {
|
||||
vars.insert(*x);
|
||||
}
|
||||
}
|
||||
}
|
||||
/////////////////////////////
|
||||
// Condition
|
||||
/////////////////////////////
|
||||
@ -724,6 +752,12 @@ void IR2_Condition::get_regs(std::vector<Register>* out) const {
|
||||
}
|
||||
}
|
||||
|
||||
void IR2_Condition::collect_vars(VariableSet& vars) const {
|
||||
for (int i = 0; i < get_condition_num_args(m_kind); i++) {
|
||||
m_src[i].collect_vars(vars);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// SetVarConditionOp
|
||||
/////////////////////////////
|
||||
@ -761,6 +795,11 @@ void SetVarConditionOp::update_register_info() {
|
||||
m_condition.get_regs(&m_read_regs);
|
||||
}
|
||||
|
||||
void SetVarConditionOp::collect_vars(VariableSet& vars) const {
|
||||
vars.insert(m_dst);
|
||||
m_condition.collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// StoreOp
|
||||
/////////////////////////////
|
||||
@ -824,6 +863,11 @@ void StoreOp::update_register_info() {
|
||||
m_value.get_regs(&m_read_regs);
|
||||
}
|
||||
|
||||
void StoreOp::collect_vars(VariableSet& vars) const {
|
||||
m_addr.collect_vars(vars);
|
||||
m_value.collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// LoadVarOp
|
||||
/////////////////////////////
|
||||
@ -902,6 +946,11 @@ void LoadVarOp::update_register_info() {
|
||||
m_write_regs.push_back(m_dst.reg());
|
||||
}
|
||||
|
||||
void LoadVarOp::collect_vars(VariableSet& vars) const {
|
||||
vars.insert(m_dst);
|
||||
m_src.collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// IR2_BranchDelay
|
||||
/////////////////////////////
|
||||
@ -1009,6 +1058,14 @@ void IR2_BranchDelay::get_regs(std::vector<Register>* write, std::vector<Registe
|
||||
}
|
||||
}
|
||||
|
||||
void IR2_BranchDelay::collect_vars(VariableSet& vars) const {
|
||||
for (auto& x : m_var) {
|
||||
if (x.has_value()) {
|
||||
vars.insert(*x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// BranchOp
|
||||
/////////////////////////////
|
||||
@ -1064,6 +1121,11 @@ void BranchOp::update_register_info() {
|
||||
m_branch_delay.get_regs(&m_write_regs, &m_read_regs);
|
||||
}
|
||||
|
||||
void BranchOp::collect_vars(VariableSet& vars) const {
|
||||
m_condition.collect_vars(vars);
|
||||
m_branch_delay.collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// SpecialOp
|
||||
/////////////////////////////
|
||||
@ -1124,16 +1186,26 @@ void SpecialOp::update_register_info() {
|
||||
}
|
||||
}
|
||||
|
||||
void SpecialOp::collect_vars(VariableSet&) const {}
|
||||
|
||||
/////////////////////////////
|
||||
// CallOp
|
||||
/////////////////////////////
|
||||
|
||||
CallOp::CallOp(int my_idx) : AtomicOp(my_idx) {}
|
||||
CallOp::CallOp(int my_idx)
|
||||
: AtomicOp(my_idx),
|
||||
m_function_var(VariableMode::READ, Register(Reg::GPR, Reg::T9), my_idx),
|
||||
m_return_var(VariableMode::WRITE, Register(Reg::GPR, Reg::V0), my_idx) {}
|
||||
|
||||
goos::Object CallOp::to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const {
|
||||
(void)labels;
|
||||
(void)env;
|
||||
return pretty_print::build_list("call!");
|
||||
std::vector<goos::Object> forms;
|
||||
forms.push_back(pretty_print::to_symbol("call!"));
|
||||
for (auto& x : m_arg_vars) {
|
||||
forms.push_back(pretty_print::to_symbol(x.to_string(env)));
|
||||
}
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
bool CallOp::operator==(const AtomicOp& other) const {
|
||||
@ -1164,6 +1236,15 @@ void CallOp::update_register_info() {
|
||||
clobber_temps();
|
||||
}
|
||||
|
||||
void CallOp::collect_vars(VariableSet& vars) const {
|
||||
vars.insert(m_function_var);
|
||||
for (auto& e : m_arg_vars) {
|
||||
vars.insert(e);
|
||||
}
|
||||
|
||||
vars.insert(m_return_var);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// ConditionalMoveFalseOp
|
||||
/////////////////////////////
|
||||
@ -1200,4 +1281,9 @@ void ConditionalMoveFalseOp::update_register_info() {
|
||||
m_write_regs.push_back(m_dst.reg());
|
||||
m_read_regs.push_back(m_src.reg());
|
||||
}
|
||||
|
||||
void ConditionalMoveFalseOp::collect_vars(VariableSet& vars) const {
|
||||
vars.insert(m_dst);
|
||||
vars.insert(m_src);
|
||||
}
|
||||
} // namespace decompiler
|
@ -16,51 +16,6 @@ class ConditionElement;
|
||||
class FormPool;
|
||||
class DecompilerTypeSystem;
|
||||
|
||||
/*!
|
||||
* A "Variable" represents a register at a given instruction index.
|
||||
* The register can either be a GOAL local variable or a GOAL register used in inline assembly.
|
||||
* Because OpenGOAL's registers don't one-to-one map to GOAL registers, GOAL "inline assembly
|
||||
* registers" will become OpenGOAL variables, and are treated similarly to variables in
|
||||
* decompilation.
|
||||
*
|
||||
* In the earlier parts of decompilation, this just behaves like a register in all cases.
|
||||
* But in later parts registers can be mapped to real local variables with types. A variable can
|
||||
* look itself up in an environment to determine what "local variable" it is.
|
||||
*
|
||||
* Note: a variable is _not_ allowed to be R0, AT, S7, K0, K1, FP, or RA by default, as these
|
||||
* can never hold normal GOAL locals. Inline assembly may use these, but you must set the allow_all
|
||||
* flag to true in the constructor of Variable to indicate this is what you really want.
|
||||
*
|
||||
* Note: access to the process pointer (s6) is handled as a variable. As a result, you may always
|
||||
* use s6 as a variable.
|
||||
*/
|
||||
class Variable {
|
||||
public:
|
||||
Variable() = default;
|
||||
Variable(VariableMode mode, Register reg, int atomic_idx, bool allow_all = false);
|
||||
|
||||
enum class Print {
|
||||
AS_REG, // print as a PS2 register name
|
||||
FULL, // print as a register name, plus an index, plus read or write
|
||||
AS_VARIABLE, // print local variable name, error if impossible
|
||||
AUTOMATIC, // print as variable, but if that's not possible print as reg.
|
||||
};
|
||||
|
||||
std::string to_string(const Env* env, Print mode = Print::AUTOMATIC) const;
|
||||
|
||||
bool operator==(const Variable& other) const;
|
||||
bool operator!=(const Variable& other) const;
|
||||
|
||||
const Register& reg() const { return m_reg; }
|
||||
VariableMode mode() const { return m_mode; }
|
||||
int idx() const { return m_atomic_idx; }
|
||||
|
||||
private:
|
||||
VariableMode m_mode = VariableMode::READ; // do we represent a read or a write?
|
||||
Register m_reg; // the EE register
|
||||
int m_atomic_idx = -1; // the index in the function's list of AtomicOps
|
||||
};
|
||||
|
||||
/*!
|
||||
* An atomic operation represents a single operation from the point of view of the IR2 system.
|
||||
* Each IR2 op is one or more instructions.
|
||||
@ -114,6 +69,8 @@ class AtomicOp {
|
||||
// read twice.
|
||||
virtual void update_register_info() = 0;
|
||||
|
||||
virtual void collect_vars(VariableSet& vars) const = 0;
|
||||
|
||||
TypeState propagate_types(const TypeState& input, const Env& env, DecompilerTypeSystem& dts);
|
||||
|
||||
int op_id() const { return m_my_idx; }
|
||||
@ -171,6 +128,7 @@ class SimpleAtom {
|
||||
static SimpleAtom make_int_constant(s64 value);
|
||||
static SimpleAtom make_static_address(int static_label_id);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const;
|
||||
void collect_vars(VariableSet& vars) const;
|
||||
|
||||
bool is_var() const { return m_kind == Kind::VARIABLE; }
|
||||
const Variable& var() const {
|
||||
@ -274,6 +232,7 @@ class SimpleExpression {
|
||||
TP_Type get_type_int1(const TypeState& input,
|
||||
const Env& env,
|
||||
const DecompilerTypeSystem& dts) const;
|
||||
void collect_vars(VariableSet& vars) const;
|
||||
|
||||
private:
|
||||
Kind m_kind = Kind::INVALID;
|
||||
@ -302,6 +261,7 @@ class SetVarOp : public AtomicOp {
|
||||
TypeState propagate_types_internal(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
Variable m_dst;
|
||||
@ -327,6 +287,7 @@ class AsmOp : public AtomicOp {
|
||||
TypeState propagate_types_internal(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
Instruction m_instr;
|
||||
@ -392,6 +353,7 @@ class IR2_Condition {
|
||||
Kind kind() const { return m_kind; }
|
||||
const SimpleAtom& src(int i) const { return m_src[i]; }
|
||||
ConditionElement* get_as_form(FormPool& pool) const;
|
||||
void collect_vars(VariableSet& vars) const;
|
||||
|
||||
private:
|
||||
Kind m_kind = Kind::INVALID;
|
||||
@ -418,6 +380,7 @@ class SetVarConditionOp : public AtomicOp {
|
||||
TypeState propagate_types_internal(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
Variable m_dst;
|
||||
@ -441,6 +404,7 @@ class StoreOp : public AtomicOp {
|
||||
TypeState propagate_types_internal(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
int m_size;
|
||||
@ -467,6 +431,7 @@ class LoadVarOp : public AtomicOp {
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) override;
|
||||
TP_Type get_src_type(const TypeState& input, const Env& env, DecompilerTypeSystem& dts) const;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
Kind m_kind;
|
||||
@ -507,6 +472,7 @@ class IR2_BranchDelay {
|
||||
TypeState propagate_types(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) const;
|
||||
void collect_vars(VariableSet& vars) const;
|
||||
Kind kind() const { return m_kind; }
|
||||
const Variable& var(int idx) const {
|
||||
assert(idx < 3);
|
||||
@ -539,8 +505,10 @@ class BranchOp : public AtomicOp {
|
||||
TypeState propagate_types_internal(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
const IR2_BranchDelay& branch_delay() const { return m_branch_delay; }
|
||||
const IR2_Condition& condition() const { return m_condition; }
|
||||
ConditionElement* get_condition_as_form(FormPool& pool) const;
|
||||
bool likely() const { return m_likely; }
|
||||
|
||||
private:
|
||||
@ -573,6 +541,7 @@ class SpecialOp : public AtomicOp {
|
||||
TypeState propagate_types_internal(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
Kind m_kind;
|
||||
@ -594,10 +563,15 @@ class CallOp : public AtomicOp {
|
||||
TypeState propagate_types_internal(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
protected:
|
||||
TypeSpec m_call_type;
|
||||
bool m_call_type_set = false;
|
||||
|
||||
std::vector<Variable> m_arg_vars;
|
||||
Variable m_function_var;
|
||||
Variable m_return_var;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -624,6 +598,7 @@ class ConditionalMoveFalseOp : public AtomicOp {
|
||||
TypeState propagate_types_internal(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
Variable m_dst, m_src;
|
||||
|
@ -3,6 +3,10 @@
|
||||
|
||||
namespace decompiler {
|
||||
|
||||
ConditionElement* BranchOp::get_condition_as_form(FormPool& pool) const {
|
||||
return m_condition.get_as_form(pool);
|
||||
}
|
||||
|
||||
ConditionElement* IR2_Condition::get_as_form(FormPool& pool) const {
|
||||
Form* sources[2] = {nullptr, nullptr};
|
||||
int n_sources = get_condition_num_args(m_kind);
|
||||
|
@ -724,9 +724,11 @@ TypeState CallOp::propagate_types_internal(const TypeState& input,
|
||||
|
||||
// we can also update register usage here.
|
||||
m_read_regs.clear();
|
||||
m_arg_vars.clear();
|
||||
m_read_regs.emplace_back(Reg::GPR, Reg::T9);
|
||||
for (int i = 0; i < arg_count; i++) {
|
||||
m_read_regs.emplace_back(Reg::GPR, arg_regs[i]);
|
||||
m_arg_vars.push_back(Variable(VariableMode::READ, m_read_regs.back(), m_my_idx));
|
||||
}
|
||||
|
||||
return end_types;
|
||||
@ -742,10 +744,12 @@ TypeState CallOp::propagate_types_internal(const TypeState& input,
|
||||
|
||||
// we can also update register usage here.
|
||||
m_read_regs.clear();
|
||||
m_arg_vars.clear();
|
||||
m_read_regs.emplace_back(Reg::GPR, Reg::T9);
|
||||
|
||||
for (uint32_t i = 0; i < in_type.arg_count() - 1; i++) {
|
||||
m_read_regs.emplace_back(Reg::GPR, arg_regs[i]);
|
||||
m_arg_vars.push_back(Variable(VariableMode::READ, m_read_regs.back(), m_my_idx));
|
||||
}
|
||||
|
||||
m_write_regs.clear();
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
#include <algorithm>
|
||||
#include "Env.h"
|
||||
#include "Form.h"
|
||||
|
||||
namespace decompiler {
|
||||
std::string Env::get_variable_name(Register reg, int atomic_idx, VariableMode mode) const {
|
||||
@ -17,32 +19,69 @@ void Env::set_types(const std::vector<TypeState>& block_init_types,
|
||||
m_has_types = true;
|
||||
}
|
||||
|
||||
std::string Env::print_local_var_types() const {
|
||||
std::string Env::print_local_var_types(const Form* top_level_form) const {
|
||||
assert(has_local_vars());
|
||||
std::vector<std::string> entries;
|
||||
std::unordered_map<Register, std::unordered_set<int>, Register::hash> printed;
|
||||
|
||||
for (auto& reg_info : m_var_names.read_vars) {
|
||||
auto& reg_printed = printed[reg_info.first];
|
||||
for (int var_id = 0; var_id < int(reg_info.second.size()); var_id++) {
|
||||
auto& info = reg_info.second.at(var_id);
|
||||
if (top_level_form) {
|
||||
VariableSet var_set;
|
||||
top_level_form->collect_vars(var_set);
|
||||
|
||||
// we want to sort them for easier reading:
|
||||
std::vector<std::pair<RegId, Variable>> vars;
|
||||
|
||||
for (auto& x : var_set) {
|
||||
vars.push_back(std::make_pair(get_ssa_var(x), x));
|
||||
}
|
||||
|
||||
std::sort(vars.begin(), vars.end(),
|
||||
[](const std::pair<RegId, Variable>& a, const std::pair<RegId, Variable>& b) {
|
||||
return a.first < b.first;
|
||||
});
|
||||
|
||||
RegId* prev = nullptr;
|
||||
for (auto& x : vars) {
|
||||
// sorted by ssa var and there are likely duplicates of Variables and SSA vars, only print
|
||||
// unique ssa variables.
|
||||
if (prev && x.first == *prev) {
|
||||
continue;
|
||||
}
|
||||
prev = &x.first;
|
||||
auto& map = x.second.mode() == VariableMode::WRITE ? m_var_names.write_vars.at(x.second.reg())
|
||||
: m_var_names.read_vars.at(x.second.reg());
|
||||
auto& info = map.at(x.first.id);
|
||||
|
||||
if (info.initialized) {
|
||||
reg_printed.insert(var_id);
|
||||
entries.push_back(fmt::format("{}: {}", info.name(), info.type.typespec().print()));
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::unordered_map<Register, std::unordered_set<int>, Register::hash> printed;
|
||||
|
||||
for (auto& reg_info : m_var_names.write_vars) {
|
||||
auto& reg_printed = printed[reg_info.first];
|
||||
for (int var_id = 0; var_id < int(reg_info.second.size()); var_id++) {
|
||||
auto& info = reg_info.second.at(var_id);
|
||||
if (info.initialized) {
|
||||
if (reg_printed.find(var_id) == reg_printed.end()) {
|
||||
for (auto& reg_info : m_var_names.read_vars) {
|
||||
auto& reg_printed = printed[reg_info.first];
|
||||
for (int var_id = 0; var_id < int(reg_info.second.size()); var_id++) {
|
||||
auto& info = reg_info.second.at(var_id);
|
||||
if (info.initialized) {
|
||||
reg_printed.insert(var_id);
|
||||
entries.push_back(fmt::format("{}: {}", info.name(), info.type.typespec().print()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& reg_info : m_var_names.write_vars) {
|
||||
auto& reg_printed = printed[reg_info.first];
|
||||
for (int var_id = 0; var_id < int(reg_info.second.size()); var_id++) {
|
||||
auto& info = reg_info.second.at(var_id);
|
||||
if (info.initialized) {
|
||||
if (reg_printed.find(var_id) == reg_printed.end()) {
|
||||
entries.push_back(fmt::format("{}: {}", info.name(), info.type.typespec().print()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int max_len = 0;
|
||||
@ -75,4 +114,16 @@ std::string Env::print_local_var_types() const {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unordered_set<RegId, RegId::hash> Env::get_ssa_var(const VariableSet& vars) const {
|
||||
std::unordered_set<RegId, RegId::hash> result;
|
||||
for (auto& x : vars) {
|
||||
result.insert(get_ssa_var(x));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
RegId Env::get_ssa_var(const Variable& var) const {
|
||||
return m_var_names.lookup(var.reg(), var.idx(), var.mode()).reg_id;
|
||||
}
|
||||
} // namespace decompiler
|
@ -6,20 +6,22 @@
|
||||
#include "decompiler/util/TP_Type.h"
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "decompiler/IR2/IR2_common.h"
|
||||
#include "decompiler/IR2/reg_usage.h"
|
||||
|
||||
namespace decompiler {
|
||||
class LinkedObjectFile;
|
||||
class Form;
|
||||
|
||||
struct VariableNames {
|
||||
struct VarInfo {
|
||||
VarInfo() = default;
|
||||
std::string name() const { return fmt::format("{}-{}", reg.to_charp(), id); }
|
||||
std::string name() const { return fmt::format("{}-{}", reg_id.reg.to_charp(), reg_id.id); }
|
||||
TP_Type type;
|
||||
Register reg;
|
||||
int id = -1;
|
||||
RegId reg_id;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
// todo - this is kind of gross.
|
||||
std::unordered_map<Register, std::vector<VariableNames::VarInfo>, Register::hash> read_vars,
|
||||
write_vars;
|
||||
std::unordered_map<Register, std::vector<int>, Register::hash> read_opid_to_varid,
|
||||
@ -44,6 +46,18 @@ class Env {
|
||||
public:
|
||||
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; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::string get_variable_name(Register reg, int atomic_idx, VariableMode mode) const;
|
||||
|
||||
/*!
|
||||
@ -71,15 +85,22 @@ class Env {
|
||||
m_has_local_vars = true;
|
||||
}
|
||||
|
||||
std::string print_local_var_types() const;
|
||||
std::string print_local_var_types(const Form* top_level_form) const;
|
||||
|
||||
std::unordered_set<RegId, RegId::hash> get_ssa_var(const VariableSet& vars) const;
|
||||
RegId get_ssa_var(const Variable& var) const;
|
||||
|
||||
LinkedObjectFile* file = nullptr;
|
||||
|
||||
private:
|
||||
bool m_has_reg_use = false;
|
||||
RegUsageInfo m_reg_use;
|
||||
|
||||
bool m_has_local_vars = false;
|
||||
VariableNames m_var_names;
|
||||
|
||||
bool m_has_types = false;
|
||||
std::vector<TypeState> m_block_init_types;
|
||||
std::vector<TypeState> m_op_end_types;
|
||||
VariableNames m_var_names;
|
||||
};
|
||||
} // namespace decompiler
|
@ -1,4 +1,6 @@
|
||||
#include "Form.h"
|
||||
|
||||
#include <utility>
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
#include "common/goos/PrettyPrinter.h"
|
||||
|
||||
@ -55,11 +57,17 @@ void Form::apply_form(const std::function<void(Form*)>& f) {
|
||||
}
|
||||
}
|
||||
|
||||
void Form::collect_vars(VariableSet& vars) const {
|
||||
for (auto e : m_elements) {
|
||||
e->collect_vars(vars);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// SimpleExpressionElement
|
||||
/////////////////////////////
|
||||
|
||||
SimpleExpressionElement::SimpleExpressionElement(const SimpleExpression& expr) : m_expr(expr) {}
|
||||
SimpleExpressionElement::SimpleExpressionElement(SimpleExpression expr) : m_expr(std::move(expr)) {}
|
||||
|
||||
goos::Object SimpleExpressionElement::to_form(const Env& env) const {
|
||||
return m_expr.to_form(env.file->labels, &env);
|
||||
@ -75,95 +83,8 @@ bool SimpleExpressionElement::is_sequence_point() const {
|
||||
throw std::runtime_error("Should not check if a SimpleExpressionElement is a sequence point");
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// SetVarElement
|
||||
/////////////////////////////
|
||||
|
||||
SetVarElement::SetVarElement(const Variable& var, Form* value, bool is_sequence_point)
|
||||
: m_dst(var), m_src(value), m_is_sequence_point(is_sequence_point) {
|
||||
value->parent_element = this;
|
||||
}
|
||||
|
||||
goos::Object SetVarElement::to_form(const Env& env) const {
|
||||
return pretty_print::build_list("set!", m_dst.to_string(&env), m_src->to_form(env));
|
||||
}
|
||||
|
||||
void SetVarElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
f(this);
|
||||
m_src->apply(f);
|
||||
}
|
||||
|
||||
void SetVarElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
m_src->apply_form(f);
|
||||
}
|
||||
|
||||
bool SetVarElement::is_sequence_point() const {
|
||||
return m_is_sequence_point;
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// AtomicOpElement
|
||||
/////////////////////////////
|
||||
|
||||
AtomicOpElement::AtomicOpElement(const AtomicOp* op) : m_op(op) {}
|
||||
|
||||
goos::Object AtomicOpElement::to_form(const Env& env) const {
|
||||
return m_op->to_form(env.file->labels, &env);
|
||||
}
|
||||
|
||||
void AtomicOpElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
f(this);
|
||||
}
|
||||
|
||||
void AtomicOpElement::apply_form(const std::function<void(Form*)>&) {}
|
||||
|
||||
/////////////////////////////
|
||||
// ConditionElement
|
||||
/////////////////////////////
|
||||
|
||||
ConditionElement::ConditionElement(IR2_Condition::Kind kind, Form* src0, Form* src1)
|
||||
: m_kind(kind) {
|
||||
m_src[0] = src0;
|
||||
m_src[1] = src1;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (m_src[i]) {
|
||||
m_src[i]->parent_element = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object ConditionElement::to_form(const Env& env) const {
|
||||
std::vector<goos::Object> forms;
|
||||
forms.push_back(pretty_print::to_symbol(get_condition_kind_name(m_kind)));
|
||||
for (int i = 0; i < get_condition_num_args(m_kind); i++) {
|
||||
forms.push_back(m_src[i]->to_form(env));
|
||||
}
|
||||
if (forms.size() > 1) {
|
||||
return pretty_print::build_list(forms);
|
||||
} else {
|
||||
return forms.front();
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
f(this);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (m_src[i]) {
|
||||
m_src[i]->apply(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (m_src[i]) {
|
||||
m_src[i]->apply_form(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionElement::invert() {
|
||||
m_kind = get_condition_opposite(m_kind);
|
||||
void SimpleExpressionElement::collect_vars(VariableSet& vars) const {
|
||||
m_expr.collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
@ -182,6 +103,10 @@ void StoreElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
|
||||
void StoreElement::apply_form(const std::function<void(Form*)>&) {}
|
||||
|
||||
void StoreElement::collect_vars(VariableSet& vars) const {
|
||||
return m_op->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// LoadSourceElement
|
||||
/////////////////////////////
|
||||
@ -236,6 +161,10 @@ void LoadSourceElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
m_addr->apply_form(f);
|
||||
}
|
||||
|
||||
void LoadSourceElement::collect_vars(VariableSet& vars) const {
|
||||
m_addr->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// SimpleAtomElement
|
||||
/////////////////////////////
|
||||
@ -252,6 +181,118 @@ void SimpleAtomElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
|
||||
void SimpleAtomElement::apply_form(const std::function<void(Form*)>&) {}
|
||||
|
||||
void SimpleAtomElement::collect_vars(VariableSet& vars) const {
|
||||
return m_atom.collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// SetVarElement
|
||||
/////////////////////////////
|
||||
|
||||
SetVarElement::SetVarElement(const Variable& var, Form* value, bool is_sequence_point)
|
||||
: m_dst(var), m_src(value), m_is_sequence_point(is_sequence_point) {
|
||||
value->parent_element = this;
|
||||
}
|
||||
|
||||
goos::Object SetVarElement::to_form(const Env& env) const {
|
||||
return pretty_print::build_list("set!", m_dst.to_string(&env), m_src->to_form(env));
|
||||
}
|
||||
|
||||
void SetVarElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
f(this);
|
||||
m_src->apply(f);
|
||||
}
|
||||
|
||||
void SetVarElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
m_src->apply_form(f);
|
||||
}
|
||||
|
||||
bool SetVarElement::is_sequence_point() const {
|
||||
return m_is_sequence_point;
|
||||
}
|
||||
|
||||
void SetVarElement::collect_vars(VariableSet& vars) const {
|
||||
vars.insert(m_dst);
|
||||
m_src->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// AtomicOpElement
|
||||
/////////////////////////////
|
||||
|
||||
AtomicOpElement::AtomicOpElement(const AtomicOp* op) : m_op(op) {}
|
||||
|
||||
goos::Object AtomicOpElement::to_form(const Env& env) const {
|
||||
return m_op->to_form(env.file->labels, &env);
|
||||
}
|
||||
|
||||
void AtomicOpElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
f(this);
|
||||
}
|
||||
|
||||
void AtomicOpElement::apply_form(const std::function<void(Form*)>&) {}
|
||||
|
||||
void AtomicOpElement::collect_vars(VariableSet& vars) const {
|
||||
m_op->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// ConditionElement
|
||||
/////////////////////////////
|
||||
|
||||
ConditionElement::ConditionElement(IR2_Condition::Kind kind, Form* src0, Form* src1)
|
||||
: m_kind(kind) {
|
||||
m_src[0] = src0;
|
||||
m_src[1] = src1;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (m_src[i]) {
|
||||
m_src[i]->parent_element = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object ConditionElement::to_form(const Env& env) const {
|
||||
std::vector<goos::Object> forms;
|
||||
forms.push_back(pretty_print::to_symbol(get_condition_kind_name(m_kind)));
|
||||
for (int i = 0; i < get_condition_num_args(m_kind); i++) {
|
||||
forms.push_back(m_src[i]->to_form(env));
|
||||
}
|
||||
if (forms.size() > 1) {
|
||||
return pretty_print::build_list(forms);
|
||||
} else {
|
||||
return forms.front();
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
f(this);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (m_src[i]) {
|
||||
m_src[i]->apply(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (m_src[i]) {
|
||||
m_src[i]->apply_form(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionElement::invert() {
|
||||
m_kind = get_condition_opposite(m_kind);
|
||||
}
|
||||
|
||||
void ConditionElement::collect_vars(VariableSet& vars) const {
|
||||
for (auto src : m_src) {
|
||||
if (src) {
|
||||
src->collect_vars(vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// FunctionCallElement
|
||||
/////////////////////////////
|
||||
@ -268,6 +309,10 @@ void FunctionCallElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
|
||||
void FunctionCallElement::apply_form(const std::function<void(Form*)>&) {}
|
||||
|
||||
void FunctionCallElement::collect_vars(VariableSet& vars) const {
|
||||
return m_op->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// BranchElement
|
||||
/////////////////////////////
|
||||
@ -284,6 +329,10 @@ void BranchElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
|
||||
void BranchElement::apply_form(const std::function<void(Form*)>&) {}
|
||||
|
||||
void BranchElement::collect_vars(VariableSet& vars) const {
|
||||
return m_op->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// ReturnElement
|
||||
/////////////////////////////
|
||||
@ -307,6 +356,11 @@ void ReturnElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
dead_code->apply_form(f);
|
||||
}
|
||||
|
||||
void ReturnElement::collect_vars(VariableSet& vars) const {
|
||||
return_code->collect_vars(vars);
|
||||
dead_code->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// BreakElement
|
||||
/////////////////////////////
|
||||
@ -330,6 +384,11 @@ void BreakElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
dead_code->apply_form(f);
|
||||
}
|
||||
|
||||
void BreakElement::collect_vars(VariableSet& vars) const {
|
||||
return_code->collect_vars(vars);
|
||||
dead_code->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// CondWithElseElement
|
||||
/////////////////////////////
|
||||
@ -379,6 +438,14 @@ void CondWithElseElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
else_ir->apply_form(f);
|
||||
}
|
||||
|
||||
void CondWithElseElement::collect_vars(VariableSet& vars) const {
|
||||
for (auto& entry : entries) {
|
||||
entry.condition->collect_vars(vars);
|
||||
entry.body->collect_vars(vars);
|
||||
}
|
||||
else_ir->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// EmptyElement
|
||||
/////////////////////////////
|
||||
@ -393,6 +460,8 @@ void EmptyElement::apply(const std::function<void(FormElement*)>& f) {
|
||||
|
||||
void EmptyElement::apply_form(const std::function<void(Form*)>&) {}
|
||||
|
||||
void EmptyElement::collect_vars(VariableSet&) const {}
|
||||
|
||||
/////////////////////////////
|
||||
// WhileElement
|
||||
/////////////////////////////
|
||||
@ -417,6 +486,11 @@ void WhileElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
condition->apply_form(f);
|
||||
}
|
||||
|
||||
void WhileElement::collect_vars(VariableSet& vars) const {
|
||||
body->collect_vars(vars);
|
||||
condition->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// UntilElement
|
||||
/////////////////////////////
|
||||
@ -441,6 +515,11 @@ void UntilElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
condition->apply_form(f);
|
||||
}
|
||||
|
||||
void UntilElement::collect_vars(VariableSet& vars) const {
|
||||
body->collect_vars(vars);
|
||||
condition->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// ShortCircuitElement
|
||||
/////////////////////////////
|
||||
@ -487,8 +566,15 @@ goos::Object ShortCircuitElement::to_form(const Env& env) const {
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
void ShortCircuitElement::collect_vars(VariableSet& vars) const {
|
||||
vars.insert(final_result); // todo - this might be unused.
|
||||
for (auto& entry : entries) {
|
||||
entry.condition->collect_vars(vars);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// ShortCircuitElement
|
||||
// CondNoElseElement
|
||||
/////////////////////////////
|
||||
|
||||
goos::Object CondNoElseElement::to_form(const Env& env) const {
|
||||
@ -536,6 +622,15 @@ void CondNoElseElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
}
|
||||
}
|
||||
|
||||
void CondNoElseElement::collect_vars(VariableSet& vars) const {
|
||||
for (auto& e : entries) {
|
||||
e.condition->collect_vars(vars);
|
||||
e.body->collect_vars(vars);
|
||||
if (e.false_destination.has_value()) {
|
||||
vars.insert(*e.false_destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
/////////////////////////////
|
||||
// AbsElement
|
||||
/////////////////////////////
|
||||
@ -557,6 +652,10 @@ void AbsElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
source->apply_form(f);
|
||||
}
|
||||
|
||||
void AbsElement::collect_vars(VariableSet& vars) const {
|
||||
source->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// AshElement
|
||||
/////////////////////////////
|
||||
@ -586,6 +685,11 @@ void AshElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
value->apply_form(f);
|
||||
}
|
||||
|
||||
void AshElement::collect_vars(VariableSet& vars) const {
|
||||
shift_amount->collect_vars(vars);
|
||||
value->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// TypeOfElement
|
||||
/////////////////////////////
|
||||
@ -608,6 +712,10 @@ void TypeOfElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
value->apply_form(f);
|
||||
}
|
||||
|
||||
void TypeOfElement::collect_vars(VariableSet& vars) const {
|
||||
value->collect_vars(vars);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// ConditionalMoveFalseElement
|
||||
/////////////////////////////
|
||||
@ -632,4 +740,9 @@ void ConditionalMoveFalseElement::apply(const std::function<void(FormElement*)>&
|
||||
void ConditionalMoveFalseElement::apply_form(const std::function<void(Form*)>& f) {
|
||||
source->apply_form(f);
|
||||
}
|
||||
|
||||
void ConditionalMoveFalseElement::collect_vars(VariableSet& vars) const {
|
||||
vars.insert(dest);
|
||||
source->collect_vars(vars);
|
||||
}
|
||||
} // namespace decompiler
|
||||
|
@ -11,6 +11,7 @@
|
||||
namespace decompiler {
|
||||
class Form;
|
||||
class Env;
|
||||
class IR2_Stack;
|
||||
|
||||
/*!
|
||||
* A "FormElement" represents a single LISP form that's not a begin.
|
||||
@ -25,6 +26,15 @@ class FormElement {
|
||||
virtual void apply(const std::function<void(FormElement*)>& f) = 0;
|
||||
virtual void apply_form(const std::function<void(Form*)>& f) = 0;
|
||||
virtual bool is_sequence_point() const { return true; }
|
||||
virtual void collect_vars(VariableSet& vars) const = 0;
|
||||
|
||||
// // push the result of this operation to the operation stack
|
||||
// // this is used for the forms that aren't last in a multi-form.
|
||||
// virtual void push_to_stack(const Env& env, IR2_Stack& stack) = 0;
|
||||
//
|
||||
// // this is used for the final of a multi-form only.
|
||||
// // using the current expressions on the stack, simplify myself.
|
||||
// virtual FormElement* simplify(const Env& env, FormPool& pool, IR2_Stack& stack) = 0;
|
||||
|
||||
protected:
|
||||
friend class Form;
|
||||
@ -36,12 +46,14 @@ class FormElement {
|
||||
*/
|
||||
class SimpleExpressionElement : public FormElement {
|
||||
public:
|
||||
explicit SimpleExpressionElement(const SimpleExpression& expr);
|
||||
explicit SimpleExpressionElement(SimpleExpression expr);
|
||||
|
||||
goos::Object to_form(const Env& env) const override;
|
||||
void apply(const std::function<void(FormElement*)>& f) override;
|
||||
void apply_form(const std::function<void(Form*)>& f) override;
|
||||
bool is_sequence_point() const override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
const SimpleExpression& expr() const { return m_expr; }
|
||||
|
||||
private:
|
||||
@ -60,6 +72,7 @@ class StoreElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
// todo - we may eventually want to use a different representation for more
|
||||
@ -77,6 +90,7 @@ class LoadSourceElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
int size() const { return m_size; }
|
||||
LoadVarOp::Kind kind() const { return m_kind; }
|
||||
const Form* location() const { return m_addr; }
|
||||
@ -87,12 +101,17 @@ class LoadSourceElement : public FormElement {
|
||||
LoadVarOp::Kind m_kind;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Representing an indivisible thing, like an integer constant variable, etc.
|
||||
* Just a wrapper around SimpleAtom.
|
||||
*/
|
||||
class SimpleAtomElement : public FormElement {
|
||||
public:
|
||||
explicit SimpleAtomElement(const SimpleAtom& var);
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
SimpleAtom m_atom;
|
||||
@ -108,6 +127,8 @@ class SetVarElement : public FormElement {
|
||||
void apply(const std::function<void(FormElement*)>& f) override;
|
||||
void apply_form(const std::function<void(Form*)>& f) override;
|
||||
bool is_sequence_point() const override;
|
||||
void collect_vars(VariableSet& vars) const override;
|
||||
|
||||
const Variable& dst() const { return m_dst; }
|
||||
const Form* src() const { return m_src; }
|
||||
|
||||
@ -123,6 +144,7 @@ class AtomicOpElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
const AtomicOp* m_op;
|
||||
@ -134,6 +156,7 @@ class ConditionElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
void invert();
|
||||
|
||||
private:
|
||||
@ -147,6 +170,7 @@ class FunctionCallElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
|
||||
private:
|
||||
const CallOp* m_op;
|
||||
@ -158,6 +182,7 @@ class BranchElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
const BranchOp* op() const { return m_op; }
|
||||
|
||||
private:
|
||||
@ -173,6 +198,7 @@ class ReturnElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
};
|
||||
|
||||
class BreakElement : public FormElement {
|
||||
@ -184,6 +210,7 @@ class BreakElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
};
|
||||
|
||||
class CondWithElseElement : public FormElement {
|
||||
@ -200,6 +227,7 @@ class CondWithElseElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
};
|
||||
|
||||
class EmptyElement : public FormElement {
|
||||
@ -208,6 +236,7 @@ class EmptyElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
};
|
||||
|
||||
class WhileElement : public FormElement {
|
||||
@ -216,6 +245,7 @@ class WhileElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
Form* condition = nullptr;
|
||||
Form* body = nullptr;
|
||||
bool cleaned = false;
|
||||
@ -227,6 +257,7 @@ class UntilElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
Form* condition = nullptr;
|
||||
Form* body = nullptr;
|
||||
};
|
||||
@ -237,7 +268,7 @@ class ShortCircuitElement : public FormElement {
|
||||
Form* condition = nullptr;
|
||||
// in the case where there's no else, each delay slot will write #f to the "output" register.
|
||||
// this can be with an or <output>, s7, r0
|
||||
Form* output = nullptr;
|
||||
// Form* output = nullptr; // todo, what? add to collect vars if we need it?
|
||||
bool is_output_trick = false;
|
||||
bool cleaned = false;
|
||||
};
|
||||
@ -252,6 +283,7 @@ class ShortCircuitElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
};
|
||||
|
||||
class CondNoElseElement : public FormElement {
|
||||
@ -270,6 +302,7 @@ class CondNoElseElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
};
|
||||
|
||||
class AbsElement : public FormElement {
|
||||
@ -278,6 +311,7 @@ class AbsElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
Form* source = nullptr;
|
||||
};
|
||||
|
||||
@ -291,6 +325,7 @@ class AshElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
};
|
||||
|
||||
class TypeOfElement : public FormElement {
|
||||
@ -301,6 +336,7 @@ class TypeOfElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
};
|
||||
|
||||
class ConditionalMoveFalseElement : public FormElement {
|
||||
@ -312,6 +348,7 @@ class ConditionalMoveFalseElement : public FormElement {
|
||||
goos::Object to_form(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(VariableSet& vars) const override;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -328,7 +365,7 @@ class Form {
|
||||
single_child->parent_form = this;
|
||||
}
|
||||
|
||||
Form(FormElement* parent, const std::vector<FormElement*> sequence)
|
||||
Form(FormElement* parent, const std::vector<FormElement*>& sequence)
|
||||
: parent_element(parent), m_elements(sequence) {
|
||||
for (auto& x : sequence) {
|
||||
x->parent_form = this;
|
||||
@ -370,6 +407,8 @@ class Form {
|
||||
void inline_forms(std::vector<goos::Object>& forms, const Env& env) const;
|
||||
void apply(const std::function<void(FormElement*)>& f);
|
||||
void apply_form(const std::function<void(Form*)>& f);
|
||||
void collect_vars(VariableSet& vars) const;
|
||||
|
||||
FormElement* parent_element = nullptr;
|
||||
|
||||
private:
|
||||
|
@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace decompiler {
|
||||
class IR2 {
|
||||
public:
|
||||
private:
|
||||
};
|
||||
} // namespace decompiler
|
@ -1,9 +1,88 @@
|
||||
#pragma once
|
||||
#include <unordered_set>
|
||||
#include "common/common_types.h"
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
|
||||
namespace decompiler {
|
||||
enum class VariableMode : u8 {
|
||||
READ, // represents value of the variable at the beginning of the instruction
|
||||
WRITE // represents value of the variable at the end of the instruction
|
||||
};
|
||||
}
|
||||
|
||||
/*!
|
||||
* A register plus an integer ID.
|
||||
*/
|
||||
struct RegId {
|
||||
Register reg;
|
||||
int id = -1;
|
||||
struct hash {
|
||||
auto operator()(const RegId& x) const {
|
||||
return Register::hash()(x.reg) ^ std::hash<int>()(x.id);
|
||||
}
|
||||
};
|
||||
|
||||
bool operator==(const RegId& other) const { return reg == other.reg && id == other.id; }
|
||||
bool operator!=(const RegId& other) const { return !((*this) == other); }
|
||||
bool operator<(const RegId& other) const {
|
||||
if (reg == other.reg) {
|
||||
return id < other.id;
|
||||
} else {
|
||||
return reg < other.reg;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Env;
|
||||
/*!
|
||||
* A "Variable" represents a register at a given instruction index.
|
||||
* The register can either be a GOAL local variable or a GOAL register used in inline assembly.
|
||||
* Because OpenGOAL's registers don't one-to-one map to GOAL registers, GOAL "inline assembly
|
||||
* registers" will become OpenGOAL variables, and are treated similarly to variables in
|
||||
* decompilation.
|
||||
*
|
||||
* In the earlier parts of decompilation, this just behaves like a register in all cases.
|
||||
* But in later parts registers can be mapped to real local variables with types. A variable can
|
||||
* look itself up in an environment to determine what "local variable" it is.
|
||||
*
|
||||
* Note: a variable is _not_ allowed to be R0, AT, S7, K0, K1, FP, or RA by default, as these
|
||||
* can never hold normal GOAL locals. Inline assembly may use these, but you must set the allow_all
|
||||
* flag to true in the constructor of Variable to indicate this is what you really want.
|
||||
*
|
||||
* Note: access to the process pointer (s6) is handled as a variable. As a result, you may always
|
||||
* use s6 as a variable.
|
||||
*/
|
||||
class Variable {
|
||||
public:
|
||||
Variable() = default;
|
||||
Variable(VariableMode mode, Register reg, int atomic_idx, bool allow_all = false);
|
||||
|
||||
enum class Print {
|
||||
AS_REG, // print as a PS2 register name
|
||||
FULL, // print as a register name, plus an index, plus read or write
|
||||
AS_VARIABLE, // print local variable name, error if impossible
|
||||
AUTOMATIC, // print as variable, but if that's not possible print as reg.
|
||||
};
|
||||
|
||||
std::string to_string(const Env* env, Print mode = Print::AUTOMATIC) const;
|
||||
|
||||
bool operator==(const Variable& other) const;
|
||||
bool operator!=(const Variable& other) const;
|
||||
|
||||
const Register& reg() const { return m_reg; }
|
||||
VariableMode mode() const { return m_mode; }
|
||||
int idx() const { return m_atomic_idx; }
|
||||
|
||||
struct hash {
|
||||
auto operator()(const Variable& x) const {
|
||||
return (Register::hash()(x.m_reg) << 2) ^ (int(x.m_mode) << 1) ^ x.m_atomic_idx;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
VariableMode m_mode = VariableMode::READ; // do we represent a read or a write?
|
||||
Register m_reg; // the EE register
|
||||
int m_atomic_idx = -1; // the index in the function's list of AtomicOps
|
||||
};
|
||||
|
||||
using VariableSet = std::unordered_set<Variable, Variable::hash>;
|
||||
} // namespace decompiler
|
||||
|
@ -72,7 +72,7 @@ void clean_up_cond_with_else(FormPool& pool, FormElement* ir) {
|
||||
assert(jump_to_next.first);
|
||||
assert(jump_to_next.first->op()->branch_delay().kind() == IR2_BranchDelay::Kind::NOP);
|
||||
// patch the branch to next with a condition.
|
||||
auto replacement = jump_to_next.first->op()->condition().get_as_form(pool);
|
||||
auto replacement = jump_to_next.first->op()->get_condition_as_form(pool);
|
||||
replacement->invert();
|
||||
*(jump_to_next.second) = replacement;
|
||||
|
||||
@ -106,7 +106,7 @@ void clean_up_until_loop(FormPool& pool, UntilElement* ir) {
|
||||
auto condition_branch = get_condition_branch(ir->condition);
|
||||
assert(condition_branch.first);
|
||||
assert(condition_branch.first->op()->branch_delay().kind() == IR2_BranchDelay::Kind::NOP);
|
||||
auto replacement = condition_branch.first->op()->condition().get_as_form(pool);
|
||||
auto replacement = condition_branch.first->op()->get_condition_as_form(pool);
|
||||
replacement->invert();
|
||||
*(condition_branch.second) = replacement;
|
||||
}
|
||||
@ -244,8 +244,8 @@ bool try_clean_up_sc_as_and(FormPool& pool, const Function& func, ShortCircuitEl
|
||||
auto branch = get_condition_branch(ir->entries.at(i).condition);
|
||||
assert(branch.first);
|
||||
|
||||
if (func.ir2.has_reg_use) {
|
||||
auto& branch_info = func.ir2.reg_use.op.at(branch.first->op()->op_id());
|
||||
if (func.ir2.env.has_reg_use()) {
|
||||
auto& branch_info = func.ir2.env.reg_use().op.at(branch.first->op()->op_id());
|
||||
|
||||
if (i == 0) {
|
||||
live_out_result = (branch_info.written_and_unused.find(ir_dest.reg()) ==
|
||||
@ -261,7 +261,7 @@ bool try_clean_up_sc_as_and(FormPool& pool, const Function& func, ShortCircuitEl
|
||||
}
|
||||
}
|
||||
|
||||
auto replacement = branch.first->op()->condition().get_as_form(pool);
|
||||
auto replacement = branch.first->op()->get_condition_as_form(pool);
|
||||
replacement->invert();
|
||||
*(branch.second) = replacement;
|
||||
}
|
||||
@ -304,8 +304,8 @@ bool try_clean_up_sc_as_or(FormPool& pool, const Function& func, ShortCircuitEle
|
||||
auto branch = get_condition_branch(ir->entries.at(i).condition);
|
||||
assert(branch.first);
|
||||
|
||||
if (func.ir2.has_reg_use) {
|
||||
auto& branch_info = func.ir2.reg_use.op.at(branch.first->op()->op_id());
|
||||
if (func.ir2.env.has_reg_use()) {
|
||||
auto& branch_info = func.ir2.env.reg_use().op.at(branch.first->op()->op_id());
|
||||
|
||||
if (i == 0) {
|
||||
live_out_result = (branch_info.written_and_unused.find(ir_dest.reg()) ==
|
||||
@ -317,7 +317,7 @@ bool try_clean_up_sc_as_or(FormPool& pool, const Function& func, ShortCircuitEle
|
||||
}
|
||||
}
|
||||
|
||||
auto replacement = branch.first->op()->condition().get_as_form(pool);
|
||||
auto replacement = branch.first->op()->get_condition_as_form(pool);
|
||||
*(branch.second) = replacement;
|
||||
}
|
||||
|
||||
@ -442,7 +442,7 @@ void convert_cond_no_else_to_compare(FormPool& pool,
|
||||
|
||||
auto condition_as_single =
|
||||
dynamic_cast<BranchElement*>(cne->entries.front().condition->try_as_single_element());
|
||||
auto condition_replacement = condition.first->op()->condition().get_as_form(pool);
|
||||
auto condition_replacement = condition.first->op()->get_condition_as_form(pool);
|
||||
auto crf = pool.alloc_single_form(nullptr, condition_replacement);
|
||||
auto replacement = pool.alloc_element<SetVarElement>(dst, crf, true);
|
||||
replacement->parent_form = cne->parent_form;
|
||||
@ -490,17 +490,17 @@ void clean_up_cond_no_else_final(const Function& func, CondNoElseElement* cne) {
|
||||
auto last_branch = dynamic_cast<BranchElement*>(cne->entries.back().original_condition_branch);
|
||||
assert(last_branch);
|
||||
|
||||
if (func.ir2.has_reg_use) {
|
||||
auto& last_branch_info = func.ir2.reg_use.op.at(last_branch->op()->op_id());
|
||||
if (func.ir2.env.has_reg_use()) {
|
||||
auto& last_branch_info = func.ir2.env.reg_use().op.at(last_branch->op()->op_id());
|
||||
cne->used_as_value = last_branch_info.written_and_unused.find(cne->final_destination) ==
|
||||
last_branch_info.written_and_unused.end();
|
||||
}
|
||||
|
||||
// check that all other delay slot writes are unused.
|
||||
for (size_t i = 0; i < cne->entries.size() - 1; i++) {
|
||||
if (func.ir2.has_reg_use) {
|
||||
if (func.ir2.env.has_reg_use()) {
|
||||
auto branch = dynamic_cast<BranchElement*>(cne->entries.at(i).original_condition_branch);
|
||||
auto& branch_info_i = func.ir2.reg_use.op.at(branch->op()->op_id());
|
||||
auto& branch_info_i = func.ir2.env.reg_use().op.at(branch->op()->op_id());
|
||||
auto reg = cne->entries.at(i).false_destination;
|
||||
assert(reg.has_value());
|
||||
assert(branch);
|
||||
@ -552,7 +552,7 @@ void clean_up_cond_no_else(FormPool& pool,
|
||||
|
||||
e.original_condition_branch = *jump_to_next.second;
|
||||
|
||||
auto replacement = jump_to_next.first->op()->condition().get_as_form(pool);
|
||||
auto replacement = jump_to_next.first->op()->get_condition_as_form(pool);
|
||||
replacement->invert();
|
||||
*(jump_to_next.second) = replacement;
|
||||
e.cleaned = true;
|
||||
@ -1181,7 +1181,7 @@ void clean_up_while_loops(FormPool& pool, Form* sequence) {
|
||||
assert(condition_branch.first);
|
||||
assert(condition_branch.first->op()->branch_delay().kind() == IR2_BranchDelay::Kind::NOP);
|
||||
// printf("got while condition branch %s\n", condition_branch.first->print(file).c_str());
|
||||
auto replacement = condition_branch.first->op()->condition().get_as_form(pool);
|
||||
auto replacement = condition_branch.first->op()->get_condition_as_form(pool);
|
||||
|
||||
*(condition_branch.second) = replacement;
|
||||
}
|
||||
|
@ -465,13 +465,13 @@ void update_var_info(VariableNames::VarInfo* info,
|
||||
int var_id,
|
||||
const DecompilerTypeSystem& dts) {
|
||||
if (info->initialized) {
|
||||
assert(info->id == var_id);
|
||||
assert(info->reg == reg);
|
||||
assert(info->reg_id.id == var_id);
|
||||
assert(info->reg_id.reg == reg);
|
||||
bool changed;
|
||||
info->type = dts.tp_lca(info->type, ts.get(reg), &changed);
|
||||
} else {
|
||||
info->id = var_id;
|
||||
info->reg = reg;
|
||||
info->reg_id.id = var_id;
|
||||
info->reg_id.reg = reg;
|
||||
info->type = ts.get(reg);
|
||||
info->initialized = true;
|
||||
}
|
||||
|
@ -270,7 +270,6 @@ void ObjectFileDB::ir2_type_analysis_pass() {
|
||||
auto hints = get_config().type_hints_by_function_by_idx[func.guessed_name.to_string()];
|
||||
if (func.run_type_analysis_ir2(ts, dts, data.linked_data, hints)) {
|
||||
successful_functions++;
|
||||
func.ir2.has_type_info = true;
|
||||
} else {
|
||||
func.warnings.append(";; Type analysis failed\n");
|
||||
}
|
||||
@ -295,8 +294,7 @@ void ObjectFileDB::ir2_register_usage_pass() {
|
||||
total_funcs++;
|
||||
if (!func.suspected_asm && func.ir2.atomic_ops_succeeded) {
|
||||
analyzed_funcs++;
|
||||
func.ir2.reg_use = analyze_ir2_register_usage(func);
|
||||
func.ir2.has_reg_use = true;
|
||||
func.ir2.env.set_reg_use(analyze_ir2_register_usage(func));
|
||||
}
|
||||
});
|
||||
|
||||
@ -314,7 +312,8 @@ void ObjectFileDB::ir2_variable_pass() {
|
||||
if (!func.suspected_asm && func.ir2.atomic_ops_succeeded && func.ir2.env.has_type_analysis()) {
|
||||
try {
|
||||
attempted++;
|
||||
auto result = run_variable_renaming(func, func.ir2.reg_use, *func.ir2.atomic_ops, dts);
|
||||
auto result =
|
||||
run_variable_renaming(func, func.ir2.env.reg_use(), *func.ir2.atomic_ops, dts);
|
||||
if (result.has_value()) {
|
||||
successful++;
|
||||
func.ir2.env.set_local_vars(*result);
|
||||
@ -462,7 +461,7 @@ std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function&
|
||||
}
|
||||
|
||||
if (func.ir2.env.has_local_vars()) {
|
||||
result += func.ir2.env.print_local_var_types();
|
||||
result += func.ir2.env.print_local_var_types(func.ir2.top_form);
|
||||
}
|
||||
|
||||
bool print_atomics = func.ir2.atomic_ops_succeeded;
|
||||
@ -544,9 +543,9 @@ std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function&
|
||||
op.reg_type_info_as_string(*init_types, func.ir2.env.get_types_after_op(op_id)), 50);
|
||||
}
|
||||
|
||||
if (func.ir2.has_reg_use) {
|
||||
if (func.ir2.env.has_reg_use()) {
|
||||
std::string regs;
|
||||
for (auto r : func.ir2.reg_use.op.at(op_id).consumes) {
|
||||
for (auto r : func.ir2.env.reg_use().op.at(op_id).consumes) {
|
||||
regs += r.to_charp();
|
||||
regs += ' ';
|
||||
}
|
||||
|
@ -107,17 +107,12 @@ class DecompilerRegressionTest : public ::testing::Test {
|
||||
test->func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops));
|
||||
test->func.ir2.atomic_ops_succeeded = true;
|
||||
|
||||
if (test->func.run_type_analysis_ir2(function_type, *dts, test->file, {})) {
|
||||
test->func.ir2.has_type_info = true;
|
||||
} else {
|
||||
EXPECT_TRUE(false);
|
||||
}
|
||||
EXPECT_TRUE(test->func.run_type_analysis_ir2(function_type, *dts, test->file, {}));
|
||||
|
||||
test->func.ir2.reg_use = analyze_ir2_register_usage(test->func);
|
||||
test->func.ir2.has_reg_use = true;
|
||||
test->func.ir2.env.set_reg_use(analyze_ir2_register_usage(test->func));
|
||||
|
||||
auto result =
|
||||
run_variable_renaming(test->func, test->func.ir2.reg_use, *test->func.ir2.atomic_ops, *dts);
|
||||
auto result = run_variable_renaming(test->func, test->func.ir2.env.reg_use(),
|
||||
*test->func.ir2.atomic_ops, *dts);
|
||||
if (result.has_value()) {
|
||||
test->func.ir2.env.set_local_vars(*result);
|
||||
} else {
|
||||
@ -127,6 +122,10 @@ class DecompilerRegressionTest : public ::testing::Test {
|
||||
build_initial_forms(test->func);
|
||||
EXPECT_TRUE(test->func.ir2.top_form);
|
||||
|
||||
// for now, just test that this can at least be called.
|
||||
VariableSet vars;
|
||||
test->func.ir2.top_form->collect_vars(vars);
|
||||
|
||||
return test;
|
||||
}
|
||||
|
||||
@ -334,7 +333,7 @@ TEST_F(DecompilerRegressionTest, FormatString) {
|
||||
" (set! a1-0 L343)\n"
|
||||
" (set! f0-0 (l.f gp-0))\n"
|
||||
" (set! a2-0 (fpr->gpr f0-0))\n"
|
||||
" (set! v0-0 (call!))\n"
|
||||
" (set! v0-0 (call! a0-1 a1-0 a2-0))\n" // #t, "~f", the float
|
||||
" (set! v0-1 gp-0)\n"
|
||||
" )";
|
||||
test(func, type, expected, false, "", {{"L343", "~f"}});
|
||||
@ -702,7 +701,7 @@ TEST_F(DecompilerRegressionTest, FunctionCall) {
|
||||
" (set! t9-0 name=)\n"
|
||||
" (set! a0-2 (l.w (+ gp-0 -2)))\n"
|
||||
" (set! a1-1 s5-0)\n"
|
||||
" (set! v0-0 (call!))\n"
|
||||
" (set! v0-0 (call! a0-2 a1-1))\n"
|
||||
" (set! v1-1 v0-0)\n" // name match
|
||||
" )\n"
|
||||
" )\n"
|
||||
@ -861,7 +860,7 @@ TEST_F(DecompilerRegressionTest, NestedAndOr) {
|
||||
" (set! t9-0 s5-0)\n" // func
|
||||
" (set! a0-1 s2-0)\n" // car
|
||||
" (set! a1-1 s1-0)\n" // cadr
|
||||
" (set! v0-0 (call!))\n" // compare!
|
||||
" (set! v0-0 (call! a0-1 a1-1))\n" // compare!
|
||||
" (set! v1-1 v0-0)\n"
|
||||
" (not v1-1)\n" // result is false (secretly sets a0-2)
|
||||
" )\n"
|
||||
@ -982,7 +981,7 @@ TEST_F(DecompilerRegressionTest, Recursive) {
|
||||
" (else\n"
|
||||
" (set! t9-0 fact)\n" // recurse!
|
||||
" (set! a0-1 (+ gp-0 -1))\n"
|
||||
" (set! v0-1 (call!))\n"
|
||||
" (set! v0-1 (call! a0-1))\n"
|
||||
" (set! v0-2 (*.si gp-0 v0-1))\n" // not quite a tail call...
|
||||
" )\n"
|
||||
" )";
|
||||
@ -1019,7 +1018,7 @@ TEST_F(DecompilerRegressionTest, TypeOf) {
|
||||
"(begin\n"
|
||||
" (set! v1-1 (type-of a0-0))\n"
|
||||
" (set! t9-0 (l.wu (+ v1-1 24)))\n" // print method.
|
||||
" (set! v0-0 (call!))\n"
|
||||
" (set! v0-0 (call! a0-0))\n"
|
||||
" )";
|
||||
test(func, type, expected, false);
|
||||
}
|
Loading…
Reference in New Issue
Block a user