[Decompiler] Remove most IR1 Analysis (#207)

* temp

* remove some of ir1
This commit is contained in:
water111
2021-01-22 22:03:58 -05:00
committed by GitHub
parent 8135c18e91
commit 4a97e15b40
21 changed files with 6 additions and 4105 deletions
-6
View File
@@ -15,18 +15,12 @@ add_library(
Function/BasicBlocks.cpp
Function/CfgVtx.cpp
Function/ExpressionBuilder.cpp
Function/ExpressionStack.cpp
Function/Function.cpp
Function/RegUsage.cpp
Function/TypeAnalysis.cpp
Function/TypeInspector.cpp
IR/BasicOpBuilder.cpp
IR/CfgBuilder.cpp
IR/IR.cpp
IR/IR_ExpressionStack.cpp
IR/IR_TypeAnalysis.cpp
IR2/atomic_op_builder.cpp
IR2/AtomicOp.cpp
-60
View File
@@ -1,60 +0,0 @@
#include "Function.h"
#include "decompiler/IR/IR.h"
#include "ExpressionStack.h"
namespace decompiler {
namespace {
bool expressionize_begin(IR_Begin* begin, LinkedObjectFile& file) {
ExpressionStack stack;
// todo - this might need to run multiple times?
for (auto& op : begin->forms) {
op->expression_stack(stack, file);
}
// printf("%s\n", stack.print(file).c_str());
begin->forms = stack.get_result();
return true;
}
} // namespace
bool Function::build_expression(LinkedObjectFile& file) {
if (!ir) {
printf("build_expression on %s failed due to no IR.\n", guessed_name.to_string().c_str());
return false;
}
try {
// first we get a list of begins, which are where we can build up expressions.
// we want to start with innermost begins because we'll probably need to do some fixing up
// or more complicated analysis to do as good as possible on outer begins.
auto all_children = ir->get_all_ir(file);
std::vector<IR_Begin*> all_begins;
// the top level may also be a begin
auto as_begin = dynamic_cast<IR_Begin*>(ir.get());
if (as_begin) {
all_begins.push_back(as_begin);
}
for (auto& i : all_children) {
auto child_as_begin = dynamic_cast<IR_Begin*>(i.get());
if (child_as_begin) {
all_begins.push_back(child_as_begin);
}
}
// turn each begin into an expression
for (auto b : all_begins) {
// printf("BEFORE:\n%s\n", b->print(file).c_str());
if (!expressionize_begin(b, file)) {
return false;
}
// printf("AFTER:\n%s\n", b->print(file).c_str());
}
} catch (std::exception& e) {
printf("build_expression failed on %s due to %s\n", guessed_name.to_string().c_str(), e.what());
return false;
}
return true;
}
} // namespace decompiler
-112
View File
@@ -1,112 +0,0 @@
#include "third-party/fmt/core.h"
#include "ExpressionStack.h"
namespace decompiler {
std::string ExpressionStack::StackEntry::print(LinkedObjectFile& file) {
return fmt::format("d: {} s: {} | {} <- {}", display, sequence_point,
destination.has_value() ? destination.value().to_charp() : "N/A",
source->print(file));
}
std::string ExpressionStack::print(LinkedObjectFile& file) {
std::string result;
for (auto& x : m_stack) {
result += x.print(file);
result += '\n';
}
return result;
}
void ExpressionStack::set(Register reg, std::shared_ptr<IR> value, bool sequence_point) {
StackEntry entry;
entry.display = true; // by default, we should display everything!
entry.sequence_point = sequence_point;
entry.destination = reg;
entry.source = std::move(value);
m_stack.push_back(entry);
}
bool ExpressionStack::is_single_expression() {
int count = 0;
for (auto& e : m_stack) {
if (e.display) {
count++;
}
}
return count == 1;
}
void ExpressionStack::add_no_set(std::shared_ptr<IR> value, bool sequence_point) {
StackEntry entry;
entry.display = true;
entry.destination = std::nullopt;
entry.source = value;
entry.sequence_point = sequence_point;
m_stack.push_back(entry);
}
/*!
* "Remove" an entry from the stack. Cannot cross a sequence point.
* Internally, the entry is still stored. It is just flagged with display=false.
*/
std::shared_ptr<IR> ExpressionStack::get(Register reg) {
for (size_t i = m_stack.size(); i-- > 0;) {
auto& entry = m_stack.at(i);
if (entry.display) {
if (entry.destination == reg) {
entry.display = false;
return entry.source;
} else {
// we didn't match
if (entry.sequence_point) {
// and it's a sequence point! can't look any more back than this.
return std::make_shared<IR_Register>(reg, -1);
}
}
}
}
return std::make_shared<IR_Register>(reg, -1);
}
/*!
* Convert the stack into a sequence of compacted expressions.
* This is final result of the expression compaction algorithm.
*/
std::vector<std::shared_ptr<IR>> ExpressionStack::get_result() {
std::vector<std::shared_ptr<IR>> result;
for (auto& e : m_stack) {
if (!e.display) {
continue;
}
if (e.destination.has_value()) {
auto dst_reg = std::make_shared<IR_Register>(e.destination.value(), -1);
auto op = std::make_shared<IR_Set>(IR_Set::EXPR, dst_reg, e.source);
result.push_back(op);
} else {
result.push_back(e.source);
}
}
return result;
}
bool ExpressionStack::display_stack_empty() {
for (auto& e : m_stack) {
if (e.display) {
return false;
}
}
return true;
}
ExpressionStack::StackEntry& ExpressionStack::get_display_stack_top() {
for (size_t i = m_stack.size(); i-- > 0;) {
auto& entry = m_stack.at(i);
if (entry.display) {
return entry;
}
}
assert(false);
}
} // namespace decompiler
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include <vector>
#include <optional>
#include "decompiler/IR/IR.h"
#include "decompiler/Disasm/Register.h"
#include "decompiler/util/TP_Type.h"
namespace decompiler {
/*!
* An ExpressionStack is used to track partial expressions when rebuilding the tree structure of
* GOAL code. Linear sequences of operations are added onto the expression stack.
*/
class ExpressionStack {
public:
ExpressionStack() = default;
void set(Register reg, std::shared_ptr<IR> value, bool sequence_point);
void add_no_set(std::shared_ptr<IR> value, bool sequence_point);
std::shared_ptr<IR> get(Register reg);
bool is_single_expression();
std::string print(LinkedObjectFile& file);
std::vector<std::shared_ptr<IR>> get_result();
private:
struct StackEntry {
bool display = true; // should this appear in the output?
std::optional<Register> destination; // what register we are setting (or nullopt if no dest.)
std::shared_ptr<IR> source; // the value we are setting the register to.
bool sequence_point = false;
// TP_Type type;
std::string print(LinkedObjectFile& file);
};
std::vector<StackEntry> m_stack;
bool display_stack_empty();
StackEntry& get_display_stack_top();
};
} // namespace decompiler
-10
View File
@@ -711,16 +711,6 @@ int Function::get_failed_basic_op_count() {
return count;
}
int Function::get_reginfo_basic_op_count() {
int count = 0;
for (auto& x : basic_ops) {
if (x->reg_info_set) {
count++;
}
}
return count;
}
/*!
* Topological sort of basic blocks.
* Returns a valid ordering + a list of blocks that you can't reach and therefore
+1 -7
View File
@@ -89,17 +89,11 @@ class Function {
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();
bool run_type_analysis(const TypeSpec& my_type,
DecompilerTypeSystem& dts,
LinkedObjectFile& file,
const std::unordered_map<int, std::vector<TypeHint>>& hints);
bool run_type_analysis_ir2(const TypeSpec& my_type,
DecompilerTypeSystem& dts,
LinkedObjectFile& file,
const std::unordered_map<int, std::vector<TypeHint>>& hints);
void run_reg_usage();
bool build_expression(LinkedObjectFile& file);
BlockTopologicalSort bb_topo_sort();
TypeSpec type;
-175
View File
@@ -1,175 +0,0 @@
#include "Function.h"
#include "decompiler/IR/IR.h"
namespace decompiler {
namespace {
bool in_set(RegSet& set, const Register& obj) {
return set.find(obj) != set.end();
}
void phase1(Function& f, BasicBlock& block) {
for (int i = block.end_basic_op; i-- > block.start_basic_op;) {
auto& instr = f.basic_ops.at(i);
auto& lv = block.live.at(i - block.start_basic_op);
auto& dd = block.dead.at(i - block.start_basic_op);
// make all read live out
auto read = instr->read_regs;
lv.clear();
for (auto& x : read) {
lv.insert(x);
}
// kill things which are overwritten
dd.clear();
auto write = instr->write_regs;
for (auto& x : write) {
if (!in_set(lv, x)) {
dd.insert(x);
}
}
// b.use = i.liveout
RegSet use_old = block.use;
block.use.clear();
for (auto& x : lv) {
block.use.insert(x);
}
// | (bu.use & !i.dead)
for (auto& x : use_old) {
if (!in_set(dd, x)) {
block.use.insert(x);
}
}
// b.defs = i.dead
RegSet defs_old = block.defs;
block.defs.clear();
for (auto& x : dd) {
block.defs.insert(x);
}
// | b.defs & !i.lv
for (auto& x : defs_old) {
if (!in_set(lv, x)) {
block.defs.insert(x);
}
}
}
}
bool phase2(std::vector<BasicBlock>& blocks, BasicBlock& block) {
bool changed = false;
auto out = block.defs;
for (auto s : {block.succ_branch, block.succ_ft}) {
if (s == -1) {
continue;
}
for (auto in : blocks.at(s).input) {
out.insert(in);
}
}
RegSet in = block.use;
for (auto x : out) {
if (!in_set(block.defs, x)) {
in.insert(x);
}
}
if (in != block.input || out != block.output) {
changed = true;
block.input = in;
block.output = out;
}
return changed;
}
void phase3(std::vector<BasicBlock>& blocks, BasicBlock& block) {
RegSet live_local;
for (auto s : {block.succ_branch, block.succ_ft}) {
if (s == -1) {
continue;
}
for (auto i : blocks.at(s).input) {
live_local.insert(i);
}
}
for (int i = block.end_basic_op; i-- > block.start_basic_op;) {
auto& lv = block.live.at(i - block.start_basic_op);
auto& dd = block.dead.at(i - block.start_basic_op);
RegSet new_live = lv;
for (auto x : live_local) {
if (!in_set(dd, x)) {
new_live.insert(x);
}
}
lv = live_local;
live_local = new_live;
}
}
} // namespace
/*!
* Analyze the function use of registers to determine which are live where.
*/
void Function::run_reg_usage() {
// phase 1
for (auto& block : basic_blocks) {
block.live.resize(block.basic_op_size());
block.dead.resize(block.basic_op_size());
phase1(*this, block);
}
// phase 2
bool changed = false;
do {
changed = false;
for (auto& block : basic_blocks) {
if (phase2(basic_blocks, block)) {
changed = true;
}
}
} while (changed);
// phase 3
for (auto& block : basic_blocks) {
phase3(basic_blocks, block);
}
// we want to know if an op "consumes" a register.
// this means that the value of the register coming in to the operation is:
// A. read by the operation.
// B. no longer read after the operation.
for (auto& block : basic_blocks) {
for (int i = block.start_basic_op; i < block.end_basic_op; i++) {
auto& op = basic_ops.at(i);
// look at each register that we read
for (auto reg : op->read_regs) {
if (!block.op_has_reg_live_out(i, reg)) {
// if the register is not live out, we definitely consume it.
op->consumed.insert(reg);
} else {
// it's live out... but it could be a new value.
for (auto wr : op->write_regs) {
if (wr == reg) {
op->consumed.insert(reg);
}
}
}
}
for (auto reg : op->write_regs) {
if (!block.op_has_reg_live_out(i, reg)) {
// we wrote it, but it is immediately dead. this is nice to know for things like
// "is this if/and/or expression used as a value?"
op->written_and_unused.insert(reg);
}
}
}
}
}
} // namespace decompiler
+1 -92
View File
@@ -44,97 +44,6 @@ void try_apply_hints(int idx,
}
} // namespace
bool Function::run_type_analysis(const TypeSpec& my_type,
DecompilerTypeSystem& dts,
LinkedObjectFile& file,
const std::unordered_map<int, std::vector<TypeHint>>& hints) {
// STEP 0 - setup settings
dts.type_prop_settings.reset();
if (get_config().pair_functions_by_name.find(guessed_name.to_string()) !=
get_config().pair_functions_by_name.end()) {
dts.type_prop_settings.allow_pair = true;
}
if (guessed_name.kind == FunctionName::FunctionKind::METHOD) {
dts.type_prop_settings.current_method_type = guessed_name.type_name;
}
// STEP 1 - get the topo sort.
auto order = bb_topo_sort();
// fmt::print("blocks: {}\n ", basic_blocks.size());
// for (auto x : order.vist_order) {
// fmt::print("{} ", x);
// }
// fmt::print("\n");
// STEP 2 - establish visit order
assert(!order.vist_order.empty());
assert(order.vist_order.front() == 0);
// STEP 3 - initialize type state.
basic_blocks.at(0).init_types = construct_initial_typestate(my_type);
// and add hints:
try_apply_hints(0, hints, &basic_blocks.at(0).init_types, dts);
// STEP 2 - loop while types are changing
bool run_again = true;
while (run_again) {
run_again = false;
// each block in order now.
for (auto block_id : order.vist_order) {
auto& block = basic_blocks.at(block_id);
TypeState* init_types = &block.init_types;
for (int op_id = block.start_basic_op; op_id < block.end_basic_op; op_id++) {
auto& op = basic_ops.at(op_id);
// apply type hints only if we are not the first op.
if (op_id != block.start_basic_op) {
try_apply_hints(op_id, hints, init_types, dts);
}
// while the implementation of propagate_types_internal is in progress, it may throw
// for unimplemented cases. Eventually this try/catch should be removed.
try {
op->propagate_types(*init_types, file, dts);
} catch (std::runtime_error& e) {
fmt::print("Type prop fail on {}: {}\n", guessed_name.to_string(), e.what());
warnings += ";; Type prop attempted and failed.\n";
return false;
}
// todo, set run again??
// for the next op...
init_types = &op->end_types;
}
// propagate the types: for each possible succ
for (auto succ_block_id : {block.succ_ft, block.succ_branch}) {
if (succ_block_id != -1) {
auto& succ_block = basic_blocks.at(succ_block_id);
// apply hint
try_apply_hints(succ_block.start_basic_op, hints, init_types, dts);
// set types to LCA (current, new)
if (dts.tp_lca(&succ_block.init_types, *init_types)) {
// if something changed, run again!
run_again = true;
}
}
}
}
}
auto last_op = basic_ops.back();
auto last_type = last_op->end_types.get(Register(Reg::GPR, Reg::V0)).typespec();
if (last_type != my_type.last_arg()) {
warnings += fmt::format(";; return type mismatch {} vs {}. ", last_type.print(),
my_type.last_arg().print());
}
return true;
}
bool Function::run_type_analysis_ir2(const TypeSpec& my_type,
DecompilerTypeSystem& dts,
LinkedObjectFile& file,
@@ -232,4 +141,4 @@ bool Function::run_type_analysis_ir2(const TypeSpec& my_type,
return true;
}
} // namespace decompiler
} // namespace decompiler
File diff suppressed because it is too large Load Diff
-12
View File
@@ -1,12 +0,0 @@
#pragma once
#include <memory>
namespace decompiler {
class IR;
class Function;
class LinkedObjectFile;
class ControlFlowGraph;
std::shared_ptr<IR> build_cfg_ir(Function& function, ControlFlowGraph& cfg, LinkedObjectFile& file);
} // namespace decompiler
-266
View File
@@ -49,64 +49,6 @@ void add_regs_to_str(const T& regs, std::string& str) {
}
} // namespace
std::string IR_Atomic::print_with_reguse(const LinkedObjectFile& file) const {
std::string result = print(file);
if (result.length() < 40) {
result.append(40 - result.length(), ' ');
}
result += " ;;";
if (!write_regs.empty()) {
result += "write: [";
add_regs_to_str(write_regs, result);
result += "] ";
}
if (!read_regs.empty()) {
result += "read: [";
add_regs_to_str(read_regs, result);
result += "] ";
}
if (!clobber_regs.empty()) {
result += "clobber: [";
add_regs_to_str(clobber_regs, result);
result += "] ";
}
if (!consumed.empty()) {
result += "consumed: [";
add_regs_to_str(consumed, result);
result += "] ";
}
return result;
}
std::string IR_Atomic::print_with_types(const TypeState& init_types,
const LinkedObjectFile& file) const {
std::string result;
for (auto& warning : warnings) {
result += ";; warn: " + warning + "\n";
}
result += print(file);
if (result.length() < 40) {
result.append(40 - result.length(), ' ');
}
result += " ;; ";
auto read_mask = regs_to_gpr_mask(read_regs);
auto write_mask = regs_to_gpr_mask(write_regs);
result += fmt::format("[{}] -> [{}]", init_types.print_gpr_masked(read_mask),
end_types.print_gpr_masked(write_mask));
if (!consumed.empty()) {
result += "c:";
for (auto x : consumed) {
result += " ";
result += x.to_charp();
}
}
return result;
}
goos::Object IR_Failed::to_form(const LinkedObjectFile& file) const {
(void)file;
return pretty_print::build_list("INVALID-OPERATION");
@@ -996,189 +938,6 @@ void IR_Breakpoint_Atomic::get_children(std::vector<std::shared_ptr<IR>>* output
(void)output;
}
goos::Object IR_Begin::to_form(const LinkedObjectFile& file) const {
if (forms.size() == 1 && inline_single_begins) {
return forms.front()->to_form(file);
}
std::vector<goos::Object> list;
list.push_back(pretty_print::to_symbol("begin"));
for (auto& x : forms) {
list.push_back(x->to_form(file));
}
return pretty_print::build_list(list);
}
void IR_Begin::get_children(std::vector<std::shared_ptr<IR>>* output) const {
for (auto& x : forms) {
output->push_back(x);
}
}
namespace {
void print_inlining_begin(std::vector<goos::Object>* output, IR* ir, const LinkedObjectFile& file) {
auto as_begin = dynamic_cast<IR_Begin*>(ir);
if (as_begin) {
for (auto& x : as_begin->forms) {
output->push_back(x->to_form(file));
}
} else {
output->push_back(ir->to_form(file));
}
}
bool is_single_expression(IR* in) {
return !dynamic_cast<IR_Begin*>(in);
}
} // namespace
goos::Object IR_WhileLoop::to_form(const LinkedObjectFile& file) const {
std::vector<goos::Object> list;
list.push_back(pretty_print::to_symbol("while"));
list.push_back(condition->to_form(file));
print_inlining_begin(&list, body.get(), file);
return pretty_print::build_list(list);
}
void IR_WhileLoop::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(condition);
output->push_back(body);
}
goos::Object IR_UntilLoop::to_form(const LinkedObjectFile& file) const {
std::vector<goos::Object> list;
list.push_back(pretty_print::to_symbol("until"));
list.push_back(condition->to_form(file));
print_inlining_begin(&list, body.get(), file);
return pretty_print::build_list(list);
}
void IR_UntilLoop::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(condition);
output->push_back(body);
}
goos::Object IR_CondWithElse::to_form(const LinkedObjectFile& file) const {
// for now we only turn it into an if statement if both cases won't require a begin at the top
// level. I think it is more common to write these as a two-case cond instead of an if with begin.
if (entries.size() == 1 && is_single_expression(entries.front().body.get()) &&
is_single_expression(else_ir.get())) {
std::vector<goos::Object> list;
list.push_back(pretty_print::to_symbol("if"));
list.push_back(entries.front().condition->to_form(file));
list.push_back(entries.front().body->to_form(file));
list.push_back(else_ir->to_form(file));
return pretty_print::build_list(list);
} else {
std::vector<goos::Object> list;
list.push_back(pretty_print::to_symbol("cond"));
for (auto& e : entries) {
std::vector<goos::Object> entry;
entry.push_back(e.condition->to_form(file));
print_inlining_begin(&entry, e.body.get(), file);
list.push_back(pretty_print::build_list(entry));
}
std::vector<goos::Object> else_form;
else_form.push_back(pretty_print::to_symbol("else"));
print_inlining_begin(&else_form, else_ir.get(), file);
list.push_back(pretty_print::build_list(else_form));
return pretty_print::build_list(list);
}
}
void IR_CondWithElse::get_children(std::vector<std::shared_ptr<IR>>* output) const {
for (auto& e : entries) {
output->push_back(e.condition);
output->push_back(e.body);
}
output->push_back(else_ir);
}
goos::Object IR_GetRuntimeType::to_form(const LinkedObjectFile& file) const {
std::vector<goos::Object> list = {pretty_print::to_symbol("type-of"), object->to_form(file)};
return pretty_print::build_list(list);
}
void IR_GetRuntimeType::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(object);
}
goos::Object IR_Cond::to_form(const LinkedObjectFile& file) const {
if (entries.size() == 1 && is_single_expression(entries.front().body.get())) {
// print as an if statement if we can put the body in a single form.
std::vector<goos::Object> list;
list.push_back(pretty_print::to_symbol("if"));
list.push_back(entries.front().condition->to_form(file));
list.push_back(entries.front().body->to_form(file));
return pretty_print::build_list(list);
} else if (entries.size() == 1) {
// turn into a when if the body requires multiple forms
// todo check to see if the condition starts with a NOT and this can be simplified to an
// unless.
std::vector<goos::Object> list;
list.push_back(pretty_print::to_symbol("when"));
list.push_back(entries.front().condition->to_form(file));
print_inlining_begin(&list, entries.front().body.get(), file);
return pretty_print::build_list(list);
} else {
std::vector<goos::Object> list;
list.push_back(pretty_print::to_symbol("cond"));
for (auto& e : entries) {
std::vector<goos::Object> entry;
entry.push_back(e.condition->to_form(file));
print_inlining_begin(&entry, e.body.get(), file);
list.push_back(pretty_print::build_list(entry));
}
return pretty_print::build_list(list);
}
}
void IR_Cond::get_children(std::vector<std::shared_ptr<IR>>* output) const {
for (auto& e : entries) {
output->push_back(e.condition);
output->push_back(e.body);
}
}
goos::Object IR_ShortCircuit::to_form(const LinkedObjectFile& file) const {
std::vector<goos::Object> forms;
switch (kind) {
case UNKNOWN:
forms.push_back(pretty_print::to_symbol("unknown-sc"));
break;
case AND:
forms.push_back(pretty_print::to_symbol("and"));
break;
case OR:
forms.push_back(pretty_print::to_symbol("or"));
break;
default:
assert(false);
}
for (auto& x : entries) {
forms.push_back(x.condition->to_form(file));
}
return pretty_print::build_list(forms);
}
void IR_ShortCircuit::get_children(std::vector<std::shared_ptr<IR>>* output) const {
for (auto& x : entries) {
output->push_back(x.condition);
if (x.output) {
output->push_back(x.output);
}
}
}
goos::Object IR_Ash::to_form(const LinkedObjectFile& file) const {
return pretty_print::build_list(pretty_print::to_symbol(is_signed ? "ash.si" : "ash.ui"),
value->to_form(file), shift_amount->to_form(file));
}
void IR_Ash::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(value);
output->push_back(shift_amount);
}
goos::Object IR_AsmOp::to_form(const LinkedObjectFile& file) const {
std::vector<goos::Object> forms;
forms.push_back(pretty_print::to_symbol(name));
@@ -1240,29 +999,4 @@ void IR_AsmReg::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
goos::Object IR_Return::to_form(const LinkedObjectFile& file) const {
std::vector<goos::Object> forms;
forms.push_back(pretty_print::to_symbol("return"));
forms.push_back(pretty_print::build_list(return_code->to_form(file)));
forms.push_back(pretty_print::build_list(dead_code->to_form(file)));
return pretty_print::build_list(forms);
}
void IR_Return::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(return_code);
output->push_back(dead_code);
}
goos::Object IR_Break::to_form(const LinkedObjectFile& file) const {
std::vector<goos::Object> forms;
forms.push_back(pretty_print::to_symbol("break")); // todo break destination...
forms.push_back(pretty_print::build_list(return_code->to_form(file)));
forms.push_back(pretty_print::build_list(dead_code->to_form(file)));
return pretty_print::build_list(forms);
}
void IR_Break::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(return_code);
output->push_back(dead_code);
}
} // namespace decompiler
-318
View File
@@ -27,29 +27,6 @@ class IR {
std::string print(const LinkedObjectFile& file) const;
virtual void get_children(std::vector<std::shared_ptr<IR>>* output) const = 0;
bool is_basic_op = false;
virtual TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts);
// update the expression stack
virtual bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
(void)stack;
(void)file;
throw std::runtime_error("expression_stack NYI for " + print(file));
}
// update myself to use consumed registers from the stack.
virtual bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
(void)consume;
(void)stack;
throw std::runtime_error("update_from_stack NYI for " + print(file));
}
virtual std::unordered_set<Register, Register::hash> get_consumed(LinkedObjectFile& file) {
throw std::runtime_error("get_consumed NYI for " + print(file));
}
virtual ~IR() = default;
};
@@ -62,12 +39,6 @@ class IR_Atomic : public virtual IR {
TypeState end_types; // types at the end of this instruction
std::vector<std::string> warnings;
void warn(const std::string& str) { warnings.emplace_back(str); }
virtual void propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts);
std::string print_with_types(const TypeState& init_types, const LinkedObjectFile& file) const;
std::string print_with_reguse(const LinkedObjectFile& file) const;
};
class IR_Failed : public virtual IR {
@@ -89,9 +60,6 @@ class IR_Register : public virtual IR {
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
Register reg;
int instr_idx = -1;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
};
class IR_Set : public virtual IR {
@@ -112,7 +80,6 @@ class IR_Set : public virtual IR {
: kind(_kind), dst(std::move(_dst)), src(std::move(_src)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
std::shared_ptr<IR> dst, src;
std::shared_ptr<IR> clobber = nullptr;
@@ -127,10 +94,6 @@ class IR_Set_Atomic : public IR_Set, public IR_Atomic {
template <typename T>
void update_reginfo_self(int n_dest, int n_src, int n_clobber);
void update_reginfo_regreg();
void propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
};
class IR_IntMath2;
@@ -158,9 +121,6 @@ class IR_Store_Atomic : public IR_Set_Atomic {
int size;
goos::Object to_form(const LinkedObjectFile& file) const override;
void update_reginfo_self(int n_dest, int n_src, int n_clobber);
void propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
};
class IR_Symbol : public virtual IR {
@@ -169,17 +129,6 @@ class IR_Symbol : public virtual IR {
std::string name;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override {
(void)consume;
(void)stack;
(void)file;
return true;
}
};
class IR_SymbolValue : public virtual IR {
@@ -188,17 +137,6 @@ class IR_SymbolValue : public virtual IR {
std::string name;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override {
(void)consume;
(void)stack;
(void)file;
return true;
}
};
class IR_EmptyPair : public virtual IR {
@@ -206,17 +144,6 @@ class IR_EmptyPair : public virtual IR {
explicit IR_EmptyPair() = default;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override {
(void)consume;
(void)stack;
(void)file;
return true;
}
};
class IR_StaticAddress : public virtual IR {
@@ -225,12 +152,6 @@ class IR_StaticAddress : public virtual IR {
int label_id = -1;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
};
class IR_Load : public virtual IR {
@@ -243,12 +164,6 @@ class IR_Load : public virtual IR {
std::shared_ptr<IR> location;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
// this load_path stuff is just for debugging and shouldn't be used as part of the real
// decompilation.
@@ -272,12 +187,6 @@ class IR_FloatMath2 : public virtual IR {
std::shared_ptr<IR> arg0, arg1;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
};
class IR_FloatMath1 : public virtual IR {
@@ -287,12 +196,6 @@ class IR_FloatMath1 : public virtual IR {
std::shared_ptr<IR> arg;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
};
class IR_IntMath2 : public virtual IR {
@@ -321,12 +224,6 @@ class IR_IntMath2 : public virtual IR {
std::shared_ptr<IR> arg0, arg1;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
};
class IR_IntMath1 : public virtual IR {
@@ -341,13 +238,6 @@ class IR_IntMath1 : public virtual IR {
std::shared_ptr<IR_Atomic> abs_op = nullptr;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
std::unordered_set<Register, Register::hash> get_consumed(LinkedObjectFile& file) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
};
class IR_Call : public virtual IR {
@@ -364,10 +254,6 @@ class IR_Call : public virtual IR {
class IR_Call_Atomic : public virtual IR_Call, public IR_Atomic {
public:
IR_Call_Atomic() = default;
void propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
};
class IR_IntegerConstant : public virtual IR {
@@ -376,17 +262,6 @@ class IR_IntegerConstant : public virtual IR {
explicit IR_IntegerConstant(int64_t _value) : value(_value) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override {
(void)consume;
(void)stack;
(void)file;
return true;
}
};
struct BranchDelay {
@@ -492,9 +367,6 @@ class IR_Branch_Atomic : public virtual IR_Branch, public IR_Atomic {
: IR_Branch(std::move(_condition), _dest_label_idx, std::move(_branch_delay), _likely) {}
// note - counts only for the condition.
void update_reginfo_self(int n_dst, int n_src, int n_clobber);
void propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
};
class IR_Compare : public virtual IR {
@@ -512,14 +384,6 @@ class IR_Compare : public virtual IR {
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
std::unordered_set<Register, Register::hash> get_consumed(LinkedObjectFile& file) override;
};
class IR_Nop : public virtual IR {
@@ -527,15 +391,11 @@ class IR_Nop : public virtual IR {
IR_Nop() = default;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
};
class IR_Nop_Atomic : public IR_Nop, public IR_Atomic {
public:
IR_Nop_Atomic() = default;
void propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
};
class IR_Suspend_Atomic : public virtual IR, public IR_Atomic {
@@ -543,14 +403,6 @@ class IR_Suspend_Atomic : public virtual IR, public IR_Atomic {
IR_Suspend_Atomic() = default;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
void propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override {
(void)stack;
(void)file;
return true;
}
};
class IR_Breakpoint_Atomic : public virtual IR_Atomic {
@@ -558,147 +410,6 @@ class IR_Breakpoint_Atomic : public virtual IR_Atomic {
IR_Breakpoint_Atomic() = default;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
void propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override {
(void)stack;
(void)file;
return true;
}
};
class IR_Begin : public virtual IR {
public:
IR_Begin() = default;
explicit IR_Begin(const std::vector<std::shared_ptr<IR>>& _forms) : forms(std::move(_forms)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
std::vector<std::shared_ptr<IR>> forms;
};
class IR_WhileLoop : public virtual IR {
public:
IR_WhileLoop(std::shared_ptr<IR> _condition, std::shared_ptr<IR> _body)
: condition(std::move(_condition)), body(std::move(_body)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
std::shared_ptr<IR> condition, body;
bool cleaned = false;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
};
class IR_UntilLoop : public virtual IR {
public:
IR_UntilLoop(std::shared_ptr<IR> _condition, std::shared_ptr<IR> _body)
: condition(std::move(_condition)), body(std::move(_body)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
std::shared_ptr<IR> condition, body;
};
class IR_CondWithElse : public virtual IR {
public:
struct Entry {
std::shared_ptr<IR> condition = nullptr;
std::shared_ptr<IR> body = nullptr;
bool cleaned = false;
};
std::vector<Entry> entries;
std::shared_ptr<IR> else_ir;
IR_CondWithElse(std::vector<Entry> _entries, std::shared_ptr<IR> _else_ir)
: entries(std::move(_entries)), else_ir(std::move(_else_ir)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
};
// this one doesn't have an else statement. Will return false if none of the cases are taken.
class IR_Cond : public virtual IR {
public:
struct Entry {
std::shared_ptr<IR> condition = nullptr;
std::shared_ptr<IR> body = nullptr;
std::shared_ptr<IR> false_destination = nullptr;
std::shared_ptr<IR> original_condition_branch = nullptr;
bool cleaned = false;
};
Register final_destination;
bool used_as_value = false;
std::vector<Entry> entries;
explicit IR_Cond(std::vector<Entry> _entries) : entries(std::move(_entries)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
};
// this will work on pairs, bintegers, or basics
class IR_GetRuntimeType : public virtual IR {
public:
std::shared_ptr<IR> object, clobber;
IR_GetRuntimeType(std::shared_ptr<IR> _object, std::shared_ptr<IR> _clobber)
: object(std::move(_object)), clobber(std::move(_clobber)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
std::unordered_set<Register, Register::hash> get_consumed(LinkedObjectFile& file) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
};
class IR_ShortCircuit : public virtual IR {
public:
struct Entry {
std::shared_ptr<IR> 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
std::shared_ptr<IR> output = nullptr;
bool is_output_trick = false;
bool cleaned = false;
};
enum Kind { UNKNOWN, AND, OR } kind = UNKNOWN;
std::shared_ptr<IR> final_result = nullptr; // the register that the final result goes in.
std::vector<Entry> entries;
std::optional<bool> used_as_value = std::nullopt;
explicit IR_ShortCircuit(std::vector<Entry> _entries) : entries(std::move(_entries)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
};
class IR_Ash : public virtual IR {
public:
std::shared_ptr<IR> shift_amount, value, clobber;
std::shared_ptr<IR_Atomic> branch_op, sub_op, shift_op;
bool is_signed = true;
IR_Ash(std::shared_ptr<IR> _shift_amount,
std::shared_ptr<IR> _value,
std::shared_ptr<IR> _clobber,
std::shared_ptr<IR_Atomic> _branch_op,
std::shared_ptr<IR_Atomic> _sub_op,
std::shared_ptr<IR_Atomic> _shift_op,
bool _is_signed)
: shift_amount(std::move(_shift_amount)),
value(std::move(_value)),
clobber(std::move(_clobber)),
branch_op(std::move(_branch_op)),
sub_op(std::move(_sub_op)),
shift_op(std::move(_shift_op)),
is_signed(_is_signed) {
assert(sub_op);
assert(shift_op);
assert(branch_op);
}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
std::unordered_set<Register, Register::hash> get_consumed(LinkedObjectFile& file) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
};
class IR_AsmOp : public virtual IR {
@@ -711,16 +422,12 @@ class IR_AsmOp : public virtual IR {
IR_AsmOp(std::string _name) : name(std::move(_name)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
bool expression_stack(ExpressionStack& stack, LinkedObjectFile& file) override;
};
class IR_AsmOp_Atomic : public virtual IR_AsmOp, public IR_Atomic {
public:
IR_AsmOp_Atomic(std::string _name) : IR_AsmOp(std::move(_name)) {}
void set_reg_info();
void propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
};
class IR_CMoveF : public virtual IR {
@@ -731,12 +438,6 @@ class IR_CMoveF : public virtual IR {
: src(std::move(_src)), on_zero(_on_zero) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
TP_Type get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) override;
bool update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) override;
};
class IR_AsmReg : public virtual IR {
@@ -747,24 +448,5 @@ class IR_AsmReg : public virtual IR {
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_Return : public virtual IR {
public:
std::shared_ptr<IR> return_code;
std::shared_ptr<IR> dead_code;
IR_Return(std::shared_ptr<IR> _return_code, std::shared_ptr<IR> _dead_code)
: return_code(std::move(_return_code)), dead_code(std::move(_dead_code)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_Break : public virtual IR {
public:
std::shared_ptr<IR> return_code;
std::shared_ptr<IR> dead_code;
IR_Break(std::shared_ptr<IR> _return_code, std::shared_ptr<IR> _dead_code)
: return_code(std::move(_return_code)), dead_code(std::move(_dead_code)) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
} // namespace decompiler
#endif // JAK_IR_H
-453
View File
@@ -1,453 +0,0 @@
#include <algorithm>
#include "IR.h"
#include "decompiler/Function/ExpressionStack.h"
namespace decompiler {
bool IR_Set_Atomic::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
// first determine the type of the set.
switch (kind) {
case IR_Set::REG_64:
case IR_Set::LOAD:
case IR_Set::GPR_TO_FPR: // TODO - this should probably not be invisible.
case IR_Set::FPR_TO_GPR64:
case IR_Set::REG_FLT:
case IR_Set::SYM_LOAD: {
// normal 64-bit GPR set!
// first, we update our source to substitute in more complicated expressions.
auto src_as_reg = dynamic_cast<IR_Register*>(src.get());
if (src_as_reg) {
// we're reading a register. Let's find out if it's safe to directly copy it's value.
if (consumed.find(src_as_reg->reg) != consumed.end()) {
// yep. Let's read it off of the stack.
src = stack.get(src_as_reg->reg);
}
} else {
// our source is some expression. we need to make sure the expression is up-to-date.
src->update_from_stack(consumed, stack, file);
}
// next, we tell the stack the value of the register we just set
auto dest_reg = dynamic_cast<IR_Register*>(dst.get());
assert(dest_reg);
// sequence point if not a register -> register set.
stack.set(dest_reg->reg, src, !src_as_reg);
return true;
}
case IR_Set::STORE:
case IR_Set::SYM_STORE: {
auto src_as_reg = dynamic_cast<IR_Register*>(src.get());
if (src_as_reg) {
// we're reading a register. Let's find out if it's safe to directly copy it's value.
if (consumed.find(src_as_reg->reg) != consumed.end()) {
// yep. Let's read it off of the stack.
src = stack.get(src_as_reg->reg);
}
} else {
// our source is some expression. we need to make sure the expression is up-to-date.
src->update_from_stack(consumed, stack, file);
}
stack.add_no_set(std::make_shared<IR_Set_Atomic>(*this), true);
return true;
}
break;
default:
throw std::runtime_error("IR_Set_Atomic::expression_stack NYI for " + print(file));
}
}
bool IR_Set::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
// first determine the type of the set.
switch (kind) {
case IR_Set::REG_64:
case IR_Set::LOAD:
case IR_Set::GPR_TO_FPR: // TODO - this should probably not be invisible.
case IR_Set::FPR_TO_GPR64:
case IR_Set::REG_FLT: {
// normal 64-bit GPR set!
// first, we update our source to substitute in more complicated expressions.
auto consumed = src->get_consumed(file);
auto src_as_reg = dynamic_cast<IR_Register*>(src.get());
if (src_as_reg) {
// an annoying special case.
if (consumed.find(src_as_reg->reg) != consumed.end()) {
// we consume it.
src = stack.get(src_as_reg->reg);
}
} else {
src->update_from_stack(consumed, stack, file);
}
// next, we tell the stack the value of the register we just set
auto dest_reg = dynamic_cast<IR_Register*>(dst.get());
assert(dest_reg);
stack.set(dest_reg->reg, src, !src_as_reg);
return true;
}
break;
default:
throw std::runtime_error("IR_Set_Atomic::expression_stack NYI for " + print(file));
}
}
bool IR_Call_Atomic::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
(void)file;
if (!call_type_set) {
throw std::runtime_error("Call type is unknown on an IR_Call_Atomic");
}
const Reg::Gpr arg_regs[8] = {Reg::A0, Reg::A1, Reg::A2, Reg::A3,
Reg::T0, Reg::T1, Reg::T2, Reg::T3};
int nargs = int(call_type.arg_count()) - 1;
// printf("%s\n", stack.print(file).c_str());
// get all arguments.
for (int i = nargs; i-- > 0;) {
args.push_back(stack.get(Register(Reg::GPR, arg_regs[i])));
}
args.push_back(stack.get(Register(Reg::GPR, Reg::T9)));
std::reverse(args.begin(), args.end());
auto return_type = call_type.get_arg(call_type.arg_count() - 1);
// bleh...
stack.set(Register(Reg::GPR, Reg::V0), std::make_shared<IR_Call_Atomic>(*this), true);
return true;
}
bool IR_UntilLoop::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
(void)stack;
(void)file;
stack.add_no_set(std::make_shared<IR_UntilLoop>(*this), true);
return true;
}
namespace {
void update_from_stack_helper(std::shared_ptr<IR>* ir,
const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
auto as_reg = dynamic_cast<IR_Register*>(ir->get());
if (as_reg) {
if (consume.find(as_reg->reg) != consume.end()) {
*ir = stack.get(as_reg->reg);
}
} else {
(*ir)->update_from_stack(consume, stack, file);
}
}
} // namespace
bool IR_Compare::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
if (condition.kind != Condition::ALWAYS) {
assert(root_op);
// auto consumed = root_op->get_consumed(file);
auto& consumed = root_op->consumed;
switch (condition.num_args()) {
case 0:
break;
case 1:
update_from_stack_helper(&condition.src0, consumed, stack, file);
break;
case 2:
update_from_stack_helper(&condition.src1, consumed, stack, file);
update_from_stack_helper(&condition.src0, consumed, stack, file);
break;
default:
assert(false);
}
}
stack.add_no_set(std::make_shared<IR_Compare>(*this), true);
return true;
}
bool IR_Compare::update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
if (condition.kind != Condition::ALWAYS) {
switch (condition.num_args()) {
case 0:
break;
case 1:
update_from_stack_helper(&condition.src0, consume, stack, file);
break;
case 2:
update_from_stack_helper(&condition.src1, consume, stack, file);
update_from_stack_helper(&condition.src0, consume, stack, file);
break;
default:
assert(false);
}
}
return true;
}
bool IR_ShortCircuit::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
(void)file;
// this one is weird. All forms but the last implicitly set final_destination.
// the last form should somewhere set final_destination, but due to tricky coloring we
// can't identify this 100% of the time.
// so we settle for something like:
// (set! result (or <clause-a> ... (begin (blah) (set! result x) (blah))))
// in the future, we may want to handle this a little bit better, at least in the obvious cases.
assert(final_result);
assert(used_as_value.has_value());
if (used_as_value.value()) {
auto dest_reg = dynamic_cast<IR_Register*>(final_result.get());
// try as a set
auto last_entry_as_set = dynamic_cast<IR_Set*>(entries.back().condition.get());
if (last_entry_as_set) {
auto sd = last_entry_as_set->dst;
auto sd_as_reg = dynamic_cast<IR_Register*>(sd.get());
if (sd_as_reg && sd_as_reg->reg == dest_reg->reg) {
entries.back().condition = last_entry_as_set->src;
stack.set(dest_reg->reg, std::make_shared<IR_ShortCircuit>(*this), true);
return true;
}
}
// try as the last thing in a begin.
auto last_entry_as_begin = dynamic_cast<IR_Begin*>(entries.back().condition.get());
if (last_entry_as_begin) {
last_entry_as_set = dynamic_cast<IR_Set*>(last_entry_as_begin->forms.back().get());
if (last_entry_as_set) {
auto sd = last_entry_as_set->dst;
auto sd_as_reg = dynamic_cast<IR_Register*>(sd.get());
if (sd_as_reg && sd_as_reg->reg == dest_reg->reg) {
entries.back().condition = last_entry_as_set->src;
stack.set(dest_reg->reg, std::make_shared<IR_ShortCircuit>(*this), true);
return true;
}
}
}
// nope. if we have something like (and x (if a b c)), we may need to explictly add an
// evaluation of the if's result.
auto new_last_entry = std::make_shared<IR_Begin>();
new_last_entry->forms.push_back(entries.back().condition);
new_last_entry->forms.push_back(std::make_shared<IR_Register>(dest_reg->reg, -1));
entries.back().condition = new_last_entry;
stack.set(dest_reg->reg, std::make_shared<IR_ShortCircuit>(*this), true);
return true;
// throw std::runtime_error("Last entry in short circuit was bad: " +
// entries.back().condition->print(file));
} else {
stack.add_no_set(std::make_shared<IR_ShortCircuit>(*this), true);
return true;
}
}
bool IR_Cond::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
if (used_as_value) {
// we have to make sure that all of the bodies evaluate to the value stored in the
// final_destination register.
for (auto& entry : entries) {
IR* current_ir = entry.body.get();
while (dynamic_cast<IR_Begin*>(current_ir)) {
current_ir = dynamic_cast<IR_Begin*>(current_ir)->forms.back().get();
}
auto as_set = dynamic_cast<IR_Set*>(current_ir);
if (as_set) {
auto sd = as_set->dst;
auto sd_as_reg = dynamic_cast<IR_Register*>(sd.get());
if (sd_as_reg && sd_as_reg->reg == final_destination) {
// yep! it's okay. set!'s evaluate to the thing they are setting.
continue;
}
}
throw std::runtime_error("IR_Cond used as value didn't work for reg " +
final_destination.to_string() + "\n" + entry.body->print(file));
}
return true;
} else {
(void)file;
stack.add_no_set(std::make_shared<IR_Cond>(*this), true);
return true;
}
}
bool IR_WhileLoop::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
(void)file;
// while loops are never "used by value" yet, but this is okay because they don't
// do any tricks in delay slots like IR_Cond's do.
stack.add_no_set(std::make_shared<IR_WhileLoop>(*this), true);
return true;
}
bool IR_AsmOp::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
(void)file;
// we only fall back to asm ops if we don't understand the GOAL code, or if the original code
// used inline assembly. In these cases, we create a sequence point here.
stack.add_no_set(std::make_shared<IR_AsmOp>(*this), true);
return true;
}
bool IR_CondWithElse::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
(void)file;
// cond with else are never "used by value" yet, but this is okay because they don't
// do any tricks in delay slots like IR_Cond's do.
stack.add_no_set(std::make_shared<IR_CondWithElse>(*this), true);
return true;
}
bool IR_Load::update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
update_from_stack_helper(&location, consume, stack, file);
return true;
}
bool IR_StaticAddress::update_from_stack(
const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
(void)consume;
(void)stack;
(void)file;
return true;
}
bool IR_FloatMath2::update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
if (kind == DIV) {
for (auto reg : {&arg1, &arg0}) {
auto as_reg = dynamic_cast<IR_Register*>(reg->get());
if (as_reg) {
if (consume.find(as_reg->reg) != consume.end()) {
*reg = stack.get(as_reg->reg);
}
} else {
(*reg)->update_from_stack(consume, stack, file);
}
}
} else {
for (auto reg : {&arg1, &arg0}) {
auto as_reg = dynamic_cast<IR_Register*>(reg->get());
if (as_reg) {
if (consume.find(as_reg->reg) != consume.end()) {
*reg = stack.get(as_reg->reg);
}
} else {
(*reg)->update_from_stack(consume, stack, file);
}
}
}
return true;
}
bool IR_IntMath2::update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
for (auto reg : {&arg1, &arg0}) {
auto as_reg = dynamic_cast<IR_Register*>(reg->get());
if (as_reg) {
if (consume.find(as_reg->reg) != consume.end()) {
*reg = stack.get(as_reg->reg);
}
} else {
(*reg)->update_from_stack(consume, stack, file);
}
}
return true;
}
std::unordered_set<Register, Register::hash> IR_Ash::get_consumed(LinkedObjectFile& file) {
(void)file;
// first get the set of read registers...
auto value_as_reg = dynamic_cast<IR_Register*>(value.get());
auto sa_as_reg = dynamic_cast<IR_Register*>(shift_amount.get());
if (!sa_as_reg || !value_as_reg) {
// consume nobody.
// todo - is this actually right? If not, this is "safe", but might lead to ugly code.
return {};
}
std::unordered_set<Register, Register::hash> result;
for (auto& op : {branch_op, sub_op, shift_op}) {
for (auto& reg : {value_as_reg->reg, sa_as_reg->reg}) {
if (op->consumed.find(reg) != op->consumed.end()) {
result.insert(reg);
}
}
}
return result;
}
bool IR_Ash::update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
for (auto x : {&value, &shift_amount}) {
update_from_stack_helper(x, consume, stack, file);
}
return true;
}
std::unordered_set<Register, Register::hash> IR_IntMath1::get_consumed(LinkedObjectFile& file) {
if (kind == ABS) {
assert(abs_op);
return abs_op->consumed;
} else {
throw std::runtime_error("IR_IntMath1::get_consumed NYI for " + print(file));
}
}
bool IR_IntMath1::update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
update_from_stack_helper(&arg, consume, stack, file);
return true;
}
bool IR_GetRuntimeType::update_from_stack(
const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
update_from_stack_helper(&object, consume, stack, file);
return true;
}
std::unordered_set<Register, Register::hash> IR_GetRuntimeType::get_consumed(
LinkedObjectFile& file) {
// todo, this can actually consume stuff.
(void)file;
return {};
}
std::unordered_set<Register, Register::hash> IR_Compare::get_consumed(LinkedObjectFile& file) {
// todo, this can actually consume stuff.
(void)file;
return {};
}
bool IR_Nop::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
(void)stack;
(void)file;
return true;
}
bool IR_CMoveF::update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
update_from_stack_helper(&src, consume, stack, file);
return true;
}
bool IR_FloatMath1::update_from_stack(const std::unordered_set<Register, Register::hash>& consume,
ExpressionStack& stack,
LinkedObjectFile& file) {
update_from_stack_helper(&arg, consume, stack, file);
return true;
}
} // namespace decompiler
-950
View File
@@ -1,950 +0,0 @@
#include "IR.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "third-party/fmt/core.h"
#include "common/goos/Object.h"
#include "decompiler/util/TP_Type.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
namespace decompiler {
namespace {
// bool is_plain_type(const TP_Type& type, const TypeSpec& ts) {
// return type.as_typespec() == ts;
//}
//
// bool is_integer_type(const TP_Type& type) {
// return is_plain_type(type, TypeSpec("int")) || is_plain_type(type, TypeSpec("uint"));
//}
//
///*!
// * If first arg is unsigned, make the result unsigned.
// * Otherwise signed. This is the default GOAL behavior I guess.
// * This strips away any fancy stuff like [uint x 4]
// */
// TP_Type get_int_type(const TP_Type& one) {
// if (is_plain_type(one, TypeSpec("uint"))) {
// return TP_Type(one.as_typespec());
// } else {
// return TP_Type(TypeSpec("int"));
// }
//}
//
bool tc(DecompilerTypeSystem& dts, const TypeSpec& expected, const TP_Type& actual) {
return dts.ts.typecheck(expected, actual.typespec(), "", false, false);
}
bool is_int_or_uint(DecompilerTypeSystem& dts, const TP_Type& type) {
return tc(dts, TypeSpec("int"), type) || tc(dts, TypeSpec("uint"), type);
}
struct RegOffset {
Register reg;
std::shared_ptr<IR_Register> reg_ir;
int offset;
};
bool get_as_reg_offset(const IR* ir, RegOffset* out) {
auto as_reg = dynamic_cast<const IR_Register*>(ir);
if (as_reg) {
out->reg = as_reg->reg;
out->reg_ir = std::make_shared<IR_Register>(*as_reg);
out->offset = 0;
return true;
}
auto as_math = dynamic_cast<const IR_IntMath2*>(ir);
if (as_math && as_math->kind == IR_IntMath2::ADD) {
auto first_as_reg = dynamic_cast<const IR_Register*>(as_math->arg0.get());
auto second_as_const = dynamic_cast<const IR_IntegerConstant*>(as_math->arg1.get());
if (first_as_reg && second_as_const) {
out->reg = first_as_reg->reg;
out->offset = second_as_const->value;
out->reg_ir = std::dynamic_pointer_cast<IR_Register>(as_math->arg0);
return true;
}
}
return false;
}
RegClass get_reg_kind(const Register& r) {
switch (r.get_kind()) {
case Reg::GPR:
return RegClass::GPR_64;
case Reg::FPR:
return RegClass::FLOAT;
default:
assert(false);
}
}
} // namespace
/*!
* Default implementation of propagate types, throw an NYI error.
*/
void IR_Atomic::propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)dts;
throw std::runtime_error(
fmt::format("Could not propagate types for {}, not yet implemented", print(file)));
}
/*!
* Default implementation of get_expression_type.
*/
TP_Type IR::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)dts;
throw std::runtime_error(
fmt::format("Could not get expression types for {}, not yet implemented", print(file)));
}
/*!
* Propagate types through a set! operation.
*/
void IR_Set_Atomic::propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
// pass through types
end_types = input;
// modify as needed
switch (kind) {
case IR_Set::REG_64:
case IR_Set::LOAD:
case IR_Set::GPR_TO_FPR:
case IR_Set::FPR_TO_GPR64:
case IR_Set::REG_FLT:
case IR_Set::SYM_LOAD: {
// all these should set a register.
auto as_reg = dynamic_cast<IR_Register*>(dst.get());
assert(as_reg);
// get the type of the source,
auto t = src->get_expression_type(input, file, dts);
// set the type of the register.
end_types.get(as_reg->reg) = t;
} break;
case IR_Set::SYM_STORE: {
auto as_reg = dynamic_cast<IR_Register*>(dst.get());
assert(!as_reg);
return;
}
default:
throw std::runtime_error(fmt::format(
"Could not propagate types through IR_Set_Atomic, kind not handled {}", print(file)));
}
}
/*!
* Get the type of a register.
*/
TP_Type IR_Register::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)file;
(void)dts;
return input.get(reg);
}
TP_Type IR_Load::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
clear_load_path();
////////////////////
// STATIC
////////////////////
auto as_static = dynamic_cast<IR_StaticAddress*>(location.get());
if (as_static) {
// todo - we should map out static data and use an actual type system lookup to figure this out.
// but for now, this is probably good enough.
if (kind == FLOAT) {
// loading static data with a FLOAT kind load (lwc1), assume result is a float.
return TP_Type::make_from_ts(dts.ts.make_typespec("float"));
}
if (size == 8) {
// 8 byte integer constants are always loaded from a static pool
// this could technically hide loading a different type from inside of a static basic.
return TP_Type::make_from_ts(dts.ts.make_typespec("uint"));
}
}
///////////////////////////////////////
// REGISTER + OFFSET (possibly 0)
///////////////////////////////////////
RegOffset ro;
if (get_as_reg_offset(location.get(), &ro)) {
auto& input_type = input.get(ro.reg);
if (input_type.kind == TP_Type::Kind::TYPE_OF_TYPE_OR_CHILD && ro.offset >= 16 &&
(ro.offset & 3) == 0 && size == 4 && kind == UNSIGNED) {
// method get of fixed type
auto type_name = input_type.get_type_objects_typespec().base_type();
auto method_id = (ro.offset - 16) / 4;
auto method_info = dts.ts.lookup_method(type_name, method_id);
auto method_type = method_info.type.substitute_for_method_call(type_name);
if (type_name == "object" && method_id == GOAL_NEW_METHOD) {
// remember that we're an object new.
return TP_Type::make_object_new(method_type);
}
return TP_Type::make_from_ts(method_type);
}
if (input_type.kind == TP_Type::Kind::TYPESPEC && input_type.typespec() == TypeSpec("type") &&
ro.offset >= 16 && (ro.offset & 3) == 0 && size == 4 && kind == UNSIGNED) {
// method get of an unknown type. We assume the most general "object" type.
auto method_id = (ro.offset - 16) / 4;
auto method_info = dts.ts.lookup_method("object", method_id);
if (method_id != GOAL_NEW_METHOD && method_id != GOAL_RELOC_METHOD) {
// this can get us the wrong thing for `new` methods. And maybe relocate?
return TP_Type::make_from_ts(method_info.type.substitute_for_method_call("object"));
}
}
if (input_type.typespec() == TypeSpec("pointer")) {
// we got a plain pointer. let's just assume we're loading an integer.
// perhaps we should disable this feature by default on 4-byte loads if we're getting
// lots of false positives for loading pointers from plain pointers.
switch (kind) {
case UNSIGNED:
switch (size) {
case 1:
case 2:
case 4:
case 8:
return TP_Type::make_from_ts(TypeSpec("uint"));
default:
break;
}
break;
case SIGNED:
switch (size) {
case 1:
case 2:
case 4:
case 8:
return TP_Type::make_from_ts(TypeSpec("int"));
default:
break;
}
break;
case FLOAT:
return TP_Type::make_from_ts(TypeSpec("float"));
default:
assert(false);
}
}
if (input_type.kind == TP_Type::Kind::OBJECT_PLUS_PRODUCT_WITH_CONSTANT) {
FieldReverseLookupInput rd_in;
DerefKind dk;
dk.is_store = false;
dk.reg_kind = get_reg_kind(ro.reg);
dk.sign_extend = kind == SIGNED;
dk.size = size;
rd_in.deref = dk;
rd_in.base_type = input_type.get_obj_plus_const_mult_typespec();
rd_in.stride = input_type.get_multiplier();
rd_in.offset = ro.offset;
auto rd = dts.ts.reverse_field_lookup(rd_in);
if (rd.success) {
load_path_set = true;
load_path_addr_of = rd.addr_of;
load_path_base = ro.reg_ir;
for (auto& x : rd.tokens) {
load_path.push_back(x.print());
}
return TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type));
}
}
if (input_type.kind == TP_Type::Kind::TYPESPEC && ro.offset == -4 && kind == UNSIGNED &&
size == 4 && ro.reg.get_kind() == Reg::GPR) {
// get type of basic likely, but misrecognized as an object.
// occurs often in typecase-like structures because other possible types are
// "stripped".
load_path_base = ro.reg_ir;
load_path_addr_of = false;
load_path.push_back("type");
load_path_set = true;
return TP_Type::make_type_object(input_type.typespec().base_type());
}
//
// if (input_type.as_typespec() == TypeSpec("object") && ro.offset == -4 && kind ==
// UNSIGNED
// &&
// size == 4 && ro.reg.get_kind() == Reg::GPR) {
// // get type of basic likely, but misrecognized as an object.
// // occurs often in typecase-like structures because other possible types are
// "stripped". return TP_Type(TypeSpec("type"));
// }
//
if (input_type.kind == TP_Type::Kind::DYNAMIC_METHOD_ACCESS && ro.offset == 16) {
// access method vtable. The input is type + (4 * method), and the 16 is the offset
// of method 0.
return TP_Type::make_from_ts(TypeSpec("function"));
}
// Assume we're accessing a field of an object.
FieldReverseLookupInput rd_in;
DerefKind dk;
dk.is_store = false;
dk.reg_kind = get_reg_kind(ro.reg);
dk.sign_extend = kind == SIGNED;
dk.size = size;
rd_in.deref = dk;
rd_in.base_type = input_type.typespec();
rd_in.stride = 0;
rd_in.offset = ro.offset;
auto rd = dts.ts.reverse_field_lookup(rd_in);
// only error on failure if "pair" is disabled. otherwise it might be a pair.
if (!rd.success && !dts.type_prop_settings.allow_pair) {
printf("input type is %s, offset is %d, sign %d size %d\n", rd_in.base_type.print().c_str(),
rd_in.offset, rd_in.deref.value().sign_extend, rd_in.deref.value().size);
throw std::runtime_error(
fmt::format("Could not get type of load: {}. Reverse Deref Failed.", print(file)));
}
if (rd.success) {
load_path_set = true;
load_path_addr_of = rd.addr_of;
load_path_base = ro.reg_ir;
for (auto& x : rd.tokens) {
load_path.push_back(x.print());
}
return TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type));
}
// rd failed, try as pair.
if (dts.type_prop_settings.allow_pair) {
// we are strict here - only permit pair-type loads from object or pair.
// object is permitted for stuff like association lists where the car is also a pair.
if (kind == SIGNED && size == 4 &&
(input_type.typespec() == TypeSpec("object") ||
input_type.typespec() == TypeSpec("pair"))) {
// these rules are of course not always correct or the most specific, but it's the best
// we can do.
if (ro.offset == 2) {
// cdr = another pair.
return TP_Type::make_from_ts(TypeSpec("pair"));
} else if (ro.offset == -2) {
// car = some object.
return TP_Type::make_from_ts(TypeSpec("object"));
}
}
}
}
throw std::runtime_error(fmt::format("Could not get type of load: {}. Not handled: {}",
print(file), location->print(file)));
}
TP_Type IR_FloatMath2::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)file;
// regardless of input types, the output is going to be a float.
// todo - if we ever support meters we should do something better here.
switch (kind) {
case DIV:
case MUL:
case ADD:
case SUB:
case MIN:
case MAX:
return TP_Type::make_from_ts(dts.ts.make_typespec("float"));
default:
assert(false);
}
}
TP_Type IR_FloatMath1::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)file;
(void)dts;
// FLOAT_TO_INT, INT_TO_FLOAT, ABS, NEG, SQRT
switch (kind) {
case FLOAT_TO_INT:
return TP_Type::make_from_ts(TypeSpec("int"));
case INT_TO_FLOAT:
case ABS:
case NEG:
case SQRT:
return TP_Type::make_from_ts(TypeSpec("float"));
default:
assert(false);
}
}
TP_Type IR_IntMath2::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
auto arg0_type = arg0->get_expression_type(input, file, dts);
auto arg1_type = arg1->get_expression_type(input, file, dts);
// special cases for integers
switch (kind) {
case LEFT_SHIFT:
// multiply!
{
auto as_const = dynamic_cast<IR_IntegerConstant*>(arg1.get());
if (as_const && is_int_or_uint(dts, arg0_type)) {
assert(as_const->value >= 0);
assert(as_const->value < 64);
return TP_Type::make_from_product((1ull << as_const->value));
}
break;
}
case MUL_SIGNED: {
if (arg0_type.is_integer_constant() && is_int_or_uint(dts, arg1_type)) {
return TP_Type::make_from_product(arg0_type.get_integer_constant());
}
} break;
case ADD:
if (arg0_type.is_product_with(4) && tc(dts, TypeSpec("type"), arg1_type)) {
// dynamic access into the method array with shift, add, offset-load
// no need to track the type because we don't know the method index anyway.
return TP_Type::make_partial_dyanmic_vtable_access();
}
break;
default:
break;
}
if (arg0_type == arg1_type && is_int_or_uint(dts, arg0_type)) {
// both are the same type and both are int/uint, so we assume that we're doing integer math.
// we strip off any weird things like multiplication or integer constant.
return TP_Type::make_from_ts(arg0_type.typespec());
}
if (is_int_or_uint(dts, arg0_type) && is_int_or_uint(dts, arg1_type)) {
// usually we would want to use arg0's type as the "winning" type.
// but we use arg1's if arg0 is an integer constant
// in either case, strip off weird stuff.
if (arg0_type.is_integer_constant() && !arg1_type.is_integer_constant()) {
return TP_Type::make_from_ts(arg1_type.typespec());
}
return TP_Type::make_from_ts(arg0_type.typespec());
}
if (tc(dts, TypeSpec("binteger"), arg0_type) && is_int_or_uint(dts, arg1_type)) {
return TP_Type::make_from_ts(TypeSpec("binteger"));
}
// special cases for non-integers
if ((arg0_type.typespec() == TypeSpec("object") || arg0_type.typespec() == TypeSpec("pair")) &&
(arg1_type.is_integer_constant(62) || arg1_type.is_integer_constant(61))) {
// boxed object tag trick.
return TP_Type::make_from_ts(TypeSpec("int"));
}
//
// if (is_integer_type(arg0_type) && is_integer_type(arg1_type)) {
// // case where both arguments are integers.
// // in this case we assume we're actually doing math.
// switch (kind) {
// case ADD:
// case SUB:
// case AND:
// case OR:
// case NOR:
// case XOR:
// // we don't know if we're signed or unsigned. so let's just go with the first type.
// return get_int_type(arg0_type);
// case MUL_SIGNED:
// case DIV_SIGNED:
// case RIGHT_SHIFT_ARITH:
// case MOD_SIGNED:
// case MIN_SIGNED:
// case MAX_SIGNED:
// // result is going to be signed, regardless of inputs.
// return TP_Type(TypeSpec("int"));
//
// case MUL_UNSIGNED:
// case RIGHT_SHIFT_LOGIC:
// // result is going to be unsigned, regardless of inputs.
// return TP_Type(TypeSpec("uint"));
//
// case LEFT_SHIFT: {
// // multiply!
// auto as_const = dynamic_cast<IR_IntegerConstant*>(arg1.get());
// if (as_const) {
// // shift by constant integer. could be accessing the method array.
// TP_Type result;
// result.kind = TP_Type::PRODUCT;
// result.ts = get_int_type(arg0_type).ts;
// result.multiplier = (1 << as_const->value);
// return result;
// } else {
// // normal variable shift.
// return get_int_type(arg0_type);
// }
// }
// default:
// break;
// }
// }
//
//
auto a1_const = dynamic_cast<IR_IntegerConstant*>(arg1.get());
if (a1_const && kind == ADD && arg0_type.kind == TP_Type::Kind::TYPESPEC) {
// access a field.
FieldReverseLookupInput rd_in;
rd_in.deref = std::nullopt;
rd_in.stride = 0;
rd_in.offset = a1_const->value;
rd_in.base_type = arg0_type.typespec();
auto rd = dts.ts.reverse_field_lookup(rd_in);
if (rd.success) {
// todo, load path.
return TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type));
}
}
//
// if (kind == ADD && is_integer_type(arg0_type) && arg1_type.kind == TP_Type::OBJECT_OF_TYPE)
// {
// // product + object with multiplier 1 (access array of bytes for example)
// TP_Type result;
// result.kind = TP_Type::OBJ_PLUS_PRODUCT;
// result.ts = arg1_type.as_typespec();
// result.multiplier = 1;
// return result;
// }
//
if (kind == ADD && arg0_type.is_product() && arg1_type.kind == TP_Type::Kind::TYPESPEC) {
return TP_Type::make_object_plus_product(arg1_type.typespec(), arg0_type.get_multiplier());
}
if (kind == ADD && arg1_type.is_product() && arg0_type.kind == TP_Type::Kind::TYPESPEC) {
return TP_Type::make_object_plus_product(arg0_type.typespec(), arg1_type.get_multiplier());
}
if (kind == ADD && arg0_type.typespec().base_type() == "pointer" &&
tc(dts, TypeSpec("integer"), arg1_type)) {
// plain pointer plus integer = plain pointer
return TP_Type::make_from_ts(TypeSpec("pointer"));
}
if (kind == ADD && arg1_type.typespec().base_type() == "pointer" &&
tc(dts, TypeSpec("integer"), arg0_type)) {
// plain pointer plus integer = plain pointer
return TP_Type::make_from_ts(TypeSpec("pointer"));
}
if (tc(dts, TypeSpec("structure"), arg1_type) && !dynamic_cast<IR_IntegerConstant*>(arg0.get()) &&
is_int_or_uint(dts, arg0_type)) {
if (arg1_type.typespec() == TypeSpec("symbol") &&
arg0_type.is_integer_constant(SYM_INFO_OFFSET + POINTER_SIZE)) {
// symbol -> GOAL String
return TP_Type::make_from_ts(dts.ts.make_pointer_typespec("string"));
} else {
// byte access of offset array field trick.
// arg1 holds a structure.
// arg0 is an integer in a register.
return TP_Type::make_object_plus_product(arg1_type.typespec(), 1);
}
}
if (kind == AND) {
// base case for and. Just get an integer.
return TP_Type::make_from_ts(TypeSpec("int"));
}
//
// if (kind == ADD &&
// dts.ts.typecheck(TypeSpec("pointer"), arg0_type.as_typespec(), "", false, false) &&
// is_integer_type(arg1_type)) {
// return arg0_type;
// }
//
// if ((kind == ADD || kind == AND) &&
// dts.ts.typecheck(TypeSpec("pointer"), arg1_type.as_typespec(), "", false, false) &&
// is_integer_type(arg0_type)) {
// return arg1_type;
// }
//
// if (kind == ADD &&
// dts.ts.typecheck(TypeSpec("binteger"), arg0_type.as_typespec(), "", false, false) &&
// is_integer_type(arg1_type)) {
// return arg0_type;
// }
//
if (kind == SUB && tc(dts, TypeSpec("pointer"), arg0_type) &&
tc(dts, TypeSpec("pointer"), arg1_type)) {
return TP_Type::make_from_ts(TypeSpec("int"));
}
throw std::runtime_error(
fmt::format("Can't get_expression_type on this IR_IntMath2: {}, args {} and {}", print(file),
arg0_type.print(), arg1_type.print()));
}
void BranchDelay::type_prop(TypeState& output,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
// (void)dts;
switch (kind) {
case DSLLV: {
// I believe this is only used in ash. We ignore the shift amount's type and just look
// at the input value. If it's a uint/int based type, we just return uint/int (not the type)
// this will kill any weird stuff like product, etc.
// if it's not an integer type, it's currently an error.
auto dst = dynamic_cast<IR_Register*>(destination.get());
assert(dst);
auto src = dynamic_cast<IR_Register*>(source.get());
assert(src);
if (tc(dts, TypeSpec("uint"), output.get(src->reg))) {
output.get(dst->reg) = TP_Type::make_from_ts(TypeSpec("uint"));
} else if (tc(dts, TypeSpec("int"), output.get(src->reg))) {
output.get(dst->reg) = TP_Type::make_from_ts(TypeSpec("int"));
} else {
throw std::runtime_error("BranchDelay::type_prop DSLLV for src " +
output.get(src->reg).print());
}
} break;
case NEGATE: {
auto dst = dynamic_cast<IR_Register*>(destination.get());
assert(dst);
// to match the behavior in IntMath1, assume signed when negating.
output.get(dst->reg) = TP_Type::make_from_ts(TypeSpec("int"));
} break;
case SET_REG_FALSE: {
auto dst = dynamic_cast<IR_Register*>(destination.get());
assert(dst);
output.get(dst->reg) = TP_Type::make_false();
} break;
case SET_REG_REG: {
auto dst = dynamic_cast<IR_Register*>(destination.get());
assert(dst);
auto src = dynamic_cast<IR_Register*>(source.get());
assert(src);
output.get(dst->reg) = output.get(src->reg);
break;
}
case SET_REG_TRUE: {
auto dst = dynamic_cast<IR_Register*>(destination.get());
assert(dst);
output.get(dst->reg) = TP_Type::make_from_ts(TypeSpec("symbol"));
} break;
case SET_BINTEGER: {
auto dst = dynamic_cast<IR_Register*>(destination.get());
assert(dst);
output.get(dst->reg) = TP_Type::make_type_object(TypeSpec("binteger"));
} break;
case SET_PAIR: {
auto dst = dynamic_cast<IR_Register*>(destination.get());
assert(dst);
output.get(dst->reg) = TP_Type::make_type_object(TypeSpec("pair"));
} break;
case NOP:
break;
default:
throw std::runtime_error("Unhandled branch delay in type_prop: " + to_form(file).print());
}
}
void IR_Branch_Atomic::propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
// pass through types
end_types = input;
branch_delay.type_prop(end_types, file, dts);
// todo clobbers.
}
TP_Type IR_IntMath1::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)dts;
auto arg_type = arg->get_expression_type(input, file, dts);
if (is_int_or_uint(dts, arg_type)) {
switch (kind) {
case NEG:
// if we negate a thing, let's just make it a signed integer.
return TP_Type::make_from_ts(TypeSpec("int"));
case ABS:
// if we take the absolute value of a thing, just make it signed.
return TP_Type::make_from_ts(TypeSpec("int"));
case NOT:
// otherwise, make it int/uint as needed (this works because we check is_int_or_uint
// above)
return TP_Type::make_from_ts(arg_type.typespec());
}
}
throw std::runtime_error("IR_IntMath1::get_expression_type case not handled: " +
to_form(file).print() + " " + arg_type.print());
}
TP_Type IR_SymbolValue::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)file;
if (name == "#f") {
// if we ever read the false symbol, it should contain the false symbol as its value.
return TP_Type::make_false();
} else if (name == "__START-OF-TABLE__") {
// another annoying special case. We have a fake symbol called __START-OF-TABLE__
// which actually means that you get the first address in the symbol table.
// it's not really a linked symbol, but the basic op builder represents it as one.
return TP_Type::make_from_ts(TypeSpec("pointer"));
}
// look up the type of the symbol
auto type = dts.symbol_types.find(name);
if (type == dts.symbol_types.end()) {
throw std::runtime_error("Don't have the type of symbol " + name);
}
if (type->second == TypeSpec("type")) {
// if we get a type by symbol, we should remember which type we got it from.
return TP_Type::make_type_object(TypeSpec(name));
}
// otherwise, just return a normal typespec
return TP_Type::make_from_ts(type->second);
}
TP_Type IR_Symbol::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)file;
(void)dts;
if (name == "#f") {
return TP_Type::make_false();
}
return TP_Type::make_from_ts(TypeSpec("symbol"));
}
TP_Type IR_IntegerConstant::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)file;
(void)dts;
return TP_Type::make_from_integer(value);
}
TP_Type IR_Compare::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)file;
(void)dts;
// really a boolean.
return TP_Type::make_from_ts(TypeSpec("symbol"));
}
void IR_Nop_Atomic::propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)file;
(void)dts;
end_types = input;
}
void IR_Suspend_Atomic::propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)file;
(void)dts;
end_types = input;
}
void IR_Call_Atomic::propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)file;
(void)dts;
const Reg::Gpr arg_regs[8] = {Reg::A0, Reg::A1, Reg::A2, Reg::A3,
Reg::T0, Reg::T1, Reg::T2, Reg::T3};
const Reg::Gpr goal_function_clobber_regs[] = {Reg::A0, Reg::A1, Reg::A2, Reg::A3,
Reg::T0, Reg::T1, Reg::T2, Reg::T3,
Reg::T4, Reg::V1, Reg::T9};
end_types = input;
auto in_tp = input.get(Register(Reg::GPR, Reg::T9));
if (in_tp.kind == TP_Type::Kind::OBJECT_NEW_METHOD &&
!dts.type_prop_settings.current_method_type.empty()) {
// calling object new method. Set the result to a new object of our type
end_types.get(Register(Reg::GPR, Reg::V0)) =
TP_Type::make_from_ts(dts.type_prop_settings.current_method_type);
// update the call type
call_type = in_tp.get_method_new_object_typespec();
call_type.get_arg(call_type.arg_count() - 1) =
TypeSpec(dts.type_prop_settings.current_method_type);
call_type_set = true;
return;
}
auto in_type = in_tp.typespec();
if (in_type.base_type() != "function") {
throw std::runtime_error("Called something that wasn't a function: " + in_type.print());
}
if (in_type.arg_count() < 1) {
throw std::runtime_error("Called a function, but we don't know its type");
}
if (in_type.arg_count() == 2 && in_type.get_arg(0) == TypeSpec("_varargs_")) {
// we're calling a varags function, which is format. We can determine the argument count
// by looking at the format string, if we can get it.
auto arg_type = input.get(Register(Reg::GPR, Reg::A1));
if (arg_type.is_constant_string() || arg_type.is_format_string()) {
int arg_count = -1;
if (arg_type.is_constant_string()) {
auto& str = arg_type.get_string();
arg_count = dts.get_format_arg_count(str);
} else {
// is format string.
arg_count = arg_type.get_format_string_arg_count();
}
TypeSpec format_call_type("function");
format_call_type.add_arg(TypeSpec("object")); // destination
format_call_type.add_arg(TypeSpec("string")); // format string
for (int i = 0; i < arg_count; i++) {
format_call_type.add_arg(TypeSpec("object"));
}
format_call_type.add_arg(TypeSpec("object"));
arg_count += 2; // for destination and format string.
call_type = format_call_type;
call_type_set = true;
end_types.get(Register(Reg::GPR, Reg::V0)) = TP_Type::make_from_ts(in_type.last_arg());
// we can also update register usage here.
read_regs.clear();
read_regs.emplace_back(Reg::GPR, Reg::T9);
for (int i = 0; i < arg_count; i++) {
read_regs.emplace_back(Reg::GPR, arg_regs[i]);
}
for (auto reg : goal_function_clobber_regs) {
end_types.get(Register(Reg::GPR, reg)) = TP_Type::make_uninitialized();
}
return;
} else {
throw std::runtime_error("Failed to get string for _varags_ call, got " + arg_type.print());
}
}
// set the call type!
call_type = in_type;
call_type_set = true;
end_types.get(Register(Reg::GPR, Reg::V0)) = TP_Type::make_from_ts(in_type.last_arg());
// we can also update register usage here.
read_regs.clear();
read_regs.emplace_back(Reg::GPR, Reg::T9);
for (uint32_t i = 0; i < in_type.arg_count() - 1; i++) {
read_regs.emplace_back(Reg::GPR, arg_regs[i]);
}
for (auto reg : goal_function_clobber_regs) {
end_types.get(Register(Reg::GPR, reg)) = TP_Type::make_uninitialized();
}
}
void IR_Store_Atomic::propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)file;
(void)dts;
end_types = input;
}
TP_Type IR_StaticAddress::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)dts;
// todo - we should map out static data and use a real type system lookup here.
auto label = file.labels.at(label_id);
// strings are 16-byte aligned, but functions are 8 byte aligned?
if ((label.offset & 7) == BASIC_OFFSET) {
// it's a basic! probably.
const auto& word = file.words_by_seg.at(label.target_segment).at((label.offset - 4) / 4);
if (word.kind == LinkedWord::TYPE_PTR) {
if (word.symbol_name == "string") {
return TP_Type::make_from_string(file.get_goal_string_by_label(label));
} else {
// otherwise, some other static basic.
return TP_Type::make_from_ts(TypeSpec(word.symbol_name));
}
}
} else if ((label.offset & 7) == PAIR_OFFSET) {
return TP_Type::make_from_ts(TypeSpec("pair"));
}
throw std::runtime_error("IR_StaticAddress couldn't figure out the type: " + label.name);
}
void IR_AsmOp_Atomic::propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)file;
(void)dts;
auto dst_reg = dynamic_cast<IR_Register*>(dst.get());
end_types = input;
if (dst_reg) {
if (name == "daddu") {
end_types.get(dst_reg->reg) = TP_Type::make_from_ts(TypeSpec("uint"));
}
}
}
void IR_Breakpoint_Atomic::propagate_types(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)file;
(void)dts;
end_types = input;
}
TP_Type IR_EmptyPair::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)file;
(void)dts;
// GOAL's empty pair is actually a pair type, containing the empty pair as the car and cdr
return TP_Type::make_from_ts(TypeSpec("pair"));
}
TP_Type IR_CMoveF::get_expression_type(const TypeState& input,
const LinkedObjectFile& file,
DecompilerTypeSystem& dts) {
(void)input;
(void)file;
(void)dts;
return TP_Type::make_from_ts(TypeSpec("symbol"));
}
} // namespace decompiler
+3 -1
View File
@@ -1242,7 +1242,9 @@ void CallOp::collect_vars(VariableSet& vars) const {
vars.insert(e);
}
vars.insert(m_return_var);
if (m_call_type_set && m_call_type.last_arg() != TypeSpec("none")) {
vars.insert(m_return_var);
}
}
/////////////////////////////
@@ -769,76 +769,6 @@ std::string LinkedObjectFile::print_disassembly() {
return result;
}
std::string LinkedObjectFile::print_type_analysis_debug() {
std::string result;
assert(segments <= 3);
for (int seg = segments; seg-- > 0;) {
// segment header
result += ";------------------------------------------\n; ";
result += segment_names[seg];
result += "\n;------------------------------------------\n\n";
// functions
for (auto& func : functions_by_seg.at(seg)) {
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
result += "; .function " + func.guessed_name.to_string() + "\n";
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
if (!func.warnings.empty()) {
result += ";; WARNING:\n" + func.warnings + "\n";
}
for (auto& block : func.basic_blocks) {
result += "\n";
if (!block.label_name.empty()) {
result += block.label_name + ":\n";
}
TypeState* init_types = &block.init_types;
for (int i = block.start_basic_op; i < block.end_basic_op; i++) {
result += " ";
// result += func.basic_ops.at(i)->print_with_reguse(*this);
// result += func.basic_ops.at(i)->print(*this);
if (func.attempted_type_analysis) {
result += fmt::format("[{:3d}] ", i);
auto& op = func.basic_ops.at(i);
result += op->print_with_types(*init_types, *this);
// temporary debug load path print
auto op_as_set = dynamic_cast<IR_Set_Atomic*>(op.get());
if (op_as_set) {
auto op_as_load = dynamic_cast<IR_Load*>(op_as_set->src.get());
if (op_as_load && op_as_load->load_path_set) {
if (op_as_load->load_path_addr_of) {
result += " (&->";
} else {
result += " (->";
}
result += ' ';
result += op_as_load->load_path_base->print(*this);
for (auto& tok : op_as_load->load_path) {
result += ' ';
result += tok;
}
result += ')';
}
}
result += "\n";
init_types = &func.basic_ops.at(i)->end_types;
} else {
result += fmt::format("[{:3d}] ", i);
result += func.basic_ops.at(i)->print(*this);
result += "\n";
}
}
}
}
}
return result;
}
/*!
* Hacky way to get a GOAL string object
*/
-1
View File
@@ -51,7 +51,6 @@ class LinkedObjectFile {
void process_fp_relative_links();
std::string print_scripts();
std::string print_disassembly();
std::string print_type_analysis_debug();
bool has_any_functions();
void append_word_to_string(std::string& dest, const LinkedWord& word) const;
std::string to_asm_json(const std::string& obj_file_name);
-243
View File
@@ -22,7 +22,6 @@
#include "common/util/FileUtil.h"
#include "decompiler/Function/BasicBlocks.h"
#include "decompiler/IR/BasicOpBuilder.h"
#include "decompiler/IR/CfgBuilder.h"
#include "decompiler/Function/TypeInspector.h"
#include "common/log/log.h"
#include "third-party/json.hpp"
@@ -533,31 +532,6 @@ void ObjectFileDB::write_object_file_words(const std::string& output_dir, bool d
// printf("\n");
}
void ObjectFileDB::write_debug_type_analysis(const std::string& output_dir,
const std::string& suffix) {
lg::info("- Writing debug type analysis...");
Timer timer;
uint32_t total_bytes = 0, total_files = 0;
for_each_obj([&](ObjectFileData& obj) {
if (obj.linked_data.has_any_functions()) {
auto file_text = obj.linked_data.print_type_analysis_debug();
auto file_name =
file_util::combine_path(output_dir, obj.to_unique_name() + suffix + "_dbt.asm");
total_bytes += file_text.size();
file_util::write_text_file(file_name, file_text);
total_files++;
}
});
lg::info("Wrote functions dumps:");
lg::info(" Total {} files", total_files);
lg::info(" Total {} MB", total_bytes / ((float)(1u << 20u)));
lg::info(" Total {} ms ({:.3f} MB/sec)", timer.getMs(),
total_bytes / ((1u << 20u) * timer.getSeconds()));
}
/*!
* Dump disassembly for object files containing code. Data zones will also be dumped.
*/
@@ -744,7 +718,6 @@ void ObjectFileDB::analyze_functions_ir1() {
Timer timer;
int total_functions = 0;
int resolved_cfg_functions = 0;
const auto& config = get_config();
// Step 1 - analyze the "top level" or "login" code for each object file.
@@ -803,27 +776,13 @@ void ObjectFileDB::analyze_functions_ir1() {
func.warnings += ";; this function exists in multiple non-identical object files\n";
}
});
/*
for (const auto& kv : duplicated_functions) {
printf("Function %s is found in non-identical object files:\n", kv.first.c_str());
for (const auto& obj : kv.second) {
printf(" %s\n", obj.c_str());
}
}
*/
int total_trivial_cfg_functions = 0;
int total_named_functions = 0;
int total_basic_ops = 0;
int total_failed_basic_ops = 0;
int total_reginfo_ops = 0;
int asm_funcs = 0;
int non_asm_funcs = 0;
int successful_cfg_irs = 0;
int successful_type_analysis = 0;
int attempted_type_analysis = 0;
int bad_type_analysis = 0; // didn't attempt because we didn't know how + attempted but failed
std::map<int, std::vector<std::string>> unresolved_by_length;
@@ -833,11 +792,6 @@ void ObjectFileDB::analyze_functions_ir1() {
// Main Pass over each function...
for_each_function_def_order([&](Function& func, int segment_id, ObjectFileData& data) {
total_functions++;
// if (func.guessed_name.to_string() != "sort") {
// return;
// }
// printf("in %s from %s\n", func.guessed_name.to_string().c_str(),
// data.to_unique_name().c_str());
// first, find basic blocks.
auto blocks = find_blocks_in_function(data.linked_data, segment_id, func);
@@ -872,7 +826,6 @@ void ObjectFileDB::analyze_functions_ir1() {
}
total_basic_ops += func.get_basic_op_count();
total_failed_basic_ops += func.get_failed_basic_op_count();
total_reginfo_ops += func.get_reginfo_basic_op_count();
// if we got an inspect method, inspect it.
if (func.is_inspect_method) {
@@ -880,157 +833,10 @@ void ObjectFileDB::analyze_functions_ir1() {
all_type_defs += ";; " + data.to_unique_name() + "\n";
all_type_defs += result.print_as_deftype() + "\n";
}
// Combine basic ops + CFG to build a nested IR
// register usage first, so we can tell if the SC's if's are used by value.
func.run_reg_usage();
func.ir = build_cfg_ir(func, *func.cfg, data.linked_data);
non_asm_funcs++;
if (func.ir) {
successful_cfg_irs++;
}
if (func.cfg->is_fully_resolved()) {
resolved_cfg_functions++;
} else {
lg::warn("Function {} from {} failed cfg ir", func.guessed_name.to_string(),
data.to_unique_name());
}
// type analysis
if (get_config().function_type_prop) {
auto hints = get_config().type_hints_by_function_by_idx[func.guessed_name.to_string()];
if (get_config().no_type_analysis_functions_by_name.find(func.guessed_name.to_string()) ==
get_config().no_type_analysis_functions_by_name.end()) {
if (func.guessed_name.kind == FunctionName::FunctionKind::GLOBAL) {
// we're a global named function. This means we're stored in a symbol
auto kv = dts.symbol_types.find(func.guessed_name.function_name);
if (kv != dts.symbol_types.end() && kv->second.arg_count() >= 1) {
if (kv->second.base_type() != "function") {
lg::error("Found a function named {} but the symbol has type {}",
func.guessed_name.to_string(), kv->second.print());
assert(false);
}
// GOOD!
func.type = kv->second;
func.attempted_type_analysis = true;
attempted_type_analysis++;
// lg::info("Type Analysis on {} {}", func.guessed_name.to_string(),
// kv->second.print());
if (func.run_type_analysis(kv->second, dts, data.linked_data, hints)) {
successful_type_analysis++;
} else {
// bad, failed.
bad_type_analysis++;
}
} else {
// bad, don't know global type
bad_type_analysis++;
}
} else if (func.guessed_name.kind == FunctionName::FunctionKind::METHOD) {
// it's a method.
try {
auto info =
dts.ts.lookup_method(func.guessed_name.type_name, func.guessed_name.method_id);
if (info.type.arg_count() >= 1) {
if (info.type.base_type() != "function") {
lg::error("Found a method named {} but the symbol has type {}",
func.guessed_name.to_string(), info.type.print());
assert(false);
}
// GOOD!
func.type = info.type.substitute_for_method_call(func.guessed_name.type_name);
func.attempted_type_analysis = true;
attempted_type_analysis++;
// lg::info("Type Analysis on {} {}",
// func.guessed_name.to_string(),
// func.type.print());
if (func.run_type_analysis(func.type, dts, data.linked_data, hints)) {
successful_type_analysis++;
} else {
bad_type_analysis++;
}
} else {
// not enough type info
bad_type_analysis++;
}
} catch (std::runtime_error& e) {
// failed to lookup method info
bad_type_analysis++;
}
} else if (func.guessed_name.kind == FunctionName::FunctionKind::TOP_LEVEL_INIT) {
attempted_type_analysis++;
func.type = dts.ts.make_function_typespec({}, "none");
func.attempted_type_analysis = true;
if (func.run_type_analysis(func.type, dts, data.linked_data, hints)) {
successful_type_analysis++;
} else {
// failed
bad_type_analysis++;
}
} else if (func.guessed_name.kind == FunctionName::FunctionKind::UNIDENTIFIED) {
auto obj_name = data.to_unique_name();
// try looking up the object
const auto& map = get_config().anon_function_types_by_obj_by_id;
auto obj_kv = map.find(obj_name);
if (obj_kv != map.end()) {
auto func_kv = obj_kv->second.find(func.guessed_name.get_anon_id());
if (func_kv != obj_kv->second.end()) {
attempted_type_analysis++;
func.type = dts.parse_type_spec(func_kv->second);
func.attempted_type_analysis = true;
if (func.run_type_analysis(func.type, dts, data.linked_data, hints)) {
successful_type_analysis++;
} else {
// tried, but failed.
bad_type_analysis++;
}
} else {
// no id
bad_type_analysis++;
}
} else {
// no object in map
bad_type_analysis++;
}
} else {
// unsupported function kind
bad_type_analysis++;
}
if (!func.attempted_type_analysis) {
func.warnings.append(";; Failed to try type analysis\n");
}
} else {
func.warnings.append(";; Marked as no type analysis in config\n");
}
}
} else {
asm_funcs++;
func.warnings.append(";; Assembly Function. Analysis passes were not attempted.\n");
}
if (func.basic_blocks.size() > 1 && !func.suspected_asm) {
if (func.cfg->is_fully_resolved()) {
} else {
unresolved_by_length[func.end_word - func.start_word].push_back(
func.guessed_name.to_string());
}
}
if (!func.suspected_asm && func.basic_blocks.size() <= 1) {
total_trivial_cfg_functions++;
}
if (!func.guessed_name.empty()) {
total_named_functions++;
}
// if (func.guessed_name.to_string() == "reset-and-call") {
// assert(false);
// }
});
lg::info("Found {} functions ({} with no control flow)", total_functions,
@@ -1039,58 +845,9 @@ void ObjectFileDB::analyze_functions_ir1() {
100.f * float(total_named_functions) / float(total_functions));
lg::info("Excluding {} asm functions", asm_funcs);
lg::info("Found {} basic blocks in {:.3f} ms", total_basic_blocks, timer.getMs());
lg::info(" {}/{} functions passed cfg analysis stage ({:.3f}%)", resolved_cfg_functions,
non_asm_funcs, 100.f * float(resolved_cfg_functions) / float(non_asm_funcs));
int successful_basic_ops = total_basic_ops - total_failed_basic_ops;
lg::info(" {}/{} basic ops converted successfully ({:.3f}%)", successful_basic_ops,
total_basic_ops, 100.f * float(successful_basic_ops) / float(total_basic_ops));
lg::info(" {}/{} basic ops with reginfo ({:.3f}%)", total_reginfo_ops, total_basic_ops,
100.f * float(total_reginfo_ops) / float(total_basic_ops));
lg::info(" {}/{} cfgs converted to ir ({:.3f}%)", successful_cfg_irs, non_asm_funcs,
100.f * float(successful_cfg_irs) / float(non_asm_funcs));
lg::info(" {}/{} functions attempted type analysis ({:.2f}%)", attempted_type_analysis,
non_asm_funcs, 100.f * float(attempted_type_analysis) / float(non_asm_funcs));
lg::info(" {}/{} functions that attempted type analysis succeeded ({:.2f}%)",
successful_type_analysis, attempted_type_analysis,
100.f * float(successful_type_analysis) / float(attempted_type_analysis));
lg::info(" {}/{} functions passed type analysis ({:.2f}%)", successful_type_analysis,
non_asm_funcs, 100.f * float(successful_type_analysis) / float(non_asm_funcs));
lg::info(
" {} functions were supposed to do type analysis but either failed or didn't know their "
"types.\n",
bad_type_analysis);
// for (auto& kv : unresolved_by_length) {
// printf("LEN %d\n", kv.first);
// for (auto& x : kv.second) {
// printf(" %s\n", x.c_str());
// }
// }
}
void ObjectFileDB::analyze_expressions() {
lg::info("- Analyzing Expressions...");
Timer timer;
int attempts = 0;
int success = 0;
bool had_failure = false;
for_each_function_def_order([&](Function& func, int segment_id, ObjectFileData& data) {
(void)segment_id;
if (/*!had_failure &&*/ func.attempted_type_analysis) {
attempts++;
lg::info("Analyze {}", func.guessed_name.to_string());
if (func.build_expression(data.linked_data)) {
success++;
} else {
func.warnings.append(";; Expression analysis failed.\n");
had_failure = true;
}
}
});
lg::info(" {}/{} functions passed expression building ({:.2f}%)\n", success, attempts,
100.f * float(success) / float(attempts));
}
void ObjectFileDB::dump_raw_objects(const std::string& output_dir) {
-2
View File
@@ -64,7 +64,6 @@ class ObjectFileDB {
bool write_json,
const std::string& file_suffix = "");
void write_debug_type_analysis(const std::string& output_dir, const std::string& suffix = "");
void analyze_functions_ir1();
void analyze_functions_ir2(const std::string& output_dir);
void ir2_top_level_pass();
@@ -81,7 +80,6 @@ class ObjectFileDB {
std::string ir2_function_to_string(ObjectFileData& data, Function& function, int seg);
void process_tpages();
void analyze_expressions();
std::string process_game_count_file();
std::string process_game_text_files();
@@ -61,7 +61,7 @@
"write_disassembly":true,
"write_hex_near_instructions":false,
"run_ir2":false,
"run_ir2":true,
// 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,
-6
View File
@@ -70,12 +70,6 @@ int main(int argc, char** argv) {
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");
}
}