From d5d0d7f924d25af3acd31d8183e5e1eaf54dffa1 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Fri, 16 Oct 2020 17:08:26 -0400 Subject: [PATCH] Add compiler features for gkernel (#83) * add some type stuff for gkernel * more tweaks * blah * more little tweaks and more of gkernel * add static structures with integer fields but not basics * static structures * update gkernel --- common/type_system/Type.h | 1 + common/type_system/TypeSystem.cpp | 127 +++++-- common/type_system/TypeSystem.h | 14 +- common/type_system/deftype.cpp | 41 ++- common/type_system/deftype.h | 1 + doc/changelog.md | 15 +- goal_src/kernel/gkernel-h.gc | 311 +++++++++++++++++- goal_src/kernel/gkernel.gc | 93 +++++- goalc/CMakeLists.txt | 1 + goalc/compiler/Compiler.h | 23 +- goalc/compiler/StaticObject.cpp | 57 ++++ goalc/compiler/StaticObject.h | 29 ++ goalc/compiler/Util.cpp | 24 +- goalc/compiler/compilation/Atoms.cpp | 4 +- goalc/compiler/compilation/Define.cpp | 5 + goalc/compiler/compilation/Function.cpp | 4 + goalc/compiler/compilation/Macro.cpp | 44 ++- goalc/compiler/compilation/Static.cpp | 132 ++++++++ goalc/compiler/compilation/Type.cpp | 98 ++++-- goalc/emitter/ObjectGenerator.cpp | 1 + .../methods/methods.static.gc | 4 +- .../with_game/test-new-static-basic.gc | 23 ++ .../test-new-static-structure-integers.gc | 21 ++ test/goalc/test_with_game.cpp | 4 + 24 files changed, 985 insertions(+), 92 deletions(-) create mode 100644 goalc/compiler/compilation/Static.cpp create mode 100644 test/goalc/source_templates/with_game/test-new-static-basic.gc create mode 100644 test/goalc/source_templates/with_game/test-new-static-structure-integers.gc diff --git a/common/type_system/Type.h b/common/type_system/Type.h index dab3adec97..ccaf3c0f21 100644 --- a/common/type_system/Type.h +++ b/common/type_system/Type.h @@ -224,6 +224,7 @@ class StructureType : public ReferenceType { bool lookup_field(const std::string& name, Field* out); bool is_dynamic() const { return m_dynamic; } ~StructureType() = default; + void set_pack(bool pack) { m_pack = pack; } protected: friend class TypeSystem; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 0d1f4a93a1..2ba3c9089a 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -68,8 +68,32 @@ Type* TypeSystem::add_type(const std::string& name, std::unique_ptr type) * This will allow the type system to generate TypeSpecs for this type, but not access detailed * information, or know the exact size. */ -void TypeSystem::forward_declare_type(std::string name) { - m_forward_declared_types.insert(std::move(name)); +void TypeSystem::forward_declare_type(const std::string& name) { + if (m_types.find(name) == m_types.end()) { + m_forward_declared_types[name] = TYPE; + } +} + +/*! + * Inform the type system that there will eventually be a type named "name" and that it's a basic. + * This allows the type to be used in a few specific places. For instance a basic can have + * a field where an element is the same type. + */ +void TypeSystem::forward_declare_type_as_basic(const std::string& name) { + if (m_types.find(name) == m_types.end()) { + m_forward_declared_types[name] = BASIC; + } +} + +/*! + * Inform the type system that there will eventually be a type named "name" and that it's a + * structure. This allows the type to be used in a few specific places. For instance a structure can + * have a field where an element is the same type. + */ +void TypeSystem::forward_declare_type_as_structure(const std::string& name) { + if (m_types.find(name) == m_types.end()) { + m_forward_declared_types[name] = STRUCTURE; + } } /*! @@ -121,7 +145,7 @@ DerefInfo TypeSystem::get_deref_info(const TypeSpec& ts) { } else if (ts.base_type() == "pointer") { info.can_deref = true; info.result_type = ts.get_single_arg(); - auto result_type = lookup_type(info.result_type); + auto result_type = lookup_type_allow_partial_def(info.result_type); if (result_type->is_reference()) { // in memory, an array of pointers info.stride = POINTER_SIZE; @@ -160,6 +184,10 @@ bool TypeSystem::fully_defined_type_exists(const std::string& name) const { return m_types.find(name) != m_types.end(); } +bool TypeSystem::partially_defined_type_exists(const std::string& name) const { + return m_forward_declared_types.find(name) != m_forward_declared_types.end(); +} + /*! * Create a typespec for a function. If the function doesn't return anything, use "none" as the * return type. @@ -233,6 +261,42 @@ Type* TypeSystem::lookup_type(const TypeSpec& ts) const { return lookup_type(ts.base_type()); } +/*! + * Get type info. If the type is not fully defined (ie, we are parsing its deftype now) and its + * forward defined as a basic or structure, just get basic/structure. + */ +Type* TypeSystem::lookup_type_allow_partial_def(const TypeSpec& ts) const { + return lookup_type_allow_partial_def(ts.base_type()); +} + +/*! + * Get type info. If the type is not fully defined (ie, we are parsing its deftype now) and its + * forward defined as a basic or structure, just get basic/structure. + */ +Type* TypeSystem::lookup_type_allow_partial_def(const std::string& name) const { + // look up fully defined types first: + auto kv = m_types.find(name); + if (kv != m_types.end()) { + return kv->second.get(); + } + + auto fwd_dec = m_forward_declared_types.find(name); + if (fwd_dec != m_forward_declared_types.end()) { + if (fwd_dec->second == STRUCTURE) { + return lookup_type("structure"); + } else if (fwd_dec->second == BASIC) { + return lookup_type("basic"); + } else { + fmt::print("[TypeSystem] The type {} is not fully define (allow partial).\n", name); + } + + } else { + fmt::print("[TypeSystem] The type {} is not defined.\n", name); + } + + throw std::runtime_error("lookup_type_allow_partial_def failed"); +} + MethodInfo TypeSystem::add_method(const std::string& type_name, const std::string& method_name, const TypeSpec& ts, @@ -412,7 +476,7 @@ FieldLookupInfo TypeSystem::lookup_field_info(const std::string& type_name, info.array_size = info.field.array_size(); } - auto base_type = lookup_type(info.field.type()); + auto base_type = lookup_type_allow_partial_def(info.field.type()); if (base_type->is_reference()) { if (info.field.is_inline()) { if (info.field.is_array()) { @@ -542,11 +606,7 @@ void TypeSystem::add_builtin_types() { auto kheap_type = add_builtin_structure("structure", "kheap"); auto array_type = add_builtin_basic("basic", "array"); auto pair_type = add_builtin_structure("object", "pair", true); - auto process_tree_type = add_builtin_basic("basic", "process-tree"); - auto process_type = add_builtin_basic("process-tree", "process"); - auto thread_type = add_builtin_basic("basic", "thread"); auto connectable_type = add_builtin_structure("structure", "connectable"); - auto stack_frame_type = add_builtin_basic("basic", "stack-frame"); auto file_stream_type = add_builtin_basic("basic", "file-stream"); add_builtin_value_type("object", "pointer", 4); auto inline_array_type = add_builtin_value_type("object", "inline-array", 4); @@ -566,7 +626,7 @@ void TypeSystem::add_builtin_types() { add_builtin_value_type("uinteger", "uint8", 1); add_builtin_value_type("uinteger", "uint16", 2); add_builtin_value_type("uinteger", "uint32", 4); - add_builtin_value_type("uinteger", "uint64", 81); + add_builtin_value_type("uinteger", "uint64", 8); add_builtin_value_type("uinteger", "uint128", 16, false, false, RegKind::INT_128); auto int_type = add_builtin_value_type("integer", "int", 8, false, true); @@ -585,7 +645,7 @@ void TypeSystem::add_builtin_types() { make_function_typespec({"_type_"}, "int")); // todo - this integer type? add_method(obj_type, "asize-of", make_function_typespec({"_type_"}, "int")); add_method(obj_type, "copy", make_function_typespec({"_type_", "symbol"}, "_type_")); - add_method(obj_type, "relocate", make_function_typespec({"_type_", "int32"}, "_type_")); + add_method(obj_type, "relocate", make_function_typespec({"_type_", "int"}, "_type_")); add_method(obj_type, "mem-usage", make_function_typespec({"_type_"}, "int32")); // todo - this is a guess. @@ -660,11 +720,7 @@ void TypeSystem::add_builtin_types() { add_field_to_type(pair_type, "cdr", make_typespec("object")); // todo, with kernel - (void)process_tree_type; - (void)process_type; - (void)thread_type; (void)connectable_type; - (void)stack_frame_type; (void)file_stream_type; } @@ -717,7 +773,7 @@ Field TypeSystem::lookup_field(const std::string& type_name, const std::string& * Get the minimum required aligment of a field. */ int TypeSystem::get_alignment_in_type(const Field& field) { - auto field_type = lookup_type(field.type()); + auto field_type = lookup_type_allow_partial_def(field.type()); if (field.is_inline()) { if (field.is_array()) { @@ -727,7 +783,7 @@ int TypeSystem::get_alignment_in_type(const Field& field) { } else { // it is an inlined field, so return the alignment in memory // TODO - for inline, but not inline array, do we use structure alignment always? - return field_type->get_in_memory_alignment(); + return field_type->get_inline_array_alignment(); } } @@ -740,18 +796,31 @@ int TypeSystem::get_alignment_in_type(const Field& field) { return POINTER_SIZE; } +namespace { +bool allow_inline(const Type* type) { + auto name = type->get_name(); + return name != "basic" && name != "structure"; +} +} // namespace + /*! * Get the size of a field in a type. The array sizes should be consistent with get_deref_info's * stride. */ -int TypeSystem::get_size_in_type(const Field& field) { +int TypeSystem::get_size_in_type(const Field& field) const { if (field.is_dynamic()) { return 0; } - auto field_type = lookup_type(field.type()); + auto field_type = lookup_type_allow_partial_def(field.type()); if (field.is_array()) { if (field.is_inline()) { + if (!allow_inline(field_type)) { + fmt::print( + "[Type System] Attempted to use {} inline, this probably isn't what you wanted.\n", + field_type->get_name()); + throw std::runtime_error("bad get size in type"); + } assert(field_type->is_reference()); return field.array_size() * align(field_type->get_size_in_memory(), field_type->get_inline_array_alignment()); @@ -766,8 +835,16 @@ int TypeSystem::get_size_in_type(const Field& field) { } else { // not an array if (field.is_inline()) { + if (!allow_inline(field_type)) { + fmt::print( + "[Type System] Attempted to use {} inline, this probably isn't what you wanted.\n", + field_type->get_name()); + throw std::runtime_error("bad get size in type"); + } assert(field_type->is_reference()); - return align(field_type->get_size_in_memory(), field_type->get_in_memory_alignment()); + // return align(field_type->get_size_in_memory(), field_type->get_in_memory_alignment()); + // looking at dead-pool-heap we tightly pack in this case + return field_type->get_size_in_memory(); } else { if (field_type->is_reference()) { return POINTER_SIZE; @@ -888,16 +965,16 @@ bool TypeSystem::typecheck_base_types(const std::string& expected, // declared, but not defined?) lookup_type(expected); - if (expected == actual) { - lookup_type(actual); // make sure it exists + if (expected == actual || expected == lookup_type_allow_partial_def(actual)->get_name()) { + lookup_type_allow_partial_def(actual); // make sure it exists return true; } std::string actual_name = actual; - auto actual_type = lookup_type(actual_name); + auto actual_type = lookup_type_allow_partial_def(actual_name); while (actual_type->has_parent()) { actual_name = actual_type->get_parent(); - actual_type = lookup_type(actual_name); + actual_type = lookup_type_allow_partial_def(actual_name); if (expected == actual_name) { return true; @@ -991,12 +1068,12 @@ TypeSpec TypeSystem::lowest_common_ancestor(const std::vector& types) TypeSpec coerce_to_reg_type(const TypeSpec& in) { if (in.arg_count() == 0) { if (in.base_type() == "int8" || in.base_type() == "int16" || in.base_type() == "int32" || - in.base_type() == "int16") { + in.base_type() == "int64") { return TypeSpec("int"); } if (in.base_type() == "uint8" || in.base_type() == "uint16" || in.base_type() == "uint32" || - in.base_type() == "uint16") { + in.base_type() == "uint64") { return TypeSpec("uint"); } } diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index c62074cb52..8d31139477 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -34,12 +34,15 @@ class TypeSystem { TypeSystem(); Type* add_type(const std::string& name, std::unique_ptr type); - void forward_declare_type(std::string name); + void forward_declare_type(const std::string& name); + void forward_declare_type_as_basic(const std::string& name); + void forward_declare_type_as_structure(const std::string& name); std::string get_runtime_type(const TypeSpec& ts); DerefInfo get_deref_info(const TypeSpec& ts); bool fully_defined_type_exists(const std::string& name) const; + bool partially_defined_type_exists(const std::string& name) const; TypeSpec make_typespec(const std::string& name) const; TypeSpec make_function_typespec(const std::vector& arg_types, const std::string& return_type); @@ -52,6 +55,9 @@ class TypeSystem { Type* lookup_type(const TypeSpec& ts) const; Type* lookup_type(const std::string& name) const; + Type* lookup_type_allow_partial_def(const TypeSpec& ts) const; + Type* lookup_type_allow_partial_def(const std::string& name) const; + MethodInfo add_method(const std::string& type_name, const std::string& method_name, const TypeSpec& ts, @@ -105,7 +111,7 @@ class TypeSystem { private: std::string lca_base(const std::string& a, const std::string& b); bool typecheck_base_types(const std::string& expected, const std::string& actual) const; - int get_size_in_type(const Field& field); + int get_size_in_type(const Field& field) const; int get_alignment_in_type(const Field& field); Field lookup_field(const std::string& type_name, const std::string& field_name); StructureType* add_builtin_structure(const std::string& parent, @@ -120,8 +126,10 @@ class TypeSystem { RegKind reg = RegKind::GPR_64); void builtin_structure_inherit(StructureType* st); + enum ForwardDeclareKind { TYPE, STRUCTURE, BASIC }; + std::unordered_map> m_types; - std::unordered_set m_forward_declared_types; + std::unordered_map m_forward_declared_types; std::vector> m_old_types; bool m_allow_redefinition = false; diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp index 691865ece0..1487640080 100644 --- a/common/type_system/deftype.cpp +++ b/common/type_system/deftype.cpp @@ -178,11 +178,17 @@ void declare_method(StructureType* type, TypeSystem* type_system, const goos::Ob }); } -TypeFlags parse_structure_def(StructureType* type, - TypeSystem* ts, - const goos::Object& fields, - const goos::Object& options) { - (void)options; +struct StructureDefResult { + TypeFlags flags; + bool generate_runtime_type = true; + bool pack_me = false; +}; + +StructureDefResult parse_structure_def(StructureType* type, + TypeSystem* ts, + const goos::Object& fields, + const goos::Object& options) { + StructureDefResult result; for_each_in_list(fields, [&](const goos::Object& o) { add_field(type, ts, o); }); TypeFlags flags; flags.heap_base = 0; @@ -229,6 +235,10 @@ TypeFlags parse_structure_def(StructureType* type, flag_assert = get_int(car(rest)); flag_assert_set = true; rest = cdr(rest); + } else if (opt_name == ":no-runtime-type") { + result.generate_runtime_type = false; + } else if (opt_name == ":pack-me") { + result.pack_me = true; } else { throw std::runtime_error("Invalid option in field specification: " + opt_name); } @@ -255,7 +265,8 @@ TypeFlags parse_structure_def(StructureType* type, flags.flag, flag_assert)); } - return flags; + result.flags = flags; + return result; } } // namespace @@ -303,14 +314,28 @@ DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) { auto pto = dynamic_cast(ts->lookup_type(parent_type)); assert(pto); new_type->inherit(pto); - result.flags = parse_structure_def(new_type.get(), ts, field_list_obj, options_obj); + ts->forward_declare_type_as_basic(name); + auto sr = parse_structure_def(new_type.get(), ts, field_list_obj, options_obj); + result.flags = sr.flags; + result.create_runtime_type = sr.generate_runtime_type; + if (sr.pack_me) { + fmt::print("[TypeSystem] :pack-me was set on {}, which is a basic and cannot be packed.", + name); + throw std::runtime_error("invalid pack option on basic"); + } ts->add_type(name, std::move(new_type)); } else if (is_type("structure", parent_type, ts)) { auto new_type = std::make_unique(parent_type_name, name); auto pto = dynamic_cast(ts->lookup_type(parent_type)); assert(pto); new_type->inherit(pto); - result.flags = parse_structure_def(new_type.get(), ts, field_list_obj, options_obj); + ts->forward_declare_type_as_structure(name); + auto sr = parse_structure_def(new_type.get(), ts, field_list_obj, options_obj); + result.flags = sr.flags; + result.create_runtime_type = sr.generate_runtime_type; + if (sr.pack_me) { + new_type->set_pack(true); + } ts->add_type(name, std::move(new_type)); } else if (is_type("integer", parent_type, ts)) { throw std::runtime_error("Creating a child type of integer is not supported yet."); diff --git a/common/type_system/deftype.h b/common/type_system/deftype.h index 151601be37..2021726497 100644 --- a/common/type_system/deftype.h +++ b/common/type_system/deftype.h @@ -19,6 +19,7 @@ struct DeftypeResult { TypeFlags flags; TypeSpec type; Type* type_info = nullptr; + bool create_runtime_type = true; }; DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts); diff --git a/doc/changelog.md b/doc/changelog.md index c63901eac8..8f9b961c3e 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -21,4 +21,17 @@ - The `&->` operator has been added - The `new` operator can create arrays and inline arrays on heaps - The value of `deftype` is now `none` -- Creating a method with more than 8 arguments is an error instead of a crash. \ No newline at end of file +- Creating a method with more than 8 arguments is an error instead of a crash. +- The `defconstant` form for defining a constant in GOAL but not GOOS has been added +- Both `defconstant` and `defglobalconstant` throw an error if you define a constant with the same name as a symbol. +- The `uint64` type now uses 8 bytes instead of 81 in a type (this was a typo) +- `deftype` allows basics/structures with a field that is the same type as the basic/structure. +- Doing a `define-extern` with a type of `type` will forward declare the type. +- `deftype` now has a `:no-runtime-type` flag to disable the creation of a runtime type. +- There is a `declare-type` form for forward declaring types to allow circular dependencies. +- Types that are `structure` but not `basic` can request that they be tightly packed when possible with `:pack-me`. +- Using `method` on a forward declared type is an error. The old behavior was to get a method of `type`, which is confusing. +- Loading an `int64`/`uint64` gives a `int`/`uint`, like the other register integers. +- Defining a type with `deftype` will auto-generate an inspect method. +- The `new` operator can now create static structures and basics and set fields to integers or symbols. +- The `neq?` operator now works when used outside of a branch condition (previously it generated a syntax error) diff --git a/goal_src/kernel/gkernel-h.gc b/goal_src/kernel/gkernel-h.gc index 65050e91ea..be0cdfea14 100644 --- a/goal_src/kernel/gkernel-h.gc +++ b/goal_src/kernel/gkernel-h.gc @@ -5,5 +5,312 @@ ;; name in dgo: gkernel-h ;; dgos: KERNEL -(defglobalconstant *kernel-major-version* 2) -(defglobalconstant *kernel-minor-version* 0) \ No newline at end of file +;; Type definitions for the GOAL Kernel. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; CONSTANTS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; -hardware- + +;; the end of the 16 kB fast "scratchpad" memory of the PS2. +;; this memory is mapped to 0x70000000 in the PS2. +(defconstant *scratch-memory-top* (the pointer #x70004000)) + +;; -versions- + +;; the version of the kernel. This is checked in the C Kernel. +;; This must match the version in common/versions.h when building gk +(defconstant *kernel-major-version* 2) +(defconstant *kernel-minor-version* 0) + +;; the version of the OVERLORD I/O driver. +;; this may be unused. +(defconstant *irx-major-version* 1) +(defconstant *irx-minor-version* 2) + +;; -memory- + +;; the size of the execution stack (~14 kB) shared by all threads +(defconstant DPROCESS_STACK_SIZE #x3800) + +;; another stack size used as a maximum for temporary threads +(defconstant PROCESS_STACK_SIZE #x1c00) + +;; default size of stack to backup for a process +(defconstant PROCESS_STACK_SAVE_SIZE 256) + +;; the size of the shared heap used by dynamically sized processes +(defconstant PROCESS_HEAP_SIZE (* 984 1024)) + +;; -system- + +;; tab size for printing. +(defconstant *tab-size* (the binteger 8)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ENUMS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; todo, processs mask + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; MACROS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; trigger an exception. (GOAL used lw r0, 2(r0)) +(defmacro break () + `(/ 0 0) + ) + +;; todo, process check and set + +(defmacro msg-err (&rest args) + ;"Print a message to stdout immediately. This won't appear in the compiler." + `(format 0 ,@args) + ) + +;; todo process pointer +;; todo process memory usage +;; with pp +;; todo suspend + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TYPES +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; this stores the current state of the kernel. +(deftype kernel-context (basic) + ((prevent-from-run int32 :offset-assert 4) ;; actually a process-mask + (require-for-run int32 :offset-assert 8) ;; actually a process-mask, unused? + (allow-to-run int32 :offset-assert 12) ;; actually a process-mask, unused? + (next-pid int32 :offset-assert 16) ;; next PID to give out + (fast-stack-top pointer :offset-assert 20) ;; scratchpad stack (unused?) + (current-process basic :offset-assert 24) ;; process? + (relocating-process basic :offset-assert 28) ;; process? + (relocating-min int32 :offset-assert 32) ;; print hex + (relocating-max int32 :offset-assert 36) ;; print hex + (relocating-offset int32 :offset-assert 40) ;; ? + (low-memory-message basic :offset-assert 44) ;; boolean? + ) + + :size-assert #x30 + :method-count-assert 9 + :flag-assert #x900000030 + ) + +; A thread belongs to a process and has a reference to a stack. +; they have an "execution stack", which is where the stack goes when the thread runs. +; and also a "backup stack", which stores the stack when the thread doesn't run. +; this means threads can't leak pointers to stack variables to other threads... +; optionally, threads may know how to suspend/resume themselves. + +(declare-type process basic) + +; DANGER - this type is created in kscheme.cpp. It has room for 12 methods and size 0x28 bytes. +(deftype thread (basic) + ((name basic :offset-assert 4) ;; name of the thread (usually a symbol?) + (process process :offset-assert 8) ;; process that the thread belongs to + (previous thread :offset-assert 12) ;; previous thread that was running in the process + (suspend-hook basic :offset-assert 16) ;; function to suspend this thread + (resume-hook basic :offset-assert 20) ;; function to resume this thread + (pc pointer :offset-assert 24) ;; program counter of the thread + (sp pointer :offset-assert 28) ;; stack pointer of the thread (actual stack) + (stack-top pointer :offset-assert 32) ;; top of the thread's stack (actual stack) + (stack-size int32 :offset-assert 36) ;; size of the thread's stack (backup stack) + ) + + (:methods + ;; todo, triple check these method numbers. + (stack-size-set! ((this thread) (stack-size integer)) none 9) + (thread-suspend ((this _type_)) none 10) ;; only safe on a cpu-thread, but slot exists for thread + (thread-resume ((to-resume _type_)) none 11) ;; only safe on a cpu-thread, but slot exists for thread + ) + + :size-assert #x28 + :method-count-assert 12 + :flag-assert #xc00000028 + ;; is already defined in kscheme but we define it again. + ) + +;; A CPU thread is a thread which has some memory to save registers and a stack +(deftype cpu-thread (thread) + ((rreg uint64 8 :offset-assert 40) ;; general purpose saved registers + (freg float 6 :offset-assert 104) ;; floating point registers + (stack uint8 :dynamic :offset-assert 128) ;; stack memory (dynamic array) + ) + + (:methods + (thread-suspend ((this _type_)) none 10) + (thread-resume ((to-resume _type_)) none 11) + ) + + :size-assert #x80 + :method-count-assert 12 + :flag-assert #xc00000080 + ) + +;; Parent type of all process tree nodes. +;; A process-tree is a left-child right-sibling binary tree +;; (except GOAL is old and it looks like they called them left-child right-brother trees back then) +(deftype process-tree (basic) + ((name basic :offset-assert 4) + (mask int32 :offset-assert 8) + (parent pointer :offset-assert 12) + (brother pointer :offset-assert 16) + (child pointer :offset-assert 20) + (ppointer pointer :offset-assert 24) + (self basic :offset-assert 28) + ) + + (:methods + (activate ((obj process-tree) (dest process-tree) (name basic) (stack-top pointer)) basic 9) + (deactivate ((obj process-tree)) basic 10) + (dummy-method-11 () none 11) + (run-logic? ((obj process-tree)) symbol 12) + (dummy-method () none 13) + ) + + :size-assert #x20 + :method-count-assert 14 + :no-runtime-type ;; already defined by kscheme. Don't do it again. + ) + +;; A dead pool is simply a process-tree node which contains all dead processes. +;; It supports getting and returning processes. +(deftype dead-pool (process-tree) + ( + ;; nothing new! + ) + (:methods + (get-process ((pool dead-pool) (type-to-make type) (stack-size integer)) process 14) + (return-process ((pool dead-pool) (proc process)) process-tree 15) + ) + :size-assert #x20 + :method-count-assert 16 + :flag-assert #x1000000020 + ) + +;; A dead-pool-heap-rec is a record for a process which lives on a dead-pool-heap. +;; these processes can move around in memory, but the records can't. +;; Therefore a pointer to these can be used as a handle for the process, so you can +;; find it after it moves +(deftype dead-pool-heap-rec (structure) + ((process process :offset-assert 0) ;; the process of this record + (prev dead-pool-heap-rec :offset-assert 4) ;; next rec in the linked list + (next dead-pool-heap-rec :offset-assert 8) ;; prev. rec in the linked list + ) + + :pack-me ; don't worry about aligning me to 16-bytes in arrays and types. + :size-assert #xc + :method-count-assert 9 + :flag-assert #x90000000c + ) + +;; This is a pool of dead processes which can be dynamically sized and allocated from a common heap. +;; Alive processess in a dead-pool-heap can be relocated and compacted to reduce heap fragmentation. +(deftype dead-pool-heap (dead-pool) + ((allocated-length int32 :offset-assert #x20) ;; size of heap + (compact-time uint32 :offset-assert #x24) ;; ?? + (compact-count-targ uint32 :offset-assert #x28) ;; ?? + (compact-count uint32 :offset-assert #x2c) ;; ?? + (fill-percent float :offset-assert #x30) ;; ?? + (first-gap dead-pool-heap-rec :offset-assert #x34) ;; ?? + (first-shrink dead-pool-heap-rec :offset-assert #x38) ;; ?? + (heap kheap :inline :offset-assert 64) ;; ?? + (alive-list dead-pool-heap-rec :inline :offset-assert 80) ;; ?? + (last dead-pool-heap-rec :offset #x54 :offset-assert #x54) ;; overlay of (-> alive-list prev) + ;; note - the placement of dead-list at 92 here is used to determine the packing behavior. + ;; see TypeSystem::get_size_in_type(). + (dead-list dead-pool-heap-rec :inline :offset-assert 92) ;; ?? + (process-list dead-pool-heap-rec :inline :dynamic :offset-assert 104) + ) + (:methods + (compact ((this dead-pool-heap) (count integer)) none 16) + (shrink-heap ((this dead-pool-heap) (proc process)) dead-pool-heap 17) + (churn ((this dead-pool-heap) (count integer)) none 18) + (memory-used ((this dead-pool-heap)) integer 19) + (memory-total ((this dead-pool-heap)) integer 20) + (gap-size ((this dead-pool-heap) (rec dead-pool-heap-rec)) integer 21) + (gap-location ((this dead-pool-heap) (rec dead-pool-heap-rec)) pointer 22) + (find-gap ((this dead-pool-heap) (rec dead-pool-heap-rec)) dead-pool-heap-rec 23) + (find-gap-by-size ((this dead-pool-heap) (size integer)) dead-pool-heap-rec 24) + (memory-free ((this dead-pool-heap)) integer 25) + (compact-time ((this dead-pool-heap)) integer 26) + ) + + :size-assert #x68 + :method-count-assert #x1b + :flag-assert #x1b00000068 + ) + + +;; GOAL can create a series of stack frames for unwinding/cleaning up. +;; This is the parent type for any stack frame. +(deftype stack-frame (basic) + ((name basic :offset 4) + (next stack-frame :offset 8) ;; which way does this point? + ) + + :size-assert #xc + :method-count-assert 9 + :flag-assert #x90000000c + ) + + +;; A catch frame is a frame you can "throw" to, by name. +;; You can "throw" out of a function and into another function. +(deftype catch-frame (stack-frame) + ((sp pointer :offset 12) ;; where to reset the stack when throwing. + (ra pointer :offset 16) ;; where to jump when throwing + + ;; todo - rework for x86-64. + (freg float 6 :offset 20) ;; saved floating point registers from "catch" statement + (rreg uint128 8 :offset 48) ;; saved GPRs from "catch" statement (ugh they are 128s) + ) + :size-assert #xb0 + :method-count-assert 9 + :flag-assert #x9000000b0 + ) + +;; A protect frame is a frame which has a cleanup function called on exit. +(deftype protect-frame (stack-frame) + ((exit function :offset-assert 12)) ;; function to call to clean up + + :size-assert 16 + :method-count-assert 9 + :flag-assert #x900000010 + ) + +;; handle (todo, need bitfield types) + +;; GOAL State. A Process can be in a State. +(deftype state (protect-frame) ; state is a protect frame so we can "exit" it with cleanup + ((code function :offset-assert 16) ;; main code to run in this state + (trans function :offset-assert 20) ;; ? run once and return ? when ? + (post function :offset-assert 24) ;; ? + (enter function :offset-assert 28) ;; ? + (event basic :offset-assert 32) ;; event handler function? + ) + + :size-assert #x24 + :method-count-assert 9 + :flag-assert #x900000024 + ) + + +;; Not sure what this is, but it's probably used in the event system. +(deftype event-message-block (structure) + ((to basic :offset-assert 0) + (from basic :offset-assert 4) + (num-params int32 :offset-assert 8) + (message basic :offset-assert 12) + (param uint64 7 :offset-assert 16) + ) + + :size-assert #x48 + :method-count-assert 9 + :flag-assert #x900000048 + ) \ No newline at end of file diff --git a/goal_src/kernel/gkernel.gc b/goal_src/kernel/gkernel.gc index f3aa580851..9ecf1d35e0 100644 --- a/goal_src/kernel/gkernel.gc +++ b/goal_src/kernel/gkernel.gc @@ -5,7 +5,98 @@ ;; name in dgo: gkernel ;; dgos: KERNEL + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; System Globals +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Set version number symbols (define *kernel-version* (the binteger (logior (ash *kernel-major-version* 16) *kernel-minor-version*))) +(define *irx-version* (the binteger (logior (ash *irx-major-version* 16) *irx-minor-version*))) + +;; Set default options. The C Kernel may modify these before loading the engine. + +;; Can be 'boot, 'listener, or 'debug-boot +;; set to 'boot when DiskBooting. +(define *kernel-boot-mode* 'listener) + +;; DebugBootLevel in C Kernel +(define *kernel-boot-level* (the symbol #f)) + +;; The number of DECI messages received. +;; The C Kernel increments this. +(define *deci-count* 0) + +;; Some debug stats. Unused? +(define *last-loado-length* 0) +(define *last-loado-global-usage* 0) +(define *last-loado-debug-usage* 0) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Relocate +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Objects on a dynamic process heap may be relocated. +;; They should provide their own relocate method to do any fixups +;; for any references. +(defmethod relocate object ((this object) (offset int)) + this + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Kernel Package System +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; The kernel has a weird package system. It's not really used and doesn't do much. +;; Both the C Kernel and GOAL Kernel update the kernel-packages list. +;; The list is used to avoid loading the same package multiple times. + +(define *kernel-packages* '()) + +(defun load-package ((package string) (allocation kheap)) + "Load a Package from a CGO/DGO" + (unless (nmember package *kernel-packages*) + ;; #xf = OUTPUT_LOAD, OUTPUT_TRUE, EXECUTE, PRINT_LOGIN + (dgo-load package allocation #xf #x200000) + (set! *kernel-packages* (cons package *kernel-packages*)) + ) + ) + +(defun unload-package ((package string)) + "Mark a package as unloaded, if it was loaded previously" + (let ((pack (nmember package *kernel-packages*))) + (when pack + (set! *kernel-packages* (delete! (car pack) *kernel-packages*)) + ) + *kernel-packages* + ) + ) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Kernel Globals +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; The kernel context is a global which stores the state of the kernel. +(define *kernel-context* (new 'static 'kernel-context + :prevent-from-run #x41 ;; todo, bitfield enum types + :next-pid 2 + :current-process '#f + :relocating-process '#f + :low-memory-message '#t + ) + ) + +;; the main stack for running GOAL code! +(define *dram-stack* (new 'global 'array 'uint8 #x3800)) ;;DPROCESS_STACK_SIZE + +;; I don't think this stack is used, but I'm not sure. +(set! (-> *kernel-context* fast-stack-top) *scratch-memory-top*) + +;; A context with all process masks set to 0. This can be used to iterate through a process tree +;; without executing anything, to find a process for instance. +(define *null-kernel-context* (new 'static 'kernel-context)) + (defun kernel-dispatcher () "Kernel Dispatcher Function. This gets called from the main loop in kboot.cpp's KernelCheckAndDispatch" @@ -24,4 +115,4 @@ ;; clear the pending function. (set! *listener-function* (the (function object) #f)) ) - ) \ No newline at end of file + ) diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index e21bec115e..c3157b9fbb 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(compiler compiler/compilation/Function.cpp compiler/compilation/ControlFlow.cpp compiler/compilation/Type.cpp + compiler/compilation/Static.cpp compiler/Util.cpp logger/Logger.cpp regalloc/IRegister.cpp diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 0f6c4164f4..d93791f67f 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -72,7 +72,9 @@ class Compiler { std::string as_string(const goos::Object& o); std::string symbol_string(const goos::Object& o); std::string quoted_sym_as_string(const goos::Object& o); + bool is_quoted_sym(const goos::Object& o); bool is_basic(const TypeSpec& ts); + bool is_structure(const TypeSpec& ts); const goos::Object& pair_car(const goos::Object& o); const goos::Object& pair_cdr(const goos::Object& o); void expect_empty_list(const goos::Object& o); @@ -118,20 +120,29 @@ class Compiler { Val* compile_format_string(const goos::Object& form, Env* env, - std::string& fmt_template, + const std::string& fmt_template, std::vector args, const std::string& out_stream = "#t"); - void generate_field_description(StructureType* type, + void generate_field_description(const goos::Object& form, + StructureType* type, Env* env, RegVal* reg, - Field f, - std::vector format_args, - std::string& str_template); + const Field& f); Val* generate_inspector_for_type(const goos::Object& form, Env* env, Type* type); RegVal* compile_get_method_of_type(const TypeSpec& type, const std::string& method_name, Env* env); RegVal* compile_get_method_of_object(RegVal* object, const std::string& method_name, Env* env); + Val* compile_define_constant(const goos::Object& form, + const goos::Object& rest, + Env* env, + bool goos, + bool goal); + + Val* compile_new_static_structure_or_basic(const goos::Object& form, + const TypeSpec& type, + const goos::Object& field_defs, + Env* env); public: // Atoms @@ -171,6 +182,7 @@ class Compiler { Val* compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_quote(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_defglobalconstant(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_defconstant(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_mlet(const goos::Object& form, const goos::Object& rest, Env* env); // Math @@ -205,6 +217,7 @@ class Compiler { Val* compile_cdr(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_method(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_addr_of(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_declare_type(const goos::Object& form, const goos::Object& rest, Env* env); }; #endif // JAK_COMPILER_H diff --git a/goalc/compiler/StaticObject.cpp b/goalc/compiler/StaticObject.cpp index cdd6be0641..828403876b 100644 --- a/goalc/compiler/StaticObject.cpp +++ b/goalc/compiler/StaticObject.cpp @@ -80,4 +80,61 @@ int StaticFloat::get_addr_offset() const { std::string StaticFloat::print() const { return fmt::format("(sf {})", value); +} + +/////////////////// +// StaticStructure +/////////////////// + +StaticStructure::StaticStructure(int _seg) : seg(_seg) {} + +std::string StaticStructure::print() const { + return "static-structure"; +} + +StaticObject::LoadInfo StaticStructure::get_load_info() const { + LoadInfo info; + info.requires_load = false; + info.prefer_xmm = false; + return info; +} + +int StaticStructure::get_addr_offset() const { + return 0; +} + +void StaticStructure::generate_structure(emitter::ObjectGenerator* gen) { + rec = gen->add_static_to_seg(seg, 16); + auto& d = gen->get_static_data(rec); + d.insert(d.end(), data.begin(), data.end()); + for (auto& sym : symbols) { + gen->link_static_symbol_ptr(rec, sym.offset, sym.name); + } +} + +void StaticStructure::generate(emitter::ObjectGenerator* gen) { + generate_structure(gen); +} + +void StaticStructure::add_symbol_record(std::string name, int offset) { + SymbolRecord srec; + srec.name = std::move(name); + srec.offset = offset; + symbols.push_back(srec); +} + +/////////////////// +// StaticBasic +/////////////////// + +StaticBasic::StaticBasic(int _seg, std::string _type_name) + : StaticStructure(_seg), type_name(std::move(_type_name)) {} + +int StaticBasic::get_addr_offset() const { + return BASIC_OFFSET; +} + +void StaticBasic::generate(emitter::ObjectGenerator* gen) { + generate_structure(gen); + gen->link_static_type_ptr(rec, 0, type_name); } \ No newline at end of file diff --git a/goalc/compiler/StaticObject.h b/goalc/compiler/StaticObject.h index 6e768ab1c6..0c55a97663 100644 --- a/goalc/compiler/StaticObject.h +++ b/goalc/compiler/StaticObject.h @@ -4,6 +4,7 @@ #define JAK_STATICOBJECT_H #include +#include #include "goalc/emitter/ObjectGenerator.h" class StaticObject { @@ -46,4 +47,32 @@ class StaticFloat : public StaticObject { int get_addr_offset() const override; }; +class StaticStructure : public StaticObject { + public: + StaticStructure(int _seg); + std::vector data; + int seg = -1; + std::string print() const override; + LoadInfo get_load_info() const override; + void generate_structure(emitter::ObjectGenerator* gen); + void generate(emitter::ObjectGenerator* gen) override; + int get_addr_offset() const override; + + struct SymbolRecord { + int offset = -1; + std::string name; + }; + std::vector symbols; + + void add_symbol_record(std::string name, int offset); +}; + +class StaticBasic : public StaticStructure { + public: + std::string type_name; + StaticBasic(int _seg, std::string _type_name); + int get_addr_offset() const override; + void generate(emitter::ObjectGenerator* gen) override; +}; + #endif // JAK_STATICOBJECT_H diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index d2b4cf070d..b3a805be69 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -113,6 +113,24 @@ std::string Compiler::quoted_sym_as_string(const goos::Object& o) { return symbol_string(args.unnamed.at(1)); } +bool Compiler::is_quoted_sym(const goos::Object& o) { + if (o.is_pair()) { + auto car = pair_car(o); + auto cdr = pair_cdr(o); + if (car.is_symbol() && car.as_symbol()->name == "quote") { + if (cdr.is_pair()) { + auto thing = pair_car(cdr); + if (thing.is_symbol()) { + if (pair_cdr(cdr).is_empty_list()) { + return true; + } + } + } + } + } + return false; +} + const goos::Object& Compiler::pair_car(const goos::Object& o) { return o.as_pair()->car; } @@ -186,6 +204,10 @@ bool Compiler::is_basic(const TypeSpec& ts) { return m_ts.typecheck(m_ts.make_typespec("basic"), ts, "", false, false); } +bool Compiler::is_structure(const TypeSpec& ts) { + return m_ts.typecheck(m_ts.make_typespec("structure"), ts, "", false, false); +} + bool Compiler::try_getting_constant_integer(const goos::Object& in, int64_t* out, Env* env) { (void)env; if (in.is_int()) { @@ -193,6 +215,6 @@ bool Compiler::try_getting_constant_integer(const goos::Object& in, int64_t* out return true; } - // todo, try more things before giving up. + // todo, try more things like constants before giving up. return false; } \ No newline at end of file diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index b47d0c12d9..146c5b01a4 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -65,6 +65,7 @@ static const std::unordered_map< {"car", &Compiler::compile_car}, {"cdr", &Compiler::compile_cdr}, {"method", &Compiler::compile_method}, + {"declare-type", &Compiler::compile_declare_type}, // LAMBDA {"lambda", &Compiler::compile_lambda}, @@ -77,7 +78,7 @@ static const std::unordered_map< // MACRO {"quote", &Compiler::compile_quote}, {"mlet", &Compiler::compile_mlet}, - // {"defconstant", &Compiler::compile_defconstant}, + {"defconstant", &Compiler::compile_defconstant}, // OBJECT // {"current-method-type", &Compiler::compile_current_method_type}, @@ -101,6 +102,7 @@ static const std::unordered_map< {"=", &Compiler::compile_condition_as_bool}, {"!=", &Compiler::compile_condition_as_bool}, {"eq?", &Compiler::compile_condition_as_bool}, + {"neq?", &Compiler::compile_condition_as_bool}, {"not", &Compiler::compile_condition_as_bool}, {"<=", &Compiler::compile_condition_as_bool}, {">=", &Compiler::compile_condition_as_bool}, diff --git a/goalc/compiler/compilation/Define.cpp b/goalc/compiler/compilation/Define.cpp index 72b4888b30..dd83c3c50c 100644 --- a/goalc/compiler/compilation/Define.cpp +++ b/goalc/compiler/compilation/Define.cpp @@ -75,6 +75,11 @@ Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Objec symbol_string(sym).c_str(), existing_type->second.print().c_str(), new_type.print().c_str()); } + + if (new_type == m_ts.make_typespec("type")) { + m_ts.forward_declare_type(symbol_string(sym)); + } + m_symbol_types[symbol_string(sym)] = new_type; return get_none(); } diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index 480ad161c1..19be2ee0c2 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -444,6 +444,10 @@ Val* Compiler::compile_real_function_call(const goos::Object& form, } } + if (args.size() > 8) { + throw_compile_error(form, "Function call cannot use more than 8 parameters"); + } + // set args (introducing a move here makes coloring more likely to be possible) std::vector arg_outs; for (auto& arg : args) { diff --git a/goalc/compiler/compilation/Macro.cpp b/goalc/compiler/compilation/Macro.cpp index 9cbc91b24e..aaeb4220a4 100644 --- a/goalc/compiler/compilation/Macro.cpp +++ b/goalc/compiler/compilation/Macro.cpp @@ -112,16 +112,15 @@ Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest, return get_none(); } -/*! - * Compile defglobalconstant forms, which define a constant in both GOOS and GOAL. - */ -Val* Compiler::compile_defglobalconstant(const goos::Object& form, - const goos::Object& _rest, - Env* env) { +Val* Compiler::compile_define_constant(const goos::Object& form, + const goos::Object& _rest, + Env* env, + bool goos, + bool goal) { auto rest = &_rest; (void)env; if (!rest->is_pair()) { - throw_compile_error(form, "invalid defglobalconstant"); + throw_compile_error(form, "invalid constant definition"); } auto sym = pair_car(*rest).as_symbol(); @@ -130,18 +129,43 @@ Val* Compiler::compile_defglobalconstant(const goos::Object& form, rest = &rest->as_pair()->cdr; if (!rest->is_empty_list()) { - throw_compile_error(form, "invalid defglobalconstant"); + throw_compile_error(form, "invalid constant definition"); } // GOAL constant - m_global_constants[sym] = value; + if (goal) { + if (m_symbol_types.find(sym->name) != m_symbol_types.end()) { + throw_compile_error(form, fmt::format("The name {} cannot be defined as a constant because " + "it is already the name of a symbol of type {}", + sym->name, m_symbol_types.at(sym->name).print())); + } + m_global_constants[sym] = value; + } // GOOS constant - m_goos.global_environment.as_env()->vars[sym] = value; + if (goos) { + m_goos.global_environment.as_env()->vars[sym] = value; + } return get_none(); } +/*! + * Compile defglobalconstant forms, which define a constant in both GOOS and GOAL. + */ +Val* Compiler::compile_defglobalconstant(const goos::Object& form, + const goos::Object& rest, + Env* env) { + return compile_define_constant(form, rest, env, true, true); +} + +/*! + * Compile a defconstant form, which defines a constant that is only in GOAL. + */ +Val* Compiler::compile_defconstant(const goos::Object& form, const goos::Object& rest, Env* env) { + return compile_define_constant(form, rest, env, false, true); +} + /*! * Compile an "mlet" scoped constant/symbol macro form */ diff --git a/goalc/compiler/compilation/Static.cpp b/goalc/compiler/compilation/Static.cpp new file mode 100644 index 0000000000..e1eead7400 --- /dev/null +++ b/goalc/compiler/compilation/Static.cpp @@ -0,0 +1,132 @@ +/*! + * @file Static.cpp + * Compiler helper functions for creating static data. + * This is the front end for things in StaticObject.cpp + */ + +#include "goalc/compiler/Compiler.h" + +namespace { +bool integer_fits(s64 in, int size, bool is_signed) { + switch (size) { + case 1: + if (is_signed) { + return in >= INT8_MIN && in <= INT8_MAX; + } else { + return in >= 0 && in <= UINT8_MAX; + } + case 2: + if (is_signed) { + return in >= INT16_MIN && in <= INT16_MAX; + } else { + return in >= 0 && in <= UINT16_MAX; + } + case 4: + if (is_signed) { + return in >= INT32_MIN && in <= INT32_MAX; + } else { + return in >= 0 && in <= UINT32_MAX; + } + case 8: + return true; + default: + assert(false); + } +} +} // namespace + +Val* Compiler::compile_new_static_structure_or_basic(const goos::Object& form, + const TypeSpec& type, + const goos::Object& _field_defs, + Env* env) { + auto fe = get_parent_env_of_type(env); + std::unique_ptr obj; + if (is_basic(type)) { + obj = std::make_unique(MAIN_SEGMENT, type.base_type()); + } else { + // if we ever find this type of static data outside of MAIN_SEGMENT, we can create an option + // in the new form to pick the segment. + obj = std::make_unique(MAIN_SEGMENT); + } + + auto type_info = dynamic_cast(m_ts.lookup_type(type)); + assert(type_info); // should always be at least a structure. + obj->data.resize(type_info->get_size_in_memory()); + // the file env will end up owning the obj. + auto result = fe->alloc_val(obj.get(), type); + + auto* field_defs = &_field_defs; + while (!field_defs->is_empty_list()) { + auto field_name_def = symbol_string(pair_car(*field_defs)); + field_defs = &pair_cdr(*field_defs); + + auto field_value = pair_car(*field_defs); + field_defs = &pair_cdr(*field_defs); + + if (field_name_def.at(0) != ':') { + throw_compile_error(form, + "expected field def name to start with :, instead got " + field_name_def); + } + + field_name_def = field_name_def.substr(1); + auto field_info = m_ts.lookup_field_info(type_info->get_name(), field_name_def); + + if (field_info.field.is_dynamic() || field_info.field.is_inline() || + field_info.field.is_array()) { + throw_compile_error(form, "Static objects not yet implemented for dynamic/inline/array"); + } + + auto field_offset = field_info.field.offset(); + assert(field_info.needs_deref); // for now... + auto deref_info = m_ts.get_deref_info(m_ts.make_pointer_typespec(field_info.type)); + auto field_size = deref_info.load_size; + assert(field_offset + field_size <= int(obj->data.size())); + + if (is_integer(field_info.type)) { + s64 value = 0; + if (!try_getting_constant_integer(field_value, &value, env)) { + throw_compile_error(form, + fmt::format("Field {} is an integer, but the value given couldn't be " + "converted to an integer at compile time.", + field_name_def)); + } + + if (!integer_fits(value, deref_info.load_size, deref_info.sign_extend)) { + throw_compile_error( + form, fmt::format("Field {} is set to a compile time integer value of {} which would " + "overflow (size {} signed {})", + field_name_def, value, deref_info.load_size, deref_info.sign_extend)); + } + + if (field_size == 1 || field_size == 2 || field_size == 4 || field_size == 8) { + memcpy(obj->data.data() + field_offset, &value, field_size); + } else { + // not sure how we can create 128-bit integer constants at this point... + assert(false); + } + } else if (is_basic(field_info.type)) { + if (is_quoted_sym(field_value)) { + obj->add_symbol_record(quoted_sym_as_string(field_value), field_offset); + assert(deref_info.mem_deref); + assert(deref_info.can_deref); + assert(deref_info.load_size == 4); + + // the linker needs to see a -1 in order to know to insert a symbol pointer + // instead of just the symbol table offset. + u32 linker_val = 0xffffffff; + memcpy(obj->data.data() + field_offset, &linker_val, 4); + } else { + throw_compile_error( + form, "Setting a basic field to anything other than a symbol is currently unsupported"); + } + } + + else { + assert(false); // for now + } + } + + auto fie = get_parent_env_of_type(env); + fie->add_static(std::move(obj)); + return result; +} \ No newline at end of file diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index 6efb13c252..37c944b830 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -87,7 +87,7 @@ RegVal* Compiler::compile_get_method_of_object(RegVal* object, Val* Compiler::compile_format_string(const goos::Object& form, Env* env, - std::string& fmt_template, + const std::string& fmt_template, std::vector args, const std::string& out_stream) { // Add first two format args @@ -101,12 +101,13 @@ Val* Compiler::compile_format_string(const goos::Object& form, return compile_real_function_call(form, format_function, args, env); } -void Compiler::generate_field_description(StructureType* type, +void Compiler::generate_field_description(const goos::Object& form, + StructureType* type, Env* env, RegVal* reg, - Field f, - std::vector format_args, - std::string& str_template) { + const Field& f) { + std::string str_template; + std::vector format_args = {}; if (m_ts.typecheck(m_ts.make_typespec("type"), f.type(), "", false, false)) { // type return; @@ -114,36 +115,38 @@ void Compiler::generate_field_description(StructureType* type, m_ts.typecheck(m_ts.make_typespec("binteger"), f.type(), "", false, false) || m_ts.typecheck(m_ts.make_typespec("pair"), f.type(), "", false, false)) { // basic, binteger, pair - str_template += fmt::format("~T- {}: ~T~A~%", f.name()); + str_template += fmt::format("~T{}: ~A~%", f.name()); format_args.push_back(get_field_of_structure(type, reg, f.name(), env)->to_gpr(env)); } else if (m_ts.typecheck(m_ts.make_typespec("integer"), f.type(), "", false, false)) { // Integer - str_template += fmt::format("~T- {}: ~T~D~%", f.name()); + str_template += fmt::format("~T{}: ~D~%", f.name()); format_args.push_back(get_field_of_structure(type, reg, f.name(), env)->to_gpr(env)); } else if (m_ts.typecheck(m_ts.make_typespec("float"), f.type(), "", false, false)) { // Float - str_template += fmt::format("~T- {}: ~T~f~%", f.name()); + str_template += fmt::format("~T{}: ~f~%", f.name()); format_args.push_back(get_field_of_structure(type, reg, f.name(), env)->to_gpr(env)); } else if (m_ts.typecheck(m_ts.make_typespec("pointer"), f.type(), "", false, false)) { // Pointers - str_template += fmt::format("~T- {}: ~T#x~X~%", f.name()); + str_template += fmt::format("~T{}: #x~X~%", f.name()); format_args.push_back(get_field_of_structure(type, reg, f.name(), env)->to_gpr(env)); - } else if (f.is_array()) { + } else if (f.is_array() && !f.is_dynamic()) { // Arrays - str_template += fmt::format("~T- {}[{}]: ~T@ #x~X~%", f.name(), f.array_size()); + str_template += fmt::format("~T{}[{}] @ #x~X~%", f.name(), f.array_size()); format_args.push_back(get_field_of_structure(type, reg, f.name(), env)->to_gpr(env)); } else if (f.is_dynamic()) { // Dynamic Field - str_template += fmt::format("~T- {}[0]: ~T@ #x~X~%", f.name()); + str_template += fmt::format("~T{}[0] @ #x~X~%", f.name()); format_args.push_back(get_field_of_structure(type, reg, f.name(), env)->to_gpr(env)); } else if (f.is_dynamic()) { // Structure - str_template += fmt::format("~T- {}: ~T#<{} @ #x~X>~%", f.name(), f.type().print()); + str_template += fmt::format("~T{}: #<{} @ #x~X>~%", f.name(), f.type().print()); format_args.push_back(get_field_of_structure(type, reg, f.name(), env)->to_gpr(env)); } else { // Otherwise, we havn't implemented it! - str_template += fmt::format("~T- {}: ~TUndefined!~%", f.name()); + str_template += fmt::format("~T{}: Undefined!~%", f.name()); } + + compile_format_string(form, env, str_template, format_args); } Val* Compiler::generate_inspector_for_type(const goos::Object& form, Env* env, Type* type) { @@ -171,20 +174,20 @@ Val* Compiler::generate_inspector_for_type(const goos::Object& form, Env* env, T // Inform the compiler that `input`'s value will be written to `rdi` (first arg register) method_env->emit(std::make_unique(std::vector{input})); - std::string str_template = fmt::format("~T- type: ~T{}~%", type->get_name()); - std::vector format_args = {}; - - // Check if there are no fields - if (structured_type->fields().empty()) { - str_template += "~T- No fields!~%"; + RegVal* type_name = nullptr; + if (dynamic_cast(structured_type)) { + type_name = get_field_of_structure(structured_type, input, "type", method_env.get()) + ->to_gpr(method_env.get()); } else { - for (Field f : structured_type->fields()) { - generate_field_description(structured_type, method_env.get(), input, f, format_args, - str_template); - } + type_name = compile_get_sym_obj(structured_type->get_name(), method_env.get()) + ->to_gpr(method_env.get()); + } + compile_format_string(form, method_env.get(), "[~8x] ~A~%", {input, type_name}); + + for (const Field& f : structured_type->fields()) { + generate_field_description(form, structured_type, method_env.get(), input, f); } - compile_format_string(form, method_env.get(), str_template, format_args); method_env->emit(std::make_unique(method_env->make_gpr(input->type()), input)); // add this function to the object file @@ -223,13 +226,16 @@ Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& res // remember that this is a type m_symbol_types[result.type.base_type()] = m_ts.make_typespec("type"); - // get the new method of type object. this is new_type in kscheme.cpp - auto new_type_method = compile_get_method_of_type(m_ts.make_typespec("type"), "new", env); - // call (new 'type 'type-name parent-type flags) - auto new_type_symbol = compile_get_sym_obj(result.type.base_type(), env)->to_gpr(env); - auto parent_type = compile_get_symbol_value(result.type_info->get_parent(), env)->to_gpr(env); - auto flags_int = compile_integer(result.flags.flag, env)->to_gpr(env); - compile_real_function_call(form, new_type_method, {new_type_symbol, parent_type, flags_int}, env); + if (result.create_runtime_type) { + // get the new method of type object. this is new_type in kscheme.cpp + auto new_type_method = compile_get_method_of_type(m_ts.make_typespec("type"), "new", env); + // call (new 'type 'type-name parent-type flags) + auto new_type_symbol = compile_get_sym_obj(result.type.base_type(), env)->to_gpr(env); + auto parent_type = compile_get_symbol_value(result.type_info->get_parent(), env)->to_gpr(env); + auto flags_int = compile_integer(result.flags.flag, env)->to_gpr(env); + compile_real_function_call(form, new_type_method, {new_type_symbol, parent_type, flags_int}, + env); + } // Auto-generate (inspect) method generate_inspector_for_type(form, env, result.type_info); @@ -620,13 +626,15 @@ Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest, }); auto new_method = compile_get_method_of_type(type_of_obj, "new", env); - auto new_obj = compile_real_function_call(form, new_method, args, env); new_obj->set_type(type_of_obj); return new_obj; } } else if (allocation == "static") { - assert(false); + auto type_of_object = m_ts.make_typespec(type_as_string); + if (is_structure(type_of_object)) { + return compile_new_static_structure_or_basic(form, type_of_object, *rest, env); + } } throw_compile_error(form, "unsupported new form"); @@ -670,9 +678,31 @@ Val* Compiler::compile_method(const goos::Object& form, const goos::Object& rest if (arg.is_symbol()) { if (m_ts.fully_defined_type_exists(symbol_string(arg))) { return compile_get_method_of_type(m_ts.make_typespec(symbol_string(arg)), method_name, env); + } else if (m_ts.partially_defined_type_exists(symbol_string(arg))) { + throw_compile_error(form, + "The method form is ambiguous when used on a forward declared type."); } } auto obj = compile_error_guard(arg, env)->to_gpr(env); return compile_get_method_of_object(obj, method_name, env); } + +Val* Compiler::compile_declare_type(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + auto args = get_va(form, rest); + va_check(form, args, {goos::ObjectType::SYMBOL, goos::ObjectType::SYMBOL}, {}); + + auto kind = symbol_string(args.unnamed.at(1)); + auto type_name = symbol_string(args.unnamed.at(0)); + + if (kind == "basic") { + m_ts.forward_declare_type_as_basic(type_name); + } else if (kind == "structure") { + m_ts.forward_declare_type_as_structure(type_name); + } else { + throw_compile_error(form, "Invalid declare-type form"); + } + + return get_none(); +} diff --git a/goalc/emitter/ObjectGenerator.cpp b/goalc/emitter/ObjectGenerator.cpp index 12e66bd3b7..b8fed2da0e 100644 --- a/goalc/emitter/ObjectGenerator.cpp +++ b/goalc/emitter/ObjectGenerator.cpp @@ -74,6 +74,7 @@ ObjectFileData ObjectGenerator::generate_data_v3() { // step 3, cleaning up things now that we know the memory layout for (int seg = N_SEG; seg-- > 0;) { handle_temp_static_type_links(seg); + handle_temp_static_sym_links(seg); handle_temp_jump_links(seg); handle_temp_instr_sym_links(seg); handle_temp_rip_func_links(seg); diff --git a/test/goalc/source_templates/methods/methods.static.gc b/test/goalc/source_templates/methods/methods.static.gc index 121b508964..33518fd31d 100644 --- a/test/goalc/source_templates/methods/methods.static.gc +++ b/test/goalc/source_templates/methods/methods.static.gc @@ -1,6 +1,8 @@ (define format _format) -(format #t "~A~A~%" (eq? (-> process method-table 2) (method process print)) +; no longer use process as a test type here because it's no longer built-in so is +; only forward declared at this point. +(format #t "~A~A~%" (eq? (-> type method-table 2) (method type print)) (eq? (-> string method-table 3) (method "test" inspect)) ) 0 \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-new-static-basic.gc b/test/goalc/source_templates/with_game/test-new-static-basic.gc new file mode 100644 index 0000000000..d31351961f --- /dev/null +++ b/test/goalc/source_templates/with_game/test-new-static-basic.gc @@ -0,0 +1,23 @@ +(start-test "new-static-basic") + +(deftype static-test-basic-type (basic) + ((s8 int8) + (s16 int16) + (thing basic) + (u64 uint64)) + ) + +(define test-static-basic (new 'static 'static-test-basic-type :s8 -122 :s16 -23123 :u64 434343 :thing 'bean)) + +(expect-true (< (the int test-static-basic) (-> debug current))) ;; should be in debug segment? +(expect-true (> (the int test-static-basic) (-> debug base))) + +(expect-true (= (-> test-static-basic s8) -122)) +(expect-true (= (-> test-static-basic s16) -23123)) +(expect-true (= (-> test-static-basic u64) 434343)) +(expect-true (eq? (-> test-static-basic thing) 'bean)) +(expect-true (neq? (-> test-static-basic thing) 'not-bean)) +(expect-true (eq? (-> test-static-basic type symbol) 'static-test-basic-type)) +(expect-true (eq? (-> test-static-basic type) static-test-basic-type)) + +(finish-test) \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-new-static-structure-integers.gc b/test/goalc/source_templates/with_game/test-new-static-structure-integers.gc new file mode 100644 index 0000000000..e4d05c816c --- /dev/null +++ b/test/goalc/source_templates/with_game/test-new-static-structure-integers.gc @@ -0,0 +1,21 @@ +(start-test "new-static-structure-integers") + +(deftype static-test-structure-type (structure) + ((s8 int8) + (s16 int16) + (thing basic) + (u64 uint64)) + ) + +(define test-static-structure (new 'static 'static-test-structure-type :s8 -122 :s16 -23123 :u64 434343 :thing 'bean)) + +(expect-true (< (the int test-static-structure) (-> debug current))) ;; should be in debug segment? +(expect-true (> (the int test-static-structure) (-> debug base))) + +(expect-true (= (-> test-static-structure s8) -122)) +(expect-true (= (-> test-static-structure s16) -23123)) +(expect-true (= (-> test-static-structure u64) 434343)) +(expect-true (eq? (-> test-static-structure thing) 'bean)) +(expect-true (neq? (-> test-static-structure thing) 'not-bean)) + +(finish-test) \ No newline at end of file diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 65f55c0f94..9567c63f98 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -127,4 +127,8 @@ TEST_F(WithGameTests, All) { runner.run_static_test(env, testCategory, "test-set-self.gc", {"#t\n0\n"}); runner.run_static_test(env, testCategory, "test-new-array.gc", get_test_pass_string("new-array", 8)); + runner.run_static_test(env, testCategory, "test-new-static-structure-integers.gc", + get_test_pass_string("new-static-structure-integers", 7)); + runner.run_static_test(env, testCategory, "test-new-static-basic.gc", + get_test_pass_string("new-static-basic", 9)); }