mirror of
https://github.com/open-goal/jak-project
synced 2026-07-09 23:01:56 -04:00
Merge remote-tracking branch 'water111/master' into generative-tests
This commit is contained in:
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: Build Project
|
||||
run: |
|
||||
cd build
|
||||
make -j
|
||||
make -j4
|
||||
- name: Run Tests
|
||||
timeout-minutes: 5
|
||||
run: ./test_code_coverage.sh
|
||||
|
||||
+4
-1
@@ -1,3 +1,6 @@
|
||||
[submodule "third-party/googletest"]
|
||||
path = third-party/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
url = https://github.com/google/googletest.git
|
||||
[submodule "third-party/spdlog"]
|
||||
path = third-party/spdlog
|
||||
url = https://github.com/gabime/spdlog.git
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Top Level CMakeLists.txt
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(jak)
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Debug")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
@@ -49,6 +52,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)
|
||||
|
||||
@@ -85,6 +96,9 @@ add_subdirectory(third-party/minilzo)
|
||||
# build format library
|
||||
add_subdirectory(third-party/fmt)
|
||||
|
||||
# build spdlog library
|
||||
add_subdirectory(third-party/spdlog)
|
||||
|
||||
# windows memory management lib
|
||||
IF (WIN32)
|
||||
add_subdirectory(third-party/mman)
|
||||
|
||||
@@ -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)
|
||||
@@ -637,4 +637,4 @@ Object Interpreter::eval_error(const Object& form,
|
||||
throw_eval_error(form, "Error: " + args.unnamed.at(0).as_string()->data);
|
||||
return EmptyListObject::make_new();
|
||||
}
|
||||
} // namespace goos
|
||||
} // namespace goos
|
||||
|
||||
@@ -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
|
||||
@@ -78,24 +78,6 @@ int64_t get_int(const goos::Object& obj) {
|
||||
throw std::runtime_error(obj.print() + " was supposed to be an integer, but isn't");
|
||||
}
|
||||
|
||||
TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src) {
|
||||
if (src.is_symbol()) {
|
||||
return type_system->make_typespec(symbol_string(src));
|
||||
} else if (src.is_pair()) {
|
||||
TypeSpec ts = type_system->make_typespec(symbol_string(car(&src)));
|
||||
const auto& rest = *cdr(&src);
|
||||
|
||||
for_each_in_list(rest,
|
||||
[&](const goos::Object& o) { ts.add_arg(parse_typespec(type_system, o)); });
|
||||
|
||||
return ts;
|
||||
} else {
|
||||
throw std::runtime_error("invalid typespec: " + src.print());
|
||||
}
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
void add_field(StructureType* structure, TypeSystem* ts, const goos::Object& def) {
|
||||
auto rest = &def;
|
||||
|
||||
@@ -278,6 +260,24 @@ TypeFlags parse_structure_def(StructureType* type,
|
||||
|
||||
} // namespace
|
||||
|
||||
TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src) {
|
||||
if (src.is_symbol()) {
|
||||
return type_system->make_typespec(symbol_string(src));
|
||||
} else if (src.is_pair()) {
|
||||
TypeSpec ts = type_system->make_typespec(symbol_string(car(&src)));
|
||||
const auto& rest = *cdr(&src);
|
||||
|
||||
for_each_in_list(rest,
|
||||
[&](const goos::Object& o) { ts.add_arg(parse_typespec(type_system, o)); });
|
||||
|
||||
return ts;
|
||||
} else {
|
||||
throw std::runtime_error("invalid typespec: " + src.print());
|
||||
}
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) {
|
||||
auto iter = &deftype;
|
||||
|
||||
|
||||
@@ -22,3 +22,4 @@ struct DeftypeResult {
|
||||
};
|
||||
|
||||
DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts);
|
||||
TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src);
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# Directory of this script
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
$DIR/build/decompiler/decompiler $DIR/decompiler/config/jak1_ntsc_black_label.jsonc $DIR/iso_data $DIR/decompiler_out
|
||||
$DIR/build/decompiler/decompiler $DIR/decompiler/config/jak1_ntsc_black_label.jsonc $DIR/iso_data $DIR/decompiler_out
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
add_executable(decompiler
|
||||
util/LispPrint.cpp
|
||||
main.cpp
|
||||
ObjectFile/ObjectFileDB.cpp
|
||||
Disasm/Instruction.cpp
|
||||
@@ -11,15 +10,17 @@ add_executable(decompiler
|
||||
Function/Function.cpp
|
||||
util/FileIO.cpp
|
||||
config.cpp
|
||||
util/LispPrint.cpp
|
||||
util/DecompilerTypeSystem.cpp
|
||||
Function/BasicBlocks.cpp
|
||||
Disasm/InstructionMatching.cpp
|
||||
TypeSystem/GoalType.cpp
|
||||
TypeSystem/GoalFunction.cpp
|
||||
TypeSystem/GoalSymbol.cpp
|
||||
TypeSystem/TypeInfo.cpp
|
||||
TypeSystem/TypeSpec.cpp Function/CfgVtx.cpp Function/CfgVtx.h)
|
||||
Function/CfgVtx.cpp Function/CfgVtx.h
|
||||
IR/BasicOpBuilder.cpp
|
||||
IR/CfgBuilder.cpp
|
||||
IR/IR.cpp)
|
||||
|
||||
target_link_libraries(decompiler
|
||||
goos
|
||||
minilzo
|
||||
common_util)
|
||||
common_util
|
||||
type_system
|
||||
fmt)
|
||||
@@ -44,6 +44,12 @@ struct InstructionAtom {
|
||||
std::string to_string(const LinkedObjectFile& file) const;
|
||||
|
||||
bool is_link_or_label() const;
|
||||
bool is_reg() const { return kind == REGISTER; }
|
||||
bool is_imm() const { return kind == IMM; }
|
||||
bool is_label() const { return kind == LABEL; }
|
||||
bool is_sym() const { return kind == IMM_SYM; }
|
||||
|
||||
bool is_reg(Register r) const { return kind == REGISTER && reg == r; }
|
||||
|
||||
private:
|
||||
int32_t imm;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
/*!
|
||||
* @file InstructionMatching.h
|
||||
* Utilities for checking if an instruction matches some criteria.
|
||||
*/
|
||||
|
||||
#ifndef JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H
|
||||
#define JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* @file OpcodeInfo.cpp
|
||||
* Decoding info for each opcode.
|
||||
*/
|
||||
|
||||
#include "OpcodeInfo.h"
|
||||
#include <cassert>
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef JAK_DISASSEMBLER_BASICBLOCKS_H
|
||||
#define JAK_DISASSEMBLER_BASICBLOCKS_H
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
@@ -21,5 +18,3 @@ struct BasicBlock {
|
||||
std::vector<BasicBlock> find_blocks_in_function(const LinkedObjectFile& file,
|
||||
int seg,
|
||||
const Function& func);
|
||||
|
||||
#endif // JAK_DISASSEMBLER_BASICBLOCKS_H
|
||||
|
||||
@@ -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() {
|
||||
@@ -1637,7 +1636,6 @@ void ControlFlowGraph::flag_early_exit(const std::vector<BasicBlock>& blocks) {
|
||||
* Build and resolve a Control Flow Graph as much as possible.
|
||||
*/
|
||||
std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int seg, Function& func) {
|
||||
printf("build cfg : %s\n", func.guessed_name.to_string().c_str());
|
||||
auto cfg = std::make_shared<ControlFlowGraph>();
|
||||
|
||||
const auto& blocks = cfg->create_blocks(func.basic_blocks.size());
|
||||
@@ -1716,13 +1714,6 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
|
||||
|
||||
cfg->flag_early_exit(func.basic_blocks);
|
||||
|
||||
// if(func.guessed_name.to_string() == "(method 9 thread)")
|
||||
// cfg->find_cond_w_else();
|
||||
|
||||
// if (func.guessed_name.to_string() != "looping-code") {
|
||||
// return cfg;
|
||||
// }
|
||||
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
@@ -1730,11 +1721,10 @@ 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_n_else();
|
||||
changed = changed || cfg->find_cond_w_else();
|
||||
|
||||
changed = changed || cfg->find_while_loop_top_level();
|
||||
// //// printf("while loops? %d\n", changed);
|
||||
//// changed = changed || cfg->find_if_else_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();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "Function.h"
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
#include "decompiler/TypeSystem/TypeInfo.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
|
||||
namespace {
|
||||
std::vector<Register> gpr_backups = {make_gpr(Reg::GP), make_gpr(Reg::S5), make_gpr(Reg::S4),
|
||||
@@ -418,7 +418,7 @@ void Function::check_epilogue(const LinkedObjectFile& file) {
|
||||
*
|
||||
* Updates the guessed_name of the function and updates type_info
|
||||
*/
|
||||
void Function::find_global_function_defs(LinkedObjectFile& file) {
|
||||
void Function::find_global_function_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts) {
|
||||
int state = 0;
|
||||
int label_id = -1;
|
||||
Register reg;
|
||||
@@ -457,7 +457,8 @@ void Function::find_global_function_defs(LinkedObjectFile& file) {
|
||||
auto& func = file.get_function_at_label(label_id);
|
||||
assert(func.guessed_name.empty());
|
||||
func.guessed_name.set_as_global(name);
|
||||
get_type_info().inform_symbol(name, TypeSpec("function"));
|
||||
dts.add_symbol(name, "function");
|
||||
;
|
||||
// todo - inform function.
|
||||
}
|
||||
|
||||
@@ -549,4 +550,42 @@ void Function::find_method_defs(LinkedObjectFile& file) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Function::add_basic_op(std::shared_ptr<IR> op, int start_instr, int end_instr) {
|
||||
op->is_basic_op = true;
|
||||
assert(end_instr > start_instr);
|
||||
|
||||
for (int i = start_instr; i < end_instr; i++) {
|
||||
instruction_to_basic_op[i] = basic_ops.size();
|
||||
}
|
||||
basic_op_to_instruction[basic_ops.size()] = start_instr;
|
||||
basic_ops.push_back(op);
|
||||
}
|
||||
|
||||
bool Function::instr_starts_basic_op(int idx) {
|
||||
auto op = instruction_to_basic_op.find(idx);
|
||||
if (op != instruction_to_basic_op.end()) {
|
||||
auto start_instr = basic_op_to_instruction.at(op->second);
|
||||
return start_instr == idx;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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() {
|
||||
return basic_ops.size();
|
||||
}
|
||||
|
||||
int Function::get_failed_basic_op_count() {
|
||||
int count = 0;
|
||||
for (auto& x : basic_ops) {
|
||||
if (dynamic_cast<IR_Failed*>(x.get())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
#include "decompiler/Disasm/Instruction.h"
|
||||
#include "BasicBlocks.h"
|
||||
#include "CfgVtx.h"
|
||||
#include "decompiler/IR/IR.h"
|
||||
|
||||
class DecompilerTypeSystem;
|
||||
|
||||
struct FunctionName {
|
||||
enum class FunctionKind {
|
||||
@@ -60,8 +63,16 @@ class Function {
|
||||
public:
|
||||
Function(int _start_word, int _end_word);
|
||||
void analyze_prologue(const LinkedObjectFile& file);
|
||||
void find_global_function_defs(LinkedObjectFile& file);
|
||||
void find_global_function_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts);
|
||||
void find_method_defs(LinkedObjectFile& file);
|
||||
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);
|
||||
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;
|
||||
@@ -115,6 +126,9 @@ class Function {
|
||||
|
||||
private:
|
||||
void check_epilogue(const LinkedObjectFile& file);
|
||||
std::vector<std::shared_ptr<IR>> basic_ops;
|
||||
std::unordered_map<int, int> instruction_to_basic_op;
|
||||
std::unordered_map<int, int> basic_op_to_instruction;
|
||||
};
|
||||
|
||||
#endif // NEXT_FUNCTION_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
/*!
|
||||
* @file BasicOpBuilder.h
|
||||
* Analyzes a basic block and converts instructions to BasicOps.
|
||||
* These will be used later to convert the Cfg into the nested IR format.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class Function;
|
||||
struct BasicBlock;
|
||||
class LinkedObjectFile;
|
||||
|
||||
void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
@@ -0,0 +1,803 @@
|
||||
#include "IR.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
|
||||
std::vector<std::shared_ptr<IR>> IR::get_all_ir(LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
std::vector<std::shared_ptr<IR>> result;
|
||||
get_children(&result);
|
||||
size_t last_checked = 0;
|
||||
size_t last_last_checked = -1;
|
||||
|
||||
while (last_checked != last_last_checked) {
|
||||
last_last_checked = last_checked;
|
||||
auto end_of_check = result.size();
|
||||
for (size_t i = last_checked; i < end_of_check; i++) {
|
||||
auto it = result.at(i).get();
|
||||
assert(it);
|
||||
it->get_children(&result);
|
||||
}
|
||||
last_checked = end_of_check;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string IR::print(const LinkedObjectFile& file) const {
|
||||
return pretty_print::to_string(to_form(file));
|
||||
}
|
||||
|
||||
goos::Object IR_Failed::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
return pretty_print::build_list("INVALID-OPERATION");
|
||||
}
|
||||
|
||||
void IR_Failed::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
goos::Object IR_Register::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
return pretty_print::to_symbol(reg.to_charp());
|
||||
}
|
||||
|
||||
void IR_Register::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
goos::Object IR_Set::to_form(const LinkedObjectFile& file) const {
|
||||
return pretty_print::build_list(pretty_print::to_symbol("set!"), dst->to_form(file),
|
||||
src->to_form(file));
|
||||
}
|
||||
|
||||
void IR_Set::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
// note that we are not returning clobber here because it shouldn't contain anything that
|
||||
// the IR simplification code should touch.
|
||||
output->push_back(dst);
|
||||
output->push_back(src);
|
||||
}
|
||||
|
||||
goos::Object IR_Store::to_form(const LinkedObjectFile& file) const {
|
||||
std::string store_operator;
|
||||
switch (kind) {
|
||||
case FLOAT:
|
||||
store_operator = "s.f";
|
||||
break;
|
||||
case INTEGER:
|
||||
switch (size) {
|
||||
case 1:
|
||||
store_operator = "s.b";
|
||||
break;
|
||||
case 2:
|
||||
store_operator = "s.h";
|
||||
break;
|
||||
case 4:
|
||||
store_operator = "s.w";
|
||||
break;
|
||||
case 8:
|
||||
store_operator = "s.d";
|
||||
break;
|
||||
case 16:
|
||||
store_operator = "s.q";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return pretty_print::build_list(pretty_print::to_symbol(store_operator), dst->to_form(file),
|
||||
src->to_form(file));
|
||||
}
|
||||
|
||||
goos::Object IR_Symbol::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
return pretty_print::to_symbol("'" + name);
|
||||
}
|
||||
|
||||
void IR_Symbol::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
goos::Object IR_SymbolValue::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
return pretty_print::to_symbol(name);
|
||||
}
|
||||
|
||||
void IR_SymbolValue::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
goos::Object IR_StaticAddress::to_form(const LinkedObjectFile& file) const {
|
||||
// return pretty_print::build_list(pretty_print::to_symbol("&"), file.get_label_name(label_id));
|
||||
return pretty_print::to_symbol(file.get_label_name(label_id));
|
||||
}
|
||||
|
||||
void IR_StaticAddress::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
goos::Object IR_Load::to_form(const LinkedObjectFile& file) const {
|
||||
std::string load_operator;
|
||||
switch (kind) {
|
||||
case FLOAT:
|
||||
load_operator = "l.f";
|
||||
break;
|
||||
case UNSIGNED:
|
||||
switch (size) {
|
||||
case 1:
|
||||
load_operator = "l.bu";
|
||||
break;
|
||||
case 2:
|
||||
load_operator = "l.hu";
|
||||
break;
|
||||
case 4:
|
||||
load_operator = "l.wu";
|
||||
break;
|
||||
case 8:
|
||||
load_operator = "l.d";
|
||||
break;
|
||||
case 16:
|
||||
load_operator = "l.q";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
break;
|
||||
case SIGNED:
|
||||
switch (size) {
|
||||
case 1:
|
||||
load_operator = "l.bs";
|
||||
break;
|
||||
case 2:
|
||||
load_operator = "l.hs";
|
||||
break;
|
||||
case 4:
|
||||
load_operator = "l.ws";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
return pretty_print::build_list(pretty_print::to_symbol(load_operator), location->to_form(file));
|
||||
}
|
||||
|
||||
void IR_Load::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(location);
|
||||
}
|
||||
|
||||
goos::Object IR_FloatMath2::to_form(const LinkedObjectFile& file) const {
|
||||
std::string math_operator;
|
||||
switch (kind) {
|
||||
case DIV:
|
||||
math_operator = "/.f";
|
||||
break;
|
||||
case MUL:
|
||||
math_operator = "*.f";
|
||||
break;
|
||||
case ADD:
|
||||
math_operator = "+.f";
|
||||
break;
|
||||
case SUB:
|
||||
math_operator = "-.f";
|
||||
break;
|
||||
case MIN:
|
||||
math_operator = "min.f";
|
||||
break;
|
||||
case MAX:
|
||||
math_operator = "max.f";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return pretty_print::build_list(pretty_print::to_symbol(math_operator), arg0->to_form(file),
|
||||
arg1->to_form(file));
|
||||
}
|
||||
|
||||
void IR_FloatMath2::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(arg0);
|
||||
output->push_back(arg1);
|
||||
}
|
||||
|
||||
void IR_FloatMath1::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(arg);
|
||||
}
|
||||
|
||||
goos::Object IR_IntMath2::to_form(const LinkedObjectFile& file) const {
|
||||
std::string math_operator;
|
||||
switch (kind) {
|
||||
case ADD:
|
||||
math_operator = "+.i";
|
||||
break;
|
||||
case SUB:
|
||||
math_operator = "-.i";
|
||||
break;
|
||||
case MUL_SIGNED:
|
||||
math_operator = "*.si";
|
||||
break;
|
||||
case MUL_UNSIGNED:
|
||||
math_operator = "*.ui";
|
||||
break;
|
||||
case DIV_SIGNED:
|
||||
math_operator = "/.si";
|
||||
break;
|
||||
case MOD_SIGNED:
|
||||
math_operator = "mod.si";
|
||||
break;
|
||||
case DIV_UNSIGNED:
|
||||
math_operator = "/.ui";
|
||||
break;
|
||||
case MOD_UNSIGNED:
|
||||
math_operator = "mod.ui";
|
||||
break;
|
||||
case OR:
|
||||
math_operator = "logior";
|
||||
break;
|
||||
case AND:
|
||||
math_operator = "logand";
|
||||
break;
|
||||
case NOR:
|
||||
math_operator = "lognor";
|
||||
break;
|
||||
case XOR:
|
||||
math_operator = "logxor";
|
||||
break;
|
||||
case LEFT_SHIFT:
|
||||
math_operator = "shl";
|
||||
break;
|
||||
case RIGHT_SHIFT_ARITH:
|
||||
math_operator = "sar";
|
||||
break;
|
||||
case RIGHT_SHIFT_LOGIC:
|
||||
math_operator = "shr";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
return pretty_print::build_list(pretty_print::to_symbol(math_operator), arg0->to_form(file),
|
||||
arg1->to_form(file));
|
||||
}
|
||||
|
||||
void IR_IntMath2::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(arg0);
|
||||
output->push_back(arg1);
|
||||
}
|
||||
|
||||
goos::Object IR_IntMath1::to_form(const LinkedObjectFile& file) const {
|
||||
std::string math_operator;
|
||||
switch (kind) {
|
||||
case NOT:
|
||||
math_operator = "lognot";
|
||||
break;
|
||||
case ABS:
|
||||
math_operator = "abs.si";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
return pretty_print::build_list(pretty_print::to_symbol(math_operator), arg->to_form(file));
|
||||
}
|
||||
|
||||
void IR_IntMath1::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(arg);
|
||||
}
|
||||
|
||||
goos::Object IR_FloatMath1::to_form(const LinkedObjectFile& file) const {
|
||||
std::string math_operator;
|
||||
switch (kind) {
|
||||
case FLOAT_TO_INT:
|
||||
math_operator = "int<-float";
|
||||
break;
|
||||
case INT_TO_FLOAT:
|
||||
math_operator = "float<-int";
|
||||
break;
|
||||
case ABS:
|
||||
math_operator = "abs.f";
|
||||
break;
|
||||
case NEG:
|
||||
math_operator = "neg.f";
|
||||
break;
|
||||
case SQRT:
|
||||
math_operator = "sqrt.f";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
return pretty_print::build_list(pretty_print::to_symbol(math_operator), arg->to_form(file));
|
||||
}
|
||||
|
||||
goos::Object IR_Call::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
return pretty_print::build_list("call!");
|
||||
}
|
||||
|
||||
void IR_Call::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
goos::Object IR_IntegerConstant::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
return pretty_print::to_symbol(std::to_string(value));
|
||||
}
|
||||
|
||||
void IR_IntegerConstant::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
goos::Object BranchDelay::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
switch (kind) {
|
||||
case NOP:
|
||||
return pretty_print::build_list("nop");
|
||||
case SET_REG_FALSE:
|
||||
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
|
||||
"'#f");
|
||||
case SET_REG_TRUE:
|
||||
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
|
||||
"'#t");
|
||||
case SET_REG_REG:
|
||||
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
|
||||
source->to_form(file));
|
||||
case SET_BINTEGER:
|
||||
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
|
||||
"binteger");
|
||||
case SET_PAIR:
|
||||
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
|
||||
"pair");
|
||||
case DSLLV:
|
||||
return pretty_print::build_list(
|
||||
pretty_print::to_symbol("set!"), destination->to_form(file),
|
||||
pretty_print::build_list(pretty_print::to_symbol("shl"), source->to_form(file),
|
||||
source2->to_form(file)));
|
||||
case NEGATE:
|
||||
return pretty_print::build_list(pretty_print::to_symbol("set!"), destination->to_form(file),
|
||||
pretty_print::build_list("-", source->to_form(file)));
|
||||
case UNKNOWN:
|
||||
return pretty_print::build_list("unknown-branch-delay");
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void BranchDelay::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
if (destination) {
|
||||
output->push_back(destination);
|
||||
}
|
||||
|
||||
if (source) {
|
||||
output->push_back(source);
|
||||
}
|
||||
|
||||
if (source2) {
|
||||
output->push_back(source2);
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object IR_Nop::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
return pretty_print::build_list("nop!");
|
||||
}
|
||||
|
||||
int Condition::num_args() const {
|
||||
switch (kind) {
|
||||
case NOT_EQUAL:
|
||||
case EQUAL:
|
||||
case LESS_THAN_SIGNED:
|
||||
case LESS_THAN_UNSIGNED:
|
||||
case GREATER_THAN_SIGNED:
|
||||
case GREATER_THAN_UNSIGNED:
|
||||
case LEQ_SIGNED:
|
||||
case GEQ_SIGNED:
|
||||
case LEQ_UNSIGNED:
|
||||
case GEQ_UNSIGNED:
|
||||
case FLOAT_EQUAL:
|
||||
case FLOAT_NOT_EQUAL:
|
||||
case FLOAT_LESS_THAN:
|
||||
case FLOAT_GEQ:
|
||||
return 2;
|
||||
case ZERO:
|
||||
case NONZERO:
|
||||
case FALSE:
|
||||
case TRUTHY:
|
||||
case GREATER_THAN_ZERO_SIGNED:
|
||||
case GEQ_ZERO_SIGNED:
|
||||
case LESS_THAN_ZERO:
|
||||
case LEQ_ZERO_SIGNED:
|
||||
return 1;
|
||||
case ALWAYS:
|
||||
case NEVER:
|
||||
return 0;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Condition::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
if (src0) {
|
||||
output->push_back(src0);
|
||||
}
|
||||
|
||||
if (src1) {
|
||||
output->push_back(src1);
|
||||
}
|
||||
}
|
||||
|
||||
void Condition::invert() {
|
||||
switch (kind) {
|
||||
case NOT_EQUAL:
|
||||
kind = EQUAL;
|
||||
break;
|
||||
case EQUAL:
|
||||
kind = NOT_EQUAL;
|
||||
break;
|
||||
case LESS_THAN_SIGNED:
|
||||
kind = GEQ_SIGNED;
|
||||
break;
|
||||
case GREATER_THAN_SIGNED:
|
||||
kind = LEQ_SIGNED;
|
||||
break;
|
||||
case LEQ_SIGNED:
|
||||
kind = GREATER_THAN_SIGNED;
|
||||
break;
|
||||
case GEQ_SIGNED:
|
||||
kind = LESS_THAN_SIGNED;
|
||||
break;
|
||||
case GREATER_THAN_ZERO_SIGNED:
|
||||
kind = LEQ_ZERO_SIGNED;
|
||||
break;
|
||||
case LEQ_ZERO_SIGNED:
|
||||
kind = GREATER_THAN_ZERO_SIGNED;
|
||||
break;
|
||||
case LESS_THAN_ZERO:
|
||||
kind = GEQ_ZERO_SIGNED;
|
||||
break;
|
||||
case GEQ_ZERO_SIGNED:
|
||||
kind = LESS_THAN_ZERO;
|
||||
break;
|
||||
case LESS_THAN_UNSIGNED:
|
||||
kind = GEQ_UNSIGNED;
|
||||
break;
|
||||
case GREATER_THAN_UNSIGNED:
|
||||
kind = LEQ_UNSIGNED;
|
||||
break;
|
||||
case LEQ_UNSIGNED:
|
||||
kind = GREATER_THAN_UNSIGNED;
|
||||
break;
|
||||
case GEQ_UNSIGNED:
|
||||
kind = LESS_THAN_UNSIGNED;
|
||||
break;
|
||||
case ZERO:
|
||||
kind = NONZERO;
|
||||
break;
|
||||
case NONZERO:
|
||||
kind = ZERO;
|
||||
break;
|
||||
case FALSE:
|
||||
kind = TRUTHY;
|
||||
break;
|
||||
case TRUTHY:
|
||||
kind = FALSE;
|
||||
break;
|
||||
case ALWAYS:
|
||||
kind = NEVER;
|
||||
break;
|
||||
case NEVER:
|
||||
kind = ALWAYS;
|
||||
break;
|
||||
case FLOAT_EQUAL:
|
||||
kind = FLOAT_NOT_EQUAL;
|
||||
break;
|
||||
case FLOAT_NOT_EQUAL:
|
||||
kind = FLOAT_EQUAL;
|
||||
break;
|
||||
case FLOAT_LESS_THAN:
|
||||
kind = FLOAT_GEQ;
|
||||
break;
|
||||
case FLOAT_GEQ:
|
||||
kind = FLOAT_LESS_THAN;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object Condition::to_form(const LinkedObjectFile& file) const {
|
||||
int nargs = num_args();
|
||||
std::string condtion_operator;
|
||||
switch (kind) {
|
||||
case NOT_EQUAL:
|
||||
condtion_operator = "!=";
|
||||
break;
|
||||
case EQUAL:
|
||||
condtion_operator = "=";
|
||||
break;
|
||||
case LESS_THAN_SIGNED:
|
||||
condtion_operator = "<.si";
|
||||
break;
|
||||
case LESS_THAN_UNSIGNED:
|
||||
condtion_operator = "<.ui";
|
||||
break;
|
||||
case GREATER_THAN_SIGNED:
|
||||
condtion_operator = ">.si";
|
||||
break;
|
||||
case GREATER_THAN_UNSIGNED:
|
||||
condtion_operator = ">.ui";
|
||||
break;
|
||||
case LEQ_SIGNED:
|
||||
condtion_operator = "<=.si";
|
||||
break;
|
||||
case GEQ_SIGNED:
|
||||
condtion_operator = ">=.si";
|
||||
break;
|
||||
case LEQ_UNSIGNED:
|
||||
condtion_operator = "<=.ui";
|
||||
break;
|
||||
case GEQ_UNSIGNED:
|
||||
condtion_operator = ">=.ui";
|
||||
break;
|
||||
case ZERO:
|
||||
condtion_operator = "zero?";
|
||||
break;
|
||||
case NONZERO:
|
||||
condtion_operator = "nonzero?";
|
||||
break;
|
||||
case FALSE:
|
||||
condtion_operator = "not";
|
||||
break;
|
||||
case TRUTHY:
|
||||
condtion_operator = "";
|
||||
break;
|
||||
case ALWAYS:
|
||||
condtion_operator = "'#t";
|
||||
break;
|
||||
case NEVER:
|
||||
condtion_operator = "'#f";
|
||||
break;
|
||||
case FLOAT_EQUAL:
|
||||
condtion_operator = "=.f";
|
||||
break;
|
||||
case FLOAT_NOT_EQUAL:
|
||||
condtion_operator = "!=.f";
|
||||
break;
|
||||
case FLOAT_LESS_THAN:
|
||||
condtion_operator = "<.f";
|
||||
break;
|
||||
case FLOAT_GEQ:
|
||||
condtion_operator = ">=.f";
|
||||
break;
|
||||
case GREATER_THAN_ZERO_SIGNED:
|
||||
condtion_operator = ">0.si";
|
||||
break;
|
||||
case GEQ_ZERO_SIGNED:
|
||||
condtion_operator = ">=0.si";
|
||||
break;
|
||||
case LESS_THAN_ZERO:
|
||||
condtion_operator = "<0.si";
|
||||
break;
|
||||
case LEQ_ZERO_SIGNED:
|
||||
condtion_operator = "<=0.si";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (nargs == 2) {
|
||||
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 pretty_print::build_list(pretty_print::to_symbol(condtion_operator),
|
||||
src0->to_form(file));
|
||||
}
|
||||
} else if (nargs == 0) {
|
||||
return pretty_print::to_symbol(condtion_operator);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void IR_Branch::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
condition.get_children(output);
|
||||
branch_delay.get_children(output);
|
||||
}
|
||||
|
||||
goos::Object IR_Compare::to_form(const LinkedObjectFile& file) const {
|
||||
return condition.to_form(file);
|
||||
}
|
||||
|
||||
void IR_Compare::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
condition.get_children(output);
|
||||
}
|
||||
|
||||
goos::Object IR_Suspend::to_form(const LinkedObjectFile& file) const {
|
||||
(void)file;
|
||||
return pretty_print::build_list("suspend!");
|
||||
}
|
||||
|
||||
void IR_Nop::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
void IR_Suspend::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
(void)output;
|
||||
}
|
||||
|
||||
goos::Object IR_Begin::to_form(const LinkedObjectFile& file) const {
|
||||
std::vector<goos::Object> list;
|
||||
list.push_back(pretty_print::to_symbol("begin"));
|
||||
for (auto& x : forms) {
|
||||
list.push_back(x->to_form(file));
|
||||
}
|
||||
return pretty_print::build_list(list);
|
||||
}
|
||||
|
||||
void IR_Begin::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
for (auto& x : forms) {
|
||||
output->push_back(x);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void print_inlining_begin(std::vector<goos::Object>* output, IR* ir, const LinkedObjectFile& file) {
|
||||
auto as_begin = dynamic_cast<IR_Begin*>(ir);
|
||||
if (as_begin) {
|
||||
for (auto& x : as_begin->forms) {
|
||||
output->push_back(x->to_form(file));
|
||||
}
|
||||
} else {
|
||||
output->push_back(ir->to_form(file));
|
||||
}
|
||||
}
|
||||
|
||||
bool is_single_expression(IR* in) {
|
||||
return !dynamic_cast<IR_Begin*>(in);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
goos::Object IR_WhileLoop::to_form(const LinkedObjectFile& file) const {
|
||||
std::vector<goos::Object> list;
|
||||
list.push_back(pretty_print::to_symbol("while"));
|
||||
list.push_back(condition->to_form(file));
|
||||
print_inlining_begin(&list, body.get(), file);
|
||||
return pretty_print::build_list(list);
|
||||
}
|
||||
|
||||
void IR_WhileLoop::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(condition);
|
||||
output->push_back(body);
|
||||
}
|
||||
|
||||
goos::Object IR_CondWithElse::to_form(const LinkedObjectFile& file) const {
|
||||
// for now we only turn it into an if statement if both cases won't require a begin at the top
|
||||
// level. I think it is more common to write these as a two-case cond instead of an if with begin.
|
||||
if (entries.size() == 1 && is_single_expression(entries.front().body.get()) &&
|
||||
is_single_expression(else_ir.get())) {
|
||||
std::vector<goos::Object> list;
|
||||
list.push_back(pretty_print::to_symbol("if"));
|
||||
list.push_back(entries.front().condition->to_form(file));
|
||||
list.push_back(entries.front().body->to_form(file));
|
||||
list.push_back(else_ir->to_form(file));
|
||||
return pretty_print::build_list(list);
|
||||
} else {
|
||||
std::vector<goos::Object> list;
|
||||
list.push_back(pretty_print::to_symbol("cond"));
|
||||
for (auto& e : entries) {
|
||||
std::vector<goos::Object> entry;
|
||||
entry.push_back(e.condition->to_form(file));
|
||||
print_inlining_begin(&entry, e.body.get(), file);
|
||||
list.push_back(pretty_print::build_list(entry));
|
||||
}
|
||||
std::vector<goos::Object> else_form;
|
||||
else_form.push_back(pretty_print::to_symbol("else"));
|
||||
print_inlining_begin(&else_form, else_ir.get(), file);
|
||||
list.push_back(pretty_print::build_list(else_form));
|
||||
return pretty_print::build_list(list);
|
||||
}
|
||||
}
|
||||
|
||||
void IR_CondWithElse::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
for (auto& e : entries) {
|
||||
output->push_back(e.condition);
|
||||
output->push_back(e.body);
|
||||
}
|
||||
output->push_back(else_ir);
|
||||
}
|
||||
|
||||
goos::Object IR_GetRuntimeType::to_form(const LinkedObjectFile& file) const {
|
||||
std::vector<goos::Object> list = {pretty_print::to_symbol("type-of"), object->to_form(file)};
|
||||
return pretty_print::build_list(list);
|
||||
}
|
||||
|
||||
void IR_GetRuntimeType::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(object);
|
||||
}
|
||||
|
||||
goos::Object IR_Cond::to_form(const LinkedObjectFile& file) const {
|
||||
if (entries.size() == 1 && is_single_expression(entries.front().body.get())) {
|
||||
// print as an if statement if we can put the body in a single form.
|
||||
std::vector<goos::Object> list;
|
||||
list.push_back(pretty_print::to_symbol("if"));
|
||||
list.push_back(entries.front().condition->to_form(file));
|
||||
list.push_back(entries.front().body->to_form(file));
|
||||
return pretty_print::build_list(list);
|
||||
} else if (entries.size() == 1) {
|
||||
// turn into a when if the body requires multiple forms
|
||||
// todo check to see if the condition starts with a NOT and this can be simplified to an
|
||||
// unless.
|
||||
std::vector<goos::Object> list;
|
||||
list.push_back(pretty_print::to_symbol("when"));
|
||||
list.push_back(entries.front().condition->to_form(file));
|
||||
print_inlining_begin(&list, entries.front().body.get(), file);
|
||||
return pretty_print::build_list(list);
|
||||
} else {
|
||||
std::vector<goos::Object> list;
|
||||
list.push_back(pretty_print::to_symbol("cond"));
|
||||
for (auto& e : entries) {
|
||||
std::vector<goos::Object> entry;
|
||||
entry.push_back(e.condition->to_form(file));
|
||||
print_inlining_begin(&entry, e.body.get(), file);
|
||||
list.push_back(pretty_print::build_list(entry));
|
||||
}
|
||||
return pretty_print::build_list(list);
|
||||
}
|
||||
}
|
||||
|
||||
void IR_Cond::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
for (auto& e : entries) {
|
||||
output->push_back(e.condition);
|
||||
output->push_back(e.body);
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object IR_ShortCircuit::to_form(const LinkedObjectFile& file) const {
|
||||
std::vector<goos::Object> forms;
|
||||
switch (kind) {
|
||||
case UNKNOWN:
|
||||
forms.push_back(pretty_print::to_symbol("unknown-sc"));
|
||||
break;
|
||||
case AND:
|
||||
forms.push_back(pretty_print::to_symbol("and"));
|
||||
break;
|
||||
case OR:
|
||||
forms.push_back(pretty_print::to_symbol("or"));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
for (auto& x : entries) {
|
||||
forms.push_back(x.condition->to_form(file));
|
||||
}
|
||||
return pretty_print::build_list(forms);
|
||||
}
|
||||
|
||||
void IR_ShortCircuit::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
for (auto& x : entries) {
|
||||
output->push_back(x.condition);
|
||||
if (x.output) {
|
||||
output->push_back(x.output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object IR_Ash::to_form(const LinkedObjectFile& file) const {
|
||||
return pretty_print::build_list(pretty_print::to_symbol(is_signed ? "ash.si" : "ash.ui"),
|
||||
value->to_form(file), shift_amount->to_form(file));
|
||||
}
|
||||
|
||||
void IR_Ash::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(value);
|
||||
output->push_back(shift_amount);
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
#ifndef JAK_IR_H
|
||||
#define JAK_IR_H
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "common/goos/PrettyPrinter.h"
|
||||
|
||||
class LinkedObjectFile;
|
||||
|
||||
class IR {
|
||||
public:
|
||||
virtual goos::Object to_form(const LinkedObjectFile& file) const = 0;
|
||||
std::vector<std::shared_ptr<IR>> get_all_ir(LinkedObjectFile& file) const;
|
||||
std::string print(const LinkedObjectFile& file) const;
|
||||
virtual void get_children(std::vector<std::shared_ptr<IR>>* output) const = 0;
|
||||
|
||||
bool is_basic_op = false;
|
||||
};
|
||||
|
||||
class IR_Failed : public IR {
|
||||
public:
|
||||
IR_Failed() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Register : public IR {
|
||||
public:
|
||||
IR_Register(Register _reg, int _instr_idx) : reg(_reg), instr_idx(_instr_idx) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
Register reg;
|
||||
int instr_idx = -1;
|
||||
};
|
||||
|
||||
class IR_Set : public IR {
|
||||
public:
|
||||
enum Kind {
|
||||
REG_64,
|
||||
LOAD,
|
||||
STORE,
|
||||
SYM_LOAD,
|
||||
SYM_STORE,
|
||||
FPR_TO_GPR64,
|
||||
GPR_TO_FPR,
|
||||
REG_FLT,
|
||||
REG_I128
|
||||
} 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)) {}
|
||||
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;
|
||||
};
|
||||
|
||||
class IR_Store : public IR_Set {
|
||||
public:
|
||||
enum Kind { INTEGER, FLOAT } kind;
|
||||
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;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
};
|
||||
|
||||
class IR_Symbol : public IR {
|
||||
public:
|
||||
explicit IR_Symbol(std::string _name) : name(std::move(_name)) {}
|
||||
std::string name;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_SymbolValue : public IR {
|
||||
public:
|
||||
explicit IR_SymbolValue(std::string _name) : name(std::move(_name)) {}
|
||||
std::string name;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_StaticAddress : public IR {
|
||||
public:
|
||||
explicit IR_StaticAddress(int _label_id) : label_id(_label_id) {}
|
||||
int label_id = -1;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Load : public IR {
|
||||
public:
|
||||
enum Kind { UNSIGNED, SIGNED, FLOAT } kind;
|
||||
|
||||
IR_Load(Kind _kind, int _size, std::shared_ptr<IR> _location)
|
||||
: kind(_kind), size(_size), location(std::move(_location)) {}
|
||||
int size;
|
||||
std::shared_ptr<IR> location;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_FloatMath2 : public IR {
|
||||
public:
|
||||
enum Kind { DIV, MUL, ADD, SUB, MIN, MAX } kind;
|
||||
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;
|
||||
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 {
|
||||
public:
|
||||
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;
|
||||
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 {
|
||||
public:
|
||||
enum Kind {
|
||||
ADD,
|
||||
SUB,
|
||||
MUL_SIGNED,
|
||||
DIV_SIGNED,
|
||||
MOD_SIGNED,
|
||||
DIV_UNSIGNED,
|
||||
MOD_UNSIGNED,
|
||||
OR,
|
||||
AND,
|
||||
NOR,
|
||||
XOR,
|
||||
LEFT_SHIFT,
|
||||
RIGHT_SHIFT_ARITH,
|
||||
RIGHT_SHIFT_LOGIC,
|
||||
MUL_UNSIGNED
|
||||
} kind;
|
||||
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;
|
||||
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, ABS } kind;
|
||||
IR_IntMath1(Kind _kind, std::shared_ptr<IR> _arg) : kind(_kind), arg(std::move(_arg)) {}
|
||||
std::shared_ptr<IR> arg;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Call : public IR {
|
||||
public:
|
||||
IR_Call() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_IntegerConstant : public IR {
|
||||
public:
|
||||
int64_t value;
|
||||
explicit IR_IntegerConstant(int64_t _value) : value(_value) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
struct BranchDelay {
|
||||
enum Kind {
|
||||
NOP,
|
||||
SET_REG_FALSE,
|
||||
SET_REG_TRUE,
|
||||
SET_REG_REG,
|
||||
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 {
|
||||
enum Kind {
|
||||
NOT_EQUAL,
|
||||
EQUAL,
|
||||
LESS_THAN_SIGNED,
|
||||
GREATER_THAN_SIGNED,
|
||||
LEQ_SIGNED,
|
||||
GEQ_SIGNED,
|
||||
GREATER_THAN_ZERO_SIGNED,
|
||||
LEQ_ZERO_SIGNED,
|
||||
LESS_THAN_ZERO,
|
||||
GEQ_ZERO_SIGNED,
|
||||
LESS_THAN_UNSIGNED,
|
||||
GREATER_THAN_UNSIGNED,
|
||||
LEQ_UNSIGNED,
|
||||
GEQ_UNSIGNED,
|
||||
ZERO,
|
||||
NONZERO,
|
||||
FALSE,
|
||||
TRUTHY,
|
||||
ALWAYS,
|
||||
NEVER,
|
||||
FLOAT_EQUAL,
|
||||
FLOAT_NOT_EQUAL,
|
||||
FLOAT_LESS_THAN,
|
||||
FLOAT_GEQ
|
||||
} kind;
|
||||
|
||||
Condition(Kind _kind,
|
||||
std::shared_ptr<IR> _src0,
|
||||
std::shared_ptr<IR> _src1,
|
||||
std::shared_ptr<IR> _clobber)
|
||||
: kind(_kind), src0(std::move(_src0)), src1(std::move(_src1)), clobber(std::move(_clobber)) {
|
||||
int nargs = num_args();
|
||||
if (nargs == 2) {
|
||||
assert(src0 && src1);
|
||||
} else if (nargs == 1) {
|
||||
assert(src0 && !src1);
|
||||
} else if (nargs == 0) {
|
||||
assert(!src0 && !src1);
|
||||
}
|
||||
}
|
||||
|
||||
int num_args() const;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const;
|
||||
std::shared_ptr<IR> src0, src1, clobber;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const;
|
||||
void invert();
|
||||
};
|
||||
|
||||
class IR_Branch : public IR {
|
||||
public:
|
||||
IR_Branch(Condition _condition, int _dest_label_idx, BranchDelay _branch_delay, bool _likely)
|
||||
: condition(std::move(_condition)),
|
||||
dest_label_idx(_dest_label_idx),
|
||||
branch_delay(std::move(_branch_delay)),
|
||||
likely(_likely) {}
|
||||
|
||||
Condition condition;
|
||||
int dest_label_idx;
|
||||
BranchDelay branch_delay;
|
||||
bool likely;
|
||||
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Compare : public IR {
|
||||
public:
|
||||
explicit IR_Compare(Condition _condition) : condition(std::move(_condition)) {}
|
||||
|
||||
Condition condition;
|
||||
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Nop : public IR {
|
||||
public:
|
||||
IR_Nop() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Suspend : public IR {
|
||||
public:
|
||||
IR_Suspend() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Begin : public IR {
|
||||
public:
|
||||
IR_Begin() = default;
|
||||
explicit IR_Begin(const std::vector<std::shared_ptr<IR>>& _forms) : forms(std::move(_forms)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
std::vector<std::shared_ptr<IR>> forms;
|
||||
};
|
||||
|
||||
class IR_WhileLoop : public IR {
|
||||
public:
|
||||
IR_WhileLoop(std::shared_ptr<IR> _condition, std::shared_ptr<IR> _body)
|
||||
: condition(std::move(_condition)), body(std::move(_body)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
std::shared_ptr<IR> condition, body;
|
||||
};
|
||||
|
||||
class IR_CondWithElse : public IR {
|
||||
public:
|
||||
struct Entry {
|
||||
std::shared_ptr<IR> condition = nullptr;
|
||||
std::shared_ptr<IR> body = nullptr;
|
||||
bool cleaned = false;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
std::shared_ptr<IR> else_ir;
|
||||
IR_CondWithElse(std::vector<Entry> _entries, std::shared_ptr<IR> _else_ir)
|
||||
: entries(std::move(_entries)), else_ir(std::move(_else_ir)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
// this one doesn't have an else statement. Will return false if none of the cases are taken.
|
||||
class IR_Cond : public IR {
|
||||
public:
|
||||
struct Entry {
|
||||
std::shared_ptr<IR> condition = nullptr;
|
||||
std::shared_ptr<IR> body = nullptr;
|
||||
std::shared_ptr<IR> false_destination = nullptr;
|
||||
bool cleaned = false;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
explicit IR_Cond(std::vector<Entry> _entries) : entries(std::move(_entries)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
// this will work on pairs, bintegers, or basics
|
||||
class IR_GetRuntimeType : public IR {
|
||||
public:
|
||||
std::shared_ptr<IR> object, clobber;
|
||||
IR_GetRuntimeType(std::shared_ptr<IR> _object, std::shared_ptr<IR> _clobber)
|
||||
: object(std::move(_object)), clobber(std::move(_clobber)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_ShortCircuit : public IR {
|
||||
public:
|
||||
struct Entry {
|
||||
std::shared_ptr<IR> condition = nullptr;
|
||||
std::shared_ptr<IR> output = nullptr; // where the delay slot writes to.
|
||||
bool cleaned = false;
|
||||
};
|
||||
|
||||
enum Kind { UNKNOWN, AND, OR } kind = UNKNOWN;
|
||||
|
||||
std::shared_ptr<IR> final_result = nullptr; // the register that the final result goes in.
|
||||
|
||||
std::vector<Entry> entries;
|
||||
explicit IR_ShortCircuit(std::vector<Entry> _entries) : entries(std::move(_entries)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Ash : public IR {
|
||||
public:
|
||||
std::shared_ptr<IR> shift_amount, value, clobber;
|
||||
bool is_signed = true;
|
||||
IR_Ash(std::shared_ptr<IR> _shift_amount,
|
||||
std::shared_ptr<IR> _value,
|
||||
std::shared_ptr<IR> _clobber,
|
||||
bool _is_signed)
|
||||
: shift_amount(std::move(_shift_amount)),
|
||||
value(std::move(_value)),
|
||||
clobber(std::move(_clobber)),
|
||||
is_signed(_is_signed) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
#endif // JAK_IR_H
|
||||
@@ -483,6 +483,8 @@ void LinkedObjectFile::process_fp_relative_links() {
|
||||
if (pprev_instr && pprev_instr->kind == InstructionKind::LUI) {
|
||||
assert(pprev_instr->get_dst(0).get_reg() == offset_reg);
|
||||
additional_offset = (1 << 16) * pprev_instr->get_imm_src().get_imm();
|
||||
pprev_instr->get_imm_src().set_label(
|
||||
get_label_id_for(seg, current_fp + atom.get_imm() + additional_offset));
|
||||
}
|
||||
atom.set_label(
|
||||
get_label_id_for(seg, current_fp + atom.get_imm() + additional_offset));
|
||||
@@ -554,6 +556,12 @@ std::string LinkedObjectFile::print_disassembly() {
|
||||
auto& word = words_by_seg[seg].at(func.start_word + i);
|
||||
append_word_to_string(result, word);
|
||||
} else {
|
||||
if (func.has_basic_ops() && func.instr_starts_basic_op(i)) {
|
||||
if (line.length() < 40) {
|
||||
line.append(40 - line.length(), ' ');
|
||||
}
|
||||
line += ";; " + func.get_basic_op_at_instr(i)->print(*this);
|
||||
}
|
||||
result += line + "\n";
|
||||
}
|
||||
|
||||
@@ -614,6 +622,11 @@ std::string LinkedObjectFile::print_disassembly() {
|
||||
*/
|
||||
}
|
||||
|
||||
if (func.ir) {
|
||||
result += ";; ir\n";
|
||||
result += func.ir->print(*this);
|
||||
}
|
||||
|
||||
result += "\n\n\n";
|
||||
}
|
||||
|
||||
@@ -706,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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -728,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;
|
||||
@@ -747,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.
|
||||
@@ -764,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;
|
||||
}
|
||||
}
|
||||
@@ -801,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:
|
||||
@@ -812,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;
|
||||
@@ -824,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);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <cstring>
|
||||
#include "LinkedObjectFileCreation.h"
|
||||
#include "decompiler/config.h"
|
||||
#include "decompiler/TypeSystem/TypeInfo.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
|
||||
// There are three link versions:
|
||||
// V2 - not really in use anymore, but V4 will resue logic from it (and the game didn't rename the
|
||||
@@ -86,8 +86,9 @@ static uint32_t c_symlink2(LinkedObjectFile& f,
|
||||
uint32_t link_ptr_offset,
|
||||
SymbolLinkKind kind,
|
||||
const char* name,
|
||||
int seg_id) {
|
||||
get_type_info().inform_symbol_with_no_type_info(name);
|
||||
int seg_id,
|
||||
DecompilerTypeSystem& dts) {
|
||||
dts.add_symbol(name);
|
||||
auto initial_offset = code_ptr_offset;
|
||||
do {
|
||||
auto table_value = data.at(link_ptr_offset);
|
||||
@@ -130,7 +131,7 @@ static uint32_t c_symlink2(LinkedObjectFile& f,
|
||||
word_kind = LinkedWord::EMPTY_PTR;
|
||||
break;
|
||||
case SymbolLinkKind::TYPE:
|
||||
get_type_info().inform_type(name);
|
||||
dts.add_symbol(name, "type");
|
||||
word_kind = LinkedWord::TYPE_PTR;
|
||||
break;
|
||||
default:
|
||||
@@ -162,8 +163,9 @@ static uint32_t c_symlink3(LinkedObjectFile& f,
|
||||
uint32_t link_ptr,
|
||||
SymbolLinkKind kind,
|
||||
const char* name,
|
||||
int seg) {
|
||||
get_type_info().inform_symbol_with_no_type_info(name);
|
||||
int seg,
|
||||
DecompilerTypeSystem& dts) {
|
||||
dts.add_symbol(name);
|
||||
auto initial_offset = code_ptr;
|
||||
do {
|
||||
// seek, with a variable length encoding that sucks.
|
||||
@@ -187,7 +189,7 @@ static uint32_t c_symlink3(LinkedObjectFile& f,
|
||||
word_kind = LinkedWord::EMPTY_PTR;
|
||||
break;
|
||||
case SymbolLinkKind::TYPE:
|
||||
get_type_info().inform_type(name);
|
||||
dts.add_symbol(name, "type");
|
||||
word_kind = LinkedWord::TYPE_PTR;
|
||||
break;
|
||||
default:
|
||||
@@ -223,7 +225,8 @@ static uint32_t align16(uint32_t in) {
|
||||
*/
|
||||
static void link_v4(LinkedObjectFile& f,
|
||||
const std::vector<uint8_t>& data,
|
||||
const std::string& name) {
|
||||
const std::string& name,
|
||||
DecompilerTypeSystem& dts) {
|
||||
// read the V4 header to find where the link data really is
|
||||
const auto* header = (const LinkHeaderV4*)&data.at(0);
|
||||
uint32_t link_data_offset = header->code_size + sizeof(LinkHeaderV4); // no basic offset
|
||||
@@ -358,7 +361,7 @@ static void link_v4(LinkedObjectFile& f,
|
||||
|
||||
link_ptr_offset += strlen(s_name) + 1;
|
||||
f.stats.total_v2_symbol_count++;
|
||||
link_ptr_offset = c_symlink2(f, data, code_offset, link_ptr_offset, kind, s_name, 0);
|
||||
link_ptr_offset = c_symlink2(f, data, code_offset, link_ptr_offset, kind, s_name, 0, dts);
|
||||
if (data.at(link_ptr_offset) == 0)
|
||||
break;
|
||||
}
|
||||
@@ -384,7 +387,8 @@ static void assert_string_empty_after(const char* str, int size) {
|
||||
|
||||
static void link_v5(LinkedObjectFile& f,
|
||||
const std::vector<uint8_t>& data,
|
||||
const std::string& name) {
|
||||
const std::string& name,
|
||||
DecompilerTypeSystem& dts) {
|
||||
auto header = (const LinkHeaderV5*)(&data.at(0));
|
||||
if (header->n_segments == 1) {
|
||||
printf("abandon %s!\n", name.c_str());
|
||||
@@ -539,10 +543,10 @@ static void link_v5(LinkedObjectFile& f,
|
||||
|
||||
if (std::string("_empty_") == sname) {
|
||||
link_ptr = c_symlink2(f, data, segment_data_offsets[seg_id], link_ptr,
|
||||
SymbolLinkKind::EMPTY_LIST, sname, seg_id);
|
||||
SymbolLinkKind::EMPTY_LIST, sname, seg_id, dts);
|
||||
} else {
|
||||
link_ptr = c_symlink2(f, data, segment_data_offsets[seg_id], link_ptr,
|
||||
SymbolLinkKind::SYMBOL, sname, seg_id);
|
||||
SymbolLinkKind::SYMBOL, sname, seg_id, dts);
|
||||
}
|
||||
} else if ((reloc & 0x3f) == 0x3f) {
|
||||
assert(false); // todo, does this ever get hit?
|
||||
@@ -556,7 +560,7 @@ static void link_v5(LinkedObjectFile& f,
|
||||
const char* sname = (const char*)(&data.at(link_ptr));
|
||||
link_ptr += strlen(sname) + 1;
|
||||
link_ptr = c_symlink2(f, data, segment_data_offsets[seg_id], link_ptr,
|
||||
SymbolLinkKind::TYPE, sname, seg_id);
|
||||
SymbolLinkKind::TYPE, sname, seg_id, dts);
|
||||
}
|
||||
|
||||
sub_link_ptr = link_ptr;
|
||||
@@ -586,7 +590,8 @@ static void link_v5(LinkedObjectFile& f,
|
||||
|
||||
static void link_v3(LinkedObjectFile& f,
|
||||
const std::vector<uint8_t>& data,
|
||||
const std::string& name) {
|
||||
const std::string& name,
|
||||
DecompilerTypeSystem& dts) {
|
||||
auto header = (const LinkHeaderV3*)(&data.at(0));
|
||||
assert(name == header->name);
|
||||
assert(header->segments == 3);
|
||||
@@ -739,7 +744,7 @@ static void link_v3(LinkedObjectFile& f,
|
||||
// methods todo
|
||||
|
||||
s_name = (const char*)(&data.at(link_ptr));
|
||||
get_type_info().inform_type_method_count(s_name, reloc & 0x7f);
|
||||
// get_type_info().inform_type_method_count(s_name, reloc & 0x7f); todo
|
||||
kind = SymbolLinkKind::TYPE;
|
||||
}
|
||||
|
||||
@@ -750,7 +755,7 @@ static void link_v3(LinkedObjectFile& f,
|
||||
|
||||
link_ptr += strlen(s_name) + 1;
|
||||
f.stats.v3_symbol_count++;
|
||||
link_ptr = c_symlink3(f, data, base_ptr, link_ptr, kind, s_name, seg_id);
|
||||
link_ptr = c_symlink3(f, data, base_ptr, link_ptr, kind, s_name, seg_id, dts);
|
||||
}
|
||||
segment_link_ends[seg_id] = link_ptr;
|
||||
}
|
||||
@@ -775,19 +780,21 @@ static void link_v3(LinkedObjectFile& f,
|
||||
/*!
|
||||
* Main function to generate LinkedObjectFiles from raw object data.
|
||||
*/
|
||||
LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data, const std::string& name) {
|
||||
LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data,
|
||||
const std::string& name,
|
||||
DecompilerTypeSystem& dts) {
|
||||
LinkedObjectFile result;
|
||||
const auto* header = (const LinkHeaderCommon*)&data.at(0);
|
||||
|
||||
// use appropriate linker
|
||||
if (header->version == 3) {
|
||||
assert(header->type_tag == 0);
|
||||
link_v3(result, data, name);
|
||||
link_v3(result, data, name, dts);
|
||||
} else if (header->version == 4) {
|
||||
assert(header->type_tag == 0xffffffff);
|
||||
link_v4(result, data, name);
|
||||
link_v4(result, data, name, dts);
|
||||
} else if (header->version == 5) {
|
||||
link_v5(result, data, name);
|
||||
link_v5(result, data, name, dts);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
#include "LinkedObjectFile.h"
|
||||
|
||||
LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data, const std::string& name);
|
||||
class DecompilerTypeSystem;
|
||||
LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data,
|
||||
const std::string& name,
|
||||
DecompilerTypeSystem& dts);
|
||||
|
||||
#endif // NEXT_LINKEDOBJECTFILECREATION_H
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "common/util/Timer.h"
|
||||
#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.
|
||||
@@ -64,6 +66,8 @@ ObjectFileData& ObjectFileDB::lookup_record(ObjectFileRecord rec) {
|
||||
*/
|
||||
ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos) {
|
||||
Timer timer;
|
||||
printf("- Loading Types...\n");
|
||||
dts.parse_type_defs({"decompiler", "config", "all-types.gc"});
|
||||
|
||||
printf("- Initializing ObjectFileDB...\n");
|
||||
for (auto& dgo : _dgos) {
|
||||
@@ -71,7 +75,7 @@ ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos) {
|
||||
}
|
||||
|
||||
printf("ObjectFileDB Initialized:\n");
|
||||
printf(" total dgos: %lld\n", _dgos.size());
|
||||
printf(" total dgos: %d\n", int(_dgos.size()));
|
||||
printf(" total data: %d bytes\n", stats.total_dgo_bytes);
|
||||
printf(" total objs: %d\n", stats.total_obj_files);
|
||||
printf(" unique objs: %d\n", stats.unique_obj_files);
|
||||
@@ -356,7 +360,7 @@ void ObjectFileDB::process_link_data() {
|
||||
LinkedObjectFile::Stats combined_stats;
|
||||
|
||||
for_each_obj([&](ObjectFileData& obj) {
|
||||
obj.linked_data = to_linked_object_file(obj.data, obj.record.name);
|
||||
obj.linked_data = to_linked_object_file(obj.data, obj.record.name, dts);
|
||||
combined_stats.add(obj.linked_data.stats);
|
||||
});
|
||||
|
||||
@@ -543,7 +547,7 @@ void ObjectFileDB::analyze_functions() {
|
||||
auto& func = data.linked_data.functions_by_seg.at(2).front();
|
||||
assert(func.guessed_name.empty());
|
||||
func.guessed_name.set_as_top_level();
|
||||
func.find_global_function_defs(data.linked_data);
|
||||
func.find_global_function_defs(data.linked_data, dts);
|
||||
func.find_method_defs(data.linked_data);
|
||||
}
|
||||
});
|
||||
@@ -588,34 +592,52 @@ void ObjectFileDB::analyze_functions() {
|
||||
// }
|
||||
}
|
||||
|
||||
int total_nontrivial_functions = 0;
|
||||
int total_resolved_nontrivial_functions = 0;
|
||||
int total_trivial_cfg_functions = 0;
|
||||
int total_named_functions = 0;
|
||||
int total_basic_ops = 0;
|
||||
int total_failed_basic_ops = 0;
|
||||
|
||||
int asm_funcs = 0;
|
||||
int non_asm_funcs = 0;
|
||||
int successful_cfg_irs = 0;
|
||||
|
||||
std::map<int, std::vector<std::string>> unresolved_by_length;
|
||||
if (get_config().find_basic_blocks) {
|
||||
timer.start();
|
||||
int total_basic_blocks = 0;
|
||||
for_each_function([&](Function& func, int segment_id, ObjectFileData& data) {
|
||||
// printf("in %s\n", func.guessed_name.to_string().c_str());
|
||||
auto blocks = find_blocks_in_function(data.linked_data, segment_id, func);
|
||||
total_basic_blocks += blocks.size();
|
||||
func.basic_blocks = blocks;
|
||||
|
||||
total_functions++;
|
||||
if (!func.suspected_asm) {
|
||||
func.analyze_prologue(data.linked_data);
|
||||
func.cfg = build_cfg(data.linked_data, segment_id, func);
|
||||
total_functions++;
|
||||
for (auto& block : func.basic_blocks) {
|
||||
if (block.end_word > block.start_word) {
|
||||
add_basic_ops_to_block(&func, block, &data.linked_data);
|
||||
}
|
||||
}
|
||||
total_basic_ops += func.get_basic_op_count();
|
||||
total_failed_basic_ops += func.get_failed_basic_op_count();
|
||||
|
||||
func.ir = build_cfg_ir(func, *func.cfg, data.linked_data);
|
||||
non_asm_funcs++;
|
||||
if (func.ir) {
|
||||
successful_cfg_irs++;
|
||||
}
|
||||
|
||||
if (func.cfg->is_fully_resolved()) {
|
||||
resolved_cfg_functions++;
|
||||
}
|
||||
} else {
|
||||
resolved_cfg_functions++;
|
||||
asm_funcs++;
|
||||
}
|
||||
|
||||
if (func.basic_blocks.size() > 1 && !func.suspected_asm) {
|
||||
total_nontrivial_functions++;
|
||||
if (func.cfg->is_fully_resolved()) {
|
||||
total_resolved_nontrivial_functions++;
|
||||
} else {
|
||||
if (!func.guessed_name.empty()) {
|
||||
unresolved_by_length[func.end_word - func.start_word].push_back(
|
||||
@@ -624,27 +646,38 @@ void ObjectFileDB::analyze_functions() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!func.suspected_asm && func.basic_blocks.size() <= 1) {
|
||||
total_trivial_cfg_functions++;
|
||||
}
|
||||
|
||||
if (!func.guessed_name.empty()) {
|
||||
total_named_functions++;
|
||||
}
|
||||
|
||||
// if (func.guessed_name.to_string() == "inspect") {
|
||||
// assert(false);
|
||||
// }
|
||||
});
|
||||
|
||||
printf("Found %d functions (%d with nontrivial cfgs)\n", total_functions,
|
||||
total_nontrivial_functions);
|
||||
printf("Found %d functions (%d with no control flow)\n", total_functions,
|
||||
total_trivial_cfg_functions);
|
||||
printf("Named %d/%d functions (%.2f%%)\n", total_named_functions, total_functions,
|
||||
100.f * float(total_named_functions) / float(total_functions));
|
||||
printf("Excluding %d asm functions\n", asm_funcs);
|
||||
printf("Found %d basic blocks in %.3f ms\n", total_basic_blocks, timer.getMs());
|
||||
printf(" %d/%d functions passed cfg analysis stage (%.2f%%)\n", resolved_cfg_functions,
|
||||
total_functions, 100.f * float(resolved_cfg_functions) / float(total_functions));
|
||||
printf(" %d/%d nontrivial cfg's resolved (%.2f%%)\n", total_resolved_nontrivial_functions,
|
||||
total_nontrivial_functions,
|
||||
100.f * float(total_resolved_nontrivial_functions) / float(total_nontrivial_functions));
|
||||
non_asm_funcs, 100.f * float(resolved_cfg_functions) / float(non_asm_funcs));
|
||||
int successful_basic_ops = total_basic_ops - total_failed_basic_ops;
|
||||
printf(" %d/%d basic ops converted successfully (%.2f%%)\n", successful_basic_ops,
|
||||
total_basic_ops, 100.f * float(successful_basic_ops) / float(total_basic_ops));
|
||||
printf(" %d/%d cfgs converted to ir (%.2f%%)\n", successful_cfg_irs, non_asm_funcs,
|
||||
100.f * float(successful_cfg_irs) / float(non_asm_funcs));
|
||||
|
||||
for (auto& kv : unresolved_by_length) {
|
||||
printf("LEN %d\n", kv.first);
|
||||
for (auto& x : kv.second) {
|
||||
printf(" %s\n", x.c_str());
|
||||
}
|
||||
}
|
||||
// for (auto& kv : unresolved_by_length) {
|
||||
// printf("LEN %d\n", kv.first);
|
||||
// for (auto& x : kv.second) {
|
||||
// printf(" %s\n", x.c_str());
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "LinkedObjectFile.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
|
||||
/*!
|
||||
* A "record" which can be used to identify an object file.
|
||||
@@ -55,6 +56,7 @@ class ObjectFileDB {
|
||||
void write_disassembly(const std::string& output_dir, bool disassemble_objects_without_functions);
|
||||
void analyze_functions();
|
||||
ObjectFileData& lookup_record(ObjectFileRecord rec);
|
||||
DecompilerTypeSystem dts;
|
||||
|
||||
private:
|
||||
void get_objs_from_dgo(const std::string& filename);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#include "GoalFunction.h"
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef JAK_DISASSEMBLER_GOALFUNCTION_H
|
||||
#define JAK_DISASSEMBLER_GOALFUNCTION_H
|
||||
|
||||
class GoalFunction {
|
||||
public:
|
||||
// enum Kind {
|
||||
// GLOBAL_FUNCTION,
|
||||
// ANON_FUNCTION,
|
||||
// METHOD,
|
||||
// BEHAVIOR,
|
||||
// UNKNOWN
|
||||
// };
|
||||
};
|
||||
|
||||
#endif // JAK_DISASSEMBLER_GOALFUNCTION_H
|
||||
@@ -1 +0,0 @@
|
||||
#include "GoalSymbol.h"
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef JAK_DISASSEMBLER_GOALSYMBOL_H
|
||||
#define JAK_DISASSEMBLER_GOALSYMBOL_H
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include "TypeSpec.h"
|
||||
|
||||
class GoalSymbol {
|
||||
public:
|
||||
GoalSymbol() = default;
|
||||
explicit GoalSymbol(std::string name) : m_name(std::move(name)) {}
|
||||
GoalSymbol(std::string name, TypeSpec ts) : m_name(std::move(name)), m_type(std::move(ts)) {
|
||||
m_has_type_info = true;
|
||||
}
|
||||
|
||||
bool has_type_info() const { return m_has_type_info; }
|
||||
|
||||
void set_type(TypeSpec ts) {
|
||||
if (m_has_type_info) {
|
||||
if (ts != m_type) {
|
||||
printf("symbol %s %s -> %s", m_name.c_str(), m_type.to_string().c_str(),
|
||||
ts.to_string().c_str());
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
m_has_type_info = true;
|
||||
m_type = std::move(ts);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
TypeSpec m_type;
|
||||
bool m_has_type_info = false;
|
||||
};
|
||||
|
||||
#endif // JAK_DISASSEMBLER_GOALSYMBOL_H
|
||||
@@ -1,13 +0,0 @@
|
||||
#include "GoalType.h"
|
||||
|
||||
void GoalType::set_methods(int n) {
|
||||
if (m_method_count_set) {
|
||||
if (m_method_count != n) {
|
||||
printf("Type %s had %d methods, set_methods tried to change it to %d\n", m_name.c_str(),
|
||||
m_method_count, n);
|
||||
}
|
||||
} else {
|
||||
m_method_count = n;
|
||||
m_method_count_set = true;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef JAK_DISASSEMBLER_GOALTYPE_H
|
||||
#define JAK_DISASSEMBLER_GOALTYPE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
class GoalType {
|
||||
public:
|
||||
GoalType() = default;
|
||||
GoalType(std::string name) : m_name(std::move(name)) {}
|
||||
bool has_info() const { return m_has_info; }
|
||||
|
||||
bool has_method_count() const { return m_method_count_set; }
|
||||
|
||||
void set_methods(int n);
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
bool m_has_info = false;
|
||||
bool m_method_count_set = false;
|
||||
int m_method_count = -1;
|
||||
};
|
||||
|
||||
#endif // JAK_DISASSEMBLER_GOALTYPE_H
|
||||
@@ -1,99 +0,0 @@
|
||||
#include "TypeInfo.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
TypeInfo gTypeInfo;
|
||||
}
|
||||
|
||||
TypeInfo::TypeInfo() {
|
||||
GoalType type_type("type");
|
||||
m_types["type"] = type_type;
|
||||
GoalSymbol type_symbol("type");
|
||||
m_symbols["type"] = type_symbol;
|
||||
}
|
||||
|
||||
TypeInfo& get_type_info() {
|
||||
return gTypeInfo;
|
||||
}
|
||||
|
||||
std::string TypeInfo::get_summary() {
|
||||
int total_symbols = 0;
|
||||
int syms_with_type_info = 0;
|
||||
for (const auto& kv : m_symbols) {
|
||||
total_symbols++;
|
||||
if (kv.second.has_type_info()) {
|
||||
syms_with_type_info++;
|
||||
}
|
||||
}
|
||||
|
||||
int total_types = 0;
|
||||
int types_with_info = 0;
|
||||
int types_with_method_count = 0;
|
||||
for (const auto& kv : m_types) {
|
||||
total_types++;
|
||||
if (kv.second.has_info()) {
|
||||
types_with_info++;
|
||||
}
|
||||
if (kv.second.has_method_count()) {
|
||||
types_with_method_count++;
|
||||
}
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
sprintf(buffer,
|
||||
"TypeInfo Summary\n"
|
||||
" Total Symbols: %d\n"
|
||||
" with type info: %d (%.2f%%)\n"
|
||||
" Total Types: %d\n"
|
||||
" with info: %d (%.2f%%)\n"
|
||||
" with method count: %d (%.2f%%)\n",
|
||||
total_symbols, syms_with_type_info,
|
||||
100.f * float(syms_with_type_info) / float(total_symbols), total_types, types_with_info,
|
||||
100.f * float(types_with_info) / float(total_types), types_with_method_count,
|
||||
100.f * float(types_with_method_count) / float(total_types));
|
||||
|
||||
return {buffer};
|
||||
}
|
||||
|
||||
/*!
|
||||
* inform TypeInfo that there is a symbol with this name.
|
||||
* Provides no type info - if some is already known there is no change.
|
||||
*/
|
||||
void TypeInfo::inform_symbol_with_no_type_info(const std::string& name) {
|
||||
if (m_symbols.find(name) == m_symbols.end()) {
|
||||
// only add it if we haven't seen this already.
|
||||
GoalSymbol sym(name);
|
||||
m_symbols[name] = sym;
|
||||
}
|
||||
}
|
||||
|
||||
void TypeInfo::inform_symbol(const std::string& name, TypeSpec type) {
|
||||
inform_symbol_with_no_type_info(name);
|
||||
m_symbols.at(name).set_type(std::move(type));
|
||||
}
|
||||
|
||||
void TypeInfo::inform_type(const std::string& name) {
|
||||
if (m_types.find(name) == m_types.end()) {
|
||||
GoalType typ(name);
|
||||
m_types[name] = typ;
|
||||
}
|
||||
inform_symbol(name, TypeSpec("type"));
|
||||
}
|
||||
|
||||
void TypeInfo::inform_type_method_count(const std::string& name, int methods) {
|
||||
// create type and symbol
|
||||
inform_type(name);
|
||||
m_types.at(name).set_methods(methods);
|
||||
}
|
||||
|
||||
std::string TypeInfo::get_all_symbols_debug() {
|
||||
std::string result = "const char* all_syms[" + std::to_string(m_symbols.size()) + "] = {";
|
||||
for (auto& x : m_symbols) {
|
||||
result += "\"" + x.first + "\",";
|
||||
}
|
||||
if (!result.empty()) {
|
||||
result.pop_back();
|
||||
}
|
||||
return result + "};";
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef JAK_DISASSEMBLER_TYPEINFO_H
|
||||
#define JAK_DISASSEMBLER_TYPEINFO_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include "GoalType.h"
|
||||
#include "GoalFunction.h"
|
||||
#include "GoalSymbol.h"
|
||||
|
||||
class TypeInfo {
|
||||
public:
|
||||
TypeInfo();
|
||||
|
||||
void inform_symbol(const std::string& name, TypeSpec type);
|
||||
void inform_symbol_with_no_type_info(const std::string& name);
|
||||
void inform_type(const std::string& name);
|
||||
void inform_type_method_count(const std::string& name, int methods);
|
||||
|
||||
std::string get_summary();
|
||||
std::string get_all_symbols_debug();
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, GoalType> m_types;
|
||||
std::unordered_map<std::string, GoalFunction> m_global_functions;
|
||||
std::unordered_map<std::string, GoalSymbol> m_symbols;
|
||||
};
|
||||
|
||||
TypeInfo& get_type_info();
|
||||
void init_type_info();
|
||||
|
||||
#endif // JAK_DISASSEMBLER_TYPEINFO_H
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "TypeSpec.h"
|
||||
|
||||
std::string TypeSpec::to_string() const {
|
||||
if (m_args.empty()) {
|
||||
return m_base_type;
|
||||
} else {
|
||||
std::string result = "(";
|
||||
result += m_base_type;
|
||||
for (const auto& x : m_args) {
|
||||
result += " ";
|
||||
result += x.to_string();
|
||||
}
|
||||
result += ")";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Form> TypeSpec::to_form() const {
|
||||
if (m_args.empty()) {
|
||||
return toForm(m_base_type);
|
||||
} else {
|
||||
std::vector<std::shared_ptr<Form>> all;
|
||||
all.push_back(toForm(m_base_type));
|
||||
for (const auto& x : m_args) {
|
||||
all.push_back(x.to_form());
|
||||
}
|
||||
return buildList(all);
|
||||
}
|
||||
}
|
||||
|
||||
bool TypeSpec::operator==(const TypeSpec& other) const {
|
||||
if (m_base_type != other.m_base_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_args.size() != other.m_args.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_args.size(); i++) {
|
||||
if (m_args[i] != other.m_args[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TypeSpec::operator!=(const TypeSpec& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef JAK_DISASSEMBLER_TYPESPEC_H
|
||||
#define JAK_DISASSEMBLER_TYPESPEC_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "decompiler/util/LispPrint.h"
|
||||
|
||||
class TypeSpec {
|
||||
public:
|
||||
TypeSpec() = default;
|
||||
explicit TypeSpec(std::string base_type) : m_base_type(std::move(base_type)) {}
|
||||
TypeSpec(std::string base_type, std::vector<TypeSpec> args)
|
||||
: m_base_type(std::move(base_type)), m_args(std::move(args)) {}
|
||||
|
||||
std::string to_string() const;
|
||||
std::shared_ptr<Form> to_form() const;
|
||||
|
||||
bool operator==(const TypeSpec& other) const;
|
||||
bool operator!=(const TypeSpec& other) const;
|
||||
|
||||
private:
|
||||
std::string m_base_type;
|
||||
std::vector<TypeSpec> m_args;
|
||||
};
|
||||
|
||||
#endif // JAK_DISASSEMBLER_TYPESPEC_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,13 +3,14 @@
|
||||
{
|
||||
"game_version":1,
|
||||
// the order here matters. KERNEL and GAME should go first
|
||||
"dgo_names":["CGO/KERNEL.CGO"
|
||||
, "CGO/GAME.CGO", "CGO/ENGINE.CGO"
|
||||
"dgo_names":["CGO/KERNEL.CGO", "CGO/GAME.CGO"],
|
||||
/*, "CGO/ENGINE.CGO"
|
||||
, "CGO/ART.CGO", "DGO/BEA.DGO", "DGO/CIT.DGO", "CGO/COMMON.CGO", "DGO/DAR.DGO", "DGO/DEM.DGO",
|
||||
"DGO/FIN.DGO", "DGO/INT.DGO", "DGO/JUB.DGO", "DGO/JUN.DGO", "CGO/JUNGLE.CGO", "CGO/L1.CGO", "DGO/FIC.DGO",
|
||||
"DGO/LAV.DGO", "DGO/MAI.DGO", "CGO/MAINCAVE.CGO", "DGO/MIS.DGO", "DGO/OGR.DGO", "CGO/RACERP.CGO", "DGO/ROB.DGO", "DGO/ROL.DGO",
|
||||
"DGO/SNO.DGO", "DGO/SUB.DGO", "DGO/SUN.DGO", "CGO/SUNKEN.CGO", "DGO/SWA.DGO", "DGO/TIT.DGO", "DGO/TRA.DGO", "DGO/VI1.DGO",
|
||||
"DGO/VI2.DGO", "DGO/VI3.DGO", "CGO/VILLAGEP.CGO", "CGO/WATER-AN.CGO"],
|
||||
"DGO/VI2.DGO", "DGO/VI3.DGO", "CGO/VILLAGEP.CGO", "CGO/WATER-AN.CGO"
|
||||
],*/
|
||||
|
||||
"write_disassembly":true,
|
||||
"write_hex_near_instructions":false,
|
||||
@@ -22,16 +23,56 @@
|
||||
"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", "collide-do-primitives", "draw-bones-check-longest-edge-asm",
|
||||
"min", "max", "(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!",
|
||||
|
||||
// pskernel
|
||||
"resend-exception", "kernel-set-interrupt-vector", "kernel-set-exception-vector", "return-from-exception",
|
||||
"kernel-read", "kernel-read-function", "kernel-write", "kernel-write-function", "kernel-copy-to-kernel-ram",
|
||||
|
||||
// this one needs more investigation. nothing looks weird about it but it fails...
|
||||
"camera-change-to",
|
||||
|
||||
// two back to back arithmetic shifts...
|
||||
"texture-relocate",
|
||||
|
||||
// this one fails due to false compaction where an else case has only a not expression in it.
|
||||
"master-is-hopeful-better?",
|
||||
|
||||
// fails for unknown reason
|
||||
"target-falling-anim-trans", "change-brother",
|
||||
|
||||
// merged right typecase... can probably handle this
|
||||
"cspace-inspect-tree",
|
||||
|
||||
// these are all valid, but use short circuiting branches in strange ways. There's probably a few compiler uses that we're not
|
||||
"(method 21 actor-link-info)","(method 20 actor-link-info)","(method 28 collide-shape-prim-mesh)", "(method 35 collide-shape)",
|
||||
"debug-menu-item-var-render", "(method 14 level)","add-blue-motion","anim-tester-add-newobj","(method 27 orb-cache-top)",
|
||||
|
||||
// real asm
|
||||
"cspace<-parented-transformq-joint!", "blerc-a-fragment", "render-boundary-tri", "render-boundary-quad",
|
||||
"(method 19 collide-shape-prim-sphere)","vector-segment-distance-point!", "exp", "(method 11 collide-mesh-cache)",
|
||||
"(method 13 collide-edge-work)", "ambient-inspect",
|
||||
|
||||
"(method 11 cpu-thread)", "atan0", "sincos!", "sincos-rad!", "disasm-dma-list", "vblank-handler", "vif1-handler",
|
||||
"vif1-handler-debug", "entity-actor-count", "decompress-frame-data-pair-to-accumulator",
|
||||
"decompress-frame-data-to-accumulator", "normalize-frame-quaternions", "clear-frame-accumulator",
|
||||
"generic-copy-vtx-dclr-dtex", "generic-no-light-dproc-only", "generic-no-light-proc", "mercneric-bittable-asm",
|
||||
"generic-tie-decompress", "matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!", "generic-prepare-dma-single",
|
||||
"(method 13 collide-shape-prim-sphere)", "(method 14 collide-shape-prim-sphere)", "(method 12 collide-shape-prim-sphere)",
|
||||
"adgif-shader<-texture-with-update!", "generic-interp-dproc", "sprite-draw-distorters", "draw-bones", "(method 9 collide-mesh-cache)",
|
||||
"(method 18 collide-shape-prim-sphere)","birth-pickup-at-point",
|
||||
|
||||
"collide-do-primitives", "draw-bones-check-longest-edge-asm",
|
||||
"sp-launch-particles-var", "(method 15 collide-shape-prim-mesh)", "(method 15 collide-shape-prim-sphere)",
|
||||
"(method 45 collide-shape)", "cam-layout-save-cam-trans", "kernel-copy-function", "dma-sync-hang", "generic-no-light-dproc", "dma-sync-fast", "bsp-camera-asm",
|
||||
"(method 45 collide-shape)", "cam-layout-save-cam-trans", "kernel-copy-function", "dma-sync-hang", "generic-no-light-dproc",
|
||||
"dma-sync-fast", "bsp-camera-asm",
|
||||
"generic-none-dma-wait", "unpack-comp-rle", "level-remap-texture", "(method 10 collide-edge-hold-list)"
|
||||
]
|
||||
}
|
||||
+5
-4
@@ -4,7 +4,7 @@
|
||||
#include "ObjectFile/ObjectFileDB.h"
|
||||
#include "config.h"
|
||||
#include "util/FileIO.h"
|
||||
#include "TypeSystem/TypeInfo.h"
|
||||
|
||||
#include "common/util/FileUtil.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -48,8 +48,9 @@ int main(int argc, char** argv) {
|
||||
db.write_disassembly(out_folder, get_config().disassemble_objects_without_functions);
|
||||
}
|
||||
|
||||
printf("%s\n", get_type_info().get_summary().c_str());
|
||||
// printf("%d\n", InstructionKind::EE_OP_MAX);
|
||||
// printf("%s\n", get_type_info().get_all_symbols_debug().c_str());
|
||||
// todo print type summary
|
||||
// printf("%s\n", get_type_info().get_summary().c_str());
|
||||
|
||||
file_util::write_text_file(combine_path(out_folder, "all-syms.gc"), db.dts.dump_symbol_types());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "DecompilerTypeSystem.h"
|
||||
#include "common/goos/Reader.h"
|
||||
#include "common/type_system/deftype.h"
|
||||
|
||||
DecompilerTypeSystem::DecompilerTypeSystem() {
|
||||
ts.add_builtin_types();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// some utilities for parsing the type def file
|
||||
|
||||
goos::Object& car(goos::Object& pair) {
|
||||
if (pair.is_pair()) {
|
||||
return pair.as_pair()->car;
|
||||
} else {
|
||||
throw std::runtime_error("car called on something that wasn't a pair: " + pair.print());
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object& cdr(goos::Object& pair) {
|
||||
if (pair.is_pair()) {
|
||||
return pair.as_pair()->cdr;
|
||||
} else {
|
||||
throw std::runtime_error("cdr called on something that wasn't a pair");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void for_each_in_list(goos::Object& list, T f) {
|
||||
goos::Object* iter = &list;
|
||||
while (iter->is_pair()) {
|
||||
f(car(*iter));
|
||||
iter = &cdr(*iter);
|
||||
}
|
||||
|
||||
if (!iter->is_empty_list()) {
|
||||
throw std::runtime_error("malformed list");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void DecompilerTypeSystem::parse_type_defs(const std::vector<std::string>& file_path) {
|
||||
goos::Reader reader;
|
||||
auto read = reader.read_from_file(file_path);
|
||||
auto data = cdr(read);
|
||||
|
||||
for_each_in_list(data, [&](goos::Object& o) {
|
||||
if (car(o).as_symbol()->name == "define-extern") {
|
||||
auto* rest = &cdr(o);
|
||||
auto sym_name = car(*rest);
|
||||
rest = &cdr(*rest);
|
||||
auto sym_type = car(*rest);
|
||||
if (!cdr(*rest).is_empty_list()) {
|
||||
throw std::runtime_error("malformed define-extern");
|
||||
}
|
||||
add_symbol(sym_name.as_symbol()->name, parse_typespec(&ts, sym_type));
|
||||
|
||||
} else if (car(o).as_symbol()->name == "deftype") {
|
||||
parse_deftype(cdr(o), &ts);
|
||||
} else {
|
||||
throw std::runtime_error("Decompiler cannot parse " + car(o).print());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::string DecompilerTypeSystem::dump_symbol_types() {
|
||||
assert(symbol_add_order.size() == symbols.size());
|
||||
std::string result;
|
||||
for (auto& symbol_name : symbol_add_order) {
|
||||
auto skv = symbol_types.find(symbol_name);
|
||||
if (skv == symbol_types.end()) {
|
||||
result += fmt::format(";;(define-extern {} object) ;; unknown type\n", symbol_name);
|
||||
} else {
|
||||
result += fmt::format("(define-extern {} {})\n", symbol_name, skv->second.print());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef JAK_DECOMPILERTYPESYSTEM_H
|
||||
#define JAK_DECOMPILERTYPESYSTEM_H
|
||||
|
||||
#include "common/type_system/TypeSystem.h"
|
||||
#include "third-party/fmt/format.h"
|
||||
|
||||
class DecompilerTypeSystem {
|
||||
public:
|
||||
DecompilerTypeSystem();
|
||||
TypeSystem ts;
|
||||
std::unordered_map<std::string, TypeSpec> symbol_types;
|
||||
std::unordered_set<std::string> symbols;
|
||||
std::vector<std::string> symbol_add_order;
|
||||
|
||||
void add_symbol(const std::string& name) {
|
||||
if (symbols.find(name) == symbols.end()) {
|
||||
symbols.insert(name);
|
||||
symbol_add_order.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
void add_symbol(const std::string& name, const std::string& base_type) {
|
||||
add_symbol(name, TypeSpec(base_type));
|
||||
}
|
||||
|
||||
void add_symbol(const std::string& name, const TypeSpec& type_spec) {
|
||||
add_symbol(name);
|
||||
auto skv = symbol_types.find(name);
|
||||
if (skv == symbol_types.end() || skv->second == type_spec) {
|
||||
symbol_types[name] = type_spec;
|
||||
} else {
|
||||
if (ts.typecheck(type_spec, skv->second, "", false, false)) {
|
||||
} else {
|
||||
fmt::print("Attempting to redefine type of symbol {} from {} to {}\n", name,
|
||||
skv->second.print(), type_spec.print());
|
||||
throw std::runtime_error("Type redefinition");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse_type_defs(const std::vector<std::string>& file_path);
|
||||
|
||||
std::string dump_symbol_types();
|
||||
};
|
||||
|
||||
#endif // JAK_DECOMPILERTYPESYSTEM_H
|
||||
@@ -1,516 +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());
|
||||
}
|
||||
@@ -1,144 +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);
|
||||
|
||||
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
@@ -26,7 +26,7 @@ endif(CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
||||
enable_language(ASM_NASM)
|
||||
set(CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS ${CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS} asm)
|
||||
set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> <INCLUDES> <FLAGS> -f ${CMAKE_ASM_NASM_OBJECT_FORMAT} -o <OBJECT> <SOURCE>")
|
||||
set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> <INCLUDES> -f ${CMAKE_ASM_NASM_OBJECT_FORMAT} -o <OBJECT> <SOURCE>")
|
||||
set_source_files_properties(kernel/asm_funcs.asm PROPERTIES COMPILE_FLAGS "-g")
|
||||
set(RUNTIME_SOURCE
|
||||
runtime.cpp
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "game/common/ramdisk_rpc_types.h"
|
||||
#include "game/common/loader_rpc_types.h"
|
||||
#include "game/common/play_rpc_types.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
|
||||
using namespace ee;
|
||||
|
||||
@@ -175,9 +176,8 @@ void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8>
|
||||
|
||||
// file name
|
||||
strcpy(sMsg[msgID].name, name);
|
||||
printf("[Begin Loading DGO RPC] %s, 0x%x, 0x%x, 0x%x\n", name, buffer1.offset, buffer2.offset,
|
||||
currentHeap.offset);
|
||||
|
||||
spdlog::debug("[Begin Loading DGO RPC] {}, 0x{}, 0x{}, 0x{}", name, buffer1.offset,
|
||||
buffer2.offset, currentHeap.offset);
|
||||
// this RPC will return once we have loaded the first object file.
|
||||
// but we call async, so we don't block here.
|
||||
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess,
|
||||
@@ -299,7 +299,7 @@ void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size)
|
||||
* This does not use the mutli-threaded linker and will block until the entire file is done.e
|
||||
*/
|
||||
void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFlag, s32 bufferSize) {
|
||||
printf("[Load and Link DGO From C] %s\n", name);
|
||||
spdlog::debug("[Load and Link DGO From C] {}", name);
|
||||
u32 oldShowStall = sShowStallMsg;
|
||||
|
||||
// remember where the heap top point is so we can clear temporary allocations
|
||||
@@ -348,7 +348,7 @@ void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFla
|
||||
|
||||
char objName[64];
|
||||
strcpy(objName, (dgoObj + 4).cast<char>().c()); // name from dgo object header
|
||||
printf("[link and exec] %s %d\n", objName, lastObjectLoaded);
|
||||
spdlog::debug("[link and exec] {} {}", objName, lastObjectLoaded);
|
||||
link_and_exec(obj, objName, objSize, heap, linkFlag); // link now!
|
||||
|
||||
// inform IOP we are done
|
||||
@@ -357,4 +357,4 @@ void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFla
|
||||
}
|
||||
}
|
||||
sShowStallMsg = oldShowStall;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-22
@@ -25,7 +25,7 @@
|
||||
#include "game/sce/libcdvd_ee.h"
|
||||
#include "game/sce/stubs.h"
|
||||
#include "common/symbols.h"
|
||||
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
using namespace ee;
|
||||
|
||||
/*!
|
||||
@@ -148,13 +148,13 @@ void InitParms(int argc, const char* const* argv) {
|
||||
* DONE, EXACT
|
||||
*/
|
||||
void InitCD() {
|
||||
printf("Initializing CD drive\nThis may take a while ...\n");
|
||||
spdlog::info("Initializing CD drive\nThis may take a while...\n");
|
||||
sceCdInit(SCECdINIT);
|
||||
sceCdMmode(SCECdDVD);
|
||||
while (sceCdDiskReady(0) == SCECdNotReady) {
|
||||
printf("Drive not ready ... insert a disk!\n");
|
||||
spdlog::debug("Drive not ready... insert a disk!\n");
|
||||
}
|
||||
printf("Disk type %d\n", sceCdGetDiskType());
|
||||
spdlog::debug("Disk type {}\n", sceCdGetDiskType());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -172,22 +172,22 @@ void InitIOP() {
|
||||
|
||||
if (!reboot) {
|
||||
// reboot with development IOP kernel
|
||||
printf("Rebooting IOP...\n");
|
||||
spdlog::debug("Rebooting IOP...");
|
||||
while (!sceSifRebootIop("host0:/usr/local/sce/iop/modules/ioprp221.img")) {
|
||||
printf("Failed, retrying...\n");
|
||||
spdlog::debug("Failed, retrying");
|
||||
}
|
||||
while (!sceSifSyncIop()) {
|
||||
printf("Syncing...\n");
|
||||
spdlog::debug("Syncing...");
|
||||
}
|
||||
} else {
|
||||
// reboot with IOP kernel off of the disk
|
||||
// reboot with development IOP kernel
|
||||
printf("Rebooting IOP...\n");
|
||||
spdlog::debug("Rebooting IOP...");
|
||||
while (!sceSifRebootIop("cdrom0:\\DRIVERS\\IOPRP221.IMG;1")) {
|
||||
printf("Failed, retrying...\n");
|
||||
spdlog::debug("Failed, retrying");
|
||||
}
|
||||
while (!sceSifSyncIop()) {
|
||||
printf("Syncing...\n");
|
||||
spdlog::debug("Syncing...");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ void InitIOP() {
|
||||
|
||||
sceSifLoadModule("host0:/usr/home/src/989snd10/iop/989ERR.IRX", 0, nullptr);
|
||||
|
||||
printf("Initializing CD library\n");
|
||||
spdlog::debug("Initializing CD library...");
|
||||
auto rv = sceSifLoadModule("host0:binee/overlord.irx", cmd + len + 1 - overlord_boot_command,
|
||||
overlord_boot_command);
|
||||
if (rv < 0) {
|
||||
@@ -270,7 +270,7 @@ void InitIOP() {
|
||||
MsgErr("loading 989snd.irx failed\n");
|
||||
}
|
||||
|
||||
printf("Initializing CD library in ISO_CD mode\n");
|
||||
spdlog::debug("Initializing CD library in ISO_CD mode...");
|
||||
auto rv = sceSifLoadModule("cdrom0:\\\\DRIVERS\\\\OVERLORD.IRX;1",
|
||||
cmd + len + 1 - overlord_boot_command, overlord_boot_command);
|
||||
if (rv < 0) {
|
||||
@@ -281,7 +281,7 @@ void InitIOP() {
|
||||
if (rv < 0) {
|
||||
MsgErr("MC driver init failed %d\n", rv);
|
||||
} else {
|
||||
printf("InitIOP OK\n");
|
||||
spdlog::info("InitIOP OK");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,8 +302,8 @@ int InitMachine() {
|
||||
// initialize the global heap
|
||||
u32 global_heap_size = GLOBAL_HEAP_END - HEAP_START;
|
||||
float size_mb = ((float)global_heap_size) / (float)(1 << 20);
|
||||
printf("gkernel: global heap - 0x%x to 0x%x (size %.3f MB)\n", HEAP_START, GLOBAL_HEAP_END,
|
||||
size_mb);
|
||||
spdlog::info("gkernel: global heap 0x{} to 0x{} (size {} MB)", HEAP_START, GLOBAL_HEAP_END,
|
||||
size_mb);
|
||||
kinitheap(kglobalheap, Ptr<u8>(HEAP_START), global_heap_size);
|
||||
|
||||
// initialize the debug heap, if appropriate
|
||||
@@ -312,8 +312,8 @@ int InitMachine() {
|
||||
kinitheap(kdebugheap, Ptr<u8>(DEBUG_HEAP_START), debug_heap_size);
|
||||
float debug_size_mb = ((float)debug_heap_size) / (float)(1 << 20);
|
||||
float gap_size_mb = ((float)DEBUG_HEAP_START - GLOBAL_HEAP_END) / (float)(1 << 20);
|
||||
printf("gkernel: debug heap - 0x%x to 0x%x (size %.3f MB, gap %.3f MB)\n", DEBUG_HEAP_START,
|
||||
debug_heap_end, debug_size_mb, gap_size_mb);
|
||||
spdlog::info("gkernel: global heap 0x{} to 0x{} (size {} MB, gap {} MB)", DEBUG_HEAP_START,
|
||||
debug_heap_end, debug_size_mb, gap_size_mb);
|
||||
} else {
|
||||
// if no debug, we make the kheapinfo structure NULL so GOAL knows not to use it.
|
||||
kdebugheap.offset = 0;
|
||||
@@ -338,9 +338,9 @@ int InitMachine() {
|
||||
InitGoalProto();
|
||||
}
|
||||
|
||||
printf("InitSound\n");
|
||||
spdlog::info("InitSound");
|
||||
InitSound(); // do nothing!
|
||||
printf("InitRPC\n");
|
||||
spdlog::info("InitRPC");
|
||||
InitRPC(); // connect to IOP
|
||||
reset_output(); // reset output buffers
|
||||
clear_print();
|
||||
@@ -350,9 +350,9 @@ int InitMachine() {
|
||||
return goal_status;
|
||||
}
|
||||
|
||||
printf("InitListenerConnect\n");
|
||||
spdlog::info("InitListenerConnect");
|
||||
InitListenerConnect();
|
||||
printf("InitCheckListener\n");
|
||||
spdlog::info("InitCheckListener");
|
||||
InitCheckListener();
|
||||
Msg(6, "kernel: machine started\n");
|
||||
return 0;
|
||||
@@ -619,7 +619,7 @@ void InitMachineScheme() {
|
||||
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE).cast<u32>()),
|
||||
make_string_from_c("common"), kernel_packages->value);
|
||||
|
||||
printf("calling play!\n");
|
||||
spdlog::info("calling fake play~");
|
||||
call_goal_function_by_name("play");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "common/symbols.h"
|
||||
#include "common/versions.h"
|
||||
#include "common/goal_constants.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
|
||||
//! Controls link mode when EnableMethodSet = 0, MasterDebug = 1, DiskBoot = 0. Will enable a
|
||||
//! warning message if EnableMethodSet = 1
|
||||
@@ -1895,8 +1896,10 @@ s32 InitHeapAndSymbol() {
|
||||
(kernel_version >> 3) & 0xffff);
|
||||
return -1;
|
||||
} else {
|
||||
printf("Got correct kernel version %d.%d\n", kernel_version >> 0x13,
|
||||
(kernel_version >> 3) & 0xffff);
|
||||
spdlog::info("Got correct kernel version {}.{}", kernel_version >> 0x13,
|
||||
(kernel_version >> 3) & 0xffff);
|
||||
// printf("Got correct kernel version %d.%d\n", kernel_version >> 0x13,
|
||||
// (kernel_version >> 3) & 0xffff);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -5,14 +5,18 @@
|
||||
#include <cstdio>
|
||||
#include "runtime.h"
|
||||
#include "common/versions.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "third-party/spdlog/include/spdlog/sinks/basic_file_sink.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
while (true) {
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
// run the runtime in a loop so we can reset the game and have it restart cleanly
|
||||
printf("gk %d.%d\n", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR);
|
||||
spdlog::info("gk {}.{} OK!\n", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR);
|
||||
|
||||
if (exec_runtime(argc, argv) == 2) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "isocommon.h"
|
||||
#include "overlord.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
|
||||
using namespace iop;
|
||||
|
||||
@@ -215,7 +216,8 @@ uint32_t FS_GetLength(FileRecord* fr) {
|
||||
* This is an ISO FS API Function
|
||||
*/
|
||||
LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
|
||||
printf("[OVERLORD] FS Open %s\n", fr->name); // Added
|
||||
// printf("[OVERLORD] FS Open %s\n", fr->name); // Added
|
||||
spdlog::debug("[OVERLORD] FS Open {}", fr->name);
|
||||
LoadStackEntry* selected = nullptr;
|
||||
// find first unused spot on load stack.
|
||||
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
|
||||
@@ -229,7 +231,8 @@ LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
printf("[OVERLORD ISO CD] Failed to FS_Open %s\n", fr->name);
|
||||
// printf("[OVERLORD ISO CD] Failed to FS_Open %s\n", fr->name);
|
||||
spdlog::warn("[OVERLORD] Failed to FS Open {}", fr->name);
|
||||
ExitIOP();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -240,7 +243,8 @@ LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
|
||||
* This is an ISO FS API Function
|
||||
*/
|
||||
LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
|
||||
printf("[OVERLORD] FS Open %s\n", fr->name); // Added
|
||||
// printf("[OVERLORD] FS Open %s\n", fr->name); // Added
|
||||
spdlog::debug("[OVERLORD] FS_OpenWad {}", fr->name);
|
||||
LoadStackEntry* selected = nullptr;
|
||||
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
|
||||
if (!sLoadStack[i].fr) {
|
||||
@@ -250,7 +254,8 @@ LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
printf("[OVERLORD ISO CD] Failed to FS_OpenWad %s\n", fr->name);
|
||||
// printf("[OVERLORD ISO CD] Failed to FS_OpenWad %s\n", fr->name);
|
||||
spdlog::warn("[OVERLORD] Failed to FS_OpenWad {}", fr->name);
|
||||
ExitIOP();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -260,7 +265,8 @@ LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
|
||||
* This is an ISO FS API Function
|
||||
*/
|
||||
void FS_Close(LoadStackEntry* fd) {
|
||||
printf("[OVERLORD] FS Close %s\n", fd->fr->name);
|
||||
// printf("[OVERLORD] FS Close %s\n", fd->fr->name);
|
||||
spdlog::debug("[OVERLORD] FS_Close {}", fd->fr->name);
|
||||
|
||||
// close the FD
|
||||
fd->fr = nullptr;
|
||||
@@ -279,7 +285,8 @@ uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) {
|
||||
int32_t real_size = len;
|
||||
if (len < 0) {
|
||||
// not sure what this is about...
|
||||
printf("[OVERLORD ISO CD] negative length warning!\n");
|
||||
// printf("[OVERLORD ISO CD] negative length warning!\n");
|
||||
spdlog::warn("[OVERLORD ISO CD] Negative length warning!");
|
||||
real_size = len + 0x7ff;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* This is a huge mess
|
||||
*/
|
||||
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include <assert.h>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
@@ -492,9 +493,11 @@ u32 RunDGOStateMachine(IsoMessage* _cmd, IsoBufferHeader* buffer) {
|
||||
|
||||
// if we are done with header
|
||||
if (cmd->bytes_processed == sizeof(DgoHeader)) {
|
||||
printf("[Overlord DGO] Got DGO file header for %s with %d objects\n",
|
||||
cmd->dgo_header.name,
|
||||
cmd->dgo_header.object_count); // added
|
||||
// printf("[Overlord DGO] Got DGO file header for %s with %d objects\n",
|
||||
// cmd->dgo_header.name,
|
||||
// cmd->dgo_header.object_count); // added
|
||||
spdlog::info("[Overlord DGO] Got DGO file header for {} with {} objects",
|
||||
cmd->dgo_header.name, cmd->dgo_header.object_count);
|
||||
cmd->bytes_processed = 0;
|
||||
cmd->objects_loaded = 0;
|
||||
if (cmd->dgo_header.object_count == 1) {
|
||||
@@ -923,4 +926,4 @@ void CancelDGO(RPC_Dgo_Cmd* cmd) {
|
||||
// TODO - VAG_MarkLoopStart
|
||||
// TODO - VAG_MarkLoopEnd
|
||||
// TODO - VAG_MarkNonloopStart
|
||||
// TODO - VAG_MarkNonloopEnd
|
||||
// TODO - VAG_MarkNonloopEnd
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "iso_api.h"
|
||||
#include "game/sce/iop.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
|
||||
using namespace iop;
|
||||
|
||||
@@ -7,7 +8,8 @@ using namespace iop;
|
||||
* Load a File to IOP memory (blocking)
|
||||
*/
|
||||
void LoadISOFileToIOP(FileRecord* file, void* addr, uint32_t length) {
|
||||
printf("[OVERLORD] LoadISOFileToIOP %s, %d/%d bytes\n", file->name, length, file->size);
|
||||
// printf("[OVERLORD] LoadISOFileToIOP %s, %d/%d bytes\n", file->name, length, file->size);
|
||||
spdlog::debug("[OVERLORD] LoadISOFileToIOP {}, {}/{} bytes", file->name, length, file->size);
|
||||
IsoCommandLoadSingle cmd;
|
||||
cmd.cmd_id = LOAD_TO_IOP_CMD_ID;
|
||||
cmd.messagebox_to_reply = 0;
|
||||
@@ -27,7 +29,8 @@ void LoadISOFileToIOP(FileRecord* file, void* addr, uint32_t length) {
|
||||
* Load a File to IOP memory (blocking)
|
||||
*/
|
||||
void LoadISOFileToEE(FileRecord* file, uint32_t addr, uint32_t length) {
|
||||
printf("[OVERLORD] LoadISOFileToEE %s, %d/%d bytes\n", file->name, length, file->size);
|
||||
// printf("[OVERLORD] LoadISOFileToEE %s, %d/%d bytes\n", file->name, length, file->size);
|
||||
spdlog::debug("[OVERLORD] LoadISOFileToEE {}, {}/{} bytes", file->name, length, file->size);
|
||||
IsoCommandLoadSingle cmd;
|
||||
cmd.cmd_id = LOAD_TO_EE_CMD_ID;
|
||||
cmd.messagebox_to_reply = 0;
|
||||
@@ -41,4 +44,4 @@ void LoadISOFileToEE(FileRecord* file, uint32_t addr, uint32_t length) {
|
||||
if (cmd.status) {
|
||||
cmd.length_to_copy = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-14
@@ -68,7 +68,7 @@ void deci2_runner(SystemThreadInterface& iface) {
|
||||
iface.initialization_complete();
|
||||
|
||||
// in our own thread, wait for the EE to register the first protocol driver
|
||||
printf("[DECI2] waiting for EE to register protos\n");
|
||||
spdlog::debug("[DECI2] Waiting for EE to register protos");
|
||||
server.wait_for_protos_ready();
|
||||
// then allow the server to accept connections
|
||||
if (!server.init()) {
|
||||
@@ -76,11 +76,13 @@ void deci2_runner(SystemThreadInterface& iface) {
|
||||
}
|
||||
|
||||
printf("[DECI2] waiting for listener...\n");
|
||||
// spdlog::debug("[DECI2] Waiting for listener..."); --> disabled temporarily, some weird race
|
||||
// condition?
|
||||
bool saw_listener = false;
|
||||
while (!iface.get_want_exit()) {
|
||||
if (server.check_for_listener()) {
|
||||
if (!saw_listener) {
|
||||
printf("[DECI2] Connected!\n");
|
||||
spdlog::debug("[DECI2] Connected!");
|
||||
}
|
||||
saw_listener = true;
|
||||
// we have a listener, run!
|
||||
@@ -117,19 +119,19 @@ void ee_runner(SystemThreadInterface& iface) {
|
||||
}
|
||||
|
||||
if (g_ee_main_mem == (u8*)(-1)) {
|
||||
printf(" Failed to initialize main memory! %s\n", strerror(errno));
|
||||
spdlog::debug("Failed to initialize main memory! {}", strerror(errno));
|
||||
iface.initialization_complete();
|
||||
return;
|
||||
}
|
||||
|
||||
printf(" Main memory mapped at 0x%016llx\n", (u64)(g_ee_main_mem));
|
||||
printf(" Main memory size 0x%x bytes (%.3f MB)\n", EE_MAIN_MEM_SIZE,
|
||||
(double)EE_MAIN_MEM_SIZE / (1 << 20));
|
||||
spdlog::debug("Main memory mapped at 0x{:016x}", (u64)(g_ee_main_mem));
|
||||
spdlog::debug("Main memory size 0x{} bytes ({} MB)", EE_MAIN_MEM_SIZE,
|
||||
(double)EE_MAIN_MEM_SIZE / (1 << 20));
|
||||
|
||||
printf("[EE] Initialization complete!\n");
|
||||
spdlog::debug("[EE] Initialization complete!");
|
||||
iface.initialization_complete();
|
||||
|
||||
printf("[EE] Run!\n");
|
||||
spdlog::debug("[EE] Run!");
|
||||
memset((void*)g_ee_main_mem, 0, EE_MAIN_MEM_SIZE);
|
||||
|
||||
// prevent access to the first 1 MB of memory.
|
||||
@@ -151,7 +153,7 @@ void ee_runner(SystemThreadInterface& iface) {
|
||||
kprint_init_globals();
|
||||
|
||||
goal_main(g_argc, g_argv);
|
||||
printf("[EE] Done!\n");
|
||||
spdlog::debug("[EE] Done!");
|
||||
|
||||
// // kill the IOP todo
|
||||
iop::LIBRARY_kill();
|
||||
@@ -167,7 +169,7 @@ void ee_runner(SystemThreadInterface& iface) {
|
||||
*/
|
||||
void iop_runner(SystemThreadInterface& iface) {
|
||||
IOP iop;
|
||||
printf("[IOP] Restart!\n");
|
||||
spdlog::debug("[IOP] Restart!");
|
||||
iop.reset_allocator();
|
||||
ee::LIBRARY_sceSif_register(&iop);
|
||||
iop::LIBRARY_register(&iop);
|
||||
@@ -190,12 +192,12 @@ void iop_runner(SystemThreadInterface& iface) {
|
||||
|
||||
iface.initialization_complete();
|
||||
|
||||
printf("[IOP] Wait for OVERLORD to be started...\n");
|
||||
spdlog::debug("[IOP] Wait for OVERLORD to start...");
|
||||
iop.wait_for_overlord_start_cmd();
|
||||
if (iop.status == IOP_OVERLORD_INIT) {
|
||||
printf("[IOP] Run!\n");
|
||||
spdlog::debug("[IOP] Run!");
|
||||
} else {
|
||||
printf("[IOP] shutdown!\n");
|
||||
spdlog::debug("[IOP] Shutdown!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -257,6 +259,7 @@ u32 exec_runtime(int argc, char** argv) {
|
||||
|
||||
// join and exit
|
||||
tm.join();
|
||||
printf("GOAL Runtime Shutdown (code %d)\n", MasterExit);
|
||||
// printf("GOAL Runtime Shutdown (code %d)\n", MasterExit);
|
||||
spdlog::info("GOAL Runtime Shutdown (code {})", MasterExit);
|
||||
return MasterExit;
|
||||
}
|
||||
|
||||
+2
-1
@@ -69,7 +69,8 @@ s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param,
|
||||
drv.id = protocol_count + 1;
|
||||
drv.active = true;
|
||||
protocols[protocol_count++] = drv;
|
||||
printf("[DECI2] Add new protocol driver %d for 0x%x\n", drv.id, drv.protocol);
|
||||
// printf("[DECI2] Add new protocol driver %d for 0x%x\n", drv.id, drv.protocol);
|
||||
spdlog::info("[DECI2] Add new protocol driver {} for 0x{}", drv.id, drv.protocol);
|
||||
server->unlock();
|
||||
|
||||
if (protocol_count == 1) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define JAK1_IOP_H
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
|
||||
#define SMEM_Low (0)
|
||||
#define SMEM_High (1)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include "game/system/deci_common.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
|
||||
class Deci2Server {
|
||||
public:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#endif
|
||||
|
||||
#include "SystemThread.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
|
||||
//////////////////////
|
||||
// Thread Manager //
|
||||
@@ -13,7 +14,8 @@
|
||||
*/
|
||||
SystemThread& SystemThreadManager::create_thread(const std::string& name) {
|
||||
if (thread_count >= MAX_SYSTEM_THREADS) {
|
||||
throw std::runtime_error("Out of System Threads! Please increase MAX_SYSTEM_THREADS");
|
||||
spdlog::critical("Out of System Threads! MAX_SYSTEM_THREADS is ", MAX_SYSTEM_THREADS);
|
||||
throw std::runtime_error("Out of System Threads! Please increase MAX_SYSTEM_THREADS");
|
||||
}
|
||||
auto& thread = threads[thread_count];
|
||||
|
||||
@@ -49,7 +51,7 @@ void SystemThreadManager::print_stats() {
|
||||
*/
|
||||
void SystemThreadManager::shutdown() {
|
||||
for (int i = 0; i < thread_count; i++) {
|
||||
printf("# Stop %s\n", threads[i].name.c_str());
|
||||
spdlog::debug("# Stop {}", threads[i].name.c_str());
|
||||
threads[i].stop();
|
||||
}
|
||||
}
|
||||
@@ -59,7 +61,7 @@ void SystemThreadManager::shutdown() {
|
||||
*/
|
||||
void SystemThreadManager::join() {
|
||||
for (int i = 0; i < thread_count; i++) {
|
||||
printf("# Join %s\n", threads[i].name.c_str());
|
||||
spdlog::debug(" # Join {}", threads[i].name.c_str());
|
||||
if (threads[i].running) {
|
||||
threads[i].join();
|
||||
}
|
||||
@@ -73,7 +75,7 @@ void* bootstrap_thread_func(void* x) {
|
||||
SystemThread* thd = (SystemThread*)x;
|
||||
SystemThreadInterface iface(thd);
|
||||
thd->function(iface);
|
||||
printf("[SYSTEM] Thread %s is returning\n", thd->name.c_str());
|
||||
spdlog::debug("[SYSTEM] Thread {} is returning", thd->name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -81,7 +83,8 @@ void* bootstrap_thread_func(void* x) {
|
||||
* Start a thread and wait for its initialization
|
||||
*/
|
||||
void SystemThread::start(std::function<void(SystemThreadInterface&)> f) {
|
||||
printf("# Initialize %s...\n", name.c_str());
|
||||
spdlog::debug("# Initialize {}...", name.c_str());
|
||||
|
||||
function = f;
|
||||
thread = std::thread(bootstrap_thread_func, this);
|
||||
running = true;
|
||||
@@ -118,7 +121,7 @@ void SystemThreadInterface::initialization_complete() {
|
||||
std::unique_lock<std::mutex> mlk(thread.initialization_mutex);
|
||||
thread.initialization_complete = true;
|
||||
thread.initialization_cv.notify_all();
|
||||
printf("# %s initialized\n", thread.name.c_str());
|
||||
spdlog::debug("# {} initialized", thread.name.c_str());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -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;
|
||||
|
||||
+17
-15
@@ -2,21 +2,23 @@ include("goalc/CMakeLists.txt")
|
||||
|
||||
add_executable(goalc-test
|
||||
test_main.cpp
|
||||
#test_test.cpp
|
||||
#test_reader.cpp
|
||||
#test_goos.cpp
|
||||
#test_listener_deci2.cpp
|
||||
#test_kernel.cpp
|
||||
#all_jak1_symbols.cpp
|
||||
#test_type_system.cpp
|
||||
#test_CodeTester.cpp
|
||||
#test_emitter_slow.cpp
|
||||
#test_emitter_loads_and_store.cpp
|
||||
#test_emitter_xmm32.cpp
|
||||
#test_emitter_integer_math.cpp
|
||||
#test_common_util.cpp
|
||||
${GOALC_TEST_FRAMEWORK_SOURCES}
|
||||
${GOALC_TEST_CASES})
|
||||
test_test.cpp
|
||||
test_reader.cpp
|
||||
test_goos.cpp
|
||||
test_listener_deci2.cpp
|
||||
test_kernel.cpp
|
||||
all_jak1_symbols.cpp
|
||||
test_type_system.cpp
|
||||
test_CodeTester.cpp
|
||||
test_emitter_slow.cpp
|
||||
test_emitter_loads_and_store.cpp
|
||||
test_emitter_xmm32.cpp
|
||||
test_emitter_integer_math.cpp
|
||||
test_common_util.cpp
|
||||
test_compiler_and_runtime.cpp
|
||||
test_deftype.cpp
|
||||
${GOALC_TEST_FRAMEWORK_SOURCES}
|
||||
${GOALC_TEST_CASES})
|
||||
|
||||
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, 80);
|
||||
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);
|
||||
}
|
||||
+1
Submodule third-party/spdlog added at cbe9448650
Reference in New Issue
Block a user