mirror of
https://github.com/open-goal/jak-project
synced 2026-07-30 07:55:01 -04:00
Merge remote-tracking branch 'upstream/w/cfg_2_ir' into logging
This commit is contained in:
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user