diff --git a/common/goos/PrettyPrinter.cpp b/common/goos/PrettyPrinter.cpp index f0bc951013..60019ddb4c 100644 --- a/common/goos/PrettyPrinter.cpp +++ b/common/goos/PrettyPrinter.cpp @@ -730,4 +730,16 @@ goos::Object build_list(const std::vector& symbols) { } return build_list(f.data(), f.size()); } + +void append(goos::Object& _in, const goos::Object& add) { + auto* in = &_in; + while (in->is_pair() && !in->as_pair()->cdr.is_empty_list()) { + in = &in->as_pair()->cdr; + } + + if (!in->is_pair()) { + assert(false); // invalid list + } + in->as_pair()->cdr = add; +} } // namespace pretty_print diff --git a/common/goos/PrettyPrinter.h b/common/goos/PrettyPrinter.h index a59049bddc..c02e2d6f06 100644 --- a/common/goos/PrettyPrinter.h +++ b/common/goos/PrettyPrinter.h @@ -53,4 +53,6 @@ goos::Reader& get_pretty_printer_reader(); goos::Object float_representation(float value); +void append(goos::Object& _in, const goos::Object& add); + } // namespace pretty_print diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index 388b463e8d..06cb9bf609 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -2,6 +2,7 @@ add_library( decomp SHARED + analysis/anonymous_function_def.cpp analysis/atomic_op_builder.cpp analysis/cfg_builder.cpp analysis/expression_build.cpp diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index 1042948421..eb67c2501a 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -109,10 +109,18 @@ TP_Type SimpleAtom::get_type(const TypeState& input, if (word.kind == LinkedWord::TYPE_PTR) { if (word.symbol_name == "string") { return TP_Type::make_from_string(env.file->get_goal_string_by_label(label)); - } else { - // otherwise, some other static basic. - return TP_Type::make_from_ts(TypeSpec(word.symbol_name)); + } else if (word.symbol_name == "function") { + // let's see if the user marked this as a lambda and if we can get a more specific type. + auto hint_kv = env.label_types().find(label.name); + if (hint_kv != env.label_types().end() && hint_kv->second.type_name == "_lambda_") { + auto func = env.file->try_get_function_at_label(m_int); + if (func) { + return TP_Type::make_from_ts(func->type); + } + } } + // otherwise, some other static basic. + return TP_Type::make_from_ts(TypeSpec(word.symbol_name)); } } else if ((label.offset & 7) == PAIR_OFFSET) { return TP_Type::make_from_ts(TypeSpec("pair")); diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp index 6d9ec99747..c8984db910 100644 --- a/decompiler/IR2/Form.cpp +++ b/decompiler/IR2/Form.cpp @@ -2178,6 +2178,26 @@ void DoTimesElement::get_modified_regs(RegSet& regs) const { m_check_value->get_modified_regs(regs); } +///////////////////////////// +// LambdaDefinitionElement +///////////////////////////// + +LambdaDefinitionElement::LambdaDefinitionElement(const goos::Object& def) : m_def(def) {} + +goos::Object LambdaDefinitionElement::to_form_internal(const Env&) const { + return m_def; +} + +void LambdaDefinitionElement::apply_form(const std::function&) {} + +void LambdaDefinitionElement::apply(const std::function& f) { + f(this); +} + +void LambdaDefinitionElement::collect_vars(RegAccessSet&, bool) const {} + +void LambdaDefinitionElement::get_modified_regs(RegSet&) const {} + std::optional form_as_atom(const Form* f) { auto as_single = f->try_as_single_element(); auto as_atom = dynamic_cast(as_single); @@ -2192,5 +2212,4 @@ std::optional form_as_atom(const Form* f) { return {}; } - } // namespace decompiler diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index 488caea108..932aa4f09d 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -1234,6 +1234,19 @@ class DoTimesElement : public FormElement { Form* m_body = nullptr; }; +class LambdaDefinitionElement : public FormElement { + public: + LambdaDefinitionElement(const goos::Object& def); + goos::Object to_form_internal(const Env& env) const override; + void apply(const std::function& f) override; + void apply_form(const std::function& f) override; + void collect_vars(RegAccessSet& vars, bool recursive) const override; + void get_modified_regs(RegSet& regs) const override; + + private: + goos::Object m_def; +}; + /*! * A Form is a wrapper around one or more FormElements. * This is done for two reasons: diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index 6e1fcb90ec..f437b77735 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -393,6 +393,8 @@ void SimpleExpressionElement::update_from_stack_identity(const Env& env, auto decompiled_data = decompile_at_label_guess_type(lab, env.file->labels, env.file->words_by_seg, env.dts->ts); result->push_back(pool.alloc_element(decompiled_data)); + } else if (type_name == "_lambda_") { + result->push_back(this); } else { auto decompiled_data = decompile_at_label_with_hint(kv->second, lab, env.file->labels, env.file->words_by_seg, *env.dts); diff --git a/decompiler/ObjectFile/ObjectFileDB.h b/decompiler/ObjectFile/ObjectFileDB.h index 0c9c34a629..a9dae48ae7 100644 --- a/decompiler/ObjectFile/ObjectFileDB.h +++ b/decompiler/ObjectFile/ObjectFileDB.h @@ -77,6 +77,7 @@ class ObjectFileDB { void ir2_build_expressions(); void ir2_insert_lets(); void ir2_rewrite_inline_asm_instructions(); + void ir2_insert_anonymous_functions(); 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); diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index b3d3eca4a1..f68c10764b 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -16,6 +16,7 @@ #include "decompiler/analysis/final_output.h" #include "decompiler/analysis/expression_build.h" #include "decompiler/analysis/inline_asm_rewrite.h" +#include "decompiler/analysis/anonymous_function_def.h" #include "common/goos/PrettyPrinter.h" #include "decompiler/IR2/Form.h" @@ -53,6 +54,8 @@ void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir) { lg::info("Inserting lets..."); ir2_insert_lets(); } + lg::info("Inserting anonymous function definitions..."); + ir2_insert_anonymous_functions(); } if (!output_dir.empty()) { @@ -480,6 +483,20 @@ void ObjectFileDB::ir2_rewrite_inline_asm_instructions() { timer.getMs()); } +void ObjectFileDB::ir2_insert_anonymous_functions() { + Timer timer; + int total = 0; + for_each_function_def_order([&](Function& func, int segment_id, ObjectFileData& data) { + (void)segment_id; + (void)data; + if (func.ir2.top_form && func.ir2.env.has_type_analysis()) { + total += insert_anonymous_functions(func.ir2.top_form, *func.ir2.form_pool, func, dts); + } + }); + + lg::info("Inserted {} anonymous functions in {:.2f} ms\n", total, timer.getMs()); +} + void ObjectFileDB::ir2_write_results(const std::string& output_dir) { Timer timer; lg::info("Writing IR2 results to file..."); diff --git a/decompiler/analysis/anonymous_function_def.cpp b/decompiler/analysis/anonymous_function_def.cpp new file mode 100644 index 0000000000..fbd91d4270 --- /dev/null +++ b/decompiler/analysis/anonymous_function_def.cpp @@ -0,0 +1,39 @@ +#include "anonymous_function_def.h" +#include "common/goos/PrettyPrinter.h" +#include "decompiler/Function/Function.h" +#include "decompiler/ObjectFile/LinkedObjectFile.h" +#include "decompiler/analysis/final_output.h" + +namespace decompiler { +int insert_anonymous_functions(Form* top_level_form, + FormPool& pool, + const Function& function, + const DecompilerTypeSystem&) { + int replaced = 0; + top_level_form->apply_form([&](Form* f) { + auto atom = form_as_atom(f); + if (atom && atom->is_static_addr()) { + auto lab = function.ir2.env.file->labels.at(atom->label()); + auto& env = function.ir2.env; + auto label_kv = env.label_types().find(lab.name); + if (label_kv != env.label_types().end()) { + if (label_kv->second.type_name == "_lambda_") { + auto& file = env.file; + auto other_func = file->try_get_function_at_label(atom->label()); + if (other_func) { + std::vector inline_body; + other_func->ir2.top_form->inline_forms(inline_body, other_func->ir2.env); + auto result = pretty_print::build_list( + "lambda", get_arg_list_for_function(*other_func, other_func->ir2.env)); + pretty_print::append(result, pretty_print::build_list(inline_body)); + + f->clear(); + f->push_back(pool.alloc_element(result)); + } + } + } + } + }); + return replaced; +} +} // namespace decompiler diff --git a/decompiler/analysis/anonymous_function_def.h b/decompiler/analysis/anonymous_function_def.h new file mode 100644 index 0000000000..0ee1aed78a --- /dev/null +++ b/decompiler/analysis/anonymous_function_def.h @@ -0,0 +1,10 @@ +#pragma once + +#include "decompiler/IR2/Form.h" + +namespace decompiler { +int insert_anonymous_functions(Form* top_level_form, + FormPool& pool, + const Function& function, + const DecompilerTypeSystem& dts); +} diff --git a/decompiler/analysis/final_output.cpp b/decompiler/analysis/final_output.cpp index f70b47c3aa..d755346bbc 100644 --- a/decompiler/analysis/final_output.cpp +++ b/decompiler/analysis/final_output.cpp @@ -7,24 +7,23 @@ namespace decompiler { -namespace { -void append(goos::Object& _in, const goos::Object& add) { - auto* in = &_in; - while (in->is_pair() && !in->as_pair()->cdr.is_empty_list()) { - in = &in->as_pair()->cdr; +goos::Object get_arg_list_for_function(const Function& func, const Env& env) { + std::vector argument_elts; + assert(func.type.arg_count() >= 1); + for (size_t i = 0; i < func.type.arg_count() - 1; i++) { + auto reg = Register(Reg::GPR, Reg::A0 + i); + auto name = fmt::format("{}-0", reg.to_charp()); + argument_elts.push_back( + pretty_print::build_list(env.remapped_name(name), func.type.get_arg(i).print())); } - - if (!in->is_pair()) { - assert(false); // invalid list - } - in->as_pair()->cdr = add; + return pretty_print::build_list(argument_elts); } -} // namespace std::string final_defun_out(const Function& func, const Env& env, const DecompilerTypeSystem& dts, FunctionDefSpecials special_mode) { + using pretty_print::append; std::vector inline_body; try { func.ir2.top_form->inline_forms(inline_body, env); @@ -34,16 +33,7 @@ std::string final_defun_out(const Function& func, int var_count = 0; auto var_dec = env.local_var_type_list(func.ir2.top_form, func.type.arg_count() - 1, &var_count); - - std::vector argument_elts; - assert(func.type.arg_count() >= 1); - for (size_t i = 0; i < func.type.arg_count() - 1; i++) { - auto reg = Register(Reg::GPR, Reg::A0 + i); - auto name = fmt::format("{}-0", reg.to_charp()); - argument_elts.push_back( - pretty_print::build_list(env.remapped_name(name), func.type.get_arg(i).print())); - } - auto arguments = pretty_print::build_list(argument_elts); + auto arguments = get_arg_list_for_function(func, env); if (func.guessed_name.kind == FunctionName::FunctionKind::GLOBAL) { std::string def_name = "defun"; diff --git a/decompiler/analysis/final_output.h b/decompiler/analysis/final_output.h index 2d739a9671..051977d69c 100644 --- a/decompiler/analysis/final_output.h +++ b/decompiler/analysis/final_output.h @@ -14,4 +14,6 @@ std::string write_from_top_level(const Function& top_level, const DecompilerTypeSystem& dts, const LinkedObjectFile& file, const std::unordered_set& skip_functions = {}); + +goos::Object get_arg_list_for_function(const Function& func, const Env& env); } // 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 94f5f7fff4..5d2e8d3727 100644 --- a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc @@ -11,7 +11,13 @@ ["L347", "float", true], ["L348", "float", true], ["L289", "_auto_", true], - ["L282", "_auto_", true] + ["L282", "_auto_", true], + ["L122", "_lambda_", true], + ["L129", "_lambda_", true], + ["L134", "_lambda_", true], + ["L136", "_lambda_", true], + ["L139", "_lambda_", true], + ["L86", "_lambda_", true] ], "math": [ diff --git a/test/decompiler/reference/all_forward_declarations.gc b/test/decompiler/reference/all_forward_declarations.gc index 7bab130d20..0eea3bd498 100644 --- a/test/decompiler/reference/all_forward_declarations.gc +++ b/test/decompiler/reference/all_forward_declarations.gc @@ -68,4 +68,7 @@ (define-extern *camera-pool* process-tree) (define-extern *target-pool* process-tree) (define-extern *entity-pool* process-tree) -(define-extern *default-pool* process-tree) \ No newline at end of file +(define-extern *default-pool* process-tree) +(define-extern *stdcon0* string) +(define-extern *stdcon1* string) +(define-extern *debug-draw-pauseable* symbol) \ No newline at end of file diff --git a/test/decompiler/reference/gkernel_REF.gc b/test/decompiler/reference/gkernel_REF.gc index f8ae20461a..51ccbd6c7c 100644 --- a/test/decompiler/reference/gkernel_REF.gc +++ b/test/decompiler/reference/gkernel_REF.gc @@ -1160,13 +1160,19 @@ ;; definition for function process-by-name (defun process-by-name ((arg0 object) (arg1 process-tree)) (set! *global-search-name* (the-as basic arg0)) - (search-process-tree arg1 (the-as (function process-tree object) L139)) + (search-process-tree + arg1 + (lambda ((a0-0 process)) (name= (-> a0-0 name) *global-search-name*)) + ) ) ;; definition for function process-not-name (defun process-not-name ((arg0 object) (arg1 process-tree)) (set! *global-search-name* (the-as basic arg0)) - (search-process-tree arg1 (the-as (function process-tree object) L136)) + (search-process-tree + arg1 + (lambda ((a0-0 process)) (not (name= (-> a0-0 name) *global-search-name*))) + ) ) ;; definition for function process-count @@ -1174,7 +1180,11 @@ (set! *global-search-count* 0) (iterate-process-tree arg0 - (the-as (function object object) L134) + (lambda + ((a0-0 process)) + (set! *global-search-count* (+ *global-search-count* 1)) + #t + ) *null-kernel-context* ) *global-search-count* @@ -1203,7 +1213,7 @@ ((v0-0 (search-process-tree arg1 - (the-as (function process-tree object) L129) + (lambda ((a0-0 process)) (= (-> a0-0 type) *global-search-name*)) ) ) ) @@ -1239,7 +1249,7 @@ ((v0-0 (search-process-tree arg1 - (the-as (function process-tree object) L122) + (lambda ((a0-0 process)) (!= (-> a0-0 type) *global-search-name*)) ) ) ) @@ -1362,7 +1372,98 @@ ) (execute-process-tree *active-pool* - (the-as (function object object) L86) + (lambda ((a0-0 process)) (let ((s5-0 *kernel-context*) + (v1-0 (-> a0-0 status)) + ) + (cond + ((or (= v1-0 'waiting-to-run) (= v1-0 'suspended)) + (set! (-> s5-0 current-process) a0-0) + (cond + ((nonzero? (logand (-> a0-0 mask) 4)) + (set! *stdcon* *stdcon1*) + (set! *debug-draw-pauseable* #t) + ) + (else + (set! *stdcon* *stdcon0*) + (set! *debug-draw-pauseable* #f) + ) + ) + (when (-> a0-0 trans-hook) + (let* + ((s4-0 + (new + 'process + 'cpu-thread + a0-0 + 'trans + 256 + (-> a0-0 main-thread stack-top) + ) + ) + (v0-1 + (reset-and-call s4-0 (-> a0-0 trans-hook)) + ) + ) + (delete s4-0) + (let ((v1-12 v0-1)) + ) + ) + (if (= (-> a0-0 status) 'dead) + (return (begin + (set! (-> s5-0 current-process) #f) + 'dead + ) + ) + ) + ) + (if (nonzero? (logand (-> a0-0 mask) 128)) + (set! (-> a0-0 status) 'suspended) + ((-> a0-0 main-thread resume-hook) + (-> a0-0 main-thread) + ) + ) + (cond + ((= (-> a0-0 status) 'dead) + (set! (-> s5-0 current-process) #f) + 'dead + ) + (else + (when (-> a0-0 post-hook) + (let + ((s4-1 + (new + 'process + 'cpu-thread + a0-0 + 'post + 256 + (&-> *dram-stack* 14336) + ) + ) + ) + (reset-and-call s4-1 (-> a0-0 post-hook)) + (delete s4-1) + ) + (if (= (-> a0-0 status) 'dead) + (return (begin + (set! (-> s5-0 current-process) #f) + 'dead + ) + ) + ) + (set! (-> a0-0 status) 'suspended) + ) + (set! (-> s5-0 current-process) #f) + #f + ) + ) + ) + ((= v1-0 'dead) + 'dead + ) + ) + ) + ) *kernel-context* ) ) diff --git a/test/offline/offline_test_main.cpp b/test/offline/offline_test_main.cpp index dedf8894a1..794460e395 100644 --- a/test/offline/offline_test_main.cpp +++ b/test/offline/offline_test_main.cpp @@ -67,9 +67,6 @@ const std::unordered_set skip_in_compiling = { ////////////////////// // GKERNEL ////////////////////// - // these refer to anonymous functions, which aren't yet implemented. - "process-by-name", "process-not-name", "process-count", "kill-by-type", "kill-not-type", - "kill-by-name", "kill-not-name", "kernel-dispatcher", // asm "(method 10 process)"