diff --git a/decompiler/Function/Function.h b/decompiler/Function/Function.h index b8aa7b96ff..59dfc58aae 100644 --- a/decompiler/Function/Function.h +++ b/decompiler/Function/Function.h @@ -168,6 +168,7 @@ class Function { Form* top_form = nullptr; std::string debug_form_string; bool print_debug_forms = false; + bool expressions_succeeded = false; } ir2; private: diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index 75e27384cf..df51515cf1 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -2241,10 +2241,14 @@ void ArrayFieldAccess::update_with_val(Form* new_val, auto sll_matcher = Matcher::fixed_op(FixedOperatorKind::SHL, {reg1_matcher, Matcher::integer(power_of_two)}); sll_matcher = Matcher::match_or({Matcher::cast("uint", sll_matcher), sll_matcher}); - auto matcher = Matcher::fixed_op(FixedOperatorKind::ADDITION, {reg0_matcher, sll_matcher}); + auto matcher = Matcher::match_or( + {Matcher::fixed_op(FixedOperatorKind::ADDITION, {reg0_matcher, sll_matcher}), + Matcher::fixed_op(FixedOperatorKind::ADDITION_PTR, {reg0_matcher, sll_matcher})}); auto match_result = match(matcher, new_val); if (!match_result.matched) { - matcher = Matcher::fixed_op(FixedOperatorKind::ADDITION, {sll_matcher, reg0_matcher}); + matcher = Matcher::match_or( + {Matcher::fixed_op(FixedOperatorKind::ADDITION, {sll_matcher, reg0_matcher}), + Matcher::fixed_op(FixedOperatorKind::ADDITION_PTR, {sll_matcher, reg0_matcher})}); match_result = match(matcher, new_val); if (!match_result.matched) { fmt::print("power {}\n", power_of_two); @@ -2405,7 +2409,6 @@ void TypeOfElement::update_from_stack(const Env& env, value->update_children_from_stack(env, pool, stack, allow_side_effects); result->push_back(this); } - //////////////////////// // EmptyElement //////////////////////// @@ -2434,8 +2437,10 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool, // pop the value and the original auto popped = pop_to_forms({old_value, source}, env, pool, stack, true); if (!is_symbol_true(popped.at(0))) { - throw std::runtime_error("Got unrecognized ConditionalMoveFalseElement original: " + - popped.at(0)->to_string(env)); + lg::warn("Failed to ConditionalMoveFalseElement::push_to_stack"); + stack.push_value_to_reg(source, popped.at(1), true); + stack.push_form_element(this, true); + return; } stack.push_value_to_reg(dest, pool.alloc_single_element_form( diff --git a/decompiler/ObjectFile/ObjectFileDB.h b/decompiler/ObjectFile/ObjectFileDB.h index 35290e983b..562db95062 100644 --- a/decompiler/ObjectFile/ObjectFileDB.h +++ b/decompiler/ObjectFile/ObjectFileDB.h @@ -78,7 +78,8 @@ class ObjectFileDB { void ir2_write_results(const std::string& output_dir); std::string ir2_to_file(ObjectFileData& data); std::string ir2_function_to_string(ObjectFileData& data, Function& function, int seg); - std::string ir2_final_out(ObjectFileData& data); + std::string ir2_final_out(ObjectFileData& data, + const std::unordered_set& skip_functions = {}); void process_tpages(); std::string process_game_count_file(); diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index 5b11714964..7f1c40c3dc 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -46,8 +46,11 @@ void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir) { lg::info("Expression building..."); ir2_build_expressions(); } - lg::info("Writing results..."); - ir2_write_results(output_dir); + + if (!output_dir.empty()) { + lg::info("Writing results..."); + ir2_write_results(output_dir); + } } /*! @@ -411,8 +414,7 @@ void ObjectFileDB::ir2_build_expressions() { if (convert_to_expressions(func.ir2.top_form, *func.ir2.form_pool, func, dts)) { successful++; func.ir2.print_debug_forms = true; - // auto end = final_defun_out(func, func.ir2.env, dts); - // fmt::print("{}\n\n", end); + func.ir2.expressions_succeeded = true; } } }); @@ -782,14 +784,15 @@ bool ObjectFileDB::lookup_function_type(const FunctionName& name, return false; } -std::string ObjectFileDB::ir2_final_out(ObjectFileData& data) { +std::string ObjectFileDB::ir2_final_out(ObjectFileData& data, + const std::unordered_set& skip_functions) { if (data.obj_version == 3) { std::string result; result += ";;-*-Lisp-*-\n"; result += "(in-package goal)\n\n"; assert(data.linked_data.functions_by_seg.at(TOP_LEVEL_SEGMENT).size() == 1); auto top_level = data.linked_data.functions_by_seg.at(TOP_LEVEL_SEGMENT).at(0); - result += write_from_top_level(top_level, dts, data.linked_data); + result += write_from_top_level(top_level, dts, data.linked_data, skip_functions); result += "\n\n"; return result; } else { diff --git a/decompiler/analysis/final_output.cpp b/decompiler/analysis/final_output.cpp index 23504b4299..fa78bb4139 100644 --- a/decompiler/analysis/final_output.cpp +++ b/decompiler/analysis/final_output.cpp @@ -153,7 +153,8 @@ std::string careful_function_to_string( std::string write_from_top_level(const Function& top_level, const DecompilerTypeSystem& dts, - const LinkedObjectFile& file) { + const LinkedObjectFile& file, + const std::unordered_set& skip_functions) { auto top_form = top_level.ir2.top_form; if (!top_form) { return ";; ERROR: top level function was not converted to expressions. Cannot decompile.\n\n"; @@ -212,7 +213,11 @@ std::string write_from_top_level(const Function& top_level, something_matched = true; result += fmt::format(";; definition for function {}\n", global_match_result.maps.strings.at(func_name)); - result += careful_function_to_string(func, dts); + if (skip_functions.find(func->guessed_name.to_string()) == skip_functions.end()) { + result += careful_function_to_string(func, dts); + } else { + result += ";; skipped.\n\n"; + } } } @@ -224,7 +229,11 @@ std::string write_from_top_level(const Function& top_level, something_matched = true; result += fmt::format(";; definition for method of type {}\n", method_match_result.maps.strings.at(type_name)); - result += careful_function_to_string(func, dts); + if (skip_functions.find(func->guessed_name.to_string()) == skip_functions.end()) { + result += careful_function_to_string(func, dts); + } else { + result += ";; skipped.\n\n"; + } } } } @@ -238,8 +247,8 @@ std::string write_from_top_level(const Function& top_level, result += dts.ts.generate_deftype(dts.ts.lookup_type(name)); result += "\n\n"; } else { - result += - fmt::format(";; type {} defintion, but it is unknown to the decompiler\n\n", name); + result += fmt::format( + ";; type {} is defined here, but it is unknown to the decompiler\n\n", name); } something_matched = true; } @@ -256,7 +265,11 @@ std::string write_from_top_level(const Function& top_level, something_matched = true; result += fmt::format(";; definition (debug) for function {}\n", debug_match_result.maps.strings.at(0)); - result += careful_function_to_string(func, dts, FunctionDefSpecials::DEFUN_DEBUG); + if (skip_functions.find(func->guessed_name.to_string()) == skip_functions.end()) { + result += careful_function_to_string(func, dts, FunctionDefSpecials::DEFUN_DEBUG); + } else { + result += ";; skipped.\n\n"; + } } } } diff --git a/decompiler/analysis/final_output.h b/decompiler/analysis/final_output.h index 1a345a2669..2d739a9671 100644 --- a/decompiler/analysis/final_output.h +++ b/decompiler/analysis/final_output.h @@ -12,5 +12,6 @@ std::string final_defun_out(const Function& func, FunctionDefSpecials special_mode = FunctionDefSpecials::NONE); std::string write_from_top_level(const Function& top_level, const DecompilerTypeSystem& dts, - const LinkedObjectFile& file); + const LinkedObjectFile& file, + const std::unordered_set& skip_functions = {}); } // namespace decompiler diff --git a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc index ee7a990603..7439128384 100644 --- a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc @@ -1,4 +1,8 @@ { + "gcommon":[ + ["L345", "float", true] + ], + "math":[ ["L41", "float", true], ["L34", "float", true], diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index f0bf7c1a6f..80ad8838f8 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -288,11 +288,26 @@ bool Compiler::connect_to_target() { return true; } +/*! + * Just run the front end on a string. Will not do register allocation or code generation. + * Useful for typechecking or running strings that invoke the compiler again. + */ void Compiler::run_front_end_on_string(const std::string& src) { auto code = m_goos.reader.read_from_string({src}); compile_object_file("run-on-string", code, true); } +/*! + * Run the entire compilation process on the input source code. Will generate an object file, but + * won't save it anywhere. + */ +void Compiler::run_full_compiler_on_string_no_save(const std::string& src) { + auto code = m_goos.reader.read_from_string({src}); + auto compiled = compile_object_file("run-on-string", code, true); + color_object_file(compiled); + codegen_object_file(compiled); +} + std::vector Compiler::run_test_no_load(const std::string& source_code) { auto code = m_goos.reader.read_from_file({source_code}); compile_object_file("test-code", code, true); diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index e4513659c3..9f1581ea26 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -38,6 +38,7 @@ class Compiler { std::vector run_test_no_load(const std::string& source_code); void compile_and_send_from_string(const std::string& source_code); void run_front_end_on_string(const std::string& src); + void run_full_compiler_on_string_no_save(const std::string& src); void shutdown_target(); void enable_throw_on_redefines() { m_throw_on_define_extern_redefinition = true; } Debugger& get_debugger() { return m_debugger; } diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index b6c77bc5ea..606a240345 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -197,7 +197,7 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re /*! * Simple help / documentation command */ -Val* Compiler::compile_repl_help(const goos::Object& form, const goos::Object& rest, Env* env) { +Val* Compiler::compile_repl_help(const goos::Object&, const goos::Object&, Env*) { fmt::print(fmt::emphasis::bold, "\nREPL Controls:\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(e)\n"); fmt::print(" - Exit the compiler once the current REPL command is finished\n"); diff --git a/goalc/main.cpp b/goalc/main.cpp index f03f928e85..40f0d0362e 100644 --- a/goalc/main.cpp +++ b/goalc/main.cpp @@ -4,8 +4,8 @@ #include "common/util/FileUtil.h" #include "common/log/log.h" -#include "third-party/fmt/core.h"; -#include "third-party/fmt/color.h"; +#include "third-party/fmt/core.h" +#include "third-party/fmt/color.h" #include "common/goos/ReplHistory.h" @@ -61,7 +61,7 @@ int main(int argc, char** argv) { fmt::print(" for help with common commands and REPL usage.\n"); fmt::print("Run "); fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt)"); - fmt::print(" to connect to the local listener.\n"); + fmt::print(" to connect to the local target.\n"); ReplHistory::repl_load_history(); if (argument.empty()) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 76164522e0..43ceaab5fc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,7 @@ set(CMAKE_CXX_STANDARD 17) include(${CMAKE_CURRENT_LIST_DIR}/goalc/CMakeLists.txt) +include(${CMAKE_CURRENT_LIST_DIR}/offline/CMakeLists.txt) add_executable(goalc-test ${CMAKE_CURRENT_LIST_DIR}/test_main.cpp diff --git a/test/decompiler/reference/gcommon_REF.gc b/test/decompiler/reference/gcommon_REF.gc new file mode 100644 index 0000000000..e62d9a3c70 --- /dev/null +++ b/test/decompiler/reference/gcommon_REF.gc @@ -0,0 +1,1342 @@ +;;-*-Lisp-*- +(in-package goal) + +;; definition for function identity +(defun identity ((obj object)) + obj + ) + +;; definition for function 1/ +(defun 1/ ((x float)) + (/ 1.000000 x) + ) + +;; definition for function + +(defun + ((x int) (y int)) + (+ x y) + ) + +;; definition for function - +(defun - ((x int) (y int)) + (- x y) + ) + +;; definition for function * +(defun * ((x int) (y int)) + (* x y) + ) + +;; definition for function / +(defun / ((x int) (y int)) + (/ x y) + ) + +;; definition for function ash +(defun ash ((value int) (shift-amount int)) + (ash value shift-amount) + ) + +;; definition for function mod +(defun mod ((x int) (y int)) + (mod x y) + ) + +;; definition for function rem +(defun rem ((x int) (y int)) + (mod x y) + ) + +;; definition for function abs +(defun abs ((x int)) + (local-vars (v0-0 int)) + (set! v0-0 x) + (abs v0-0) + ) + +;; definition for function min +(defun min ((x int) (y int)) + (min x y) + ) + +;; definition for function max +(defun max ((x int) (y int)) + (max x y) + ) + +;; definition for function logior +(defun logior ((x int) (y int)) + (logior x y) + ) + +;; definition for function logand +(defun logand ((x int) (y int)) + (logand x y) + ) + +;; definition for function lognor +(defun lognor ((x int) (y int)) + (lognor x y) + ) + +;; definition for function logxor +(defun logxor ((x int) (y int)) + (logxor x y) + ) + +;; definition for function lognot +(defun lognot ((x int)) + (lognot x) + ) + +;; definition for function false-func +(defun false-func () #f) + +;; definition for function true-func +(defun true-func () '#t) + +;; failed to figure out what this is: +(set! format _format) + +;; definition of type vec4s +;; cannot generate deftype for vec4s, it is not a structure/basic (parent uint128) + + +;; definition for method of type vec4s +;; INFO: Return type mismatch int vs vec4s. +(defmethod inspect vec4s ((obj vec4s)) + (local-vars + (r0-0 none) + (v0-5 int) + (v1-0 int) + (v1-1 int) + (a0-1 symbol) + (a0-2 symbol) + (a0-4 symbol) + (a0-5 symbol) + (a1-0 string) + (a1-1 string) + (a1-3 string) + (a1-4 string) + (a2-0 int) + (a2-1 int) + (a2-3 int) + (t9-0 (function _varargs_ object)) + (t9-1 (function _varargs_ object)) + (t9-3 (function _varargs_ object)) + (t9-4 (function _varargs_ object)) + (gp-0 int) + ) + (.por gp-0 obj r0-0) + (set! t9-0 format) + (set! a0-1 '#t) + (set! a1-0 "[~8x] ~A~%") + (.por a2-0 gp-0 r0-0) + (t9-0 a0-1 a1-0 a2-0 'vec4s) + (set! t9-1 format) + (set! a0-2 '#t) + (set! a1-1 "~Tx: ~f~%") + (.sllv a2-1 gp-0 r0-0) + (t9-1 a0-2 a1-1 a2-1) + (format '#t "~Ty: ~f~%" (sar gp-0 32)) + (set! t9-3 format) + (set! a0-4 '#t) + (set! a1-3 "~Tz: ~f~%") + (.pcpyud v1-0 gp-0 r0-0) + (.sllv a2-3 v1-0 r0-0) + (t9-3 a0-4 a1-3 a2-3) + (set! t9-4 format) + (set! a0-5 '#t) + (set! a1-4 "~Tw: ~f~%") + (.pcpyud v1-1 gp-0 r0-0) + (t9-4 a0-5 a1-4 (sar v1-1 32)) + (.por v0-5 gp-0 r0-0) + (the-as vec4s v0-5) + ) + +;; definition for method of type vec4s +;; INFO: Return type mismatch int vs vec4s. +(defmethod print vec4s ((obj vec4s)) + (local-vars + (r0-0 none) + (v0-1 int) + (v1-0 int) + (v1-1 int) + (a0-1 symbol) + (a1-0 string) + (a2-0 int) + (a3-0 int) + (t0-0 int) + (t1-0 int) + (t2-0 int) + (t9-0 (function _varargs_ object)) + (gp-0 int) + ) + (.por gp-0 obj r0-0) + (set! t9-0 format) + (set! a0-1 '#t) + (set! a1-0 "#") + (.sllv a2-0 gp-0 r0-0) + (set! a3-0 (sar gp-0 32)) + (.pcpyud v1-0 gp-0 r0-0) + (.sllv t0-0 v1-0 r0-0) + (.pcpyud v1-1 gp-0 r0-0) + (set! t1-0 (sar v1-1 32)) + (.por t2-0 gp-0 r0-0) + (t9-0 a0-1 a1-0 a2-0 a3-0 t0-0 t1-0 t2-0) + (.por v0-1 gp-0 r0-0) + (the-as vec4s v0-1) + ) + +;; definition of type bfloat +(deftype bfloat (basic) + ((data float :offset-assert 4) + ) + :method-count-assert 9 + :size-assert #x8 + :flag-assert #x900000008 + ) + + +;; definition for method of type bfloat +(defmethod inspect bfloat ((obj bfloat)) + (format '#t "[~8x] ~A~%" obj (-> obj type)) + (format '#t "~Tdata: ~f~%" (-> obj data)) + obj + ) + +;; definition for method of type bfloat +(defmethod print bfloat ((obj bfloat)) + (format '#t "~f" (-> obj data)) + obj + ) + +;; definition for method of type type +;; INFO: Return type mismatch uint vs int. +(defmethod asize-of type ((obj type)) + (the-as int (logand (l.d L346) (+ (shl (-> obj allocated-length) 2) 43))) + ) + +;; definition for function basic-type? +(defun basic-type? ((obj basic) (parent-type type)) + (local-vars (obj-type type) (end-type type)) + (set! obj-type (-> obj type)) + (set! end-type object) + (until + (begin (set! obj-type (-> obj-type parent)) (= obj-type end-type)) + (if (= obj-type parent-type) (return '#t)) + ) + #f + ) + +;; definition for function type-type? +(defun type-type? ((child-type type) (parent-type type)) + (local-vars (end-type type)) + (set! end-type object) + (until + (begin + (set! child-type (-> child-type parent)) + (or (= child-type end-type) (zero? child-type)) + ) + (if (= child-type parent-type) (return '#t)) + ) + #f + ) + +;; definition for function find-parent-method +(defun find-parent-method ((child-type type) (method-id int)) + (local-vars (current-method function) (original-method function)) + (set! original-method (-> child-type method-table method-id)) + (until + (!= current-method original-method) + (if (= child-type object) (return nothing)) + (set! child-type (-> child-type parent)) + (set! current-method (-> child-type method-table method-id)) + (if (zero? current-method) (return nothing)) + ) + current-method + ) + +;; definition for function ref +(defun ref ((lst object) (index int)) + (local-vars (count int)) + (set! count 0) + (while (< count index) (nop!) (nop!) (set! lst (cdr lst)) (+! count 1)) + (car lst) + ) + +;; definition for method of type pair +(defmethod length pair ((obj pair)) + (local-vars (result int) (iter object)) + (cond + ((= obj '()) (set! result 0)) + (else + (set! iter (cdr obj)) + (set! result 1) + (while + (and (!= iter '()) (< (shl (the-as int iter) 62) 0)) + (+! result 1) + (set! iter (cdr iter)) + ) + ) + ) + result + ) + +;; definition for method of type pair +;; INFO: Return type mismatch uint vs int. +(defmethod asize-of pair ((obj pair)) + (the-as int (-> pair size)) + ) + +;; definition for function last +(defun last ((lst object)) + (local-vars (iter object)) + (set! iter lst) + (while (!= (cdr iter) '()) (nop!) (nop!) (set! iter (cdr iter))) + iter + ) + +;; definition for function member +(defun member ((obj object) (lst object)) + (local-vars (iter object)) + (set! iter lst) + (while (not (or (= iter '()) (= (car iter) obj))) (set! iter (cdr iter))) + (if (!= iter '()) iter) + ) + +;; definition for function nmember +(defun nmember ((obj basic) (lst object)) + (while + (not (or (= lst '()) (name= (the-as basic (car lst)) obj))) + (set! lst (cdr lst)) + ) + (if (!= lst '()) lst) + ) + +;; definition for function assoc +(defun assoc ((item object) (alist object)) + (local-vars (iter object)) + (set! iter alist) + (while + (not (or (= iter '()) (= (car (car iter)) item))) + (set! iter (cdr iter)) + ) + (if (!= iter '()) (car iter)) + ) + +;; definition for function assoce +(defun assoce ((item object) (alist object)) + (local-vars (iter object)) + (set! iter alist) + (while + (not (or (= iter '()) (= (car (car iter)) item) (= (car (car iter)) 'else))) + (set! iter (cdr iter)) + ) + (if (!= iter '()) (car iter)) + ) + +;; definition for function nassoc +(defun nassoc ((item-name string) (alist object)) + (local-vars (key object)) + (while + (not + (or + (= alist '()) + (begin + (set! key (car (car alist))) + (if + (pair? key) + (nmember item-name key) + (name= (the-as basic key) item-name) + ) + ) + ) + ) + (set! alist (cdr alist)) + ) + (if (!= alist '()) (car alist)) + ) + +;; definition for function nassoce +(defun nassoce ((item-name string) (alist object)) + (local-vars (key object)) + (while + (not + (or + (= alist '()) + (begin + (set! key (car (car alist))) + (if + (pair? key) + (nmember item-name key) + (or (name= (the-as basic key) item-name) (= key 'else)) + ) + ) + ) + ) + (set! alist (cdr alist)) + ) + (if (!= alist '()) (car alist)) + ) + +;; definition for function append! +(defun append! ((front object) (back object)) + (local-vars (iter object)) + (cond + ((= front '()) back) + (else + (set! iter front) + (while (!= (cdr iter) '()) (nop!) (nop!) (set! iter (cdr iter))) + (if (!= iter '()) (set! (cdr iter) back)) + front + ) + ) + ) + +;; definition for function delete! +;; INFO: Return type mismatch object vs pair. +(defun delete! ((item object) (lst object)) + (local-vars (iter-prev object) (iter object)) + (the-as + pair + (cond + ((= item (car lst)) (cdr lst)) + (else + (set! iter-prev lst) + (set! iter (cdr lst)) + (while + (not (or (= iter '()) (= (car iter) item))) + (set! iter-prev iter) + (set! iter (cdr iter)) + ) + (if (!= iter '()) (set! (cdr iter-prev) (cdr iter))) + lst + ) + ) + ) + ) + +;; definition for function delete-car! +(defun delete-car! ((item object) (lst object)) + (local-vars (iter-prev object) (iter object)) + (cond + ((= item (car (car lst))) (cdr lst)) + (else + (set! iter-prev lst) + (set! iter (cdr lst)) + (while + (not (or (= iter '()) (= (car (car iter)) item))) + (set! iter-prev iter) + (set! iter (cdr iter)) + ) + (if (!= iter '()) (set! (cdr iter-prev) (cdr iter))) + lst + ) + ) + ) + +;; definition for function insert-cons! +(defun insert-cons! ((kv object) (alist object)) + (local-vars (updated-list object)) + (set! updated-list (delete-car! (car kv) alist)) + (cons kv updated-list) + ) + +;; definition for function sort +(defun sort ((lst object) (compare-func (function object object object))) + (local-vars + (compare-result object) + (seoncd-elt object) + (first-elt object) + (iter object) + (unsorted-count int) + ) + (set! unsorted-count -1) + (while + (nonzero? unsorted-count) + (set! unsorted-count 0) + (set! iter lst) + (while + (not (or (= (cdr iter) '()) (>= (shl (the-as int (cdr iter)) 62) 0))) + (set! first-elt (car iter)) + (set! seoncd-elt (car (cdr iter))) + (set! compare-result (compare-func first-elt seoncd-elt)) + (when + (and + (or (not compare-result) (> (the-as int compare-result) 0)) + (!= compare-result '#t) + ) + (+! unsorted-count 1) + (set! (car iter) seoncd-elt) + (set! (car (cdr iter)) first-elt) + ) + (set! iter (cdr iter)) + ) + ) + lst + ) + +;; definition of type inline-array-class +(deftype inline-array-class (basic) + ((length int32 :offset-assert 4) + (allocated-length int32 :offset-assert 8) + (data uint8 :dynamic :offset-assert 12) + (_pad uint8 4 :offset-assert 12) + ) + :method-count-assert 9 + :size-assert #x10 + :flag-assert #x900000010 + (:methods + (new (symbol type int) _type_ 0) + ) + ) + + +;; definition for method of type inline-array-class +(defmethod inspect inline-array-class ((obj inline-array-class)) + (format '#t "[~8x] ~A~%" obj (-> obj type)) + (format '#t "~Tlength: ~D~%" (-> obj length)) + (format '#t "~Tallocated-length: ~D~%" (-> obj allocated-length)) + obj + ) + +;; definition for method of type inline-array-class +(defmethod + new + inline-array-class + ((allocation symbol) (type-to-make type) (size int)) + (local-vars (obj inline-array-class)) + (set! + obj + (object-new + allocation + type-to-make + (the-as + int + (+ + (-> type-to-make size) + (* (the-as uint size) (-> type-to-make heap-base)) + ) + ) + ) + ) + (when + (nonzero? obj) + (set! (-> obj length) size) + (set! (-> obj allocated-length) size) + ) + obj + ) + +;; definition for method of type inline-array-class +(defmethod length inline-array-class ((obj inline-array-class)) + (-> obj length) + ) + +;; definition for method of type inline-array-class +;; INFO: Return type mismatch uint vs int. +(defmethod asize-of inline-array-class ((obj inline-array-class)) + (the-as + int + (+ + (-> obj type size) + (the-as + uint + (* (-> obj allocated-length) (the-as int (-> obj type heap-base))) + ) + ) + ) + ) + +;; definition for method of type array +(defmethod + new + array + ((allocation symbol) (type-to-make type) (content-type type) (len int)) + (local-vars (obj array)) + (set! + obj + (object-new + allocation + type-to-make + (the-as + int + (+ + (-> type-to-make size) + (the-as + uint + (* len (if (type-type? content-type number) (-> content-type size) 4)) + ) + ) + ) + ) + ) + (set! (-> obj allocated-length) len) + (set! (-> obj length) len) + (set! (-> obj content-type) content-type) + obj + ) + +;; definition for method of type array +(defmethod print array ((obj array)) + (local-vars + (content-type-sym symbol) + (v1-42 pointer) + (a0-21 symbol) + (a1-11 string) + (a2-8 int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (t9-10 (function _varargs_ object)) + ) + (format '#t "#(") + (cond + ((type-type? (-> obj content-type) integer) + (set! content-type-sym (-> obj content-type symbol)) + (cond + ((= content-type-sym 'int32) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t (if (zero? i) "~D" " ~D") (-> (the-as (array int32) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'uint32) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t (if (zero? i) "~D" " ~D") (-> (the-as (array uint32) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'int64) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t (if (zero? i) "~D" " ~D") (-> (the-as (array int64) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'uint64) + (set! i 0) + (while + (< i (-> obj length)) + (format + '#t + (if (zero? i) "#x~X" " #x~X") + (-> (the-as (array uint64) obj) i) + ) + (+! i 1) + ) + ) + ((= content-type-sym 'int8) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t (if (zero? i) "~D" " ~D") (-> (the-as (array int8) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'uint8) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t (if (zero? i) "~D" " ~D") (-> (the-as (array uint8) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'int16) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t (if (zero? i) "~D" " ~D") (-> (the-as (array int16) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'uint16) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t (if (zero? i) "~D" " ~D") (-> (the-as (array uint16) obj) i)) + (+! i 1) + ) + ) + (else + (cond + ((or (= content-type-sym 'uint128) (= content-type-sym 'int128)) + (set! i 0) + (while + (< i (-> obj length)) + (set! t9-10 format) + (set! a0-21 '#t) + (set! a1-11 (if (zero? i) "#x~X" " #x~X")) + (set! v1-42 (+ (shl i 4) (the-as int (the-as (array uint128) obj)))) + (.lq a2-8 12 v1-42) + (t9-10 a0-21 a1-11 a2-8) + (+! i 1) + ) + ) + (else + (set! i 0) + (while + (< i (-> obj length)) + (format + '#t + (if (zero? i) "~D" " ~D") + (-> (the-as (array int32) obj) i) + ) + (+! i 1) + ) + ) + ) + ) + ) + ) + (else + (cond + ((= (-> obj content-type) float) + (set! i 0) + (while + (< i (-> obj length)) + (if + (zero? i) + (format '#t "~f" (-> (the-as (array float) obj) i)) + (format '#t " ~f" (-> (the-as (array float) obj) i)) + ) + (+! i 1) + ) + ) + (else + (set! i 0) + (while + (< i (-> obj length)) + (if + (zero? i) + (format '#t "~A" (-> (the-as (array basic) obj) i)) + (format '#t " ~A" (-> (the-as (array basic) obj) i)) + ) + (+! i 1) + ) + ) + ) + ) + ) + (format '#t ")") + obj + ) + +;; definition for method of type array +(defmethod inspect array ((obj array)) + (local-vars + (content-type-sym symbol) + (v1-42 pointer) + (a0-25 symbol) + (a1-15 string) + (a2-13 int) + (a3-10 int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (i int) + (t9-14 (function _varargs_ object)) + ) + (format '#t "[~8x] ~A~%" obj (-> obj type)) + (format '#t "~Tallocated-length: ~D~%" (-> obj allocated-length)) + (format '#t "~Tlength: ~D~%" (-> obj length)) + (format '#t "~Tcontent-type: ~A~%" (-> obj content-type)) + (format '#t "~Tdata[~D]: @ #x~X~%" (-> obj allocated-length) (-> obj data)) + (cond + ((type-type? (-> obj content-type) integer) + (set! content-type-sym (-> obj content-type symbol)) + (cond + ((= content-type-sym 'int32) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~D~%" i (-> (the-as (array int32) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'uint32) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~D~%" i (-> (the-as (array uint32) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'int64) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~D~%" i (-> (the-as (array int64) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'uint64) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] #x~X~%" i (-> (the-as (array uint64) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'int8) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~D~%" i (-> (the-as (array int8) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'uint8) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~D~%" i (-> (the-as (array int8) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'int16) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~D~%" i (-> (the-as (array int16) obj) i)) + (+! i 1) + ) + ) + ((= content-type-sym 'uint16) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~D~%" i (-> (the-as (array uint16) obj) i)) + (+! i 1) + ) + ) + (else + (cond + ((or (= content-type-sym 'int128) (= content-type-sym 'uint128)) + (set! i 0) + (while + (< i (-> obj length)) + (set! t9-14 format) + (set! a0-25 '#t) + (set! a1-15 "~T [~D] #x~X~%") + (set! a2-13 i) + (set! v1-42 (+ (shl i 4) (the-as int obj))) + (.lq a3-10 12 v1-42) + (t9-14 a0-25 a1-15 a2-13 a3-10) + (+! i 1) + ) + ) + (else + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~D~%" i (-> obj i)) + (+! i 1) + ) + ) + ) + ) + ) + ) + (else + (cond + ((= (-> obj content-type) float) + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~f~%" i (-> obj i)) + (+! i 1) + ) + ) + (else + (set! i 0) + (while + (< i (-> obj length)) + (format '#t "~T [~D] ~A~%" i (-> obj i)) + (+! i 1) + ) + ) + ) + ) + ) + obj + ) + +;; definition for method of type array +(defmethod length array ((obj array)) + (-> obj length) + ) + +;; definition for method of type array +;; INFO: Return type mismatch uint vs int. +(defmethod asize-of array ((obj array)) + (the-as + int + (+ + (-> array size) + (the-as + uint + (* + (-> obj allocated-length) + (if + (type-type? (-> obj content-type) number) + (-> obj content-type size) + 4 + ) + ) + ) + ) + ) + ) + +;; definition for function mem-copy! +(defun mem-copy! ((dst pointer) (src pointer) (size int)) + (local-vars (result pointer) (i int)) + (set! result dst) + (set! i 0) + (while + (< i size) + (set! (-> (the-as (pointer int8) dst)) (-> (the-as (pointer uint8) src))) + (&+! dst 1) + (&+! src 1) + (+! i 1) + ) + result + ) + +;; definition for function qmem-copy<-! +(defun qmem-copy<-! ((dst pointer) (src pointer) (size int)) + (local-vars (result pointer) (qwc int) (value int)) + (set! result dst) + (set! qwc (sar (+ size 15) 4)) + (while + (nonzero? qwc) + (+! qwc -1) + (.lq value 0 src) + (.sq value 0 dst) + (&+! dst 16) + (&+! src 16) + ) + result + ) + +;; definition for function qmem-copy->! +(defun qmem-copy->! ((dst pointer) (src pointer) (size int)) + (local-vars + (result pointer) + (qwc int) + (src-ptr pointer) + (dst-ptr pointer) + (value int) + ) + (set! result dst) + (set! qwc (sar (+ size 15) 4)) + (&+! dst (shl qwc 4)) + (&+! src (shl qwc 4)) + (while + (nonzero? qwc) + (+! qwc -1) + (&+! src-ptr -16) + (&+! dst-ptr -16) + (.lq value 0 dst-ptr) + (.sq value 0 src-ptr) + ) + result + ) + +;; definition for function mem-set32! +(defun mem-set32! ((dst pointer) (size int) (value int)) + (local-vars (result pointer) (i int)) + (set! result dst) + (set! i 0) + (while + (< i size) + (set! (-> (the-as (pointer int32) dst)) value) + (&+! dst 4) + (nop!) + (+! i 1) + ) + result + ) + +;; definition for function mem-or! +(defun mem-or! ((dst pointer) (src pointer) (size int)) + (local-vars (result pointer) (i int)) + (set! result dst) + (set! i 0) + (while + (< i size) + (set! + (-> (the-as (pointer int8) dst)) + (logior (-> (the-as (pointer uint8) dst)) (-> (the-as (pointer uint8) src))) + ) + (&+! dst 1) + (&+! src 1) + (+! i 1) + ) + result + ) + +;; definition for function quad-copy! +;; ERROR: function was not converted to expressions. Cannot decompile. + +;; definition for function fact +(defun fact ((x int)) + (if (= x 1) 1 (* x (fact (+ x -1)))) + ) + +;; failed to figure out what this is: +(set! *print-column* 0) + +;; definition for function print +(defun print ((arg0 object)) + ((method-of-type (rtype-of arg0) print) arg0) + ) + +;; definition for function printl +(defun printl ((arg0 object)) + (local-vars (a0-1 object)) + (set! a0-1 arg0) + ((method-of-type (rtype-of a0-1) print) a0-1) + (format '#t "~%") + arg0 + ) + +;; definition for function inspect +(defun inspect ((arg0 object)) + ((method-of-type (rtype-of arg0) inspect) arg0) + ) + +;; definition (debug) for function mem-print +(defun-debug mem-print ((data (pointer uint32)) (word-count int)) + (local-vars (current-qword int)) + (set! current-qword 0) + (while + (< current-qword (sar word-count 2)) + (format + 0 + "~X: ~X ~X ~X ~X~%" + (&-> data (shl current-qword 2)) + (-> data (shl current-qword 2)) + (-> data (+ (shl current-qword 2) 1)) + (-> data (+ (shl current-qword 2) 2)) + (-> data (+ (shl current-qword 2) 3)) + ) + (+! current-qword 1) + ) + #f + ) + +;; failed to figure out what this is: +(set! *trace-list* '()) + +;; definition for function print-tree-bitmask +(defun print-tree-bitmask ((bits int) (count int)) + (local-vars (i int)) + (set! i 0) + (while + (< i count) + (if (zero? (logand bits 1)) (format '#t " ") (format '#t "| ")) + (set! bits (shr bits 1)) + (+! i 1) + ) + #f + ) + +;; definition for function breakpoint-range-set! +(defun breakpoint-range-set! ((arg0 uint) (arg1 uint) (arg2 uint)) + (.mtc0 Debug arg0) + (.mtdab arg1) + (.mtdabm arg2) + 0 + ) + +;; definition for function valid? +(defun + valid? + ((obj object) + (expected-type type) + (name basic) + (allow-false basic) + (print-dest object) + ) + (local-vars + (in-goal-mem symbol) + (v1-10 int) + (v1-11 int) + (v1-43 int) + (v1-44 int) + (v1-47 int) + (v1-48 int) + (s7-0 none) + ) + (set! + in-goal-mem + (and + (>= (the-as uint obj) (the-as uint __START-OF-TABLE__)) + (< (the-as uint obj) (the-as uint #x8000000)) + ) + ) + (cond + ((not expected-type) + (cond + ((nonzero? (logand (the-as int obj) 3)) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object (misaligned)~%" + obj + name + ) + ) + #f + ) + ((not in-goal-mem) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object (bad address)~%" + obj + name + ) + ) + #f + ) + (else '#t) + ) + ) + ((and allow-false (not obj)) '#t) + (else + (cond + ((= expected-type structure) + (cond + ((nonzero? (logand (the-as int obj) 15)) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (misaligned)~%" + obj + name + expected-type + ) + ) + #f + ) + ((or + (not in-goal-mem) + (begin + (set! v1-10 #x8000) + (.daddu v1-11 v1-10 s7-0) + (< (the-as uint obj) (the-as uint v1-11)) + ) + ) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (bad address)~%" + obj + name + expected-type + ) + ) + #f + ) + (else '#t) + ) + ) + ((= expected-type pair) + (cond + ((!= (logand (the-as int obj) 7) 2) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (misaligned)~%" + obj + name + expected-type + ) + ) + #f + ) + ((not in-goal-mem) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (bad address)~%" + obj + name + expected-type + ) + ) + #f + ) + (else '#t) + ) + ) + ((= expected-type binteger) + (cond + ((zero? (logand (the-as int obj) 7)) '#t) + (else + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (misaligned)~%" + obj + name + expected-type + ) + ) + #f + ) + ) + ) + ((!= (logand (the-as int obj) 7) 4) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (misaligned)~%" + obj + name + expected-type + ) + ) + #f + ) + ((not in-goal-mem) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (bad address)~%" + obj + name + expected-type + ) + ) + #f + ) + ((and (= expected-type type) (!= (rtype-of obj) type)) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (invalid type #x~X)~%" + obj + name + expected-type + (rtype-of obj) + ) + ) + #f + ) + (else + (cond + ((and + (!= expected-type type) + (not (valid? (rtype-of obj) type #f '#t 0)) + ) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (invalid type #x~X)~%" + obj + name + expected-type + (rtype-of obj) + ) + ) + #f + ) + ((not (type-type? (rtype-of obj) expected-type)) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (is type '~A' instead)~%" + obj + name + expected-type + (rtype-of obj) + ) + ) + #f + ) + ((= expected-type symbol) + (set! v1-43 #x8000) + (.daddu v1-44 v1-43 s7-0) + (cond + ((>= (the-as uint obj) (the-as uint v1-44)) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (not in symbol table)~%" + obj + name + expected-type + ) + ) + #f + ) + (else '#t) + ) + ) + ((begin + (set! v1-47 #x8000) + (.daddu v1-48 v1-47 s7-0) + (< (the-as uint obj) (the-as uint v1-48)) + ) + (if + name + (format + print-dest + "ERROR: object #x~X ~S is not a valid object of type '~A' (inside symbol table)~%" + obj + name + expected-type + ) + ) + #f + ) + (else '#t) + ) + ) + ) + ) + ) + ) + +;; failed to figure out what this is: +(set! v0-3 0) + diff --git a/test/decompiler/test_FormExpressionBuild.cpp b/test/decompiler/test_FormExpressionBuild.cpp index 1771a05a1a..41005a77a0 100644 --- a/test/decompiler/test_FormExpressionBuild.cpp +++ b/test/decompiler/test_FormExpressionBuild.cpp @@ -2572,4 +2572,25 @@ TEST_F(FormRegressionTest, ExprTerminal2) { " (- f0-4 (+ arg1 (* arg2 (* f0-4 f0-4))))\n" " )"; test_with_expr(func, type, expected, false, "", {{"L17", "A ~A"}}); +} + +TEST_F(FormRegressionTest, MoveFalse) { + std::string func = + "sll r0, r0, 0\n" + "L29:\n" + " daddiu sp, sp, -16\n" + " sd fp, 8(sp)\n" + " or fp, t9, r0\n" + " daddiu v1, s7, 8\n" + " daddiu a0, a0, 12\n" + " andi a0, a0, 1\n" + " movz v1, s7, a0\n" + " or v0, v1, r0\n" + " ld fp, 8(sp)\n" + " jr ra\n" + " daddiu sp, sp, 16\n"; + std::string type = "(function int symbol)"; + + std::string expected = "(nonzero? (logand (+ arg0 12) 1))"; + test_with_expr(func, type, expected, false, "", {{"L17", "A ~A"}}); } \ No newline at end of file diff --git a/test/offline/CMakeLists.txt b/test/offline/CMakeLists.txt new file mode 100644 index 0000000000..fef605f755 --- /dev/null +++ b/test/offline/CMakeLists.txt @@ -0,0 +1,5 @@ + +add_executable(offline-test + ${CMAKE_CURRENT_LIST_DIR}/offline_test_main.cpp) + +target_link_libraries(offline-test common gtest decomp compiler) \ No newline at end of file diff --git a/test/offline/offline_test_main.cpp b/test/offline/offline_test_main.cpp new file mode 100644 index 0000000000..eb8b4b664f --- /dev/null +++ b/test/offline/offline_test_main.cpp @@ -0,0 +1,303 @@ +#include +#include "common/util/FileUtil.h" +#include "gtest/gtest.h" +#include "common/log/log.h" +#include "decompiler/Disasm/OpcodeInfo.h" +#include "decompiler/config.h" +#include "decompiler/ObjectFile/ObjectFileDB.h" +#include "goalc/compiler/Compiler.h" + +int main(int argc, char** argv) { + lg::initialize(); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +namespace { +// the object files to test +const std::unordered_set object_files_to_decompile = {"gcommon"}; + +// the object files to check against a reference in test/decompiler/reference +const std::unordered_set object_files_to_check_against_reference = { + "gcommon" // NOTE: this file needs work, but adding it for now just to test the framework. +}; + +// the functions we expect the decompiler to skip +const std::unordered_set expected_skip_in_decompiler = { + // gcommon + "quad-copy!", // asm mempcy + // gkernel + "set-to-run-bootstrap", // kernel context switch + "throw", // manually sets fp/t9. + "throw-dispatch", // restore context + "(method 0 catch-frame)", // save context + "(method 11 cpu-thread)", // kernel -> user context switch + "(method 10 cpu-thread)", // user -> kernel context switch + "reset-and-call", // kernel -> user + "return-from-thread-dead", // kernel -> user + "return-from-thread", // kernel -> user + "return-from-exception", // ps2 exception -> ps2 user + // pskernel + "kernel-check-hardwired-addresses", // ps2 ee kernel debug hook + "kernel-read-function", // ps2 ee kernel debug hook + "kernel-write-function", // ps2 ee kernel debug hook + "kernel-copy-function" // ps2 ee kernel debug hook +}; + +const std::unordered_set skip_in_compiling = { + // these functions are not implemented by the compiler in OpenGOAL, but are in GOAL. + "abs", "ash", "min", "max", "lognor"}; + +} // namespace + +class OfflineDecompilation : public ::testing::Test { + protected: + static std::unique_ptr db; + + static void SetUpTestCase() { + // global setup + file_util::init_crc(); + decompiler::init_opcode_info(); + decompiler::set_config( + file_util::get_file_path({"decompiler", "config", "jak1_ntsc_black_label.jsonc"})); + + decompiler::get_config().allowed_objects = object_files_to_decompile; + db = std::make_unique( + std::vector{file_util::get_file_path({"iso_data", "CGO", "KERNEL.CGO"}), + file_util::get_file_path({"iso_data", "CGO", "ENGINE.CGO"})}, + decompiler::get_config().obj_file_name_map_file, std::vector{}, + std::vector{}); + + // basic processing to find functions/data/disassembly + db->process_link_data(); + db->find_code(); + db->process_labels(); + + // fancy decompilation. + db->analyze_functions_ir2({}); + } + + static void TearDownTestCase() { db.reset(); } +}; + +std::unique_ptr OfflineDecompilation::db; + +/*! + * Check that the most basic disassembly into files/functions/instructions has succeeded. + */ +TEST_F(OfflineDecompilation, CheckBasicDecode) { + int obj_count = 0; + db->for_each_obj([&](decompiler::ObjectFileData& obj) { + obj_count++; + auto& stats = obj.linked_data.stats; + // make sure we decoded all instructions + EXPECT_EQ(stats.code_bytes / 4, stats.decoded_ops); + // make sure all FP uses are properly recognized + EXPECT_EQ(stats.n_fp_reg_use, stats.n_fp_reg_use_resolved); + }); + + EXPECT_EQ(obj_count, decompiler::get_config().allowed_objects.size()); +} + +/*! + * Not a super great test, but check that we find functions, methods, and logins. + * This is a test of ir2_top_level_pass, which isn't tested as part of the normal decompiler tests. + */ +TEST_F(OfflineDecompilation, FunctionDetect) { + int function_count = 0; // global functions + int method_count = 0; // methods + int login_count = 0; // top-level logins + int unknown_count = 0; // unknown functions, like anonymous lambdas + + db->for_each_function( + [&](decompiler::Function& func, int segment_id, decompiler::ObjectFileData&) { + if (segment_id == TOP_LEVEL_SEGMENT) { + EXPECT_EQ(func.guessed_name.kind, decompiler::FunctionName::FunctionKind::TOP_LEVEL_INIT); + } else { + EXPECT_NE(func.guessed_name.kind, decompiler::FunctionName::FunctionKind::TOP_LEVEL_INIT); + } + switch (func.guessed_name.kind) { + case decompiler::FunctionName::FunctionKind::GLOBAL: + function_count++; + break; + case decompiler::FunctionName::FunctionKind::METHOD: + method_count++; + break; + case decompiler::FunctionName::FunctionKind::TOP_LEVEL_INIT: + login_count++; + break; + case decompiler::FunctionName::FunctionKind::UNIDENTIFIED: + unknown_count++; + break; + default: + assert(false); + } + }); + + // one login per object file + EXPECT_EQ(decompiler::get_config().allowed_objects.size(), login_count); + + // not many lambdas. + EXPECT_TRUE(unknown_count < 10); +} + +TEST_F(OfflineDecompilation, AsmFunction) { + int failed_count = 0; + db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) { + if (func.suspected_asm) { + if (expected_skip_in_decompiler.find(func.guessed_name.to_string()) == + expected_skip_in_decompiler.end()) { + lg::error("Function {} was marked as asm, but wasn't expected.", + func.guessed_name.to_string()); + failed_count++; + } + } + }); + EXPECT_EQ(failed_count, 0); +} + +/*! + * Test that all functions pass CFG build stage. + */ +TEST_F(OfflineDecompilation, CfgBuild) { + int failed_count = 0; + db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) { + if (!func.suspected_asm) { + if (!func.cfg || !func.cfg->is_fully_resolved()) { + lg::error("Function {} failed cfg", func.guessed_name.to_string()); + failed_count++; + } + } + }); + + EXPECT_EQ(failed_count, 0); +} + +/*! + * Test that all functions pass the atomic op construction stage + */ +TEST_F(OfflineDecompilation, AtomicOp) { + int failed_count = 0; + db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) { + if (!func.suspected_asm) { + if (!func.ir2.atomic_ops || !func.ir2.atomic_ops_succeeded) { + lg::error("Function {} failed atomic ops", func.guessed_name.to_string()); + failed_count++; + } + } + }); + + EXPECT_EQ(failed_count, 0); +} + +/*! + * Test that all functions pass the type analysis stage + */ +TEST_F(OfflineDecompilation, TypeAnalysis) { + int failed_count = 0; + db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) { + if (!func.suspected_asm) { + if (!func.ir2.env.has_type_analysis()) { + lg::error("Function {} failed types", func.guessed_name.to_string()); + failed_count++; + } + } + }); + + EXPECT_EQ(failed_count, 0); +} + +TEST_F(OfflineDecompilation, RegisterUse) { + int failed_count = 0; + db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) { + if (!func.suspected_asm) { + if (!func.ir2.env.has_reg_use()) { + lg::error("Function {} failed reg use", func.guessed_name.to_string()); + failed_count++; + } + } + }); + + EXPECT_EQ(failed_count, 0); +} + +TEST_F(OfflineDecompilation, VariableSSA) { + int failed_count = 0; + db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) { + if (!func.suspected_asm) { + if (!func.ir2.env.has_local_vars()) { + lg::error("Function {} failed ssa", func.guessed_name.to_string()); + failed_count++; + } + } + }); + + EXPECT_EQ(failed_count, 0); +} + +TEST_F(OfflineDecompilation, Structuring) { + int failed_count = 0; + db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) { + if (!func.suspected_asm) { + if (!func.ir2.top_form) { + lg::error("Function {} failed structuring", func.guessed_name.to_string()); + failed_count++; + } + } + }); + + EXPECT_EQ(failed_count, 0); +} + +TEST_F(OfflineDecompilation, Expressions) { + int failed_count = 0; + db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) { + if (!func.suspected_asm) { + if (!func.ir2.expressions_succeeded) { + lg::error("Function {} failed expressions", func.guessed_name.to_string()); + failed_count++; + } + } + }); + + EXPECT_EQ(failed_count, 0); +} + +namespace { +void strip_trailing_newlines(std::string& in) { + while (!in.empty() && in.back() == '\n') { + in.pop_back(); + } +} +} // namespace + +TEST_F(OfflineDecompilation, Reference) { + for (auto& file : object_files_to_check_against_reference) { + auto& obj_l = db->obj_files_by_name.at(file); + ASSERT_EQ(obj_l.size(), 1); + + std::string src = db->ir2_final_out(obj_l.at(0)); + + auto reference = file_util::read_text_file(file_util::get_file_path( + {"test", "decompiler", "reference", fmt::format("{}_REF.gc", file)})); + + strip_trailing_newlines(reference); + strip_trailing_newlines(src); + + EXPECT_EQ(reference, src); + } +} + +TEST_F(OfflineDecompilation, Compile) { + Compiler compiler; + + for (auto& file : object_files_to_check_against_reference) { + auto& obj_l = db->obj_files_by_name.at(file); + ASSERT_EQ(obj_l.size(), 1); + + std::string src = db->ir2_final_out(obj_l.at(0), skip_in_compiling); + + compiler.run_full_compiler_on_string_no_save(src); + } +} \ No newline at end of file diff --git a/test/test_main.cpp b/test/test_main.cpp index 6f08ae89d2..2be0f32eef 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -1,11 +1,9 @@ +#include + #include "gtest/gtest.h" #include "common/util/FileUtil.h" - -#include -#ifdef _WIN32 -#include -#endif +#include "common/log/log.h" // Running subsets of tests, see: // - @@ -19,22 +17,8 @@ // to make it easier to test a subset of tests int main(int argc, char** argv) { -#ifdef _WIN32 - // Always enable VIRTUAL_TERMINAL_PROCESSING, this console mode allows the console (stdout) to - // support ANSI colors in the outputted text, which are used by the logging tool. - // This mode may not be enabled by default, and changing that involves modifying the registry, - // so it seems like a better solution would be enabling it ourselves. + lg::initialize(); - // Get handle to stdout - HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); - // get current stdout mode - DWORD modeStdOut; - GetConsoleMode(hStdOut, &modeStdOut); - // enable VIRTUAL_TERMINAL_PROCESSING. As a bitwise OR it will not do anything if it is - // already set - modeStdOut |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - SetConsoleMode(hStdOut, modeStdOut); -#endif ::testing::InitGoogleTest(&argc, argv); // Re-init failed folder