mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-27 00:10:31 +00:00
[Decompiler] Write IR2 to file and implement some Atomic Op conversions (#187)
This commit is contained in:
parent
5093b97cda
commit
2901f4a99e
@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug",
|
||||
@ -9,7 +9,7 @@
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"addressSanitizerEnabled": true,
|
||||
"addressSanitizerEnabled": false,
|
||||
"ctestCommandArgs": "",
|
||||
"variables": [
|
||||
{
|
||||
|
@ -35,6 +35,7 @@ add_library(
|
||||
ObjectFile/LinkedObjectFile.cpp
|
||||
ObjectFile/LinkedObjectFileCreation.cpp
|
||||
ObjectFile/ObjectFileDB.cpp
|
||||
ObjectFile/ObjectFileDB_IR2.cpp
|
||||
|
||||
util/DecompilerTypeSystem.cpp
|
||||
util/TP_Type.cpp
|
||||
|
@ -52,6 +52,8 @@ struct InstructionAtom {
|
||||
bool is_sym() const { return kind == IMM_SYM; }
|
||||
|
||||
bool is_reg(Register r) const { return kind == REGISTER && reg == r; }
|
||||
bool is_imm(int32_t i) const { return kind == IMM && imm == i; }
|
||||
bool is_sym(const std::string& name) const { return kind == IMM_SYM && name == sym; }
|
||||
|
||||
bool operator==(const InstructionAtom& other) const;
|
||||
bool operator!=(const InstructionAtom& other) const { return !((*this) == other); }
|
||||
|
@ -41,7 +41,8 @@ InstructionParser::InstructionParser() {
|
||||
InstructionKind::BNEL, InstructionKind::BC1FL, InstructionKind::BC1TL,
|
||||
InstructionKind::BLTZ, InstructionKind::BGEZ, InstructionKind::BLEZ,
|
||||
InstructionKind::BGTZ, InstructionKind::BLTZL, InstructionKind::BGTZL,
|
||||
InstructionKind::BGEZL}) {
|
||||
InstructionKind::BGEZL, InstructionKind::MTC1, InstructionKind::MFC1,
|
||||
InstructionKind::MFLO, InstructionKind::MFHI}) {
|
||||
auto& info = gOpcodeInfo[int(i)];
|
||||
if (info.defined) {
|
||||
m_opcode_name_lookup[info.name] = int(i);
|
||||
@ -206,6 +207,18 @@ Instruction InstructionParser::parse_single_instruction(
|
||||
if (is_integer(atom_str)) {
|
||||
auto amt = parse_integer(atom_str);
|
||||
atom.set_imm(amt);
|
||||
} else if (!atom_str.empty() && atom_str.front() == 'L') {
|
||||
bool found_label = false;
|
||||
for (size_t id = 0; id < labels.size(); id++) {
|
||||
if (labels[id].name == atom_str) {
|
||||
found_label = true;
|
||||
atom.set_label(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_label) {
|
||||
atom.set_sym(atom_str);
|
||||
}
|
||||
} else {
|
||||
atom.set_sym(atom_str);
|
||||
}
|
||||
|
@ -243,7 +243,7 @@ void init_opcode_info() {
|
||||
|
||||
// weird moves
|
||||
def(IK::MFC1, "mfc1").dst_gpr(FT::RT).src_fpr(FT::FS); // Move Word from Floating Point
|
||||
def(IK::MTC1, "mtc1").src_gpr(FT::RT).dst_fpr(FT::FS); // Move Word to Floating Point
|
||||
def(IK::MTC1, "mtc1").dst_fpr(FT::FS).src_gpr(FT::RT); // Move Word to Floating Point
|
||||
def(IK::MTC0, "mtc0")
|
||||
.src_gpr(FT::RT)
|
||||
.dst(FT::RD, DT::COP0); // Move to System Control Coprocessor
|
||||
|
@ -684,6 +684,19 @@ std::shared_ptr<IR_Atomic> Function::get_basic_op_at_instr(int idx) {
|
||||
return basic_ops.at(instruction_to_basic_op.at(idx));
|
||||
}
|
||||
|
||||
bool Function::instr_starts_atomic_op(int idx) {
|
||||
auto op = ir2.atomic_ops->instruction_to_atomic_op.find(idx);
|
||||
if (op != ir2.atomic_ops->instruction_to_atomic_op.end()) {
|
||||
auto start_instr = ir2.atomic_ops->atomic_op_to_instruction.at(op->second);
|
||||
return start_instr == idx;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const AtomicOp& Function::get_atomic_op_at_instr(int idx) {
|
||||
return *ir2.atomic_ops->ops.at(ir2.atomic_ops->instruction_to_atomic_op.at(idx));
|
||||
}
|
||||
|
||||
int Function::get_basic_op_count() {
|
||||
return basic_ops.size();
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
#include "decompiler/IR2/AtomicOpBuilder.h"
|
||||
#include "decompiler/Disasm/Instruction.h"
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "BasicBlocks.h"
|
||||
@ -83,6 +84,8 @@ class Function {
|
||||
bool has_basic_ops() { return !basic_ops.empty(); }
|
||||
bool instr_starts_basic_op(int idx);
|
||||
std::shared_ptr<IR_Atomic> get_basic_op_at_instr(int idx);
|
||||
bool instr_starts_atomic_op(int idx);
|
||||
const AtomicOp& get_atomic_op_at_instr(int idx);
|
||||
int get_basic_op_count();
|
||||
int get_failed_basic_op_count();
|
||||
int get_reginfo_basic_op_count();
|
||||
@ -154,6 +157,13 @@ class Function {
|
||||
bool uses_fp_register = false;
|
||||
std::vector<std::shared_ptr<IR_Atomic>> basic_ops;
|
||||
|
||||
struct {
|
||||
bool atomic_ops_attempted = false;
|
||||
bool atomic_ops_succeeded = false;
|
||||
std::shared_ptr<FunctionAtomicOps> atomic_ops = nullptr;
|
||||
Env env;
|
||||
} ir2;
|
||||
|
||||
private:
|
||||
void check_epilogue(const LinkedObjectFile& file);
|
||||
std::unordered_map<int, int> instruction_to_basic_op;
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include <stdexcept>
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "common/goos/PrettyPrinter.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
@ -14,7 +15,10 @@ Variable::Variable(Mode mode, Register reg, int atomic_idx, bool allow_all)
|
||||
: m_mode(mode), m_reg(reg), m_atomic_idx(atomic_idx) {
|
||||
// make sure we're using a valid GPR.
|
||||
if (reg.get_kind() == Reg::GPR && !allow_all) {
|
||||
assert(Reg::allowed_local_gprs[reg.get_gpr()] || reg.get_gpr() == Reg::S6);
|
||||
if (!(Reg::allowed_local_gprs[reg.get_gpr()] || reg.get_gpr() == Reg::S6)) {
|
||||
throw std::runtime_error("Variable could not be constructed from register " +
|
||||
reg.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +55,7 @@ bool Variable::operator!=(const Variable& other) const {
|
||||
/////////////////////////////
|
||||
AtomicOp::AtomicOp(int my_idx) : m_my_idx(my_idx) {}
|
||||
|
||||
std::string AtomicOp::to_string(const std::vector<DecompilerLabel>& labels, const Env* env) {
|
||||
std::string AtomicOp::to_string(const std::vector<DecompilerLabel>& labels, const Env* env) const {
|
||||
return pretty_print::to_string(to_form(labels, env));
|
||||
}
|
||||
bool AtomicOp::operator!=(const AtomicOp& other) const {
|
||||
@ -91,10 +95,18 @@ SimpleAtom SimpleAtom::make_empty_list() {
|
||||
|
||||
SimpleAtom SimpleAtom::make_int_constant(s64 value) {
|
||||
SimpleAtom result;
|
||||
result.m_kind = Kind::INTEGER_CONSTANT;
|
||||
result.m_int = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
SimpleAtom SimpleAtom::make_static_address(int static_label_id) {
|
||||
SimpleAtom result;
|
||||
result.m_kind = Kind::STATIC_ADDRESS;
|
||||
result.m_int = static_label_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
goos::Object SimpleAtom::to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const {
|
||||
switch (m_kind) {
|
||||
case Kind::VARIABLE:
|
||||
@ -105,6 +117,8 @@ goos::Object SimpleAtom::to_form(const std::vector<DecompilerLabel>& labels, con
|
||||
return pretty_print::to_symbol(fmt::format("'{}", m_string));
|
||||
case Kind::SYMBOL_VAL:
|
||||
return pretty_print::to_symbol(m_string);
|
||||
case Kind::EMPTY_LIST:
|
||||
return pretty_print::to_symbol("'()");
|
||||
case Kind::STATIC_ADDRESS:
|
||||
return pretty_print::to_symbol(labels.at(m_int).name);
|
||||
default:
|
||||
@ -142,6 +156,10 @@ void SimpleAtom::get_regs(std::vector<Register>* out) const {
|
||||
}
|
||||
}
|
||||
|
||||
SimpleExpression SimpleAtom::as_expr() const {
|
||||
return SimpleExpression(SimpleExpression::Kind::IDENTITY, *this);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// SimpleExpression
|
||||
/////////////////////////////
|
||||
@ -201,10 +219,22 @@ std::string get_simple_expression_op_name(SimpleExpression::Kind kind) {
|
||||
return "srl";
|
||||
case SimpleExpression::Kind::MUL_UNSIGNED:
|
||||
return "*.ui";
|
||||
case SimpleExpression::Kind::NOT:
|
||||
case SimpleExpression::Kind::LOGNOT:
|
||||
return "lognot";
|
||||
case SimpleExpression::Kind::NEG:
|
||||
return "-";
|
||||
case SimpleExpression::Kind::GPR_TO_FPR:
|
||||
return "gpr->fpr";
|
||||
case SimpleExpression::Kind::FPR_TO_GPR:
|
||||
return "fpr->gpr";
|
||||
case SimpleExpression::Kind::MIN_SIGNED:
|
||||
return "min.si";
|
||||
case SimpleExpression::Kind::MIN_UNSIGNED:
|
||||
return "min.ui";
|
||||
case SimpleExpression::Kind::MAX_SIGNED:
|
||||
return "max.si";
|
||||
case SimpleExpression::Kind::MAX_UNSIGNED:
|
||||
return "max.ui";
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
@ -243,9 +273,16 @@ int get_simple_expression_arg_count(SimpleExpression::Kind kind) {
|
||||
case SimpleExpression::Kind::RIGHT_SHIFT_LOGIC:
|
||||
case SimpleExpression::Kind::MUL_UNSIGNED:
|
||||
return 2;
|
||||
case SimpleExpression::Kind::NOT:
|
||||
case SimpleExpression::Kind::LOGNOT:
|
||||
case SimpleExpression::Kind::NEG:
|
||||
case SimpleExpression::Kind::GPR_TO_FPR:
|
||||
case SimpleExpression::Kind::FPR_TO_GPR:
|
||||
return 1;
|
||||
case SimpleExpression::Kind::MIN_SIGNED:
|
||||
case SimpleExpression::Kind::MIN_UNSIGNED:
|
||||
case SimpleExpression::Kind::MAX_SIGNED:
|
||||
case SimpleExpression::Kind::MAX_UNSIGNED:
|
||||
return 2;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
@ -397,7 +434,7 @@ goos::Object AsmOp::to_form(const std::vector<DecompilerLabel>& labels, const En
|
||||
if (m_src[i].has_value()) {
|
||||
forms.push_back(pretty_print::to_symbol(m_src[i].value().to_string(env)));
|
||||
} else {
|
||||
forms.push_back(pretty_print::to_symbol(m_instr.get_src(1).to_string(labels)));
|
||||
forms.push_back(pretty_print::to_symbol(m_instr.get_src(i).to_string(labels)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -501,12 +538,24 @@ std::string get_condition_kind_name(IR2_Condition::Kind kind) {
|
||||
return "<=.s";
|
||||
case IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED:
|
||||
return ">0.si";
|
||||
case IR2_Condition::Kind::GREATER_THAN_ZERO_UNSIGNED:
|
||||
return ">0.ui";
|
||||
case IR2_Condition::Kind::GEQ_ZERO_SIGNED:
|
||||
return ">=0.si";
|
||||
case IR2_Condition::Kind::LESS_THAN_ZERO:
|
||||
case IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED:
|
||||
return "<0.si";
|
||||
case IR2_Condition::Kind::LEQ_ZERO_SIGNED:
|
||||
return "<=0.si";
|
||||
case IR2_Condition::Kind::LEQ_ZERO_UNSIGNED:
|
||||
return "<=0.ui";
|
||||
case IR2_Condition::Kind::IS_PAIR:
|
||||
return "pair?";
|
||||
case IR2_Condition::Kind::IS_NOT_PAIR:
|
||||
return "not-pair?";
|
||||
case IR2_Condition::Kind::LESS_THAN_ZERO_UNSIGNED:
|
||||
return "<0.ui";
|
||||
case IR2_Condition::Kind::GEQ_ZERO_UNSIGNED:
|
||||
return ">=0.ui";
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
@ -537,8 +586,14 @@ int get_condition_num_args(IR2_Condition::Kind kind) {
|
||||
case IR2_Condition::Kind::TRUTHY:
|
||||
case IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED:
|
||||
case IR2_Condition::Kind::GEQ_ZERO_SIGNED:
|
||||
case IR2_Condition::Kind::LESS_THAN_ZERO:
|
||||
case IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED:
|
||||
case IR2_Condition::Kind::LEQ_ZERO_SIGNED:
|
||||
case IR2_Condition::Kind::IS_PAIR:
|
||||
case IR2_Condition::Kind::IS_NOT_PAIR:
|
||||
case IR2_Condition::Kind::LEQ_ZERO_UNSIGNED:
|
||||
case IR2_Condition::Kind::GREATER_THAN_ZERO_UNSIGNED:
|
||||
case IR2_Condition::Kind::LESS_THAN_ZERO_UNSIGNED:
|
||||
case IR2_Condition::Kind::GEQ_ZERO_UNSIGNED:
|
||||
return 1;
|
||||
case IR2_Condition::Kind::ALWAYS:
|
||||
case IR2_Condition::Kind::NEVER:
|
||||
@ -566,10 +621,10 @@ IR2_Condition::Kind get_condition_opposite(IR2_Condition::Kind kind) {
|
||||
return IR2_Condition::Kind::LEQ_ZERO_SIGNED;
|
||||
case IR2_Condition::Kind::LEQ_ZERO_SIGNED:
|
||||
return IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED;
|
||||
case IR2_Condition::Kind::LESS_THAN_ZERO:
|
||||
case IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED:
|
||||
return IR2_Condition::Kind::GEQ_ZERO_SIGNED;
|
||||
case IR2_Condition::Kind::GEQ_ZERO_SIGNED:
|
||||
return IR2_Condition::Kind::LESS_THAN_ZERO;
|
||||
return IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED;
|
||||
case IR2_Condition::Kind::LESS_THAN_UNSIGNED:
|
||||
return IR2_Condition::Kind::GEQ_UNSIGNED;
|
||||
case IR2_Condition::Kind::GREATER_THAN_UNSIGNED:
|
||||
@ -602,6 +657,18 @@ IR2_Condition::Kind get_condition_opposite(IR2_Condition::Kind kind) {
|
||||
return IR2_Condition::Kind::FLOAT_LEQ;
|
||||
case IR2_Condition::Kind::FLOAT_LEQ:
|
||||
return IR2_Condition::Kind::FLOAT_GREATER_THAN;
|
||||
case IR2_Condition::Kind::IS_NOT_PAIR:
|
||||
return IR2_Condition::Kind::IS_PAIR;
|
||||
case IR2_Condition::Kind::IS_PAIR:
|
||||
return IR2_Condition::Kind::IS_NOT_PAIR;
|
||||
case IR2_Condition::Kind::LEQ_ZERO_UNSIGNED:
|
||||
return IR2_Condition::Kind::GREATER_THAN_ZERO_UNSIGNED;
|
||||
case IR2_Condition::Kind::GREATER_THAN_ZERO_UNSIGNED:
|
||||
return IR2_Condition::Kind::LEQ_ZERO_UNSIGNED;
|
||||
case IR2_Condition::Kind::LESS_THAN_ZERO_UNSIGNED:
|
||||
return IR2_Condition::Kind::GEQ_ZERO_UNSIGNED;
|
||||
case IR2_Condition::Kind::GEQ_ZERO_UNSIGNED:
|
||||
return IR2_Condition::Kind::LESS_THAN_ZERO_UNSIGNED;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
@ -612,12 +679,13 @@ IR2_Condition::IR2_Condition(Kind kind) : m_kind(kind) {
|
||||
assert(get_condition_num_args(m_kind) == 0);
|
||||
}
|
||||
|
||||
IR2_Condition::IR2_Condition(Kind kind, const Variable& src0) : m_kind(kind) {
|
||||
IR2_Condition::IR2_Condition(Kind kind, const SimpleAtom& src0) : m_kind(kind) {
|
||||
m_src[0] = src0;
|
||||
assert(get_condition_num_args(m_kind) == 1);
|
||||
}
|
||||
|
||||
IR2_Condition::IR2_Condition(Kind kind, const Variable& src0, const Variable& src1) : m_kind(kind) {
|
||||
IR2_Condition::IR2_Condition(Kind kind, const SimpleAtom& src0, const SimpleAtom& src1)
|
||||
: m_kind(kind) {
|
||||
m_src[0] = src0;
|
||||
m_src[1] = src1;
|
||||
assert(get_condition_num_args(m_kind) == 2);
|
||||
@ -646,14 +714,18 @@ goos::Object IR2_Condition::to_form(const std::vector<DecompilerLabel>& labels,
|
||||
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(pretty_print::to_symbol(m_src[i].to_string(env)));
|
||||
forms.push_back(m_src[i].to_form(labels, env));
|
||||
}
|
||||
if (forms.size() > 1) {
|
||||
return pretty_print::build_list(forms);
|
||||
} else {
|
||||
return forms.front();
|
||||
}
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
void IR2_Condition::get_regs(std::vector<Register>* out) const {
|
||||
for (int i = 0; i < get_condition_num_args(m_kind); i++) {
|
||||
out->push_back(m_src[i].reg());
|
||||
m_src[i].get_regs(out);
|
||||
}
|
||||
}
|
||||
|
||||
@ -710,11 +782,38 @@ void SetVarConditionOp::update_register_info() {
|
||||
// StoreOp
|
||||
/////////////////////////////
|
||||
|
||||
StoreOp::StoreOp(SimpleExpression addr, SimpleAtom value, int my_idx)
|
||||
: AtomicOp(my_idx), m_addr(std::move(addr)), m_value(std::move(value)) {}
|
||||
StoreOp::StoreOp(int size, bool is_float, SimpleExpression addr, SimpleAtom value, int my_idx)
|
||||
: AtomicOp(my_idx),
|
||||
m_size(size),
|
||||
m_is_float(is_float),
|
||||
m_addr(std::move(addr)),
|
||||
m_value(std::move(value)) {}
|
||||
|
||||
goos::Object StoreOp::to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const {
|
||||
return pretty_print::build_list(pretty_print::to_symbol("store!"), m_addr.to_form(labels, env),
|
||||
std::string store_name;
|
||||
if (m_is_float) {
|
||||
assert(m_size == 4);
|
||||
store_name = "s.f!";
|
||||
} else {
|
||||
switch (m_size) {
|
||||
case 1:
|
||||
store_name = "s.b!";
|
||||
break;
|
||||
case 2:
|
||||
store_name = "s.h!";
|
||||
break;
|
||||
case 4:
|
||||
store_name = "s.w!";
|
||||
break;
|
||||
case 8:
|
||||
store_name = "s.d!";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
return pretty_print::build_list(pretty_print::to_symbol(store_name), m_addr.to_form(labels, env),
|
||||
m_value.to_form(labels, env));
|
||||
}
|
||||
|
||||
@ -758,13 +857,55 @@ void StoreOp::update_register_info() {
|
||||
// LoadVarOp
|
||||
/////////////////////////////
|
||||
|
||||
LoadVarOp::LoadVarOp(Variable dst, SimpleExpression src, int my_idx)
|
||||
: AtomicOp(my_idx), m_dst(dst), m_src(std::move(src)) {}
|
||||
LoadVarOp::LoadVarOp(Kind kind, int size, Variable dst, SimpleExpression src, int my_idx)
|
||||
: AtomicOp(my_idx), m_kind(kind), m_size(size), m_dst(dst), m_src(std::move(src)) {}
|
||||
|
||||
goos::Object LoadVarOp::to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const {
|
||||
return pretty_print::build_list(pretty_print::to_symbol("set!"),
|
||||
pretty_print::to_symbol(m_dst.to_string(env)),
|
||||
m_src.to_form(labels, env));
|
||||
std::vector<goos::Object> forms = {pretty_print::to_symbol("set!"),
|
||||
pretty_print::to_symbol(m_dst.to_string(env))};
|
||||
|
||||
switch (m_kind) {
|
||||
case Kind::FLOAT:
|
||||
assert(m_size == 4);
|
||||
forms.push_back(pretty_print::build_list("l.f", m_src.to_form(labels, env)));
|
||||
break;
|
||||
case Kind::UNSIGNED:
|
||||
switch (m_size) {
|
||||
case 1:
|
||||
forms.push_back(pretty_print::build_list("l.bu", m_src.to_form(labels, env)));
|
||||
break;
|
||||
case 2:
|
||||
forms.push_back(pretty_print::build_list("l.hu", m_src.to_form(labels, env)));
|
||||
break;
|
||||
case 4:
|
||||
forms.push_back(pretty_print::build_list("l.wu", m_src.to_form(labels, env)));
|
||||
break;
|
||||
case 8:
|
||||
forms.push_back(pretty_print::build_list("l.d", m_src.to_form(labels, env)));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
break;
|
||||
case Kind::SIGNED:
|
||||
switch (m_size) {
|
||||
case 1:
|
||||
forms.push_back(pretty_print::build_list("l.b", m_src.to_form(labels, env)));
|
||||
break;
|
||||
case 2:
|
||||
forms.push_back(pretty_print::build_list("l.h", m_src.to_form(labels, env)));
|
||||
break;
|
||||
case 4:
|
||||
forms.push_back(pretty_print::build_list("l.w", m_src.to_form(labels, env)));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
bool LoadVarOp::operator==(const AtomicOp& other) const {
|
||||
@ -864,7 +1005,7 @@ goos::Object IR2_BranchDelay::to_form(const std::vector<DecompilerLabel>& labels
|
||||
assert(m_var[2].has_value());
|
||||
return pretty_print::build_list(
|
||||
"set!", m_var[0]->to_string(env),
|
||||
pretty_print::build_list("dsllv", m_var[1]->to_string(env), m_var[2]->to_string(env)));
|
||||
pretty_print::build_list("sll", m_var[1]->to_string(env), m_var[2]->to_string(env)));
|
||||
case Kind::NEGATE:
|
||||
assert(m_var[0].has_value());
|
||||
assert(m_var[1].has_value());
|
||||
@ -987,11 +1128,13 @@ goos::Object SpecialOp::to_form(const std::vector<DecompilerLabel>& labels, cons
|
||||
(void)env;
|
||||
switch (m_kind) {
|
||||
case Kind::NOP:
|
||||
return pretty_print::to_symbol("nop!");
|
||||
return pretty_print::build_list("nop!");
|
||||
case Kind::BREAK:
|
||||
return pretty_print::to_symbol("break!");
|
||||
return pretty_print::build_list("break!");
|
||||
case Kind::CRASH:
|
||||
return pretty_print::build_list("crash!");
|
||||
case Kind::SUSPEND:
|
||||
return pretty_print::to_symbol("suspend");
|
||||
return pretty_print::build_list("suspend");
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
@ -1073,7 +1216,8 @@ std::unique_ptr<Expr> CallOp::get_as_expr() const {
|
||||
}
|
||||
|
||||
void CallOp::update_register_info() {
|
||||
throw std::runtime_error("CallOp::update_register_info cannot be done until types are known");
|
||||
// throw std::runtime_error("CallOp::update_register_info cannot be done until types are known");
|
||||
m_read_regs.push_back(Register(Reg::GPR, Reg::T9));
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include "common/goos/Object.h"
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "decompiler/Disasm/Instruction.h"
|
||||
@ -88,7 +89,7 @@ class Variable {
|
||||
class AtomicOp {
|
||||
public:
|
||||
explicit AtomicOp(int my_idx);
|
||||
std::string to_string(const std::vector<DecompilerLabel>& labels, const Env* env);
|
||||
std::string to_string(const std::vector<DecompilerLabel>& labels, const Env* env) const;
|
||||
virtual goos::Object to_form(const std::vector<DecompilerLabel>& labels,
|
||||
const Env* env) const = 0;
|
||||
virtual bool operator==(const AtomicOp& other) const = 0;
|
||||
@ -123,6 +124,9 @@ class AtomicOp {
|
||||
const std::vector<Register>& read_regs() { return m_read_regs; }
|
||||
const std::vector<Register>& write_regs() { return m_write_regs; }
|
||||
const std::vector<Register>& clobber_regs() { return m_clobber_regs; }
|
||||
void add_clobber_reg(Register r) { m_clobber_regs.push_back(r); }
|
||||
|
||||
virtual ~AtomicOp() = default;
|
||||
|
||||
protected:
|
||||
int m_my_idx = -1;
|
||||
@ -135,6 +139,8 @@ class AtomicOp {
|
||||
std::vector<Register> m_clobber_regs;
|
||||
};
|
||||
|
||||
class SimpleExpression;
|
||||
|
||||
/*!
|
||||
* The has a value. In some cases it can be set.
|
||||
*/
|
||||
@ -156,6 +162,7 @@ class SimpleAtom {
|
||||
static SimpleAtom make_sym_val(const std::string& name);
|
||||
static SimpleAtom make_empty_list();
|
||||
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;
|
||||
|
||||
bool is_var() const { return m_kind == Kind::VARIABLE; }
|
||||
@ -171,6 +178,7 @@ class SimpleAtom {
|
||||
bool operator==(const SimpleAtom& other) const;
|
||||
bool operator!=(const SimpleAtom& other) const { return !((*this) == other); }
|
||||
void get_regs(std::vector<Register>* out) const;
|
||||
SimpleExpression as_expr() const;
|
||||
|
||||
private:
|
||||
Kind m_kind = Kind::INVALID;
|
||||
@ -219,8 +227,14 @@ class SimpleExpression {
|
||||
RIGHT_SHIFT_ARITH,
|
||||
RIGHT_SHIFT_LOGIC,
|
||||
MUL_UNSIGNED,
|
||||
NOT,
|
||||
NEG
|
||||
LOGNOT,
|
||||
NEG,
|
||||
GPR_TO_FPR,
|
||||
FPR_TO_GPR,
|
||||
MIN_SIGNED,
|
||||
MAX_SIGNED,
|
||||
MIN_UNSIGNED,
|
||||
MAX_UNSIGNED
|
||||
};
|
||||
|
||||
// how many arguments?
|
||||
@ -230,7 +244,7 @@ class SimpleExpression {
|
||||
return m_args[idx];
|
||||
}
|
||||
Kind kind() const { return m_kind; }
|
||||
|
||||
SimpleExpression() = default;
|
||||
SimpleExpression(Kind kind, const SimpleAtom& arg0);
|
||||
SimpleExpression(Kind kind, const SimpleAtom& arg0, const SimpleAtom& arg1);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const;
|
||||
@ -249,8 +263,8 @@ class SimpleExpression {
|
||||
*/
|
||||
class SetVarOp : public AtomicOp {
|
||||
public:
|
||||
SetVarOp(const Variable& dst, const SimpleExpression& src, int my_idx)
|
||||
: AtomicOp(my_idx), m_dst(dst), m_src(src) {
|
||||
SetVarOp(const Variable& dst, SimpleExpression src, int my_idx)
|
||||
: AtomicOp(my_idx), m_dst(dst), m_src(std::move(src)) {
|
||||
assert(my_idx == dst.idx());
|
||||
}
|
||||
virtual goos::Object to_form(const std::vector<DecompilerLabel>& labels,
|
||||
@ -310,9 +324,13 @@ class IR2_Condition {
|
||||
LEQ_SIGNED,
|
||||
GEQ_SIGNED,
|
||||
GREATER_THAN_ZERO_SIGNED,
|
||||
GREATER_THAN_ZERO_UNSIGNED,
|
||||
LEQ_ZERO_SIGNED,
|
||||
LESS_THAN_ZERO,
|
||||
LEQ_ZERO_UNSIGNED,
|
||||
LESS_THAN_ZERO_SIGNED,
|
||||
GEQ_ZERO_SIGNED,
|
||||
LESS_THAN_ZERO_UNSIGNED,
|
||||
GEQ_ZERO_UNSIGNED,
|
||||
LESS_THAN_UNSIGNED,
|
||||
GREATER_THAN_UNSIGNED,
|
||||
LEQ_UNSIGNED,
|
||||
@ -329,12 +347,15 @@ class IR2_Condition {
|
||||
FLOAT_GEQ,
|
||||
FLOAT_LEQ,
|
||||
FLOAT_GREATER_THAN,
|
||||
IS_PAIR,
|
||||
IS_NOT_PAIR,
|
||||
INVALID
|
||||
};
|
||||
|
||||
IR2_Condition() = default;
|
||||
explicit IR2_Condition(Kind kind);
|
||||
IR2_Condition(Kind kind, const Variable& src0);
|
||||
IR2_Condition(Kind kind, const Variable& src0, const Variable& src1);
|
||||
IR2_Condition(Kind kind, const SimpleAtom& src0);
|
||||
IR2_Condition(Kind kind, const SimpleAtom& src0, const SimpleAtom& src1);
|
||||
|
||||
void invert();
|
||||
bool operator==(const IR2_Condition& other) const;
|
||||
@ -344,7 +365,7 @@ class IR2_Condition {
|
||||
|
||||
private:
|
||||
Kind m_kind = Kind::INVALID;
|
||||
Variable m_src[2];
|
||||
SimpleAtom m_src[2];
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -361,6 +382,7 @@ class SetVarConditionOp : public AtomicOp {
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
void invert() { m_condition.invert(); }
|
||||
|
||||
private:
|
||||
Variable m_dst;
|
||||
@ -374,7 +396,7 @@ class SetVarConditionOp : public AtomicOp {
|
||||
*/
|
||||
class StoreOp : public AtomicOp {
|
||||
public:
|
||||
StoreOp(SimpleExpression addr, SimpleAtom value, int my_idx);
|
||||
StoreOp(int size, bool is_float, SimpleExpression addr, SimpleAtom value, int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
@ -385,6 +407,8 @@ class StoreOp : public AtomicOp {
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
int m_size;
|
||||
bool m_is_float;
|
||||
SimpleExpression m_addr;
|
||||
SimpleAtom m_value;
|
||||
};
|
||||
@ -395,7 +419,8 @@ class StoreOp : public AtomicOp {
|
||||
*/
|
||||
class LoadVarOp : public AtomicOp {
|
||||
public:
|
||||
LoadVarOp(Variable dst, SimpleExpression src, int my_idx);
|
||||
enum class Kind { UNSIGNED, SIGNED, FLOAT };
|
||||
LoadVarOp(Kind kind, int size, Variable dst, SimpleExpression src, int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
@ -406,6 +431,8 @@ class LoadVarOp : public AtomicOp {
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
Kind m_kind;
|
||||
int m_size = -1;
|
||||
Variable m_dst;
|
||||
SimpleExpression m_src;
|
||||
};
|
||||
@ -427,7 +454,8 @@ class IR2_BranchDelay {
|
||||
SET_BINTEGER,
|
||||
SET_PAIR,
|
||||
DSLLV,
|
||||
NEGATE
|
||||
NEGATE,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
explicit IR2_BranchDelay(Kind kind);
|
||||
@ -437,10 +465,11 @@ class IR2_BranchDelay {
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const;
|
||||
bool operator==(const IR2_BranchDelay& other) const;
|
||||
void get_regs(std::vector<Register>* write, std::vector<Register>* read) const;
|
||||
bool is_known() const { return m_kind != Kind::UNKNOWN; }
|
||||
|
||||
private:
|
||||
std::optional<Variable> m_var[3];
|
||||
Kind m_kind;
|
||||
Kind m_kind = Kind::UNKNOWN;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -479,6 +508,7 @@ class SpecialOp : public AtomicOp {
|
||||
enum class Kind {
|
||||
NOP,
|
||||
BREAK,
|
||||
CRASH,
|
||||
SUSPEND,
|
||||
};
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,7 @@ struct FunctionAtomicOps {
|
||||
std::vector<std::unique_ptr<AtomicOp>> ops;
|
||||
|
||||
// mappings from instructions to atomic ops and back
|
||||
std::unordered_map<int, int> instruction_to_basic_op;
|
||||
std::unordered_map<int, int> instruction_to_atomic_op;
|
||||
std::unordered_map<int, int> atomic_op_to_instruction;
|
||||
|
||||
// map from basic block to the index of the first op
|
||||
|
@ -989,7 +989,7 @@ goos::Object LinkedObjectFile::to_form_script(int seg, int word_idx, std::vector
|
||||
/*!
|
||||
* Is the thing pointed to a string?
|
||||
*/
|
||||
bool LinkedObjectFile::is_string(int seg, int byte_idx) {
|
||||
bool LinkedObjectFile::is_string(int seg, int byte_idx) const {
|
||||
if (byte_idx % 4) {
|
||||
return false; // must be aligned pointer.
|
||||
}
|
||||
|
@ -63,6 +63,8 @@ class LinkedObjectFile {
|
||||
|
||||
u32 read_data_word(const DecompilerLabel& label);
|
||||
std::string get_goal_string_by_label(const DecompilerLabel& label) const;
|
||||
std::string get_goal_string(int seg, int word_idx, bool with_quotes = true) const;
|
||||
bool is_string(int seg, int byte_idx) const;
|
||||
|
||||
struct Stats {
|
||||
uint32_t total_code_bytes = 0;
|
||||
@ -129,8 +131,6 @@ class LinkedObjectFile {
|
||||
goos::Object to_form_script(int seg, int word_idx, std::vector<bool>& seen);
|
||||
goos::Object to_form_script_object(int seg, int byte_idx, std::vector<bool>& seen);
|
||||
bool is_empty_list(int seg, int byte_idx);
|
||||
bool is_string(int seg, int byte_idx);
|
||||
std::string get_goal_string(int seg, int word_idx, bool with_quotes = true) const;
|
||||
|
||||
std::vector<std::unordered_map<int, int>> label_per_seg_by_offset;
|
||||
};
|
||||
|
@ -126,19 +126,19 @@ ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos,
|
||||
"consistent naming when doing a partial decompilation.");
|
||||
}
|
||||
|
||||
lg::info("-Loading DGOs...");
|
||||
lg::info("-Loading {} DGOs...", _dgos.size());
|
||||
for (auto& dgo : _dgos) {
|
||||
get_objs_from_dgo(dgo);
|
||||
}
|
||||
|
||||
lg::info("-Loading plain object files...");
|
||||
lg::info("-Loading {} plain object files...", object_files.size());
|
||||
for (auto& obj : object_files) {
|
||||
auto data = file_util::read_binary_file(obj);
|
||||
auto name = obj_filename_to_name(obj);
|
||||
add_obj_from_dgo(name, name, data.data(), data.size(), "NO-XGO");
|
||||
}
|
||||
|
||||
lg::info("-Loading streaming object files...");
|
||||
lg::info("-Loading {} streaming object files...", str_files.size());
|
||||
for (auto& obj : str_files) {
|
||||
StrFileReader reader(obj);
|
||||
// name from the file name
|
||||
@ -153,15 +153,7 @@ ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos,
|
||||
}
|
||||
}
|
||||
|
||||
lg::info("ObjectFileDB Initialized:");
|
||||
lg::info("Total DGOs: {}", int(_dgos.size()));
|
||||
lg::info("Total data: {} bytes", stats.total_dgo_bytes);
|
||||
lg::info("Total objs: {}", stats.total_obj_files);
|
||||
lg::info("Unique objs: {}", stats.unique_obj_files);
|
||||
lg::info("Unique data: {} bytes", stats.unique_obj_bytes);
|
||||
lg::info("Total {:.2f} ms ({:.3f} MB/sec, {:.2f} obj/sec)", timer.getMs(),
|
||||
stats.total_dgo_bytes / ((1u << 20u) * timer.getSeconds()),
|
||||
stats.total_obj_files / timer.getSeconds());
|
||||
lg::info("ObjectFileDB Initialized\n");
|
||||
}
|
||||
|
||||
void ObjectFileDB::load_map_file(const std::string& map_data) {
|
||||
@ -481,7 +473,7 @@ std::string ObjectFileDB::generate_obj_listing() {
|
||||
* Process all of the linking data of all objects.
|
||||
*/
|
||||
void ObjectFileDB::process_link_data() {
|
||||
lg::info("- Processing Link Data...");
|
||||
lg::info("Processing Link Data...");
|
||||
Timer process_link_timer;
|
||||
|
||||
LinkedObjectFile::Stats combined_stats;
|
||||
@ -491,25 +483,7 @@ void ObjectFileDB::process_link_data() {
|
||||
combined_stats.add(obj.linked_data.stats);
|
||||
});
|
||||
|
||||
lg::info("Processed Link Data:");
|
||||
lg::info(" Code {} bytes", combined_stats.total_code_bytes);
|
||||
lg::info(" v2 Code {} bytes", combined_stats.total_v2_code_bytes);
|
||||
lg::info(" v2 Link Data {} bytes", combined_stats.total_v2_link_bytes);
|
||||
lg::info(" v2 Pointers {}", combined_stats.total_v2_pointers);
|
||||
lg::info(" v2 Pointer Seeks {}", combined_stats.total_v2_pointer_seeks);
|
||||
lg::info(" v2 Symbols {}", combined_stats.total_v2_symbol_count);
|
||||
lg::info(" v2 Symbol Links {}", combined_stats.total_v2_symbol_links);
|
||||
|
||||
lg::info(" v3 Code {} bytes", combined_stats.v3_code_bytes);
|
||||
lg::info(" v3 Link Data {} bytes", combined_stats.v3_link_bytes);
|
||||
lg::info(" v3 Pointers {}", combined_stats.v3_pointers);
|
||||
lg::info(" Split {}", combined_stats.v3_split_pointers);
|
||||
lg::info(" Word {}", combined_stats.v3_word_pointers);
|
||||
lg::info(" v3 Pointer Seeks {}", combined_stats.v3_pointer_seeks);
|
||||
lg::info(" v3 Symbols {}", combined_stats.v3_symbol_count);
|
||||
lg::info(" v3 Offset Symbol Links {}", combined_stats.v3_symbol_link_offset);
|
||||
lg::info(" v3 Word Symbol Links {}", combined_stats.v3_symbol_link_word);
|
||||
|
||||
lg::info("Processed Link Data");
|
||||
lg::info(" Total {} ms\n", process_link_timer.getMs());
|
||||
// printf("\n");
|
||||
}
|
||||
@ -518,15 +492,14 @@ void ObjectFileDB::process_link_data() {
|
||||
* Process all of the labels generated from linking and give them reasonable names.
|
||||
*/
|
||||
void ObjectFileDB::process_labels() {
|
||||
lg::info("- Processing Labels...");
|
||||
lg::info("Processing Labels...");
|
||||
Timer process_label_timer;
|
||||
uint32_t total = 0;
|
||||
for_each_obj([&](ObjectFileData& obj) { total += obj.linked_data.set_ordered_label_names(); });
|
||||
|
||||
lg::info("Processed Labels:");
|
||||
lg::info(" Total {} labels", total);
|
||||
lg::info(" Total {} ms", process_label_timer.getMs());
|
||||
// printf("\n");
|
||||
lg::info(" Total {} ms\n", process_label_timer.getMs());
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -636,7 +609,7 @@ void ObjectFileDB::write_disassembly(const std::string& output_dir,
|
||||
* Find code/data zones, identify functions, and disassemble
|
||||
*/
|
||||
void ObjectFileDB::find_code() {
|
||||
lg::info("- Finding code in object files...");
|
||||
lg::info("Finding code in object files...");
|
||||
LinkedObjectFile::Stats combined_stats;
|
||||
Timer timer;
|
||||
|
||||
@ -670,8 +643,7 @@ void ObjectFileDB::find_code() {
|
||||
auto total_ops = combined_stats.code_bytes / 4;
|
||||
lg::info(" Decoded {} / {} ({:.3f} %)", combined_stats.decoded_ops, total_ops,
|
||||
100.f * (float)combined_stats.decoded_ops / total_ops);
|
||||
lg::info(" Total {:.3f} ms", timer.getMs());
|
||||
// printf("\n");
|
||||
lg::info(" Total {:.3f} ms\n", timer.getMs());
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -767,7 +739,7 @@ std::string ObjectFileDB::process_game_count_file() {
|
||||
/*!
|
||||
* This is the main decompiler routine which runs after we've identified functions.
|
||||
*/
|
||||
void ObjectFileDB::analyze_functions() {
|
||||
void ObjectFileDB::analyze_functions_ir1() {
|
||||
lg::info("- Analyzing Functions...");
|
||||
Timer timer;
|
||||
|
||||
|
@ -65,7 +65,15 @@ class ObjectFileDB {
|
||||
const std::string& file_suffix = "");
|
||||
|
||||
void write_debug_type_analysis(const std::string& output_dir, const std::string& suffix = "");
|
||||
void analyze_functions();
|
||||
void analyze_functions_ir1();
|
||||
void analyze_functions_ir2(const std::string& output_dir);
|
||||
void ir2_top_level_pass();
|
||||
void ir2_basic_block_pass();
|
||||
void ir2_atomic_op_pass();
|
||||
void ir2_write_results(const std::string& output_dir);
|
||||
std::string ir2_to_file(ObjectFileData& data);
|
||||
std::string ir2_function_to_string(ObjectFileData& data, Function& function, int seg);
|
||||
|
||||
void process_tpages();
|
||||
void analyze_expressions();
|
||||
std::string process_game_count_file();
|
||||
|
347
decompiler/ObjectFile/ObjectFileDB_IR2.cpp
Normal file
347
decompiler/ObjectFile/ObjectFileDB_IR2.cpp
Normal file
@ -0,0 +1,347 @@
|
||||
/*!
|
||||
* @file ObjectFileDB_IR2.cpp
|
||||
* This runs the IR2 analysis passes.
|
||||
*/
|
||||
|
||||
#include "ObjectFileDB.h"
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/Timer.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "decompiler/Function/TypeInspector.h"
|
||||
|
||||
namespace decompiler {
|
||||
|
||||
/*!
|
||||
* Main IR2 analysis pass.
|
||||
* At this point, we assume that the files are loaded and we've run find_code to locate all
|
||||
* functions, but nothing else.
|
||||
*/
|
||||
void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir) {
|
||||
lg::info("Using IR2 analysis...");
|
||||
lg::info("Processing top-level functions...");
|
||||
ir2_top_level_pass();
|
||||
lg::info("Processing basic blocks and control flow graph...");
|
||||
ir2_basic_block_pass();
|
||||
lg::info("Converting to atomic ops...");
|
||||
ir2_atomic_op_pass();
|
||||
lg::info("Writing results...");
|
||||
ir2_write_results(output_dir);
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_top_level_pass() {
|
||||
Timer timer;
|
||||
int total_functions = 0;
|
||||
int total_named_global_functions = 0;
|
||||
int total_methods = 0;
|
||||
int total_top_levels = 0;
|
||||
int total_unknowns = 0;
|
||||
|
||||
for_each_obj([&](ObjectFileData& data) {
|
||||
if (data.linked_data.segments == 3) {
|
||||
// the top level segment should have a single function
|
||||
assert(data.linked_data.functions_by_seg.at(2).size() == 1);
|
||||
|
||||
auto& func = data.linked_data.functions_by_seg.at(2).front();
|
||||
assert(func.guessed_name.empty());
|
||||
func.guessed_name.set_as_top_level();
|
||||
func.find_global_function_defs(data.linked_data, dts);
|
||||
func.find_type_defs(data.linked_data, dts);
|
||||
func.find_method_defs(data.linked_data, dts);
|
||||
}
|
||||
});
|
||||
|
||||
// check for function uniqueness.
|
||||
std::unordered_set<std::string> unique_names;
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> duplicated_functions;
|
||||
|
||||
int uid = 1;
|
||||
for_each_obj([&](ObjectFileData& data) {
|
||||
int func_in_obj = 0;
|
||||
for (int segment_id = 0; segment_id < int(data.linked_data.segments); segment_id++) {
|
||||
for (auto& func : data.linked_data.functions_by_seg.at(segment_id)) {
|
||||
func.guessed_name.unique_id = uid++;
|
||||
func.guessed_name.id_in_object = func_in_obj++;
|
||||
func.guessed_name.object_name = data.to_unique_name();
|
||||
auto name = func.guessed_name.to_string();
|
||||
|
||||
switch (func.guessed_name.kind) {
|
||||
case FunctionName::FunctionKind::METHOD:
|
||||
total_methods++;
|
||||
break;
|
||||
case FunctionName::FunctionKind::GLOBAL:
|
||||
total_named_global_functions++;
|
||||
break;
|
||||
case FunctionName::FunctionKind::TOP_LEVEL_INIT:
|
||||
total_top_levels++;
|
||||
break;
|
||||
case FunctionName::FunctionKind::UNIDENTIFIED:
|
||||
total_unknowns++;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
total_functions++;
|
||||
|
||||
if (unique_names.find(name) != unique_names.end()) {
|
||||
duplicated_functions[name].insert(data.to_unique_name());
|
||||
}
|
||||
|
||||
unique_names.insert(name);
|
||||
|
||||
if (get_config().asm_functions_by_name.find(name) !=
|
||||
get_config().asm_functions_by_name.end()) {
|
||||
func.warnings += ";; flagged as asm by config\n";
|
||||
func.suspected_asm = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for_each_function([&](Function& func, int segment_id, ObjectFileData& data) {
|
||||
(void)segment_id;
|
||||
auto name = func.guessed_name.to_string();
|
||||
|
||||
if (duplicated_functions.find(name) != duplicated_functions.end()) {
|
||||
duplicated_functions[name].insert(data.to_unique_name());
|
||||
func.warnings += ";; this function exists in multiple non-identical object files\n";
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("Found a total of {} functions in {:.2f} ms", total_functions, timer.getMs());
|
||||
lg::info("{:4d} unknown {:.2f}%", total_unknowns, 100.f * total_unknowns / total_functions);
|
||||
lg::info("{:4d} global {:.2f}%", total_named_global_functions,
|
||||
100.f * total_named_global_functions / total_functions);
|
||||
lg::info("{:4d} methods {:.2f}%", total_methods, 100.f * total_methods / total_functions);
|
||||
lg::info("{:4d} logins {:.2f}%\n", total_top_levels, 100.f * total_top_levels / total_functions);
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_basic_block_pass() {
|
||||
Timer timer;
|
||||
// Main Pass over each function...
|
||||
int total_basic_blocks = 0;
|
||||
int total_functions = 0;
|
||||
int functions_with_one_block = 0;
|
||||
int inspect_methods = 0;
|
||||
int suspected_asm = 0;
|
||||
int failed_to_build_cfg = 0;
|
||||
|
||||
for_each_function_def_order([&](Function& func, int segment_id, ObjectFileData& data) {
|
||||
total_functions++;
|
||||
|
||||
// first, find basic blocks.
|
||||
auto blocks = find_blocks_in_function(data.linked_data, segment_id, func);
|
||||
total_basic_blocks += blocks.size();
|
||||
if (blocks.size() == 1) {
|
||||
functions_with_one_block++;
|
||||
}
|
||||
func.basic_blocks = blocks;
|
||||
|
||||
if (!func.suspected_asm) {
|
||||
// find the prologue/epilogue so they can be excluded from basic blocks.
|
||||
func.analyze_prologue(data.linked_data);
|
||||
}
|
||||
|
||||
if (!func.suspected_asm) {
|
||||
// run analysis
|
||||
|
||||
// build a control flow graph, just looking at branch instructions.
|
||||
func.cfg = build_cfg(data.linked_data, segment_id, func);
|
||||
if (!func.cfg->is_fully_resolved()) {
|
||||
lg::warn("Function {} from {} failed to build control flow graph!",
|
||||
func.guessed_name.to_string(), data.to_unique_name());
|
||||
failed_to_build_cfg++;
|
||||
}
|
||||
|
||||
// if we got an inspect method, inspect it.
|
||||
if (func.is_inspect_method) {
|
||||
auto result = inspect_inspect_method(func, func.method_of_type, dts, data.linked_data);
|
||||
all_type_defs += ";; " + data.to_unique_name() + "\n";
|
||||
all_type_defs += result.print_as_deftype() + "\n";
|
||||
inspect_methods++;
|
||||
}
|
||||
}
|
||||
|
||||
if (func.suspected_asm) {
|
||||
suspected_asm++;
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("Found {} basic blocks in {} functions in {:.2f} ms:", total_basic_blocks,
|
||||
total_functions, timer.getMs());
|
||||
lg::info(" {} functions ({:.2f}%) failed to build control flow graph", failed_to_build_cfg,
|
||||
100.f * failed_to_build_cfg / total_functions);
|
||||
lg::info(" {} functions ({:.2f}%) had exactly one basic block", functions_with_one_block,
|
||||
100.f * functions_with_one_block / total_functions);
|
||||
lg::info(" {} functions ({:.2f}%) were ignored as assembly", suspected_asm,
|
||||
100.f * suspected_asm / total_functions);
|
||||
lg::info(" {} functions ({:.2f}%) were inspect methods\n", inspect_methods,
|
||||
100.f * inspect_methods / total_functions);
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_atomic_op_pass() {
|
||||
Timer timer;
|
||||
int total_functions = 0;
|
||||
int attempted = 0;
|
||||
int successful = 0;
|
||||
for_each_function_def_order([&](Function& func, int segment_id, ObjectFileData& data) {
|
||||
(void)segment_id;
|
||||
total_functions++;
|
||||
if (!func.suspected_asm) {
|
||||
func.ir2.atomic_ops_attempted = true;
|
||||
attempted++;
|
||||
try {
|
||||
auto ops = convert_function_to_atomic_ops(func, data.linked_data.labels);
|
||||
func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops));
|
||||
func.ir2.atomic_ops_succeeded = true;
|
||||
successful++;
|
||||
} catch (std::exception& e) {
|
||||
lg::warn("Function {} from {} could not be converted to atomic ops: {}",
|
||||
func.guessed_name.to_string(), data.to_unique_name(), e.what());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("{}/{}/{} (successful/attempted/total) functions converted to Atomic Ops in {:.2f} ms",
|
||||
successful, attempted, total_functions, timer.getMs());
|
||||
lg::info("{:.2f}% were attempted, {:.2f}% of attempted succeeded\n",
|
||||
100.f * attempted / total_functions, 100.f * successful / attempted);
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_write_results(const std::string& output_dir) {
|
||||
Timer timer;
|
||||
lg::info("Writing IR2 results to file...");
|
||||
int total_files = 0;
|
||||
int total_bytes = 0;
|
||||
for_each_obj([&](ObjectFileData& obj) {
|
||||
if (obj.linked_data.has_any_functions()) {
|
||||
// todo
|
||||
total_files++;
|
||||
auto file_text = ir2_to_file(obj);
|
||||
total_bytes += file_text.length();
|
||||
auto file_name = file_util::combine_path(output_dir, obj.to_unique_name() + "_ir2.asm");
|
||||
|
||||
file_util::write_text_file(file_name, file_text);
|
||||
}
|
||||
});
|
||||
lg::info("Wrote {} files ({:.2f} MB) in {:.2f} ms\n", total_files, total_bytes / float(1 << 20),
|
||||
timer.getMs());
|
||||
}
|
||||
|
||||
std::string ObjectFileDB::ir2_to_file(ObjectFileData& data) {
|
||||
std::string result;
|
||||
|
||||
const char* segment_names[] = {"main segment", "debug segment", "top-level segment"};
|
||||
assert(data.linked_data.segments <= 3);
|
||||
for (int seg = data.linked_data.segments; seg-- > 0;) {
|
||||
// segment header
|
||||
result += ";------------------------------------------\n; ";
|
||||
result += segment_names[seg];
|
||||
result += "\n;------------------------------------------\n\n";
|
||||
|
||||
// functions
|
||||
for (auto& func : data.linked_data.functions_by_seg.at(seg)) {
|
||||
result += ir2_function_to_string(data, func, seg);
|
||||
}
|
||||
|
||||
// print data
|
||||
for (size_t i = data.linked_data.offset_of_data_zone_by_seg.at(seg);
|
||||
i < data.linked_data.words_by_seg.at(seg).size(); i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
auto label_id = data.linked_data.get_label_at(seg, i * 4 + j);
|
||||
if (label_id != -1) {
|
||||
result += data.linked_data.labels.at(label_id).name + ":";
|
||||
if (j != 0) {
|
||||
result += " (offset " + std::to_string(j) + ")";
|
||||
}
|
||||
result += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
auto& word = data.linked_data.words_by_seg[seg][i];
|
||||
data.linked_data.append_word_to_string(result, word);
|
||||
|
||||
if (word.kind == LinkedWord::TYPE_PTR && word.symbol_name == "string") {
|
||||
result += "; " + data.linked_data.get_goal_string(seg, i) + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function& func, int seg) {
|
||||
std::string result;
|
||||
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
|
||||
result += "; .function " + func.guessed_name.to_string() + "\n";
|
||||
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
|
||||
result += func.prologue.to_string(2) + "\n";
|
||||
if (!func.warnings.empty()) {
|
||||
result += ";;Warnings:\n" + func.warnings + "\n";
|
||||
}
|
||||
|
||||
bool print_atomics = func.ir2.atomic_ops_succeeded;
|
||||
// print each instruction in the function.
|
||||
bool in_delay_slot = false;
|
||||
|
||||
for (int i = 1; i < func.end_word - func.start_word; i++) {
|
||||
// check for a label to print
|
||||
auto label_id = data.linked_data.get_label_at(seg, (func.start_word + i) * 4);
|
||||
if (label_id != -1) {
|
||||
result += data.linked_data.labels.at(label_id).name + ":\n";
|
||||
}
|
||||
|
||||
// check for no misaligned labels in code segments.
|
||||
for (int j = 1; j < 4; j++) {
|
||||
assert(data.linked_data.get_label_at(seg, (func.start_word + i) * 4 + j) == -1);
|
||||
}
|
||||
|
||||
// print the assembly instruction
|
||||
auto& instr = func.instructions.at(i);
|
||||
std::string line = " " + instr.to_string(data.linked_data.labels);
|
||||
|
||||
// printf("%d inst %s\n", print_atomics, instr.to_string(data.linked_data.labels).c_str());
|
||||
|
||||
bool printed_comment = false;
|
||||
|
||||
// print atomic op
|
||||
if (print_atomics && func.instr_starts_atomic_op(i)) {
|
||||
if (line.length() < 30) {
|
||||
line.append(30 - line.length(), ' ');
|
||||
}
|
||||
line +=
|
||||
" ;; " + func.get_atomic_op_at_instr(i).to_string(data.linked_data.labels, &func.ir2.env);
|
||||
printed_comment = true;
|
||||
}
|
||||
|
||||
// print linked strings
|
||||
for (int iidx = 0; iidx < instr.n_src; iidx++) {
|
||||
if (instr.get_src(iidx).is_label()) {
|
||||
auto lab = data.linked_data.labels.at(instr.get_src(iidx).get_label());
|
||||
if (data.linked_data.is_string(lab.target_segment, lab.offset)) {
|
||||
if (!printed_comment) {
|
||||
line += " ;; ";
|
||||
printed_comment = true;
|
||||
}
|
||||
line += " " + data.linked_data.get_goal_string(lab.target_segment, lab.offset / 4 - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
result += line + "\n";
|
||||
|
||||
// print delay slot gap
|
||||
if (in_delay_slot) {
|
||||
result += "\n";
|
||||
in_delay_slot = false;
|
||||
}
|
||||
|
||||
// for next time...
|
||||
if (gOpcodeInfo[(int)instr.kind].has_delay_slot) {
|
||||
in_delay_slot = true;
|
||||
}
|
||||
}
|
||||
result += "\n";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace decompiler
|
@ -51,6 +51,7 @@ void set_config(const std::string& path_to_config_file) {
|
||||
gConfig.write_func_json = cfg.at("write_func_json").get<bool>();
|
||||
gConfig.function_type_prop = cfg.at("function_type_prop").get<bool>();
|
||||
gConfig.analyze_expressions = cfg.at("analyze_expressions").get<bool>();
|
||||
gConfig.run_ir2 = cfg.at("run_ir2").get<bool>();
|
||||
|
||||
std::vector<std::string> asm_functions_by_name =
|
||||
cfg.at("asm_functions_by_name").get<std::vector<std::string>>();
|
||||
|
@ -43,6 +43,7 @@ struct Config {
|
||||
type_hints_by_function_by_idx;
|
||||
std::unordered_map<std::string, std::unordered_map<int, std::string>>
|
||||
anon_function_types_by_obj_by_id;
|
||||
bool run_ir2 = false;
|
||||
};
|
||||
|
||||
Config& get_config();
|
||||
|
@ -61,6 +61,8 @@
|
||||
"write_disassembly":true,
|
||||
"write_hex_near_instructions":false,
|
||||
|
||||
"run_ir2":false,
|
||||
|
||||
// if false, skips printing disassembly of object with functions, as these are usually large (~1 GB) and not interesting yet.
|
||||
"disassemble_objects_without_functions":false,
|
||||
|
||||
@ -107,12 +109,21 @@
|
||||
|
||||
// gkernel
|
||||
"(method 11 cpu-thread)",
|
||||
"throw",
|
||||
"return-from-thread",
|
||||
"return-from-thread-dead",
|
||||
"reset-and-call",
|
||||
"(method 10 cpu-thread)",
|
||||
"(method 0 catch-frame)",
|
||||
"throw-dispatch",
|
||||
"set-to-run-bootstrap",
|
||||
|
||||
// pskernel
|
||||
"return-from-exception", // F: eret
|
||||
"kernel-read-function", // F: delay slot tricks
|
||||
"kernel-write-function", // F: delay slot tricks
|
||||
"kernel-copy-function",
|
||||
"kernel-check-hardwired-addresses",
|
||||
|
||||
// math
|
||||
"rand-uint31-gen",
|
||||
@ -304,6 +315,7 @@
|
||||
"memcpy",
|
||||
"sp-process-block-3d",
|
||||
"sp-process-block-2d",
|
||||
"sp-get-particle",
|
||||
|
||||
// loader BUG
|
||||
"(method 10 external-art-buffer)",
|
||||
|
@ -22,6 +22,7 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// collect all files to process
|
||||
set_config(argv[1]);
|
||||
std::string in_folder = argv[2];
|
||||
std::string out_folder = argv[3];
|
||||
@ -39,6 +40,8 @@ int main(int argc, char** argv) {
|
||||
strs.push_back(file_util::combine_path(in_folder, str_name));
|
||||
}
|
||||
|
||||
// build file database
|
||||
lg::info("Setting up object file DB...");
|
||||
ObjectFileDB db(dgos, get_config().obj_file_name_map_file, objs, strs);
|
||||
file_util::write_text_file(file_util::combine_path(out_folder, "dgo.txt"),
|
||||
db.generate_dgo_listing());
|
||||
@ -51,10 +54,36 @@ int main(int argc, char** argv) {
|
||||
db.dump_raw_objects(path);
|
||||
}
|
||||
|
||||
// process files (basic)
|
||||
db.process_link_data();
|
||||
db.find_code();
|
||||
db.process_labels();
|
||||
|
||||
// IR1 or IR2 function analysis
|
||||
if (get_config().run_ir2) {
|
||||
db.analyze_functions_ir2(out_folder);
|
||||
} else {
|
||||
if (get_config().analyze_functions) {
|
||||
db.analyze_functions_ir1();
|
||||
}
|
||||
|
||||
if (get_config().write_disassembly) {
|
||||
db.write_disassembly(out_folder, get_config().disassemble_objects_without_functions,
|
||||
get_config().write_func_json);
|
||||
db.write_debug_type_analysis(out_folder);
|
||||
}
|
||||
|
||||
if (get_config().analyze_expressions) {
|
||||
db.analyze_expressions();
|
||||
db.write_disassembly(out_folder, false, false, "_expr");
|
||||
}
|
||||
}
|
||||
|
||||
// common IR1 and IR2 function stuff:
|
||||
file_util::write_text_file(file_util::combine_path(out_folder, "all-syms.gc"),
|
||||
db.dts.dump_symbol_types());
|
||||
|
||||
// data stuff
|
||||
if (get_config().write_scripts) {
|
||||
db.find_and_write_scripts(out_folder);
|
||||
}
|
||||
@ -63,10 +92,6 @@ int main(int argc, char** argv) {
|
||||
db.write_object_file_words(out_folder, get_config().write_hexdump_on_v3_only);
|
||||
}
|
||||
|
||||
if (get_config().analyze_functions) {
|
||||
db.analyze_functions();
|
||||
}
|
||||
|
||||
if (get_config().process_game_text) {
|
||||
auto result = db.process_game_text_files();
|
||||
file_util::write_text_file(file_util::get_file_path({"assets", "game_text.txt"}), result);
|
||||
@ -81,22 +106,9 @@ int main(int argc, char** argv) {
|
||||
file_util::write_text_file(file_util::get_file_path({"assets", "game_count.txt"}), result);
|
||||
}
|
||||
|
||||
if (get_config().write_disassembly) {
|
||||
db.write_disassembly(out_folder, get_config().disassemble_objects_without_functions,
|
||||
get_config().write_func_json);
|
||||
db.write_debug_type_analysis(out_folder);
|
||||
}
|
||||
|
||||
if (get_config().analyze_expressions) {
|
||||
db.analyze_expressions();
|
||||
db.write_disassembly(out_folder, false, false, "_expr");
|
||||
}
|
||||
|
||||
// todo print type summary
|
||||
// printf("%s\n", get_type_info().get_summary().c_str());
|
||||
|
||||
file_util::write_text_file(file_util::combine_path(out_folder, "all-syms.gc"),
|
||||
db.dts.dump_symbol_types());
|
||||
lg::info("Disassembly has completed successfully.");
|
||||
return 0;
|
||||
}
|
||||
|
40
scripts/gen-test-cases.py
Normal file
40
scripts/gen-test-cases.py
Normal file
@ -0,0 +1,40 @@
|
||||
import re
|
||||
|
||||
# Quick and dirty script to generate decompiler test cases from text file format
|
||||
|
||||
with open("test-cases.txt") as f:
|
||||
content = f.readlines()
|
||||
content = [x.strip() for x in content]
|
||||
|
||||
test_cases = {}
|
||||
|
||||
for case in content:
|
||||
args = re.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", case)
|
||||
assembly_lines = args[0].replace("\"", "").strip().split("\\n")
|
||||
main_instruction = assembly_lines[0].split(" ")[0]
|
||||
if re.match("^L\d*:\s*$", main_instruction):
|
||||
main_instruction = assembly_lines[1].strip().split(" ")[0]
|
||||
main_instruction = main_instruction.upper().replace(".", "_")
|
||||
assembly_lines = "{{{}}}".format(", ".join(["\"{}\"".format(x.replace("\\n", "").strip()) for x in assembly_lines]))
|
||||
output_lines = args[1].replace("\\n", "").strip()
|
||||
write_regs = "{{{}}}".format(args[2].replace("\\n", "").strip().replace(" ", "\",\""))
|
||||
read_regs = "{{{}}}".format(args[3].replace("\\n", "").strip().replace(" ", "\",\""))
|
||||
clob_regs = "{{{}}}".format(args[4].replace("\\n", "").strip().replace(" ", "\",\""))
|
||||
|
||||
test_case = "test_case(assembly_from_list({}), {}, {}, {}, {});".format(assembly_lines, output_lines, write_regs, read_regs, clob_regs);
|
||||
|
||||
if main_instruction in test_cases:
|
||||
test_cases[main_instruction].append(test_case)
|
||||
else:
|
||||
test_cases[main_instruction] = []
|
||||
test_cases[main_instruction].append(test_case)
|
||||
|
||||
with open("test-cases.cpp", "a") as f:
|
||||
instructions = test_cases.keys()
|
||||
instructions = sorted(instructions)
|
||||
for instr in instructions:
|
||||
f.write("TEST(DecompilerAtomicOpBuilder, {}) {{".format(instr))
|
||||
for case in test_cases[instr]:
|
||||
f.write(case)
|
||||
f.write("}\n\n")
|
||||
|
@ -2,22 +2,40 @@
|
||||
#include "decompiler/IR2/AtomicOp.h"
|
||||
#include "decompiler/IR2/AtomicOpBuilder.h"
|
||||
#include "decompiler/Disasm/InstructionParser.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "third-party/fmt/format.h"
|
||||
#include <regex>
|
||||
|
||||
using namespace decompiler;
|
||||
TEST(DecompilerAtomicOpBuilder, Example) {
|
||||
|
||||
std::regex labelRegex("^L\\d+:\\s*$");
|
||||
|
||||
// Auto indents / adds new-lines to a list of assembly lines
|
||||
std::string assembly_from_list(std::vector<std::string> assemblyLines) {
|
||||
std::string str = "";
|
||||
for (std::string line : assemblyLines) {
|
||||
if (std::regex_match(line, labelRegex)) {
|
||||
str += fmt::format("{}\n", line);
|
||||
} else {
|
||||
str += fmt::format(" {}\n", line);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void test_case(std::string assembly_lines,
|
||||
std::vector<std::string> output_lines,
|
||||
std::vector<std::vector<std::string>> write_regs,
|
||||
std::vector<std::vector<std::string>> read_regs,
|
||||
std::vector<std::vector<std::string>> clobbered_regs) {
|
||||
InstructionParser parser;
|
||||
|
||||
// some MIPS instructions. Can be a sequence of instructions, possibly with labels.
|
||||
std::string input_program =
|
||||
"and v0, v1, a3\n"
|
||||
"and a1, a2, a2";
|
||||
|
||||
// convert to Instructions:
|
||||
ParsedProgram prg = parser.parse_program(input_program);
|
||||
// some MIPS instructions. Can be a sequence of instructions, possibly with labels.
|
||||
ParsedProgram prg = parser.parse_program(assembly_lines);
|
||||
|
||||
// this verifies we can convert from a string to an instruction, and back to a string again.
|
||||
// the instruction printer will add two leading spaces and a newline.
|
||||
EXPECT_EQ(prg.print(), " and v0, v1, a3\n and a1, a2, a2\n");
|
||||
EXPECT_EQ(prg.print(), assembly_lines);
|
||||
|
||||
// next, set up a test environment for the conversion. The FunctionAtomicOps will hold
|
||||
// the result of the conversion
|
||||
@ -27,8 +45,8 @@ TEST(DecompilerAtomicOpBuilder, Example) {
|
||||
convert_block_to_atomic_ops(0, prg.instructions.begin(), prg.instructions.end(), prg.labels,
|
||||
&container);
|
||||
|
||||
// we should get back a single and operation:
|
||||
EXPECT_EQ(2, container.ops.size());
|
||||
// count operations
|
||||
EXPECT_EQ(container.ops.size(), output_lines.size());
|
||||
|
||||
// for now, we create an empty environment. The environment will be used in the future to
|
||||
// rename register to variables, but for now, we just leave it empty and the printing will
|
||||
@ -36,22 +54,393 @@ TEST(DecompilerAtomicOpBuilder, Example) {
|
||||
Env env;
|
||||
|
||||
// check the we get the right result:
|
||||
EXPECT_EQ(container.ops.at(0)->to_string(prg.labels, &env), "(set! v0 (logand v1 a3))");
|
||||
EXPECT_EQ(container.ops.at(1)->to_string(prg.labels, &env), "(set! a1 (logand a2 a2))");
|
||||
for (size_t i = 0; i < container.ops.size(); i++) {
|
||||
const auto& op = container.ops.at(i);
|
||||
EXPECT_EQ(op->to_string(prg.labels, &env), output_lines.at(i));
|
||||
|
||||
// check that the registers read/written are identified for the first op (and v0, v1, a3)
|
||||
auto& first_op = container.ops.at(0);
|
||||
// check that the registers read/written are identified for the operation
|
||||
|
||||
// two registers read (v1 and a3)
|
||||
EXPECT_EQ(first_op->read_regs().size(), 2);
|
||||
// one register written (v0)
|
||||
EXPECT_EQ(first_op->write_regs().size(), 1);
|
||||
// no clobber registers (register which ends up with a garbage value in it)
|
||||
EXPECT_EQ(first_op->clobber_regs().size(), 0);
|
||||
// check write registers
|
||||
EXPECT_EQ(op->write_regs().size(), write_regs.at(i).size());
|
||||
for (size_t j = 0; j < op->write_regs().size(); j++) {
|
||||
const std::string expected_reg = op->write_regs().at(j).to_string();
|
||||
// the ordering of the registers doesn't matter. It could happen to be in the same order
|
||||
// as the opcode here, but it may not always be the case.
|
||||
bool found = false;
|
||||
for (const std::string reg : write_regs.at(i)) {
|
||||
// TODO - is there a potential bug here in the event that either list has duplicate
|
||||
// registers?
|
||||
if (reg == expected_reg) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found) << fmt::format("Unable to find expected WRITE register - {}",
|
||||
expected_reg);
|
||||
}
|
||||
|
||||
// the ordering of the two read registers doesn't matter. It happens to be in the same order
|
||||
// as the opcode here, but it may not always be the case.
|
||||
EXPECT_EQ(first_op->read_regs().at(0).to_string(), "v1");
|
||||
EXPECT_EQ(first_op->read_regs().at(1).to_string(), "a3");
|
||||
EXPECT_EQ(first_op->write_regs().at(0).to_string(), "v0");
|
||||
}
|
||||
// check read registers
|
||||
EXPECT_EQ(op->read_regs().size(), read_regs.at(i).size());
|
||||
for (size_t j = 0; j < op->read_regs().size(); j++) {
|
||||
const std::string expected_reg = op->read_regs().at(j).to_string();
|
||||
// the ordering of the registers doesn't matter. It could happen to be in the same order
|
||||
// as the opcode here, but it may not always be the case.
|
||||
bool found = false;
|
||||
for (const std::string reg : read_regs.at(i)) {
|
||||
// TODO - is there a potential bug here in the event that either list has duplicate
|
||||
// registers?
|
||||
if (reg == expected_reg) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found) << fmt::format("Unable to find expected READ register - {}", expected_reg);
|
||||
}
|
||||
|
||||
// check clobbered registers
|
||||
EXPECT_EQ(op->clobber_regs().size(), clobbered_regs.at(i).size());
|
||||
for (size_t j = 0; j < op->clobber_regs().size(); j++) {
|
||||
const std::string expected_reg = op->clobber_regs().at(j).to_string();
|
||||
// the ordering of the registers doesn't matter. It could happen to be in the same order
|
||||
// as the opcode here, but it may not always be the case.
|
||||
bool found = false;
|
||||
for (const std::string reg : clobbered_regs.at(i)) {
|
||||
// TODO - is there a potential bug here in the event that either list has duplicate
|
||||
// registers?
|
||||
if (reg == expected_reg) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found) << fmt::format("Unable to find expected CLOBBERED register - {}",
|
||||
expected_reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, Example) {
|
||||
test_case(assembly_from_list({"and v0, v1, a3", "and a1, a2, a2"}),
|
||||
{"(set! v0 (logand v1 a3))", "(set! a1 (logand a2 a2))"}, {{"v0"}, {"a1"}},
|
||||
{{"v1", "a3"}, {"a2", "a2"}}, {{}, {}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, ABS_S) {
|
||||
test_case(assembly_from_list({"abs.s f1, f2"}), {"(set! f1 (abs.s f2))"}, {{"f1"}}, {{"f2"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, ADDIU) {
|
||||
test_case(assembly_from_list({"addiu a1, r0, 12"}), {"(set! a1 12)"}, {{"a1"}}, {{}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, ADD_S) {
|
||||
test_case(assembly_from_list({"add.s f1, f2, f3"}), {"(set! f1 (+.s f2 f3))"}, {{"f1"}},
|
||||
{{"f2", "f3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, AND) {
|
||||
test_case(assembly_from_list({"and a1, a2, a3"}), {"(set! a1 (logand a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, ANDI) {
|
||||
test_case(assembly_from_list({"andi a1, a2, 1234"}), {"(set! a1 (logand a2 1234))"}, {{"a1"}},
|
||||
{{"a2"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, CVT_S_W) {
|
||||
test_case(assembly_from_list({"cvt.s.w f1, f2"}), {"(set! f1 (i2f f2))"}, {{"f1"}}, {{"f2"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, CVT_W_S) {
|
||||
test_case(assembly_from_list({"cvt.w.s f1, f2"}), {"(set! f1 (f2i f2))"}, {{"f1"}}, {{"f2"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DADDIU) {
|
||||
test_case(assembly_from_list({"daddiu a1, s7, test"}), {"(set! a1 'test)"}, {{"a1"}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"daddiu a1, s7, -10"}), {"(set! a1 '())"}, {{"a1"}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"daddiu a1, s7, -32768"}), {"(set! a1 __START-OF-TABLE__)"},
|
||||
{{"a1"}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"daddiu a1, s7, 8"}), {"(set! a1 '#t)"}, {{"a1"}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"L123:", "daddiu a1, fp, L123"}), {"(set! a1 L123)"}, {{"a1"}},
|
||||
{{}}, {{}});
|
||||
test_case(assembly_from_list({"daddiu a1, a2, 1234"}), {"(set! a1 (+ a2 1234))"}, {{"a1"}},
|
||||
{{"a2"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DADDU) {
|
||||
test_case(assembly_from_list({"daddu a1, a2, a3"}), {"(set! a1 (+ a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DIV_S) {
|
||||
test_case(assembly_from_list({"div.s f1, f2, f3"}), {"(set! f1 (/.s f2 f3))"}, {{"f1"}},
|
||||
{{"f2", "f3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSLL) {
|
||||
test_case(assembly_from_list({"dsll a2, a3, 3"}), {"(set! a2 (shl a3 3))"}, {{"a2"}}, {{"a3"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSLL32) {
|
||||
test_case(assembly_from_list({"dsll32 a2, a3, 3"}), {"(set! a2 (shl a3 35))"}, {{"a2"}}, {{"a3"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSLLV) {
|
||||
test_case(assembly_from_list({"dsllv a1, a2, a3"}), {"(set! a1 (shl a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSRA) {
|
||||
test_case(assembly_from_list({"dsra a2, a3, 3"}), {"(set! a2 (sra a3 3))"}, {{"a2"}}, {{"a3"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSRA32) {
|
||||
test_case(assembly_from_list({"dsra32 a2, a3, 3"}), {"(set! a2 (sra a3 35))"}, {{"a2"}}, {{"a3"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSRAV) {
|
||||
test_case(assembly_from_list({"dsrav a1, a2, a3"}), {"(set! a1 (sra a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSRL) {
|
||||
test_case(assembly_from_list({"dsrl a2, a3, 3"}), {"(set! a2 (srl a3 3))"}, {{"a2"}}, {{"a3"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSRL32) {
|
||||
test_case(assembly_from_list({"dsrl32 a2, a3, 3"}), {"(set! a2 (srl a3 35))"}, {{"a2"}}, {{"a3"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSRLV) {
|
||||
test_case(assembly_from_list({"dsrlv a1, a2, a3"}), {"(set! a1 (srl a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, DSUBU) {
|
||||
test_case(assembly_from_list({"dsubu a1, a2, a3"}), {"(set! a1 (- a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
test_case(assembly_from_list({"dsubu a1, r0, a3"}), {"(set! a1 (- a3))"}, {{"a1"}}, {{"a3"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, LB) {
|
||||
test_case(assembly_from_list({"L123:", "lb a3, L123(fp)"}), {"(set! a3 (l.b L123))"}, {{"a3"}},
|
||||
{{}}, {{}});
|
||||
test_case(assembly_from_list({"lb a2, 0(a3)"}), {"(set! a2 (l.b a3))"}, {{"a2"}}, {{"a3"}}, {{}});
|
||||
test_case(assembly_from_list({"lb a2, 12(a3)"}), {"(set! a2 (l.b (+ a3 12)))"}, {{"a2"}},
|
||||
{{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, LBU) {
|
||||
test_case(assembly_from_list({"L123:", "lbu a3, L123(fp)"}), {"(set! a3 (l.bu L123))"}, {{"a3"}},
|
||||
{{}}, {{}});
|
||||
test_case(assembly_from_list({"lbu a2, 0(a3)"}), {"(set! a2 (l.bu a3))"}, {{"a2"}}, {{"a3"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"lbu a2, 12(a3)"}), {"(set! a2 (l.bu (+ a3 12)))"}, {{"a2"}},
|
||||
{{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, LD) {
|
||||
test_case(assembly_from_list({"L123:", "ld a3, L123(fp)"}), {"(set! a3 (l.d L123))"}, {{"a3"}},
|
||||
{{}}, {{}});
|
||||
test_case(assembly_from_list({"ld a2, 0(a3)"}), {"(set! a2 (l.d a3))"}, {{"a2"}}, {{"a3"}}, {{}});
|
||||
test_case(assembly_from_list({"ld a2, 12(a3)"}), {"(set! a2 (l.d (+ a3 12)))"}, {{"a2"}},
|
||||
{{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, LH) {
|
||||
test_case(assembly_from_list({"L123:", "lh a3, L123(fp)"}), {"(set! a3 (l.h L123))"}, {{"a3"}},
|
||||
{{}}, {{}});
|
||||
test_case(assembly_from_list({"lh a2, 0(a3)"}), {"(set! a2 (l.h a3))"}, {{"a2"}}, {{"a3"}}, {{}});
|
||||
test_case(assembly_from_list({"lh a2, 12(a3)"}), {"(set! a2 (l.h (+ a3 12)))"}, {{"a2"}},
|
||||
{{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, LHU) {
|
||||
test_case(assembly_from_list({"L123:", "lhu a3, L123(fp)"}), {"(set! a3 (l.hu L123))"}, {{"a3"}},
|
||||
{{}}, {{}});
|
||||
test_case(assembly_from_list({"lhu a2, 0(a3)"}), {"(set! a2 (l.hu a3))"}, {{"a2"}}, {{"a3"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"lhu a2, 12(a3)"}), {"(set! a2 (l.hu (+ a3 12)))"}, {{"a2"}},
|
||||
{{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, LUI) {
|
||||
test_case(assembly_from_list({"lui a3, 2"}), {"(set! a3 131072)"}, {{"a3"}}, {{}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, LW) {
|
||||
test_case(assembly_from_list({"lw r0, 2(r0)"}), {"(break!)"}, {{}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"lw a2, test(s7)"}), {"(set! a2 test)"}, {{"a2"}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"L123:", "lw a3, L123(fp)"}), {"(set! a3 (l.w L123))"}, {{"a3"}},
|
||||
{{}}, {{}});
|
||||
test_case(assembly_from_list({"lw a2, 0(a3)"}), {"(set! a2 (l.w a3))"}, {{"a2"}}, {{"a3"}}, {{}});
|
||||
test_case(assembly_from_list({"lw a2, 12(a3)"}), {"(set! a2 (l.w (+ a3 12)))"}, {{"a2"}},
|
||||
{{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, LWC1) {
|
||||
test_case(assembly_from_list({"L123:", "lwc1 f3, L123(fp)"}), {"(set! f3 (l.f L123))"}, {{"f3"}},
|
||||
{{}}, {{}});
|
||||
test_case(assembly_from_list({"lwc1 f2, 0(a3)"}), {"(set! f2 (l.f a3))"}, {{"f2"}}, {{"a3"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"lwc1 f2, 12(a3)"}), {"(set! f2 (l.f (+ a3 12)))"}, {{"f2"}},
|
||||
{{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, LWU) {
|
||||
test_case(assembly_from_list({"L123:", "lwu a3, L123(fp)"}), {"(set! a3 (l.wu L123))"}, {{"a3"}},
|
||||
{{}}, {{}});
|
||||
test_case(assembly_from_list({"lwu a2, 0(a3)"}), {"(set! a2 (l.wu a3))"}, {{"a2"}}, {{"a3"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"lwu a2, 12(a3)"}), {"(set! a2 (l.wu (+ a3 12)))"}, {{"a2"}},
|
||||
{{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MAX_S) {
|
||||
test_case(assembly_from_list({"max.s f1, f2, f3"}), {"(set! f1 (max.s f2 f3))"}, {{"f1"}},
|
||||
{{"f2", "f3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MFC1) {
|
||||
test_case(assembly_from_list({"mfc1 a1, f3"}), {"(set! a1 (fpr->gpr f3))"}, {{"a1"}}, {{"f3"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MIN_S) {
|
||||
test_case(assembly_from_list({"min.s f1, f2, f3"}), {"(set! f1 (min.s f2 f3))"}, {{"f1"}},
|
||||
{{"f2", "f3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MOVN) {
|
||||
test_case(assembly_from_list({"movn a1, s7, a2"}), {"(cmove-#f-nonzero a1 a2)"}, {{"a1"}},
|
||||
{{"a2"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MOVZ) {
|
||||
test_case(assembly_from_list({"movz a1, s7, a2"}), {"(cmove-#f-zero a1 a2)"}, {{"a1"}}, {{"a2"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MOV_S) {
|
||||
test_case(assembly_from_list({"mov.s f1, f2"}), {"(set! f1 f2)"}, {{"f1"}}, {{"f2"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MTC1) {
|
||||
test_case(assembly_from_list({"mtc1 f3, a1"}), {"(set! f3 (gpr->fpr a1))"}, {{"f3"}}, {{"a1"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MULT3) {
|
||||
test_case(assembly_from_list({"mult3 a1, a2, a3"}), {"(set! a1 (*.si a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MULTU3) {
|
||||
test_case(assembly_from_list({"multu3 a1, a2, a3"}), {"(set! a1 (*.ui a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, MUL_S) {
|
||||
test_case(assembly_from_list({"mul.s f1, f2, f3"}), {"(set! f1 (*.s f2 f3))"}, {{"f1"}},
|
||||
{{"f2", "f3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, NEG_S) {
|
||||
test_case(assembly_from_list({"neg.s f1, f2"}), {"(set! f1 (neg.s f2))"}, {{"f1"}}, {{"f2"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, NOR) {
|
||||
test_case(assembly_from_list({"nor a1, a2, r0"}), {"(set! a1 (lognot a2))"}, {{"a1"}}, {{"a2"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"nor a1, a2, a3"}), {"(set! a1 (lognor a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, OR) {
|
||||
test_case(assembly_from_list({"or a1, a2, a3"}), {"(set! a1 (logior a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
test_case(assembly_from_list({"or a2, r0, r0"}), {"(set! a2 0)"}, {{"a2"}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"or a1, s7, r0"}), {"(set! a1 '#f)"}, {{"a1"}}, {{}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, ORI) {
|
||||
test_case(assembly_from_list({"ori a2, r0, 1234"}), {"(set! a2 1234)"}, {{"a2"}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"ori a2, r0, -1234"}), {"(set! a2 -1234)"}, {{"a2"}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"ori a2, a3, -1234"}), {"(set! a2 (logior a3 -1234))"}, {{"a2"}},
|
||||
{{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, SB) {
|
||||
test_case(assembly_from_list({"sb a1, 2(a3)"}), {"(s.b! (+ a3 2) a1)"}, {{}}, {{"a1", "a3"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"sb a1, 0(a3)"}), {"(s.b! a3 a1)"}, {{}}, {{"a1", "a3"}}, {{}});
|
||||
test_case(assembly_from_list({"sb s7, 2(a3)"}), {"(s.b! (+ a3 2) '#f)"}, {{}}, {{"a3"}}, {{}});
|
||||
test_case(assembly_from_list({"sb s7, 0(a3)"}), {"(s.b! a3 '#f)"}, {{}}, {{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, SD) {
|
||||
test_case(assembly_from_list({"sd a1, 2(a3)"}), {"(s.d! (+ a3 2) a1)"}, {{}}, {{"a1", "a3"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"sd a1, 0(a3)"}), {"(s.d! a3 a1)"}, {{}}, {{"a1", "a3"}}, {{}});
|
||||
test_case(assembly_from_list({"sd s7, 2(a3)"}), {"(s.d! (+ a3 2) '#f)"}, {{}}, {{"a3"}}, {{}});
|
||||
test_case(assembly_from_list({"sd s7, 0(a3)"}), {"(s.d! a3 '#f)"}, {{}}, {{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, SH) {
|
||||
test_case(assembly_from_list({"sh a1, 2(a3)"}), {"(s.h! (+ a3 2) a1)"}, {{}}, {{"a1", "a3"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"sh a1, 0(a3)"}), {"(s.h! a3 a1)"}, {{}}, {{"a1", "a3"}}, {{}});
|
||||
test_case(assembly_from_list({"sh s7, 2(a3)"}), {"(s.h! (+ a3 2) '#f)"}, {{}}, {{"a3"}}, {{}});
|
||||
test_case(assembly_from_list({"sh s7, 0(a3)"}), {"(s.h! a3 '#f)"}, {{}}, {{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, SLL) {
|
||||
test_case(assembly_from_list({"sll r0, r0, 0"}), {"(nop!)"}, {{}}, {{}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, SQRT_S) {
|
||||
test_case(assembly_from_list({"sqrt.s f1, f2"}), {"(set! f1 (sqrt.s f2))"}, {{"f1"}}, {{"f2"}},
|
||||
{{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, SUB_S) {
|
||||
test_case(assembly_from_list({"sub.s f1, f2, f3"}), {"(set! f1 (-.s f2 f3))"}, {{"f1"}},
|
||||
{{"f2", "f3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, SW) {
|
||||
test_case(assembly_from_list({"sw a1, test(s7)"}), {"(s.w! test a1)"}, {{}}, {{"a1"}}, {{}});
|
||||
test_case(assembly_from_list({"sw s7, test(s7)"}), {"(s.w! test '#f)"}, {{}}, {{}}, {{}});
|
||||
test_case(assembly_from_list({"sw a1, 2(a3)"}), {"(s.w! (+ a3 2) a1)"}, {{}}, {{"a1", "a3"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"sw a1, 0(a3)"}), {"(s.w! a3 a1)"}, {{}}, {{"a1", "a3"}}, {{}});
|
||||
test_case(assembly_from_list({"sw s7, 2(a3)"}), {"(s.w! (+ a3 2) '#f)"}, {{}}, {{"a3"}}, {{}});
|
||||
test_case(assembly_from_list({"sw s7, 0(a3)"}), {"(s.w! a3 '#f)"}, {{}}, {{"a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, SWC1) {
|
||||
test_case(assembly_from_list({"swc1 f2, 2(a3)"}), {"(s.f! (+ a3 2) f2)"}, {{}}, {{"f2", "a3"}},
|
||||
{{}});
|
||||
test_case(assembly_from_list({"swc1 f2, 0(a3)"}), {"(s.f! a3 f2)"}, {{}}, {{"f2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, XOR) {
|
||||
test_case(assembly_from_list({"xor a1, a2, a3"}), {"(set! a1 (logxor a2 a3))"}, {{"a1"}},
|
||||
{{"a2", "a3"}}, {{}});
|
||||
}
|
||||
|
||||
TEST(DecompilerAtomicOpBuilder, XORI) {
|
||||
test_case(assembly_from_list({"xori a1, a2, 1234"}), {"(set! a1 (logxor a2 1234))"}, {{"a1"}},
|
||||
{{"a2"}}, {{}});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user