Merge remote-tracking branch 'upstream/w/cfg_2_ir' into logging

This commit is contained in:
Shay
2020-10-04 20:29:39 -06:00
27 changed files with 2284 additions and 882 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
- name: Build Project
run: |
cd build
make
make -j4
- name: Run Tests
timeout-minutes: 5
run: ./test_code_coverage.sh
+9 -1
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.
@@ -53,6 +53,14 @@ endif()
# includes relative to top level jak-project folder
include_directories(./)
# build spdlog as a shared library to improve compile times
# adding this as a SYSTEM include suppresses all the terrible warnings in spdlog
include_directories(SYSTEM third-party/spdlog/include)
# this makes spdlog generate a shared library that we can link against
set(SPDLOG_BUILD_SHARED ON)
# this makes the spdlog includes not use the header only version, making compiling faster
add_definitions(-DSPDLOG_COMPILED_LIB)
# build asset packer/unpacker
add_subdirectory(asset_tool)
+8 -1
View File
@@ -1,2 +1,9 @@
add_library(goos SHARED Object.cpp TextDB.cpp Reader.cpp Interpreter.cpp InterpreterEval.cpp)
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)
+3
View File
@@ -4,6 +4,9 @@
namespace goos {
std::shared_ptr<EmptyListObject> gEmptyList = nullptr;
std::shared_ptr<EmptyListObject>& get_empty_list() {
return gEmptyList;
}
/*!
* Convert type to string (name in brackets)
+4 -4
View File
@@ -319,7 +319,7 @@ class Object {
// There is a single heap allocated EmptyListObject.
class EmptyListObject;
extern std::shared_ptr<EmptyListObject> gEmptyList;
std::shared_ptr<EmptyListObject>& get_empty_list();
class EmptyListObject : public HeapObject {
public:
@@ -327,10 +327,10 @@ class EmptyListObject : public HeapObject {
static Object make_new() {
Object obj;
obj.type = ObjectType::EMPTY_LIST;
if (!gEmptyList) {
gEmptyList = std::make_shared<EmptyListObject>();
if (!get_empty_list()) {
get_empty_list() = std::make_shared<EmptyListObject>();
}
obj.heap_obj = gEmptyList;
obj.heap_obj = get_empty_list();
return obj;
}
+567
View File
@@ -0,0 +1,567 @@
#include <cassert>
#include <stdexcept>
#include <utility>
#include "PrettyPrinter.h"
#include "Reader.h"
namespace pretty_print {
/*!
* A single token which cannot be split between lines.
*/
struct FormToken {
enum class TokenKind {
WHITESPACE,
STRING,
OPEN_PAREN,
DOT,
CLOSE_PAREN,
EMPTY_PAIR,
SPECIAL_STRING // has different alignment rules than STRING
} kind;
explicit FormToken(TokenKind _kind, std::string _str = "") : kind(_kind), str(std::move(_str)) {}
std::string str;
std::string toString() const {
std::string s;
switch (kind) {
case TokenKind::WHITESPACE:
s.push_back(' ');
break;
case TokenKind::STRING:
s.append(str);
break;
case TokenKind::OPEN_PAREN:
s.push_back('(');
break;
case TokenKind::DOT:
s.push_back('.');
break;
case TokenKind::CLOSE_PAREN:
s.push_back(')');
break;
case TokenKind::EMPTY_PAIR:
s.append("()");
break;
case TokenKind::SPECIAL_STRING:
s.append(str);
break;
default:
throw std::runtime_error("toString unknown token kind");
}
return s;
}
};
/*!
* Convert a GOOS object to tokens and add it to the list.
* This is the main function which recursively builds a list of tokens out of an s-expression.
*
* Note that not all GOOS objects can be pretty printed. Only the ones that can be directly
* generated by the reader.
*/
void add_to_token_list(const goos::Object& obj, std::vector<FormToken>* tokens) {
switch (obj.type) {
case goos::ObjectType::EMPTY_LIST:
tokens->emplace_back(FormToken::TokenKind::EMPTY_PAIR);
break;
// all of these can just be printed to a string and turned into a 'symbol'
case goos::ObjectType::INTEGER:
case goos::ObjectType::FLOAT:
case goos::ObjectType::CHAR:
case goos::ObjectType::SYMBOL:
case goos::ObjectType::STRING:
tokens->emplace_back(FormToken::TokenKind::STRING, obj.print());
break;
// it's important to break the pair up into smaller tokens which can then be split
// across lines.
case goos::ObjectType::PAIR: {
tokens->emplace_back(FormToken::TokenKind::OPEN_PAREN);
auto* to_print = &obj;
for (;;) {
if (to_print->is_pair()) {
// first print the car into our token list:
add_to_token_list(to_print->as_pair()->car, tokens);
// then load up the cdr as the next thing to print
to_print = &to_print->as_pair()->cdr;
if (to_print->is_empty_list()) {
// we're done, add a close paren and finish
tokens->emplace_back(FormToken::TokenKind::CLOSE_PAREN);
return;
} else {
// more to print, add whitespace
tokens->emplace_back(FormToken::TokenKind::WHITESPACE);
}
} else {
// got an improper list.
// add a dot, space
tokens->emplace_back(FormToken::TokenKind::DOT);
tokens->emplace_back(FormToken::TokenKind::WHITESPACE);
// then the thing and a close paren.
add_to_token_list(*to_print, tokens);
tokens->emplace_back(FormToken::TokenKind::CLOSE_PAREN);
return; // and we're done with this list.
}
}
} break;
// these are unsupported by the pretty printer.
case goos::ObjectType::ARRAY: // todo, we should probably handle arrays.
case goos::ObjectType::LAMBDA:
case goos::ObjectType::MACRO:
case goos::ObjectType::ENVIRONMENT:
throw std::runtime_error("tried to pretty print a goos object kind which is not supported.");
default:
assert(false);
}
}
/*!
* Linked list node representing a token in the output (whitespace, paren, newline, etc)
*/
struct PrettyPrinterNode {
FormToken* tok = nullptr; // if we aren't a newline, we will have a token.
int line = -1; // line that token occurs on. undef for newlines
int lineIndent = -1; // indent of line. only valid for first token in the line
int offset = -1; // offset of beginning of token from left margin
int specialIndentDelta = 0;
bool is_line_separator = false; // true if line separator (not a token)
PrettyPrinterNode *next = nullptr, *prev = nullptr; // linked list
PrettyPrinterNode* paren =
nullptr; // pointer to open paren if in parens. open paren points to close and vice versa
explicit PrettyPrinterNode(FormToken* _tok) { tok = _tok; }
PrettyPrinterNode() = default;
};
/*!
* Container to track and cleanup all nodes after use.
*/
struct NodePool {
std::vector<PrettyPrinterNode*> nodes;
PrettyPrinterNode* allocate(FormToken* x) {
auto result = new PrettyPrinterNode(x);
nodes.push_back(result);
return result;
}
PrettyPrinterNode* allocate() {
auto result = new PrettyPrinterNode;
nodes.push_back(result);
return result;
}
NodePool() = default;
~NodePool() {
for (auto& x : nodes) {
delete x;
}
}
// so we don't accidentally copy this.
NodePool& operator=(const NodePool&) = delete;
NodePool(const NodePool&) = delete;
};
/*!
* Splice in a line break after the given node, it there isn't one already and if it isn't the last
* node.
*/
void insertNewlineAfter(NodePool& pool, PrettyPrinterNode* node, int specialIndentDelta) {
if (node->next && !node->next->is_line_separator) {
auto* nl = pool.allocate();
auto* next = node->next;
node->next = nl;
nl->prev = node;
nl->next = next;
next->prev = nl;
nl->is_line_separator = true;
nl->specialIndentDelta = specialIndentDelta;
}
}
/*!
* Splice in a line break before the given node, if there isn't one already and if it isn't the
* first node.
*/
void insertNewlineBefore(NodePool& pool, PrettyPrinterNode* node, int specialIndentDelta) {
if (node->prev && !node->prev->is_line_separator) {
auto* nl = pool.allocate();
auto* prev = node->prev;
prev->next = nl;
nl->prev = prev;
nl->next = node;
node->prev = nl;
nl->is_line_separator = true;
nl->specialIndentDelta = specialIndentDelta;
}
}
/*!
* Break a list across multiple lines. This is how line lengths are decreased.
* This does not compute the proper indentation and leaves the list in a bad state.
* After this has been called, the entire selection should be reformatted with propagate_pretty
*/
void breakList(NodePool& pool, PrettyPrinterNode* leftParen) {
assert(!leftParen->is_line_separator);
assert(leftParen->tok->kind == FormToken::TokenKind::OPEN_PAREN);
auto* rp = leftParen->paren;
assert(rp->tok->kind == FormToken::TokenKind::CLOSE_PAREN);
for (auto* n = leftParen->next; n && n != rp; n = n->next) {
if (!n->is_line_separator) {
if (n->tok->kind == FormToken::TokenKind::OPEN_PAREN) {
n = n->paren;
assert(n->tok->kind == FormToken::TokenKind::CLOSE_PAREN);
insertNewlineAfter(pool, n, 0);
} else if (n->tok->kind != FormToken::TokenKind::WHITESPACE) {
assert(n->tok->kind != FormToken::TokenKind::CLOSE_PAREN);
insertNewlineAfter(pool, n, 0);
}
}
}
}
/*!
* Compute proper line numbers, offsets, and indents for a list of tokens with newlines
* Will add newlines for close parens if needed.
*/
static PrettyPrinterNode* propagatePretty(NodePool& pool,
PrettyPrinterNode* list,
int line_length) {
// propagate line numbers
PrettyPrinterNode* rv = nullptr;
int line = list->line;
for (auto* n = list; n; n = n->next) {
if (n->is_line_separator) {
line++;
} else {
n->line = line;
// add the weird newline.
if (n->tok->kind == FormToken::TokenKind::CLOSE_PAREN) {
if (n->line != n->paren->line) {
if (n->prev && !n->prev->is_line_separator) {
insertNewlineBefore(pool, n, 0);
line++;
}
if (n->next && !n->next->is_line_separator) {
insertNewlineAfter(pool, n, 0);
}
}
}
}
}
// compute offsets and indents
std::vector<int> indentStack;
indentStack.push_back(0);
int offset = 0;
PrettyPrinterNode* line_start = list;
bool previous_line_sep = false;
for (auto* n = list; n; n = n->next) {
if (n->is_line_separator) {
previous_line_sep = true;
offset = indentStack.back() += n->specialIndentDelta;
} else {
if (previous_line_sep) {
line_start = n;
n->lineIndent = offset;
previous_line_sep = false;
}
n->offset = offset;
offset += n->tok->toString().length();
if (offset > line_length && !rv)
rv = line_start;
if (n->tok->kind == FormToken::TokenKind::OPEN_PAREN) {
if (!n->prev || n->prev->is_line_separator) {
indentStack.push_back(offset + 1);
} else {
indentStack.push_back(offset - 1);
}
}
if (n->tok->kind == FormToken::TokenKind::CLOSE_PAREN) {
indentStack.pop_back();
}
}
}
return rv;
}
/*!
* Get the token on the start of the next line. nullptr if we're the last line.
*/
PrettyPrinterNode* getNextLine(PrettyPrinterNode* start) {
assert(!start->is_line_separator);
int line = start->line;
for (;;) {
if (start->is_line_separator || start->line == line) {
if (start->next)
start = start->next;
else
return nullptr;
} else {
break;
}
}
return start;
}
/*!
* Get the next open paren on the current line (can start in the middle of line, not inclusive of
* start) nullptr if there's no open parens on the rest of this line.
*/
PrettyPrinterNode* getNextListOnLine(PrettyPrinterNode* start) {
int line = start->line;
assert(!start->is_line_separator);
if (!start->next || start->next->is_line_separator)
return nullptr;
start = start->next;
while (!start->is_line_separator && start->line == line) {
if (start->tok->kind == FormToken::TokenKind::OPEN_PAREN)
return start;
if (!start->next)
return nullptr;
start = start->next;
}
return nullptr;
}
/*!
* Get the first open paren on the current line (can start in the middle of line, inclusive of
* start) nullptr if there's no open parens on the rest of this line
*/
PrettyPrinterNode* getFirstListOnLine(PrettyPrinterNode* start) {
int line = start->line;
assert(!start->is_line_separator);
while (!start->is_line_separator && start->line == line) {
if (start->tok->kind == FormToken::TokenKind::OPEN_PAREN)
return start;
if (!start->next)
return nullptr;
start = start->next;
}
return nullptr;
}
/*!
* Get the first token on the first line which exceeds the max length
*/
PrettyPrinterNode* getFirstBadLine(PrettyPrinterNode* start, int line_length) {
assert(!start->is_line_separator);
int currentLine = start->line;
auto* currentLineNode = start;
for (;;) {
if (start->is_line_separator) {
assert(start->next);
start = start->next;
} else {
if (start->line != currentLine) {
currentLine = start->line;
currentLineNode = start;
}
if (start->offset > line_length) {
return currentLineNode;
}
if (!start->next) {
return nullptr;
}
start = start->next;
}
}
}
/*!
* Break insertion algorithm.
*/
void insertBreaksAsNeeded(NodePool& pool, PrettyPrinterNode* head, int line_length) {
PrettyPrinterNode* last_line_complete = nullptr;
PrettyPrinterNode* line_to_start_line_search = head;
// loop over lines
for (;;) {
// compute lines as needed
propagatePretty(pool, head, line_length);
// search for a bad line starting at the last line we fixed
PrettyPrinterNode* candidate_line = getFirstBadLine(line_to_start_line_search, line_length);
// if we got the same line we started on, this means we couldn't fix it.
if (candidate_line == last_line_complete) {
candidate_line = nullptr; // so we say our candidate was bad and try to find another
PrettyPrinterNode* next_line = getNextLine(line_to_start_line_search);
if (next_line) {
candidate_line = getFirstBadLine(next_line, line_length);
}
}
if (!candidate_line)
break;
// okay, we have a line which needs fixing.
assert(!candidate_line->prev || candidate_line->prev->is_line_separator);
PrettyPrinterNode* form_to_start = getFirstListOnLine(candidate_line);
for (;;) {
if (!form_to_start) {
// this means we failed to hit the desired line length...
break;
}
breakList(pool, form_to_start);
propagatePretty(pool, head, line_length);
if (getFirstBadLine(candidate_line, line_length) != candidate_line) {
break;
}
form_to_start = getNextListOnLine(form_to_start);
if (!form_to_start)
break;
}
last_line_complete = candidate_line;
line_to_start_line_search = candidate_line;
}
}
void insertSpecialBreaks(NodePool& pool, PrettyPrinterNode* node) {
for (; node; node = node->next) {
if (!node->is_line_separator && node->tok->kind == FormToken::TokenKind::STRING) {
std::string& name = node->tok->str;
if (name == "deftype") { // todo!
auto* parent_type_dec = getNextListOnLine(node);
if (parent_type_dec) {
insertNewlineAfter(pool, parent_type_dec->paren, 0);
}
}
if (name == "defun" || name == "defmethod") {
auto* parent_type_dec = getNextListOnLine(node);
if (parent_type_dec) {
insertNewlineAfter(pool, parent_type_dec->paren, 0);
}
}
if (name.at(0) == '"') {
insertNewlineAfter(pool, node, 0);
}
}
}
}
std::string to_string(const goos::Object& obj, int line_length) {
NodePool pool;
std::vector<FormToken> tokens;
add_to_token_list(obj, &tokens);
assert(!tokens.empty());
std::string pretty;
// build linked list of nodes
PrettyPrinterNode* head = pool.allocate(&tokens[0]);
PrettyPrinterNode* node = head;
head->line = 0;
head->offset = 0;
head->lineIndent = 0;
int offset = head->tok->toString().length();
for (size_t i = 1; i < tokens.size(); i++) {
node->next = pool.allocate(&tokens[i]);
node->next->prev = node;
node = node->next;
node->line = 0;
node->offset = offset;
offset += node->tok->toString().length();
node->lineIndent = 0;
}
// attach parens.
std::vector<PrettyPrinterNode*> parenStack;
parenStack.push_back(nullptr);
for (PrettyPrinterNode* n = head; n; n = n->next) {
if (n->tok->kind == FormToken::TokenKind::OPEN_PAREN) {
parenStack.push_back(n);
} else if (n->tok->kind == FormToken::TokenKind::CLOSE_PAREN) {
n->paren = parenStack.back();
parenStack.back()->paren = n;
parenStack.pop_back();
} else {
n->paren = parenStack.back();
}
}
assert(parenStack.size() == 1);
assert(!parenStack.back());
insertSpecialBreaks(pool, head);
propagatePretty(pool, head, line_length);
insertBreaksAsNeeded(pool, head, line_length);
// write to string
bool newline_prev = true;
for (PrettyPrinterNode* n = head; n; n = n->next) {
if (n->is_line_separator) {
pretty.push_back('\n');
newline_prev = true;
} else {
if (newline_prev) {
pretty.append(n->lineIndent, ' ');
newline_prev = false;
if (n->tok->kind == FormToken::TokenKind::WHITESPACE)
continue;
}
pretty.append(n->tok->toString());
}
}
return pretty;
}
goos::Reader pretty_printer_reader;
goos::Reader& get_pretty_printer_reader() {
return pretty_printer_reader;
}
goos::Object to_symbol(const std::string& str) {
return goos::SymbolObject::make_new(pretty_printer_reader.symbolTable, str);
}
goos::Object build_list(const std::string& str) {
return build_list(to_symbol(str));
}
goos::Object build_list(const goos::Object& obj) {
return goos::PairObject::make_new(obj, goos::EmptyListObject::make_new());
}
goos::Object build_list(const std::vector<goos::Object>& objects) {
if (objects.empty()) {
return goos::EmptyListObject::make_new();
} else {
return build_list(objects.data(), objects.size());
}
}
// build a list out of an array of forms
goos::Object build_list(const goos::Object* objects, int count) {
assert(count);
auto car = objects[0];
goos::Object cdr;
if (count - 1) {
cdr = build_list(objects + 1, count - 1);
} else {
cdr = goos::EmptyListObject::make_new();
}
return goos::PairObject::make_new(car, cdr);
}
// build a list out of a vector of strings that are converted to symbols
goos::Object build_list(const std::vector<std::string>& symbols) {
if (symbols.empty()) {
return goos::EmptyListObject::make_new();
}
std::vector<goos::Object> f;
f.reserve(symbols.size());
for (auto& x : symbols) {
f.push_back(to_symbol(x));
}
return build_list(f.data(), f.size());
}
} // namespace pretty_print
+49
View File
@@ -0,0 +1,49 @@
/*!
* @file PrettyPrinter.h
* A Pretty Printer for GOOS.
*/
#pragma once
#include <string>
#include <vector>
#include "common/goos/Object.h"
#include "common/goos/Reader.h"
namespace pretty_print {
// main pretty print function
std::string to_string(const goos::Object& obj, int line_length = 80);
// string -> object (as a symbol)
goos::Object to_symbol(const std::string& str);
// list with a single symbol from a string
goos::Object build_list(const std::string& str);
// wrap an object in a list
goos::Object build_list(const goos::Object& obj);
// build a list out of a vector of forms
goos::Object build_list(const std::vector<goos::Object>& objects);
// build a list out of an array of forms
goos::Object build_list(const goos::Object* objects, int count);
// build a list out of a vector of strings that are converted to symbols
goos::Object build_list(const std::vector<std::string>& symbols);
// fancy wrapper functions. Due to template magic these can call each other
// and accept mixed arguments!
template <typename... Args>
goos::Object build_list(const std::string& str, Args... rest) {
return goos::PairObject::make_new(to_symbol(str), build_list(rest...));
}
template <typename... Args>
goos::Object build_list(const goos::Object& car, Args... rest) {
return goos::PairObject::make_new(car, build_list(rest...));
}
goos::Reader& get_pretty_printer_reader();
} // namespace pretty_print
+2 -2
View File
@@ -1,5 +1,4 @@
add_executable(decompiler
util/LispPrint.cpp
main.cpp
ObjectFile/ObjectFileDB.cpp
Disasm/Instruction.cpp
@@ -11,15 +10,16 @@ add_executable(decompiler
Function/Function.cpp
util/FileIO.cpp
config.cpp
util/LispPrint.cpp
util/DecompilerTypeSystem.cpp
Function/BasicBlocks.cpp
Disasm/InstructionMatching.cpp
Function/CfgVtx.cpp Function/CfgVtx.h
IR/BasicOpBuilder.cpp
IR/CfgBuilder.cpp
IR/IR.cpp)
target_link_libraries(decompiler
goos
minilzo
common_util
type_system
+55 -55
View File
@@ -140,8 +140,8 @@ std::string BlockVtx::to_string() {
}
}
std::shared_ptr<Form> BlockVtx::to_form() {
return toForm("b" + std::to_string(block_id));
goos::Object BlockVtx::to_form() {
return pretty_print::to_symbol("b" + std::to_string(block_id));
}
std::string SequenceVtx::to_string() {
@@ -152,117 +152,117 @@ std::string SequenceVtx::to_string() {
return result;
}
std::shared_ptr<Form> SequenceVtx::to_form() {
std::vector<std::shared_ptr<Form>> forms;
forms.push_back(toForm("seq"));
goos::Object SequenceVtx::to_form() {
std::vector<goos::Object> forms;
forms.push_back(pretty_print::to_symbol("seq"));
for (auto* x : seq) {
forms.push_back(x->to_form());
}
return buildList(forms);
return pretty_print::build_list(forms);
}
std::string EntryVtx::to_string() {
return "ENTRY";
}
std::shared_ptr<Form> EntryVtx::to_form() {
return toForm("entry");
goos::Object EntryVtx::to_form() {
return pretty_print::to_symbol("entry");
}
std::string ExitVtx::to_string() {
return "EXIT";
}
std::shared_ptr<Form> ExitVtx::to_form() {
return toForm("exit");
goos::Object ExitVtx::to_form() {
return pretty_print::to_symbol("exit");
}
std::string CondWithElse::to_string() {
return "CONDWE" + std::to_string(uid);
}
std::shared_ptr<Form> CondWithElse::to_form() {
std::vector<std::shared_ptr<Form>> forms;
forms.push_back(toForm("cond"));
goos::Object CondWithElse::to_form() {
std::vector<goos::Object> forms;
forms.push_back(pretty_print::to_symbol("cond"));
for (const auto& x : entries) {
std::vector<std::shared_ptr<Form>> e = {x.condition->to_form(), x.body->to_form()};
forms.push_back(buildList(e));
std::vector<goos::Object> e = {x.condition->to_form(), x.body->to_form()};
forms.push_back(pretty_print::build_list(e));
}
std::vector<std::shared_ptr<Form>> e = {toForm("else"), else_vtx->to_form()};
forms.push_back(buildList(e));
return buildList(forms);
std::vector<goos::Object> e = {pretty_print::to_symbol("else"), else_vtx->to_form()};
forms.push_back(pretty_print::build_list(e));
return pretty_print::build_list(forms);
}
std::string CondNoElse::to_string() {
return "CONDNE" + std::to_string(uid);
}
std::shared_ptr<Form> CondNoElse::to_form() {
std::vector<std::shared_ptr<Form>> forms;
forms.push_back(toForm("cond"));
goos::Object CondNoElse::to_form() {
std::vector<goos::Object> forms;
forms.push_back(pretty_print::to_symbol("cond"));
for (const auto& x : entries) {
std::vector<std::shared_ptr<Form>> e = {x.condition->to_form(), x.body->to_form()};
forms.push_back(buildList(e));
std::vector<goos::Object> e = {x.condition->to_form(), x.body->to_form()};
forms.push_back(pretty_print::build_list(e));
}
return buildList(forms);
return pretty_print::build_list(forms);
}
std::string WhileLoop::to_string() {
return "WHL" + std::to_string(uid);
}
std::shared_ptr<Form> WhileLoop::to_form() {
std::vector<std::shared_ptr<Form>> forms = {toForm("while"), condition->to_form(),
body->to_form()};
return buildList(forms);
goos::Object WhileLoop::to_form() {
std::vector<goos::Object> forms = {pretty_print::to_symbol("while"), condition->to_form(),
body->to_form()};
return pretty_print::build_list(forms);
}
std::string UntilLoop::to_string() {
return "UNTL" + std::to_string(uid);
}
std::shared_ptr<Form> UntilLoop::to_form() {
std::vector<std::shared_ptr<Form>> forms = {toForm("until"), condition->to_form(),
body->to_form()};
return buildList(forms);
goos::Object UntilLoop::to_form() {
std::vector<goos::Object> forms = {pretty_print::to_symbol("until"), condition->to_form(),
body->to_form()};
return pretty_print::build_list(forms);
}
std::string UntilLoop_single::to_string() {
return "UNTLS" + std::to_string(uid);
}
std::shared_ptr<Form> UntilLoop_single::to_form() {
std::vector<std::shared_ptr<Form>> forms = {toForm("until1"), block->to_form()};
return buildList(forms);
goos::Object UntilLoop_single::to_form() {
std::vector<goos::Object> forms = {pretty_print::to_symbol("until1"), block->to_form()};
return pretty_print::build_list(forms);
}
std::string InfiniteLoopBlock::to_string() {
return "INFL" + std::to_string(uid);
}
std::shared_ptr<Form> InfiniteLoopBlock::to_form() {
std::vector<std::shared_ptr<Form>> forms = {toForm("inf-loop"), block->to_form()};
return buildList(forms);
goos::Object InfiniteLoopBlock::to_form() {
std::vector<goos::Object> forms = {pretty_print::to_symbol("inf-loop"), block->to_form()};
return pretty_print::build_list(forms);
}
std::string ShortCircuit::to_string() {
return "SC" + std::to_string(uid);
}
std::shared_ptr<Form> ShortCircuit::to_form() {
std::vector<std::shared_ptr<Form>> forms;
forms.push_back(toForm("sc"));
goos::Object ShortCircuit::to_form() {
std::vector<goos::Object> forms;
forms.push_back(pretty_print::to_symbol("sc"));
for (const auto& x : entries) {
forms.push_back(x->to_form());
}
return buildList(forms);
return pretty_print::build_list(forms);
}
/*
std::shared_ptr<Form> IfElseVtx::to_form() {
std::vector<std::shared_ptr<Form>> forms = {toForm("if"), condition->to_form(),
goos::Object IfElseVtx::to_form() {
std::vector<goos::Object> forms = {pretty_print::to_symbol("if"), condition->to_form(),
true_case->to_form(), false_case->to_form()};
return buildList(forms);
return pretty_print::build_list(forms);
}
std::string IfElseVtx::to_string() {
@@ -274,10 +274,10 @@ std::string GotoEnd::to_string() {
return "goto_end" + std::to_string(uid);
}
std::shared_ptr<Form> GotoEnd::to_form() {
std::vector<std::shared_ptr<Form>> forms = {toForm("return-from-function"), body->to_form(),
unreachable_block->to_form()};
return buildList(forms);
goos::Object GotoEnd::to_form() {
std::vector<goos::Object> forms = {pretty_print::to_symbol("return-from-function"),
body->to_form(), unreachable_block->to_form()};
return pretty_print::build_list(forms);
}
ControlFlowGraph::ControlFlowGraph() {
@@ -361,17 +361,17 @@ CfgVtx* ControlFlowGraph::get_single_top_level() {
* Turn into a form. If fully resolved, prints the nested control flow. Otherwise puts all the
* ungrouped stuff into an "(ungrouped ...)" form and prints that.
*/
std::shared_ptr<Form> ControlFlowGraph::to_form() {
goos::Object ControlFlowGraph::to_form() {
if (get_top_level_vertices_count() == 1) {
return get_single_top_level()->to_form();
} else {
std::vector<std::shared_ptr<Form>> forms = {toForm("ungrouped")};
std::vector<goos::Object> forms = {pretty_print::to_symbol("ungrouped")};
for (auto* x : m_node_pool) {
if (!x->parent && x != entry() && x != exit()) {
forms.push_back(x->to_form());
}
}
return buildList(forms);
return pretty_print::build_list(forms);
}
}
@@ -380,8 +380,7 @@ std::shared_ptr<Form> ControlFlowGraph::to_form() {
* ungrouped stuff into an "(ungrouped ...)" form and prints that.
*/
std::string ControlFlowGraph::to_form_string() {
// todo - fix bug in pretty printing and reduce this to 80!
return to_form()->toStringPretty(0, 140);
return pretty_print::to_string(to_form());
}
// bool ControlFlowGraph::compact_top_level() {
@@ -1722,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();
+16 -16
View File
@@ -6,7 +6,7 @@
#include <string>
#include <vector>
#include <cassert>
#include "decompiler/util/LispPrint.h"
#include "common/goos/PrettyPrinter.h"
/*!
* In v, find an item equal to old, and replace it with replace.
@@ -61,8 +61,8 @@ void replace_exactly_one_in(std::vector<T>& v, T old, T replace) {
*/
class CfgVtx {
public:
virtual std::string to_string() = 0; // convert to a single line string for debugging
virtual std::shared_ptr<Form> to_form() = 0; // recursive print as LISP form.
virtual std::string to_string() = 0; // convert to a single line string for debugging
virtual goos::Object to_form() = 0; // recursive print as LISP form.
virtual ~CfgVtx() = default;
CfgVtx* parent = nullptr; // parent structure, or nullptr if top level
@@ -125,7 +125,7 @@ class CfgVtx {
class EntryVtx : public CfgVtx {
public:
EntryVtx() = default;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
std::string to_string() override;
};
@@ -135,7 +135,7 @@ class EntryVtx : public CfgVtx {
class ExitVtx : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
};
/*!
@@ -145,7 +145,7 @@ class BlockVtx : public CfgVtx {
public:
explicit BlockVtx(int id) : block_id(id) {}
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
int block_id = -1; // which block are we?
bool is_early_exit_block = false; // are we an empty block at the end for early exits to jump to?
};
@@ -157,7 +157,7 @@ class BlockVtx : public CfgVtx {
class SequenceVtx : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
std::vector<CfgVtx*> seq;
};
@@ -169,7 +169,7 @@ class SequenceVtx : public CfgVtx {
class CondWithElse : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
struct Entry {
Entry() = default;
@@ -190,7 +190,7 @@ class CondWithElse : public CfgVtx {
class CondNoElse : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
struct Entry {
Entry() = default;
@@ -205,7 +205,7 @@ class CondNoElse : public CfgVtx {
class WhileLoop : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
CfgVtx* condition = nullptr;
CfgVtx* body = nullptr;
@@ -214,7 +214,7 @@ class WhileLoop : public CfgVtx {
class UntilLoop : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
CfgVtx* condition = nullptr;
CfgVtx* body = nullptr;
@@ -223,7 +223,7 @@ class UntilLoop : public CfgVtx {
class UntilLoop_single : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
CfgVtx* block = nullptr;
};
@@ -231,21 +231,21 @@ class UntilLoop_single : public CfgVtx {
class ShortCircuit : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
std::vector<CfgVtx*> entries;
};
class InfiniteLoopBlock : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
CfgVtx* block;
};
class GotoEnd : public CfgVtx {
public:
std::string to_string() override;
std::shared_ptr<Form> to_form() override;
goos::Object to_form() override;
CfgVtx* body = nullptr;
CfgVtx* unreachable_block = nullptr;
};
@@ -260,7 +260,7 @@ class ControlFlowGraph {
ControlFlowGraph();
~ControlFlowGraph();
std::shared_ptr<Form> to_form();
goos::Object to_form();
std::string to_form_string();
std::string to_dot();
int get_top_level_vertices_count();
+2 -2
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() {
+3 -1
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.
+84 -9
View File
@@ -5,10 +5,6 @@
namespace {
struct ConvertState {
void reset() {}
};
std::shared_ptr<IR_Set> make_set(IR_Set::Kind kind,
const std::shared_ptr<IR>& dst,
const std::shared_ptr<IR>& src) {
@@ -515,6 +511,17 @@ 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_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))) {
@@ -695,6 +702,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;
@@ -737,7 +766,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),
@@ -765,6 +800,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)) {
@@ -1122,6 +1187,7 @@ std::shared_ptr<IR> try_lwu(Instruction& i0,
Instruction& i3,
Instruction& i4,
int idx) {
(void)idx;
auto s6 = make_gpr(Reg::S6);
if (i0.kind == InstructionKind::LWU && i0.get_dst(0).is_reg(s6) &&
i0.get_src(0).get_imm() == 44 && i0.get_src(1).is_reg(s6) &&
@@ -1138,8 +1204,6 @@ std::shared_ptr<IR> try_lwu(Instruction& i0,
} // namespace
void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file) {
ConvertState state;
for (int instr = block.start_word; instr < block.end_word; instr++) {
auto& i = func->instructions.at(instr);
@@ -1219,6 +1283,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;
@@ -1389,6 +1462,9 @@ 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;
default:
result = nullptr;
}
@@ -1400,9 +1476,8 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
// everything failed
if (!result) {
state.reset();
// 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);
+798
View File
@@ -0,0 +1,798 @@
#include "third-party/fmt/format.h"
#include <unordered_set>
#include "common/util/MatchParam.h"
#include "CfgBuilder.h"
#include "decompiler/Function/CfgVtx.h"
#include "decompiler/Function/Function.h"
#include "decompiler/Disasm/InstructionMatching.h"
namespace {
std::shared_ptr<IR> cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx);
/*!
* This adds a single CfgVtx* to a list of IR's by converting it with cfg to IR.
* The trick here is that it will recursively inline anything which would generate an IR begin.
* This avoids the case where Begin's are nested excessively.
*/
void insert_cfg_into_list(Function& f,
LinkedObjectFile& file,
std::vector<std::shared_ptr<IR>>* output,
CfgVtx* vtx) {
auto as_sequence = dynamic_cast<SequenceVtx*>(vtx);
auto as_block = dynamic_cast<BlockVtx*>(vtx);
if (as_sequence) {
for (auto& x : as_sequence->seq) {
insert_cfg_into_list(f, file, output, x);
}
} else if (as_block) {
auto& block = f.basic_blocks.at(as_block->block_id);
IR* last = nullptr;
for (int instr = block.start_word; instr < block.end_word; instr++) {
auto got = f.get_basic_op_at_instr(instr);
if (got.get() == last) {
continue;
}
last = got.get();
output->push_back(got);
}
} else {
// doesn't look like we're going to get something that can be inlined, so try as usual
auto ir = cfg_to_ir(f, file, vtx);
auto ir_as_begin = dynamic_cast<IR_Begin*>(ir.get());
if (ir_as_begin) {
// we unexpectedly got a begin, even though we didn't think we would. This is okay, but we
// should inline this begin to avoid nested begins. This happens in the case where an entire
// control flow pattern is turned into a single op (like type-of) and includes some ops at
// the beginning. We don't have a good way of knowing this will happen until we try it.
for (auto& x : ir_as_begin->forms) {
output->push_back(x);
}
} else {
output->push_back(ir);
}
}
}
/*!
* If it's a begin with a branch as the last operation, returns a pointer to the branch IR
* and also a pointer to the vector which holds the branch operation in its last slot.
* Otherwise returns nullptr. Useful to modify or remove branches found at the end of blocks,
* and inline things into the begin they were found in.
*/
std::pair<IR_Branch*, std::vector<std::shared_ptr<IR>>*> get_condition_branch_as_vector(IR* in) {
auto as_seq = dynamic_cast<IR_Begin*>(in);
if (as_seq) {
auto irb = dynamic_cast<IR_Branch*>(as_seq->forms.back().get());
auto loc = &as_seq->forms;
assert(irb);
return std::make_pair(irb, loc);
}
return std::make_pair(nullptr, nullptr);
}
/*!
* Given an IR, find a branch IR at the end, and also the location of it so it can be patched.
* Returns nullptr as the first item in the pair if it didn't work.
*/
std::pair<IR_Branch*, std::shared_ptr<IR>*> get_condition_branch(std::shared_ptr<IR>* in) {
IR_Branch* condition_branch = dynamic_cast<IR_Branch*>(in->get());
std::shared_ptr<IR>* condition_branch_location = in;
if (!condition_branch) {
// not 100% sure this will always work
auto as_seq = dynamic_cast<IR_Begin*>(in->get());
if (as_seq) {
condition_branch = dynamic_cast<IR_Branch*>(as_seq->forms.back().get());
condition_branch_location = &as_seq->forms.back();
}
}
return std::make_pair(condition_branch, condition_branch_location);
}
/*!
* Given a CondWithElse IR, remove the internal branches and set the condition to be an actual
* compare IR instead of a branch.
* Doesn't "rebalance" the leading condition because this runs way before expression compaction.
*/
void clean_up_cond_with_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
(void)file;
auto cwe = dynamic_cast<IR_CondWithElse*>(ir->get());
assert(cwe);
for (auto& e : cwe->entries) {
if (e.cleaned) {
continue;
}
auto jump_to_next = get_condition_branch(&e.condition);
assert(jump_to_next.first);
assert(jump_to_next.first->branch_delay.kind == BranchDelay::NOP);
// patch the jump to next with a condition.
auto replacement = std::make_shared<IR_Compare>(jump_to_next.first->condition);
*(jump_to_next.second) = replacement;
// patch the jump at the end of a block.
auto jump_to_end = get_condition_branch(&e.body);
assert(jump_to_end.first);
assert(jump_to_end.first->branch_delay.kind == BranchDelay::NOP);
assert(jump_to_end.first->condition.kind == Condition::ALWAYS);
// if possible, we just want to remove this from the sequence its in.
// but sometimes there's a case with nothing in it so there is no sequence.
// in this case, we can just replace the branch with a NOP IR to indicate that nothing
// happens in this case, but there was still GOAL code to test for it.
// this happens rarely, as you would expect.
auto as_end_of_sequence = get_condition_branch_as_vector(e.body.get());
if (as_end_of_sequence.first) {
assert(as_end_of_sequence.second->size() > 1);
as_end_of_sequence.second->pop_back();
} else {
// In the future we could consider having a more explicit "this case is empty" operator so
// this doesn't get confused with an actual MIPS nop.
*(jump_to_end.second) = std::make_shared<IR_Nop>();
}
e.cleaned = true;
}
}
/*!
* A GOAL comparison which produces a boolean is recognized as a cond-no-else by the CFG analysis.
* But it should not be decompiled as a branching statement.
* This either succeeds or asserts and must be called with with something that can be converted
* successfully
*/
void convert_cond_no_else_to_compare(std::shared_ptr<IR>* ir) {
auto cne = dynamic_cast<IR_Cond*>(ir->get());
assert(cne);
auto condition = get_condition_branch(&cne->entries.front().condition);
assert(condition.first);
auto body = dynamic_cast<IR_Set*>(cne->entries.front().body.get());
assert(body);
auto dst = body->dst;
auto src = dynamic_cast<IR_Symbol*>(body->src.get());
assert(src->name == "#f");
assert(cne->entries.size() == 1);
auto condition_as_single = dynamic_cast<IR_Branch*>(cne->entries.front().condition.get());
if (condition_as_single) {
// as far as I can tell this is totally valid but just happens to not appear?
// if this case is ever hit in the future it's fine and we just need to implement this.
// but leaving empty for now so there's fewer things to test.
// assert(false);
auto replacement = std::make_shared<IR_Set>(
IR_Set::REG_64, dst, std::make_shared<IR_Compare>(condition.first->condition));
*ir = replacement;
} else {
auto condition_as_seq = dynamic_cast<IR_Begin*>(cne->entries.front().condition.get());
assert(condition_as_seq);
if (condition_as_seq) {
auto replacement = std::make_shared<IR_Begin>();
replacement->forms = condition_as_seq->forms;
assert(condition.second == &condition_as_seq->forms.back());
replacement->forms.pop_back();
replacement->forms.push_back(std::make_shared<IR_Set>(
IR_Set::REG_64, dst, std::make_shared<IR_Compare>(condition.first->condition)));
*ir = replacement;
}
}
}
/*!
* Replace internal branches inside a CondNoElse IR.
* If possible will simplify the entire expression into a comparison operation if possible.
* Will record which registers are set to false in branch delay slots.
* The exact behavior here isn't really clear to me. It's possible that these delay set false
* were disabled in cases where the result of the cond was none, or was a number or something.
* But it generally seems inconsistent. The expression propagation step will have to deal with
* this.
*/
void clean_up_cond_no_else(std::shared_ptr<IR>* ir, LinkedObjectFile& file) {
auto cne = dynamic_cast<IR_Cond*>(ir->get());
assert(cne);
for (size_t idx = 0; idx < cne->entries.size(); idx++) {
auto& e = cne->entries.at(idx);
if (e.cleaned) {
continue;
}
auto jump_to_next = get_condition_branch(&e.condition);
assert(jump_to_next.first);
if (jump_to_next.first->branch_delay.kind == BranchDelay::SET_REG_TRUE &&
cne->entries.size() == 1) {
convert_cond_no_else_to_compare(ir);
} else {
assert(jump_to_next.first->branch_delay.kind == BranchDelay::SET_REG_FALSE ||
jump_to_next.first->branch_delay.kind == BranchDelay::NOP);
assert(jump_to_next.first->condition.kind != Condition::ALWAYS);
if (jump_to_next.first->branch_delay.kind == BranchDelay::SET_REG_FALSE) {
assert(!e.false_destination);
e.false_destination = jump_to_next.first->branch_delay.destination;
assert(e.false_destination);
}
auto replacement = std::make_shared<IR_Compare>(jump_to_next.first->condition);
*(jump_to_next.second) = replacement;
e.cleaned = true;
if (idx != cne->entries.size() - 1) {
auto jump_to_end = get_condition_branch(&e.body);
assert(jump_to_end.first);
assert(jump_to_end.first->branch_delay.kind == BranchDelay::NOP);
assert(jump_to_end.first->condition.kind == Condition::ALWAYS);
auto as_end_of_sequence = get_condition_branch_as_vector(e.body.get());
if (as_end_of_sequence.first) {
assert(as_end_of_sequence.second->size() > 1);
as_end_of_sequence.second->pop_back();
} else {
*(jump_to_end.second) = std::make_shared<IR_Nop>();
}
}
}
}
}
/*!
* Match for a (set! reg (math reg reg)) form
*/
bool is_int_math_3(IR* ir,
MatchParam<IR_IntMath2::Kind> kind,
MatchParam<Register> dst,
MatchParam<Register> src0,
MatchParam<Register> src1,
Register* dst_out = nullptr,
Register* src0_out = nullptr,
Register* src1_out = nullptr) {
// should be a set reg to int math 2 ir
auto set = dynamic_cast<IR_Set*>(ir);
if (!set) {
return false;
}
// destination should be a register
auto dest = dynamic_cast<IR_Register*>(set->dst.get());
if (!dest || dst != dest->reg) {
return false;
}
auto math = dynamic_cast<IR_IntMath2*>(set->src.get());
if (!math || kind != math->kind) {
return false;
}
auto arg0 = dynamic_cast<IR_Register*>(math->arg0.get());
auto arg1 = dynamic_cast<IR_Register*>(math->arg1.get());
if (!arg0 || src0 != arg0->reg || !arg1 || src1 != arg1->reg) {
return false;
}
// it's a match!
if (dst_out) {
*dst_out = dest->reg;
}
if (src0_out) {
*src0_out = arg0->reg;
}
if (src1_out) {
*src1_out = arg1->reg;
}
return true;
}
bool is_same_reg(IR* a, IR* b) {
auto ar = dynamic_cast<IR_Register*>(a);
auto br = dynamic_cast<IR_Register*>(b);
return ar && br && ar->reg == br->reg;
}
std::shared_ptr<IR> try_sc_as_abs(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) {
if (vtx->entries.size() != 1) {
return nullptr;
}
auto b0 = dynamic_cast<BlockVtx*>(vtx->entries.at(0));
if (!b0) {
return nullptr;
}
// todo, seems possible to be a single op instead of a begin here.
auto b0_ptr = cfg_to_ir(f, file, b0);
auto b0_ir = dynamic_cast<IR_Begin*>(b0_ptr.get());
auto branch = dynamic_cast<IR_Branch*>(b0_ir->forms.back().get());
if (!branch) {
return nullptr;
}
// check the branch instruction
if (!branch->likely || branch->condition.kind != Condition::LESS_THAN_ZERO ||
branch->branch_delay.kind != BranchDelay::NEGATE) {
return nullptr;
}
auto input = branch->condition.src0;
auto output = branch->branch_delay.destination;
assert(is_same_reg(input.get(), branch->branch_delay.source.get()));
if (b0_ir->forms.size() == 1) {
// this is probably fine but happens to not occur in anything we try yet.
assert(false);
} else {
// remove the branch
b0_ir->forms.pop_back();
// add the ash
b0_ir->forms.push_back(std::make_shared<IR_Set>(
IR_Set::REG_64, output, std::make_shared<IR_IntMath1>(IR_IntMath1::ABS, input)));
return b0_ptr;
}
return nullptr;
}
/*!
* Attempt to convert a short circuit expression into an arithmetic shift.
* GOAL's shift function accepts positive/negative numbers to determine the direction
* of the shift.
*/
std::shared_ptr<IR> try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) {
if (vtx->entries.size() != 2) {
return nullptr;
}
// todo, I think b0 could possibly be something more complicated, depending on how we order.
auto b0 = dynamic_cast<BlockVtx*>(vtx->entries.at(0));
auto b1 = dynamic_cast<BlockVtx*>(vtx->entries.at(1));
if (!b0 || !b1) {
return nullptr;
}
// todo, seems possible to be a single op instead of a begin...
auto b0_ptr = cfg_to_ir(f, file, b0);
auto b0_ir = dynamic_cast<IR_Begin*>(b0_ptr.get());
auto b1_ptr = cfg_to_ir(f, file, b1);
auto b1_ir = dynamic_cast<IR_Begin*>(b1_ptr.get());
if (!b0_ir || !b1_ir) {
return nullptr;
}
auto branch = dynamic_cast<IR_Branch*>(b0_ir->forms.back().get());
if (!branch || b1_ir->forms.size() != 2) {
return nullptr;
}
// check the branch instruction
if (!branch->likely || branch->condition.kind != Condition::GEQ_ZERO_SIGNED ||
branch->branch_delay.kind != BranchDelay::DSLLV) {
return nullptr;
}
/*
* bgezl s5, L109 ; s5 is the shift amount
dsllv a0, a0, s5 ; a0 is both input and output here
dsubu a1, r0, s5 ; a1 is a temp here
dsrav a0, a0, a1 ; a0 is both input and output here
*/
auto sa_in = dynamic_cast<IR_Register*>(branch->condition.src0.get());
assert(sa_in);
auto result = dynamic_cast<IR_Register*>(branch->branch_delay.destination.get());
auto value_in = dynamic_cast<IR_Register*>(branch->branch_delay.source.get());
auto sa_in2 = dynamic_cast<IR_Register*>(branch->branch_delay.source2.get());
assert(result && value_in && sa_in2);
assert(sa_in->reg == sa_in2->reg);
auto dsubu_candidate = b1_ir->forms.at(0);
auto dsrav_candidate = b1_ir->forms.at(1);
Register clobber;
if (!is_int_math_3(dsubu_candidate.get(), IR_IntMath2::SUB, {}, make_gpr(Reg::R0), sa_in->reg,
&clobber)) {
return nullptr;
}
assert(result);
assert(value_in);
if (!is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_ARITH, result->reg,
value_in->reg, clobber)) {
return nullptr;
}
std::shared_ptr<IR> clobber_ir = nullptr;
auto dsubu_set = dynamic_cast<IR_Set*>(dsubu_candidate.get());
auto dsrav_set = dynamic_cast<IR_Set*>(dsrav_candidate.get());
if (clobber != result->reg) {
clobber_ir = dsubu_set->dst;
}
std::shared_ptr<IR> dest_ir = branch->branch_delay.destination;
std::shared_ptr<IR> shift_ir = branch->condition.src0;
std::shared_ptr<IR> value_ir = dynamic_cast<IR_IntMath2*>(dsrav_set->src.get())->arg0;
if (b0_ir->forms.size() == 1) {
// this is probably fine but happens to not occur in anything we try yet.
assert(false);
} else {
// remove the branch
b0_ir->forms.pop_back();
// add the ash
b0_ir->forms.push_back(std::make_shared<IR_Set>(
IR_Set::REG_64, dest_ir, std::make_shared<IR_Ash>(shift_ir, value_ir, clobber_ir)));
return b0_ptr;
}
return nullptr;
}
/*!
* Try to convert a short circuiting expression into a "type-of" expression.
* We do this before attempting the normal and/or expressions.
*/
std::shared_ptr<IR> try_sc_as_type_of(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) {
// the assembly looks like this:
/*
dsll32 v1, a0, 29 ;; (set! v1 (shl a0 61))
beql v1, r0, L60 ;; (bl! (= v1 r0) L60 (unknown-branch-delay))
lw v1, binteger(s7)
bgtzl v1, L60 ;; (bl! (>0.s v1) L60 (unknown-branch-delay))
lw v1, pair(s7)
lwu v1, -4(a0) ;; (set! v1 (l.wu (+.i a0 -4)))
L60:
*/
// some of these checks may be a little bit overkill but it's a nice way to sanity check that
// we have actually decoded everything correctly.
if (vtx->entries.size() != 3) {
return nullptr;
}
auto b0 = dynamic_cast<BlockVtx*>(vtx->entries.at(0));
auto b1 = dynamic_cast<BlockVtx*>(vtx->entries.at(1));
auto b2 = dynamic_cast<BlockVtx*>(vtx->entries.at(2));
if (!b0 || !b1 || !b2) {
return nullptr;
}
auto b0_ptr = cfg_to_ir(f, file, b0);
auto b0_ir = dynamic_cast<IR_Begin*>(b0_ptr.get());
auto b1_ptr = cfg_to_ir(f, file, b1);
auto b1_ir = dynamic_cast<IR_Branch*>(b1_ptr.get());
auto b2_ptr = cfg_to_ir(f, file, b2);
auto b2_ir = dynamic_cast<IR_Set*>(b2_ptr.get());
if (!b0_ir || !b1_ir || !b2_ir) {
return nullptr;
}
auto set_shift = dynamic_cast<IR_Set*>(b0_ir->forms.at(b0_ir->forms.size() - 2).get());
if (!set_shift) {
return nullptr;
}
auto temp_reg0 = dynamic_cast<IR_Register*>(set_shift->dst.get());
if (!temp_reg0) {
return nullptr;
}
auto shift = dynamic_cast<IR_IntMath2*>(set_shift->src.get());
if (!shift || shift->kind != IR_IntMath2::LEFT_SHIFT) {
return nullptr;
}
auto src_reg = dynamic_cast<IR_Register*>(shift->arg0.get());
auto sa = dynamic_cast<IR_IntegerConstant*>(shift->arg1.get());
if (!src_reg || !sa || sa->value != 61) {
return nullptr;
}
auto first_branch = dynamic_cast<IR_Branch*>(b0_ir->forms.back().get());
auto second_branch = b1_ir;
auto else_case = b2_ir;
if (!first_branch || first_branch->branch_delay.kind != BranchDelay::SET_BINTEGER ||
first_branch->condition.kind != Condition::ZERO || !first_branch->likely) {
return nullptr;
}
auto temp_reg = dynamic_cast<IR_Register*>(first_branch->condition.src0.get());
assert(temp_reg);
assert(temp_reg->reg == temp_reg0->reg);
auto dst_reg = dynamic_cast<IR_Register*>(first_branch->branch_delay.destination.get());
assert(dst_reg);
if (!second_branch || second_branch->branch_delay.kind != BranchDelay::SET_PAIR ||
second_branch->condition.kind != Condition::GREATER_THAN_ZERO_SIGNED ||
!second_branch->likely) {
return nullptr;
}
// check we agree on destination register.
auto dst_reg2 = dynamic_cast<IR_Register*>(second_branch->branch_delay.destination.get());
assert(dst_reg2->reg == dst_reg->reg);
// else case is a lwu to grab the type from a basic
assert(else_case);
auto dst_reg3 = dynamic_cast<IR_Register*>(else_case->dst.get());
assert(dst_reg3);
assert(dst_reg3->reg == dst_reg->reg);
auto load_op = dynamic_cast<IR_Load*>(else_case->src.get());
if (!load_op || load_op->kind != IR_Load::UNSIGNED || load_op->size != 4) {
return nullptr;
}
auto load_loc = dynamic_cast<IR_IntMath2*>(load_op->location.get());
if (!load_loc || load_loc->kind != IR_IntMath2::ADD) {
return nullptr;
}
auto src_reg3 = dynamic_cast<IR_Register*>(load_loc->arg0.get());
auto offset = dynamic_cast<IR_IntegerConstant*>(load_loc->arg1.get());
if (!src_reg3 || !offset) {
return nullptr;
}
assert(src_reg3->reg == src_reg->reg);
assert(offset->value == -4);
std::shared_ptr<IR> clobber = nullptr;
if (temp_reg->reg != dst_reg->reg) {
clobber = first_branch->condition.src0;
}
if (b0_ir->forms.size() == 2) {
return std::make_shared<IR_Set>(IR_Set::REG_64, else_case->dst,
std::make_shared<IR_GetRuntimeType>(shift->arg0, clobber));
} else {
// remove the branch
b0_ir->forms.pop_back();
// remove the shift
b0_ir->forms.pop_back();
// add the type-of
b0_ir->forms.push_back(std::make_shared<IR_Set>(
IR_Set::REG_64, else_case->dst, std::make_shared<IR_GetRuntimeType>(shift->arg0, clobber)));
return b0_ptr;
}
}
std::shared_ptr<IR> merge_cond_else_with_sc_cond(CondWithElse* cwe,
const std::shared_ptr<IR>& else_ir,
Function& f,
LinkedObjectFile& file) {
auto as_seq = dynamic_cast<IR_Begin*>(else_ir.get());
if (!as_seq || as_seq->forms.size() != 2) {
return nullptr;
}
auto first = dynamic_cast<IR_ShortCircuit*>(as_seq->forms.at(0).get());
auto second = dynamic_cast<IR_Cond*>(as_seq->forms.at(1).get());
if (!first || !second) {
return nullptr;
}
std::vector<IR_Cond::Entry> entries;
for (auto& x : cwe->entries) {
IR_Cond::Entry e;
e.condition = cfg_to_ir(f, file, x.condition);
e.body = cfg_to_ir(f, file, x.body);
entries.push_back(std::move(e));
}
auto first_condition = std::make_shared<IR_Begin>();
first_condition->forms.push_back(as_seq->forms.at(0));
first_condition->forms.push_back(second->entries.front().condition);
second->entries.front().condition = first_condition;
for (auto& x : second->entries) {
entries.push_back(x);
}
std::shared_ptr<IR> result = std::make_shared<IR_Cond>(entries);
clean_up_cond_no_else(&result, file);
return result;
}
/*!
* Main CFG vertex to IR conversion. Will pull basic IR ops from the provided function as needed.
*/
std::shared_ptr<IR> cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx) {
if (dynamic_cast<BlockVtx*>(vtx)) {
auto* bv = dynamic_cast<BlockVtx*>(vtx);
auto& block = f.basic_blocks.at(bv->block_id);
std::vector<std::shared_ptr<IR>> irs;
IR* last = nullptr;
for (int instr = block.start_word; instr < block.end_word; instr++) {
auto got = f.get_basic_op_at_instr(instr);
if (got.get() == last) {
continue;
}
last = got.get();
irs.push_back(got);
}
if (irs.size() == 1) {
return irs.front();
} else {
return std::make_shared<IR_Begin>(irs);
}
} else if (dynamic_cast<SequenceVtx*>(vtx)) {
auto* sv = dynamic_cast<SequenceVtx*>(vtx);
std::vector<std::shared_ptr<IR>> irs;
insert_cfg_into_list(f, file, &irs, sv);
return std::make_shared<IR_Begin>(irs);
} else if (dynamic_cast<WhileLoop*>(vtx)) {
auto wvtx = dynamic_cast<WhileLoop*>(vtx);
auto result = std::make_shared<IR_WhileLoop>(cfg_to_ir(f, file, wvtx->condition),
cfg_to_ir(f, file, wvtx->body));
return result;
} else if (dynamic_cast<CondWithElse*>(vtx)) {
auto* cvtx = dynamic_cast<CondWithElse*>(vtx);
// the cfg analysis pass may recognize some things out of order, which can cause
// fake nesting. This is actually a problem at this point because it can turn a normal
// cond into a cond with else, which emits different instructions. This attempts to recognize
// an else which is actually more cases and compacts it into a single statement. At this point
// I don't know if this is sufficient to catch all cases. it may even recognize the wrong
// thing in some cases... maybe we should check the delay slot instead?
auto else_ir = cfg_to_ir(f, file, cvtx->else_vtx);
auto fancy_compact_result = merge_cond_else_with_sc_cond(cvtx, else_ir, f, file);
if (fancy_compact_result) {
return fancy_compact_result;
}
if (dynamic_cast<IR_Cond*>(else_ir.get())) {
auto extra_cond = dynamic_cast<IR_Cond*>(else_ir.get());
std::vector<IR_Cond::Entry> entries;
for (auto& x : cvtx->entries) {
IR_Cond::Entry e;
e.condition = cfg_to_ir(f, file, x.condition);
e.body = cfg_to_ir(f, file, x.body);
entries.push_back(std::move(e));
}
for (auto& x : extra_cond->entries) {
entries.push_back(x);
}
std::shared_ptr<IR> result = std::make_shared<IR_Cond>(entries);
clean_up_cond_no_else(&result, file);
return result;
} else {
std::vector<IR_CondWithElse::Entry> entries;
for (auto& x : cvtx->entries) {
IR_CondWithElse::Entry e;
e.condition = cfg_to_ir(f, file, x.condition);
e.body = cfg_to_ir(f, file, x.body);
entries.push_back(std::move(e));
}
std::shared_ptr<IR> result = std::make_shared<IR_CondWithElse>(entries, else_ir);
clean_up_cond_with_else(&result, file);
return result;
}
} else if (dynamic_cast<ShortCircuit*>(vtx)) {
auto* svtx = dynamic_cast<ShortCircuit*>(vtx);
// try as a type of expression first
auto as_type_of = try_sc_as_type_of(f, file, svtx);
if (as_type_of) {
return as_type_of;
}
auto as_ash = try_sc_as_ash(f, file, svtx);
if (as_ash) {
return as_ash;
}
auto as_abs = try_sc_as_abs(f, file, svtx);
if (as_abs) {
return as_abs;
}
// now try as a normal and/or
std::vector<IR_ShortCircuit::Entry> entries;
for (auto& x : svtx->entries) {
IR_ShortCircuit::Entry e;
e.condition = cfg_to_ir(f, file, x);
entries.push_back(e);
}
auto result = std::make_shared<IR_ShortCircuit>(entries);
// todo clean these into real and/or.
return result;
} else if (dynamic_cast<CondNoElse*>(vtx)) {
auto* cvtx = dynamic_cast<CondNoElse*>(vtx);
std::vector<IR_Cond::Entry> entries;
for (auto& x : cvtx->entries) {
IR_Cond::Entry e;
e.condition = cfg_to_ir(f, file, x.condition);
e.body = cfg_to_ir(f, file, x.body);
entries.push_back(std::move(e));
}
std::shared_ptr<IR> result = std::make_shared<IR_Cond>(entries);
clean_up_cond_no_else(&result, file);
return result;
}
throw std::runtime_error("not yet implemented IR conversion.");
return nullptr;
}
/*!
* Post processing pass to clean up while loops - annoyingly the block before a while loop
* has a jump to the condition branch that we need to remove. This currently happens after all
* conversion but this may need to be revisited depending on the final order of simplifications.
*/
void clean_up_while_loops(IR_Begin* sequence, LinkedObjectFile& file) {
(void)file;
std::vector<size_t> to_remove; // the list of branches to remove by index in this sequence
for (size_t i = 0; i < sequence->forms.size(); i++) {
auto* form_as_while = dynamic_cast<IR_WhileLoop*>(sequence->forms.at(i).get());
if (form_as_while) {
assert(i != 0);
auto prev_as_branch = dynamic_cast<IR_Branch*>(sequence->forms.at(i - 1).get());
assert(prev_as_branch);
// printf("got while intro branch %s\n", prev_as_branch->print(file).c_str());
// this should be an always jump. We'll assume that the CFG builder successfully checked
// the brach destination, but we will check the condition.
assert(prev_as_branch->condition.kind == Condition::ALWAYS);
assert(prev_as_branch->branch_delay.kind == BranchDelay::NOP);
to_remove.push_back(i - 1);
// now we should try to find the condition branch:
auto condition_branch = get_condition_branch(&form_as_while->condition);
assert(condition_branch.first);
assert(condition_branch.first->branch_delay.kind == BranchDelay::NOP);
// printf("got while condition branch %s\n", condition_branch.first->print(file).c_str());
auto replacement = std::make_shared<IR_Compare>(condition_branch.first->condition);
*(condition_branch.second) = replacement;
}
}
// remove the implied forward always branches.
for (int i = int(to_remove.size()); i-- > 0;) {
auto idx = to_remove.at(i);
assert(dynamic_cast<IR_Branch*>(sequence->forms.at(idx).get()));
sequence->forms.erase(sequence->forms.begin() + idx);
}
}
} // namespace
/*!
* Use a control flow graph to build a single IR representing a function.
* This should be done after basic ops are added and before typing, variable splitting, and
* expression compaction.
*/
std::shared_ptr<IR> build_cfg_ir(Function& function,
ControlFlowGraph& cfg,
LinkedObjectFile& file) {
// printf("build cfg ir\n");
if (!cfg.is_fully_resolved()) {
return nullptr;
}
try {
auto top_level = cfg.get_single_top_level();
// todo, we should apply transformations for fixing up branch instructions for each IR.
// and possibly annotate the IR control flow structure so that we can determine if its and/or
// or whatever. This may require rejecting a huge number of inline assembly functions, and
// possibly resolving the min/max/ash issue.
// auto ir = cfg_to_ir(function, file, top_level);
auto ir = std::make_shared<IR_Begin>();
insert_cfg_into_list(function, file, &ir->forms, top_level);
auto all_children = ir->get_all_ir(file);
all_children.push_back(ir);
for (auto& child : all_children) {
// printf("child is %s\n", child->print(file).c_str());
auto as_begin = dynamic_cast<IR_Begin*>(child.get());
if (as_begin) {
clean_up_while_loops(as_begin, file);
}
}
return ir;
} catch (std::runtime_error& e) {
return nullptr;
}
}
+10
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);
+376 -53
View File
@@ -1,20 +1,62 @@
#include "IR.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
std::string IR::print(const LinkedObjectFile& file) const {
return to_form(file)->toStringPretty();
}
std::shared_ptr<Form> IR_Register::to_form(const LinkedObjectFile& file) const {
std::vector<std::shared_ptr<IR>> IR::get_all_ir(LinkedObjectFile& file) const {
(void)file;
return toForm(reg.to_charp());
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::shared_ptr<Form> IR_Set::to_form(const LinkedObjectFile& file) const {
return buildList(toForm("set!"), dst->to_form(file), src->to_form(file));
std::string IR::print(const LinkedObjectFile& file) const {
return pretty_print::to_string(to_form(file));
}
std::shared_ptr<Form> IR_Store::to_form(const LinkedObjectFile& file) const {
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) {
case FLOAT:
@@ -45,30 +87,38 @@ std::shared_ptr<Form> IR_Store::to_form(const LinkedObjectFile& file) const {
assert(false);
}
return buildList(toForm(store_operator), dst->to_form(file), src->to_form(file));
return pretty_print::build_list(pretty_print::to_symbol(store_operator), dst->to_form(file),
src->to_form(file));
}
std::shared_ptr<Form> IR_Failed::to_form(const LinkedObjectFile& file) const {
goos::Object IR_Symbol::to_form(const LinkedObjectFile& file) const {
(void)file;
return buildList("INVALID-OPERATION");
return pretty_print::to_symbol("'" + name);
}
std::shared_ptr<Form> IR_Symbol::to_form(const LinkedObjectFile& file) const {
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 toForm("'" + name);
return pretty_print::to_symbol(name);
}
std::shared_ptr<Form> IR_SymbolValue::to_form(const LinkedObjectFile& file) const {
(void)file;
return toForm(name);
void IR_SymbolValue::get_children(std::vector<std::shared_ptr<IR>>* output) const {
(void)output;
}
std::shared_ptr<Form> IR_StaticAddress::to_form(const LinkedObjectFile& file) const {
// return buildList(toForm("&"), file.get_label_name(label_id));
return toForm(file.get_label_name(label_id));
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));
}
std::shared_ptr<Form> IR_Load::to_form(const LinkedObjectFile& file) const {
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) {
case FLOAT:
@@ -113,10 +163,14 @@ std::shared_ptr<Form> IR_Load::to_form(const LinkedObjectFile& file) const {
default:
assert(false);
}
return buildList(toForm(load_operator), location->to_form(file));
return pretty_print::build_list(pretty_print::to_symbol(load_operator), location->to_form(file));
}
std::shared_ptr<Form> IR_FloatMath2::to_form(const LinkedObjectFile& file) const {
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) {
case DIV:
@@ -141,10 +195,20 @@ std::shared_ptr<Form> IR_FloatMath2::to_form(const LinkedObjectFile& file) const
assert(false);
}
return buildList(toForm(math_operator), arg0->to_form(file), arg1->to_form(file));
return pretty_print::build_list(pretty_print::to_symbol(math_operator), arg0->to_form(file),
arg1->to_form(file));
}
std::shared_ptr<Form> IR_IntMath2::to_form(const LinkedObjectFile& file) const {
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) {
case ADD:
@@ -195,22 +259,35 @@ std::shared_ptr<Form> IR_IntMath2::to_form(const LinkedObjectFile& file) const {
default:
assert(false);
}
return buildList(toForm(math_operator), arg0->to_form(file), arg1->to_form(file));
return pretty_print::build_list(pretty_print::to_symbol(math_operator), arg0->to_form(file),
arg1->to_form(file));
}
std::shared_ptr<Form> IR_IntMath1::to_form(const LinkedObjectFile& file) const {
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 buildList(toForm(math_operator), arg->to_form(file));
return pretty_print::build_list(pretty_print::to_symbol(math_operator), arg->to_form(file));
}
std::shared_ptr<Form> IR_FloatMath1::to_form(const LinkedObjectFile& file) const {
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) {
case FLOAT_TO_INT:
@@ -231,40 +308,79 @@ std::shared_ptr<Form> IR_FloatMath1::to_form(const LinkedObjectFile& file) const
default:
assert(false);
}
return buildList(toForm(math_operator), arg->to_form(file));
return pretty_print::build_list(pretty_print::to_symbol(math_operator), arg->to_form(file));
}
std::shared_ptr<Form> IR_Call::to_form(const LinkedObjectFile& file) const {
goos::Object IR_Call::to_form(const LinkedObjectFile& file) const {
(void)file;
return buildList("call!");
return pretty_print::build_list("call!");
}
std::shared_ptr<Form> IR_IntegerConstant::to_form(const LinkedObjectFile& file) const {
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 toForm(std::to_string(value));
return pretty_print::to_symbol(std::to_string(value));
}
std::shared_ptr<Form> BranchDelay::to_form(const LinkedObjectFile& file) const {
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) {
case NOP:
return buildList("nop");
return pretty_print::build_list("nop");
case SET_REG_FALSE:
return buildList(toForm("set!"), destination->to_form(file), "'#f");
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
"'#f");
case SET_REG_TRUE:
return buildList(toForm("set!"), destination->to_form(file), "'#t");
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
"'#t");
case SET_REG_REG:
return buildList(toForm("set!"), destination->to_form(file), source->to_form(file));
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 buildList("unknown-branch-delay");
return pretty_print::build_list("unknown-branch-delay");
default:
assert(false);
}
}
std::shared_ptr<Form> IR_Nop::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 buildList("nop!");
return pretty_print::build_list("nop!");
}
int Condition::num_args() const {
@@ -288,6 +404,9 @@ int Condition::num_args() const {
case NONZERO:
case FALSE:
case TRUTHY:
case GREATER_THAN_ZERO_SIGNED:
case GEQ_ZERO_SIGNED:
case LESS_THAN_ZERO:
return 1;
case ALWAYS:
return 0;
@@ -296,7 +415,17 @@ int Condition::num_args() const {
}
}
std::shared_ptr<Form> Condition::to_form(const LinkedObjectFile& file) const {
void Condition::get_children(std::vector<std::shared_ptr<IR>>* output) const {
if (src0) {
output->push_back(src0);
}
if (src1) {
output->push_back(src1);
}
}
goos::Object Condition::to_form(const LinkedObjectFile& file) const {
int nargs = num_args();
std::string condtion_operator;
switch (kind) {
@@ -357,35 +486,229 @@ std::shared_ptr<Form> 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;
default:
assert(false);
}
if (nargs == 2) {
return buildList(toForm(condtion_operator), src0->to_form(file), src1->to_form(file));
return pretty_print::build_list(pretty_print::to_symbol(condtion_operator), src0->to_form(file),
src1->to_form(file));
} else if (nargs == 1) {
if (condtion_operator.empty()) {
return src0->to_form(file);
} else {
return buildList(toForm(condtion_operator), src0->to_form(file));
return pretty_print::build_list(pretty_print::to_symbol(condtion_operator),
src0->to_form(file));
}
} else if (nargs == 0) {
return toForm(condtion_operator);
return pretty_print::to_symbol(condtion_operator);
} else {
assert(false);
}
}
std::shared_ptr<Form> IR_Branch::to_form(const LinkedObjectFile& file) const {
return buildList(toForm(likely ? "bl!" : "b!"), condition.to_form(file),
toForm(file.get_label_name(dest_label_idx)), branch_delay.to_form(file));
goos::Object IR_Branch::to_form(const LinkedObjectFile& file) const {
return pretty_print::build_list(
pretty_print::to_symbol(likely ? "bl!" : "b!"), condition.to_form(file),
pretty_print::to_symbol(file.get_label_name(dest_label_idx)), branch_delay.to_form(file));
}
std::shared_ptr<Form> IR_Compare::to_form(const LinkedObjectFile& file) const {
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);
}
std::shared_ptr<Form> IR_Suspend::to_form(const LinkedObjectFile& file) const {
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 buildList("suspend!");
}
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
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("ash"), 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);
}
+153 -31
View File
@@ -2,15 +2,18 @@
#define JAK_IR_H
#include <cassert>
#include <utility>
#include "decompiler/Disasm/Register.h"
#include "decompiler/util/LispPrint.h"
#include "common/goos/PrettyPrinter.h"
class LinkedObjectFile;
class IR {
public:
virtual std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const = 0;
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;
};
@@ -18,13 +21,15 @@ class IR {
class IR_Failed : public IR {
public:
IR_Failed() = default;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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) {}
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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;
};
@@ -44,7 +49,8 @@ class IR_Set : public IR {
} kind;
IR_Set(Kind _kind, std::shared_ptr<IR> _dst, std::shared_ptr<IR> _src)
: kind(_kind), dst(std::move(_dst)), src(std::move(_src)) {}
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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;
};
@@ -55,39 +61,43 @@ class IR_Store : public IR_Set {
IR_Store(Kind _kind, std::shared_ptr<IR> _dst, std::shared_ptr<IR> _src, int _size)
: IR_Set(IR_Set::LOAD, std::move(_dst), std::move(_src)), kind(_kind), size(_size) {}
int size;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
goos::Object to_form(const LinkedObjectFile& file) const override;
};
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;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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 {
@@ -96,7 +106,8 @@ class IR_FloatMath2 : public IR {
IR_FloatMath2(Kind _kind, std::shared_ptr<IR> _arg0, std::shared_ptr<IR> _arg1)
: kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {}
std::shared_ptr<IR> arg0, arg1;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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 {
@@ -104,7 +115,8 @@ class IR_FloatMath1 : public IR {
enum Kind { FLOAT_TO_INT, INT_TO_FLOAT, ABS, NEG, SQRT } kind;
IR_FloatMath1(Kind _kind, std::shared_ptr<IR> _arg) : kind(_kind), arg(std::move(_arg)) {}
std::shared_ptr<IR> arg;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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 {
@@ -129,35 +141,50 @@ class IR_IntMath2 : public IR {
IR_IntMath2(Kind _kind, std::shared_ptr<IR> _arg0, std::shared_ptr<IR> _arg1)
: kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {}
std::shared_ptr<IR> arg0, arg1;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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 {
public:
int64_t value;
explicit IR_IntegerConstant(int64_t _value) : value(_value) {}
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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) {}
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const;
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,9 @@ struct Condition {
GREATER_THAN_SIGNED,
LEQ_SIGNED,
GEQ_SIGNED,
GREATER_THAN_ZERO_SIGNED,
LESS_THAN_ZERO,
GEQ_ZERO_SIGNED,
LESS_THAN_UNSIGNED,
GREATER_THAN_UNSIGNED,
LEQ_UNSIGNED,
@@ -199,8 +229,9 @@ struct Condition {
}
int num_args() const;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) 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;
};
class IR_Branch : public IR {
@@ -216,7 +247,8 @@ class IR_Branch : public IR {
BranchDelay branch_delay;
bool likely;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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 {
@@ -225,19 +257,109 @@ class IR_Compare : public IR {
Condition condition;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
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::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;
IR_Ash(std::shared_ptr<IR> _shift_amount,
std::shared_ptr<IR> _value,
std::shared_ptr<IR> _clobber)
: shift_amount(std::move(_shift_amount)),
value(std::move(_value)),
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;
};
#endif // JAK_IR_H
+26 -21
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";
}
@@ -714,7 +719,9 @@ std::string LinkedObjectFile::print_scripts() {
if (label_id != -1) {
auto& label = labels.at(label_id);
if ((label.offset & 7) == 2) {
result += to_form_script(seg, word_idx, already_printed)->toStringPretty(0, 100) + "\n";
// result += to_form_script(seg, word_idx, already_printed)->toStringPretty(0, 100) +
// "\n";
result += pretty_print::to_string(to_form_script(seg, word_idx, already_printed)) + "\n";
}
}
}
@@ -736,16 +743,14 @@ bool LinkedObjectFile::is_empty_list(int seg, int byte_idx) {
* Note : this takes the address of the car of the pair. which is perhaps a bit confusing
* (in GOAL, this would be (&-> obj car))
*/
std::shared_ptr<Form> LinkedObjectFile::to_form_script(int seg,
int word_idx,
std::vector<bool>& seen) {
goos::Object LinkedObjectFile::to_form_script(int seg, int word_idx, std::vector<bool>& seen) {
// the object to currently print. to start off, create pair from the car address we've been given.
int goal_print_obj = word_idx * 4 + 2;
// resulting form. we can't have a totally empty list (as an empty list looks like a symbol,
// so it wouldn't be flagged), so it's safe to make this a pair.
auto result = std::make_shared<Form>();
result->kind = FormKind::PAIR;
auto result = goos::PairObject::make_new(goos::EmptyListObject::make_new(),
goos::EmptyListObject::make_new());
// the current pair to fill out.
auto fill = result;
@@ -755,14 +760,14 @@ std::shared_ptr<Form> LinkedObjectFile::to_form_script(int seg,
// check the thing to print is a a pair.
if ((goal_print_obj & 7) == 2) {
// first convert the car (again, with (&-> obj car))
fill->pair[0] = to_form_script_object(seg, goal_print_obj - 2, seen);
fill.as_pair()->car = to_form_script_object(seg, goal_print_obj - 2, seen);
seen.at(goal_print_obj / 4) = true;
auto cdr_addr = goal_print_obj + 2;
if (is_empty_list(seg, cdr_addr)) {
// the list has ended!
fill->pair[1] = gSymbolTable.getEmptyPair();
fill.as_pair()->cdr = goos::EmptyListObject::make_new();
return result;
} else {
// cdr object should be aligned.
@@ -772,12 +777,12 @@ std::shared_ptr<Form> LinkedObjectFile::to_form_script(int seg,
if (cdr_word.kind == LinkedWord::PTR && (labels.at(cdr_word.label_id).offset & 7) == 2) {
// yes, proper list. add another pair and link it in to the list.
goal_print_obj = labels.at(cdr_word.label_id).offset;
fill->pair[1] = std::make_shared<Form>();
fill->pair[1]->kind = FormKind::PAIR;
fill = fill->pair[1];
fill.as_pair()->cdr = goos::PairObject::make_new(goos::EmptyListObject::make_new(),
goos::EmptyListObject::make_new());
fill = fill.as_pair()->cdr;
} else {
// improper list, put the last thing in and end
fill->pair[1] = to_form_script_object(seg, cdr_addr, seen);
fill.as_pair()->cdr = to_form_script_object(seg, cdr_addr, seen);
return result;
}
}
@@ -809,10 +814,10 @@ bool LinkedObjectFile::is_string(int seg, int byte_idx) {
/*!
* Convert a (pointer object) to some nice representation.
*/
std::shared_ptr<Form> LinkedObjectFile::to_form_script_object(int seg,
int byte_idx,
std::vector<bool>& seen) {
std::shared_ptr<Form> result;
goos::Object LinkedObjectFile::to_form_script_object(int seg,
int byte_idx,
std::vector<bool>& seen) {
goos::Object result;
switch (byte_idx & 7) {
case 0:
@@ -820,10 +825,10 @@ std::shared_ptr<Form> LinkedObjectFile::to_form_script_object(int seg,
auto& word = words_by_seg.at(seg).at(byte_idx / 4);
if (word.kind == LinkedWord::SYM_PTR) {
// .symbol xxxx
result = toForm(word.symbol_name);
result = pretty_print::to_symbol(word.symbol_name);
} else if (word.kind == LinkedWord::PLAIN_DATA) {
// .word xxxxx
result = toForm(std::to_string(word.data));
result = pretty_print::to_symbol(std::to_string(word.data));
} else if (word.kind == LinkedWord::PTR) {
// might be a sub-list, or some other random pointer
auto offset = labels.at(word.label_id).offset;
@@ -832,14 +837,14 @@ std::shared_ptr<Form> LinkedObjectFile::to_form_script_object(int seg,
result = to_form_script(seg, offset / 4, seen);
} else {
if (is_string(seg, offset)) {
result = toForm(get_goal_string(seg, offset / 4 - 1));
result = pretty_print::to_symbol(get_goal_string(seg, offset / 4 - 1));
} else {
// some random pointer, just print the label.
result = toForm(labels.at(word.label_id).name);
result = pretty_print::to_symbol(labels.at(word.label_id).name);
}
}
} else if (word.kind == LinkedWord::EMPTY_PTR) {
result = gSymbolTable.getEmptyPair();
result = goos::EmptyListObject::make_new();
} else {
std::string debug;
append_word_to_string(debug, word);
+3 -3
View File
@@ -15,7 +15,7 @@
#include <unordered_set>
#include "LinkedWord.h"
#include "decompiler/Function/Function.h"
#include "decompiler/util/LispPrint.h"
#include "common/goos/PrettyPrinter.h"
/*!
* A label to a location in this object file.
@@ -124,8 +124,8 @@ class LinkedObjectFile {
std::vector<Label> labels;
private:
std::shared_ptr<Form> to_form_script(int seg, int word_idx, std::vector<bool>& seen);
std::shared_ptr<Form> to_form_script_object(int seg, int byte_idx, std::vector<bool>& seen);
goos::Object to_form_script(int seg, int word_idx, std::vector<bool>& seen);
goos::Object to_form_script_object(int seg, int byte_idx, std::vector<bool>& seen);
bool is_empty_list(int seg, int byte_idx);
bool is_string(int seg, int byte_idx);
std::string get_goal_string(int seg, int word_idx);
+17 -1
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.
@@ -597,11 +598,15 @@ void ObjectFileDB::analyze_functions() {
int total_basic_ops = 0;
int total_failed_basic_ops = 0;
int attempted_cfg_irs = 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,6 +623,12 @@ 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);
attempted_cfg_irs++;
if (func.ir) {
successful_cfg_irs++;
}
if (func.cfg->is_fully_resolved()) {
resolved_cfg_functions++;
}
@@ -640,6 +651,10 @@ void ObjectFileDB::analyze_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,
@@ -652,10 +667,11 @@ void ObjectFileDB::analyze_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));
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, attempted_cfg_irs,
100.f * float(successful_cfg_irs) / float(attempted_cfg_irs));
// for (auto& kv : unresolved_by_length) {
// printf("LEN %d\n", kv.first);
+28 -2
View File
@@ -23,19 +23,45 @@
"write_hexdump_on_v3_only":true,
// to write out "scripts", which are currently just all the linked lists found
"write_scripts":false,
"write_scripts":true,
// Experimental Stuff
"find_basic_blocks":true,
"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",
// 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",
// 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 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)",
"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",
-527
View File
@@ -1,527 +0,0 @@
#include "LispPrint.h"
#include <cassert>
#include <iostream>
#include <vector>
//////// HACK - symbol table now looks up by string, which makes it really stupid and store
// all strings twice.
// should probably just remove it
/*!
* String interning
*/
std::string* SymbolTable::intern(const std::string& str) {
if (map.find(str) == map.end()) {
auto* new_string = new std::string(str);
map[str] = new_string;
return new_string;
} else {
return map[str];
}
}
/*!
* Global interned string table
*/
SymbolTable gSymbolTable;
SymbolTable::SymbolTable() {
empty_pair = std::make_shared<Form>();
empty_pair->kind = FormKind::EMPTY_LIST;
}
SymbolTable::~SymbolTable() {
for (const auto& kv : map)
delete kv.second;
}
/*!
* Convert a form to a one-line string.
*/
std::string Form::toStringSimple() {
std::string result;
buildStringSimple(result);
return result;
}
void Form::buildStringSimple(std::string& str) {
std::vector<FormToken> tokens;
toTokenList(tokens);
for (auto& token : tokens) {
switch (token.kind) {
case TokenKind::WHITESPACE:
str.push_back(' ');
break;
case TokenKind::SYMBOL:
str.append(*token.str);
break;
case TokenKind::OPEN_PAREN:
str.push_back('(');
break;
case TokenKind::DOT:
str.push_back('.');
break;
case TokenKind::CLOSE_PAREN:
str.push_back(')');
break;
case TokenKind::EMPTY_PAIR:
str.append("()");
break;
case TokenKind::SPECIAL_SYMBOL:
str.append(*token.str);
break;
default:
throw std::runtime_error("buildStringSimple unknown token kind");
}
}
}
void Form::toTokenList(std::vector<FormToken>& tokens) {
switch (kind) {
case FormKind::SYMBOL:
tokens.emplace_back(TokenKind::SYMBOL, symbol);
break;
case FormKind::PAIR: {
tokens.emplace_back(TokenKind::OPEN_PAREN);
Form* toPrint = this;
for (;;) {
if (toPrint->kind == FormKind::PAIR) {
toPrint->pair[0]->toTokenList(tokens); // print CAR
toPrint = toPrint->pair[1].get();
if (toPrint->kind == FormKind::EMPTY_LIST) {
tokens.emplace_back(TokenKind::CLOSE_PAREN);
return;
} else {
tokens.emplace_back(TokenKind::WHITESPACE);
}
} else { // not a proper list!
tokens.emplace_back(TokenKind::DOT);
tokens.emplace_back(TokenKind::WHITESPACE);
toPrint->toTokenList(tokens);
tokens.emplace_back(TokenKind::CLOSE_PAREN);
return;
}
}
} break;
case FormKind::EMPTY_LIST:
tokens.emplace_back(TokenKind::EMPTY_PAIR);
break;
default:
throw std::runtime_error("unhandled form type in buildSimpleString");
break;
}
}
///////////////////
// Pretty Printer
///////////////////
/*!
* Linked list node representing a token in the output (whitespace, paren, newline, etc)
*/
struct PrettyPrinterNode {
FormToken* tok = nullptr; // if we aren't a newline, we will have a token.
int line = -1; // line that token occurs on. undef for newlines
int lineIndent = -1; // indent of line. only valid for first token in the line
int offset = -1; // offset of beginning of token from left margin
int specialIndentDelta = 0;
bool is_line_separator = false; // true if line separator (not a token)
PrettyPrinterNode *next = nullptr, *prev = nullptr; // linked list
PrettyPrinterNode* paren =
nullptr; // pointer to open paren if in parens. open paren points to close and vice versa
explicit PrettyPrinterNode(FormToken& _tok) { tok = &_tok; }
PrettyPrinterNode() = default;
};
/*!
* Splice in a line break after the given node, it there isn't one already and if it isn't the last
* node.
*/
static void insertNewlineAfter(PrettyPrinterNode* node, int specialIndentDelta) {
if (node->next && !node->next->is_line_separator) {
auto* nl = new PrettyPrinterNode;
auto* next = node->next;
node->next = nl;
nl->prev = node;
nl->next = next;
next->prev = nl;
nl->is_line_separator = true;
nl->specialIndentDelta = specialIndentDelta;
}
}
/*!
* Splice in a line break before the given node, if there isn't one already and if it isn't the
* first node.
*/
static void insertNewlineBefore(PrettyPrinterNode* node, int specialIndentDelta) {
if (node->prev && !node->prev->is_line_separator) {
auto* nl = new PrettyPrinterNode;
auto* prev = node->prev;
prev->next = nl;
nl->prev = prev;
nl->next = node;
node->prev = nl;
nl->is_line_separator = true;
nl->specialIndentDelta = specialIndentDelta;
}
}
/*!
* Break a list across multiple lines. This is the fundamental reducing operation of this algorithm
*/
static void breakList(PrettyPrinterNode* leftParen) {
assert(!leftParen->is_line_separator);
assert(leftParen->tok->kind == TokenKind::OPEN_PAREN);
auto* rp = leftParen->paren;
assert(rp->tok->kind == TokenKind::CLOSE_PAREN);
for (auto* n = leftParen->next; n && n != rp; n = n->next) {
if (!n->is_line_separator) {
if (n->tok->kind == TokenKind::OPEN_PAREN) {
n = n->paren;
assert(n->tok->kind == TokenKind::CLOSE_PAREN);
insertNewlineAfter(n, 0);
} else if (n->tok->kind != TokenKind::WHITESPACE) {
assert(n->tok->kind != TokenKind::CLOSE_PAREN);
insertNewlineAfter(n, 0);
}
}
}
}
/*!
* Compute proper line numbers, offsets, and indents for a list of tokens with newlines
* Will add newlines for close parens if needed.
*/
static PrettyPrinterNode* propagatePretty(PrettyPrinterNode* list, int line_length) {
// propagate line numbers
PrettyPrinterNode* rv = nullptr;
int line = list->line;
for (auto* n = list; n; n = n->next) {
if (n->is_line_separator) {
line++;
} else {
n->line = line;
// add the weird newline.
if (n->tok->kind == TokenKind::CLOSE_PAREN) {
if (n->line != n->paren->line) {
if (n->prev && !n->prev->is_line_separator) {
insertNewlineBefore(n, 0);
line++;
}
if (n->next && !n->next->is_line_separator) {
insertNewlineAfter(n, 0);
}
}
}
}
}
// compute offsets and indents
std::vector<int> indentStack;
indentStack.push_back(0);
int offset = 0;
PrettyPrinterNode* line_start = list;
bool previous_line_sep = false;
for (auto* n = list; n; n = n->next) {
if (n->is_line_separator) {
previous_line_sep = true;
offset = indentStack.back() += n->specialIndentDelta;
} else {
if (previous_line_sep) {
line_start = n;
n->lineIndent = offset;
previous_line_sep = false;
}
n->offset = offset;
offset += n->tok->toString().length();
if (offset > line_length && !rv)
rv = line_start;
if (n->tok->kind == TokenKind::OPEN_PAREN) {
if (!n->prev || n->prev->is_line_separator) {
indentStack.push_back(offset + 1);
} else {
indentStack.push_back(offset - 1);
}
}
if (n->tok->kind == TokenKind::CLOSE_PAREN) {
indentStack.pop_back();
}
}
}
return rv;
}
/*!
* Get the token on the start of the next line. nullptr if we're the last line.
*/
static PrettyPrinterNode* getNextLine(PrettyPrinterNode* start) {
assert(!start->is_line_separator);
int line = start->line;
for (;;) {
if (start->is_line_separator || start->line == line) {
if (start->next)
start = start->next;
else
return nullptr;
} else {
break;
}
}
return start;
}
/*!
* Get the next open paren on the current line (can start in the middle of line, not inclusive of
* start) nullptr if there's no open parens on the rest of this line.
*/
static PrettyPrinterNode* getNextListOnLine(PrettyPrinterNode* start) {
int line = start->line;
assert(!start->is_line_separator);
if (!start->next || start->next->is_line_separator)
return nullptr;
start = start->next;
while (!start->is_line_separator && start->line == line) {
if (start->tok->kind == TokenKind::OPEN_PAREN)
return start;
if (!start->next)
return nullptr;
start = start->next;
}
return nullptr;
}
/*!
* Get the first open paren on the current line (can start in the middle of line, inclusive of
* start) nullptr if there's no open parens on the rest of this line
*/
static PrettyPrinterNode* getFirstListOnLine(PrettyPrinterNode* start) {
int line = start->line;
assert(!start->is_line_separator);
while (!start->is_line_separator && start->line == line) {
if (start->tok->kind == TokenKind::OPEN_PAREN)
return start;
if (!start->next)
return nullptr;
start = start->next;
}
return nullptr;
}
/*!
* Get the first token on the first line which exceeds the max length
*/
static PrettyPrinterNode* getFirstBadLine(PrettyPrinterNode* start, int line_length) {
assert(!start->is_line_separator);
int currentLine = start->line;
auto* currentLineNode = start;
for (;;) {
if (start->is_line_separator) {
assert(start->next);
start = start->next;
} else {
if (start->line != currentLine) {
currentLine = start->line;
currentLineNode = start;
}
if (start->offset > line_length) {
return currentLineNode;
}
if (!start->next) {
return nullptr;
}
start = start->next;
}
}
}
/*!
* Break insertion algorithm.
*/
static void insertBreaksAsNeeded(PrettyPrinterNode* head, int line_length) {
PrettyPrinterNode* last_line_complete = nullptr;
PrettyPrinterNode* line_to_start_line_search = head;
// loop over lines
for (;;) {
// compute lines as needed
propagatePretty(head, line_length);
// search for a bad line starting at the last line we fixed
PrettyPrinterNode* candidate_line = getFirstBadLine(line_to_start_line_search, line_length);
// if we got the same line we started on, this means we couldn't fix it.
if (candidate_line == last_line_complete) {
candidate_line = nullptr; // so we say our candidate was bad and try to find another
PrettyPrinterNode* next_line = getNextLine(line_to_start_line_search);
if (next_line) {
candidate_line = getFirstBadLine(next_line, line_length);
}
}
if (!candidate_line)
break;
// okay, we have a line which needs fixing.
assert(!candidate_line->prev || candidate_line->prev->is_line_separator);
PrettyPrinterNode* form_to_start = getFirstListOnLine(candidate_line);
for (;;) {
if (!form_to_start) {
printf("pretty printer has failed. Fix the bug or increase the the line length.\n");
assert(false);
}
breakList(form_to_start);
propagatePretty(head, line_length);
if (getFirstBadLine(candidate_line, line_length) != candidate_line) {
break;
}
form_to_start = getNextListOnLine(form_to_start);
if (!form_to_start)
break;
}
last_line_complete = candidate_line;
line_to_start_line_search = candidate_line;
}
}
static void insertSpecialBreaks(PrettyPrinterNode* node) {
for (; node; node = node->next) {
if (!node->is_line_separator && node->tok->kind == TokenKind::SYMBOL) {
std::string& name = *node->tok->str;
if (name == "deftype") {
auto* parent_type_dec = getNextListOnLine(node);
if (parent_type_dec) {
insertNewlineAfter(parent_type_dec->paren, 0);
}
}
}
}
}
std::string Form::toStringPretty(int indent, int line_length) {
(void)indent;
(void)line_length;
std::vector<FormToken> tokens;
toTokenList(tokens);
assert(!tokens.empty());
std::string pretty;
// build linked list of nodes
PrettyPrinterNode* head = new PrettyPrinterNode(tokens[0]);
PrettyPrinterNode* node = head;
head->line = 0;
head->offset = 0;
head->lineIndent = 0;
int offset = head->tok->toString().length();
for (size_t i = 1; i < tokens.size(); i++) {
node->next = new PrettyPrinterNode(tokens[i]);
node->next->prev = node;
node = node->next;
node->line = 0;
node->offset = offset;
offset += node->tok->toString().length();
node->lineIndent = 0;
}
// attach parens.
std::vector<PrettyPrinterNode*> parenStack;
parenStack.push_back(nullptr);
for (PrettyPrinterNode* n = head; n; n = n->next) {
if (n->tok->kind == TokenKind::OPEN_PAREN) {
parenStack.push_back(n);
} else if (n->tok->kind == TokenKind::CLOSE_PAREN) {
n->paren = parenStack.back();
parenStack.back()->paren = n;
parenStack.pop_back();
} else {
n->paren = parenStack.back();
}
}
assert(parenStack.size() == 1);
assert(!parenStack.back());
insertSpecialBreaks(head);
propagatePretty(head, line_length);
insertBreaksAsNeeded(head, line_length);
// write to string
bool newline_prev = true;
for (PrettyPrinterNode* n = head; n; n = n->next) {
if (n->is_line_separator) {
pretty.push_back('\n');
newline_prev = true;
} else {
if (newline_prev) {
pretty.append(n->lineIndent, ' ');
newline_prev = false;
if (n->tok->kind == TokenKind::WHITESPACE)
continue;
}
pretty.append(n->tok->toString());
}
}
for (;;) {
if (!head)
break;
auto* next = head->next;
delete head;
head = next;
}
return pretty;
}
std::shared_ptr<Form> toForm(const std::string& str) {
auto f = std::make_shared<Form>();
f->kind = FormKind::SYMBOL;
f->symbol = gSymbolTable.intern(str);
return f;
}
std::shared_ptr<Form> buildList(std::shared_ptr<Form> form) {
auto f = std::make_shared<Form>();
f->kind = FormKind::PAIR;
f->pair[0] = form;
f->pair[1] = gSymbolTable.getEmptyPair();
return f;
}
std::shared_ptr<Form> buildList(const std::string& str) {
return buildList(toForm(str));
}
std::shared_ptr<Form> buildList(std::shared_ptr<Form>* forms, int count) {
auto f = std::make_shared<Form>();
f->kind = FormKind::PAIR;
f->pair[0] = forms[0];
if (count - 1) {
f->pair[1] = buildList(forms + 1, count - 1);
} else {
f->pair[1] = gSymbolTable.getEmptyPair();
}
return f;
}
std::shared_ptr<Form> buildList(std::vector<std::shared_ptr<Form>>& forms) {
if (forms.empty()) {
return gSymbolTable.getEmptyPair();
}
return buildList(forms.data(), forms.size());
}
std::shared_ptr<Form> buildList(std::vector<std::string>& forms) {
if (forms.empty()) {
return gSymbolTable.getEmptyPair();
}
std::vector<std::shared_ptr<Form>> f;
for (auto& x : forms) {
f.push_back(toForm(x));
}
return buildList(f.data(), f.size());
}
-145
View File
@@ -1,145 +0,0 @@
#pragma once
#ifndef JAK2_DISASSEMBLER_LISPPRINT_H
#define JAK2_DISASSEMBLER_LISPPRINT_H
#include <memory>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
/*!
* What type of thing is it?
*/
enum class FormKind {
SYMBOL,
HEX_NUMBER,
DECIMAL_NUMBER,
BINARY_NUMBER,
SIGNED_NUMBER,
STRING,
EMPTY_LIST,
PAIR
};
/*!
* Tokens in a textual representation
*/
enum class TokenKind {
WHITESPACE,
SYMBOL,
OPEN_PAREN,
DOT,
CLOSE_PAREN,
EMPTY_PAIR,
SPECIAL_SYMBOL
};
/*!
* Token in a text representation
*/
struct FormToken {
explicit FormToken(TokenKind _kind, std::string* _str = nullptr) : kind(_kind), str(_str) {}
TokenKind kind;
union {
std::string* str;
};
std::string toString() {
std::string s;
switch (kind) {
case TokenKind::WHITESPACE:
s.push_back(' ');
break;
case TokenKind::SYMBOL:
s.append(*str);
break;
case TokenKind::OPEN_PAREN:
s.push_back('(');
break;
case TokenKind::DOT:
s.push_back('.');
break;
case TokenKind::CLOSE_PAREN:
s.push_back(')');
break;
case TokenKind::EMPTY_PAIR:
s.append("()");
break;
case TokenKind::SPECIAL_SYMBOL:
s.append(*str);
break;
default:
throw std::runtime_error("toString unknown token kind");
}
return s;
}
};
/*!
* S-Expression Form
*/
class Form {
public:
FormKind kind;
std::string* symbol;
std::shared_ptr<Form> pair[2];
std::string toStringSimple();
std::string toStringPretty(int indent = 0, int line_length = 80);
void toTokenList(std::vector<FormToken>& tokens);
private:
void buildStringSimple(std::string& str);
};
/*!
* Symbol table to reduce the number of strings everywhere.
*/
class SymbolTable {
public:
SymbolTable();
std::string* intern(const std::string& str);
~SymbolTable();
std::shared_ptr<Form> getEmptyPair() { return empty_pair; }
private:
std::unordered_map<std::string, std::string*> map;
std::shared_ptr<Form> empty_pair;
};
/*!
* Global symbol table used for the compiler/decompiler
*/
extern SymbolTable gSymbolTable;
std::shared_ptr<Form> toForm(const std::string& str); //
std::shared_ptr<Form> buildList(const std::string& str);
std::shared_ptr<Form> buildList(std::shared_ptr<Form> form);
std::shared_ptr<Form> buildList(std::vector<std::shared_ptr<Form>>& forms);
std::shared_ptr<Form> buildList(std::shared_ptr<Form>* forms, int count);
std::shared_ptr<Form> buildList(std::vector<std::string>& forms);
template <typename... Args>
std::shared_ptr<Form> buildList(const std::string& str, Args... rest) {
auto f = std::make_shared<Form>();
f->kind = FormKind::PAIR;
f->pair[0] = toForm(str);
f->pair[1] = buildList(rest...);
return f;
}
template <typename... Args>
std::shared_ptr<Form> buildList(std::shared_ptr<Form> car, Args... rest) {
auto f = std::make_shared<Form>();
f->kind = FormKind::PAIR;
f->pair[0] = car;
f->pair[1] = buildList(rest...);
return f;
}
#endif // JAK2_DISASSEMBLER_LISPPRINT_H
+5 -5
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.
@@ -81,12 +81,12 @@ add_executable(gk main.cpp)
IF (WIN32)
# set stuff for windows
target_link_libraries(runtime mman cross_sockets common_util)
target_link_libraries(gk cross_sockets mman common_util runtime)
target_link_libraries(runtime mman cross_sockets common_util spdlog)
target_link_libraries(gk cross_sockets mman common_util runtime spdlog)
ELSE()
# set stuff for other systems
target_link_libraries(runtime pthread cross_sockets common_util)
target_link_libraries(gk cross_sockets pthread common_util runtime)
target_link_libraries(runtime pthread cross_sockets common_util spdlog)
target_link_libraries(gk cross_sockets pthread common_util runtime spdlog)
ENDIF()
+2 -2
View File
@@ -60,8 +60,8 @@ void find_basic_blocks(RegAllocCache* cache, const AllocationInput& in) {
}
}
if (!found) {
printf("[RegAlloc Error] couldn't find basic block beginning with instr %d of %lld\n", instr,
in.instructions.size());
printf("[RegAlloc Error] couldn't find basic block beginning with instr %d of %d\n", instr,
int(in.instructions.size()));
}
assert(found);
return result;
+1
View File
@@ -15,6 +15,7 @@ add_executable(goalc-test
test_common_util.cpp
test_compiler_and_runtime.cpp
test_deftype.cpp
test_pretty_print.cpp
)
enable_testing()
+62
View File
@@ -0,0 +1,62 @@
#include "gtest/gtest.h"
#include "common/goos/Reader.h"
#include "common/util/FileUtil.h"
#include "common/goos/PrettyPrinter.h"
using namespace goos;
namespace {
Object read(const std::string& str) {
auto body = pretty_print::get_pretty_printer_reader().read_from_string(str).as_pair()->cdr;
EXPECT_TRUE(body.as_pair()->cdr.is_empty_list());
return body.as_pair()->car;
}
std::string pprint(const Object& o, int len = 80) {
return pretty_print::to_string(o, len);
}
// read then pretty print a string.
std::string ppr(const std::string& in, int len = 80) {
return pprint(read(in), len);
}
} // namespace
TEST(PrettyPrinter, Basics) {
EXPECT_EQ(ppr("test"), "test");
EXPECT_EQ(ppr("(l 12 asdf)"), "(l 12 asdf)");
// force it to break
EXPECT_EQ(ppr("(thing 12 asd asfd sdfjk)", 10), "(thing\n 12\n asd\n asfd\n sdfjk\n )");
}
TEST(PrettyPrinter, ReadAgain) {
// first read the gcommon file
auto gcommon_code = pretty_print::get_pretty_printer_reader().read_from_file(
{"goal_src", "kernel", "gcommon.gc"});
// pretty print it
auto printed_gcommon = pretty_print::to_string(gcommon_code);
auto gcommon_code2 = pretty_print::get_pretty_printer_reader()
.read_from_string(printed_gcommon)
.as_pair()
->cdr.as_pair()
->car;
auto printed_gcommon2 = pretty_print::to_string(gcommon_code);
EXPECT_TRUE(gcommon_code == gcommon_code2);
}
TEST(PrettyPrinter, ReadAgainVeryShortLines) {
// first read the gcommon file
auto gcommon_code = pretty_print::get_pretty_printer_reader().read_from_file(
{"goal_src", "kernel", "gcommon.gc"});
// pretty print it but with a very short line length. This looks terrible but will hopefully
// hit many of the cases for line breaking.
auto printed_gcommon = pretty_print::to_string(gcommon_code, 5);
auto gcommon_code2 = pretty_print::get_pretty_printer_reader()
.read_from_string(printed_gcommon)
.as_pair()
->cdr.as_pair()
->car;
auto printed_gcommon2 = pretty_print::to_string(gcommon_code);
EXPECT_TRUE(gcommon_code == gcommon_code2);
}