mirror of
https://github.com/open-goal/jak-project
synced 2026-07-07 14:13:45 -04:00
[Decompiler] Remove most IR1 Analysis (#207)
* temp * remove some of ir1
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user