diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index 59ceb6bb53..e9319b0641 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -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 diff --git a/.gitmodules b/.gitmodules index 522084739f..4edf24cdc1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "third-party/googletest"] path = third-party/googletest - url = https://github.com/google/googletest.git \ No newline at end of file + url = https://github.com/google/googletest.git +[submodule "third-party/spdlog"] + path = third-party/spdlog + url = https://github.com/gabime/spdlog.git diff --git a/CMakeLists.txt b/CMakeLists.txt index fd0fdbd80e..8be1de52d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/common/goos/CMakeLists.txt b/common/goos/CMakeLists.txt index 4347c0d3a1..cc8c4fe041 100644 --- a/common/goos/CMakeLists.txt +++ b/common/goos/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/common/goos/InterpreterEval.cpp b/common/goos/InterpreterEval.cpp index 226bda29a5..9bbbaf0016 100644 --- a/common/goos/InterpreterEval.cpp +++ b/common/goos/InterpreterEval.cpp @@ -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 \ No newline at end of file +} // namespace goos diff --git a/common/goos/Object.cpp b/common/goos/Object.cpp index 99b66d5dca..69ee9d22b1 100644 --- a/common/goos/Object.cpp +++ b/common/goos/Object.cpp @@ -4,6 +4,9 @@ namespace goos { std::shared_ptr gEmptyList = nullptr; +std::shared_ptr& get_empty_list() { + return gEmptyList; +} /*! * Convert type to string (name in brackets) diff --git a/common/goos/Object.h b/common/goos/Object.h index a9bd7796bd..8e50ae9e97 100644 --- a/common/goos/Object.h +++ b/common/goos/Object.h @@ -319,7 +319,7 @@ class Object { // There is a single heap allocated EmptyListObject. class EmptyListObject; -extern std::shared_ptr gEmptyList; +std::shared_ptr& 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(); + if (!get_empty_list()) { + get_empty_list() = std::make_shared(); } - obj.heap_obj = gEmptyList; + obj.heap_obj = get_empty_list(); return obj; } diff --git a/common/goos/PrettyPrinter.cpp b/common/goos/PrettyPrinter.cpp new file mode 100644 index 0000000000..6bae9a4b59 --- /dev/null +++ b/common/goos/PrettyPrinter.cpp @@ -0,0 +1,567 @@ +#include +#include +#include +#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* 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 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 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 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 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& 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& symbols) { + if (symbols.empty()) { + return goos::EmptyListObject::make_new(); + } + std::vector 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 \ No newline at end of file diff --git a/common/goos/PrettyPrinter.h b/common/goos/PrettyPrinter.h new file mode 100644 index 0000000000..c971d3748d --- /dev/null +++ b/common/goos/PrettyPrinter.h @@ -0,0 +1,49 @@ +/*! + * @file PrettyPrinter.h + * A Pretty Printer for GOOS. + */ + +#pragma once +#include +#include +#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& 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& symbols); + +// fancy wrapper functions. Due to template magic these can call each other +// and accept mixed arguments! + +template +goos::Object build_list(const std::string& str, Args... rest) { + return goos::PairObject::make_new(to_symbol(str), build_list(rest...)); +} + +template +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 diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp index 3b0405ebed..691865ece0 100644 --- a/common/type_system/deftype.cpp +++ b/common/type_system/deftype.cpp @@ -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; diff --git a/common/type_system/deftype.h b/common/type_system/deftype.h index 13872693da..151601be37 100644 --- a/common/type_system/deftype.h +++ b/common/type_system/deftype.h @@ -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); diff --git a/decomp.sh b/decomp.sh index 947ec9bf78..69cbe661a9 100755 --- a/decomp.sh +++ b/decomp.sh @@ -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 \ No newline at end of file +$DIR/build/decompiler/decompiler $DIR/decompiler/config/jak1_ntsc_black_label.jsonc $DIR/iso_data $DIR/decompiler_out diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index eca54d7d2f..211efaaa62 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -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) \ No newline at end of file + common_util + type_system + fmt) \ No newline at end of file diff --git a/decompiler/Disasm/Instruction.h b/decompiler/Disasm/Instruction.h index ca3ab99a37..f3ccaf9699 100644 --- a/decompiler/Disasm/Instruction.h +++ b/decompiler/Disasm/Instruction.h @@ -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; diff --git a/decompiler/Disasm/InstructionMatching.h b/decompiler/Disasm/InstructionMatching.h index 3fe9232e34..7d67751eac 100644 --- a/decompiler/Disasm/InstructionMatching.h +++ b/decompiler/Disasm/InstructionMatching.h @@ -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 diff --git a/decompiler/Disasm/OpcodeInfo.cpp b/decompiler/Disasm/OpcodeInfo.cpp index 102493bb26..4ae837be23 100644 --- a/decompiler/Disasm/OpcodeInfo.cpp +++ b/decompiler/Disasm/OpcodeInfo.cpp @@ -1,3 +1,8 @@ +/*! + * @file OpcodeInfo.cpp + * Decoding info for each opcode. + */ + #include "OpcodeInfo.h" #include diff --git a/decompiler/Function/BasicBlocks.h b/decompiler/Function/BasicBlocks.h index 42b1a1fedc..38eefd2f45 100644 --- a/decompiler/Function/BasicBlocks.h +++ b/decompiler/Function/BasicBlocks.h @@ -1,8 +1,5 @@ #pragma once -#ifndef JAK_DISASSEMBLER_BASICBLOCKS_H -#define JAK_DISASSEMBLER_BASICBLOCKS_H - #include #include @@ -21,5 +18,3 @@ struct BasicBlock { std::vector find_blocks_in_function(const LinkedObjectFile& file, int seg, const Function& func); - -#endif // JAK_DISASSEMBLER_BASICBLOCKS_H diff --git a/decompiler/Function/CfgVtx.cpp b/decompiler/Function/CfgVtx.cpp index e7531f8d12..b95b134276 100644 --- a/decompiler/Function/CfgVtx.cpp +++ b/decompiler/Function/CfgVtx.cpp @@ -140,8 +140,8 @@ std::string BlockVtx::to_string() { } } -std::shared_ptr
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 SequenceVtx::to_form() { - std::vector> forms; - forms.push_back(toForm("seq")); +goos::Object SequenceVtx::to_form() { + std::vector 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 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 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 CondWithElse::to_form() { - std::vector> forms; - forms.push_back(toForm("cond")); +goos::Object CondWithElse::to_form() { + std::vector forms; + forms.push_back(pretty_print::to_symbol("cond")); for (const auto& x : entries) { - std::vector> e = {x.condition->to_form(), x.body->to_form()}; - forms.push_back(buildList(e)); + std::vector e = {x.condition->to_form(), x.body->to_form()}; + forms.push_back(pretty_print::build_list(e)); } - std::vector> e = {toForm("else"), else_vtx->to_form()}; - forms.push_back(buildList(e)); - return buildList(forms); + std::vector 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 CondNoElse::to_form() { - std::vector> forms; - forms.push_back(toForm("cond")); +goos::Object CondNoElse::to_form() { + std::vector forms; + forms.push_back(pretty_print::to_symbol("cond")); for (const auto& x : entries) { - std::vector> e = {x.condition->to_form(), x.body->to_form()}; - forms.push_back(buildList(e)); + std::vector 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 WhileLoop::to_form() { - std::vector> forms = {toForm("while"), condition->to_form(), - body->to_form()}; - return buildList(forms); +goos::Object WhileLoop::to_form() { + std::vector 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 UntilLoop::to_form() { - std::vector> forms = {toForm("until"), condition->to_form(), - body->to_form()}; - return buildList(forms); +goos::Object UntilLoop::to_form() { + std::vector 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 UntilLoop_single::to_form() { - std::vector> forms = {toForm("until1"), block->to_form()}; - return buildList(forms); +goos::Object UntilLoop_single::to_form() { + std::vector 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 InfiniteLoopBlock::to_form() { - std::vector> forms = {toForm("inf-loop"), block->to_form()}; - return buildList(forms); +goos::Object InfiniteLoopBlock::to_form() { + std::vector 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 ShortCircuit::to_form() { - std::vector> forms; - forms.push_back(toForm("sc")); +goos::Object ShortCircuit::to_form() { + std::vector 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 IfElseVtx::to_form() { - std::vector> forms = {toForm("if"), condition->to_form(), +goos::Object IfElseVtx::to_form() { + std::vector 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 GotoEnd::to_form() { - std::vector> forms = {toForm("return-from-function"), body->to_form(), - unreachable_block->to_form()}; - return buildList(forms); +goos::Object GotoEnd::to_form() { + std::vector 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 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> forms = {toForm("ungrouped")}; + std::vector 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 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& blocks) { * Build and resolve a Control Flow Graph as much as possible. */ std::shared_ptr 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(); const auto& blocks = cfg->create_blocks(func.basic_blocks.size()); @@ -1716,13 +1714,6 @@ std::shared_ptr 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 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(); diff --git a/decompiler/Function/CfgVtx.h b/decompiler/Function/CfgVtx.h index a19abb30cf..e3f46eb55f 100644 --- a/decompiler/Function/CfgVtx.h +++ b/decompiler/Function/CfgVtx.h @@ -6,7 +6,7 @@ #include #include #include -#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& 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 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 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 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 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 to_form() override; + goos::Object to_form() override; std::vector seq; }; @@ -169,7 +169,7 @@ class SequenceVtx : public CfgVtx { class CondWithElse : public CfgVtx { public: std::string to_string() override; - std::shared_ptr 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 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 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 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 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 to_form() override; + goos::Object to_form() override; std::vector entries; }; class InfiniteLoopBlock : public CfgVtx { public: std::string to_string() override; - std::shared_ptr to_form() override; + goos::Object to_form() override; CfgVtx* block; }; class GotoEnd : public CfgVtx { public: std::string to_string() override; - std::shared_ptr 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 to_form(); + goos::Object to_form(); std::string to_form_string(); std::string to_dot(); int get_top_level_vertices_count(); diff --git a/decompiler/Function/Function.cpp b/decompiler/Function/Function.cpp index cba5ecf577..96233104fb 100644 --- a/decompiler/Function/Function.cpp +++ b/decompiler/Function/Function.cpp @@ -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 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 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 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(x.get())) { + count++; + } + } + return count; } \ No newline at end of file diff --git a/decompiler/Function/Function.h b/decompiler/Function/Function.h index 179e51c901..156d1b8e14 100644 --- a/decompiler/Function/Function.h +++ b/decompiler/Function/Function.h @@ -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 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 get_basic_op_at_instr(int idx); + int get_basic_op_count(); + int get_failed_basic_op_count(); + + std::shared_ptr ir = nullptr; int segment = -1; int start_word = -1; @@ -115,6 +126,9 @@ class Function { private: void check_epilogue(const LinkedObjectFile& file); + std::vector> basic_ops; + std::unordered_map instruction_to_basic_op; + std::unordered_map basic_op_to_instruction; }; #endif // NEXT_FUNCTION_H diff --git a/decompiler/IR/BasicOpBuilder.cpp b/decompiler/IR/BasicOpBuilder.cpp new file mode 100644 index 0000000000..7549650503 --- /dev/null +++ b/decompiler/IR/BasicOpBuilder.cpp @@ -0,0 +1,1506 @@ +#include "BasicOpBuilder.h" +#include "decompiler/Function/Function.h" +#include "decompiler/Function/BasicBlocks.h" +#include "decompiler/Disasm/InstructionMatching.h" + +namespace { + +std::shared_ptr make_set(IR_Set::Kind kind, + const std::shared_ptr& dst, + const std::shared_ptr& src) { + return std::make_shared(kind, dst, src); +} + +std::shared_ptr make_reg(Register reg, int idx) { + return std::make_shared(reg, idx); +} + +std::shared_ptr make_sym(const std::string& name) { + return std::make_shared(name); +} + +std::shared_ptr make_sym_value(const std::string& name) { + return std::make_shared(name); +} + +std::shared_ptr make_int(int64_t x) { + return std::make_shared(x); +} + +std::shared_ptr try_or(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::OR, {}, make_gpr(Reg::S7), make_gpr(Reg::R0))) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), make_sym("#f")); + } else if (is_gpr_3(instr, InstructionKind::OR, {}, {}, make_gpr(Reg::R0))) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + make_reg(instr.get_src(0).get_reg(), idx)); + } else { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::OR, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_ori(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::ORI && instr.get_src(0).is_reg(make_gpr(Reg::R0)) && + instr.get_src(1).is_imm()) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + make_int(instr.get_src(1).get_imm())); + } else if (instr.kind == InstructionKind::ORI && instr.get_src(1).is_imm()) { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::OR, make_reg(instr.get_src(0).get_reg(), idx), + make_int(instr.get_src(1).get_imm()))); + } + return nullptr; +} + +std::shared_ptr try_por(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::POR, {}, {}, make_gpr(Reg::R0))) { + return make_set(IR_Set::REG_I128, make_reg(instr.get_dst(0).get_reg(), idx), + make_reg(instr.get_src(0).get_reg(), idx)); + } + return nullptr; +} + +std::shared_ptr try_mtc1(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::MTC1) { + return make_set(IR_Set::GPR_TO_FPR, make_reg(instr.get_dst(0).get_reg(), idx), + make_reg(instr.get_src(0).get_reg(), idx)); + } + return nullptr; +} + +std::shared_ptr try_mfc1(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::MFC1) { + return make_set(IR_Set::FPR_TO_GPR64, make_reg(instr.get_dst(0).get_reg(), idx), + make_reg(instr.get_src(0).get_reg(), idx)); + } + return nullptr; +} + +std::shared_ptr try_lwc1(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LWC1 && instr.get_dst(0).is_reg() && + instr.get_src(0).is_link_or_label() && instr.get_src(1).is_reg(make_gpr(Reg::FP))) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::FLOAT, 4, std::make_shared(instr.get_src(0).get_label()))); + } else if (instr.kind == InstructionKind::LWC1 && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm() && instr.get_src(0).get_imm() == 0) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_Load::FLOAT, 4, make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::LWC1 && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm()) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::FLOAT, 4, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(1).get_reg(), idx), + std::make_shared(instr.get_src(0).get_imm())))); + } + return nullptr; +} + +std::shared_ptr try_lhu(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LHU && instr.get_dst(0).is_reg() && + instr.get_src(0).is_link_or_label() && instr.get_src(1).is_reg(make_gpr(Reg::FP))) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 2, + std::make_shared(instr.get_src(0).get_label()))); + } else if (instr.kind == InstructionKind::LHU && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm() && instr.get_src(0).get_imm() == 0) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_Load::UNSIGNED, 2, make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::LHU && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm()) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 2, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(1).get_reg(), idx), + std::make_shared(instr.get_src(0).get_imm())))); + } + return nullptr; +} + +std::shared_ptr try_lh(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LH && instr.get_dst(0).is_reg() && + instr.get_src(0).is_link_or_label() && instr.get_src(1).is_reg(make_gpr(Reg::FP))) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::SIGNED, 2, std::make_shared(instr.get_src(0).get_label()))); + } else if (instr.kind == InstructionKind::LH && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm() && instr.get_src(0).get_imm() == 0) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_Load::SIGNED, 2, make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::LH && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm()) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::SIGNED, 2, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(1).get_reg(), idx), + std::make_shared(instr.get_src(0).get_imm())))); + } + return nullptr; +} + +std::shared_ptr try_lb(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LB && instr.get_dst(0).is_reg() && + instr.get_src(0).is_link_or_label() && instr.get_src(1).is_reg(make_gpr(Reg::FP))) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::SIGNED, 1, std::make_shared(instr.get_src(0).get_label()))); + } else if (instr.kind == InstructionKind::LB && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm() && instr.get_src(0).get_imm() == 0) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_Load::SIGNED, 1, make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::LB && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm()) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::SIGNED, 1, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(1).get_reg(), idx), + std::make_shared(instr.get_src(0).get_imm())))); + } + return nullptr; +} + +std::shared_ptr try_lbu(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LBU && instr.get_dst(0).is_reg() && + instr.get_src(0).is_link_or_label() && instr.get_src(1).is_reg(make_gpr(Reg::FP))) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 1, + std::make_shared(instr.get_src(0).get_label()))); + } else if (instr.kind == InstructionKind::LBU && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm() && instr.get_src(0).get_imm() == 0) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_Load::UNSIGNED, 1, make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::LBU && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm()) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 1, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(1).get_reg(), idx), + std::make_shared(instr.get_src(0).get_imm())))); + } + return nullptr; +} + +std::shared_ptr try_lwu(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LWU && instr.get_dst(0).is_reg() && + instr.get_src(0).is_link_or_label() && instr.get_src(1).is_reg(make_gpr(Reg::FP))) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 4, + std::make_shared(instr.get_src(0).get_label()))); + } else if (instr.kind == InstructionKind::LWU && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm() && instr.get_src(0).get_imm() == 0) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_Load::UNSIGNED, 4, make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::LWU && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm()) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 4, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(1).get_reg(), idx), + std::make_shared(instr.get_src(0).get_imm())))); + } + return nullptr; +} + +std::shared_ptr try_ld(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LD && instr.get_dst(0).is_reg() && + instr.get_src(0).is_link_or_label() && instr.get_src(1).is_reg(make_gpr(Reg::FP))) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 8, + std::make_shared(instr.get_src(0).get_label()))); + } else if (instr.kind == InstructionKind::LD && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm() && instr.get_src(0).get_imm() == 0) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_Load::UNSIGNED, 8, make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::LD && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm()) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 8, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(1).get_reg(), idx), + std::make_shared(instr.get_src(0).get_imm())))); + } + return nullptr; +} + +std::shared_ptr try_dsll(Instruction& instr, int idx) { + if (is_gpr_2_imm_int(instr, InstructionKind::DSLL, {}, {}, {})) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::LEFT_SHIFT, + make_reg(instr.get_src(0).get_reg(), idx), + make_int(instr.get_src(1).get_imm()))); + } + return nullptr; +} + +std::shared_ptr try_dsll32(Instruction& instr, int idx) { + if (is_gpr_2_imm_int(instr, InstructionKind::DSLL32, {}, {}, {})) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::LEFT_SHIFT, + make_reg(instr.get_src(0).get_reg(), idx), + make_int(32 + instr.get_src(1).get_imm()))); + } + return nullptr; +} + +std::shared_ptr try_dsra(Instruction& instr, int idx) { + if (is_gpr_2_imm_int(instr, InstructionKind::DSRA, {}, {}, {})) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::RIGHT_SHIFT_ARITH, + make_reg(instr.get_src(0).get_reg(), idx), + make_int(instr.get_src(1).get_imm()))); + } + return nullptr; +} + +std::shared_ptr try_dsra32(Instruction& instr, int idx) { + if (is_gpr_2_imm_int(instr, InstructionKind::DSRA32, {}, {}, {})) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::RIGHT_SHIFT_ARITH, + make_reg(instr.get_src(0).get_reg(), idx), + make_int(32 + instr.get_src(1).get_imm()))); + } + return nullptr; +} + +std::shared_ptr try_dsrl(Instruction& instr, int idx) { + if (is_gpr_2_imm_int(instr, InstructionKind::DSRL, {}, {}, {})) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::RIGHT_SHIFT_LOGIC, + make_reg(instr.get_src(0).get_reg(), idx), + make_int(instr.get_src(1).get_imm()))); + } + return nullptr; +} + +std::shared_ptr try_dsrl32(Instruction& instr, int idx) { + if (is_gpr_2_imm_int(instr, InstructionKind::DSRL32, {}, {}, {})) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::RIGHT_SHIFT_LOGIC, + make_reg(instr.get_src(0).get_reg(), idx), + make_int(32 + instr.get_src(1).get_imm()))); + } + return nullptr; +} + +std::shared_ptr try_float_math_2(Instruction& instr, + int idx, + InstructionKind instr_kind, + IR_FloatMath2::Kind ir_kind) { + if (is_gpr_3(instr, instr_kind, {}, {}, {})) { + return make_set( + IR_Set::REG_FLT, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(ir_kind, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_daddiu(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::DADDIU && instr.get_src(0).is_reg(make_gpr(Reg::S7)) && + instr.get_src(1).kind == InstructionAtom::IMM_SYM) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + make_sym(instr.get_src(1).get_sym())); + } else if (instr.kind == InstructionKind::DADDIU && instr.get_src(0).is_reg(make_gpr(Reg::FP)) && + instr.get_src(1).kind == InstructionAtom::LABEL) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(instr.get_src(1).get_label())); + } else if (instr.kind == InstructionKind::DADDIU) { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::ADD, make_reg(instr.get_src(0).get_reg(), idx), + make_int(instr.get_src(1).get_imm()))); + } + return nullptr; +} + +std::shared_ptr try_lw(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LW && instr.get_src(1).is_reg(make_gpr(Reg::S7)) && + instr.get_src(0).kind == InstructionAtom::IMM_SYM) { + return make_set(IR_Set::SYM_LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + make_sym_value(instr.get_src(0).get_sym())); + } else if (instr.kind == InstructionKind::LW && instr.get_dst(0).is_reg() && + instr.get_src(0).is_link_or_label() && instr.get_src(1).is_reg(make_gpr(Reg::FP))) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::SIGNED, 4, std::make_shared(instr.get_src(0).get_label()))); + } else if (instr.kind == InstructionKind::LW && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm() && instr.get_src(0).get_imm() == 0) { + return make_set( + IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_Load::SIGNED, 4, make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::LW && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm()) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::SIGNED, 4, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(1).get_reg(), idx), + std::make_shared(instr.get_src(0).get_imm())))); + } + return nullptr; +} + +std::shared_ptr try_lq(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LQ && instr.get_src(1).is_reg(make_gpr(Reg::S7)) && + instr.get_src(0).kind == InstructionAtom::IMM_SYM) { + assert(false); + } else if (instr.kind == InstructionKind::LQ && instr.get_dst(0).is_reg() && + instr.get_src(0).is_link_or_label() && instr.get_src(1).is_reg(make_gpr(Reg::FP))) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 16, + std::make_shared(instr.get_src(0).get_label()))); + } else if (instr.kind == InstructionKind::LQ && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm() && instr.get_src(0).get_imm() == 0) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_Load::UNSIGNED, 16, + make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::LQ && instr.get_dst(0).is_reg() && + instr.get_src(0).is_imm()) { + return make_set(IR_Set::LOAD, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared( + IR_Load::UNSIGNED, 16, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(1).get_reg(), idx), + std::make_shared(instr.get_src(0).get_imm())))); + } + return nullptr; +} + +std::shared_ptr try_daddu(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::DADDU, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::ADD, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_dsubu(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::DSUBU, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::SUB, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_mult3(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::MULT3, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::MUL_SIGNED, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_multu3(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::MULTU3, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::MUL_UNSIGNED, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_and(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::AND, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::AND, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_andi(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::ANDI) { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::AND, make_reg(instr.get_src(0).get_reg(), idx), + make_int(instr.get_src(1).get_imm()))); + } + return nullptr; +} + +std::shared_ptr try_nor(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::NOR, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && instr.get_src(1).is_reg(make_gpr(Reg::R0))) { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath1::NOT, make_reg(instr.get_src(0).get_reg(), idx))); + } else if (is_gpr_3(instr, InstructionKind::NOR, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && + !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::NOR, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_xor(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::XOR, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set( + IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::XOR, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_addiu(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::ADDIU && instr.get_src(0).is_reg(make_gpr(Reg::R0))) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + make_int(instr.get_src(1).get_imm())); + } + return nullptr; +} + +std::shared_ptr try_lui(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::LUI && instr.get_src(0).is_imm()) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + make_int(instr.get_src(0).get_imm() << 16)); + } + return nullptr; +} + +std::shared_ptr try_sll(Instruction& instr, int idx) { + (void)idx; + if (is_nop(instr)) { + return std::make_shared(); + } + return nullptr; +} + +std::shared_ptr try_dsrav(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::DSRAV, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::RIGHT_SHIFT_ARITH, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_dsrlv(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::DSRLV, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::RIGHT_SHIFT_LOGIC, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_sw(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::SW && instr.get_src(1).is_sym() && + instr.get_src(2).is_reg(make_gpr(Reg::S7))) { + return std::make_shared(IR_Set::SYM_STORE, make_sym_value(instr.get_src(1).get_sym()), + make_reg(instr.get_src(0).get_reg(), idx)); + } else if (instr.kind == InstructionKind::SW && instr.get_src(1).is_imm()) { + return std::make_shared( + IR_Store::INTEGER, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(2).get_reg(), idx), + std::make_shared(instr.get_src(1).get_imm())), + make_reg(instr.get_src(0).get_reg(), idx), 4); + } + return nullptr; +} + +std::shared_ptr try_sb(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::SB && instr.get_src(1).is_imm()) { + return std::make_shared( + IR_Store::INTEGER, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(2).get_reg(), idx), + std::make_shared(instr.get_src(1).get_imm())), + make_reg(instr.get_src(0).get_reg(), idx), 1); + } + return nullptr; +} + +std::shared_ptr try_sh(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::SH && instr.get_src(1).is_imm()) { + return std::make_shared( + IR_Store::INTEGER, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(2).get_reg(), idx), + std::make_shared(instr.get_src(1).get_imm())), + make_reg(instr.get_src(0).get_reg(), idx), 2); + } + return nullptr; +} + +std::shared_ptr try_sd(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::SD && instr.get_src(1).is_imm()) { + return std::make_shared( + IR_Store::INTEGER, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(2).get_reg(), idx), + std::make_shared(instr.get_src(1).get_imm())), + make_reg(instr.get_src(0).get_reg(), idx), 8); + } + return nullptr; +} + +std::shared_ptr try_sq(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::SQ && instr.get_src(1).is_imm()) { + return std::make_shared( + IR_Store::INTEGER, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(2).get_reg(), idx), + std::make_shared(instr.get_src(1).get_imm())), + make_reg(instr.get_src(0).get_reg(), idx), 16); + } + return nullptr; +} + +std::shared_ptr try_swc1(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::SWC1 && instr.get_src(1).is_imm()) { + return std::make_shared( + IR_Store::FLOAT, + std::make_shared( + IR_IntMath2::ADD, make_reg(instr.get_src(2).get_reg(), idx), + std::make_shared(instr.get_src(1).get_imm())), + make_reg(instr.get_src(0).get_reg(), idx), 4); + } + return nullptr; +} + +std::shared_ptr try_cvtws(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::CVTWS) { + return make_set(IR_Set::REG_FLT, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_FloatMath1::FLOAT_TO_INT, + make_reg(instr.get_src(0).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_cvtsw(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::CVTSW) { + return make_set(IR_Set::REG_FLT, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_FloatMath1::INT_TO_FLOAT, + make_reg(instr.get_src(0).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_float_math_1(Instruction& instr, + int idx, + InstructionKind ikind, + IR_FloatMath1::Kind irkind) { + if (instr.kind == ikind) { + return make_set( + IR_Set::REG_FLT, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(irkind, make_reg(instr.get_src(0).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_movs(Instruction& instr, int idx) { + if (instr.kind == InstructionKind::MOVS) { + return make_set(IR_Set::REG_FLT, make_reg(instr.get_dst(0).get_reg(), idx), + make_reg(instr.get_src(0).get_reg(), idx)); + } + return nullptr; +} + +// TWO Instructions +std::shared_ptr try_div(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::DIV && instr.get_src(0).is_reg() && + instr.get_src(1).is_reg() && next_instr.kind == InstructionKind::MFLO && + next_instr.get_dst(0).is_reg()) { + return make_set(IR_Set::REG_64, make_reg(next_instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::DIV_SIGNED, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::DIV && instr.get_src(0).is_reg() && + instr.get_src(1).is_reg() && next_instr.kind == InstructionKind::MFHI && + next_instr.get_dst(0).is_reg()) { + return make_set(IR_Set::REG_64, make_reg(next_instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::MOD_SIGNED, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_divu(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::DIVU && instr.get_src(0).is_reg() && + instr.get_src(1).is_reg() && next_instr.kind == InstructionKind::MFLO && + next_instr.get_dst(0).is_reg()) { + return make_set(IR_Set::REG_64, make_reg(next_instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::DIV_UNSIGNED, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } else if (instr.kind == InstructionKind::DIVU && instr.get_src(0).is_reg() && + instr.get_src(1).is_reg() && next_instr.kind == InstructionKind::MFHI && + next_instr.get_dst(0).is_reg()) { + return make_set(IR_Set::REG_64, make_reg(next_instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::MOD_UNSIGNED, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + +std::shared_ptr try_jalr(Instruction& instr, Instruction& next_instr, int idx) { + (void)idx; + if (instr.kind == InstructionKind::JALR && instr.get_dst(0).is_reg(make_gpr(Reg::RA)) && + instr.get_src(0).is_reg(make_gpr(Reg::T9)) && + is_gpr_2_imm_int(next_instr, InstructionKind::SLL, make_gpr(Reg::V0), make_gpr(Reg::RA), 0)) { + return std::make_shared(); + } + return nullptr; +} + +BranchDelay get_branch_delay(Instruction& i, int idx) { + if (is_nop(i)) { + return BranchDelay(BranchDelay::NOP); + } else if (is_gpr_3(i, InstructionKind::OR, {}, make_gpr(Reg::S7), make_gpr(Reg::R0))) { + BranchDelay b(BranchDelay::SET_REG_FALSE); + b.destination = make_reg(i.get_dst(0).get_reg(), idx); + return b; + } else if (is_gpr_3(i, InstructionKind::OR, {}, {}, make_gpr(Reg::R0))) { + BranchDelay b(BranchDelay::SET_REG_REG); + b.destination = make_reg(i.get_dst(0).get_reg(), idx); + b.source = make_reg(i.get_src(0).get_reg(), idx); + return b; + } else if (i.kind == InstructionKind::DADDIU && i.get_src(0).is_reg(make_gpr(Reg::S7)) && + i.get_src(1).is_imm() && i.get_src(1).get_imm() == 8) { + BranchDelay b(BranchDelay::SET_REG_TRUE); + b.destination = make_reg(i.get_dst(0).get_reg(), idx); + return b; + } else if (i.kind == InstructionKind::LW && i.get_src(1).is_reg(make_gpr(Reg::S7)) && + i.get_src(0).is_sym()) { + if (i.get_src(0).get_sym() == "binteger") { + BranchDelay b(BranchDelay::SET_BINTEGER); + b.destination = make_reg(i.get_dst(0).get_reg(), idx); + return b; + } else if (i.get_src(0).get_sym() == "pair") { + BranchDelay b(BranchDelay::SET_PAIR); + b.destination = make_reg(i.get_dst(0).get_reg(), idx); + return b; + } + } else if (i.kind == InstructionKind::DSLLV) { + BranchDelay b(BranchDelay::DSLLV); + b.destination = make_reg(i.get_dst(0).get_reg(), idx); + b.source = make_reg(i.get_src(0).get_reg(), idx); + b.source2 = make_reg(i.get_src(1).get_reg(), idx); + return b; + } else if (is_gpr_3(i, InstructionKind::DSUBU, {}, make_gpr(Reg::R0), {})) { + BranchDelay b(BranchDelay::NEGATE); + b.destination = make_reg(i.get_dst(0).get_reg(), idx); + b.source = make_reg(i.get_src(1).get_reg(), idx); + return b; + } + BranchDelay b(BranchDelay::UNKNOWN); + return b; +} + +std::shared_ptr try_bne(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::BNE && instr.get_src(1).is_reg(make_gpr(Reg::R0))) { + return std::make_shared( + Condition(Condition::NONZERO, make_reg(instr.get_src(0).get_reg(), idx), nullptr, nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), false); + } else if (instr.kind == InstructionKind::BNE && instr.get_src(0).is_reg(make_gpr(Reg::S7))) { + return std::make_shared( + Condition(Condition::TRUTHY, make_reg(instr.get_src(1).get_reg(), idx), nullptr, nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), false); + } else if (instr.kind == InstructionKind::BNE) { + return std::make_shared( + Condition(Condition::NOT_EQUAL, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx), nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), false); + } + return nullptr; +} + +std::shared_ptr try_bnel(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::BNEL && instr.get_src(0).is_reg(make_gpr(Reg::S7))) { + return std::make_shared( + Condition(Condition::TRUTHY, make_reg(instr.get_src(1).get_reg(), idx), nullptr, nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true); + } else if (instr.kind == InstructionKind::BNEL && instr.get_src(1).is_reg(make_gpr(Reg::R0))) { + return std::make_shared( + Condition(Condition::NONZERO, make_reg(instr.get_src(0).get_reg(), idx), nullptr, nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true); + } else if (instr.kind == InstructionKind::BNEL) { + // return std::make_shared(IR_Branch2::NOT_EQUAL, instr.get_src(2).get_label(), + // make_reg(instr.get_src(0).get_reg(), idx), + // make_reg(instr.get_src(1).get_reg(), idx), + // get_branch_delay(next_instr, idx), true); + } + return nullptr; +} + +std::shared_ptr try_beql(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::BEQL && instr.get_src(0).is_reg(make_gpr(Reg::S7))) { + return std::make_shared( + Condition(Condition::FALSE, make_reg(instr.get_src(1).get_reg(), idx), nullptr, nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true); + } else if (instr.kind == InstructionKind::BEQL && instr.get_src(1).is_reg(make_gpr(Reg::R0))) { + return std::make_shared( + Condition(Condition::ZERO, make_reg(instr.get_src(0).get_reg(), idx), nullptr, nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true); + } + + else if (instr.kind == InstructionKind::BEQL) { + return std::make_shared( + Condition(Condition::EQUAL, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx), nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), true); + } + return nullptr; +} + +std::shared_ptr try_beq(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::BEQ && instr.get_src(0).is_reg(make_gpr(Reg::R0)) && + instr.get_src(1).is_reg(make_gpr(Reg::R0))) { + return std::make_shared(Condition(Condition::ALWAYS, nullptr, nullptr, nullptr), + instr.get_src(2).get_label(), + get_branch_delay(next_instr, idx), false); + } else if (instr.kind == InstructionKind::BEQ && instr.get_src(0).is_reg(make_gpr(Reg::S7))) { + return std::make_shared( + Condition(Condition::FALSE, make_reg(instr.get_src(1).get_reg(), idx), nullptr, nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), false); + } else if (instr.kind == InstructionKind::BEQ) { + return std::make_shared( + Condition(Condition::EQUAL, make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx), nullptr), + instr.get_src(2).get_label(), get_branch_delay(next_instr, idx), false); + } + return nullptr; +} + +std::shared_ptr try_bgtzl(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::BGTZL) { + return std::make_shared( + Condition(Condition::GREATER_THAN_ZERO_SIGNED, make_reg(instr.get_src(0).get_reg(), idx), + nullptr, nullptr), + instr.get_src(1).get_label(), get_branch_delay(next_instr, idx), true); + } + return nullptr; +} + +std::shared_ptr try_bgezl(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::BGEZL) { + return std::make_shared( + Condition(Condition::GEQ_ZERO_SIGNED, make_reg(instr.get_src(0).get_reg(), idx), nullptr, + nullptr), + instr.get_src(1).get_label(), get_branch_delay(next_instr, idx), true); + } + return nullptr; +} + +std::shared_ptr try_bltzl(Instruction& instr, Instruction& next_instr, int idx) { + if (instr.kind == InstructionKind::BLTZL) { + return std::make_shared( + Condition(Condition::LESS_THAN_ZERO, make_reg(instr.get_src(0).get_reg(), idx), nullptr, + nullptr), + instr.get_src(1).get_label(), get_branch_delay(next_instr, idx), true); + } + return nullptr; +} + +std::shared_ptr try_daddiu(Instruction& i0, Instruction& i1, int idx) { + if (i0.kind == InstructionKind::DADDIU && i1.kind == InstructionKind::MOVN && + i0.get_src(0).get_reg() == make_gpr(Reg::S7)) { + auto dst_reg = i0.get_dst(0).get_reg(); + auto src_reg = i1.get_src(1).get_reg(); + assert(i0.get_src(0).get_reg() == make_gpr(Reg::S7)); + assert(i0.get_src(1).get_imm() == 8); + assert(i1.get_dst(0).get_reg() == dst_reg); + assert(i1.get_src(0).get_reg() == make_gpr(Reg::S7)); + return make_set(IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared( + Condition(Condition::ZERO, make_reg(src_reg, idx), nullptr, nullptr))); + } else if (i0.kind == InstructionKind::DADDIU && i1.kind == InstructionKind::MOVZ && + i0.get_src(0).get_reg() == make_gpr(Reg::S7)) { + auto dst_reg = i0.get_dst(0).get_reg(); + auto src_reg = i1.get_src(1).get_reg(); + assert(i0.get_src(0).get_reg() == make_gpr(Reg::S7)); + assert(i0.get_src(1).get_imm() == 8); + assert(i1.get_dst(0).get_reg() == dst_reg); + assert(i1.get_src(0).get_reg() == make_gpr(Reg::S7)); + return make_set(IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared( + Condition(Condition::NONZERO, make_reg(src_reg, idx), nullptr, nullptr))); + } + return nullptr; +} + +std::shared_ptr try_lui(Instruction& i0, Instruction& i1, int idx) { + if (i0.kind == InstructionKind::LUI && i1.kind == InstructionKind::ORI && + i0.get_src(0).is_label() && i1.get_src(1).is_label()) { + assert(i0.get_dst(0).get_reg() == i1.get_src(0).get_reg()); + assert(i0.get_src(0).get_label() == i1.get_src(1).get_label()); + auto op = make_set(IR_Set::REG_64, make_reg(i1.get_dst(0).get_reg(), idx), + std::make_shared(i0.get_src(0).get_label())); + if (i0.get_dst(0).get_reg() != i1.get_dst(0).get_reg()) { + op->clobber = make_reg(i0.get_dst(0).get_reg(), idx); + } + return op; + } else if (i0.kind == InstructionKind::LUI && i1.kind == InstructionKind::ORI && + i0.get_src(0).is_imm() && i1.get_src(1).is_imm() && + i0.get_dst(0).get_reg() == i1.get_src(0).get_reg()) { + auto op = make_set( + IR_Set::REG_64, make_reg(i1.get_dst(0).get_reg(), idx), + make_int((int64_t(i1.get_src(1).get_imm()) + int64_t(i0.get_src(0).get_imm() << 16)))); + if (i0.get_dst(0).get_reg() != i1.get_dst(0).get_reg()) { + op->clobber = make_reg(i0.get_dst(0).get_reg(), idx); + } + return op; + } + return nullptr; +} + +// THREE OP +std::shared_ptr try_dsubu(Instruction& i0, Instruction& i1, Instruction& i2, int idx) { + if (i0.kind == InstructionKind::DSUBU && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVN) { + // check for equality + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).get_reg() == make_gpr(Reg::S7)); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + assert(i2.get_src(1).get_reg() == clobber_reg); + return make_set(IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared( + Condition(Condition::EQUAL, make_reg(src0_reg, idx), + make_reg(src1_reg, idx), make_reg(clobber_reg, idx)))); + } else if (i0.kind == InstructionKind::DSUBU && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVZ) { + // check for equality + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).get_reg() == make_gpr(Reg::S7)); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + if (i2.get_src(1).get_reg() != clobber_reg) { + return nullptr; // TODO! + } + return make_set(IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared( + Condition(Condition::NOT_EQUAL, make_reg(src0_reg, idx), + make_reg(src1_reg, idx), make_reg(clobber_reg, idx)))); + } + return nullptr; +} + +std::shared_ptr try_slt(Instruction& i0, Instruction& i1, Instruction& i2, int idx) { + if (i0.kind == InstructionKind::SLT && i1.kind == InstructionKind::BNE) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + assert(i1.get_src(0).get_reg() == clobber_reg); + assert(i1.get_src(1).get_reg() == make_gpr(Reg::R0)); + return std::make_shared( + Condition(Condition::LESS_THAN_SIGNED, make_reg(src0_reg, idx), make_reg(src1_reg, idx), + make_reg(clobber_reg, idx)), + i1.get_src(2).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::SLT && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVZ) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).is_reg(make_gpr(Reg::S7))); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + if (i2.get_src(1).get_reg() != clobber_reg) { + return nullptr; // TODO! + } + return make_set(IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared( + Condition(Condition::LESS_THAN_SIGNED, make_reg(src0_reg, idx), + make_reg(src1_reg, idx), make_reg(clobber_reg, idx)))); + } else if (i0.kind == InstructionKind::SLT && i1.kind == InstructionKind::BEQ) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + assert(i1.get_src(0).get_reg() == clobber_reg); + assert(i1.get_src(1).get_reg() == make_gpr(Reg::R0)); + return std::make_shared( + Condition(Condition::GEQ_SIGNED, make_reg(src0_reg, idx), make_reg(src1_reg, idx), + make_reg(clobber_reg, idx)), + i1.get_src(2).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::SLT && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVN) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).is_reg(make_gpr(Reg::S7))); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + if (i2.get_src(1).get_reg() != clobber_reg) { + return nullptr; // TODO! + } + return make_set(IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared( + Condition(Condition::GEQ_SIGNED, make_reg(src0_reg, idx), + make_reg(src1_reg, idx), make_reg(clobber_reg, idx)))); + } + return nullptr; +} + +std::shared_ptr try_slti(Instruction& i0, Instruction& i1, Instruction& i2, int idx) { + auto src1 = make_int(i0.get_src(1).get_imm()); + if (i0.kind == InstructionKind::SLTI && i1.kind == InstructionKind::BNE) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + assert(i1.get_src(0).get_reg() == clobber_reg); + assert(i1.get_src(1).get_reg() == make_gpr(Reg::R0)); + return std::make_shared( + Condition(Condition::LESS_THAN_SIGNED, make_reg(src0_reg, idx), src1, + make_reg(clobber_reg, idx)), + i1.get_src(2).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::SLTI && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVZ) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).is_reg(make_gpr(Reg::S7))); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + if (i2.get_src(1).get_reg() != clobber_reg) { + return nullptr; // TODO! + } + return make_set( + IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared(Condition(Condition::LESS_THAN_SIGNED, make_reg(src0_reg, idx), + src1, make_reg(clobber_reg, idx)))); + } else if (i0.kind == InstructionKind::SLTI && i1.kind == InstructionKind::BEQ) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + assert(i1.get_src(0).get_reg() == clobber_reg); + assert(i1.get_src(1).get_reg() == make_gpr(Reg::R0)); + return std::make_shared( + Condition(Condition::GEQ_SIGNED, make_reg(src0_reg, idx), src1, make_reg(clobber_reg, idx)), + i1.get_src(2).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::SLTI && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVN) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).is_reg(make_gpr(Reg::S7))); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + if (i2.get_src(1).get_reg() != clobber_reg) { + return nullptr; // TODO! + } + return make_set( + IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared(Condition(Condition::GEQ_SIGNED, make_reg(src0_reg, idx), src1, + make_reg(clobber_reg, idx)))); + } + return nullptr; +} + +std::shared_ptr try_sltiu(Instruction& i0, Instruction& i1, Instruction& i2, int idx) { + auto src1 = make_int(i0.get_src(1).get_imm()); + if (i0.kind == InstructionKind::SLTIU && i1.kind == InstructionKind::BNE) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + assert(i1.get_src(0).get_reg() == clobber_reg); + assert(i1.get_src(1).get_reg() == make_gpr(Reg::R0)); + return std::make_shared( + Condition(Condition::LESS_THAN_UNSIGNED, make_reg(src0_reg, idx), src1, + make_reg(clobber_reg, idx)), + i1.get_src(2).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::SLTIU && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVZ) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).is_reg(make_gpr(Reg::S7))); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + if (i2.get_src(1).get_reg() != clobber_reg) { + return nullptr; // TODO! + } + return make_set(IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared(Condition(Condition::LESS_THAN_UNSIGNED, + make_reg(src0_reg, idx), src1, + make_reg(clobber_reg, idx)))); + } else if (i0.kind == InstructionKind::SLTIU && i1.kind == InstructionKind::BEQ) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + assert(i1.get_src(0).get_reg() == clobber_reg); + assert(i1.get_src(1).get_reg() == make_gpr(Reg::R0)); + return std::make_shared(Condition(Condition::GEQ_UNSIGNED, make_reg(src0_reg, idx), + src1, make_reg(clobber_reg, idx)), + i1.get_src(2).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::SLTIU && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVN) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).is_reg(make_gpr(Reg::S7))); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + if (i2.get_src(1).get_reg() != clobber_reg) { + return nullptr; // TODO! + } + return make_set( + IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared(Condition(Condition::GEQ_UNSIGNED, make_reg(src0_reg, idx), + src1, make_reg(clobber_reg, idx)))); + } + return nullptr; +} + +std::shared_ptr try_ceqs(Instruction& i0, Instruction& i1, Instruction& i2, int idx) { + if (i0.kind == InstructionKind::CEQS && i1.kind == InstructionKind::BC1T) { + return std::make_shared( + Condition(Condition::FLOAT_EQUAL, make_reg(i0.get_src(0).get_reg(), idx), + make_reg(i0.get_src(1).get_reg(), idx), nullptr), + i1.get_src(0).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::CEQS && i1.kind == InstructionKind::BC1F) { + return std::make_shared( + Condition(Condition::FLOAT_NOT_EQUAL, make_reg(i0.get_src(0).get_reg(), idx), + make_reg(i0.get_src(1).get_reg(), idx), nullptr), + i1.get_src(0).get_label(), get_branch_delay(i2, idx), false); + } + return nullptr; +} + +std::shared_ptr try_clts(Instruction& i0, Instruction& i1, Instruction& i2, int idx) { + if (i0.kind == InstructionKind::CLTS && i1.kind == InstructionKind::BC1T) { + return std::make_shared( + Condition(Condition::FLOAT_LESS_THAN, make_reg(i0.get_src(0).get_reg(), idx), + make_reg(i0.get_src(1).get_reg(), idx), nullptr), + i1.get_src(0).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::CLTS && i1.kind == InstructionKind::BC1F) { + return std::make_shared( + Condition(Condition::FLOAT_GEQ, make_reg(i0.get_src(0).get_reg(), idx), + make_reg(i0.get_src(1).get_reg(), idx), nullptr), + i1.get_src(0).get_label(), get_branch_delay(i2, idx), false); + } + return nullptr; +} + +std::shared_ptr try_sltu(Instruction& i0, Instruction& i1, Instruction& i2, int idx) { + if (i0.kind == InstructionKind::SLTU && i1.kind == InstructionKind::BNE) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + assert(i1.get_src(0).get_reg() == clobber_reg); + assert(i1.get_src(1).get_reg() == make_gpr(Reg::R0)); + return std::make_shared( + Condition(Condition::LESS_THAN_UNSIGNED, make_reg(src0_reg, idx), make_reg(src1_reg, idx), + make_reg(clobber_reg, idx)), + i1.get_src(2).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::SLTU && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVZ) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).is_reg(make_gpr(Reg::S7))); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + if (i2.get_src(1).get_reg() != clobber_reg) { + return nullptr; // TODO! + } + return make_set(IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared( + Condition(Condition::LESS_THAN_UNSIGNED, make_reg(src0_reg, idx), + make_reg(src1_reg, idx), make_reg(clobber_reg, idx)))); + } else if (i0.kind == InstructionKind::SLTU && i1.kind == InstructionKind::BEQ) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + assert(i1.get_src(0).get_reg() == clobber_reg); + assert(i1.get_src(1).get_reg() == make_gpr(Reg::R0)); + return std::make_shared( + Condition(Condition::GEQ_UNSIGNED, make_reg(src0_reg, idx), make_reg(src1_reg, idx), + make_reg(clobber_reg, idx)), + i1.get_src(2).get_label(), get_branch_delay(i2, idx), false); + } else if (i0.kind == InstructionKind::SLTU && i1.kind == InstructionKind::DADDIU && + i2.kind == InstructionKind::MOVN) { + auto clobber_reg = i0.get_dst(0).get_reg(); + auto src0_reg = i0.get_src(0).get_reg(); + auto src1_reg = i0.get_src(1).get_reg(); + auto dst_reg = i1.get_dst(0).get_reg(); + assert(i1.get_src(0).is_reg(make_gpr(Reg::S7))); + assert(i1.get_src(1).get_imm() == 8); + assert(i2.get_dst(0).get_reg() == dst_reg); + assert(i2.get_src(0).get_reg() == make_gpr(Reg::S7)); + if (i2.get_src(1).get_reg() != clobber_reg) { + return nullptr; // TODO! + } + return make_set(IR_Set::REG_64, make_reg(dst_reg, idx), + std::make_shared( + Condition(Condition::GEQ_UNSIGNED, make_reg(src0_reg, idx), + make_reg(src1_reg, idx), make_reg(clobber_reg, idx)))); + } + return nullptr; +} + +// five op +std::shared_ptr try_lwu(Instruction& i0, + Instruction& i1, + Instruction& i2, + Instruction& i3, + Instruction& i4, + int idx) { + (void)idx; + auto s6 = make_gpr(Reg::S6); + if (i0.kind == InstructionKind::LWU && i0.get_dst(0).is_reg(s6) && + i0.get_src(0).get_imm() == 44 && i0.get_src(1).is_reg(s6) && + i1.kind == InstructionKind::MTLO1 && i1.get_src(0).is_reg(s6) && + i2.kind == InstructionKind::LWU && i2.get_dst(0).is_reg(s6) && + i2.get_src(0).get_imm() == 12 && i2.get_src(1).is_reg(s6) && + i3.kind == InstructionKind::JALR && i3.get_dst(0).is_reg(make_gpr(Reg::RA)) && + i3.get_src(0).is_reg(s6) && i4.kind == InstructionKind::MFLO1 && i4.get_dst(0).is_reg(s6)) { + return std::make_shared(); + } + return nullptr; +} + +} // namespace + +void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file) { + (void)file; + for (int instr = block.start_word; instr < block.end_word; instr++) { + auto& i = func->instructions.at(instr); + + int length = 0; + + std::shared_ptr result = nullptr; + if (instr + 4 < block.end_word) { + auto& i1 = func->instructions.at(instr + 1); + auto& i2 = func->instructions.at(instr + 2); + auto& i3 = func->instructions.at(instr + 3); + auto& i4 = func->instructions.at(instr + 4); + switch (i.kind) { + case InstructionKind::LWU: + result = try_lwu(i, i1, i2, i3, i4, instr); + break; + default: + result = nullptr; + } + if (result) { + length = 5; + } + } + + if (!result && instr + 2 < block.end_word) { + auto& next = func->instructions.at(instr + 1); + auto& next_next = func->instructions.at(instr + 2); + switch (i.kind) { + case InstructionKind::DSUBU: + result = try_dsubu(i, next, next_next, instr); + break; + case InstructionKind::SLT: + result = try_slt(i, next, next_next, instr); + break; + case InstructionKind::SLTI: + result = try_slti(i, next, next_next, instr); + break; + case InstructionKind::SLTU: + result = try_sltu(i, next, next_next, instr); + break; + case InstructionKind::SLTIU: + result = try_sltiu(i, next, next_next, instr); + break; + case InstructionKind::CEQS: + result = try_ceqs(i, next, next_next, instr); + break; + case InstructionKind::CLTS: + result = try_clts(i, next, next_next, instr); + break; + default: + result = nullptr; + } + + if (result) { + length = 3; + } + } + + if (!result && instr + 1 < block.end_word) { + auto& next = func->instructions.at(instr + 1); + // single op failed, try double + switch (i.kind) { + case InstructionKind::DIV: + result = try_div(i, next, instr); + break; + case InstructionKind::DIVU: + result = try_divu(i, next, instr); + break; + case InstructionKind::JALR: + result = try_jalr(i, next, instr); + break; + case InstructionKind::BNE: + result = try_bne(i, next, instr); + break; + case InstructionKind::BNEL: + result = try_bnel(i, next, instr); + break; + case InstructionKind::BEQ: + result = try_beq(i, next, instr); + break; + case InstructionKind::BGTZL: + result = try_bgtzl(i, next, instr); + break; + case InstructionKind::BGEZL: + result = try_bgezl(i, next, instr); + break; + case InstructionKind::BLTZL: + result = try_bltzl(i, next, instr); + break; + case InstructionKind::BEQL: + result = try_beql(i, next, instr); + break; + case InstructionKind::DADDIU: + result = try_daddiu(i, next, instr); + break; + case InstructionKind::LUI: + result = try_lui(i, next, instr); + break; + default: + result = nullptr; + } + + if (result) { + length = 2; + } + } + + if (!result) { + switch (i.kind) { + case InstructionKind::OR: + result = try_or(i, instr); + break; + case InstructionKind::ORI: + result = try_ori(i, instr); + break; + case InstructionKind::DADDIU: + result = try_daddiu(i, instr); + break; + case InstructionKind::AND: + result = try_and(i, instr); + break; + case InstructionKind::ANDI: + result = try_andi(i, instr); + break; + case InstructionKind::NOR: + result = try_nor(i, instr); + break; + case InstructionKind::XOR: + result = try_xor(i, instr); + break; + case InstructionKind::LWC1: + result = try_lwc1(i, instr); + break; + case InstructionKind::MTC1: + result = try_mtc1(i, instr); + break; + case InstructionKind::DIVS: + result = try_float_math_2(i, instr, InstructionKind::DIVS, IR_FloatMath2::DIV); + break; + case InstructionKind::SUBS: + result = try_float_math_2(i, instr, InstructionKind::SUBS, IR_FloatMath2::SUB); + break; + case InstructionKind::ADDS: + result = try_float_math_2(i, instr, InstructionKind::ADDS, IR_FloatMath2::ADD); + break; + case InstructionKind::MULS: + result = try_float_math_2(i, instr, InstructionKind::MULS, IR_FloatMath2::MUL); + break; + case InstructionKind::ABSS: + result = try_float_math_1(i, instr, InstructionKind::ABSS, IR_FloatMath1::ABS); + break; + case InstructionKind::NEGS: + result = try_float_math_1(i, instr, InstructionKind::NEGS, IR_FloatMath1::NEG); + break; + case InstructionKind::SQRTS: + result = try_float_math_1(i, instr, InstructionKind::SQRTS, IR_FloatMath1::SQRT); + break; + case InstructionKind::MINS: + result = try_float_math_2(i, instr, InstructionKind::MINS, IR_FloatMath2::MIN); + break; + case InstructionKind::MAXS: + result = try_float_math_2(i, instr, InstructionKind::MAXS, IR_FloatMath2::MAX); + break; + case InstructionKind::MOVS: + result = try_movs(i, instr); + break; + case InstructionKind::MFC1: + result = try_mfc1(i, instr); + break; + case InstructionKind::DADDU: + result = try_daddu(i, instr); + break; + case InstructionKind::DSUBU: + result = try_dsubu(i, instr); + break; + case InstructionKind::MULT3: + result = try_mult3(i, instr); + break; + case InstructionKind::MULTU3: + result = try_multu3(i, instr); + break; + case InstructionKind::POR: + result = try_por(i, instr); + break; + case InstructionKind::LBU: + result = try_lbu(i, instr); + break; + case InstructionKind::LHU: + result = try_lhu(i, instr); + break; + case InstructionKind::LB: + result = try_lb(i, instr); + break; + case InstructionKind::LH: + result = try_lh(i, instr); + break; + case InstructionKind::LW: + result = try_lw(i, instr); + break; + case InstructionKind::LWU: + result = try_lwu(i, instr); + break; + case InstructionKind::LD: + result = try_ld(i, instr); + break; + case InstructionKind::LQ: + result = try_lq(i, instr); + break; + case InstructionKind::DSRA: + result = try_dsra(i, instr); + break; + case InstructionKind::DSRA32: + result = try_dsra32(i, instr); + break; + case InstructionKind::DSRL: + result = try_dsrl(i, instr); + break; + case InstructionKind::DSRL32: + result = try_dsrl32(i, instr); + break; + case InstructionKind::DSLL: + result = try_dsll(i, instr); + break; + case InstructionKind::DSLL32: + result = try_dsll32(i, instr); + break; + case InstructionKind::ADDIU: + result = try_addiu(i, instr); + break; + case InstructionKind::LUI: + result = try_lui(i, instr); + break; + case InstructionKind::SLL: + result = try_sll(i, instr); + break; + case InstructionKind::SB: + result = try_sb(i, instr); + break; + case InstructionKind::SH: + result = try_sh(i, instr); + break; + case InstructionKind::SW: + result = try_sw(i, instr); + break; + case InstructionKind::SD: + result = try_sd(i, instr); + break; + case InstructionKind::SQ: + result = try_sq(i, instr); + break; + case InstructionKind::SWC1: + result = try_swc1(i, instr); + break; + case InstructionKind::CVTWS: + result = try_cvtws(i, instr); + break; + case InstructionKind::CVTSW: + result = try_cvtsw(i, instr); + break; + case InstructionKind::DSRAV: + result = try_dsrav(i, instr); + break; + case InstructionKind::DSRLV: + result = try_dsrlv(i, instr); + break; + default: + result = nullptr; + } + + if (result) { + length = 1; + } + } + + // everything failed + if (!result) { + // temp hack for debug: + // printf("Instruction -> BasicOp failed on %s\n", i.to_string(*file).c_str()); + func->add_basic_op(std::make_shared(), instr, instr + 1); + } else { + func->add_basic_op(result, instr, instr + length); + instr += (length - 1); + } + } +} diff --git a/decompiler/IR/BasicOpBuilder.h b/decompiler/IR/BasicOpBuilder.h new file mode 100644 index 0000000000..b7a2b553d8 --- /dev/null +++ b/decompiler/IR/BasicOpBuilder.h @@ -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); \ No newline at end of file diff --git a/decompiler/IR/CfgBuilder.cpp b/decompiler/IR/CfgBuilder.cpp new file mode 100644 index 0000000000..eca4a3d848 --- /dev/null +++ b/decompiler/IR/CfgBuilder.cpp @@ -0,0 +1,1012 @@ +#include "third-party/fmt/format.h" +#include +#include "common/util/MatchParam.h" +#include "CfgBuilder.h" +#include "decompiler/Function/CfgVtx.h" +#include "decompiler/Function/Function.h" +#include "decompiler/Disasm/InstructionMatching.h" + +namespace { + +std::shared_ptr cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx); + +/*! + * This adds a single CfgVtx* to a list of IR's by converting it with cfg to IR. + * The trick here is that it will recursively inline anything which would generate an IR begin. + * This avoids the case where Begin's are nested excessively. + */ +void insert_cfg_into_list(Function& f, + LinkedObjectFile& file, + std::vector>* output, + CfgVtx* vtx) { + auto as_sequence = dynamic_cast(vtx); + auto as_block = dynamic_cast(vtx); + if (as_sequence) { + for (auto& x : as_sequence->seq) { + insert_cfg_into_list(f, file, output, x); + } + } else if (as_block) { + auto& block = f.basic_blocks.at(as_block->block_id); + IR* last = nullptr; + for (int instr = block.start_word; instr < block.end_word; instr++) { + auto got = f.get_basic_op_at_instr(instr); + if (got.get() == last) { + continue; + } + last = got.get(); + output->push_back(got); + } + } else { + // doesn't look like we're going to get something that can be inlined, so try as usual + auto ir = cfg_to_ir(f, file, vtx); + auto ir_as_begin = dynamic_cast(ir.get()); + if (ir_as_begin) { + // we unexpectedly got a begin, even though we didn't think we would. This is okay, but we + // should inline this begin to avoid nested begins. This happens in the case where an entire + // control flow pattern is turned into a single op (like type-of) and includes some ops at + // the beginning. We don't have a good way of knowing this will happen until we try it. + for (auto& x : ir_as_begin->forms) { + output->push_back(x); + } + } else { + output->push_back(ir); + } + } +} + +/*! + * If it's a begin with a branch as the last operation, returns a pointer to the branch IR + * and also a pointer to the vector which holds the branch operation in its last slot. + * Otherwise returns nullptr. Useful to modify or remove branches found at the end of blocks, + * and inline things into the begin they were found in. + */ +std::pair>*> get_condition_branch_as_vector(IR* in) { + auto as_seq = dynamic_cast(in); + if (as_seq) { + auto irb = dynamic_cast(as_seq->forms.back().get()); + auto loc = &as_seq->forms; + assert(irb); + return std::make_pair(irb, loc); + } + return std::make_pair(nullptr, nullptr); +} + +/*! + * Given an IR, find a branch IR at the end, and also the location of it so it can be patched. + * Returns nullptr as the first item in the pair if it didn't work. + */ +std::pair*> get_condition_branch(std::shared_ptr* in) { + IR_Branch* condition_branch = dynamic_cast(in->get()); + std::shared_ptr* condition_branch_location = in; + if (!condition_branch) { + // not 100% sure this will always work + auto as_seq = dynamic_cast(in->get()); + if (as_seq) { + condition_branch = dynamic_cast(as_seq->forms.back().get()); + condition_branch_location = &as_seq->forms.back(); + } + } + return std::make_pair(condition_branch, condition_branch_location); +} + +/*! + * Given a CondWithElse IR, remove the internal branches and set the condition to be an actual + * compare IR instead of a branch. + * Doesn't "rebalance" the leading condition because this runs way before expression compaction. + */ +void clean_up_cond_with_else(std::shared_ptr* ir, LinkedObjectFile& file) { + (void)file; + auto cwe = dynamic_cast(ir->get()); + assert(cwe); + for (auto& e : cwe->entries) { + if (e.cleaned) { + continue; + } + auto jump_to_next = get_condition_branch(&e.condition); + assert(jump_to_next.first); + assert(jump_to_next.first->branch_delay.kind == BranchDelay::NOP); + // patch the jump to next with a condition. + auto replacement = std::make_shared(jump_to_next.first->condition); + replacement->condition.invert(); + *(jump_to_next.second) = replacement; + + // patch the jump at the end of a block. + auto jump_to_end = get_condition_branch(&e.body); + assert(jump_to_end.first); + assert(jump_to_end.first->branch_delay.kind == BranchDelay::NOP); + assert(jump_to_end.first->condition.kind == Condition::ALWAYS); + + // if possible, we just want to remove this from the sequence its in. + // but sometimes there's a case with nothing in it so there is no sequence. + // in this case, we can just replace the branch with a NOP IR to indicate that nothing + // happens in this case, but there was still GOAL code to test for it. + // this happens rarely, as you would expect. + auto as_end_of_sequence = get_condition_branch_as_vector(e.body.get()); + if (as_end_of_sequence.first) { + assert(as_end_of_sequence.second->size() > 1); + as_end_of_sequence.second->pop_back(); + } else { + // In the future we could consider having a more explicit "this case is empty" operator so + // this doesn't get confused with an actual MIPS nop. + *(jump_to_end.second) = std::make_shared(); + } + e.cleaned = true; + } +} + +/*! + * Does the instruction in the delay slot set a register to false? + * Note. a beql s7, x followed by a or y, x, r0 will count as this. I don't know why but + * GOAL does this on comparisons to false. + */ +bool delay_slot_sets_false(IR_Branch* branch) { + if (branch->branch_delay.kind == BranchDelay::SET_REG_FALSE) { + return true; + } + + if (branch->condition.kind == Condition::FALSE && + branch->branch_delay.kind == BranchDelay::SET_REG_REG) { + auto reg_check = dynamic_cast(branch->condition.src0.get()); + assert(reg_check); + auto reg_read = dynamic_cast(branch->branch_delay.source.get()); + assert(reg_read); + return reg_check->reg == reg_read->reg; + } + + return false; +} + +/*! + * Does the instruction in the delay slot set a register to a truthy value, like in a GOAL + * or form branch? Either it explicitly sets #t, or it tests the value for being not false, + * then uses that + */ +bool delay_slot_sets_truthy(IR_Branch* branch) { + if (branch->branch_delay.kind == BranchDelay::SET_REG_TRUE) { + return true; + } + + if (branch->condition.kind == Condition::TRUTHY && + branch->branch_delay.kind == BranchDelay::SET_REG_REG) { + auto reg_check = dynamic_cast(branch->condition.src0.get()); + assert(reg_check); + auto reg_read = dynamic_cast(branch->branch_delay.source.get()); + assert(reg_read); + return reg_check->reg == reg_read->reg; + } + + return false; +} + +/*! + * Try to convert a short circuit to an and. + */ +bool try_clean_up_sc_as_and(std::shared_ptr& ir, LinkedObjectFile& file) { + (void)file; + Register destination; + std::shared_ptr ir_dest = nullptr; + for (int i = 0; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + if (!delay_slot_sets_false(branch.first)) { + return false; + } + + if (i == 0) { + ir_dest = branch.first->branch_delay.destination; + destination = dynamic_cast(branch.first->branch_delay.destination.get())->reg; + } else { + if (destination != + dynamic_cast(branch.first->branch_delay.destination.get())->reg) { + return false; + } + } + } + + ir->kind = IR_ShortCircuit::AND; + ir->final_result = ir_dest; + + // now get rid of the branches + for (int i = 0; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + auto replacement = std::make_shared(branch.first->condition); + replacement->condition.invert(); + *(branch.second) = replacement; + } + + return true; +} + +/*! + * Try to convert a short circuit to an or. + * Note - this will convert an and to a very strange or, so always use the try as and first. + */ +bool try_clean_up_sc_as_or(std::shared_ptr& ir, LinkedObjectFile& file) { + (void)file; + Register destination; + std::shared_ptr ir_dest = nullptr; + for (int i = 0; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + if (!delay_slot_sets_truthy(branch.first)) { + return false; + } + assert(dynamic_cast(branch.first->branch_delay.destination.get())); + + if (i == 0) { + ir_dest = branch.first->branch_delay.destination; + destination = dynamic_cast(branch.first->branch_delay.destination.get())->reg; + } else { + if (destination != + dynamic_cast(branch.first->branch_delay.destination.get())->reg) { + return false; + } + } + } + + ir->kind = IR_ShortCircuit::OR; + ir->final_result = ir_dest; + + for (int i = 0; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + auto replacement = std::make_shared(branch.first->condition); + *(branch.second) = replacement; + } + + return true; +} + +void clean_up_sc(std::shared_ptr& ir, LinkedObjectFile& file); + +/*! + * A form like (and x (or y z)) will be recognized as a single SC Vertex by the CFG pass. + * In the case where we fail to clean it up as an AND or an OR, we should attempt splitting. + * Part of the complexity here is that we want to clean up the split recursively so things like + * (and x (or y (and a b))) + * or + * (and x (or y (and a b)) c d (or z)) + * will work correctly. This may require doing more splitting on both sections! + */ +bool try_splitting_nested_sc(std::shared_ptr& ir, LinkedObjectFile& file) { + auto first_branch = get_condition_branch(&ir->entries.front().condition); + assert(first_branch.first); + bool first_is_and = delay_slot_sets_false(first_branch.first); + bool first_is_or = delay_slot_sets_truthy(first_branch.first); + assert(first_is_and != first_is_or); // one or the other but not both! + + int first_different = -1; // the index of the first one that's different. + + for (int i = 1; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + bool is_and = delay_slot_sets_false(branch.first); + bool is_or = delay_slot_sets_truthy(branch.first); + assert(is_and != is_or); + + if (first_different == -1) { + // haven't seen a change yet. + if (first_is_and != is_and) { + // change! + first_different = i; + break; + } + } + } + + assert(first_different != -1); + + std::vector nested_ir; + for (int i = first_different; i < int(ir->entries.size()); i++) { + nested_ir.push_back(ir->entries.at(i)); + } + + auto s = int(ir->entries.size()); + for (int i = first_different; i < s; i++) { + ir->entries.pop_back(); + } + + auto nested_sc = std::make_shared(nested_ir); + clean_up_sc(nested_sc, file); + + // the real trick + IR_ShortCircuit::Entry nested_entry; + nested_entry.condition = nested_sc; + ir->entries.push_back(nested_entry); + + clean_up_sc(ir, file); + + return true; +} + +/*! + * Try to clean up a single short circuit IR. It may get split up into nested IR_ShortCircuits + * if there is a case like (and a (or b c)) + */ +void clean_up_sc(std::shared_ptr& ir, LinkedObjectFile& file) { + (void)file; + assert(ir->entries.size() > 1); + if (!try_clean_up_sc_as_and(ir, file)) { + if (!try_clean_up_sc_as_or(ir, file)) { + if (!try_splitting_nested_sc(ir, file)) { + assert(false); + } + } + } +} + +/*! + * A GOAL comparison which produces a boolean is recognized as a cond-no-else by the CFG analysis. + * But it should not be decompiled as a branching statement. + * This either succeeds or asserts and must be called with with something that can be converted + * successfully + */ +void convert_cond_no_else_to_compare(std::shared_ptr* ir) { + auto cne = dynamic_cast(ir->get()); + assert(cne); + auto condition = get_condition_branch(&cne->entries.front().condition); + assert(condition.first); + auto body = dynamic_cast(cne->entries.front().body.get()); + assert(body); + auto dst = body->dst; + auto src = dynamic_cast(body->src.get()); + assert(src->name == "#f"); + assert(cne->entries.size() == 1); + + auto condition_as_single = dynamic_cast(cne->entries.front().condition.get()); + if (condition_as_single) { + auto replacement = std::make_shared( + IR_Set::REG_64, dst, std::make_shared(condition.first->condition)); + *ir = replacement; + } else { + auto condition_as_seq = dynamic_cast(cne->entries.front().condition.get()); + assert(condition_as_seq); + if (condition_as_seq) { + auto replacement = std::make_shared(); + replacement->forms = condition_as_seq->forms; + assert(condition.second == &condition_as_seq->forms.back()); + replacement->forms.pop_back(); + replacement->forms.push_back(std::make_shared( + IR_Set::REG_64, dst, std::make_shared(condition.first->condition))); + *ir = replacement; + } + } +} + +/*! + * Replace internal branches inside a CondNoElse IR. + * If possible will simplify the entire expression into a comparison operation if possible. + * Will record which registers are set to false in branch delay slots. + * The exact behavior here isn't really clear to me. It's possible that these delay set false + * were disabled in cases where the result of the cond was none, or was a number or something. + * But it generally seems inconsistent. The expression propagation step will have to deal with + * this. + */ +void clean_up_cond_no_else(std::shared_ptr* ir, LinkedObjectFile& file) { + (void)file; + auto cne = dynamic_cast(ir->get()); + assert(cne); + for (size_t idx = 0; idx < cne->entries.size(); idx++) { + auto& e = cne->entries.at(idx); + if (e.cleaned) { + continue; + } + + auto jump_to_next = get_condition_branch(&e.condition); + assert(jump_to_next.first); + + if (jump_to_next.first->branch_delay.kind == BranchDelay::SET_REG_TRUE && + cne->entries.size() == 1) { + convert_cond_no_else_to_compare(ir); + } else { + assert(jump_to_next.first->branch_delay.kind == BranchDelay::SET_REG_FALSE || + jump_to_next.first->branch_delay.kind == BranchDelay::NOP); + assert(jump_to_next.first->condition.kind != Condition::ALWAYS); + + if (jump_to_next.first->branch_delay.kind == BranchDelay::SET_REG_FALSE) { + assert(!e.false_destination); + e.false_destination = jump_to_next.first->branch_delay.destination; + assert(e.false_destination); + } + + auto replacement = std::make_shared(jump_to_next.first->condition); + replacement->condition.invert(); + *(jump_to_next.second) = replacement; + e.cleaned = true; + + if (idx != cne->entries.size() - 1) { + auto jump_to_end = get_condition_branch(&e.body); + assert(jump_to_end.first); + assert(jump_to_end.first->branch_delay.kind == BranchDelay::NOP); + assert(jump_to_end.first->condition.kind == Condition::ALWAYS); + auto as_end_of_sequence = get_condition_branch_as_vector(e.body.get()); + if (as_end_of_sequence.first) { + assert(as_end_of_sequence.second->size() > 1); + as_end_of_sequence.second->pop_back(); + } else { + *(jump_to_end.second) = std::make_shared(); + } + } + } + } +} + +/*! + * Match for a (set! reg (math reg reg)) form + */ +bool is_int_math_3(IR* ir, + MatchParam kind, + MatchParam dst, + MatchParam src0, + MatchParam src1, + Register* dst_out = nullptr, + Register* src0_out = nullptr, + Register* src1_out = nullptr) { + // should be a set reg to int math 2 ir + auto set = dynamic_cast(ir); + if (!set) { + return false; + } + + // destination should be a register + auto dest = dynamic_cast(set->dst.get()); + if (!dest || dst != dest->reg) { + return false; + } + + auto math = dynamic_cast(set->src.get()); + if (!math || kind != math->kind) { + return false; + } + + auto arg0 = dynamic_cast(math->arg0.get()); + auto arg1 = dynamic_cast(math->arg1.get()); + + if (!arg0 || src0 != arg0->reg || !arg1 || src1 != arg1->reg) { + return false; + } + + // it's a match! + if (dst_out) { + *dst_out = dest->reg; + } + + if (src0_out) { + *src0_out = arg0->reg; + } + + if (src1_out) { + *src1_out = arg1->reg; + } + return true; +} + +/*! + * Are these IR's both the same register? False if either is not a register. + */ +bool is_same_reg(IR* a, IR* b) { + auto ar = dynamic_cast(a); + auto br = dynamic_cast(b); + return ar && br && ar->reg == br->reg; +} + +/*! + * Try to convert this SC Vertex into an abs (integer). + * Will return a converted abs IR if successful, or nullptr if its not possible + */ +std::shared_ptr try_sc_as_abs(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) { + if (vtx->entries.size() != 1) { + return nullptr; + } + + auto b0 = dynamic_cast(vtx->entries.at(0)); + if (!b0) { + return nullptr; + } + + // todo, seems possible to be a single op instead of a begin here. + auto b0_ptr = cfg_to_ir(f, file, b0); + auto b0_ir = dynamic_cast(b0_ptr.get()); + + auto branch = dynamic_cast(b0_ir->forms.back().get()); + if (!branch) { + return nullptr; + } + + // check the branch instruction + if (!branch->likely || branch->condition.kind != Condition::LESS_THAN_ZERO || + branch->branch_delay.kind != BranchDelay::NEGATE) { + return nullptr; + } + + auto input = branch->condition.src0; + auto output = branch->branch_delay.destination; + + assert(is_same_reg(input.get(), branch->branch_delay.source.get())); + + if (b0_ir->forms.size() == 1) { + // this is probably fine but happens to not occur in anything we try yet. + assert(false); + } else { + // remove the branch + b0_ir->forms.pop_back(); + // add the ash + b0_ir->forms.push_back(std::make_shared( + IR_Set::REG_64, output, std::make_shared(IR_IntMath1::ABS, input))); + return b0_ptr; + } + + return nullptr; +} + +/*! + * Attempt to convert a short circuit expression into an arithmetic shift. + * GOAL's shift function accepts positive/negative numbers to determine the direction + * of the shift. + */ +std::shared_ptr try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) { + if (vtx->entries.size() != 2) { + return nullptr; + } + + // todo, I think b0 could possibly be something more complicated, depending on how we order. + auto b0 = dynamic_cast(vtx->entries.at(0)); + auto b1 = dynamic_cast(vtx->entries.at(1)); + if (!b0 || !b1) { + return nullptr; + } + + // todo, seems possible to be a single op instead of a begin... + auto b0_ptr = cfg_to_ir(f, file, b0); + auto b0_ir = dynamic_cast(b0_ptr.get()); + + auto b1_ptr = cfg_to_ir(f, file, b1); + auto b1_ir = dynamic_cast(b1_ptr.get()); + + if (!b0_ir || !b1_ir) { + return nullptr; + } + + auto branch = dynamic_cast(b0_ir->forms.back().get()); + if (!branch || b1_ir->forms.size() != 2) { + return nullptr; + } + + // check the branch instruction + if (!branch->likely || branch->condition.kind != Condition::GEQ_ZERO_SIGNED || + branch->branch_delay.kind != BranchDelay::DSLLV) { + return nullptr; + } + + /* + * bgezl s5, L109 ; s5 is the shift amount + dsllv a0, a0, s5 ; a0 is both input and output here + + dsubu a1, r0, s5 ; a1 is a temp here + dsrav a0, a0, a1 ; a0 is both input and output here + */ + + auto sa_in = dynamic_cast(branch->condition.src0.get()); + assert(sa_in); + auto result = dynamic_cast(branch->branch_delay.destination.get()); + auto value_in = dynamic_cast(branch->branch_delay.source.get()); + auto sa_in2 = dynamic_cast(branch->branch_delay.source2.get()); + assert(result && value_in && sa_in2); + assert(sa_in->reg == sa_in2->reg); + + auto dsubu_candidate = b1_ir->forms.at(0); + auto dsrav_candidate = b1_ir->forms.at(1); + + Register clobber; + if (!is_int_math_3(dsubu_candidate.get(), IR_IntMath2::SUB, {}, make_gpr(Reg::R0), sa_in->reg, + &clobber)) { + return nullptr; + } + + assert(result); + assert(value_in); + + bool is_arith = is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_ARITH, result->reg, + value_in->reg, clobber); + bool is_logical = is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_LOGIC, + result->reg, value_in->reg, clobber); + + if (!is_arith && !is_logical) { + return nullptr; + } + + std::shared_ptr clobber_ir = nullptr; + auto dsubu_set = dynamic_cast(dsubu_candidate.get()); + auto dsrav_set = dynamic_cast(dsrav_candidate.get()); + if (clobber != result->reg) { + clobber_ir = dsubu_set->dst; + } + + std::shared_ptr dest_ir = branch->branch_delay.destination; + std::shared_ptr shift_ir = branch->condition.src0; + std::shared_ptr value_ir = dynamic_cast(dsrav_set->src.get())->arg0; + if (b0_ir->forms.size() == 1) { + // this is probably fine but happens to not occur in anything we try yet. + assert(false); + } else { + // remove the branch + b0_ir->forms.pop_back(); + // add the ash + b0_ir->forms.push_back(std::make_shared( + IR_Set::REG_64, dest_ir, + std::make_shared(shift_ir, value_ir, clobber_ir, is_arith))); + return b0_ptr; + } + + return nullptr; +} + +/*! + * Try to convert a short circuiting expression into a "type-of" expression. + * We do this before attempting the normal and/or expressions. + */ +std::shared_ptr try_sc_as_type_of(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) { + // the assembly looks like this: + /* + dsll32 v1, a0, 29 ;; (set! v1 (shl a0 61)) + beql v1, r0, L60 ;; (bl! (= v1 r0) L60 (unknown-branch-delay)) + lw v1, binteger(s7) + + bgtzl v1, L60 ;; (bl! (>0.s v1) L60 (unknown-branch-delay)) + lw v1, pair(s7) + + lwu v1, -4(a0) ;; (set! v1 (l.wu (+.i a0 -4))) + L60: + */ + + // some of these checks may be a little bit overkill but it's a nice way to sanity check that + // we have actually decoded everything correctly. + if (vtx->entries.size() != 3) { + return nullptr; + } + + auto b0 = dynamic_cast(vtx->entries.at(0)); + auto b1 = dynamic_cast(vtx->entries.at(1)); + auto b2 = dynamic_cast(vtx->entries.at(2)); + + if (!b0 || !b1 || !b2) { + return nullptr; + } + + auto b0_ptr = cfg_to_ir(f, file, b0); + auto b0_ir = dynamic_cast(b0_ptr.get()); + + auto b1_ptr = cfg_to_ir(f, file, b1); + auto b1_ir = dynamic_cast(b1_ptr.get()); + + auto b2_ptr = cfg_to_ir(f, file, b2); + auto b2_ir = dynamic_cast(b2_ptr.get()); + if (!b0_ir || !b1_ir || !b2_ir) { + return nullptr; + } + + auto set_shift = dynamic_cast(b0_ir->forms.at(b0_ir->forms.size() - 2).get()); + if (!set_shift) { + return nullptr; + } + + auto temp_reg0 = dynamic_cast(set_shift->dst.get()); + if (!temp_reg0) { + return nullptr; + } + + auto shift = dynamic_cast(set_shift->src.get()); + if (!shift || shift->kind != IR_IntMath2::LEFT_SHIFT) { + return nullptr; + } + auto src_reg = dynamic_cast(shift->arg0.get()); + auto sa = dynamic_cast(shift->arg1.get()); + if (!src_reg || !sa || sa->value != 61) { + return nullptr; + } + + auto first_branch = dynamic_cast(b0_ir->forms.back().get()); + auto second_branch = b1_ir; + auto else_case = b2_ir; + + if (!first_branch || first_branch->branch_delay.kind != BranchDelay::SET_BINTEGER || + first_branch->condition.kind != Condition::ZERO || !first_branch->likely) { + return nullptr; + } + auto temp_reg = dynamic_cast(first_branch->condition.src0.get()); + assert(temp_reg); + assert(temp_reg->reg == temp_reg0->reg); + auto dst_reg = dynamic_cast(first_branch->branch_delay.destination.get()); + assert(dst_reg); + + if (!second_branch || second_branch->branch_delay.kind != BranchDelay::SET_PAIR || + second_branch->condition.kind != Condition::GREATER_THAN_ZERO_SIGNED || + !second_branch->likely) { + return nullptr; + } + + // check we agree on destination register. + auto dst_reg2 = dynamic_cast(second_branch->branch_delay.destination.get()); + assert(dst_reg2->reg == dst_reg->reg); + + // else case is a lwu to grab the type from a basic + assert(else_case); + auto dst_reg3 = dynamic_cast(else_case->dst.get()); + assert(dst_reg3); + assert(dst_reg3->reg == dst_reg->reg); + auto load_op = dynamic_cast(else_case->src.get()); + if (!load_op || load_op->kind != IR_Load::UNSIGNED || load_op->size != 4) { + return nullptr; + } + auto load_loc = dynamic_cast(load_op->location.get()); + if (!load_loc || load_loc->kind != IR_IntMath2::ADD) { + return nullptr; + } + auto src_reg3 = dynamic_cast(load_loc->arg0.get()); + auto offset = dynamic_cast(load_loc->arg1.get()); + if (!src_reg3 || !offset) { + return nullptr; + } + + assert(src_reg3->reg == src_reg->reg); + assert(offset->value == -4); + + std::shared_ptr clobber = nullptr; + if (temp_reg->reg != dst_reg->reg) { + clobber = first_branch->condition.src0; + } + if (b0_ir->forms.size() == 2) { + return std::make_shared(IR_Set::REG_64, else_case->dst, + std::make_shared(shift->arg0, clobber)); + } else { + // remove the branch + b0_ir->forms.pop_back(); + // remove the shift + b0_ir->forms.pop_back(); + // add the type-of + b0_ir->forms.push_back(std::make_shared( + IR_Set::REG_64, else_case->dst, std::make_shared(shift->arg0, clobber))); + return b0_ptr; + } +} + +std::shared_ptr merge_cond_else_with_sc_cond(CondWithElse* cwe, + const std::shared_ptr& else_ir, + Function& f, + LinkedObjectFile& file) { + auto as_seq = dynamic_cast(else_ir.get()); + if (!as_seq || as_seq->forms.size() != 2) { + return nullptr; + } + + auto first = dynamic_cast(as_seq->forms.at(0).get()); + auto second = dynamic_cast(as_seq->forms.at(1).get()); + if (!first || !second) { + return nullptr; + } + + std::vector entries; + for (auto& x : cwe->entries) { + IR_Cond::Entry e; + e.condition = cfg_to_ir(f, file, x.condition); + e.body = cfg_to_ir(f, file, x.body); + entries.push_back(std::move(e)); + } + + auto first_condition = std::make_shared(); + first_condition->forms.push_back(as_seq->forms.at(0)); + first_condition->forms.push_back(second->entries.front().condition); + + second->entries.front().condition = first_condition; + + for (auto& x : second->entries) { + entries.push_back(x); + } + std::shared_ptr result = std::make_shared(entries); + clean_up_cond_no_else(&result, file); + return result; +} + +/*! + * Main CFG vertex to IR conversion. Will pull basic IR ops from the provided function as needed. + */ +std::shared_ptr cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx) { + if (dynamic_cast(vtx)) { + auto* bv = dynamic_cast(vtx); + auto& block = f.basic_blocks.at(bv->block_id); + std::vector> irs; + IR* last = nullptr; + for (int instr = block.start_word; instr < block.end_word; instr++) { + auto got = f.get_basic_op_at_instr(instr); + if (got.get() == last) { + continue; + } + last = got.get(); + irs.push_back(got); + } + + if (irs.size() == 1) { + return irs.front(); + } else { + return std::make_shared(irs); + } + + } else if (dynamic_cast(vtx)) { + auto* sv = dynamic_cast(vtx); + + std::vector> irs; + insert_cfg_into_list(f, file, &irs, sv); + + return std::make_shared(irs); + } else if (dynamic_cast(vtx)) { + auto wvtx = dynamic_cast(vtx); + auto result = std::make_shared(cfg_to_ir(f, file, wvtx->condition), + cfg_to_ir(f, file, wvtx->body)); + return result; + } else if (dynamic_cast(vtx)) { + auto* cvtx = dynamic_cast(vtx); + + // the cfg analysis pass may recognize some things out of order, which can cause + // fake nesting. This is actually a problem at this point because it can turn a normal + // cond into a cond with else, which emits different instructions. This attempts to recognize + // an else which is actually more cases and compacts it into a single statement. At this point + // I don't know if this is sufficient to catch all cases. it may even recognize the wrong + // thing in some cases... maybe we should check the delay slot instead? + auto else_ir = cfg_to_ir(f, file, cvtx->else_vtx); + auto fancy_compact_result = merge_cond_else_with_sc_cond(cvtx, else_ir, f, file); + if (fancy_compact_result) { + return fancy_compact_result; + } + + if (dynamic_cast(else_ir.get())) { + auto extra_cond = dynamic_cast(else_ir.get()); + std::vector entries; + for (auto& x : cvtx->entries) { + IR_Cond::Entry e; + e.condition = cfg_to_ir(f, file, x.condition); + e.body = cfg_to_ir(f, file, x.body); + entries.push_back(std::move(e)); + } + for (auto& x : extra_cond->entries) { + entries.push_back(x); + } + std::shared_ptr result = std::make_shared(entries); + clean_up_cond_no_else(&result, file); + return result; + } else { + std::vector entries; + for (auto& x : cvtx->entries) { + IR_CondWithElse::Entry e; + e.condition = cfg_to_ir(f, file, x.condition); + e.body = cfg_to_ir(f, file, x.body); + entries.push_back(std::move(e)); + } + std::shared_ptr result = std::make_shared(entries, else_ir); + clean_up_cond_with_else(&result, file); + return result; + } + } else if (dynamic_cast(vtx)) { + auto* svtx = dynamic_cast(vtx); + // try as a type of expression first + auto as_type_of = try_sc_as_type_of(f, file, svtx); + if (as_type_of) { + return as_type_of; + } + + auto as_ash = try_sc_as_ash(f, file, svtx); + if (as_ash) { + return as_ash; + } + + auto as_abs = try_sc_as_abs(f, file, svtx); + if (as_abs) { + return as_abs; + } + // now try as a normal and/or + std::vector entries; + for (auto& x : svtx->entries) { + IR_ShortCircuit::Entry e; + e.condition = cfg_to_ir(f, file, x); + entries.push_back(e); + } + auto result = std::make_shared(entries); + clean_up_sc(result, file); + // todo clean these into real and/or. + return result; + } else if (dynamic_cast(vtx)) { + auto* cvtx = dynamic_cast(vtx); + std::vector entries; + for (auto& x : cvtx->entries) { + IR_Cond::Entry e; + e.condition = cfg_to_ir(f, file, x.condition); + e.body = cfg_to_ir(f, file, x.body); + entries.push_back(std::move(e)); + } + std::shared_ptr result = std::make_shared(entries); + clean_up_cond_no_else(&result, file); + return result; + } + + throw std::runtime_error("not yet implemented IR conversion."); + return nullptr; +} + +/*! + * Post processing pass to clean up while loops - annoyingly the block before a while loop + * has a jump to the condition branch that we need to remove. This currently happens after all + * conversion but this may need to be revisited depending on the final order of simplifications. + */ +void clean_up_while_loops(IR_Begin* sequence, LinkedObjectFile& file) { + (void)file; + std::vector to_remove; // the list of branches to remove by index in this sequence + for (size_t i = 0; i < sequence->forms.size(); i++) { + auto* form_as_while = dynamic_cast(sequence->forms.at(i).get()); + if (form_as_while) { + assert(i != 0); + auto prev_as_branch = dynamic_cast(sequence->forms.at(i - 1).get()); + assert(prev_as_branch); + // printf("got while intro branch %s\n", prev_as_branch->print(file).c_str()); + // this should be an always jump. We'll assume that the CFG builder successfully checked + // the brach destination, but we will check the condition. + assert(prev_as_branch->condition.kind == Condition::ALWAYS); + assert(prev_as_branch->branch_delay.kind == BranchDelay::NOP); + to_remove.push_back(i - 1); + + // now we should try to find the condition branch: + + auto condition_branch = get_condition_branch(&form_as_while->condition); + + assert(condition_branch.first); + assert(condition_branch.first->branch_delay.kind == BranchDelay::NOP); + // printf("got while condition branch %s\n", condition_branch.first->print(file).c_str()); + auto replacement = std::make_shared(condition_branch.first->condition); + *(condition_branch.second) = replacement; + } + } + + // remove the implied forward always branches. + for (int i = int(to_remove.size()); i-- > 0;) { + auto idx = to_remove.at(i); + assert(dynamic_cast(sequence->forms.at(idx).get())); + sequence->forms.erase(sequence->forms.begin() + idx); + } +} +} // namespace + +/*! + * Use a control flow graph to build a single IR representing a function. + * This should be done after basic ops are added and before typing, variable splitting, and + * expression compaction. + */ +std::shared_ptr build_cfg_ir(Function& function, + ControlFlowGraph& cfg, + LinkedObjectFile& file) { + // printf("build cfg ir\n"); + if (!cfg.is_fully_resolved()) { + return nullptr; + } + + try { + auto top_level = cfg.get_single_top_level(); + // todo, we should apply transformations for fixing up branch instructions for each IR. + // and possibly annotate the IR control flow structure so that we can determine if its and/or + // or whatever. This may require rejecting a huge number of inline assembly functions, and + // possibly resolving the min/max/ash issue. + // auto ir = cfg_to_ir(function, file, top_level); + auto ir = std::make_shared(); + insert_cfg_into_list(function, file, &ir->forms, top_level); + auto all_children = ir->get_all_ir(file); + all_children.push_back(ir); + for (auto& child : all_children) { + // printf("child is %s\n", child->print(file).c_str()); + auto as_begin = dynamic_cast(child.get()); + if (as_begin) { + clean_up_while_loops(as_begin, file); + } + } + return ir; + } catch (std::runtime_error& e) { + return nullptr; + } +} \ No newline at end of file diff --git a/decompiler/IR/CfgBuilder.h b/decompiler/IR/CfgBuilder.h new file mode 100644 index 0000000000..b15b08b626 --- /dev/null +++ b/decompiler/IR/CfgBuilder.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class IR; +class Function; +class LinkedObjectFile; +class ControlFlowGraph; + +std::shared_ptr build_cfg_ir(Function& function, ControlFlowGraph& cfg, LinkedObjectFile& file); \ No newline at end of file diff --git a/decompiler/IR/IR.cpp b/decompiler/IR/IR.cpp new file mode 100644 index 0000000000..277e51b6e7 --- /dev/null +++ b/decompiler/IR/IR.cpp @@ -0,0 +1,803 @@ +#include "IR.h" +#include "decompiler/ObjectFile/LinkedObjectFile.h" + +std::vector> IR::get_all_ir(LinkedObjectFile& file) const { + (void)file; + std::vector> 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>* 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>* 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>* 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>* 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>* 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>* 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>* 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>* output) const { + output->push_back(arg0); + output->push_back(arg1); +} + +void IR_FloatMath1::get_children(std::vector>* 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>* 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>* 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>* 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>* 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>* 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>* 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>* 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>* 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>* output) const { + (void)output; +} + +void IR_Suspend::get_children(std::vector>* output) const { + (void)output; +} + +goos::Object IR_Begin::to_form(const LinkedObjectFile& file) const { + std::vector 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>* output) const { + for (auto& x : forms) { + output->push_back(x); + } +} + +namespace { +void print_inlining_begin(std::vector* output, IR* ir, const LinkedObjectFile& file) { + auto as_begin = dynamic_cast(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(in); +} +} // namespace + +goos::Object IR_WhileLoop::to_form(const LinkedObjectFile& file) const { + std::vector 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>* 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 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 list; + list.push_back(pretty_print::to_symbol("cond")); + for (auto& e : entries) { + std::vector 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 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>* 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 list = {pretty_print::to_symbol("type-of"), object->to_form(file)}; + return pretty_print::build_list(list); +} + +void IR_GetRuntimeType::get_children(std::vector>* 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 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 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 list; + list.push_back(pretty_print::to_symbol("cond")); + for (auto& e : entries) { + std::vector 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>* 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 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>* 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>* output) const { + output->push_back(value); + output->push_back(shift_amount); +} diff --git a/decompiler/IR/IR.h b/decompiler/IR/IR.h new file mode 100644 index 0000000000..4ea0b2ac72 --- /dev/null +++ b/decompiler/IR/IR.h @@ -0,0 +1,373 @@ +#ifndef JAK_IR_H +#define JAK_IR_H + +#include +#include +#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> get_all_ir(LinkedObjectFile& file) const; + std::string print(const LinkedObjectFile& file) const; + virtual void get_children(std::vector>* 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>* 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>* 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 _dst, std::shared_ptr _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>* output) const override; + std::shared_ptr dst, src; + std::shared_ptr clobber = nullptr; +}; + +class IR_Store : public IR_Set { + public: + enum Kind { INTEGER, FLOAT } kind; + IR_Store(Kind _kind, std::shared_ptr _dst, std::shared_ptr _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>* 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>* 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>* output) const override; +}; + +class IR_Load : public IR { + public: + enum Kind { UNSIGNED, SIGNED, FLOAT } kind; + + IR_Load(Kind _kind, int _size, std::shared_ptr _location) + : kind(_kind), size(_size), location(std::move(_location)) {} + int size; + std::shared_ptr location; + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* 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 _arg0, std::shared_ptr _arg1) + : kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {} + std::shared_ptr arg0, arg1; + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* 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 _arg) : kind(_kind), arg(std::move(_arg)) {} + std::shared_ptr arg; + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* 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 _arg0, std::shared_ptr _arg1) + : kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {} + std::shared_ptr arg0, arg1; + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* output) const override; +}; + +class IR_IntMath1 : public IR { + public: + enum Kind { NOT, ABS } kind; + IR_IntMath1(Kind _kind, std::shared_ptr _arg) : kind(_kind), arg(std::move(_arg)) {} + std::shared_ptr arg; + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* 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>* 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>* 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 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>* 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 _src0, + std::shared_ptr _src1, + std::shared_ptr _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 src0, src1, clobber; + void get_children(std::vector>* 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>* 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>* 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>* 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>* output) const override; +}; + +class IR_Begin : public IR { + public: + IR_Begin() = default; + explicit IR_Begin(const std::vector>& _forms) : forms(std::move(_forms)) {} + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* output) const override; + std::vector> forms; +}; + +class IR_WhileLoop : public IR { + public: + IR_WhileLoop(std::shared_ptr _condition, std::shared_ptr _body) + : condition(std::move(_condition)), body(std::move(_body)) {} + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* output) const override; + std::shared_ptr condition, body; +}; + +class IR_CondWithElse : public IR { + public: + struct Entry { + std::shared_ptr condition = nullptr; + std::shared_ptr body = nullptr; + bool cleaned = false; + }; + std::vector entries; + std::shared_ptr else_ir; + IR_CondWithElse(std::vector _entries, std::shared_ptr _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>* 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 condition = nullptr; + std::shared_ptr body = nullptr; + std::shared_ptr false_destination = nullptr; + bool cleaned = false; + }; + std::vector entries; + explicit IR_Cond(std::vector _entries) : entries(std::move(_entries)) {} + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* output) const override; +}; + +// this will work on pairs, bintegers, or basics +class IR_GetRuntimeType : public IR { + public: + std::shared_ptr object, clobber; + IR_GetRuntimeType(std::shared_ptr _object, std::shared_ptr _clobber) + : object(std::move(_object)), clobber(std::move(_clobber)) {} + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* output) const override; +}; + +class IR_ShortCircuit : public IR { + public: + struct Entry { + std::shared_ptr condition = nullptr; + std::shared_ptr output = nullptr; // where the delay slot writes to. + bool cleaned = false; + }; + + enum Kind { UNKNOWN, AND, OR } kind = UNKNOWN; + + std::shared_ptr final_result = nullptr; // the register that the final result goes in. + + std::vector entries; + explicit IR_ShortCircuit(std::vector _entries) : entries(std::move(_entries)) {} + goos::Object to_form(const LinkedObjectFile& file) const override; + void get_children(std::vector>* output) const override; +}; + +class IR_Ash : public IR { + public: + std::shared_ptr shift_amount, value, clobber; + bool is_signed = true; + IR_Ash(std::shared_ptr _shift_amount, + std::shared_ptr _value, + std::shared_ptr _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>* output) const override; +}; + +#endif // JAK_IR_H diff --git a/decompiler/ObjectFile/LinkedObjectFile.cpp b/decompiler/ObjectFile/LinkedObjectFile.cpp index be9eaa1de7..58e86acc15 100644 --- a/decompiler/ObjectFile/LinkedObjectFile.cpp +++ b/decompiler/ObjectFile/LinkedObjectFile.cpp @@ -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 LinkedObjectFile::to_form_script(int seg, - int word_idx, - std::vector& seen) { +goos::Object LinkedObjectFile::to_form_script(int seg, int word_idx, std::vector& 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(); - 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 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 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(); - 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 LinkedObjectFile::to_form_script_object(int seg, - int byte_idx, - std::vector& seen) { - std::shared_ptr result; +goos::Object LinkedObjectFile::to_form_script_object(int seg, + int byte_idx, + std::vector& seen) { + goos::Object result; switch (byte_idx & 7) { case 0: @@ -812,10 +825,10 @@ std::shared_ptr 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 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); diff --git a/decompiler/ObjectFile/LinkedObjectFile.h b/decompiler/ObjectFile/LinkedObjectFile.h index 7b535efbf9..7c18c6c4e5 100644 --- a/decompiler/ObjectFile/LinkedObjectFile.h +++ b/decompiler/ObjectFile/LinkedObjectFile.h @@ -15,7 +15,7 @@ #include #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