From abcd444a3ba896b8d5f44a73c2d0c6a346cca521 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Thu, 17 Sep 2020 21:47:52 -0400 Subject: [PATCH] Add deftype (#48) * initial deftype implementation * fix library setup for windows * implement deftype * fix memory bug * fix formatting --- CMakeLists.txt | 3 + {goalc => common}/goos/CMakeLists.txt | 0 {goalc => common}/goos/Interpreter.cpp | 0 {goalc => common}/goos/Interpreter.h | 0 {goalc => common}/goos/InterpreterEval.cpp | 0 {goalc => common}/goos/Object.cpp | 0 {goalc => common}/goos/Object.h | 0 {goalc => common}/goos/Reader.cpp | 0 {goalc => common}/goos/Reader.h | 4 +- {goalc => common}/goos/TextDB.cpp | 0 {goalc => common}/goos/TextDB.h | 2 +- common/type_system/CMakeLists.txt | 5 +- common/type_system/Type.cpp | 2 +- common/type_system/Type.h | 2 +- common/type_system/TypeSystem.cpp | 1 + common/type_system/TypeSystem.h | 2 +- common/type_system/deftype.cpp | 325 +++++++++++++++++++++ common/type_system/deftype.h | 24 ++ common/util/FileUtil.cpp | 9 +- goal_src/kernel/gcommon.gc | 46 ++- goalc/CMakeLists.txt | 2 +- goalc/compiler/Compiler.cpp | 2 +- goalc/compiler/Compiler.h | 9 +- goalc/compiler/CompilerSettings.h | 2 +- goalc/compiler/Env.h | 2 +- goalc/compiler/IR.cpp | 34 +++ goalc/compiler/IR.h | 16 + goalc/compiler/Lambda.h | 2 +- goalc/compiler/Val.cpp | 6 + goalc/compiler/Val.h | 27 ++ goalc/compiler/compilation/Atoms.cpp | 9 +- goalc/compiler/compilation/Function.cpp | 3 +- goalc/compiler/compilation/Type.cpp | 40 +++ test/CMakeLists.txt | 1 + test/test_deftype.cpp | 48 +++ test/test_goos.cpp | 2 +- test/test_reader.cpp | 2 +- test/test_type_system.cpp | 1 + 38 files changed, 601 insertions(+), 32 deletions(-) rename {goalc => common}/goos/CMakeLists.txt (100%) rename {goalc => common}/goos/Interpreter.cpp (100%) rename {goalc => common}/goos/Interpreter.h (100%) rename {goalc => common}/goos/InterpreterEval.cpp (100%) rename {goalc => common}/goos/Object.cpp (100%) rename {goalc => common}/goos/Object.h (100%) rename {goalc => common}/goos/Reader.cpp (100%) rename {goalc => common}/goos/Reader.h (97%) rename {goalc => common}/goos/TextDB.cpp (100%) rename {goalc => common}/goos/TextDB.h (98%) create mode 100644 common/type_system/deftype.cpp create mode 100644 common/type_system/deftype.h create mode 100644 goalc/compiler/compilation/Type.cpp create mode 100644 test/test_deftype.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f3800e999..70931db1bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,9 @@ include_directories(./) # build asset packer/unpacker add_subdirectory(asset_tool) +# build goos +add_subdirectory(common/goos) + # build type_system library for compiler/decompiler add_subdirectory(common/type_system) diff --git a/goalc/goos/CMakeLists.txt b/common/goos/CMakeLists.txt similarity index 100% rename from goalc/goos/CMakeLists.txt rename to common/goos/CMakeLists.txt diff --git a/goalc/goos/Interpreter.cpp b/common/goos/Interpreter.cpp similarity index 100% rename from goalc/goos/Interpreter.cpp rename to common/goos/Interpreter.cpp diff --git a/goalc/goos/Interpreter.h b/common/goos/Interpreter.h similarity index 100% rename from goalc/goos/Interpreter.h rename to common/goos/Interpreter.h diff --git a/goalc/goos/InterpreterEval.cpp b/common/goos/InterpreterEval.cpp similarity index 100% rename from goalc/goos/InterpreterEval.cpp rename to common/goos/InterpreterEval.cpp diff --git a/goalc/goos/Object.cpp b/common/goos/Object.cpp similarity index 100% rename from goalc/goos/Object.cpp rename to common/goos/Object.cpp diff --git a/goalc/goos/Object.h b/common/goos/Object.h similarity index 100% rename from goalc/goos/Object.h rename to common/goos/Object.h diff --git a/goalc/goos/Reader.cpp b/common/goos/Reader.cpp similarity index 100% rename from goalc/goos/Reader.cpp rename to common/goos/Reader.cpp diff --git a/goalc/goos/Reader.h b/common/goos/Reader.h similarity index 97% rename from goalc/goos/Reader.h rename to common/goos/Reader.h index e234c07a5a..f5aafec45e 100644 --- a/goalc/goos/Reader.h +++ b/common/goos/Reader.h @@ -19,8 +19,8 @@ #include #include -#include "goalc/goos/Object.h" -#include "goalc/goos/TextDB.h" +#include "common/goos/Object.h" +#include "common/goos/TextDB.h" namespace goos { diff --git a/goalc/goos/TextDB.cpp b/common/goos/TextDB.cpp similarity index 100% rename from goalc/goos/TextDB.cpp rename to common/goos/TextDB.cpp diff --git a/goalc/goos/TextDB.h b/common/goos/TextDB.h similarity index 98% rename from goalc/goos/TextDB.h rename to common/goos/TextDB.h index cc86e2cd9d..f15939038d 100644 --- a/goalc/goos/TextDB.h +++ b/common/goos/TextDB.h @@ -22,7 +22,7 @@ #include #include -#include "goalc/goos/Object.h" +#include "common/goos/Object.h" namespace goos { /*! diff --git a/common/type_system/CMakeLists.txt b/common/type_system/CMakeLists.txt index d8db0b46ec..271fcd92b4 100644 --- a/common/type_system/CMakeLists.txt +++ b/common/type_system/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(type_system SHARED TypeSystem.cpp Type.cpp - TypeSpec.cpp) + TypeSpec.cpp + deftype.cpp) -target_link_libraries(type_system fmt) \ No newline at end of file +target_link_libraries(type_system fmt goos) \ No newline at end of file diff --git a/common/type_system/Type.cpp b/common/type_system/Type.cpp index f6464890f8..55b7bae132 100644 --- a/common/type_system/Type.cpp +++ b/common/type_system/Type.cpp @@ -467,7 +467,7 @@ void StructureType::inherit(StructureType* parent) { } bool StructureType::operator==(const Type& other) const { - if (typeid(StructureType) != typeid(other)) { + if (typeid(*this) != typeid(other)) { return false; } diff --git a/common/type_system/Type.h b/common/type_system/Type.h index b9f8610e06..dab3adec97 100644 --- a/common/type_system/Type.h +++ b/common/type_system/Type.h @@ -215,7 +215,7 @@ class StructureType : public ReferenceType { bool pack = false); std::string print() const override; void inherit(StructureType* parent); - const std::vector& fields() { return m_fields; } + const std::vector& fields() const { return m_fields; } bool operator==(const Type& other) const override; int get_size_in_memory() const override; int get_offset() const override; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 308128dce3..1904a6adf0 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -591,6 +591,7 @@ void TypeSystem::add_builtin_types() { // TYPE builtin_structure_inherit(type_type); + add_method(type_type, "new", make_function_typespec({"symbol", "type", "int"}, "_type_")); add_field_to_type(type_type, "symbol", make_typespec("symbol")); add_field_to_type(type_type, "parent", make_typespec("type")); add_field_to_type(type_type, "allocated-size", make_typespec("uint16")); // todo, u16 or s16? diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index cf565008db..24b3fae0b1 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -76,6 +76,7 @@ class TypeSystem { bool print_on_error = true, bool throw_on_error = true) const; std::vector get_path_up_tree(const std::string& type); + int get_next_method_id(Type* type); /*! * Get a type by name and cast to a child class of Type*. Must succeed. @@ -99,7 +100,6 @@ class TypeSystem { int get_size_in_type(const Field& field); int get_alignment_in_type(const Field& field); Field lookup_field(const std::string& type_name, const std::string& field_name); - int get_next_method_id(Type* type); StructureType* add_builtin_structure(const std::string& parent, const std::string& type_name, bool boxed = false); diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp new file mode 100644 index 0000000000..3b0405ebed --- /dev/null +++ b/common/type_system/deftype.cpp @@ -0,0 +1,325 @@ +#include "deftype.h" +#include "third-party/fmt/core.h" + +/*! + * Missing Features + * - Bitfields + * - Int128 children + * - Refer to yourself (structure/basic only) + * - Method List + * - Evaluate constants. + * + */ + +namespace { +const goos::Object& car(const goos::Object* x) { + if (!x->is_pair()) { + throw std::runtime_error("invalid deftype form"); + } + + return x->as_pair()->car; +} + +const goos::Object* cdr(const goos::Object* x) { + if (!x->is_pair()) { + throw std::runtime_error("invalid deftype form"); + } + + return &x->as_pair()->cdr; +} + +std::string deftype_parent_list(const goos::Object& list) { + if (!list.is_pair()) { + throw std::runtime_error("invalid parent list in deftype: " + list.print()); + } + + auto parent = list.as_pair()->car; + auto rest = list.as_pair()->cdr; + if (!rest.is_empty_list()) { + throw std::runtime_error("invalid parent list in deftype - can only have one parent"); + } + + if (!parent.is_symbol()) { + throw std::runtime_error("invalid parent in deftype parent list"); + } + + return parent.as_symbol()->name; +} + +bool is_type(const std::string& expected, const TypeSpec& actual, const TypeSystem* ts) { + return ts->typecheck(ts->make_typespec(expected), actual, "", false, false); +} + +template +void for_each_in_list(const goos::Object& list, T f) { + const goos::Object* iter = &list; + while (iter->is_pair()) { + auto lap = iter->as_pair(); + f(lap->car); + iter = &lap->cdr; + } + + if (!iter->is_empty_list()) { + throw std::runtime_error("invalid list in for_each_in_list: " + list.print()); + } +} + +std::string symbol_string(const goos::Object& obj) { + if (obj.is_symbol()) { + return obj.as_symbol()->name; + } + throw std::runtime_error(obj.print() + " was supposed to be a symbol, but isn't"); +} + +int64_t get_int(const goos::Object& obj) { + if (obj.is_int()) { + return obj.integer_obj.value; + } + throw std::runtime_error(obj.print() + " was supposed to be an integer, but isn't"); +} + +TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src) { + if (src.is_symbol()) { + return type_system->make_typespec(symbol_string(src)); + } else if (src.is_pair()) { + TypeSpec ts = type_system->make_typespec(symbol_string(car(&src))); + const auto& rest = *cdr(&src); + + for_each_in_list(rest, + [&](const goos::Object& o) { ts.add_arg(parse_typespec(type_system, o)); }); + + return ts; + } else { + throw std::runtime_error("invalid typespec: " + src.print()); + } + assert(false); + return {}; +} + +void add_field(StructureType* structure, TypeSystem* ts, const goos::Object& def) { + auto rest = &def; + + auto name = symbol_string(car(rest)); + rest = cdr(rest); + + auto type = parse_typespec(ts, car(rest)); + rest = cdr(rest); + + int array_size = -1; + bool is_inline = false; + bool is_dynamic = false; + int offset_override = -1; + int offset_assert = -1; + + if (!rest->is_empty_list()) { + if (car(rest).is_int()) { + array_size = car(rest).integer_obj.value; + rest = cdr(rest); + } + + while (!rest->is_empty_list()) { + auto opt_name = symbol_string(car(rest)); + rest = cdr(rest); + + if (opt_name == ":inline") { + is_inline = true; + } else if (opt_name == ":dynamic") { + is_dynamic = true; + } else if (opt_name == ":offset") { + offset_override = get_int(car(rest)); + rest = cdr(rest); + } else if (opt_name == ":offset-assert") { + offset_assert = get_int(car(rest)); + if (offset_assert == -1) { + throw std::runtime_error("Cannot use -1 as offset-assert"); + } + rest = cdr(rest); + } else { + throw std::runtime_error("Invalid option in field specification: " + opt_name); + } + } + } + + int actual_offset = ts->add_field_to_type(structure, name, type, is_inline, is_dynamic, + array_size, offset_override); + if (offset_assert != -1 && actual_offset != offset_assert) { + throw std::runtime_error("Field " + name + " was placed at " + std::to_string(actual_offset) + + " but offset-assert was set to " + std::to_string(offset_assert)); + } +} + +void declare_method(StructureType* type, TypeSystem* type_system, const goos::Object& def) { + for_each_in_list(def, [&](const goos::Object& _obj) { + auto obj = &_obj; + // (name args return-type [id]) + auto method_name = symbol_string(car(obj)); + obj = cdr(obj); + auto& args = car(obj); + obj = cdr(obj); + auto& return_type = car(obj); + obj = cdr(obj); + int id = -1; + if (!obj->is_empty_list()) { + auto& id_obj = car(obj); + id = get_int(id_obj); + if (!cdr(obj)->is_empty_list()) { + throw std::runtime_error("too many things in method def: " + def.print()); + } + } + + std::vector arg_types; + for_each_in_list(args, [&](const goos::Object& o) { + if (o.is_symbol()) { + arg_types.emplace_back(symbol_string(o)); + } else { + auto next = cdr(&o); + arg_types.emplace_back(symbol_string(car(next))); + if (!cdr(next)->is_empty_list()) { + throw std::runtime_error("too many things in method def arg type: " + def.print()); + }; + } + }); + + auto info = type_system->add_method( + type, method_name, + type_system->make_function_typespec(arg_types, symbol_string(return_type))); + + // check the method assert + if (id != -1) { + // method id assert! + if (id != info.id) { + printf("WARNING - ID assert failed on method %s of type %s (wanted %d got %d)\n", + method_name.c_str(), type->get_name().c_str(), id, info.id); + throw std::runtime_error("Method ID assert failed"); + } + } + }); +} + +TypeFlags parse_structure_def(StructureType* type, + TypeSystem* ts, + const goos::Object& fields, + const goos::Object& options) { + (void)options; + for_each_in_list(fields, [&](const goos::Object& o) { add_field(type, ts, o); }); + TypeFlags flags; + flags.heap_base = 0; + + flags.size = type->get_size_in_memory(); + flags.pad = 0; + + auto* rest = &options; + int size_assert = -1; + int method_count_assert = -1; + uint64_t flag_assert = 0; + bool flag_assert_set = false; + while (!rest->is_empty_list()) { + if (car(rest).is_pair()) { + auto opt_list = &car(rest); + auto& first = car(opt_list); + opt_list = cdr(opt_list); + + if (symbol_string(first) == ":methods") { + declare_method(type, ts, *opt_list); + } else { + throw std::runtime_error("Invalid option list in field specification: " + + car(rest).print()); + } + + rest = cdr(rest); + } else { + auto opt_name = symbol_string(car(rest)); + rest = cdr(rest); + + if (opt_name == ":size-assert") { + size_assert = get_int(car(rest)); + if (size_assert == -1) { + throw std::runtime_error("Cannot use -1 as size-assert"); + } + rest = cdr(rest); + } else if (opt_name == ":method-count-assert") { + method_count_assert = get_int(car(rest)); + if (method_count_assert == -1) { + throw std::runtime_error("Cannot use -1 as method_count_assert"); + } + rest = cdr(rest); + } else if (opt_name == ":flag-assert") { + flag_assert = get_int(car(rest)); + flag_assert_set = true; + rest = cdr(rest); + } else { + throw std::runtime_error("Invalid option in field specification: " + opt_name); + } + } + } + + if (size_assert != -1 && flags.size != u16(size_assert)) { + throw std::runtime_error("Type " + type->get_name() + " came out to size " + + std::to_string(int(flags.size)) + " but size-assert was set to " + + std::to_string(size_assert)); + } + + flags.methods = ts->get_next_method_id(type); + + if (method_count_assert != -1 && flags.methods != u16(method_count_assert)) { + throw std::runtime_error( + "Type " + type->get_name() + " has " + std::to_string(int(flags.methods)) + + " methods, but method-count-assert was set to " + std::to_string(method_count_assert)); + } + + if (flag_assert_set && (flags.flag != flag_assert)) { + throw std::runtime_error( + fmt::format("Type {} has flag 0x{:x} but flag-assert was set to 0x{:x}", type->get_name(), + flags.flag, flag_assert)); + } + + return flags; +} + +} // namespace + +DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) { + auto iter = &deftype; + + auto& type_name_obj = car(iter); + iter = cdr(iter); + auto& parent_list_obj = car(iter); + iter = cdr(iter); + auto& field_list_obj = car(iter); + iter = cdr(iter); + auto& options_obj = *iter; + + if (!type_name_obj.is_symbol()) { + throw std::runtime_error("deftype must be given a symbol as the type name"); + } + + auto name = type_name_obj.as_symbol()->name; + auto parent_type_name = deftype_parent_list(parent_list_obj); + auto parent_type = ts->make_typespec(parent_type_name); + DeftypeResult result; + + if (is_type("basic", 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->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->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."); + } else { + throw std::runtime_error("Creating a child type from " + parent_type.print() + + " is not allowed or not supported yet."); + } + + result.type = ts->make_typespec(name); + result.type_info = ts->lookup_type(result.type); + return result; +} diff --git a/common/type_system/deftype.h b/common/type_system/deftype.h new file mode 100644 index 0000000000..13872693da --- /dev/null +++ b/common/type_system/deftype.h @@ -0,0 +1,24 @@ +#pragma once + +#include "TypeSystem.h" +#include "common/goos/Object.h" + +struct TypeFlags { + union { + uint64_t flag = 0; + struct { + uint16_t size; + uint16_t heap_base; + uint16_t methods; + uint16_t pad; + }; + }; +}; + +struct DeftypeResult { + TypeFlags flags; + TypeSpec type; + Type* type_info = nullptr; +}; + +DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts); diff --git a/common/util/FileUtil.cpp b/common/util/FileUtil.cpp index 1ad686450a..f10c05f070 100644 --- a/common/util/FileUtil.cpp +++ b/common/util/FileUtil.cpp @@ -22,10 +22,11 @@ std::string file_util::get_project_path() { 0, pos + 11); // + 12 to include "\jak-project" in the returned filepath #else // do Linux stuff - char buffer[FILENAME_MAX]; - readlink("/proc/self/exe", buffer, - FILENAME_MAX); // /proc/self acts like a "virtual folder" containing information about - // the current process + char buffer[FILENAME_MAX + 1]; + auto len = readlink("/proc/self/exe", buffer, + FILENAME_MAX); // /proc/self acts like a "virtual folder" containing + // information about the current process + buffer[len] = '\0'; std::string::size_type pos = std::string(buffer).rfind("jak-project"); // Strip file path down to /jak-project/ directory return std::string(buffer).substr( diff --git a/goal_src/kernel/gcommon.gc b/goal_src/kernel/gcommon.gc index f611a24b9d..26a7b272a2 100644 --- a/goal_src/kernel/gcommon.gc +++ b/goal_src/kernel/gcommon.gc @@ -100,7 +100,7 @@ (defun mod ((a integer) (b integer)) "Compute mod. It does what you expect for positive numbers. For negative numbers, nobody knows what to expect. This is a 32-bit operation. It uses an idiv on x86 and gets the remainder." - + ;; The original implementation is div, mfhi ;; todo - verify this is exactly the same as the PS2. (mod a b) @@ -109,7 +109,7 @@ (defun rem ((a integer) (b integer)) "Compute remainder (32-bit). It is identical to mod. It uses a idiv and gets the remainder" - + ;; The original implementation is div, mfhi ;; todo - verify this is exactly the same as the PS2. (mod a b) @@ -206,5 +206,43 @@ ;; or 128-bit arguments (unimplemented in C Kernel), but both of these were never finished. (define format _format) -;; todo bfloat -;; todo pinrt bfloat \ No newline at end of file +;; TODO - vec4s + +;; The "boxed float" type bfloat is just a float wrapped in a basic (structure type that has runtime type information) + +;; define a type called "bfloat" which is a child of "basic". +;; "basic" is just a type with structures and runtime type information +(deftype bfloat (basic) + ;; the field list. + ;; there is a single field named data, of type float. + ;; the :offset-assert makes sure that OpenGOAL's type layout places the field at the given offset. + ;; if not, it creates a compiler error. This is used to make sure we exactly copy the game's memory layout, + ;; as we can get the exact offset of fields from the disassembly + ((data float :offset-assert 4)) + + ;; declare methods. If you are overriding a parent method, you don't have to declare it, but you can if you want + ;; The number after the return type is the method ID, that can be checked against the disassembly to make sure + ;; the type and method hierarchy is correct. If OpenGOAL's method table layout doesn't match, it will create + ;; compiler error. + + (:methods (print (_type_) _type_ 2) ;; we will override print later on + (inspect (_type_) _type_ 3) ;; this is a parent method we won't override. It's fine to put it here anyway. + ) + + + ;; Note that the special type "_type_" can be used in methods to indicate "the type of the object method is called on". + ;; this is used for 2 things: + ;; 1. Child who overrides it can use their own type as an argument, rather than a less specific parent type. + ;; 2. Caller who calls an overriden method and knows it at compile time can know a return type more specifically. + + + ;; make sure the size of the type is correct (this is stored in the type structure, so we can check it) + :size-assert 8 + ;; make sure method count is correct (again, stored in the type structure) + :method-count-assert 9 + ;; flags passed to the new_type function in the runtime. + :flag-assert #x900000008 + ) + + +;; todo print bfloat \ No newline at end of file diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 3c309bec06..e21bec115e 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -1,4 +1,3 @@ -add_subdirectory(goos) add_subdirectory(listener) add_library(compiler @@ -22,6 +21,7 @@ add_library(compiler compiler/compilation/Define.cpp compiler/compilation/Function.cpp compiler/compilation/ControlFlow.cpp + compiler/compilation/Type.cpp compiler/Util.cpp logger/Logger.cpp regalloc/IRegister.cpp diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 6ca06f87a1..c47962b609 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -200,7 +200,7 @@ std::vector Compiler::run_test(const std::string& source_code) { return m_listener.stop_recording_messages(); } catch (std::exception& e) { fmt::print("[Compiler] Failed to compile test program {}: {}\n", source_code, e.what()); - return {}; + throw e; } } diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 6dbab4077f..af97fae84b 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -6,7 +6,7 @@ #include "common/type_system/TypeSystem.h" #include "Env.h" #include "goalc/listener/Listener.h" -#include "goalc/goos/Interpreter.h" +#include "common/goos/Interpreter.h" #include "goalc/compiler/IR.h" #include "CompilerSettings.h" @@ -105,6 +105,10 @@ class Compiler { Val* compile_variable_shift(const RegVal* in, const RegVal* sa, Env* env, IntegerMathKind kind); + RegVal* compile_get_method_of_type(const TypeSpec& type, + const std::string& method_name, + Env* env); + public: // Atoms @@ -163,6 +167,9 @@ class Compiler { Val* compile_lambda(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_inline(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_declare(const goos::Object& form, const goos::Object& rest, Env* env); + + // Type + Val* compile_deftype(const goos::Object& form, const goos::Object& rest, Env* env); }; #endif // JAK_COMPILER_H diff --git a/goalc/compiler/CompilerSettings.h b/goalc/compiler/CompilerSettings.h index 0788653e57..caecf4e179 100644 --- a/goalc/compiler/CompilerSettings.h +++ b/goalc/compiler/CompilerSettings.h @@ -5,7 +5,7 @@ #include #include -#include "goalc/goos/Object.h" +#include "common/goos/Object.h" class CompilerSettings { public: diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index 3d7f6113a3..a1f5a822ec 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -14,7 +14,7 @@ #include #include "common/type_system/TypeSpec.h" #include "goalc/regalloc/allocate.h" -#include "goalc/goos/Object.h" +#include "common/goos/Object.h" #include "StaticObject.h" #include "Label.h" #include "Val.h" diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index 2562bdcf0a..dcd079df31 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -665,4 +665,38 @@ void IR_ConditionalBranch::do_codegen(emitter::ObjectGenerator* gen, auto jump_rec = gen->add_instr(jump_instr, irec); gen->link_instruction_jump(jump_rec, gen->get_future_ir_record_in_same_func(irec, label.idx)); +} + +///////////////////// +// LoadConstantOffset +///////////////////// + +IR_LoadConstOffset::IR_LoadConstOffset(const RegVal* dest, + int offset, + const RegVal* base, + MemLoadInfo info) + : m_dest(dest), m_offset(offset), m_base(base), m_info(info) {} + +std::string IR_LoadConstOffset::print() { + return fmt::format("mov {}, [{} + {}]\n", m_dest->print(), m_base->print(), m_offset); +} + +RegAllocInstr IR_LoadConstOffset::to_rai() { + RegAllocInstr rai; + rai.write.push_back(m_dest->ireg()); + rai.read.push_back(m_base->ireg()); + return rai; +} + +void IR_LoadConstOffset::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + if (m_dest->ireg().kind == emitter::RegKind::GPR) { + gen->add_instr(IGen::load_goal_gpr(get_reg(m_dest, allocs, irec), get_reg(m_base, allocs, irec), + emitter::gRegInfo.get_offset_reg(), m_offset, m_info.size, + m_info.sign_extend), + irec); + } else { + throw std::runtime_error("IR_LoadConstOffset::do_codegen xmm not supported"); + } } \ No newline at end of file diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index 75eac893e7..05917f9338 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -270,4 +270,20 @@ class IR_ConditionalBranch : public IR { bool m_resolved = false; }; +class IR_LoadConstOffset : public IR { + public: + IR_LoadConstOffset(const RegVal* dest, int offset, const RegVal* base, MemLoadInfo info); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + private: + const RegVal* m_dest = nullptr; + int m_offset = 0; + const RegVal* m_base = nullptr; + MemLoadInfo m_info; +}; + #endif // JAK_IR_H diff --git a/goalc/compiler/Lambda.h b/goalc/compiler/Lambda.h index 37762b549b..8be93f675b 100644 --- a/goalc/compiler/Lambda.h +++ b/goalc/compiler/Lambda.h @@ -3,7 +3,7 @@ #ifndef JAK_LAMBDA_H #define JAK_LAMBDA_H -#include "goalc/goos/Object.h" +#include "common/goos/Object.h" // note - we cannot easily reuse the GOOS argument system because GOAL's is slightly different. // there's no rest or keyword support. diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index e922c8c165..b9c932f731 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -91,4 +91,10 @@ RegVal* FloatConstantVal::to_reg(Env* fe) { auto re = fe->make_xmm(m_ts); fe->emit(std::make_unique(re, m_value)); return re; +} + +RegVal* MemoryOffsetConstantVal::to_reg(Env* fe) { + auto re = fe->make_gpr(deref_type); + fe->emit(std::make_unique(re, offset, base, info)); + return re; } \ No newline at end of file diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index 846f975620..5fc61f2732 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -125,6 +125,33 @@ class StaticVal : public Val { RegVal* to_reg(Env* fe) override; }; +struct MemLoadInfo { + bool sign_extend = false; + int size = -1; +}; + +class MemoryOffsetConstantVal : public Val { + public: + MemoryOffsetConstantVal(TypeSpec ts, + RegVal* _base, + int _offset, + MemLoadInfo _info, + TypeSpec _deref_type) + : Val(std::move(ts)), + base(_base), + offset(_offset), + info(_info), + deref_type(std::move(_deref_type)) {} + std::string print() const override { + return "(" + base->print() + " + " + std::to_string(offset) + ")"; + } + RegVal* to_reg(Env* fe) override; + RegVal* base = nullptr; + int offset = 0; + MemLoadInfo info; + TypeSpec deref_type; +}; + // MemOffConstant // MemOffVar // MemDeref diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index ceeadddbcd..7cf2c5e9af 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -55,13 +55,9 @@ static const std::unordered_map< // {"defun-extern", &Compiler::compile_defun_extern}, // {"declare-method", &Compiler::compile_declare_method}, // - // // DEFTYPE - // {"deftype", &Compiler::compile_deftype}, - // - // // ENUM + // TYPE + {"deftype", &Compiler::compile_deftype}, // {"defenum", &Compiler::compile_defenum}, - // - // // Field Access // {"->", &Compiler::compile_deref}, // {"&", &Compiler::compile_addr_of}, // @@ -72,7 +68,6 @@ static const std::unordered_map< {"inline", &Compiler::compile_inline}, // {"with-inline", &Compiler::compile_with_inline}, // {"rlet", &Compiler::compile_rlet}, - // {"get-ra-ptr", &Compiler::compile_get_ra_ptr}, // // diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index 025b9ea827..ff300401e1 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -341,7 +341,8 @@ Val* Compiler::compile_real_function_call(const goos::Object& form, printf("got type %s\n", function->type().print().c_str()); throw_compile_error(form, "invalid number of arguments to function call: got " + std::to_string(args.size()) + " and expected " + - std::to_string(function->type().arg_count() - 1)); + std::to_string(function->type().arg_count() - 1) + " for " + + function->type().print()); } for (uint32_t i = 0; i < args.size(); i++) { typecheck(form, function->type().get_arg(i), args.at(i)->type(), "function argument"); diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp new file mode 100644 index 0000000000..ad07d6c42c --- /dev/null +++ b/goalc/compiler/compilation/Type.cpp @@ -0,0 +1,40 @@ +#include "goalc/compiler/Compiler.h" +#include "common/type_system/deftype.h" + +RegVal* Compiler::compile_get_method_of_type(const TypeSpec& type, + const std::string& method_name, + Env* env) { + auto info = m_ts.lookup_method(type.base_type(), method_name); + auto offset_of_method = 16 + 4 * info.id; // todo, something more flexible? + + auto fe = get_parent_env_of_type(env); + auto typ = compile_get_symbol_value(type.base_type(), env)->to_gpr(env); + MemLoadInfo load_info; + load_info.sign_extend = false; + load_info.size = POINTER_SIZE; + + return fe + ->alloc_val(typ->type(), typ, offset_of_method, load_info, info.type) + ->to_reg(env); +} + +Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)form; + (void)env; + + auto result = parse_deftype(rest, &m_ts); + + auto kv = m_symbol_types.find(result.type.base_type()); + if (kv != m_symbol_types.end() && kv->second.base_type() != "type") { + fmt::print("[Warning] deftype will redefined {} from {} to a type.\n", result.type.base_type(), + kv->second.print()); + } + m_symbol_types[result.type.base_type()] = m_ts.make_typespec("type"); + + auto new_type_method = compile_get_method_of_type(m_ts.make_typespec("type"), "new", env); + 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); + return compile_real_function_call(form, new_type_method, + {new_type_symbol, parent_type, flags_int}, env); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index adfec63cc2..cf38f56bf7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(goalc-test test_emitter_integer_math.cpp test_common_util.cpp test_compiler_and_runtime.cpp + test_deftype.cpp ) enable_testing() diff --git a/test/test_deftype.cpp b/test/test_deftype.cpp new file mode 100644 index 0000000000..3bea903e54 --- /dev/null +++ b/test/test_deftype.cpp @@ -0,0 +1,48 @@ +#include "gtest/gtest.h" +#include "common/type_system/TypeSystem.h" +#include "common/type_system/deftype.h" +#include "common/goos/Reader.h" +#include "third-party/fmt/core.h" + +TEST(Deftype, deftype) { + TypeSystem ts; + ts.add_builtin_types(); + std::string input = + "(deftype my-type (basic) ((f1 int64) (f2 string) (f3 int8) (f4 type :inline)))"; + goos::Reader reader; + auto in = reader.read_from_string(input).as_pair()->cdr.as_pair()->car.as_pair()->cdr; + auto result = parse_deftype(in, &ts); + + auto& f = dynamic_cast(ts.lookup_type(result.type))->fields(); + EXPECT_EQ(f.size(), 5); + + auto& tf = f.at(0); + EXPECT_EQ(tf.name(), "type"); + EXPECT_EQ(tf.offset(), 0); + EXPECT_EQ(tf.type().print(), "type"); + EXPECT_EQ(tf.is_inline(), false); + + auto& f1 = f.at(1); + EXPECT_EQ(f1.name(), "f1"); + EXPECT_EQ(f1.offset(), 8); + EXPECT_EQ(f1.type().print(), "int64"); + EXPECT_EQ(f1.is_inline(), false); + + auto& f2 = f.at(2); + EXPECT_EQ(f2.name(), "f2"); + EXPECT_EQ(f2.offset(), 16); + EXPECT_EQ(f2.type().print(), "string"); + EXPECT_EQ(f2.is_inline(), false); + + auto& f3 = f.at(3); + EXPECT_EQ(f3.name(), "f3"); + EXPECT_EQ(f3.offset(), 20); + EXPECT_EQ(f3.type().print(), "int8"); + EXPECT_EQ(f3.is_inline(), false); + + auto& f4 = f.at(4); + EXPECT_EQ(f4.name(), "f4"); + EXPECT_EQ(f4.offset(), 32); + EXPECT_EQ(f4.type().print(), "type"); + EXPECT_EQ(f4.is_inline(), true); +} \ No newline at end of file diff --git a/test/test_goos.cpp b/test/test_goos.cpp index 6b67bf3354..d723fae816 100644 --- a/test/test_goos.cpp +++ b/test/test_goos.cpp @@ -4,7 +4,7 @@ */ #include "gtest/gtest.h" -#include "goalc/goos/Interpreter.h" +#include "common/goos/Interpreter.h" using namespace goos; diff --git a/test/test_reader.cpp b/test/test_reader.cpp index d8f4b68252..5b5e6b885e 100644 --- a/test/test_reader.cpp +++ b/test/test_reader.cpp @@ -5,7 +5,7 @@ */ #include "gtest/gtest.h" -#include "goalc/goos/Reader.h" +#include "common/goos/Reader.h" #include "common/util/FileUtil.h" using namespace goos; diff --git a/test/test_type_system.cpp b/test/test_type_system.cpp index 784aa130fb..b1e47cd535 100644 --- a/test/test_type_system.cpp +++ b/test/test_type_system.cpp @@ -315,4 +315,5 @@ TEST(TypeSystem, lca) { .print(), "(pointer object)"); } + // TODO - a big test to make sure all the builtin types are what we expect.