Add CFG to IR decompiler pass (#60)

* add some more cfg ir stuff

* add cond with else

* add type of recognition

* add cond to compare conversion

* finally all conds are passing

* started sc recognize, but ash min and max should be recognized first

* fix ash showing up as sc

* add abs

* fix merge issues

* try building goos with optimizations on

* sc mostly working, still need to fix right aligned nesting

* ands and ors are converting correctly now

* clean up
This commit is contained in:
water111 2020-10-06 18:14:27 -04:00 committed by GitHub
parent b102d22dd9
commit a64dd6c90b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1755 additions and 37 deletions

View File

@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 17)
# Set default compile flags for GCC
# optimization level can be set here. Note that game/ overwrites this for building game C++ code.

View File

@ -1,2 +1,9 @@
IF (WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O2")
ELSE()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
ENDIF()
add_library(goos SHARED Object.cpp TextDB.cpp Reader.cpp Interpreter.cpp InterpreterEval.cpp PrettyPrinter.cpp)
target_link_libraries(goos common_util fmt)

View File

@ -15,6 +15,7 @@ add_executable(decompiler
Disasm/InstructionMatching.cpp
Function/CfgVtx.cpp Function/CfgVtx.h
IR/BasicOpBuilder.cpp
IR/CfgBuilder.cpp
IR/IR.cpp)
target_link_libraries(decompiler

View File

@ -1721,8 +1721,9 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
// printf("%s\n", cfg->to_dot().c_str());
// printf("%s\n", cfg->to_form()->toStringPretty().c_str());
changed = changed || cfg->find_cond_w_else();
changed = changed || cfg->find_cond_n_else();
changed = changed || cfg->find_cond_w_else();
changed = changed || cfg->find_while_loop_top_level();
changed = changed || cfg->find_seq_top_level();
changed = changed || cfg->find_short_circuits();

View File

@ -572,8 +572,8 @@ bool Function::instr_starts_basic_op(int idx) {
return false;
}
IR* Function::get_basic_op_at_instr(int idx) {
return basic_ops.at(instruction_to_basic_op.at(idx)).get();
std::shared_ptr<IR> Function::get_basic_op_at_instr(int idx) {
return basic_ops.at(instruction_to_basic_op.at(idx));
}
int Function::get_basic_op_count() {

View File

@ -68,10 +68,12 @@ class Function {
void add_basic_op(std::shared_ptr<IR> op, int start_instr, int end_instr);
bool has_basic_ops() { return !basic_ops.empty(); }
bool instr_starts_basic_op(int idx);
IR* get_basic_op_at_instr(int idx);
std::shared_ptr<IR> get_basic_op_at_instr(int idx);
int get_basic_op_count();
int get_failed_basic_op_count();
std::shared_ptr<IR> ir = nullptr;
int segment = -1;
int start_word = -1;
int end_word = -1; // not inclusive, but does include padding.

View File

@ -511,6 +511,28 @@ std::shared_ptr<IR> try_sll(Instruction& instr, int idx) {
return nullptr;
}
std::shared_ptr<IR> try_dsrav(Instruction& instr, int idx) {
if (is_gpr_3(instr, InstructionKind::DSRAV, {}, {}, {}) &&
!instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) {
return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx),
std::make_shared<IR_IntMath2>(IR_IntMath2::RIGHT_SHIFT_ARITH,
make_reg(instr.get_src(0).get_reg(), idx),
make_reg(instr.get_src(1).get_reg(), idx)));
}
return nullptr;
}
std::shared_ptr<IR> try_dsrlv(Instruction& instr, int idx) {
if (is_gpr_3(instr, InstructionKind::DSRLV, {}, {}, {}) &&
!instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) {
return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx),
std::make_shared<IR_IntMath2>(IR_IntMath2::RIGHT_SHIFT_LOGIC,
make_reg(instr.get_src(0).get_reg(), idx),
make_reg(instr.get_src(1).get_reg(), idx)));
}
return nullptr;
}
std::shared_ptr<IR> try_sw(Instruction& instr, int idx) {
if (instr.kind == InstructionKind::SW && instr.get_src(1).is_sym() &&
instr.get_src(2).is_reg(make_gpr(Reg::S7))) {
@ -691,6 +713,28 @@ BranchDelay get_branch_delay(Instruction& i, int idx) {
BranchDelay b(BranchDelay::SET_REG_TRUE);
b.destination = make_reg(i.get_dst(0).get_reg(), idx);
return b;
} else if (i.kind == InstructionKind::LW && i.get_src(1).is_reg(make_gpr(Reg::S7)) &&
i.get_src(0).is_sym()) {
if (i.get_src(0).get_sym() == "binteger") {
BranchDelay b(BranchDelay::SET_BINTEGER);
b.destination = make_reg(i.get_dst(0).get_reg(), idx);
return b;
} else if (i.get_src(0).get_sym() == "pair") {
BranchDelay b(BranchDelay::SET_PAIR);
b.destination = make_reg(i.get_dst(0).get_reg(), idx);
return b;
}
} else if (i.kind == InstructionKind::DSLLV) {
BranchDelay b(BranchDelay::DSLLV);
b.destination = make_reg(i.get_dst(0).get_reg(), idx);
b.source = make_reg(i.get_src(0).get_reg(), idx);
b.source2 = make_reg(i.get_src(1).get_reg(), idx);
return b;
} else if (is_gpr_3(i, InstructionKind::DSUBU, {}, make_gpr(Reg::R0), {})) {
BranchDelay b(BranchDelay::NEGATE);
b.destination = make_reg(i.get_dst(0).get_reg(), idx);
b.source = make_reg(i.get_src(1).get_reg(), idx);
return b;
}
BranchDelay b(BranchDelay::UNKNOWN);
return b;
@ -719,6 +763,10 @@ std::shared_ptr<IR> try_bnel(Instruction& instr, Instruction& next_instr, int id
return std::make_shared<IR_Branch>(
Condition(Condition::TRUTHY, make_reg(instr.get_src(1).get_reg(), idx), nullptr, nullptr),
instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true);
} else if (instr.kind == InstructionKind::BNEL && instr.get_src(1).is_reg(make_gpr(Reg::R0))) {
return std::make_shared<IR_Branch>(
Condition(Condition::NONZERO, make_reg(instr.get_src(0).get_reg(), idx), nullptr, nullptr),
instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true);
} else if (instr.kind == InstructionKind::BNEL) {
// return std::make_shared<IR_Branch2>(IR_Branch2::NOT_EQUAL, instr.get_src(2).get_label(),
// make_reg(instr.get_src(0).get_reg(), idx),
@ -733,7 +781,13 @@ std::shared_ptr<IR> try_beql(Instruction& instr, Instruction& next_instr, int id
return std::make_shared<IR_Branch>(
Condition(Condition::FALSE, make_reg(instr.get_src(1).get_reg(), idx), nullptr, nullptr),
instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true);
} else if (instr.kind == InstructionKind::BEQL) {
} else if (instr.kind == InstructionKind::BEQL && instr.get_src(1).is_reg(make_gpr(Reg::R0))) {
return std::make_shared<IR_Branch>(
Condition(Condition::ZERO, make_reg(instr.get_src(0).get_reg(), idx), nullptr, nullptr),
instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true);
}
else if (instr.kind == InstructionKind::BEQL) {
return std::make_shared<IR_Branch>(
Condition(Condition::EQUAL, make_reg(instr.get_src(0).get_reg(), idx),
make_reg(instr.get_src(1).get_reg(), idx), nullptr),
@ -761,6 +815,36 @@ std::shared_ptr<IR> try_beq(Instruction& instr, Instruction& next_instr, int idx
return nullptr;
}
std::shared_ptr<IR> try_bgtzl(Instruction& instr, Instruction& next_instr, int idx) {
if (instr.kind == InstructionKind::BGTZL) {
return std::make_shared<IR_Branch>(
Condition(Condition::GREATER_THAN_ZERO_SIGNED, make_reg(instr.get_src(0).get_reg(), idx),
nullptr, nullptr),
instr.get_src(1).get_label(), get_branch_delay(next_instr, idx), true);
}
return nullptr;
}
std::shared_ptr<IR> try_bgezl(Instruction& instr, Instruction& next_instr, int idx) {
if (instr.kind == InstructionKind::BGEZL) {
return std::make_shared<IR_Branch>(
Condition(Condition::GEQ_ZERO_SIGNED, make_reg(instr.get_src(0).get_reg(), idx), nullptr,
nullptr),
instr.get_src(1).get_label(), get_branch_delay(next_instr, idx), true);
}
return nullptr;
}
std::shared_ptr<IR> try_bltzl(Instruction& instr, Instruction& next_instr, int idx) {
if (instr.kind == InstructionKind::BLTZL) {
return std::make_shared<IR_Branch>(
Condition(Condition::LESS_THAN_ZERO, make_reg(instr.get_src(0).get_reg(), idx), nullptr,
nullptr),
instr.get_src(1).get_label(), get_branch_delay(next_instr, idx), true);
}
return nullptr;
}
std::shared_ptr<IR> try_daddiu(Instruction& i0, Instruction& i1, int idx) {
if (i0.kind == InstructionKind::DADDIU && i1.kind == InstructionKind::MOVN &&
i0.get_src(0).get_reg() == make_gpr(Reg::S7)) {
@ -1135,6 +1219,7 @@ std::shared_ptr<IR> try_lwu(Instruction& i0,
} // namespace
void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file) {
(void)file;
for (int instr = block.start_word; instr < block.end_word; instr++) {
auto& i = func->instructions.at(instr);
@ -1214,6 +1299,15 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
case InstructionKind::BEQ:
result = try_beq(i, next, instr);
break;
case InstructionKind::BGTZL:
result = try_bgtzl(i, next, instr);
break;
case InstructionKind::BGEZL:
result = try_bgezl(i, next, instr);
break;
case InstructionKind::BLTZL:
result = try_bltzl(i, next, instr);
break;
case InstructionKind::BEQL:
result = try_beql(i, next, instr);
break;
@ -1384,6 +1478,12 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
case InstructionKind::CVTSW:
result = try_cvtsw(i, instr);
break;
case InstructionKind::DSRAV:
result = try_dsrav(i, instr);
break;
case InstructionKind::DSRLV:
result = try_dsrlv(i, instr);
break;
default:
result = nullptr;
}
@ -1396,7 +1496,7 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
// everything failed
if (!result) {
// temp hack for debug:
printf("Instruction -> BasicOp failed on %s\n", i.to_string(*file).c_str());
// printf("Instruction -> BasicOp failed on %s\n", i.to_string(*file).c_str());
func->add_basic_op(std::make_shared<IR_Failed>(), instr, instr + 1);
} else {
func->add_basic_op(result, instr, instr + length);

1012
decompiler/IR/CfgBuilder.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
#pragma once
#include <memory>
class IR;
class Function;
class LinkedObjectFile;
class ControlFlowGraph;
std::shared_ptr<IR> build_cfg_ir(Function& function, ControlFlowGraph& cfg, LinkedObjectFile& file);

View File

@ -1,21 +1,61 @@
#include "IR.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
std::vector<std::shared_ptr<IR>> IR::get_all_ir(LinkedObjectFile& file) const {
(void)file;
std::vector<std::shared_ptr<IR>> result;
get_children(&result);
size_t last_checked = 0;
size_t last_last_checked = -1;
while (last_checked != last_last_checked) {
last_last_checked = last_checked;
auto end_of_check = result.size();
for (size_t i = last_checked; i < end_of_check; i++) {
auto it = result.at(i).get();
assert(it);
it->get_children(&result);
}
last_checked = end_of_check;
}
return result;
}
std::string IR::print(const LinkedObjectFile& file) const {
// return to_form(file)->toStringPretty();
return pretty_print::to_string(to_form(file));
}
goos::Object IR_Failed::to_form(const LinkedObjectFile& file) const {
(void)file;
return pretty_print::build_list("INVALID-OPERATION");
}
void IR_Failed::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
goos::Object IR_Register::to_form(const LinkedObjectFile& file) const {
(void)file;
return pretty_print::to_symbol(reg.to_charp());
}
void IR_Register::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
goos::Object IR_Set::to_form(const LinkedObjectFile& file) const {
return pretty_print::build_list(pretty_print::to_symbol("set!"), dst->to_form(file),
src->to_form(file));
}
void IR_Set::get_children(std::vector<std::shared_ptr<IR>>* output) const {
// note that we are not returning clobber here because it shouldn't contain anything that
// the IR simplification code should touch.
output->push_back(dst);
output->push_back(src);
}
goos::Object IR_Store::to_form(const LinkedObjectFile& file) const {
std::string store_operator;
switch (kind) {
@ -51,26 +91,33 @@ goos::Object IR_Store::to_form(const LinkedObjectFile& file) const {
src->to_form(file));
}
goos::Object IR_Failed::to_form(const LinkedObjectFile& file) const {
(void)file;
return pretty_print::build_list("INVALID-OPERATION");
}
goos::Object IR_Symbol::to_form(const LinkedObjectFile& file) const {
(void)file;
return pretty_print::to_symbol("'" + name);
}
void IR_Symbol::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
goos::Object IR_SymbolValue::to_form(const LinkedObjectFile& file) const {
(void)file;
return pretty_print::to_symbol(name);
}
void IR_SymbolValue::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
goos::Object IR_StaticAddress::to_form(const LinkedObjectFile& file) const {
// return pretty_print::build_list(pretty_print::to_symbol("&"), file.get_label_name(label_id));
return pretty_print::to_symbol(file.get_label_name(label_id));
}
void IR_StaticAddress::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
goos::Object IR_Load::to_form(const LinkedObjectFile& file) const {
std::string load_operator;
switch (kind) {
@ -119,6 +166,10 @@ goos::Object IR_Load::to_form(const LinkedObjectFile& file) const {
return pretty_print::build_list(pretty_print::to_symbol(load_operator), location->to_form(file));
}
void IR_Load::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(location);
}
goos::Object IR_FloatMath2::to_form(const LinkedObjectFile& file) const {
std::string math_operator;
switch (kind) {
@ -148,6 +199,15 @@ goos::Object IR_FloatMath2::to_form(const LinkedObjectFile& file) const {
arg1->to_form(file));
}
void IR_FloatMath2::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(arg0);
output->push_back(arg1);
}
void IR_FloatMath1::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(arg);
}
goos::Object IR_IntMath2::to_form(const LinkedObjectFile& file) const {
std::string math_operator;
switch (kind) {
@ -203,18 +263,30 @@ goos::Object IR_IntMath2::to_form(const LinkedObjectFile& file) const {
arg1->to_form(file));
}
void IR_IntMath2::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(arg0);
output->push_back(arg1);
}
goos::Object IR_IntMath1::to_form(const LinkedObjectFile& file) const {
std::string math_operator;
switch (kind) {
case NOT:
math_operator = "lognot";
break;
case ABS:
math_operator = "abs.si";
break;
default:
assert(false);
}
return pretty_print::build_list(pretty_print::to_symbol(math_operator), arg->to_form(file));
}
void IR_IntMath1::get_children(std::vector<std::shared_ptr<IR>>* output) const {
output->push_back(arg);
}
goos::Object IR_FloatMath1::to_form(const LinkedObjectFile& file) const {
std::string math_operator;
switch (kind) {
@ -244,11 +316,19 @@ goos::Object IR_Call::to_form(const LinkedObjectFile& file) const {
return pretty_print::build_list("call!");
}
void IR_Call::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
goos::Object IR_IntegerConstant::to_form(const LinkedObjectFile& file) const {
(void)file;
return pretty_print::to_symbol(std::to_string(value));
}
void IR_IntegerConstant::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
goos::Object BranchDelay::to_form(const LinkedObjectFile& file) const {
(void)file;
switch (kind) {
@ -263,6 +343,20 @@ goos::Object BranchDelay::to_form(const LinkedObjectFile& file) const {
case SET_REG_REG:
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
source->to_form(file));
case SET_BINTEGER:
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
"binteger");
case SET_PAIR:
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
"pair");
case DSLLV:
return pretty_print::build_list(
pretty_print::to_symbol("set!"), destination->to_form(file),
pretty_print::build_list(pretty_print::to_symbol("shl"), source->to_form(file),
source2->to_form(file)));
case NEGATE:
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
pretty_print::build_list("-", source->to_form(file)));
case UNKNOWN:
return pretty_print::build_list("unknown-branch-delay");
default:
@ -270,6 +364,20 @@ goos::Object BranchDelay::to_form(const LinkedObjectFile& file) const {
}
}
void BranchDelay::get_children(std::vector<std::shared_ptr<IR>>* output) const {
if (destination) {
output->push_back(destination);
}
if (source) {
output->push_back(source);
}
if (source2) {
output->push_back(source2);
}
}
goos::Object IR_Nop::to_form(const LinkedObjectFile& file) const {
(void)file;
return pretty_print::build_list("nop!");
@ -296,14 +404,108 @@ int Condition::num_args() const {
case NONZERO:
case FALSE:
case TRUTHY:
case GREATER_THAN_ZERO_SIGNED:
case GEQ_ZERO_SIGNED:
case LESS_THAN_ZERO:
case LEQ_ZERO_SIGNED:
return 1;
case ALWAYS:
case NEVER:
return 0;
default:
assert(false);
}
}
void Condition::get_children(std::vector<std::shared_ptr<IR>>* output) const {
if (src0) {
output->push_back(src0);
}
if (src1) {
output->push_back(src1);
}
}
void Condition::invert() {
switch (kind) {
case NOT_EQUAL:
kind = EQUAL;
break;
case EQUAL:
kind = NOT_EQUAL;
break;
case LESS_THAN_SIGNED:
kind = GEQ_SIGNED;
break;
case GREATER_THAN_SIGNED:
kind = LEQ_SIGNED;
break;
case LEQ_SIGNED:
kind = GREATER_THAN_SIGNED;
break;
case GEQ_SIGNED:
kind = LESS_THAN_SIGNED;
break;
case GREATER_THAN_ZERO_SIGNED:
kind = LEQ_ZERO_SIGNED;
break;
case LEQ_ZERO_SIGNED:
kind = GREATER_THAN_ZERO_SIGNED;
break;
case LESS_THAN_ZERO:
kind = GEQ_ZERO_SIGNED;
break;
case GEQ_ZERO_SIGNED:
kind = LESS_THAN_ZERO;
break;
case LESS_THAN_UNSIGNED:
kind = GEQ_UNSIGNED;
break;
case GREATER_THAN_UNSIGNED:
kind = LEQ_UNSIGNED;
break;
case LEQ_UNSIGNED:
kind = GREATER_THAN_UNSIGNED;
break;
case GEQ_UNSIGNED:
kind = LESS_THAN_UNSIGNED;
break;
case ZERO:
kind = NONZERO;
break;
case NONZERO:
kind = ZERO;
break;
case FALSE:
kind = TRUTHY;
break;
case TRUTHY:
kind = FALSE;
break;
case ALWAYS:
kind = NEVER;
break;
case NEVER:
kind = ALWAYS;
break;
case FLOAT_EQUAL:
kind = FLOAT_NOT_EQUAL;
break;
case FLOAT_NOT_EQUAL:
kind = FLOAT_EQUAL;
break;
case FLOAT_LESS_THAN:
kind = FLOAT_GEQ;
break;
case FLOAT_GEQ:
kind = FLOAT_LESS_THAN;
break;
default:
assert(false);
}
}
goos::Object Condition::to_form(const LinkedObjectFile& file) const {
int nargs = num_args();
std::string condtion_operator;
@ -353,6 +555,9 @@ goos::Object Condition::to_form(const LinkedObjectFile& file) const {
case ALWAYS:
condtion_operator = "'#t";
break;
case NEVER:
condtion_operator = "'#f";
break;
case FLOAT_EQUAL:
condtion_operator = "=.f";
break;
@ -365,6 +570,18 @@ goos::Object Condition::to_form(const LinkedObjectFile& file) const {
case FLOAT_GEQ:
condtion_operator = ">=.f";
break;
case GREATER_THAN_ZERO_SIGNED:
condtion_operator = ">0.si";
break;
case GEQ_ZERO_SIGNED:
condtion_operator = ">=0.si";
break;
case LESS_THAN_ZERO:
condtion_operator = "<0.si";
break;
case LEQ_ZERO_SIGNED:
condtion_operator = "<=0.si";
break;
default:
assert(false);
}
@ -392,11 +609,195 @@ goos::Object IR_Branch::to_form(const LinkedObjectFile& file) const {
pretty_print::to_symbol(file.get_label_name(dest_label_idx)), branch_delay.to_form(file));
}
void IR_Branch::get_children(std::vector<std::shared_ptr<IR>>* output) const {
condition.get_children(output);
branch_delay.get_children(output);
}
goos::Object IR_Compare::to_form(const LinkedObjectFile& file) const {
return condition.to_form(file);
}
void IR_Compare::get_children(std::vector<std::shared_ptr<IR>>* output) const {
condition.get_children(output);
}
goos::Object IR_Suspend::to_form(const LinkedObjectFile& file) const {
(void)file;
return pretty_print::build_list("suspend!");
}
}
void IR_Nop::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
void IR_Suspend::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
goos::Object IR_Begin::to_form(const LinkedObjectFile& file) const {
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_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);
}

View File

@ -2,6 +2,7 @@
#define JAK_IR_H
#include <cassert>
#include <utility>
#include "decompiler/Disasm/Register.h"
#include "common/goos/PrettyPrinter.h"
@ -10,7 +11,9 @@ class LinkedObjectFile;
class IR {
public:
virtual goos::Object to_form(const LinkedObjectFile& file) const = 0;
std::vector<std::shared_ptr<IR>> get_all_ir(LinkedObjectFile& file) const;
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;
};
@ -19,12 +22,14 @@ class IR_Failed : public IR {
public:
IR_Failed() = default;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_Register : public IR {
public:
IR_Register(Register _reg, int _instr_idx) : reg(_reg), instr_idx(_instr_idx) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
Register reg;
int instr_idx = -1;
};
@ -45,6 +50,7 @@ class IR_Set : public IR {
IR_Set(Kind _kind, std::shared_ptr<IR> _dst, std::shared_ptr<IR> _src)
: 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;
std::shared_ptr<IR> dst, src;
std::shared_ptr<IR> clobber = nullptr;
};
@ -60,34 +66,38 @@ class IR_Store : public IR_Set {
class IR_Symbol : public IR {
public:
IR_Symbol(std::string _name) : name(std::move(_name)) {}
explicit IR_Symbol(std::string _name) : name(std::move(_name)) {}
std::string name;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_SymbolValue : public IR {
public:
IR_SymbolValue(std::string _name) : name(std::move(_name)) {}
explicit IR_SymbolValue(std::string _name) : name(std::move(_name)) {}
std::string name;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_StaticAddress : public IR {
public:
IR_StaticAddress(int _label_id) : label_id(_label_id) {}
explicit IR_StaticAddress(int _label_id) : label_id(_label_id) {}
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;
};
class IR_Load : public IR {
public:
enum Kind { UNSIGNED, SIGNED, FLOAT } kind;
IR_Load(Kind _kind, int _size, const std::shared_ptr<IR>& _location)
: kind(_kind), size(_size), location(_location) {}
IR_Load(Kind _kind, int _size, std::shared_ptr<IR> _location)
: kind(_kind), size(_size), location(std::move(_location)) {}
int size;
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;
};
class IR_FloatMath2 : public IR {
@ -97,6 +107,7 @@ class IR_FloatMath2 : public IR {
: kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {}
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;
};
class IR_FloatMath1 : public IR {
@ -105,6 +116,7 @@ class IR_FloatMath1 : public IR {
IR_FloatMath1(Kind _kind, std::shared_ptr<IR> _arg) : kind(_kind), arg(std::move(_arg)) {}
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;
};
class IR_IntMath2 : public IR {
@ -130,20 +142,23 @@ class IR_IntMath2 : public IR {
: kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {}
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;
};
class IR_IntMath1 : public IR {
public:
enum Kind { NOT } kind;
enum Kind { NOT, ABS } kind;
IR_IntMath1(Kind _kind, std::shared_ptr<IR> _arg) : kind(_kind), arg(std::move(_arg)) {}
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;
};
class IR_Call : public IR {
public:
IR_Call() = default;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_IntegerConstant : public IR {
@ -151,13 +166,25 @@ class IR_IntegerConstant : public IR {
int64_t value;
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;
};
struct BranchDelay {
enum Kind { NOP, SET_REG_FALSE, SET_REG_TRUE, SET_REG_REG, UNKNOWN } kind;
std::shared_ptr<IR> destination = nullptr, source = nullptr;
BranchDelay(Kind _kind) : kind(_kind) {}
enum Kind {
NOP,
SET_REG_FALSE,
SET_REG_TRUE,
SET_REG_REG,
SET_BINTEGER,
SET_PAIR,
DSLLV,
NEGATE,
UNKNOWN
} kind;
std::shared_ptr<IR> destination = nullptr, source = nullptr, source2 = nullptr;
explicit BranchDelay(Kind _kind) : kind(_kind) {}
goos::Object to_form(const LinkedObjectFile& file) const;
void get_children(std::vector<std::shared_ptr<IR>>* output) const;
};
struct Condition {
@ -168,6 +195,10 @@ struct Condition {
GREATER_THAN_SIGNED,
LEQ_SIGNED,
GEQ_SIGNED,
GREATER_THAN_ZERO_SIGNED,
LEQ_ZERO_SIGNED,
LESS_THAN_ZERO,
GEQ_ZERO_SIGNED,
LESS_THAN_UNSIGNED,
GREATER_THAN_UNSIGNED,
LEQ_UNSIGNED,
@ -177,6 +208,7 @@ struct Condition {
FALSE,
TRUTHY,
ALWAYS,
NEVER,
FLOAT_EQUAL,
FLOAT_NOT_EQUAL,
FLOAT_LESS_THAN,
@ -201,6 +233,8 @@ struct Condition {
int num_args() const;
goos::Object to_form(const LinkedObjectFile& file) const;
std::shared_ptr<IR> src0, src1, clobber;
void get_children(std::vector<std::shared_ptr<IR>>* output) const;
void invert();
};
class IR_Branch : public IR {
@ -217,6 +251,7 @@ class IR_Branch : public IR {
bool likely;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_Compare : public IR {
@ -226,18 +261,113 @@ class IR_Compare : public IR {
Condition condition;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_Nop : public IR {
public:
IR_Nop() = default;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_Suspend : public IR {
public:
IR_Suspend() = default;
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
class IR_Begin : public 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 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;
};
class IR_CondWithElse : public 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;
};
// this one doesn't have an else statement. Will return false if none of the cases are taken.
class IR_Cond : public IR {
public:
struct Entry {
std::shared_ptr<IR> condition = nullptr;
std::shared_ptr<IR> body = nullptr;
std::shared_ptr<IR> false_destination = nullptr;
bool cleaned = 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;
};
// this will work on pairs, bintegers, or basics
class IR_GetRuntimeType : public 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;
};
class IR_ShortCircuit : public IR {
public:
struct Entry {
std::shared_ptr<IR> condition = nullptr;
std::shared_ptr<IR> output = nullptr; // where the delay slot writes to.
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;
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;
};
class IR_Ash : public IR {
public:
std::shared_ptr<IR> shift_amount, value, clobber;
bool is_signed = true;
IR_Ash(std::shared_ptr<IR> _shift_amount,
std::shared_ptr<IR> _value,
std::shared_ptr<IR> _clobber,
bool _is_signed)
: shift_amount(std::move(_shift_amount)),
value(std::move(_value)),
clobber(std::move(_clobber)),
is_signed(_is_signed) {}
goos::Object to_form(const LinkedObjectFile& file) const override;
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
};
#endif // JAK_IR_H

View File

@ -622,6 +622,11 @@ std::string LinkedObjectFile::print_disassembly() {
*/
}
if (func.ir) {
result += ";; ir\n";
result += func.ir->print(*this);
}
result += "\n\n\n";
}

View File

@ -19,6 +19,7 @@
#include "common/util/FileUtil.h"
#include "decompiler/Function/BasicBlocks.h"
#include "decompiler/IR/BasicOpBuilder.h"
#include "decompiler/IR/CfgBuilder.h"
/*!
* Get a unique name for this object file.
@ -591,17 +592,21 @@ void ObjectFileDB::analyze_functions() {
// }
}
int total_nontrivial_functions = 0;
int total_resolved_nontrivial_functions = 0;
int total_trivial_cfg_functions = 0;
int total_named_functions = 0;
int total_basic_ops = 0;
int total_failed_basic_ops = 0;
int asm_funcs = 0;
int non_asm_funcs = 0;
int successful_cfg_irs = 0;
std::map<int, std::vector<std::string>> unresolved_by_length;
if (get_config().find_basic_blocks) {
timer.start();
int total_basic_blocks = 0;
for_each_function([&](Function& func, int segment_id, ObjectFileData& data) {
// printf("in %s\n", func.guessed_name.to_string().c_str());
auto blocks = find_blocks_in_function(data.linked_data, segment_id, func);
total_basic_blocks += blocks.size();
func.basic_blocks = blocks;
@ -618,17 +623,21 @@ void ObjectFileDB::analyze_functions() {
total_basic_ops += func.get_basic_op_count();
total_failed_basic_ops += func.get_failed_basic_op_count();
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 {
resolved_cfg_functions++;
asm_funcs++;
}
if (func.basic_blocks.size() > 1 && !func.suspected_asm) {
total_nontrivial_functions++;
if (func.cfg->is_fully_resolved()) {
total_resolved_nontrivial_functions++;
} else {
if (!func.guessed_name.empty()) {
unresolved_by_length[func.end_word - func.start_word].push_back(
@ -637,25 +646,32 @@ void ObjectFileDB::analyze_functions() {
}
}
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() == "inspect") {
// assert(false);
// }
});
printf("Found %d functions (%d with nontrivial cfgs)\n", total_functions,
total_nontrivial_functions);
printf("Found %d functions (%d with no control flow)\n", total_functions,
total_trivial_cfg_functions);
printf("Named %d/%d functions (%.2f%%)\n", total_named_functions, total_functions,
100.f * float(total_named_functions) / float(total_functions));
printf("Excluding %d asm functions\n", asm_funcs);
printf("Found %d basic blocks in %.3f ms\n", total_basic_blocks, timer.getMs());
printf(" %d/%d functions passed cfg analysis stage (%.2f%%)\n", resolved_cfg_functions,
total_functions, 100.f * float(resolved_cfg_functions) / float(total_functions));
printf(" %d/%d nontrivial cfg's resolved (%.2f%%)\n", total_resolved_nontrivial_functions,
total_nontrivial_functions,
100.f * float(total_resolved_nontrivial_functions) / float(total_nontrivial_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;
printf(" %d/%d basic ops converted successfully (%.2f%%)\n", successful_basic_ops,
total_basic_ops, 100.f * float(successful_basic_ops) / float(total_basic_ops));
printf(" %d/%d cfgs converted to ir (%.2f%%)\n", successful_cfg_irs, non_asm_funcs,
100.f * float(successful_cfg_irs) / float(non_asm_funcs));
// for (auto& kv : unresolved_by_length) {
// printf("LEN %d\n", kv.first);

View File

@ -30,12 +30,45 @@
"asm_functions_by_name":[
// gcommon
"ash", "abs", "min", "max", "(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!",
"min", "max", "(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!",
// pskernel
"resend-exception", "kernel-set-interrupt-vector", "kernel-set-exception-vector", "return-from-exception",
"kernel-read", "kernel-read-function", "kernel-write", "kernel-write-function", "kernel-copy-to-kernel-ram",
// this one needs more investigation. nothing looks weird about it but it fails...
"camera-change-to",
// two back to back arithmetic shifts...
"texture-relocate",
// this one fails due to false compaction where an else case has only a not expression in it.
"master-is-hopeful-better?",
// fails for unknown reason
"target-falling-anim-trans", "change-brother",
// merged right typecase... can probably handle this
"cspace-inspect-tree",
// these are all valid, but use short circuiting branches in strange ways. There's probably a few compiler uses that we're not
"(method 21 actor-link-info)","(method 20 actor-link-info)","(method 28 collide-shape-prim-mesh)", "(method 35 collide-shape)",
"debug-menu-item-var-render", "(method 14 level)","add-blue-motion","anim-tester-add-newobj","(method 27 orb-cache-top)",
// real asm
"cspace<-parented-transformq-joint!", "blerc-a-fragment", "render-boundary-tri", "render-boundary-quad",
"(method 19 collide-shape-prim-sphere)","vector-segment-distance-point!", "exp", "(method 11 collide-mesh-cache)",
"(method 13 collide-edge-work)", "ambient-inspect",
"(method 11 cpu-thread)", "atan0", "sincos!", "sincos-rad!", "disasm-dma-list", "vblank-handler", "vif1-handler",
"vif1-handler-debug", "entity-actor-count", "decompress-frame-data-pair-to-accumulator",
"decompress-frame-data-to-accumulator", "normalize-frame-quaternions", "clear-frame-accumulator",
"generic-copy-vtx-dclr-dtex", "generic-no-light-dproc-only", "generic-no-light-proc", "mercneric-bittable-asm",
"generic-tie-decompress", "matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!", "generic-prepare-dma-single",
"(method 13 collide-shape-prim-sphere)", "(method 14 collide-shape-prim-sphere)", "(method 12 collide-shape-prim-sphere)",
"adgif-shader<-texture-with-update!", "generic-interp-dproc", "sprite-draw-distorters", "draw-bones", "(method 9 collide-mesh-cache)",
"(method 18 collide-shape-prim-sphere)","birth-pickup-at-point",
"collide-do-primitives", "draw-bones-check-longest-edge-asm",
"sp-launch-particles-var", "(method 15 collide-shape-prim-mesh)", "(method 15 collide-shape-prim-sphere)",
"(method 45 collide-shape)", "cam-layout-save-cam-trans", "kernel-copy-function", "dma-sync-hang", "generic-no-light-dproc",

View File

@ -1,5 +1,5 @@
# We define our own compilation flags here.
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 17)
# Set default compile flags for GCC
# optimization level can be set here. Note that game/ overwrites this for building game C++ code.