From 2c6b916503d6b5351a520dbe3a1dbb4e8e631eb1 Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Jul 2021 12:17:34 -0400 Subject: [PATCH 1/8] temp --- common/type_system/TypeSpec.cpp | 75 ++++++++++--- common/type_system/TypeSpec.h | 39 +++++-- common/type_system/TypeSystem.cpp | 21 ++++ common/type_system/deftype.cpp | 44 +++++++- goal_src/kernel/gkernel-h.gc | 12 +++ goal_src/kernel/gkernel.gc | 11 +- goalc/compiler/Util.cpp | 16 +-- goalc/compiler/compilation/Function.cpp | 30 +++++- goalc/compiler/compilation/Type.cpp | 25 ++++- scripts/emacs/goal-config.el | 102 +++++++++--------- .../with_game/test-behaviors.gc | 28 +++++ test/goalc/test_with_game.cpp | 6 ++ 12 files changed, 300 insertions(+), 109 deletions(-) create mode 100644 test/goalc/source_templates/with_game/test-behaviors.gc diff --git a/common/type_system/TypeSpec.cpp b/common/type_system/TypeSpec.cpp index c55f08803c..4ff3f95b6f 100644 --- a/common/type_system/TypeSpec.cpp +++ b/common/type_system/TypeSpec.cpp @@ -4,18 +4,20 @@ */ #include "TypeSpec.h" -#include "Type.h" +#include "third-party/fmt/core.h" -TypeSpec::TypeSpec(std::string type) : m_type(std::move(type)) {} - -TypeSpec::TypeSpec(std::string type, std::vector arguments) - : m_type(std::move(type)), m_arguments(std::move(arguments)) {} +bool TypeTag::operator==(const TypeTag& other) const { + return name == other.name && value == other.value; +} std::string TypeSpec::print() const { - if (m_arguments.empty()) { + if (m_arguments.empty() && m_tags.empty()) { return m_type; } else { std::string result = "(" + m_type; + for (const auto& tag : m_tags) { + result += fmt::format(" :{} {}", tag.name, tag.value); + } for (auto& x : m_arguments) { result += " " + x.print(); } @@ -28,17 +30,7 @@ bool TypeSpec::operator!=(const TypeSpec& other) const { } bool TypeSpec::operator==(const TypeSpec& other) const { - if (other.m_type != m_type || other.m_arguments.size() != m_arguments.size()) { - return false; - } - - for (size_t i = 0; i < m_arguments.size(); i++) { - if (other.m_arguments[i] != m_arguments[i]) { - return false; - } - } - - return true; + return m_type == other.m_type && m_arguments == other.m_arguments && m_tags == other.m_tags; } TypeSpec TypeSpec::substitute_for_method_call(const std::string& method_type) const { @@ -65,4 +57,53 @@ bool TypeSpec::is_compatible_child_method(const TypeSpec& implementation, } return true; +} + +void TypeSpec::add_new_tag(const std::string& tag_name, const std::string& tag_value) { + for (auto& v : m_tags) { + if (v.name == tag_name) { + throw std::runtime_error( + fmt::format("Attempted to add a duplicate tag {} to typespec.", tag_name)); + } + } + + m_tags.push_back({tag_name, tag_value}); +} + +std::optional TypeSpec::try_get_tag(const std::string& tag_name) const { + for (auto& tag : m_tags) { + if (tag.name == tag_name) { + return tag.value; + } + } + return {}; +} + +const std::string& TypeSpec::get_tag(const std::string& tag_name) const { + for (auto& tag : m_tags) { + if (tag.name == tag_name) { + return tag.value; + } + } + throw std::runtime_error(fmt::format("TypeSpec didn't have tag {}", tag_name)); +} + +void TypeSpec::modify_tag(const std::string& tag_name, const std::string& tag_value) { + for (auto& tag : m_tags) { + if (tag.name == tag_name) { + tag.value = tag_value; + return; + } + } + throw std::runtime_error(fmt::format("TypeSpec didn't have tag {}", tag_name)); +} + +void TypeSpec::add_or_modify_tag(const std::string& tag_name, const std::string& tag_value) { + for (auto& tag : m_tags) { + if (tag.name == tag_name) { + tag.value = tag_value; + return; + } + } + m_tags.push_back({tag_name, tag_value}); } \ No newline at end of file diff --git a/common/type_system/TypeSpec.h b/common/type_system/TypeSpec.h index d5880d199a..5e893bc19f 100644 --- a/common/type_system/TypeSpec.h +++ b/common/type_system/TypeSpec.h @@ -5,14 +5,20 @@ * A GOAL TypeSpec is a reference to a type or compound type. */ -#ifndef JAK_TYPESPEC_H -#define JAK_TYPESPEC_H - #include #include +#include #include "common/util/assert.h" -class Type; +/*! + * A :name value modifier to apply to a type. + */ +struct TypeTag { + std::string name; + std::string value; + + bool operator==(const TypeTag& other) const; +}; /*! * A TypeSpec is a reference to a Type, or possible a compound type. This is the best way to @@ -24,10 +30,19 @@ class Type; */ class TypeSpec { public: - // create a typespec for a single type TypeSpec() = default; - TypeSpec(std::string type); - TypeSpec(std::string type, std::vector arguments); + TypeSpec(const std::string& type) : m_type(type) {} + + TypeSpec(const std::string& type, const std::vector& arguments) + : m_type(type), m_arguments(arguments) {} + + TypeSpec(const std::string& type, const std::vector& tags) + : m_type(type), m_tags(tags) {} + + TypeSpec(const std::string type, + const std::vector& arguments, + const std::vector& tags) + : m_type(type), m_arguments(arguments), m_tags(tags) {} bool operator!=(const TypeSpec& other) const; bool operator==(const TypeSpec& other) const; @@ -36,6 +51,11 @@ class TypeSpec { std::string print() const; void add_arg(const TypeSpec& ts) { m_arguments.push_back(ts); } + void add_new_tag(const std::string& tag_name, const std::string& tag_value); + std::optional try_get_tag(const std::string& tag_name) const; + const std::string& get_tag(const std::string& tag_name) const; + void modify_tag(const std::string& tag_name, const std::string& tag_value); + void add_or_modify_tag(const std::string& tag_name, const std::string& tag_value); const std::string base_type() const { return m_type; } @@ -57,10 +77,11 @@ class TypeSpec { return m_arguments.back(); } + const std::vector& tags() const { return m_tags; } + private: friend class TypeSystem; std::string m_type; std::vector m_arguments; + std::vector m_tags; }; - -#endif // JAK_TYPESPEC_H diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 3c0aa0912c..0b6c62abbf 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -1314,6 +1314,22 @@ bool TypeSystem::typecheck_and_throw(const TypeSpec& expected, } } + // next, tag checks. It's fine to throw away tags, but the child must match all parent tags + for (auto& tag : expected.tags()) { + if (tag.name == "behavior") { + auto got = actual.try_get_tag(tag.name); + if (!got) { + success = false; + } else { + if (*got != tag.value) { + success = false; + } + } + } else { + throw_typesystem_error("Unknown tag {}", tag.name); + } + } + if (!success) { if (print_on_error) { if (error_source_name.empty()) { @@ -1617,6 +1633,11 @@ std::string TypeSystem::generate_deftype_footer(const Type* type) const { methods_string.append(":replace "); } + auto behavior = info.type.try_get_tag("behavior"); + if (behavior) { + methods_string.append(fmt::format(":behavior {} ", *behavior)); + } + methods_string.append(fmt::format("{})\n ", info.id)); } diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp index c878757149..e1fdc024c7 100644 --- a/common/type_system/deftype.cpp +++ b/common/type_system/deftype.cpp @@ -189,6 +189,7 @@ void declare_method(Type* type, TypeSystem* type_system, const goos::Object& def bool no_virtual = false; bool replace_method = false; + TypeSpec function_typespec("function"); if (!obj->is_empty_list() && car(obj).is_symbol(":no-virtual")) { obj = cdr(obj); @@ -200,6 +201,12 @@ void declare_method(Type* type, TypeSystem* type_system, const goos::Object& def replace_method = true; } + if (!obj->is_empty_list() && car(obj).is_symbol(":behavior")) { + obj = cdr(obj); + function_typespec.add_new_tag("behavior", symbol_string(obj->as_pair()->car)); + obj = cdr(obj); + } + int id = -1; if (!obj->is_empty_list() && car(obj).is_int()) { auto& id_obj = car(obj); @@ -211,8 +218,6 @@ void declare_method(Type* type, TypeSystem* type_system, const goos::Object& def throw std::runtime_error("too many things in method def: " + def.print()); } - TypeSpec function_typespec("function"); - for_each_in_list(args, [&](const goos::Object& o) { function_typespec.add_arg(parse_typespec(type_system, o)); }); @@ -441,10 +446,39 @@ TypeSpec parse_typespec(const TypeSystem* type_system, const goos::Object& src) 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); + const auto* rest = cdr(&src); + + while (rest->is_pair()) { + auto& it = rest->as_pair()->car; + + if (it.is_symbol() && it.as_symbol()->name.at(0) == ':') { + auto tag_name = it.as_symbol()->name.substr(1); + rest = &rest->as_pair()->cdr; - for_each_in_list(rest, - [&](const goos::Object& o) { ts.add_arg(parse_typespec(type_system, o)); }); + if (!rest->is_pair()) { + throw std::runtime_error("TypeSpec missing tag value"); + } + + auto& tag_val = rest->as_pair()->car; + + if (tag_name == "behavior") { + if (!type_system->fully_defined_type_exists(tag_val.as_symbol()->name) && + !type_system->partially_defined_type_exists(tag_val.as_symbol()->name)) { + throw std::runtime_error( + fmt::format("Behavior tag uses an unknown type {}", tag_val.as_symbol()->name)); + } + ts.add_new_tag(tag_name, tag_val.as_symbol()->name); + } else { + throw std::runtime_error(fmt::format("Type tag {} is unknown", tag_name)); + } + + } else { + // normal argument. + ts.add_arg(parse_typespec(type_system, it)); + } + + rest = &rest->as_pair()->cdr; + } return ts; } else { diff --git a/goal_src/kernel/gkernel-h.gc b/goal_src/kernel/gkernel-h.gc index 4f3693fd6e..0641953f22 100644 --- a/goal_src/kernel/gkernel-h.gc +++ b/goal_src/kernel/gkernel-h.gc @@ -558,3 +558,15 @@ `(rlet ((pp :reg r13 :reset-here #t :type process)) ,@body) ) + +(defmacro defbehavior (name process-type bindings &rest body) + (if (and + (> (length body) 1) ;; more than one thing in function + (string? (first body)) ;; first thing is a string + ) + ;; then it's a docstring and we ignore it. + `(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@(cdr body))) + ;; otherwise don't ignore it. + `(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@body)) + ) + ) diff --git a/goal_src/kernel/gkernel.gc b/goal_src/kernel/gkernel.gc index c9a1638c45..6601256237 100644 --- a/goal_src/kernel/gkernel.gc +++ b/goal_src/kernel/gkernel.gc @@ -249,13 +249,10 @@ ;; Remove Exit ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defun remove-exit () - "This is likely a defbehavior for process. - Pops a single stack frame, if there is one." - (rlet ((self :reg r13 :type process)) - (when (-> self stack-frame-top) - (set! (-> self stack-frame-top) (-> self stack-frame-top next)) - ) +(defbehavior remove-exit process () + "Pops a single stack frame, if there is one." + (when (-> self stack-frame-top) + (set! (-> self stack-frame-top) (-> self stack-frame-top next)) ) ) diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index 1b5b4a7781..e12b3810a2 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -1,6 +1,7 @@ #include "goalc/compiler/Compiler.h" #include "goalc/compiler/IR.h" #include "common/goos/ParseHelpers.h" +#include "common/type_system/deftype.h" /*! * Parse arguments into a goos::Arguments format. @@ -124,20 +125,7 @@ void Compiler::expect_empty_list(const goos::Object& o) { } TypeSpec Compiler::parse_typespec(const goos::Object& src) { - if (src.is_symbol()) { - return m_ts.make_typespec(symbol_string(src)); - } else if (src.is_pair()) { - TypeSpec ts = m_ts.make_typespec(symbol_string(pair_car(src))); - const auto& rest = pair_cdr(src); - - for_each_in_list(rest, [&](const goos::Object& o) { ts.add_arg(parse_typespec(o)); }); - - return ts; - } else { - throw_compiler_error(src, "Invalid typespec."); - } - assert(false); - return {}; + return ::parse_typespec(&m_ts, src); } bool Compiler::is_local_symbol(const goos::Object& obj, Env* env) { diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index c0571f81a3..d282f5a025 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -116,7 +116,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest auto obj_env = get_parent_env_of_type(env); auto args = get_va(form, rest); if (args.unnamed.empty() || !args.unnamed.front().is_list() || - !args.only_contains_named({"name", "inline-only", "segment"})) { + !args.only_contains_named({"name", "inline-only", "segment", "behavior"})) { throw_compiler_error(form, "Invalid lambda form"); } @@ -196,7 +196,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest } // set up argument register constraints. - std::vector args_for_coloring; + std::vector reset_args_for_coloring; std::vector arg_types; for (auto& parm : lambda.params) { arg_types.push_back(parm.type); @@ -213,7 +213,25 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest constr.desired_register = arg_regs.at(i); new_func_env->params[lambda.params.at(i).name] = ireg; new_func_env->constrain(constr); - args_for_coloring.push_back(ireg); + reset_args_for_coloring.push_back(ireg); + } + + if (args.has_named("behavior")) { + const std::string behavior_type = symbol_string(args.get_named("behavior")); + auto self_var = new_func_env->make_gpr(m_ts.make_typespec(behavior_type)); + IRegConstraint constr; + constr.contrain_everywhere = true; + constr.desired_register = emitter::gRegInfo.get_process_reg(); + constr.ireg = self_var->ireg(); + self_var->set_rlet_constraint(constr.desired_register); + new_func_env->constrain(constr); + + if (new_func_env->params.find("self") != new_func_env->params.end()) { + throw_compiler_error(form, "Cannot have an argument named self in a behavior"); + } + new_func_env->params["self"] = self_var; + reset_args_for_coloring.push_back(self_var); + lambda_ts.add_new_tag("behavior", behavior_type); } place->func = new_func_env.get(); @@ -224,7 +242,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest auto func_block_env = new_func_env->alloc_env(new_func_env.get(), "#f"); func_block_env->return_value = return_reg; func_block_env->end_label = Label(new_func_env.get()); - func_block_env->emit(std::make_unique(args_for_coloring)); + func_block_env->emit(std::make_unique(reset_args_for_coloring)); // compile the function, iterating through the body. Val* result = nullptr; @@ -287,6 +305,10 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest if (new_func_env->settings.save_code) { obj_env->add_function(std::move(new_func_env)); } + } else { + if (args.has_named("behavior")) { + throw_compiler_error(form, "Inline behaviors are not yet implemented."); + } } place->set_type(lambda_ts); diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index 39504021c7..7bebbc9497 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -424,7 +424,7 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ if (lambda.params.size() > 8) { throw_compiler_error(form, "Methods cannot have more than 8 arguments"); } - std::vector args_for_coloring; + std::vector reset_args_for_coloring; std::vector arg_types; for (auto& parm : lambda.params) { arg_types.push_back(parm.type); @@ -441,7 +441,26 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ constr.desired_register = arg_regs.at(i); new_func_env->params[lambda.params.at(i).name] = ireg; new_func_env->constrain(constr); - args_for_coloring.push_back(ireg); + reset_args_for_coloring.push_back(ireg); + } + + auto method_info = m_ts.lookup_method(symbol_string(type_name), symbol_string(method_name)); + auto behavior = method_info.type.try_get_tag("behavior"); + if (behavior) { + auto self_var = new_func_env->make_gpr(m_ts.make_typespec(*behavior)); + IRegConstraint constr; + constr.contrain_everywhere = true; + constr.desired_register = emitter::gRegInfo.get_process_reg(); + constr.ireg = self_var->ireg(); + self_var->set_rlet_constraint(constr.desired_register); + new_func_env->constrain(constr); + + if (new_func_env->params.find("self") != new_func_env->params.end()) { + throw_compiler_error(form, "Cannot have an argument named self in a behavior"); + } + new_func_env->params["self"] = self_var; + reset_args_for_coloring.push_back(self_var); + lambda_ts.add_new_tag("behavior", *behavior); } place->func = new_func_env.get(); @@ -451,7 +470,7 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ auto func_block_env = new_func_env->alloc_env(new_func_env.get(), "#f"); func_block_env->return_value = return_reg; func_block_env->end_label = Label(new_func_env.get()); - func_block_env->emit(std::make_unique(args_for_coloring)); + func_block_env->emit(std::make_unique(reset_args_for_coloring)); // compile the function! Val* result = nullptr; diff --git a/scripts/emacs/goal-config.el b/scripts/emacs/goal-config.el index d4782cba65..da7a123486 100644 --- a/scripts/emacs/goal-config.el +++ b/scripts/emacs/goal-config.el @@ -30,59 +30,60 @@ (show-paren-mode 1) ;; the default color is hard to see - make it the most painful color possible. (custom-set-faces - '(show-paren-match ((t (:foreground "white" :background "red"))))) + '(show-paren-match ((t (:foreground "white" :background "red"))))) ) ;; very simple syntax highlighting (sometimes fails) ;; these are added to lisp-mode's highlighting. (font-lock-add-keywords 'lisp-mode - '(("set!" . font-lock-keyword-face) - ("local-vars" . font-lock-keyword-face) - ("until" . font-lock-keyword-face) - ("car" . font-lock-keyword-face) - ("cdr" . font-lock-keyword-face) - ("->" . font-lock-keyword-face) - ("else" . font-lock-constant-face) - ("nop!" . font-lock-builtin-face) - ;;("and" . font-lock-keyword-face) - ;;("or" . font-lock-keyword-face) + '(("set!" . font-lock-keyword-face) + ("local-vars" . font-lock-keyword-face) + ("until" . font-lock-keyword-face) + ("car" . font-lock-keyword-face) + ("cdr" . font-lock-keyword-face) + ("->" . font-lock-keyword-face) + ("else" . font-lock-constant-face) + ("nop!" . font-lock-builtin-face) + ;;("and" . font-lock-keyword-face) + ;;("or" . font-lock-keyword-face) ("format" . font-lock-builtin-face) ("defun-debug" . font-lock-keyword-face) + ("defbehavior" . font-lock-keyword-face) ("defenum" . font-lock-keyword-face) - ("begin" . font-lock-keyword-face) - ("zero?" . font-lock-builtin-face) - ("#f" . font-lock-constant-face) - ("#t" . font-lock-constant-face))) + ("begin" . font-lock-keyword-face) + ("zero?" . font-lock-builtin-face) + ("#f" . font-lock-constant-face) + ("#t" . font-lock-constant-face))) ;; more advanced syntax highlighting. These still fail sometimes, but look nicer. (defconst scheme-font-lock-keywords-1 - (eval-when-compile - (list - ;; - ;; Declarations. Hannes Haug says - ;; this works for SOS, STklos, SCOOPS, Meroon and Tiny CLOS. - (list (concat "(\\(define\\*?\\(" - ;; Function names. - "\\(\\|-public\\|-method\\|-generic\\(-procedure\\)?\\)\\|" - ;; Macro names, as variable names. A bit dubious, this. - "\\(-syntax\\|-macro\\)\\|" - ;; Class names. - "-class" - ;; Guile modules. - "\\|-module" - "\\)\\)\\>" - ;; Any whitespace and declared object. - ;; The "(*" is for curried definitions, e.g., - ;; (define ((sum a) b) (+ a b)) - "[ \t]*(*" - "\\(\\sw+\\)?") - '(1 font-lock-keyword-face) - '(6 (cond ((match-beginning 3) font-lock-function-name-face) - ((match-beginning 5) font-lock-variable-name-face) - (t font-lock-type-face)) - nil t)) - )) - "Subdued expressions to highlight in Scheme modes.") + (eval-when-compile + (list + ;; + ;; Declarations. Hannes Haug says + ;; this works for SOS, STklos, SCOOPS, Meroon and Tiny CLOS. + (list (concat "(\\(define\\*?\\(" + ;; Function names. + "\\(\\|-public\\|-method\\|-generic\\(-procedure\\)?\\)\\|" + ;; Macro names, as variable names. A bit dubious, this. + "\\(-syntax\\|-macro\\)\\|" + ;; Class names. + "-class" + ;; Guile modules. + "\\|-module" + "\\)\\)\\>" + ;; Any whitespace and declared object. + ;; The "(*" is for curried definitions, e.g., + ;; (define ((sum a) b) (+ a b)) + "[ \t]*(*" + "\\(\\sw+\\)?") + '(1 font-lock-keyword-face) + '(6 (cond ((match-beginning 3) font-lock-function-name-face) + ((match-beginning 5) font-lock-variable-name-face) + (t font-lock-type-face)) + nil t)) + )) + "Subdued expressions to highlight in Scheme modes.") (font-lock-add-keywords 'lisp-mode scheme-font-lock-keywords-1) ;; make gc files use lisp-mode @@ -103,6 +104,7 @@ (put 'countdown 'common-lisp-indent-function 1) (put 'defun-debug 'common-lisp-indent-function 2) (put 'defenum 'common-lisp-indent-function 2) + (put 'defbehavior 'common-lisp-indent-function 3) ;; indent for common lisp, this makes if's look nicer (custom-set-variables '(lisp-indent-function 'common-lisp-indent-function)) @@ -130,10 +132,10 @@ (let ((buffer (comint-check-proc "OpenGOAL"))) ;; create or reuse a buffer and make it visible (pop-to-buffer - (if (or buffer (not (derived-mode-p 'opengoal-mode)) - (comint-check-proc (current-buffer))) - (get-buffer-create (or buffer "*OpenGOAL*")) - (current-buffer))) + (if (or buffer (not (derived-mode-p 'opengoal-mode)) + (comint-check-proc (current-buffer))) + (get-buffer-create (or buffer "*OpenGOAL*")) + (current-buffer))) (unless buffer ;; start the compiler @@ -159,10 +161,10 @@ (defun opengoal-compile-current-file (mode) "Check if the current file is saved. If so, compile it!" (cond - ((buffer-modified-p) - (message "Cannot compile current file: save your changes first!")) - (t - (opengoal-compile-file (buffer-file-name) mode)))) + ((buffer-modified-p) + (message "Cannot compile current file: save your changes first!")) + (t + (opengoal-compile-file (buffer-file-name) mode)))) (defun opengoal-m-file () "Make the current file." diff --git a/test/goalc/source_templates/with_game/test-behaviors.gc b/test/goalc/source_templates/with_game/test-behaviors.gc new file mode 100644 index 0000000000..5711871a08 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-behaviors.gc @@ -0,0 +1,28 @@ +(deftype behavior-test-type (basic) + ((foo int)) + (:methods + (test (_type_ basic) int :behavior behavior-test-type) + ) + ) + +(defmethod test behavior-test-type ((obj behavior-test-type) (arg0 basic)) + (format #t "method obj: ~D self: ~D~%" (-> obj foo) (-> self foo)) + 0 + ) + +(defbehavior test-bfunc behavior-test-type ((arg0 basic)) + (format #t "function self: ~D~%" (-> self foo)) + ) + + +(let ((obj1 (new 'static 'behavior-test-type :foo 123)) + (obj2 (new 'static 'behavior-test-type :foo 456))) + (with-pp + (protect (pp) + (set! pp (the process obj1)) + (test-bfunc #f) + (test obj2 #f) + ) + ) + ) +0 \ No newline at end of file diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 18080ecb19..2d508c563e 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -780,6 +780,12 @@ TEST_F(WithGameTests, MethodReplace) { {"relocate! foo: 123 heap: 1 name: 2\n0\n"}); } +TEST_F(WithGameTests, Behaviors) { + runner.run_static_test(env, testCategory, "test-behaviors.gc", + {"function self: 123\n" + "method obj: 456 self: 123\n0\n"}); +} + TEST(TypeConsistency, TypeConsistency) { Compiler compiler; compiler.enable_throw_on_redefines(); From 097cbd7110f0ec5ae8881da5a98b145d829eaf0c Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Jul 2021 12:42:34 -0400 Subject: [PATCH 2/8] working, but type pass got really slow --- common/goos/PrettyPrinter.cpp | 2 +- decompiler/IR2/Env.cpp | 30 +++++++-- decompiler/IR2/Env.h | 9 ++- decompiler/analysis/expression_build.cpp | 11 ++- decompiler/analysis/final_output.cpp | 28 +++++--- decompiler/config/all-types.gc | 4 +- .../decompiler/reference/decompiler-macros.gc | 12 ++++ .../reference/kernel/gkernel_REF.gc | 67 +++++++++---------- 8 files changed, 106 insertions(+), 57 deletions(-) diff --git a/common/goos/PrettyPrinter.cpp b/common/goos/PrettyPrinter.cpp index f0ccb5afd5..5e35b4edc8 100644 --- a/common/goos/PrettyPrinter.cpp +++ b/common/goos/PrettyPrinter.cpp @@ -601,7 +601,7 @@ void insertSpecialBreaks(NodePool& pool, PrettyPrinterNode* node) { } if (name == "defun" || name == "defmethod" || name == "defun-debug" || name == "let" || - name == "let*" || name == "rlet") { + name == "let*" || name == "rlet" || name == "defbehavior") { auto* first_list = getNextListOrEmptyListOnLine(node); if (first_list) { if (first_list->tok->kind == FormToken::TokenKind::EMPTY_PAIR) { diff --git a/decompiler/IR2/Env.cpp b/decompiler/IR2/Env.cpp index 8e21c8c227..ed587959fb 100644 --- a/decompiler/IR2/Env.cpp +++ b/decompiler/IR2/Env.cpp @@ -9,7 +9,8 @@ #include "common/util/math_util.h" namespace decompiler { -void Env::set_remap_for_function(int nargs) { +void Env::set_remap_for_function(const TypeSpec& ts) { + int nargs = ts.arg_count() - 1; for (int i = 0; i < nargs; i++) { std::string var_name; var_name.push_back(i >= 4 ? 't' : 'a'); @@ -18,10 +19,16 @@ void Env::set_remap_for_function(int nargs) { var_name.push_back('0'); m_var_remap[var_name] = ("arg" + std::to_string(i)); } - m_var_remap["s6-0"] = "pp"; + if (ts.try_get_tag("behavior")) { + m_var_remap["s6-0"] = "self"; + m_pp_mapped_by_behavior = true; + } else { + m_var_remap["s6-0"] = "pp"; + } } -void Env::set_remap_for_new_method(int nargs) { +void Env::set_remap_for_new_method(const TypeSpec& ts) { + int nargs = ts.arg_count() - 1; m_var_remap["a0-0"] = "allocation"; m_var_remap["a1-0"] = "type-to-make"; for (int i = 2; i < nargs; i++) { @@ -32,10 +39,16 @@ void Env::set_remap_for_new_method(int nargs) { var_name.push_back('0'); m_var_remap[var_name] = ("arg" + std::to_string(i - 2)); } - m_var_remap["s6-0"] = "pp"; + if (ts.try_get_tag("behavior")) { + m_var_remap["s6-0"] = "self"; + m_pp_mapped_by_behavior = true; + } else { + m_var_remap["s6-0"] = "pp"; + } } -void Env::set_remap_for_method(int nargs) { +void Env::set_remap_for_method(const TypeSpec& ts) { + int nargs = ts.arg_count() - 1; m_var_remap["a0-0"] = "obj"; for (int i = 1; i < nargs; i++) { std::string var_name; @@ -45,7 +58,12 @@ void Env::set_remap_for_method(int nargs) { var_name.push_back('0'); m_var_remap[var_name] = ("arg" + std::to_string(i - 1)); } - m_var_remap["s6-0"] = "pp"; + if (ts.try_get_tag("behavior")) { + m_var_remap["s6-0"] = "self"; + m_pp_mapped_by_behavior = true; + } else { + m_var_remap["s6-0"] = "pp"; + } } void Env::map_args_from_config(const std::vector& args_names, diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h index e18d5ab451..df5de86c37 100644 --- a/decompiler/IR2/Env.h +++ b/decompiler/IR2/Env.h @@ -149,9 +149,9 @@ class Env { const std::unordered_map& stack_casts() const { return m_stack_typecasts; } - void set_remap_for_function(int nargs); - void set_remap_for_method(int nargs); - void set_remap_for_new_method(int nargs); + void set_remap_for_function(const TypeSpec& ts); + void set_remap_for_method(const TypeSpec& ts); + void set_remap_for_new_method(const TypeSpec& ts); void map_args_from_config(const std::vector& args_names, const std::unordered_map& var_names); void map_args_from_config(const std::vector& args_names, @@ -208,6 +208,8 @@ class Env { // hacks: bool aggressively_reject_cond_to_value_rewrite = false; + bool pp_mapped_by_behavior() const { return m_pp_mapped_by_behavior; } + private: RegisterAccess m_end_var; @@ -218,6 +220,7 @@ class Env { VariableNames m_var_names; bool m_has_types = false; + bool m_pp_mapped_by_behavior = false; std::vector m_block_init_types; std::vector m_op_end_types; std::vector m_op_init_types; diff --git a/decompiler/analysis/expression_build.cpp b/decompiler/analysis/expression_build.cpp index b38ae9214d..079f7eb8cd 100644 --- a/decompiler/analysis/expression_build.cpp +++ b/decompiler/analysis/expression_build.cpp @@ -19,16 +19,21 @@ bool convert_to_expressions( const DecompilerTypeSystem& dts) { assert(top_level_form); + std::string pp_name = "pp"; + if (f.type.try_get_tag("behavior")) { + pp_name = "self"; + } + // set argument names to some reasonable defaults. these will be used if the user doesn't // give us anything more specific. if (f.guessed_name.kind == FunctionName::FunctionKind::GLOBAL || f.guessed_name.kind == FunctionName::FunctionKind::UNIDENTIFIED) { - f.ir2.env.set_remap_for_function(f.type.arg_count() - 1); + f.ir2.env.set_remap_for_function(f.type); } else if (f.guessed_name.kind == FunctionName::FunctionKind::METHOD) { if (f.guessed_name.method_id == GOAL_NEW_METHOD) { - f.ir2.env.set_remap_for_new_method(f.type.arg_count() - 1); + f.ir2.env.set_remap_for_new_method(f.type); } else { - f.ir2.env.set_remap_for_method(f.type.arg_count() - 1); + f.ir2.env.set_remap_for_method(f.type); } } diff --git a/decompiler/analysis/final_output.cpp b/decompiler/analysis/final_output.cpp index c2aaea1059..03b8375f51 100644 --- a/decompiler/analysis/final_output.cpp +++ b/decompiler/analysis/final_output.cpp @@ -26,12 +26,13 @@ goos::Object get_arg_list_for_function(const Function& func, const Env& env) { namespace { void append_body_to_function_definition(goos::Object* top_form, const std::vector& inline_body, - const FunctionVariableDefinitions& var_dec) { + const FunctionVariableDefinitions& var_dec, + const TypeSpec& ts) { if (var_dec.local_vars) { pretty_print::append(*top_form, pretty_print::build_list(*var_dec.local_vars)); } - if (var_dec.had_pp) { + if (var_dec.had_pp && !ts.try_get_tag("behavior")) { std::vector body_with_pp; body_with_pp.push_back(pretty_print::to_symbol("with-pp")); body_with_pp.insert(body_with_pp.end(), inline_body.begin(), inline_body.end()); @@ -48,7 +49,7 @@ goos::Object final_output_lambda(const Function& func) { func.ir2.top_form->inline_forms(inline_body, func.ir2.env); auto var_dec = func.ir2.env.local_var_type_list(func.ir2.top_form, func.type.arg_count() - 1); auto result = pretty_print::build_list("lambda", get_arg_list_for_function(func, func.ir2.env)); - append_body_to_function_definition(&result, inline_body, var_dec); + append_body_to_function_definition(&result, inline_body, var_dec, func.type); return result; } @@ -75,13 +76,24 @@ std::string final_defun_out(const Function& func, } else { assert(special_mode == FunctionDefSpecials::NONE); } + + auto behavior = func.type.try_get_tag("behavior"); + if (behavior) { + def_name = "defbehavior"; + } + std::vector top; top.push_back(pretty_print::to_symbol(def_name)); - top.push_back(pretty_print::to_symbol(func.guessed_name.to_string())); + + if (behavior) { + top.push_back(pretty_print::to_symbol(func.guessed_name.to_string() + " " + *behavior)); + } else { + top.push_back(pretty_print::to_symbol(func.guessed_name.to_string())); + } top.push_back(arguments); auto top_form = pretty_print::build_list(top); - append_body_to_function_definition(&top_form, inline_body, var_dec); + append_body_to_function_definition(&top_form, inline_body, var_dec, func.type); return pretty_print::to_string(top_form); } @@ -96,7 +108,7 @@ std::string final_defun_out(const Function& func, top.push_back(arguments); auto top_form = pretty_print::build_list(top); - append_body_to_function_definition(&top_form, inline_body, var_dec); + append_body_to_function_definition(&top_form, inline_body, var_dec, func.type); return pretty_print::to_string(top_form); } @@ -107,7 +119,7 @@ std::string final_defun_out(const Function& func, top.push_back(arguments); auto top_form = pretty_print::build_list(top); - append_body_to_function_definition(&top_form, inline_body, var_dec); + append_body_to_function_definition(&top_form, inline_body, var_dec, func.type); return pretty_print::to_string(top_form); } @@ -120,7 +132,7 @@ std::string final_defun_out(const Function& func, top.push_back(arguments); auto top_form = pretty_print::build_list(top); - append_body_to_function_definition(&top_form, inline_body, var_dec); + append_body_to_function_definition(&top_form, inline_body, var_dec, func.type); return pretty_print::to_string(top_form); } return "nyi"; diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index a3c9285636..db5e6dc1e4 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -1244,8 +1244,8 @@ (define-extern load-package (function string kheap pair)) (define-extern unload-package (function string pair)) (define-extern malloc (function symbol int pointer)) ;; from kernel-defs.gc -(define-extern remove-exit (function stack-frame)) -(define-extern stream<-process-mask (function object int object)) +(define-extern remove-exit (function :behavior process stack-frame)) +(define-extern stream<-process-mask (function object process-mask process-mask)) (define-extern return-from-thread (function none)) (define-extern return-from-thread-dead (function none)) (define-extern process-count (function process-tree int)) diff --git a/test/decompiler/reference/decompiler-macros.gc b/test/decompiler/reference/decompiler-macros.gc index b66c4533b2..dbd622b9ca 100644 --- a/test/decompiler/reference/decompiler-macros.gc +++ b/test/decompiler/reference/decompiler-macros.gc @@ -102,3 +102,15 @@ ) ) ) + +(defmacro defbehavior (name process-type bindings &rest body) + (if (and + (> (length body) 1) ;; more than one thing in function + (string? (first body)) ;; first thing is a string + ) + ;; then it's a docstring and we ignore it. + `(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@(cdr body))) + ;; otherwise don't ignore it. + `(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@body)) + ) + ) \ No newline at end of file diff --git a/test/decompiler/reference/kernel/gkernel_REF.gc b/test/decompiler/reference/kernel/gkernel_REF.gc index 8eb29768f8..8abe605873 100644 --- a/test/decompiler/reference/kernel/gkernel_REF.gc +++ b/test/decompiler/reference/kernel/gkernel_REF.gc @@ -197,93 +197,92 @@ ) ;; definition for function remove-exit -(defun remove-exit () - (with-pp (when (-> pp stack-frame-top) - (let ((v0-0 (-> pp stack-frame-top next))) - (set! (-> pp stack-frame-top) v0-0) - v0-0 - ) - ) +(defbehavior remove-exit process () + (when (-> self stack-frame-top) + (let ((v0-0 (-> self stack-frame-top next))) + (set! (-> self stack-frame-top) v0-0) + v0-0 + ) ) ) ;; definition (debug) for function stream<-process-mask -;; INFO: Return type mismatch int vs object. -(defun-debug stream<-process-mask ((arg0 object) (arg1 int)) +(defun-debug stream<-process-mask ((arg0 object) (arg1 process-mask)) (let ((s4-0 arg1)) - (if (= (logand #x1000000 s4-0) #x1000000) + (if (= (logand #x1000000 (the-as int s4-0)) (process-mask death)) (format arg0 "death ") ) - (if (= (logand #x800000 s4-0) #x800000) + (if (= (logand #x800000 (the-as int s4-0)) (process-mask attackable)) (format arg0 "attackable ") ) - (if (= (logand #x400000 s4-0) #x400000) + (if (= (logand #x400000 (the-as int s4-0)) (process-mask projectile)) (format arg0 "projectile ") ) - (if (= (logand #x200000 s4-0) #x200000) + (if (= (logand #x200000 (the-as int s4-0)) (process-mask entity)) (format arg0 "entity ") ) - (if (= (logand #x100000 s4-0) #x100000) + (if (= (logand #x100000 (the-as int s4-0)) (process-mask ambient)) (format arg0 "ambient ") ) - (if (= (logand #x80000 s4-0) #x80000) + (if (= (logand #x80000 (the-as int s4-0)) (process-mask platform)) (format arg0 "platform ") ) - (if (= (logand #x40000 s4-0) #x40000) + (if (= (logand #x40000 (the-as int s4-0)) (process-mask camera)) (format arg0 "camera ") ) - (if (= (logand #x20000 s4-0) #x20000) + (if (= (logand #x20000 (the-as int s4-0)) (process-mask enemy)) (format arg0 "enemy ") ) - (if (= (logand #x10000 s4-0) #x10000) + (if (= (logand #x10000 (the-as int s4-0)) (process-mask collectable)) (format arg0 "collectable ") ) - (if (= (logand s4-0 #x8000) #x8000) + (if (= (logand s4-0 (process-mask crate)) (process-mask crate)) (format arg0 "crate ") ) - (if (= (logand s4-0 #x4000) #x4000) + (if (= (logand s4-0 (process-mask sidekick)) (process-mask sidekick)) (format arg0 "sidekick ") ) - (if (= (logand s4-0 8192) 8192) + (if (= (logand s4-0 (process-mask target)) (process-mask target)) (format arg0 "target ") ) - (if (= (logand s4-0 4096) 4096) + (if + (= (logand s4-0 (process-mask movie-subject)) (process-mask movie-subject)) (format arg0 "movie-subject ") ) - (if (= (logand s4-0 2048) 2048) + (if (= (logand s4-0 (process-mask movie)) (process-mask movie)) (format arg0 "movie ") ) - (if (= (logand s4-0 1024) 1024) + (if (= (logand s4-0 (process-mask going)) (process-mask going)) (format arg0 "going ") ) - (if (= (logand s4-0 512) 512) + (if (= (logand s4-0 (process-mask heap-shrunk)) (process-mask heap-shrunk)) (format arg0 "heap-shrunk ") ) - (if (= (logand s4-0 256) 256) + (if (= (logand s4-0 (process-mask process-tree)) (process-mask process-tree)) (format arg0 "process-tree ") ) - (if (= (logand s4-0 128) 128) + (if (= (logand s4-0 (process-mask sleep-code)) (process-mask sleep-code)) (format arg0 "sleep-code ") ) - (if (= (logand s4-0 64) 64) + (if (= (logand s4-0 (process-mask sleep)) (process-mask sleep)) (format arg0 "sleep ") ) - (if (= (logand s4-0 32) 32) + (if (= (logand s4-0 (process-mask actor-pause)) (process-mask actor-pause)) (format arg0 "actor-pause ") ) - (if (= (logand s4-0 16) 16) + (if (= (logand s4-0 (process-mask progress)) (process-mask progress)) (format arg0 "progress ") ) - (if (= (logand s4-0 8) 8) + (if (= (logand s4-0 (process-mask menu)) (process-mask menu)) (format arg0 "menu ") ) - (if (= (logand s4-0 4) 4) + (if (= (logand s4-0 (process-mask pause)) (process-mask pause)) (format arg0 "pause ") ) - (if (= (logand s4-0 2) 2) + (if (= (logand s4-0 (process-mask draw)) (process-mask draw)) (format arg0 "draw ") ) - (if (= (logand s4-0 1) 1) + (if (= (logand s4-0 (process-mask execute)) (process-mask execute)) (format arg0 "execute ") ) ) From d45838a5d2b839653f13a0c49e33bbbf5a297cea Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Jul 2021 13:33:37 -0400 Subject: [PATCH 3/8] clean up --- common/type_system/TypeSpec.cpp | 37 +- common/type_system/TypeSpec.h | 102 ++- common/type_system/TypeSystem.cpp | 31 +- common/util/SmallVector.h | 637 ++++++++++++++++++ decompiler/analysis/expression_build.cpp | 11 +- decompiler/analysis/final_output.cpp | 2 +- decompiler/config/all-types.gc | 4 +- goal_src/engine/anim/aligner-h.gc | 2 +- goal_src/engine/camera/cam-interface.gc | 31 +- goal_src/engine/draw/drawable-h.gc | 3 + goal_src/kernel/gkernel.gc | 157 ++--- .../reference/engine/anim/aligner-h_REF.gc | 34 +- .../engine/camera/cam-interface_REF.gc | 35 +- 13 files changed, 907 insertions(+), 179 deletions(-) create mode 100644 common/util/SmallVector.h diff --git a/common/type_system/TypeSpec.cpp b/common/type_system/TypeSpec.cpp index 4ff3f95b6f..aeff692087 100644 --- a/common/type_system/TypeSpec.cpp +++ b/common/type_system/TypeSpec.cpp @@ -11,16 +11,19 @@ bool TypeTag::operator==(const TypeTag& other) const { } std::string TypeSpec::print() const { - if (m_arguments.empty() && m_tags.empty()) { + if ((!m_arguments || m_arguments->empty()) && m_tags.empty()) { return m_type; } else { std::string result = "(" + m_type; for (const auto& tag : m_tags) { result += fmt::format(" :{} {}", tag.name, tag.value); } - for (auto& x : m_arguments) { - result += " " + x.print(); + if (m_arguments) { + for (auto& x : *m_arguments) { + result += " " + x.print(); + } } + return result + ")"; } } @@ -30,15 +33,31 @@ bool TypeSpec::operator!=(const TypeSpec& other) const { } bool TypeSpec::operator==(const TypeSpec& other) const { - return m_type == other.m_type && m_arguments == other.m_arguments && m_tags == other.m_tags; + if (m_type != other.m_type) { + return false; + } + + if (m_tags != other.m_tags) { + return false; + } + + if (m_arguments && other.m_arguments) { + return *m_arguments == *other.m_arguments; + } + + return empty() && other.empty(); } TypeSpec TypeSpec::substitute_for_method_call(const std::string& method_type) const { TypeSpec result; result.m_type = (m_type == "_type_") ? method_type : m_type; - for (const auto& x : m_arguments) { - result.m_arguments.push_back(x.substitute_for_method_call(method_type)); + if (m_arguments) { + result.m_arguments = new std::vector(); + for (const auto& x : *m_arguments) { + result.m_arguments->push_back(x.substitute_for_method_call(method_type)); + } } + return result; } @@ -46,12 +65,12 @@ bool TypeSpec::is_compatible_child_method(const TypeSpec& implementation, const std::string& child_type) const { bool ok = implementation.m_type == m_type || (m_type == "_type_" && implementation.m_type == child_type); - if (!ok || implementation.m_arguments.size() != m_arguments.size()) { + if (!ok || implementation.arg_count() != arg_count()) { return false; } - for (size_t i = 0; i < m_arguments.size(); i++) { - if (!m_arguments[i].is_compatible_child_method(implementation.m_arguments[i], child_type)) { + for (size_t i = 0; i < arg_count(); i++) { + if (!get_arg(i).is_compatible_child_method(implementation.get_arg(i), child_type)) { return false; } } diff --git a/common/type_system/TypeSpec.h b/common/type_system/TypeSpec.h index 5e893bc19f..0aa5043972 100644 --- a/common/type_system/TypeSpec.h +++ b/common/type_system/TypeSpec.h @@ -9,6 +9,7 @@ #include #include #include "common/util/assert.h" +#include "common/util/SmallVector.h" /*! * A :name value modifier to apply to a type. @@ -18,6 +19,7 @@ struct TypeTag { std::string value; bool operator==(const TypeTag& other) const; + bool operator!=(const TypeTag& other) const { return !((*this) == other); } }; /*! @@ -34,15 +36,44 @@ class TypeSpec { TypeSpec(const std::string& type) : m_type(type) {} TypeSpec(const std::string& type, const std::vector& arguments) - : m_type(type), m_arguments(arguments) {} + : m_type(type), m_arguments(new std::vector(arguments)) {} + + TypeSpec(const TypeSpec& other) { + m_type = other.m_type; + m_tags = other.m_tags; + if (other.m_arguments) { + m_arguments = new std::vector(*other.m_arguments); + } + } + + TypeSpec& operator=(const TypeSpec& other) { + if (this == &other) { + return *this; + } - TypeSpec(const std::string& type, const std::vector& tags) - : m_type(type), m_tags(tags) {} + if (m_arguments) { + delete m_arguments; + m_arguments = nullptr; + } - TypeSpec(const std::string type, - const std::vector& arguments, - const std::vector& tags) - : m_type(type), m_arguments(arguments), m_tags(tags) {} + m_type = other.m_type; + m_tags = other.m_tags; + if (other.m_arguments) { + m_arguments = new std::vector(*other.m_arguments); + } + + return *this; + } + + ~TypeSpec() { delete m_arguments; } + + // TypeSpec(const std::string& type, const std::vector& tags) + // : m_type(type), m_tags(tags) {} + // + // TypeSpec(const std::string type, + // const std::vector& arguments, + // const std::vector& tags) + // : m_type(type), m_arguments(arguments), m_tags(tags) {} bool operator!=(const TypeSpec& other) const; bool operator==(const TypeSpec& other) const; @@ -50,7 +81,12 @@ class TypeSpec { const std::string& child_type) const; std::string print() const; - void add_arg(const TypeSpec& ts) { m_arguments.push_back(ts); } + void add_arg(const TypeSpec& ts) { + if (!m_arguments) { + m_arguments = new std::vector(); + } + m_arguments->push_back(ts); + } void add_new_tag(const std::string& tag_name, const std::string& tag_value); std::optional try_get_tag(const std::string& tag_name) const; const std::string& get_tag(const std::string& tag_name) const; @@ -59,29 +95,57 @@ class TypeSpec { const std::string base_type() const { return m_type; } - bool has_single_arg() const { return m_arguments.size() == 1; } + bool has_single_arg() const { + if (m_arguments) { + return m_arguments->size() == 1; + } + return 0; + } const TypeSpec& get_single_arg() const { - assert(m_arguments.size() == 1); - return m_arguments.front(); + assert(m_arguments); + assert(m_arguments->size() == 1); + return m_arguments->front(); } TypeSpec substitute_for_method_call(const std::string& method_type) const; - size_t arg_count() const { return m_arguments.size(); } + size_t arg_count() const { + if (!m_arguments) { + return 0; + } + return m_arguments->size(); + } - const TypeSpec& get_arg(int idx) const { return m_arguments.at(idx); } - TypeSpec& get_arg(int idx) { return m_arguments.at(idx); } + const TypeSpec& get_arg(int idx) const { + assert(m_arguments); + return m_arguments->at(idx); + } + TypeSpec& get_arg(int idx) { + assert(m_arguments); + return m_arguments->at(idx); + } const TypeSpec& last_arg() const { - assert(!m_arguments.empty()); - return m_arguments.back(); + assert(m_arguments); + assert(!m_arguments->empty()); + return m_arguments->back(); + } + + bool empty() const { + if (!m_arguments) { + return true; + } else { + return m_arguments->empty(); + } } - const std::vector& tags() const { return m_tags; } + const cu::SmallVector& tags() const { return m_tags; } private: friend class TypeSystem; std::string m_type; - std::vector m_arguments; - std::vector m_tags; + // hiding this behind a pointer makes things faster in the case where we have no + // arguments (most of the time) and makes the type analysis pass in the decompiler 2x faster. + std::vector* m_arguments = nullptr; + cu::SmallVector m_tags; }; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 0b6c62abbf..8ab18a34e2 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -1295,18 +1295,18 @@ bool TypeSystem::typecheck_and_throw(const TypeSpec& expected, } // next argument checks: - if (expected.m_arguments.size() == actual.m_arguments.size()) { - for (size_t i = 0; i < expected.m_arguments.size(); i++) { + if (expected.arg_count() == actual.arg_count()) { + for (size_t i = 0; i < expected.arg_count(); i++) { // don't print/throw because the error would be confusing. Better to fail only the // outer most check and print a single error message. - if (!tc(expected.m_arguments[i], actual.m_arguments[i])) { + if (!tc(expected.get_arg(i), actual.get_arg(i))) { success = false; break; } } } else { // different sizes of arguments. - if (expected.m_arguments.empty()) { + if (expected.arg_count() == 0) { // we expect zero arguments, but got some. The actual type is more specific, so this is fine. } else { // different sizes, and we expected arguments. No good! @@ -1447,16 +1447,14 @@ std::string TypeSystem::lca_base(const std::string& a, const std::string& b) con */ TypeSpec TypeSystem::lowest_common_ancestor(const TypeSpec& a, const TypeSpec& b) const { auto result = make_typespec(lca_base(a.base_type(), b.base_type())); - if (result == TypeSpec("function") && a.m_arguments.size() == 2 && b.m_arguments.size() == 2 && - (a.m_arguments.at(0) == TypeSpec("_varargs_") || - b.m_arguments.at(0) == TypeSpec("_varargs_"))) { + if (result == TypeSpec("function") && a.arg_count() == 2 && b.arg_count() == 2 && + (a.get_arg(0) == TypeSpec("_varargs_") || b.get_arg(0) == TypeSpec("_varargs_"))) { return TypeSpec("function"); } - if (!a.m_arguments.empty() && !b.m_arguments.empty() && - a.m_arguments.size() == b.m_arguments.size()) { + if (!a.empty() && !b.empty() && a.arg_count() == b.arg_count()) { // recursively add arguments - for (size_t i = 0; i < a.m_arguments.size(); i++) { - result.add_arg(lowest_common_ancestor(a.m_arguments.at(i), b.m_arguments.at(i))); + for (size_t i = 0; i < a.arg_count(); i++) { + result.add_arg(lowest_common_ancestor(a.get_arg(i), b.get_arg(i))); } } return result; @@ -1610,8 +1608,15 @@ std::string TypeSystem::generate_deftype_footer(const Type* type) const { methods_string.push_back(' '); } } - methods_string.append(fmt::format( - ") {} {})\n ", new_info->type.get_arg(new_info->type.arg_count() - 1).print(), 0)); + methods_string.append( + fmt::format(") {} ", new_info->type.get_arg(new_info->type.arg_count() - 1).print(), 0)); + + auto behavior = new_info->type.try_get_tag("behavior"); + if (behavior) { + methods_string.append(fmt::format(":behavior {} ", *behavior)); + } + + methods_string.append("0)\n "); } for (auto& info : type->get_methods_defined_for_type()) { diff --git a/common/util/SmallVector.h b/common/util/SmallVector.h new file mode 100644 index 0000000000..82c73d8bfa --- /dev/null +++ b/common/util/SmallVector.h @@ -0,0 +1,637 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace cu { +// This might seem stupid, but compiling an empty file with #include takes 0.5 seconds. +// Avoiding the include for is a huge win for compile times. +template +constexpr const T& max(const T& a, const T& b) { + return (a < b) ? b : a; +} + +template +constexpr const T& min(const T& a, const T& b) { + return (b < a) ? b : a; +} +/*! + * A SmallVector is a replacement for std::vector. It is optimized for the case where there + * are only a few elements by having a small inline array of objects. This uses more memory, but + * handles the very common case of having only a few elements much more efficiently. + * It will work correctly even with many elements by falling back to allocating on the heap. + * Even when it falls back to being a normal heap allocation, it is the same speed as a + * std::vector for most operations. Resizing operations may be a tiny bit slower, but this + * doesn't seem to cause issues in practice. + * + * You can specify the number of inline elements. If you don't exceed this number, it won't + * allocate. By default, it picks the largest nonzero number of elements that keeps the size + * under 128-bytes, which is somewhat arbitrary. + * + * It does not implement all features of a std::vector. + * Missing: + * - the "at" operator asserts instead of throwing an exception if the index is invalid. + * - no custom Allocators + * - no reverse iterators + * - max_size is not implemented + * - Can't have a SmallVector as a member of X. + * + */ +template +class SmallVector { + private: + // how much to increase the storage amount when we run out. + static constexpr double GROW_AMOUNT = 1.5; + + // by default, store stuff in here. C++ always uses sizeof(T) as the stride of an array, so this + // is just like an uninitialized array of T's, which starts at the proper alignment. + // We can't use an array of T's because that would default construct them, which we don't want. + typename std::aligned_storage::type m_inline[inline_elt_count]; + + // get a T* at the beginning of our inline storage. + constexpr const T* inline_begin() const { + return std::launder(reinterpret_cast(m_inline)); + } + constexpr T* inline_begin() { return std::launder(reinterpret_cast(m_inline)); } + + // regardless of our storage mode, these hold the beginning and end of the storage. + // by default, they are initialized to the inline storage. + T* m_storage_begin; + T* m_storage_end; + + // the number of in-use elements + std::size_t m_size; + + //// STORAGE HELPERS + + /*! + * Allocate new heap storage and set it as the current. + * The objects in storage are uninitialized. + */ + void allocate_and_set_heap_storage(std::size_t elt_count) { + m_storage_begin = std::launder(reinterpret_cast(new uint8_t[elt_count * sizeof(T)])); + m_storage_end = m_storage_begin + elt_count; + } + + /*! + * Free heap storage, without calling destructors of objects. + */ + void free_heap_storage(T* ptr) { delete[] std::launder(reinterpret_cast(ptr)); } + + /*! + * Set the current storage to the inline memory. + * We rely on m_storage_begin == inline_begin() to determine if we are using the inline memory. + * This ends up being faster than storing a bool for the mode. + */ + void set_inline_storage() { + m_storage_begin = inline_begin(); + m_storage_end = inline_begin() + inline_elt_count; + } + + bool using_heap_storage() const { return m_storage_begin != inline_begin(); } + + /*! + * Allocate (if needed) storage to hold size objects. + * Sets the container size. + * Assumes the current storage is stack (the default) + */ + void initialize_storage_and_size(std::size_t size) { + if (size > inline_elt_count) { + allocate_and_set_heap_storage(size); + } else { + set_inline_storage(); + } + m_size = size; + } + + void free_current_storage() { + if (using_heap_storage()) { + free_heap_storage(m_storage_begin); + } + } + + //// CONSTRUCTION HELPERS + + /*! + * Construct objects using the copy constructor, copying val for each. + */ + void construct_objects_by_filling(T* ptr, std::size_t count, const T& val) { + for (std::size_t i = 0; i < count; i++) { + new (ptr + i) T(val); + } + } + + /*! + * Construct objects using the default constructor. + */ + void construct_objects_by_default_ctor(T* ptr, std::size_t count) { + for (std::size_t i = 0; i < count; i++) { + new (ptr + i) T(); + } + } + + /*! + * Call destructor on an array of objects. + */ + void destroy_objects(T* ptr, std::size_t count) { + for (std::size_t i = 0; i < count; i++) { + ptr[i].~T(); + } + } + + /*! + * Move objects from src to dst, deleting them in src. + * Doing this in a single pass is more cache friendly for huge vectors. + * Compilers seem to be smart enough to turn this into a memcpy for trivial types still. + */ + void move_and_destroy(T* dst, T* src, std::size_t count) { + for (std::size_t i = 0; i < count; i++) { + new (dst + i) T(std::move(src[i])); + src[i].~T(); + } + } + + /*! + * Move objects from src to dst, starting at the highest memory address. This is useful + * if src and dst overlap. + */ + void move_and_destroy_reverse(T* dst, T* src, std::size_t count) { + for (std::size_t i = count; i-- > 0;) { + new (dst + i) T(std::move(src[i])); + src[i].~T(); + } + } + + /*! + * Copy objects from src to dst, using the copy constructor. + */ + void copy_construct_objects(T* dst, const T* src, std::size_t count) { + for (std::size_t i = 0; i < count; i++) { + new (dst + i) T(src[i]); + } + } + + template + void copy_objects_from_range(T* dst, InputIt first, InputIt last) { + auto iter = first; + while (iter != last) { + new (dst) T(*iter); + iter++; + dst++; + } + } + + public: + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + //// CONSTRUCTORS + /*! + * Constructor a SmallVector. By default the size is 0 and no elements are constructed. + */ + SmallVector() { initialize_storage_and_size(0); } + + /*! + * Initialize vector with count elements that are default initialized + */ + explicit SmallVector(std::size_t count) { + initialize_storage_and_size(count); + construct_objects_by_default_ctor(m_storage_begin, count); + } + + /*! + * Initialize vector with count values that are copied from the given value. + */ + SmallVector(std::size_t count, const T& value) { + // set up storage: + initialize_storage_and_size(count); + construct_objects_by_filling(m_storage_begin, count, value); + } + + /*! + * Initialize vector from iterators. + * In the case where your input iterator doesn't support random access, this ends up + * making two passes through your data. The first to check the size, then the second to copy. + */ + template + SmallVector(InputIt first, InputIt last) { + // this does a subtraction for random access iterators, or increments one-by-one for others. + initialize_storage_and_size(std::distance(first, last)); + copy_objects_from_range(m_storage_begin, first, last); + } + + /*! + * Initialize vector using values from another vector. + */ + SmallVector(const SmallVector& other) { + initialize_storage_and_size(other.size()); + copy_construct_objects(m_storage_begin, other.m_storage_begin, other.size()); + } + + /*! + * Initialize vector by moving from existing vector. Will leave other vector with size = 0. + */ + SmallVector(SmallVector&& other) { + if (other.using_heap_storage()) { + // steal the storage from the existing vector + m_storage_begin = other.m_storage_begin; + m_storage_end = other.m_storage_end; + // don't let the other vector reuse the storage by setting it back to inline. + other.set_inline_storage(); + } else { + set_inline_storage(); + // move from inline to inline + // we're going to set the other's size to 0, so we have to destroy its objects. + move_and_destroy(m_storage_begin, other.m_storage_begin, other.size()); + } + + m_size = other.m_size; + other.m_size = 0; + } + + /*! + * Initialize vector from an initializer list. + */ + SmallVector(std::initializer_list lst) { + initialize_storage_and_size(lst.size()); + copy_objects_from_range(m_storage_begin, lst.begin(), lst.end()); + } + + //// ASSIGNMENT + /*! + * Copy all data from another vector. + * Uses copy ctor in all cases, never copy assignment. + */ + SmallVector& operator=(const SmallVector& other) { + if (&other == this) { + return *this; + } + + // destroy all existing objects + destroy_objects(m_storage_begin, size()); + + // if we're bothering to copy, let's resize the storage to exactly what we need. + if (capacity() != other.size()) { + free_current_storage(); + initialize_storage_and_size(other.size()); + } + m_size = other.size(); + + copy_construct_objects(m_storage_begin, other.m_storage_begin, other.size()); + return *this; + } + + /*! + * Move all data from another vector. Leaves the other vector with nothing. + */ + SmallVector& operator=(SmallVector&& other) { + if (&other == this) { + return *this; + } + + // destroy existing objects + destroy_objects(m_storage_begin, size()); + + // kill old storage. We will either steal it from other or use stack. + free_current_storage(); + + if (other.size() > inline_elt_count) { + // steal the heap array + m_storage_begin = other.m_storage_begin; + m_storage_end = other.m_storage_end; + other.set_inline_storage(); + } else { + // move from inline storage + set_inline_storage(); + move_and_destroy(m_storage_begin, other.m_storage_begin, other.size()); + } + m_size = other.size(); + other.m_size = 0; + return *this; + } + + /*! + * Set the contents to count copies of a given value. + * Note - this is not super efficient and could avoid reallocating the heap memory in some cases. + */ + void assign(std::size_t count, const T& value) { + *this = SmallVector(count, value); + } + + /*! + * See note on other assign. + */ + template + void assign(InputIt first, InputIt last) { + *this = SmallVector(first, last); + } + + /*! + * See note on other assign. + */ + void assign(std::initializer_list lst) { *this = SmallVector(lst); } + + /*! + * Get the element at the index. Assert the index is valid. + */ + T& at(std::size_t idx) { + assert(idx < m_size); + return m_storage_begin[idx]; + } + + /*! + * Get the element at the index. Assert the index is valid. + */ + const T& at(std::size_t idx) const { + assert(idx < m_size); + return m_storage_begin[idx]; + } + + /*! + * Get the element at the index. No range checking. + */ + T& operator[](std::size_t idx) { return m_storage_begin[idx]; } + + /*! + * Get the element at the index. No range checking. + */ + const T& operator[](std::size_t idx) const { return m_storage_begin[idx]; } + + /*! + * Get the first element. Not valid if size == 0 + */ + T& front() { return m_storage_begin[0]; } + + /*! + * Get the first element. Not valid if size == 0 + */ + const T& front() const { return m_storage_begin[0]; } + + /*! + * Get the last element. Not valid if size == 0 + */ + T& back() { return m_storage_begin[m_size - 1]; } + + /*! + * Get the last element. Not valid if size == 0 + */ + const T& back() const { return m_storage_begin[m_size - 1]; } + + /*! + * Get a pointer to data. This may become invalid after any resize, just like a std::vector. + */ + T* data() { return m_storage_begin; } + + /*! + * Get a pointer to data. This may become invalid after any resize, just like a std::vector. + */ + const T* data() const { return m_storage_begin; } + + iterator begin() { return m_storage_begin; } + const_iterator begin() const { return m_storage_begin; } + const_iterator cbegin() const { return m_storage_begin; } + iterator end() { return m_storage_begin + m_size; } + const_iterator end() const { return m_storage_begin + m_size; } + const_iterator cend() const { return m_storage_begin + m_size; } + + reverse_iterator rbegin() { return m_storage_begin + m_size; } + const_reverse_iterator rbegin() const { return m_storage_begin + m_size; } + const_reverse_iterator crbegin() const { return m_storage_begin + m_size; } + reverse_iterator rend() { return m_storage_begin; } + const_reverse_iterator rend() const { return m_storage_begin; } + const_reverse_iterator crend() const { return m_storage_begin; } + + bool empty() const { return m_size == 0; } + std::size_t size() const { return m_size; } + + /*! + * Make sure we have enough storage to hold desired_count without allocating. + */ + void reserve(std::size_t desired_count) { + auto old_count = std::size_t(m_storage_end - m_storage_begin); + if (desired_count > old_count) { + // we need more storage. + auto old_storage = m_storage_begin; + auto old_uses_heap = using_heap_storage(); + + // replace current storage + allocate_and_set_heap_storage(desired_count); + // move objects into the new location and destroy the old ones. + move_and_destroy(m_storage_begin, old_storage, size()); + // free old, if needed. + if (old_uses_heap) { + free_heap_storage(old_storage); + } + } + } + + /*! + * The number of elements we can hold without allocating more memory. + */ + std::size_t capacity() const { return m_storage_end - m_storage_begin; } + + /*! + * Shrink our heap allocation to the smallest possible size that can possibly hold the + * current elements. This can shrink it to zero and move us back to inline storage. + */ + void shrink_to_fit() { + if (using_heap_storage() && capacity() > size()) { + auto old_storage = m_storage_begin; + if (size() > inline_elt_count) { + // go to smaller heap + allocate_and_set_heap_storage(size()); + } else { + // go back to inline + set_inline_storage(); + } + move_and_destroy(m_storage_begin, old_storage, size()); + free_heap_storage(old_storage); // was heap. + } + } + + /*! + * Destroy all objects and free all of our memory. Sets our size to 0. + */ + void clear() { + destroy_objects(m_storage_begin, m_size); + if (using_heap_storage()) { + free_heap_storage(m_storage_begin); + } + m_size = 0; + } + + /*! + * Insert value _before_ pos. + */ + iterator insert(iterator pos, const T& value) { + auto objects_before_insert = pos - begin(); + auto objects_after_insert = end() - pos; + iterator result; + if (size() + 1 > capacity()) { + // not enough room, need to reallocate + auto old_storage = m_storage_begin; + auto old_used_heap = using_heap_storage(); + allocate_and_set_heap_storage(GROW_AMOUNT * (size() + 1)); // todo grow policy here. + + // copy the objects before the insert + move_and_destroy(m_storage_begin, old_storage, objects_before_insert); + // copy the object to insert + result = new (m_storage_begin + objects_before_insert) T(value); + // copy the objects after the insert + move_and_destroy(m_storage_begin + objects_before_insert + 1, + old_storage + objects_before_insert, objects_after_insert); + + if (old_used_heap) { + free_heap_storage(old_storage); + } + } else { + // we have enough room, just move. + move_and_destroy_reverse(pos + 1, pos, objects_after_insert); + // and insert! + result = new (pos) T(value); + } + + m_size++; + return result; + } + + // insert iterator T&& value + // insert iterator count value + // insert iterator (it, it) + + // emplace + // erase + + // push_back + void push_back(const T& value) { + if (size() + 1 > capacity()) { + // not enough room! + reserve(GROW_AMOUNT * (size() + 1)); + } + // copy + new (m_storage_begin + size()) T(value); + m_size++; + } + + void push_back(const T&& value) { + if (size() + 1 > capacity()) { + // not enough room! + reserve(GROW_AMOUNT * (size() + 1)); + } + // move: + new (m_storage_begin + size()) T(std::move(value)); + m_size++; + } + + template + void emplace_back(Args&&... args) { + if (size() + 1 > capacity()) { + // not enough room! + reserve(GROW_AMOUNT * (size() + 1)); + } + new (m_storage_begin + size()) T(std::forward(args)...); + m_size++; + } + + void pop_back() { m_size--; } + + void relocate_some_and_destroy_rest(T* dst, + T* src, + std::size_t dst_count, + std::size_t src_count) { + std::size_t i = 0; + while (i < dst_count) { + new (dst + i) T(std::move(src[i])); + src[i].~T(); + i++; + } + + while (i < src_count) { + src[i].~T(); + i++; + } + } + + void resize(std::size_t new_size) { + if (size() == new_size) { + return; + } + + auto copy_count = min(size(), new_size); + bool old_used_heap = using_heap_storage(); + auto old_storage = m_storage_begin; + + if (new_size < inline_elt_count) { + // heap to inline + set_inline_storage(); + } else { + allocate_and_set_heap_storage(new_size); + } + + relocate_some_and_destroy_rest(m_storage_begin, old_storage, copy_count, size()); + if (old_used_heap) { + free_heap_storage(old_storage); + } + construct_objects_by_default_ctor(m_storage_begin + copy_count, new_size - copy_count); + m_size = new_size; + } + + void resize(std::size_t new_size, const T& value) { + if (size() == new_size) { + return; + } + + auto copy_count = min(size(), new_size); + bool old_used_heap = using_heap_storage(); + auto old_storage = m_storage_begin; + + if (new_size < inline_elt_count) { + // heap to inline + set_inline_storage(); + } else { + allocate_and_set_heap_storage(new_size); + } + + relocate_some_and_destroy_rest(m_storage_begin, old_storage, copy_count, size()); + if (old_used_heap) { + free_heap_storage(old_storage); + } + construct_objects_by_filling(m_storage_begin + copy_count, new_size - copy_count, value); + m_size = new_size; + } + + // swap + + bool operator==(const SmallVector& other) const { + if (other.size() != size()) { + return false; + } + for (std::size_t i = 0; i < size(); i++) { + if (operator[](i) != other[i]) { + return false; + } + } + return true; + } + + bool operator!=(const SmallVector& other) const { + return !((*this) == other); + } + + // std::swap + + ~SmallVector() { + // destroy allocated objects + destroy_objects(m_storage_begin, m_size); + // free heap storage. + if (using_heap_storage()) { + free_heap_storage(m_storage_begin); + } + } +}; +} // namespace cu diff --git a/decompiler/analysis/expression_build.cpp b/decompiler/analysis/expression_build.cpp index 079f7eb8cd..aa9b637935 100644 --- a/decompiler/analysis/expression_build.cpp +++ b/decompiler/analysis/expression_build.cpp @@ -19,21 +19,18 @@ bool convert_to_expressions( const DecompilerTypeSystem& dts) { assert(top_level_form); - std::string pp_name = "pp"; - if (f.type.try_get_tag("behavior")) { - pp_name = "self"; - } - // set argument names to some reasonable defaults. these will be used if the user doesn't // give us anything more specific. if (f.guessed_name.kind == FunctionName::FunctionKind::GLOBAL || f.guessed_name.kind == FunctionName::FunctionKind::UNIDENTIFIED) { f.ir2.env.set_remap_for_function(f.type); } else if (f.guessed_name.kind == FunctionName::FunctionKind::METHOD) { + auto method_type = + dts.ts.lookup_method(f.guessed_name.type_name, f.guessed_name.method_id).type; if (f.guessed_name.method_id == GOAL_NEW_METHOD) { - f.ir2.env.set_remap_for_new_method(f.type); + f.ir2.env.set_remap_for_new_method(method_type); } else { - f.ir2.env.set_remap_for_method(f.type); + f.ir2.env.set_remap_for_method(method_type); } } diff --git a/decompiler/analysis/final_output.cpp b/decompiler/analysis/final_output.cpp index 03b8375f51..fe0aef7a38 100644 --- a/decompiler/analysis/final_output.cpp +++ b/decompiler/analysis/final_output.cpp @@ -108,7 +108,7 @@ std::string final_defun_out(const Function& func, top.push_back(arguments); auto top_form = pretty_print::build_list(top); - append_body_to_function_definition(&top_form, inline_body, var_dec, func.type); + append_body_to_function_definition(&top_form, inline_body, var_dec, method_info.type); return pretty_print::to_string(top_form); } diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index db5e6dc1e4..2cb0ea5261 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -9666,7 +9666,7 @@ :size-assert #x134 :flag-assert #xe00000134 (:methods - (new (symbol type process) _type_ 0) + (new (symbol type process) _type_ :behavior process-drawable 0) (dummy-9 () none 9) (dummy-10 () none 10) (dummy-11 () none 11) @@ -17698,7 +17698,7 @@ (define-extern matrix-world->local (function matrix)) (define-extern camera-angle (function float)) -(define-extern camera-teleport-to-entity (function entity-actor none)) +(define-extern camera-teleport-to-entity (function entity-actor none :behavior process)) ;; - Symbols diff --git a/goal_src/engine/anim/aligner-h.gc b/goal_src/engine/anim/aligner-h.gc index 3098d6eafc..144b935e06 100644 --- a/goal_src/engine/anim/aligner-h.gc +++ b/goal_src/engine/anim/aligner-h.gc @@ -21,7 +21,7 @@ :size-assert #x134 :flag-assert #xe00000134 (:methods - (new (symbol type process) _type_ 0) + (new (symbol type process) _type_ :behavior process-drawable 0) (dummy-9 () none 9) (dummy-10 () none 10) (dummy-11 () none 11) diff --git a/goal_src/engine/camera/cam-interface.gc b/goal_src/engine/camera/cam-interface.gc index 5215eed92f..672a303376 100644 --- a/goal_src/engine/camera/cam-interface.gc +++ b/goal_src/engine/camera/cam-interface.gc @@ -63,22 +63,21 @@ ;; definition for function camera-teleport-to-entity ;; INFO: Return type mismatch int vs none. ;; Used lq/sq -(defun camera-teleport-to-entity ((arg0 entity-actor)) - (with-pp - (let ((gp-0 (new 'stack 'transformq))) - (let ((v1-1 (-> gp-0 trans))) - (set! (-> v1-1 quad) (-> (the-as transform (-> arg0 extra)) scale quad)) - ) - (quaternion-copy! (-> gp-0 quat) (-> arg0 quat)) - (vector-identity! (-> gp-0 scale)) - (let ((a1-2 (new 'stack-no-clear 'event-message-block))) - (set! (-> a1-2 from) pp) - (set! (-> a1-2 num-params) 1) - (set! (-> a1-2 message) 'teleport-to-transformq) - (set! (-> a1-2 param 0) (the-as uint gp-0)) - (send-event-function *camera* a1-2) - ) +(defbehavior camera-teleport-to-entity process ((arg0 entity-actor)) + (let ((gp-0 (new 'stack 'transformq))) + (set! (-> gp-0 trans quad) + (-> (the-as transform (-> arg0 extra)) scale quad) + ) + (quaternion-copy! (-> gp-0 quat) (-> arg0 quat)) + (vector-identity! (-> gp-0 scale)) + (let ((a1-2 (new 'stack-no-clear 'event-message-block))) + (set! (-> a1-2 from) self) + (set! (-> a1-2 num-params) 1) + (set! (-> a1-2 message) 'teleport-to-transformq) + (set! (-> a1-2 param 0) (the-as uint gp-0)) + (send-event-function *camera* a1-2) ) - (none) ) + 0 + (none) ) diff --git a/goal_src/engine/draw/drawable-h.gc b/goal_src/engine/draw/drawable-h.gc index 8ea2a3b571..a16467d69b 100644 --- a/goal_src/engine/draw/drawable-h.gc +++ b/goal_src/engine/draw/drawable-h.gc @@ -37,4 +37,7 @@ ;; NOTE - I'm guessing there was a define-extern earlier in the build process ;; This is actually set in process-drawable.gc, but it's used by files earlier in the process +;; assuming (state process-drawable) (define-extern process-drawable-art-error state) + +(declare-type process-drawable process) \ No newline at end of file diff --git a/goal_src/kernel/gkernel.gc b/goal_src/kernel/gkernel.gc index 6601256237..accbefcc4d 100644 --- a/goal_src/kernel/gkernel.gc +++ b/goal_src/kernel/gkernel.gc @@ -265,83 +265,86 @@ ;; The base class of process is process-tree. ;; Each process-tree element has a process-mask which indicates what type of node it is. -(defun-debug stream<-process-mask (stream (mask int)) - "Print out a process mask. This function may have been auto-generated?" - ; 24 - (if (not (eq? 0 (logand mask (process-mask death)))) - (format stream "death ")) - ; 23 - (if (not (eq? 0 (logand mask (process-mask attackable)))) - (format stream "attackable ")) - ; 22 - (if (not (eq? 0 (logand mask (process-mask projectile)))) - (format stream "projectile ")) - ; 21 - (if (not (eq? 0 (logand mask (process-mask entity)))) - (format stream "entity ")) - ; 20 - (if (not (eq? 0 (logand mask (process-mask ambient)))) - (format stream "ambient ")) - ; 19 - (if (not (eq? 0 (logand mask (process-mask platform)))) - (format stream "platform ")) - ; 18 - (if (not (eq? 0 (logand mask (process-mask camera)))) - (format stream "camera ")) - ; 17 - (if (not (eq? 0 (logand mask (process-mask enemy)))) - (format stream "enemy ")) - ; 16 - (if (not (eq? 0 (logand mask (process-mask collectable)))) - (format stream "collectable ")) - ; 15 - (if (not (eq? 0 (logand mask (process-mask crate)))) - (format stream "crate ")) - ; 14 - (if (not (eq? 0 (logand mask (process-mask sidekick)))) - (format stream "sidekick ")) - ; 13 - (if (not (eq? 0 (logand mask (process-mask target)))) - (format stream "target ")) - ; 12 - (if (not (eq? 0 (logand mask (process-mask movie-subject)))) - (format stream "movie-subject ")) - ; 11 - (if (not (eq? 0 (logand mask (process-mask movie)))) - (format stream "movie ")) - ; 10 - (if (not (eq? 0 (logand mask (process-mask going)))) - (format stream "going ")) - ; 9 - (if (not (eq? 0 (logand mask (process-mask heap-shrunk)))) - (format stream "heap-shrunk ")) - ; 8 - (if (not (eq? 0 (logand mask (process-mask process-tree)))) - (format stream "process-tree ")) - ; 7 - (if (not (eq? 0 (logand mask (process-mask sleep-code)))) - (format stream "sleep-code ")) - ; 6 - (if (not (eq? 0 (logand mask (process-mask sleep)))) - (format stream "sleep ")) - ; 5 - (if (not (eq? 0 (logand mask (process-mask actor-pause)))) - (format stream "actor-pause ")) - ; 4 - (if (not (eq? 0 (logand mask (process-mask progress)))) - (format stream "progress ")) - ; 3 - (if (not (eq? 0 (logand mask (process-mask menu)))) - (format stream "menu ")) - ; 2 - (if (not (eq? 0 (logand mask (process-mask pause)))) - (format stream "pause ")) - ; 1 - (if (not (eq? 0 (logand mask (process-mask draw)))) - (format stream "draw ")) - ; 0 - (if (not (eq? 0 (logand mask (process-mask execute)))) - (format stream "execute ")) +(defun-debug stream<-process-mask ((arg0 object) (arg1 process-mask)) + (let ((s4-0 arg1)) + (if (= (logand #x1000000 (the-as int s4-0)) (process-mask death)) + (format arg0 "death ") + ) + (if (= (logand #x800000 (the-as int s4-0)) (process-mask attackable)) + (format arg0 "attackable ") + ) + (if (= (logand #x400000 (the-as int s4-0)) (process-mask projectile)) + (format arg0 "projectile ") + ) + (if (= (logand #x200000 (the-as int s4-0)) (process-mask entity)) + (format arg0 "entity ") + ) + (if (= (logand #x100000 (the-as int s4-0)) (process-mask ambient)) + (format arg0 "ambient ") + ) + (if (= (logand #x80000 (the-as int s4-0)) (process-mask platform)) + (format arg0 "platform ") + ) + (if (= (logand #x40000 (the-as int s4-0)) (process-mask camera)) + (format arg0 "camera ") + ) + (if (= (logand #x20000 (the-as int s4-0)) (process-mask enemy)) + (format arg0 "enemy ") + ) + (if (= (logand #x10000 (the-as int s4-0)) (process-mask collectable)) + (format arg0 "collectable ") + ) + (if (= (logand s4-0 (process-mask crate)) (process-mask crate)) + (format arg0 "crate ") + ) + (if (= (logand s4-0 (process-mask sidekick)) (process-mask sidekick)) + (format arg0 "sidekick ") + ) + (if (= (logand s4-0 (process-mask target)) (process-mask target)) + (format arg0 "target ") + ) + (if + (= (logand s4-0 (process-mask movie-subject)) (process-mask movie-subject)) + (format arg0 "movie-subject ") + ) + (if (= (logand s4-0 (process-mask movie)) (process-mask movie)) + (format arg0 "movie ") + ) + (if (= (logand s4-0 (process-mask going)) (process-mask going)) + (format arg0 "going ") + ) + (if (= (logand s4-0 (process-mask heap-shrunk)) (process-mask heap-shrunk)) + (format arg0 "heap-shrunk ") + ) + (if (= (logand s4-0 (process-mask process-tree)) (process-mask process-tree)) + (format arg0 "process-tree ") + ) + (if (= (logand s4-0 (process-mask sleep-code)) (process-mask sleep-code)) + (format arg0 "sleep-code ") + ) + (if (= (logand s4-0 (process-mask sleep)) (process-mask sleep)) + (format arg0 "sleep ") + ) + (if (= (logand s4-0 (process-mask actor-pause)) (process-mask actor-pause)) + (format arg0 "actor-pause ") + ) + (if (= (logand s4-0 (process-mask progress)) (process-mask progress)) + (format arg0 "progress ") + ) + (if (= (logand s4-0 (process-mask menu)) (process-mask menu)) + (format arg0 "menu ") + ) + (if (= (logand s4-0 (process-mask pause)) (process-mask pause)) + (format arg0 "pause ") + ) + (if (= (logand s4-0 (process-mask draw)) (process-mask draw)) + (format arg0 "draw ") + ) + (if (= (logand s4-0 (process-mask execute)) (process-mask execute)) + (format arg0 "execute ") + ) + ) + arg1 ) ;; game state diff --git a/test/decompiler/reference/engine/anim/aligner-h_REF.gc b/test/decompiler/reference/engine/anim/aligner-h_REF.gc index 7e14f4ed0e..a15693a4db 100644 --- a/test/decompiler/reference/engine/anim/aligner-h_REF.gc +++ b/test/decompiler/reference/engine/anim/aligner-h_REF.gc @@ -17,7 +17,7 @@ :size-assert #x134 :flag-assert #xe00000134 (:methods - (new (symbol type process) _type_ 0) + (new (symbol type process) _type_ :behavior process-drawable 0) (dummy-9 () none 9) (dummy-10 () none 10) (dummy-11 () none 11) @@ -47,23 +47,25 @@ new align-control ((allocation symbol) (type-to-make type) (arg0 process)) - (with-pp - (let - ((obj - (object-new allocation type-to-make (the-as int (-> type-to-make size))) - ) + (let + ((obj + (object-new allocation type-to-make (the-as int (-> type-to-make size))) ) - (when (zero? obj) - (let ((t9-1 (the-as (function object object) enter-state)) - (a0-1 "memory") - ) - (set! (-> pp next-state) process-drawable-art-error) - (t9-1 a0-1) - ) - (return (the-as align-control 0)) + ) + (when (zero? obj) + (let ((t9-1 (the-as (function object object) enter-state)) + (a0-1 "memory") + ) + (set! (-> self next-state) process-drawable-art-error) + (t9-1 a0-1) ) - (set! (-> obj process) arg0) - obj + (return (the-as align-control 0)) ) + (set! (-> obj process) arg0) + obj ) ) + + + + diff --git a/test/decompiler/reference/engine/camera/cam-interface_REF.gc b/test/decompiler/reference/engine/camera/cam-interface_REF.gc index 8c1baf0c04..f85fcdf4da 100644 --- a/test/decompiler/reference/engine/camera/cam-interface_REF.gc +++ b/test/decompiler/reference/engine/camera/cam-interface_REF.gc @@ -59,23 +59,22 @@ ;; definition for function camera-teleport-to-entity ;; INFO: Return type mismatch int vs none. ;; Used lq/sq -(defun camera-teleport-to-entity ((arg0 entity-actor)) - (with-pp (let ((gp-0 (new 'stack 'transformq))) - (set! - (-> gp-0 trans quad) - (-> (the-as transform (-> arg0 extra)) scale quad) - ) - (quaternion-copy! (-> gp-0 quat) (-> arg0 quat)) - (vector-identity! (-> gp-0 scale)) - (let ((a1-2 (new 'stack-no-clear 'event-message-block))) - (set! (-> a1-2 from) pp) - (set! (-> a1-2 num-params) 1) - (set! (-> a1-2 message) 'teleport-to-transformq) - (set! (-> a1-2 param 0) (the-as uint gp-0)) - (send-event-function *camera* a1-2) - ) - ) - 0 - (none) +(defbehavior camera-teleport-to-entity process ((arg0 entity-actor)) + (let ((gp-0 (new 'stack 'transformq))) + (set! + (-> gp-0 trans quad) + (-> (the-as transform (-> arg0 extra)) scale quad) + ) + (quaternion-copy! (-> gp-0 quat) (-> arg0 quat)) + (vector-identity! (-> gp-0 scale)) + (let ((a1-2 (new 'stack-no-clear 'event-message-block))) + (set! (-> a1-2 from) self) + (set! (-> a1-2 num-params) 1) + (set! (-> a1-2 message) 'teleport-to-transformq) + (set! (-> a1-2 param 0) (the-as uint gp-0)) + (send-event-function *camera* a1-2) + ) ) + 0 + (none) ) From 3d05ee076095532c5744e9b3a39925280b15ac26 Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Jul 2021 13:39:12 -0400 Subject: [PATCH 4/8] changelog and flip order --- common/type_system/TypeSpec.cpp | 8 +++++--- docs/markdown/progress-notes/changelog.md | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/common/type_system/TypeSpec.cpp b/common/type_system/TypeSpec.cpp index aeff692087..733134deaa 100644 --- a/common/type_system/TypeSpec.cpp +++ b/common/type_system/TypeSpec.cpp @@ -15,15 +15,17 @@ std::string TypeSpec::print() const { return m_type; } else { std::string result = "(" + m_type; - for (const auto& tag : m_tags) { - result += fmt::format(" :{} {}", tag.name, tag.value); - } + if (m_arguments) { for (auto& x : *m_arguments) { result += " " + x.print(); } } + for (const auto& tag : m_tags) { + result += fmt::format(" :{} {}", tag.name, tag.value); + } + return result + ")"; } } diff --git a/docs/markdown/progress-notes/changelog.md b/docs/markdown/progress-notes/changelog.md index 455bfa04a8..a8b637fcd4 100644 --- a/docs/markdown/progress-notes/changelog.md +++ b/docs/markdown/progress-notes/changelog.md @@ -172,4 +172,7 @@ - You can now set a field which has a forward declared structure or basic type - `cdr` now returns an object of type `pair`. - `lambda`s can now be used inside of a static object definition. -- Methods can now be `:replace`d to override their type from their parent. Use this with extreme care. \ No newline at end of file +- Methods can now be `:replace`d to override their type from their parent. Use this with extreme care. +- TypeSpecs now support "tags". This can specify a `:behavior` tag for a function. +- Lambdas and methods now support `:behavior` to specify the current process type. +- `defbehavior` has been added to define a global behavior. \ No newline at end of file From f0fa9e4da614548e56fc196f3447a0b9c5f745c2 Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Jul 2021 13:47:59 -0400 Subject: [PATCH 5/8] clean up and add tests --- common/type_system/TypeSpec.cpp | 1 + test/test_common_util.cpp | 210 +++++++++++++++++++++++++++++++- 2 files changed, 210 insertions(+), 1 deletion(-) diff --git a/common/type_system/TypeSpec.cpp b/common/type_system/TypeSpec.cpp index 733134deaa..2765bd0225 100644 --- a/common/type_system/TypeSpec.cpp +++ b/common/type_system/TypeSpec.cpp @@ -3,6 +3,7 @@ * A GOAL TypeSpec is a reference to a type or compound type. */ +#include #include "TypeSpec.h" #include "third-party/fmt/core.h" diff --git a/test/test_common_util.cpp b/test/test_common_util.cpp index b9683a605f..472ec7399e 100644 --- a/test/test_common_util.cpp +++ b/test/test_common_util.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "common/util/FileUtil.h" #include "common/util/Trie.h" @@ -12,6 +13,7 @@ #include "third-party/fmt/core.h" #include "common/util/print_float.h" #include "common/util/CopyOnWrite.h" +#include "common/util/SmallVector.h" TEST(CommonUtil, get_file_path) { std::vector test = {"cabbage", "banana", "apple"}; @@ -182,4 +184,210 @@ TEST(CommonUtil, CopyOnWrite) { EXPECT_EQ(*x, 3); EXPECT_EQ(*y, 3); EXPECT_EQ(*z, 15); -} \ No newline at end of file +} + +namespace cu { +namespace test { + +class ThrowOnDefaultConstruct { + public: + ThrowOnDefaultConstruct() { + throw std::runtime_error("ThrowOnDefaultConstruct was default constructed."); + } +}; + +class ThrowOnDestruct { + public: + ~ThrowOnDestruct() { + // not a good idea to throw. + exit(-1); + } +}; + +struct RuleOfFiveExample { + // if we fail to call the destructor we'll leak memory, which will get caught with valgrind. + RuleOfFiveExample() { mem = new int; } + RuleOfFiveExample(const RuleOfFiveExample& other) { + if (&other != this) { + mem = new int; + } + } + RuleOfFiveExample(RuleOfFiveExample&& other) noexcept { + if (&other != this) { + mem = other.mem; + other.mem = nullptr; + } + } + + RuleOfFiveExample& operator=(const RuleOfFiveExample& other) { + if (&other != this) { + delete mem; + mem = new int; + } + return *this; + } + + RuleOfFiveExample& operator=(RuleOfFiveExample&& other) noexcept { + if (&other != this) { + mem = other.mem; + other.mem = nullptr; + } + return *this; + } + + ~RuleOfFiveExample() { delete mem; } + int value = 12; + int* mem; +}; + +TEST(SmallVector, NoConstruction) { + // Confirm that an empty vector constructs nothing. + SmallVector empty; + EXPECT_EQ(empty.size(), 0); + EXPECT_TRUE(empty.empty()); + + // should also destroy nothing + SmallVector empty2; + // should be no issues declaring a vector with no inline storage. + SmallVector no_stack_storage; +} + +TEST(SmallVector, ConstructWithSize) { + // Test construction calls default constructors. + SmallVector heap_no_stack(12); + SmallVector full_stack(12); + SmallVector not_full_stack(11); + SmallVector overflow_to_heap(13); + + // size + EXPECT_EQ(heap_no_stack.size(), 12); + EXPECT_EQ(full_stack.size(), 12); + EXPECT_EQ(not_full_stack.size(), 11); + EXPECT_EQ(overflow_to_heap.size(), 13); + + // capacity + EXPECT_EQ(heap_no_stack.capacity(), 12); + EXPECT_EQ(full_stack.capacity(), 12); + EXPECT_EQ(not_full_stack.capacity(), 12); + EXPECT_EQ(overflow_to_heap.capacity(), 13); + + // were they constructed? + int i = 0; + for (auto& obj : heap_no_stack) { + EXPECT_EQ(obj.value, 12); + i++; + } + EXPECT_EQ(i, 12); + + for (auto vec : {full_stack, not_full_stack, overflow_to_heap}) { + int j = 0; + for (auto& obj : heap_no_stack) { + EXPECT_EQ(obj.value, 12); + j++; + } + EXPECT_EQ(j, 12); + } +} + +// small std::string's aren't heap allocated. +constexpr const char* long_string_1 = "this-is-a-string-thats-long-enough-to-go-on-the-heap!"; +constexpr const char* long_string_2 = "another-string-thats-long-enough-to-go-on-the-heap!"; +constexpr const char* long_string_3 = "also-long-enough-to-go-on-the-heap!"; + +TEST(SmallVector, ConstructByCopying) { + // test that we copy the input properly. + SmallVector strings(20, long_string_1); + EXPECT_EQ(strings[0], long_string_1); + strings[0] = long_string_2; + EXPECT_EQ(strings[1], long_string_1); + EXPECT_EQ(strings.size(), 20); +} + +TEST(SmallVector, ConstructFromIterator) { + std::unordered_set stuff; + for (auto x : Range(10, 20)) { + stuff.insert(long_string_1 + std::to_string(x)); + } + + // iterators into unordered set can't be subtracted, but this should still work. + SmallVector strings(stuff.begin(), stuff.end()); + EXPECT_EQ(strings.size(), 10); + std::unordered_set stuff2(strings.begin(), strings.end()); + EXPECT_EQ(stuff, stuff2); + + // these can be subtracted. + SmallVector strings2(strings.begin(), strings.end()); + EXPECT_EQ(strings, strings2); + EXPECT_EQ(strings.at(1), strings2.at(1)); + strings.at(1) = long_string_2; + EXPECT_TRUE(strings.at(1) != strings2.at(1)); +} + +TEST(SmallVector, ConstructFromCopy) { + SmallVector one = {long_string_1, long_string_2, long_string_3}; + SmallVector two(one); + EXPECT_EQ(two.at(2), long_string_3); + two.at(2) = "four"; + EXPECT_EQ(one.at(2), long_string_3); +} + +TEST(SmallVector, ConstructFromMoveInline) { + // stack move + SmallVector one = {long_string_1, long_string_2, long_string_3}; + SmallVector two(std::move(one)); + EXPECT_TRUE(one.empty()); // this is the convention of SmallVector. + EXPECT_EQ(two.at(2), long_string_3); +} + +TEST(SmallVector, ConstructFromMoveHeap) { + // heap move + SmallVector one = {long_string_1, long_string_2, long_string_3}; + SmallVector two(std::move(one)); + EXPECT_TRUE(one.empty()); // this is the convention of SmallVector. + EXPECT_EQ(two.at(2), long_string_3); + EXPECT_EQ(two.size(), 3); +} + +TEST(SmallVector, ConstructFromInitList) { + SmallVector one({long_string_1, long_string_2, long_string_3}); + EXPECT_EQ(one.at(2), long_string_3); + EXPECT_EQ(one.size(), 3); +} + +/* +TEST(SmallVector, SelfCopyAndMoveAssignment) { + SmallVector one({long_string_1, long_string_2, long_string_3}); + one = one; + one = std::move(one); + EXPECT_EQ(one.at(2), long_string_3); + EXPECT_EQ(one.size(), 3); +} +*/ + +TEST(SmallVector, CopyAssign) { + // heap -> heap + SmallVector heap_one({long_string_1, long_string_2, long_string_3}), + heap_two({"a", "b"}); + heap_one = heap_two; + EXPECT_TRUE(heap_one.size() == 2); + EXPECT_TRUE(heap_one[0] == "a"); + EXPECT_TRUE(heap_one[1] == "b"); +} + +TEST(SmallVector, Construction) { + SmallVector empty; + EXPECT_EQ(empty.size(), 0); + EXPECT_TRUE(empty.empty()); + empty.reserve(256); + EXPECT_EQ(empty.size(), 0); + EXPECT_TRUE(empty.empty()); + empty.shrink_to_fit(); + EXPECT_EQ(empty.capacity(), 128); + + SmallVector one(1); + EXPECT_EQ(one.size(), 1); + EXPECT_FALSE(one.empty()); +} + +} // namespace test +} // namespace cu \ No newline at end of file From f659090f73d17eb3b7b110e2562862fe6b16ce2d Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Jul 2021 16:11:29 -0400 Subject: [PATCH 6/8] fix zero size array --- test/test_common_util.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/test_common_util.cpp b/test/test_common_util.cpp index 472ec7399e..61d90985e3 100644 --- a/test/test_common_util.cpp +++ b/test/test_common_util.cpp @@ -248,8 +248,6 @@ TEST(SmallVector, NoConstruction) { // should also destroy nothing SmallVector empty2; - // should be no issues declaring a vector with no inline storage. - SmallVector no_stack_storage; } TEST(SmallVector, ConstructWithSize) { @@ -341,15 +339,15 @@ TEST(SmallVector, ConstructFromMoveInline) { TEST(SmallVector, ConstructFromMoveHeap) { // heap move - SmallVector one = {long_string_1, long_string_2, long_string_3}; - SmallVector two(std::move(one)); + SmallVector one = {long_string_1, long_string_2, long_string_3}; + SmallVector two(std::move(one)); EXPECT_TRUE(one.empty()); // this is the convention of SmallVector. EXPECT_EQ(two.at(2), long_string_3); EXPECT_EQ(two.size(), 3); } TEST(SmallVector, ConstructFromInitList) { - SmallVector one({long_string_1, long_string_2, long_string_3}); + SmallVector one({long_string_1, long_string_2, long_string_3}); EXPECT_EQ(one.at(2), long_string_3); EXPECT_EQ(one.size(), 3); } From cee34bcee820dfea4c070e03a78f76adb6fd32d1 Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Jul 2021 16:18:28 -0400 Subject: [PATCH 7/8] handle lambdas correctly --- decompiler/analysis/final_output.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/decompiler/analysis/final_output.cpp b/decompiler/analysis/final_output.cpp index fe0aef7a38..4a34c5060d 100644 --- a/decompiler/analysis/final_output.cpp +++ b/decompiler/analysis/final_output.cpp @@ -48,9 +48,18 @@ goos::Object final_output_lambda(const Function& func) { std::vector inline_body; func.ir2.top_form->inline_forms(inline_body, func.ir2.env); auto var_dec = func.ir2.env.local_var_type_list(func.ir2.top_form, func.type.arg_count() - 1); - auto result = pretty_print::build_list("lambda", get_arg_list_for_function(func, func.ir2.env)); - append_body_to_function_definition(&result, inline_body, var_dec, func.type); - return result; + + auto behavior = func.type.try_get_tag("behavior"); + if (behavior) { + auto result = pretty_print::build_list(fmt::format("lambda :behavior {}", *behavior), + get_arg_list_for_function(func, func.ir2.env)); + append_body_to_function_definition(&result, inline_body, var_dec, func.type); + return result; + } else { + auto result = pretty_print::build_list("lambda", get_arg_list_for_function(func, func.ir2.env)); + append_body_to_function_definition(&result, inline_body, var_dec, func.type); + return result; + } } std::string final_defun_out(const Function& func, From 63f68f738aec5a9d09c37cd162f4d05271af71d7 Mon Sep 17 00:00:00 2001 From: water Date: Sun, 4 Jul 2021 16:40:33 -0400 Subject: [PATCH 8/8] another windows fix --- test/test_common_util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_common_util.cpp b/test/test_common_util.cpp index 61d90985e3..94c8c2a62b 100644 --- a/test/test_common_util.cpp +++ b/test/test_common_util.cpp @@ -252,7 +252,7 @@ TEST(SmallVector, NoConstruction) { TEST(SmallVector, ConstructWithSize) { // Test construction calls default constructors. - SmallVector heap_no_stack(12); + SmallVector heap_no_stack(12); SmallVector full_stack(12); SmallVector not_full_stack(11); SmallVector overflow_to_heap(13);