diff --git a/common/goal_constants.h b/common/goal_constants.h index bad789b68..88d6896e2 100644 --- a/common/goal_constants.h +++ b/common/goal_constants.h @@ -1,10 +1,24 @@ #ifndef JAK_GOAL_CONSTANTS_H #define JAK_GOAL_CONSTANTS_H +#include "common_types.h" + +constexpr s32 BINTEGER_OFFSET = 0; +constexpr s32 PAIR_OFFSET = 2; constexpr int POINTER_SIZE = 4; constexpr int BASIC_OFFSET = 4; constexpr int STRUCTURE_ALIGNMENT = 16; enum class RegKind { GPR_64, FLOAT, INT_128, FLOAT_4X, INVALID }; +constexpr u32 GOAL_NEW_METHOD = 0; // method ID of GOAL new +constexpr u32 GOAL_DEL_METHOD = 1; // method ID of GOAL delete +constexpr u32 GOAL_PRINT_METHOD = 2; // method ID of GOAL print +constexpr u32 GOAL_INSPECT_METHOD = 3; // method ID of GOAL inspect +constexpr u32 GOAL_LENGTH_METHOD = 4; // method ID of GOAL length +constexpr u32 GOAL_ASIZE_METHOD = 5; // method ID of GOAL size +constexpr u32 GOAL_COPY_METHOD = 6; // method ID of GOAL copy +constexpr u32 GOAL_RELOC_METHOD = 7; // method ID of GOAL relocate +constexpr u32 GOAL_MEMUSAGE_METHOD = 8; // method ID of GOAL mem-usage + #endif // JAK_GOAL_CONSTANTS_H diff --git a/common/type_system/Type.cpp b/common/type_system/Type.cpp index 3dd0fe1a8..0c68f48d8 100644 --- a/common/type_system/Type.cpp +++ b/common/type_system/Type.cpp @@ -108,6 +108,11 @@ std::string Type::get_name() const { } std::string Type::get_runtime_name() const { + if (!m_allow_in_runtime) { + fmt::print("[TypeSystem] Tried to use type {} as a runtime type, which is not allowed.\n", + get_name()); + throw std::runtime_error("get_runtime_name"); + } return m_runtime_name; } @@ -137,7 +142,7 @@ bool Type::is_equal(const Type& other) const { * parents. */ bool Type::has_parent() const { - return m_parent != "object" && !m_parent.empty(); + return m_name != "object" && !m_parent.empty(); } /*! @@ -218,52 +223,53 @@ std::string Type::print_method_info() const { } ///////////// -// NoneType +// NullType ///////////// -// Special Type representing nothing. -// it's an error to try to do anything with None. +// Special Type for both "none" and "_type_" types +// it's an error to try to do anything with Null. -NoneType::NoneType() : Type("", "none", false) {} +NullType::NullType(std::string name) : Type("", std::move(name), false) {} -bool NoneType::is_reference() const { - throw std::runtime_error("is_reference called on NoneType"); +bool NullType::is_reference() const { + throw std::runtime_error("is_reference called on NullType"); } -int NoneType::get_load_size() const { - throw std::runtime_error("get_load_size called on NoneType"); +int NullType::get_load_size() const { + throw std::runtime_error("get_load_size called on NullType"); } -bool NoneType::get_load_signed() const { - throw std::runtime_error("get_load_size called on NoneType"); +bool NullType::get_load_signed() const { + throw std::runtime_error("get_load_size called on NullType"); } -int NoneType::get_size_in_memory() const { - throw std::runtime_error("get_size_in_memory called on NoneType"); +int NullType::get_size_in_memory() const { + throw std::runtime_error("get_size_in_memory called on NullType"); } -RegKind NoneType::get_preferred_reg_kind() const { - throw std::runtime_error("get_preferred_reg_kind called on NoneType"); +RegKind NullType::get_preferred_reg_kind() const { + throw std::runtime_error("get_preferred_reg_kind called on NullType"); } -int NoneType::get_offset() const { +int NullType::get_offset() const { throw std::runtime_error("get_offset called on NoneType"); } -int NoneType::get_in_memory_alignment() const { - throw std::runtime_error("get_in_memory_alignment called on NoneType"); +int NullType::get_in_memory_alignment() const { + throw std::runtime_error("get_in_memory_alignment called on NullType"); } -int NoneType::get_inline_array_alignment() const { - throw std::runtime_error("get_inline_array_alignment called on NoneType"); +int NullType::get_inline_array_alignment() const { + throw std::runtime_error("get_inline_array_alignment called on NullType"); } -std::string NoneType::print() const { - return "none"; +std::string NullType::print() const { + return m_name; } -bool NoneType::operator==(const Type& other) const { - // there should be only one none type, so this is safe. +bool NullType::operator==(const Type& other) const { + // any redefinition by the user should be invalid, so this will always return false unless + // you're calling it on the same object. return this == &other; } @@ -484,7 +490,7 @@ void StructureType::override_size_in_memory(int size) { } int StructureType::get_offset() const { - return 0; + return m_offset; } int StructureType::get_in_memory_alignment() const { diff --git a/common/type_system/Type.h b/common/type_system/Type.h index 5ce8dd69d..a8c3474a3 100644 --- a/common/type_system/Type.h +++ b/common/type_system/Type.h @@ -68,6 +68,8 @@ class Type { const MethodInfo& add_new_method(const MethodInfo& info); std::string print_method_info() const; + void disallow_in_runtime() { m_allow_in_runtime = false; } + virtual ~Type() = default; protected: @@ -79,6 +81,7 @@ class Type { std::string m_parent; // the parent type (is empty for none and object) std::string m_name; + bool m_allow_in_runtime = true; std::string m_runtime_name; bool m_is_boxed = false; // does this have runtime type information? }; @@ -87,9 +90,9 @@ class Type { * Used only for "none" - this is a type that the compiler can use for "this has no value". * Attempting to do anything with a NoneType is an error. */ -class NoneType : public Type { +class NullType : public Type { public: - NoneType(); + NullType(std::string name); bool is_reference() const override; int get_load_size() const override; bool get_load_signed() const override; @@ -100,7 +103,7 @@ class NoneType : public Type { int get_in_memory_alignment() const override; std::string print() const override; bool operator==(const Type& other) const override; - ~NoneType() = default; + ~NullType() = default; }; /*! @@ -217,10 +220,12 @@ class StructureType : public ReferenceType { int get_in_memory_alignment() const override; int get_inline_array_alignment() const override; bool lookup_field(const std::string& name, Field* out); + bool is_dynamic() const { return m_dynamic; } ~StructureType() = default; protected: friend class TypeSystem; + void override_offset(int offset) { m_offset = offset; } void override_size_in_memory( int size); // only to be used for setting up weird types like "structure" void add_field(const Field& f, int new_size_in_mem) { @@ -234,6 +239,7 @@ class StructureType : public ReferenceType { bool m_dynamic = false; int m_size_in_mem = 0; bool m_pack = false; + int m_offset = 0; }; class BasicType : public StructureType { diff --git a/common/type_system/TypeSpec.cpp b/common/type_system/TypeSpec.cpp index f52cd558c..c78def6cd 100644 --- a/common/type_system/TypeSpec.cpp +++ b/common/type_system/TypeSpec.cpp @@ -34,4 +34,13 @@ bool TypeSpec::operator==(const TypeSpec& other) const { } return true; +} + +TypeSpec TypeSpec::substitute_for_method_call(const std::string& method_type) const { + TypeSpec result; + result.m_type = (m_type == "_type_") ? method_type : m_type; + for (const auto& x : m_arguments) { + result.m_arguments.push_back(x.substitute_for_method_call(method_type)); + } + return result; } \ No newline at end of file diff --git a/common/type_system/TypeSpec.h b/common/type_system/TypeSpec.h index cb42ba788..04a0431b0 100644 --- a/common/type_system/TypeSpec.h +++ b/common/type_system/TypeSpec.h @@ -33,12 +33,18 @@ class TypeSpec { void add_arg(const TypeSpec& ts) { m_arguments.push_back(ts); } const std::string base_type() const { return m_type; } + + bool has_single_arg() const { return m_arguments.size() == 1; } + const TypeSpec& get_single_arg() const { assert(m_arguments.size() == 1); return m_arguments.front(); } + TypeSpec substitute_for_method_call(const std::string& method_type) const; + private: + friend class TypeSystem; std::string m_type; std::vector m_arguments; }; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 76e4005c5..a96344339 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -5,8 +5,9 @@ #include TypeSystem::TypeSystem() { - // the "none" type is included by default. - add_type("none", std::make_unique()); + // the "none" and "_type_" types are included by default. + add_type("none", std::make_unique("none")); + add_type("_type_", std::make_unique("_type_")); } /*! @@ -39,7 +40,7 @@ Type* TypeSystem::add_type(const std::string& name, std::unique_ptr type) // newly defined! // none/object get to skip these checks because they are roots. - if (name != "object" && name != "none") { + if (name != "object" && name != "none" && name != "_type_") { if (m_forward_declared_types.find(type->get_parent()) != m_forward_declared_types.end()) { fmt::print("[TypeSystem] Type {} has incompletely defined parent {}\n", type->get_name(), type->get_parent()); @@ -83,11 +84,24 @@ std::string TypeSystem::get_runtime_type(const TypeSpec& ts) { DerefInfo TypeSystem::get_deref_info(const TypeSpec& ts) { DerefInfo info; + if (!ts.has_single_arg()) { + // not enough info. + info.can_deref = false; + return info; + } + // default to GPR info.reg = RegKind::GPR_64; info.mem_deref = true; if (ts.base_type() == "inline-array") { + auto result_type = lookup_type(ts.get_single_arg()); + auto result_structure_type = dynamic_cast(result_type); + if (!result_structure_type || result_structure_type->is_dynamic()) { + info.can_deref = false; + return info; + } + // it's an inline array of structures. We can "dereference". But really we don't do a memory // dereference, we just add stride*idx to the pointer. info.can_deref = true; // deref operators should work... @@ -95,7 +109,6 @@ DerefInfo TypeSystem::get_deref_info(const TypeSpec& ts) { info.result_type = ts.get_single_arg(); // what we're an inline-array of info.sign_extend = false; // not applicable anyway - auto result_type = lookup_type(info.result_type); if (result_type->is_reference()) { info.stride = align(result_type->get_size_in_memory(), result_type->get_inline_array_alignment()); @@ -111,11 +124,13 @@ DerefInfo TypeSystem::get_deref_info(const TypeSpec& ts) { // in memory, an array of pointers info.stride = POINTER_SIZE; info.sign_extend = false; + info.load_size = POINTER_SIZE; } else { // an array of values, which should be loaded in the correct way to the correct register info.stride = result_type->get_size_in_memory(); info.sign_extend = result_type->get_load_signed(); info.reg = result_type->get_preferred_reg_kind(); + info.load_size = result_type->get_load_size(); assert(result_type->get_size_in_memory() == result_type->get_load_size()); } } else { @@ -187,7 +202,7 @@ TypeSpec TypeSystem::make_inline_array_typespec(const TypeSpec& type) { * possible, don't store a Type* and store a TypeSpec instead. The TypeSpec can then be used with * lookup_type to find the most up-to-date type information. */ -Type* TypeSystem::lookup_type(const std::string& name) { +Type* TypeSystem::lookup_type(const std::string& name) const { auto kv = m_types.find(name); if (kv != m_types.end()) { return kv->second.get(); @@ -208,7 +223,7 @@ Type* TypeSystem::lookup_type(const std::string& name) { * possible, don't store a Type* and store a TypeSpec instead. The TypeSpec can then be used with * lookup_type to find the most up-to-date type information. */ -Type* TypeSystem::lookup_type(const TypeSpec& ts) { +Type* TypeSystem::lookup_type(const TypeSpec& ts) const { return lookup_type(ts.base_type()); } @@ -487,7 +502,7 @@ int TypeSystem::add_field_to_type(StructureType* type, * Add types which are built-in to GOAL. */ void TypeSystem::add_builtin_types() { - // some of the basic types having confusing circular dependencies, so this is done manually. + // some of the basic types have confusing circular dependencies, so this is done manually. // there are no inlined things so its ok to do some things out of order because the actual size // doesn't really matter. @@ -505,48 +520,53 @@ void TypeSystem::add_builtin_types() { auto link_block_type = add_builtin_basic("basic", "link-block"); auto kheap_type = add_builtin_structure("structure", "kheap"); auto array_type = add_builtin_basic("basic", "array"); - auto pair_type = add_builtin_structure("object", "pair"); + 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"); - auto pointer_type = add_builtin_value_type("object", "pointer", 4); - auto number_type = add_builtin_value_type("object", "number", 8); // sign extend? - auto float_type = add_builtin_value_type("number", "float", 4, false, false, RegKind::FLOAT); - auto integer_type = add_builtin_value_type("number", "integer", 8, false, false); // sign extend? - auto binteger_type = - add_builtin_value_type("integer", "binteger", 8, true, false); // sign extend? - auto sinteger_type = add_builtin_value_type("integer", "sinteger", 8, false, true); - auto int8_type = add_builtin_value_type("sinteger", "int8", 1, false, true); - auto int16_type = add_builtin_value_type("sinteger", "int16", 2, false, true); - auto int32_type = add_builtin_value_type("sinteger", "int32", 4, false, true); - auto int64_type = add_builtin_value_type("sinteger", "int64", 8, false, true); - auto int128_type = - add_builtin_value_type("sinteger", "int128", 16, false, true, RegKind::INT_128); - auto uinteger_type = add_builtin_value_type("integer", "uinteger", 8); - auto uint8_type = add_builtin_value_type("uinteger", "uint8", 1); - auto uint16_type = add_builtin_value_type("uinteger", "uint16", 2); - auto uint32_type = add_builtin_value_type("uinteger", "uint32", 4); - auto uint64_type = add_builtin_value_type("uinteger", "uint64", 81); - auto uint128_type = - add_builtin_value_type("uinteger", "uint128", 16, false, false, RegKind::INT_128); + add_builtin_value_type("object", "pointer", 4); + auto inline_array_type = add_builtin_value_type("object", "inline-array", 4); + inline_array_type->set_runtime_type("pointer"); + + add_builtin_value_type("object", "number", 8); // sign extend? + add_builtin_value_type("number", "float", 4, false, false, RegKind::FLOAT); + add_builtin_value_type("number", "integer", 8, false, false); // sign extend? + add_builtin_value_type("integer", "binteger", 8, true, false); // sign extend? + add_builtin_value_type("integer", "sinteger", 8, false, true); + add_builtin_value_type("sinteger", "int8", 1, false, true); + add_builtin_value_type("sinteger", "int16", 2, false, true); + add_builtin_value_type("sinteger", "int32", 4, false, true); + add_builtin_value_type("sinteger", "int64", 8, false, true); + add_builtin_value_type("sinteger", "int128", 16, false, true, RegKind::INT_128); + add_builtin_value_type("integer", "uinteger", 8); + 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", "uint128", 16, false, false, RegKind::INT_128); + + auto int_type = add_builtin_value_type("integer", "int", 8, false, true); + int_type->disallow_in_runtime(); + auto uint_type = add_builtin_value_type("uinteger", "uint", 8, false, false); + uint_type->disallow_in_runtime(); // Methods and Fields // OBJECT - add_method(obj_type, "new", make_function_typespec({"symbol", "type", "int32"}, "object")); - add_method(obj_type, "delete", make_function_typespec({"object"}, "none")); - add_method(obj_type, "print", make_function_typespec({"object"}, "object")); - add_method(obj_type, "inspect", make_function_typespec({"object"}, "object")); + add_method(obj_type, "new", make_function_typespec({"symbol", "type", "int32"}, "_type_")); + add_method(obj_type, "delete", make_function_typespec({"_type_"}, "none")); + add_method(obj_type, "print", make_function_typespec({"_type_"}, "_type_")); + add_method(obj_type, "inspect", make_function_typespec({"_type_"}, "_type_")); add_method(obj_type, "length", - make_function_typespec({"object"}, "int32")); // todo - this integer type? - add_method(obj_type, "asize-of", make_function_typespec({"object"}, "int32")); - add_method(obj_type, "copy", make_function_typespec({"object", "symbol"}, "object")); - add_method(obj_type, "relocate", make_function_typespec({"object", "int32"}, "object")); + make_function_typespec({"_type_"}, "int32")); // todo - this integer type? + add_method(obj_type, "asize-of", make_function_typespec({"_type_"}, "int32")); + 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, "mem-usage", - make_function_typespec({"object"}, "int32")); // todo - this is a guess. + make_function_typespec({"_type_"}, "int32")); // todo - this is a guess. // STRUCTURE // structure new doesn't support dynamic sizing, which is kinda weird - it grabs the size from @@ -590,6 +610,38 @@ void TypeSystem::add_builtin_types() { // VU FUNCTION // don't inherit + add_field_to_type(vu_function_type, "length", make_typespec("int32")); // todo integer type + add_field_to_type(vu_function_type, "origin", make_typespec("pointer")); // todo sign extend? + add_field_to_type(vu_function_type, "qlength", make_typespec("int32")); // todo integer type + + // link block + builtin_structure_inherit(link_block_type); + add_field_to_type(link_block_type, "allocated-length", + make_typespec("int32")); // todo integer type + add_field_to_type(link_block_type, "version", make_typespec("int32")); // todo integer type + // there's probably some dynamically sized stuff after this... + + // kheap + add_field_to_type(kheap_type, "base", make_typespec("pointer")); + add_field_to_type(kheap_type, "top", make_typespec("pointer")); + add_field_to_type(kheap_type, "current", make_typespec("pointer")); + add_field_to_type(kheap_type, "top-base", make_typespec("pointer")); + + // todo + (void)array_type; + + // pair + pair_type->override_offset(2); + add_field_to_type(pair_type, "car", make_typespec("object")); + 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; } /*! @@ -624,23 +676,6 @@ int TypeSystem::get_next_method_id(Type* type) { } } -/*! - * For debugging, todo remove. - */ -int TypeSystem::manual_add_field_to_type(StructureType* type, - const std::string& field_name, - const TypeSpec& field_type, - int offset, - int size, - int alignment) { - Field field(field_name, field_type); - field.set_alignment(alignment); - field.set_offset(offset); - int new_size = type->get_size_in_memory() + size; - type->add_field(field, new_size); - return offset; -} - /*! * Lookup a field of a type by name */ @@ -724,8 +759,9 @@ int TypeSystem::get_size_in_type(const Field& field) { * things in the wrong order. */ StructureType* TypeSystem::add_builtin_structure(const std::string& parent, - const std::string& type_name) { - add_type(type_name, std::make_unique(parent, type_name)); + const std::string& type_name, + bool boxed) { + add_type(type_name, std::make_unique(parent, type_name, boxed)); return get_type_of_type(type_name); } @@ -758,4 +794,172 @@ ValueType* TypeSystem::add_builtin_value_type(const std::string& parent, */ void TypeSystem::builtin_structure_inherit(StructureType* st) { st->inherit(get_type_of_type(st->get_parent())); +} + +/*! + * Main compile-time type check! + * @param expected - the expected type + * @param actual - the actual type (can be more specific) + * @param error_source_name - optional, can provide a name for where the error comes from + * @param print_on_error - print a message explaining the type error, if there is one + * @param throw_on_error - throw a std::runtime_error on failure if set. + * @return if the type check passes + */ +bool TypeSystem::typecheck(const TypeSpec& expected, + const TypeSpec& actual, + const std::string& error_source_name, + bool print_on_error, + bool throw_on_error) const { + bool success = true; + // first, typecheck the base types: + if (!typecheck_base_types(expected.base_type(), actual.base_type())) { + success = false; + } + + // next argument checks: + if (expected.m_arguments.size() == actual.m_arguments.size()) { + for (size_t i = 0; i < expected.m_arguments.size(); i++) { + // don't print/throw because the error would be confusing. Better to fail only the + // outer most check and print a single error message. + if (!typecheck(expected.m_arguments[i], actual.m_arguments[i], "", false, false)) { + success = false; + break; + } + } + } else { + // different sizes of arguments. + if (expected.m_arguments.empty()) { + // we expect zero arguments, but got some. The actual type is more specific, so this is fine. + } else { + // different sizes, and we expected arguments. No good! + success = false; + } + } + + if (!success) { + if (print_on_error) { + if (error_source_name.empty()) { + fmt::print("[TypeSystem] Got type \"{}\" when expecting \"{}\"\n", actual.print(), + expected.print()); + } else { + fmt::print("[TypeSystem] For {}, got type \"{}\" when expecting \"{}\"\n", + error_source_name, actual.print(), expected.print()); + } + } + + if (throw_on_error) { + throw std::runtime_error("typecheck failed"); + } + } + + return success; +} + +/*! + * Is actual of type expected? For base types. + */ +bool TypeSystem::typecheck_base_types(const std::string& expected, + const std::string& actual) const { + // just to make sure it exists. (note - could there be a case when it just has to be forward + // declared, but not defined?) + lookup_type(expected); + + if (expected == actual) { + lookup_type(actual); // make sure it exists + return true; + } + + std::string actual_name = actual; + auto actual_type = lookup_type(actual_name); + while (actual_type->has_parent()) { + actual_name = actual_type->get_parent(); + actual_type = lookup_type(actual_name); + + if (expected == actual_name) { + return true; + } + } + + return false; +} + +/*! + * Get a path from type to object. + */ +std::vector TypeSystem::get_path_up_tree(const std::string& type) { + auto parent = lookup_type(type)->get_parent(); + std::vector path = {type}; + path.push_back(parent); + auto parent_type = lookup_type(parent); + + while (parent_type->has_parent()) { + parent = parent_type->get_parent(); + parent_type = lookup_type(parent); + path.push_back(parent); + } + + return path; +} + +/*! + * Lowest common ancestor of two base types. + */ +std::string TypeSystem::lca_base(const std::string& a, const std::string& b) { + if (a == b) { + return a; + } + + auto a_up = get_path_up_tree(a); + auto b_up = get_path_up_tree(b); + + int ai = a_up.size() - 1; + int bi = b_up.size() - 1; + + std::string* result = nullptr; + while (ai >= 0 && bi >= 0) { + if (a_up.at(ai) == b_up.at(bi)) { + result = &a_up.at(ai); + } else { + break; + } + ai--; + bi--; + } + + assert(result); + return *result; +} + +/*! + * Lowest common ancestor of two typespecs. Will recursively apply to arguments, if compatible. + * Otherwise arguments are stripped off. + * In a situation like lca("(a b)", "(c d)"), the result will be + * (lca(a, b) lca(b, d)). + */ +TypeSpec TypeSystem::lowest_common_ancestor(const TypeSpec& a, const TypeSpec& b) { + auto result = make_typespec(lca_base(a.base_type(), b.base_type())); + if (!a.m_arguments.empty() && !b.m_arguments.empty() && + a.m_arguments.size() == b.m_arguments.size()) { + // recursively add arguments + for (size_t i = 0; i < a.m_arguments.size(); i++) { + result.add_arg(lowest_common_ancestor(a.m_arguments.at(i), b.m_arguments.at(i))); + } + } + return result; +} + +/*! + * Lowest common ancestor of multiple (or at least one) type. + */ +TypeSpec TypeSystem::lowest_common_ancestor(const std::vector& types) { + assert(!types.empty()); + if (types.size() == 1) { + return types.front(); + } + + auto result = lowest_common_ancestor(types.at(0), types.at(1)); + for (size_t i = 2; i < types.size(); i++) { + result = lowest_common_ancestor(result, types.at(i)); + } + return result; } \ No newline at end of file diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index 71a865588..722ca515a 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -23,6 +23,7 @@ struct DerefInfo { bool sign_extend = false; RegKind reg = RegKind::INVALID; int stride = -1; + int load_size = -1; TypeSpec result_type; }; @@ -45,8 +46,8 @@ class TypeSystem { TypeSpec make_inline_array_typespec(const std::string& type); TypeSpec make_inline_array_typespec(const TypeSpec& type); - Type* lookup_type(const TypeSpec& ts); - Type* lookup_type(const std::string& name); + Type* lookup_type(const TypeSpec& ts) const; + Type* lookup_type(const std::string& name) const; MethodInfo add_method(Type* type, const std::string& method_name, const TypeSpec& ts); MethodInfo add_new_method(Type* type, const TypeSpec& ts); @@ -67,6 +68,12 @@ class TypeSystem { void add_builtin_types(); std::string print_all_type_information() const; + bool typecheck(const TypeSpec& expected, + const TypeSpec& actual, + const std::string& error_source_name = "", + bool print_on_error = true, + bool throw_on_error = true) const; + std::vector get_path_up_tree(const std::string& type); /*! * Get a type by name and cast to a child class of Type*. Must succeed. @@ -81,19 +88,19 @@ class TypeSystem { return result; } + TypeSpec lowest_common_ancestor(const TypeSpec& a, const TypeSpec& b); + TypeSpec lowest_common_ancestor(const std::vector& types); + 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_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); - int manual_add_field_to_type(StructureType* type, - const std::string& field_name, - const TypeSpec& field_type, - int offset, - int size, - int alignment); - - StructureType* add_builtin_structure(const std::string& parent, const std::string& type_name); + StructureType* add_builtin_structure(const std::string& parent, + const std::string& type_name, + bool boxed = false); BasicType* add_builtin_basic(const std::string& parent, const std::string& type_name); ValueType* add_builtin_value_type(const std::string& parent, const std::string& type_name, @@ -104,7 +111,6 @@ class TypeSystem { void builtin_structure_inherit(StructureType* st); std::unordered_map> m_types; - std::unordered_map m_global_types; std::unordered_set m_forward_declared_types; std::vector> m_old_types; diff --git a/doc/type_system.md b/doc/type_system.md index bbc7a5a2e..50d87924c 100644 --- a/doc/type_system.md +++ b/doc/type_system.md @@ -6,7 +6,7 @@ This document explains the GOAL type system. The GOAL type system supports runt Some objects have runtime type information, and others don't. Objects which have runtime type information can have their type identified at runtime, and are called "boxed objects". Objects without runtime type information are called "unboxed objects". An unboxed object cannot reliably be detected as a unboxed object - you can't write a function that takes an arbitrary object and tells you if its boxed or not. However, boxed objects can always be recognized as boxed. - All types have a parent type, and all types descend from the parent type `object`, except for the special type `none`. The `none` type doesn't exist in the runtime and is used to represent an invalid value that the compiler should not use. For example, the return type of a function which doesn't return anything is `none`, and attempting to use this value should cause an error. + All types have a parent type, and all types descend from the parent type `object`, except for the special type `none` (and maybe `_type_`, but more on this later). The `none` type doesn't exist in the runtime and is used to represent an invalid value that the compiler should not use. For example, the return type of a function which doesn't return anything is `none`, and attempting to use this value should cause an error. Here are some important special types: - `object` - the parent of all types @@ -15,7 +15,7 @@ This document explains the GOAL type system. The GOAL type system supports runt All types have methods. Objects have access to all of their parents methods, and may override parent methods. All types have these 9 methods: - - `new` - like a constructor, returns a new object. It's not used in all cases, and on all types, and needs more documentation. + - `new` - like a constructor, returns a new object. It's not used in all cases, and on all types, and needs more documentation on when specifically it is used. - `delete` - basically unused, but like a destructor. Often calls `kfree`, which does nothing. - `print` - prints a short, one line representation of the object to the `PrintBuffer` - `inspect` - prints a multi-line description of the object to the `PrintBuffer`. Usually auto-generated by the compiler and prints out the name and value of each field. @@ -25,7 +25,7 @@ This document explains the GOAL type system. The GOAL type system supports runt - `relocate` - Some GOAL objects will be moved in memory by the kernel as part of the compacting actor heap system. After being moved, the `relocate` method will be called with the offset of the move, and the object should fix up any internal pointers which may point to the old location. It's also called on v2 objects loaded by the linker when they are first loaded into memory. - `memusage` - Not understood yet, but probably returns how much memory in bytes the object uses. Not supported by all objects. -Usually a method which overrides a parent method will have the same argument and return types. The only exception is `new` methods, which can have different argument/return types from the parent. +Usually a method which overrides a parent method must have the same argument and return types. The only exception is `new` methods, which can have different argument/return types from the parent. (Dee the later section on `_type_` for another exception) The compiler's implementation for calling a method is: - Is the type a basic? @@ -118,9 +118,8 @@ There's a single type system library, located in `common/type_system`. It will The main features are: - `TypeSystem` stores all type information and provides a convenient way to add new types or request information about existing types. -- `Type` a GOAL Type. A `Type` is identified by a single unique string. Examples: `function`, `string`, `vector3h`. +- `Type` information about a GOAL Type. A "base GOAL type" is identified by a single unique string. Examples: `function`, `string`, `vector3h`. - `TypeSpec` a way to specify either `Type` or a "compound type". Compound types are used to create types which represent specific function types (function which takes two integer arguments and returns a string), or specific pointer/array types (pointer to an integer). These can be represented as (possibly nested) lists, like `(pointer integer)` or `(function (integer integer) string)`. -- `FunctionSpec` (unimplemented) - contains a `TypeSpec` plus some additional information about a function (like if it is a global function, a method, names of the arguments, etc) - Type Checking for compiler - Parsing of type definitions for compiler - Lowest common ancestor implementation for compiler to figure out return types for branching forms. @@ -187,33 +186,14 @@ Method System All type definitions should also define all the methods, in the order they appear in the vtable. I suspect GOAL had this as well because the method ordering otherwise seems random, and in some cases impossible to get right unless (at least) the number of methods was specified in the type declaration. - Todo ---------- -- [x] Difference between "runtime" and "compile time" types? - - [ ] `inline-array` and `pointer` -- [x] Arrays which aren't `array`s and aren't fields. -- [x] `lookup_field_info` (returning the correct field type for arrays/dynamics, info about how to deref) -- [x] `deref_info` -- [ ] Finish builtin types -- [ ] Tests for... - - [ ] Builtin types - - [ ] Methods - - [ ] Multiple definition checks - - [ ] Deref - - [ ] Array access - - [ ] Field creation -- [ ] Support for `_type_` / method specific stuff. (maybe this should live outside the type system?) -- [ ] Ability to export type in `deftype` form. -- [x] Multiple definition checks -- [ ] Reverse field (offset/deref to fields) -- [ ] Type Checking -- [ ] Function Specs -- [ ] Lowest Common Ancestor -- [x] Document `:inline`, `:dynamic:` and field arrays. -- [x] Document alignment rules -- [ ] Structure type with itself as a field -- [ ] Ability to read a `deftype` form. - - [ ] In the decompiler - - [ ] In the compiler, with the ability to do constant propagation and put things like `(+ 1 2)` or `MY_CONSTANT` as compile-time array size constants by providing a function evaluating an `Object` to an `int`. -- [ ] Bitfield types \ No newline at end of file +----- +- [ ] Kernel types that are built-in +- [ ] Signed/unsigned for a few built-in type fields +- [ ] Tests for field placement logic (probably a full compiler test?) +- [ ] Bitfield types +- [ ] Type redefinition tests (these are a pain and probably useless, might just wait for full compiler tests?) +- [ ] Stuff for decompiler + - [ ] What field is here? + - [ ] Export all deftypes + \ No newline at end of file diff --git a/game/kernel/klink.cpp b/game/kernel/klink.cpp index 38eba8ee7..597813140 100644 --- a/game/kernel/klink.cpp +++ b/game/kernel/klink.cpp @@ -14,6 +14,7 @@ #include "kboot.h" #include "kprint.h" #include "common/symbols.h" +#include "common/goal_constants.h" namespace { // turn on printf's for debugging linking issues. diff --git a/game/kernel/kprint.cpp b/game/kernel/kprint.cpp index 59b086a66..0fde516a4 100644 --- a/game/kernel/kprint.cpp +++ b/game/kernel/kprint.cpp @@ -8,6 +8,7 @@ #include #include +#include "common/goal_constants.h" #include "common/common_types.h" #include "kprint.h" #include "kmachine.h" @@ -880,7 +881,7 @@ s32 format_impl(uint64_t* args) { if (sym.offset) { Ptr type = *sym.cast>(); if (type.offset) { - call_method_of_type(in, type, GOAL_PRINT_FUNC); + call_method_of_type(in, type, GOAL_PRINT_METHOD); } } else { throw std::runtime_error("failed to find symbol in format!"); @@ -901,7 +902,7 @@ s32 format_impl(uint64_t* args) { if (sym.offset) { Ptr type = *sym.cast>(); if (type.offset) { - call_method_of_type(in, type, GOAL_INSPECT_FUNC); + call_method_of_type(in, type, GOAL_INSPECT_METHOD); } } else { throw std::runtime_error("failed to find symbol in format!"); diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index eff441ee0..1fb6a379b 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -18,6 +18,7 @@ #include "klink.h" #include "common/symbols.h" #include "common/versions.h" +#include "common/goal_constants.h" //! Controls link mode when EnableMethodSet = 0, MasterDebug = 1, DiskBoot = 0. Will enable a //! warning message if EnableMethodSet = 1 @@ -986,7 +987,7 @@ u64 print_object(u32 obj) { } else if ((obj & OFFSET_MASK) == PAIR_OFFSET) { return print_pair(obj); } else if ((obj & OFFSET_MASK) == BASIC_OFFSET) { - return call_method_of_type(obj, Ptr(*Ptr(obj - 4)), GOAL_PRINT_FUNC); + return call_method_of_type(obj, Ptr(*Ptr(obj - 4)), GOAL_PRINT_METHOD); } else { cprintf("#", obj & OFFSET_MASK, obj); } @@ -1194,7 +1195,7 @@ u64 copy_structure(u32 it, u32 unknown) { u64 copy_basic(u32 obj, u32 heap) { // determine size of basic. We call a method instead of using asize_of_basic in case the type has // overridden the default asize_of method. - u32 size = call_method_of_type(obj, Ptr(*Ptr(obj - BASIC_OFFSET)), GOAL_ASIZE_FUNC); + u32 size = call_method_of_type(obj, Ptr(*Ptr(obj - BASIC_OFFSET)), GOAL_ASIZE_METHOD); u32 result; if (*Ptr(heap - 4) == *(s7 + FIX_SYM_SYMBOL_TYPE)) { @@ -1224,7 +1225,8 @@ u64 inspect_object(u32 obj) { } else if ((obj & OFFSET_MASK) == PAIR_OFFSET) { return inspect_pair(obj); } else if ((obj & OFFSET_MASK) == BASIC_OFFSET) { - return call_method_of_type(obj, Ptr(*Ptr(obj - BASIC_OFFSET)), GOAL_INSPECT_FUNC); + return call_method_of_type(obj, Ptr(*Ptr(obj - BASIC_OFFSET)), + GOAL_INSPECT_METHOD); } else { cprintf("#", obj & OFFSET_MASK, obj); } diff --git a/game/kernel/kscheme.h b/game/kernel/kscheme.h index 38c8df351..5fa1bbf33 100644 --- a/game/kernel/kscheme.h +++ b/game/kernel/kscheme.h @@ -18,23 +18,12 @@ extern Ptr SymbolTable2; extern Ptr LastSymbol; constexpr s32 GOAL_MAX_SYMBOLS = 0x2000; -constexpr s32 BINTEGER_OFFSET = 0; -constexpr s32 PAIR_OFFSET = 2; -constexpr s32 BASIC_OFFSET = 4; + constexpr s32 SYM_INFO_OFFSET = 0xff34; constexpr u32 EMPTY_HASH = 0x8454B6E6; constexpr u32 OFFSET_MASK = 7; constexpr u32 CRC_POLY = 0x04c11db7; -constexpr u32 GOAL_NEW_FUNC = 0; // method ID of GOAL new -constexpr u32 GOAL_DEL_FUNC = 1; // method ID of GOAL delete -constexpr u32 GOAL_PRINT_FUNC = 2; // method ID of GOAL print -constexpr u32 GOAL_INSPECT_FUNC = 3; // method ID of GOAL inspect -constexpr u32 GOAL_LENGTH_FUNC = 4; // method ID of GOAL length -constexpr u32 GOAL_ASIZE_FUNC = 5; // method ID of GOAL size -constexpr u32 GOAL_COPY_FUNC = 6; // method ID of GOAL copy -constexpr u32 GOAL_RELOC_FUNC = 7; // method ID of GOAL relocate - constexpr u32 DEFAULT_METHOD_COUNT = 12; constexpr u32 FALLBACK_UNKNOWN_METHOD_COUNT = 44; diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 562b73af6..1ec43eb75 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(goos) add_subdirectory(listener) add_subdirectory(emitter) -add_executable(goalc main.cpp) +add_executable(goalc main.cpp + compiler/Compiler.cpp) -target_link_libraries(goalc util goos) \ No newline at end of file +target_link_libraries(goalc util goos type_system) \ No newline at end of file diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp new file mode 100644 index 000000000..b1d4ba135 --- /dev/null +++ b/goalc/compiler/Compiler.cpp @@ -0,0 +1 @@ +#include "Compiler.h" diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h new file mode 100644 index 000000000..c9565aae1 --- /dev/null +++ b/goalc/compiler/Compiler.h @@ -0,0 +1,12 @@ +#ifndef JAK_COMPILER_H +#define JAK_COMPILER_H + +#include "common/type_system/TypeSystem.h" + +class Compiler { + public: + private: + TypeSystem m_ts; +}; + +#endif // JAK_COMPILER_H diff --git a/goalc/listener/CMakeLists.txt b/goalc/listener/CMakeLists.txt index 470fe89d3..f978b4d63 100644 --- a/goalc/listener/CMakeLists.txt +++ b/goalc/listener/CMakeLists.txt @@ -1,2 +1,2 @@ add_library(listener SHARED - Listener.cpp Deci2Server.cpp Deci2Server.h) + Listener.cpp) diff --git a/goalc/listener/Deci2Server.cpp b/goalc/listener/Deci2Server.cpp deleted file mode 100644 index 9b2a29f94..000000000 --- a/goalc/listener/Deci2Server.cpp +++ /dev/null @@ -1,264 +0,0 @@ -/*! - * @file Deci2Server.cpp - * Basic implementation of a DECI2 server. - * Works with deci2.cpp (sceDeci2) to implement the networking on target - */ - -#include -#include -#include -#include -#include -#include - -#include "common/listener_common.h" -#include "common/versions.h" -#include "Deci2Server.h" - -Deci2Server::Deci2Server(std::function shutdown_callback) { - buffer = new char[BUFFER_SIZE]; - want_exit = std::move(shutdown_callback); -} - -Deci2Server::~Deci2Server() { - // if accept thread is running, kill it - if (accept_thread_running) { - kill_accept_thread = true; - accept_thread.join(); - accept_thread_running = false; - } - - delete[] buffer; - - if (server_fd >= 0) { - close(server_fd); - } - - if (new_sock >= 0) { - close(new_sock); - } -} - -/*! - * Start waiting for the Listener to connect - */ -bool Deci2Server::init() { - server_fd = socket(AF_INET, SOCK_STREAM, 0); - if (server_fd < 0) { - server_fd = -1; - return false; - } - - int opt = 1; - if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { - printf("[Deci2Server] Failed to setsockopt 1\n"); - close(server_fd); - server_fd = -1; - return false; - } - - int one = 1; - if (setsockopt(server_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one))) { - printf("[Deci2Server] Failed to setsockopt 2\n"); - close(server_fd); - server_fd = -1; - return false; - } - - timeval timeout = {}; - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - - if (setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { - printf("[Deci2Server] Failed to setsockopt 3\n"); - close(server_fd); - server_fd = -1; - return false; - } - - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - addr.sin_port = htons(DECI2_PORT); - - if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { - printf("[Deci2Server] Failed to bind\n"); - close(server_fd); - server_fd = -1; - return false; - } - - if (listen(server_fd, 0) < 0) { - printf("[Deci2Server] Failed to listen\n"); - close(server_fd); - server_fd = -1; - return false; - } - - server_initialized = true; - accept_thread_running = true; - kill_accept_thread = false; - accept_thread = std::thread(&Deci2Server::accept_thread_func, this); - return true; -} - -/*! - * Return true if the listener is connected. - */ -bool Deci2Server::check_for_listener() { - if (server_connected) { - if (accept_thread_running) { - accept_thread.join(); - accept_thread_running = false; - } - return true; - } else { - return false; - } -} - -/*! - * Send data from buffer. User must provide appropriate headers. - */ -void Deci2Server::send_data(void* buf, u16 len) { - lock(); - if (!server_connected) { - printf("[DECI2] send while not connected, not sending!\n"); - } else { - uint16_t prog = 0; - while (prog < len) { - auto wrote = write(new_sock, (char*)(buf) + prog, len - prog); - prog += wrote; - if (!server_connected || want_exit()) { - unlock(); - return; - } - } - } - unlock(); -} - -/*! - * Lock the DECI mutex. Should be done before modifying protocols. - */ -void Deci2Server::lock() { - deci_mutex.lock(); -} - -/*! - * Unlock the DECI mutex. Should be done after modifying protocols. - */ -void Deci2Server::unlock() { - deci_mutex.unlock(); -} - -/*! - * Wait for protocols to become ready. - * This avoids the case where we receive messages before protocol handlers are set up. - */ -void Deci2Server::wait_for_protos_ready() { - if (protocols_ready) - return; - std::unique_lock lk(deci_mutex); - cv.wait(lk, [&] { return protocols_ready; }); -} - -/*! - * Inform server that protocol handlers are ready. - * Will unblock wait_for_protos_ready and incoming messages will be dispatched to these - * protocols. You can change the protocol handlers, but you should lock the mutex before - * doing so. - */ -void Deci2Server::send_proto_ready(Deci2Driver* drivers, int* driver_count) { - lock(); - d2_drivers = drivers; - d2_driver_count = driver_count; - protocols_ready = true; - unlock(); - cv.notify_all(); -} - -void Deci2Server::run() { - int desired_size = (int)sizeof(Deci2Header); - int got = 0; - - while (got < desired_size) { - assert(got + desired_size < BUFFER_SIZE); - auto x = read(new_sock, buffer + got, desired_size - got); - if (want_exit()) { - return; - } - got += x > 0 ? x : 0; - } - - auto* hdr = (Deci2Header*)(buffer); - printf("[DECI2] Got message:\n"); - printf(" %d %d 0x%x %c -> %c\n", hdr->len, hdr->rsvd, hdr->proto, hdr->src, hdr->dst); - - hdr->rsvd = got; - - // see what protocol we got: - lock(); - - int handler = -1; - for (int i = 0; i < *d2_driver_count; i++) { - auto& prot = d2_drivers[i]; - if (prot.active && prot.protocol) { - if (handler != -1) { - printf("[DECI2] Warning: more than on protocol handler for this message!\n"); - } - handler = i; - } - } - - if (handler == -1) { - printf("[DECI2] Warning: no handler for this message, ignoring...\n"); - unlock(); - return; - // throw std::runtime_error("no handler!"); - } - - auto& driver = d2_drivers[handler]; - - int sent_to_program = 0; - while (!want_exit() && (hdr->rsvd < hdr->len || sent_to_program < hdr->rsvd)) { - // send what we have to the program - if (sent_to_program < hdr->rsvd) { - // driver.next_recv_size = 0; - // driver.next_recv = nullptr; - driver.recv_buffer = buffer + sent_to_program; - driver.available_to_receive = hdr->rsvd - sent_to_program; - (driver.handler)(DECI2_READ, driver.available_to_receive, driver.opt); - // memcpy(driver.next_recv, buffer + sent_to_program, driver.next_recv_size); - sent_to_program += driver.recv_size; - } - - // receive from network - if (hdr->rsvd < hdr->len) { - auto x = read(new_sock, buffer + hdr->rsvd, hdr->len - hdr->rsvd); - if (want_exit()) { - return; - } - got += x > 0 ? x : 0; - hdr->rsvd += got; - } - } - - (driver.handler)(DECI2_READDONE, 0, driver.opt); - unlock(); -} - -/*! - * Background thread for waiting for the listener. - */ -void Deci2Server::accept_thread_func() { - socklen_t l = sizeof(addr); - while (!kill_accept_thread) { - new_sock = accept(server_fd, (sockaddr*)&addr, &l); - if (new_sock >= 0) { - u32 versions[2] = {versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR}; - send(new_sock, &versions, 8, 0); // todo, check result? - server_connected = true; - return; - } - } -} diff --git a/goalc/listener/Deci2Server.h b/goalc/listener/Deci2Server.h deleted file mode 100644 index b9ca8ce7d..000000000 --- a/goalc/listener/Deci2Server.h +++ /dev/null @@ -1,53 +0,0 @@ -/*! - * @file Deci2Server.h - * Basic implementation of a DECI2 server. - * Works with deci2.cpp (sceDeci2) to implement the networking on target - */ - -#ifndef JAK1_DECI2SERVER_H -#define JAK1_DECI2SERVER_H - -#include -#include -#include -#include -#include -#include "game/system/deci_common.h" // todo, move me! - -class Deci2Server { - public: - static constexpr int BUFFER_SIZE = 32 * 1024 * 1024; - Deci2Server(std::function shutdown_callback); - ~Deci2Server(); - bool init(); - bool check_for_listener(); - void send_data(void* buf, u16 len); - - void lock(); - void unlock(); - void wait_for_protos_ready(); - void send_proto_ready(Deci2Driver* drivers, int* driver_count); - - void run(); - - private: - void accept_thread_func(); - bool kill_accept_thread = false; - char* buffer = nullptr; - int server_fd; - sockaddr_in addr; - int new_sock; - bool server_initialized = false; - bool accept_thread_running = false; - bool server_connected = false; - std::function want_exit; - std::thread accept_thread; - - std::condition_variable cv; - bool protocols_ready = false; - std::mutex deci_mutex; - Deci2Driver* d2_drivers = nullptr; - int* d2_driver_count = nullptr; -}; - -#endif // JAK1_DECI2SERVER_H diff --git a/test.sh b/test.sh index 4d275a0f8..3cff7a597 100755 --- a/test.sh +++ b/test.sh @@ -4,4 +4,4 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export NEXT_DIR=$DIR -$DIR/build/test/goalc-test --gtest_color=yes --gtest_brief=1 "$@" \ No newline at end of file +$DIR/build/test/goalc-test --gtest_color=yes "$@" \ No newline at end of file diff --git a/test/test_kernel.cpp b/test/test_kernel.cpp index a5e9a1482..658f357e8 100644 --- a/test/test_kernel.cpp +++ b/test/test_kernel.cpp @@ -3,6 +3,7 @@ #include #include "gtest/gtest.h" #include "common/symbols.h" +#include "common/goal_constants.h" #include "game/kernel/fileio.h" #include "game/kernel/kboot.h" #include "game/kernel/kprint.h" diff --git a/test/test_listener_deci2.cpp b/test/test_listener_deci2.cpp index d5730ded9..05e8c843d 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -1,6 +1,6 @@ #include "gtest/gtest.h" #include "goalc/listener/Listener.h" -#include "goalc/listener/Deci2Server.h" +#include "game/system/Deci2Server.h" using namespace listener; @@ -34,6 +34,11 @@ TEST(Listener, DeciInit) { */ TEST(Listener, ListenToNothing) { Listener l; + if (l.connect_to_target()) { + printf( + "~~~~~~ Test connected to a runtime when there shouldn't be anything running! Check that " + "you don't have gk running in the background!\n"); + } EXPECT_FALSE(l.connect_to_target()); l.disconnect(); } @@ -89,7 +94,7 @@ TEST(Listener, ListenerThenDeci) { EXPECT_FALSE(s.check_for_listener()); EXPECT_TRUE(l.connect_to_target()); while (!s.check_for_listener()) { - printf("...\n"); + // printf("...\n"); } } } diff --git a/test/test_type_system.cpp b/test/test_type_system.cpp index c9d9ceba3..784aa130f 100644 --- a/test/test_type_system.cpp +++ b/test/test_type_system.cpp @@ -3,22 +3,316 @@ #include "third-party/fmt/core.h" TEST(TypeSystem, Construction) { + // test that we can add all builtin types without any type errors TypeSystem ts; ts.add_builtin_types(); - fmt::print("{}", ts.print_all_type_information()); + + // useful for debugging. + // fmt::print("{}", ts.print_all_type_information()); } TEST(TypeSystem, DefaultMethods) { TypeSystem ts; ts.add_builtin_types(); - ts.assert_method_id("object", "new", 0); - ts.assert_method_id("object", "delete", 1); - ts.assert_method_id("object", "print", 2); - ts.assert_method_id("object", "inspect", 3); - ts.assert_method_id("object", "length", 4); - ts.assert_method_id("object", "asize-of", 5); - ts.assert_method_id("object", "copy", 6); - ts.assert_method_id("object", "relocate", 7); - ts.assert_method_id("object", "mem-usage", 8); -} \ No newline at end of file + // check that default methods have the right ID's used by the kernel + ts.assert_method_id("object", "new", GOAL_NEW_METHOD); + ts.assert_method_id("object", "delete", GOAL_DEL_METHOD); + ts.assert_method_id("object", "print", GOAL_PRINT_METHOD); + ts.assert_method_id("object", "inspect", GOAL_INSPECT_METHOD); + ts.assert_method_id("object", "length", GOAL_LENGTH_METHOD); + ts.assert_method_id("object", "asize-of", GOAL_ASIZE_METHOD); + ts.assert_method_id("object", "copy", GOAL_COPY_METHOD); + ts.assert_method_id("object", "relocate", GOAL_RELOC_METHOD); + ts.assert_method_id("object", "mem-usage", GOAL_MEMUSAGE_METHOD); + + // check that they are inherited. + ts.assert_method_id("function", "new", GOAL_NEW_METHOD); + ts.assert_method_id("function", "delete", GOAL_DEL_METHOD); + ts.assert_method_id("function", "print", GOAL_PRINT_METHOD); + ts.assert_method_id("function", "inspect", GOAL_INSPECT_METHOD); + ts.assert_method_id("function", "length", GOAL_LENGTH_METHOD); + ts.assert_method_id("function", "asize-of", GOAL_ASIZE_METHOD); + ts.assert_method_id("function", "copy", GOAL_COPY_METHOD); + ts.assert_method_id("function", "relocate", GOAL_RELOC_METHOD); + ts.assert_method_id("function", "mem-usage", GOAL_MEMUSAGE_METHOD); +} + +TEST(TypeSystem, TypeSpec) { + TypeSystem ts; + ts.add_builtin_types(); + + // try some simple typespecs + auto string_typespec = ts.make_typespec("string"); + auto function_typespec = ts.make_typespec("function"); + EXPECT_EQ(string_typespec.print(), "string"); + EXPECT_EQ(string_typespec.base_type(), "string"); + EXPECT_TRUE(function_typespec == function_typespec); + EXPECT_FALSE(function_typespec == string_typespec); + + // try some pointer typespecs + auto pointer_function_typespec = ts.make_pointer_typespec("function"); + EXPECT_EQ(pointer_function_typespec.print(), "(pointer function)"); + EXPECT_EQ(pointer_function_typespec.get_single_arg(), ts.make_typespec("function")); + EXPECT_EQ(pointer_function_typespec.base_type(), "pointer"); + + // try some function typespecs + auto test_function_typespec = ts.make_function_typespec({"string", "symbol"}, "integer"); + EXPECT_EQ(test_function_typespec.base_type(), "function"); + EXPECT_EQ(test_function_typespec.print(), "(function string symbol integer)"); + + // try the none typespec + EXPECT_EQ(ts.make_typespec("none").base_type(), "none"); +} + +TEST(TypeSystem, TypeSpecEquality) { + TypeSystem ts; + ts.add_builtin_types(); + + auto pointer_to_function = ts.make_pointer_typespec("function"); + auto ia_to_function = ts.make_inline_array_typespec("function"); + auto pointer_to_string = ts.make_pointer_typespec("string"); + + EXPECT_TRUE(pointer_to_function == pointer_to_function); + EXPECT_FALSE(pointer_to_function == ia_to_function); + EXPECT_FALSE(pointer_to_string == pointer_to_function); +} + +TEST(TypeSystem, RuntimeTypes) { + TypeSystem ts; + ts.add_builtin_types(); + + // pointers and inline arrays should all become simple pointers + EXPECT_EQ(ts.get_runtime_type(ts.make_typespec("pointer")), "pointer"); + EXPECT_EQ(ts.get_runtime_type(ts.make_typespec("inline-array")), "pointer"); + EXPECT_EQ(ts.get_runtime_type(ts.make_pointer_typespec("function")), "pointer"); + EXPECT_EQ(ts.get_runtime_type(ts.make_inline_array_typespec("function")), "pointer"); + + // functions of any kind should become function + EXPECT_EQ(ts.get_runtime_type(ts.make_function_typespec({"integer", "string"}, "symbol")), + "function"); +} + +TEST(TypeSystem, ForwardDeclaration) { + TypeSystem ts; + ts.add_builtin_types(); + + // before forward declaring, lookup and creating a typespec should fail + EXPECT_ANY_THROW(ts.lookup_type("test-type")); + EXPECT_ANY_THROW(ts.make_typespec("test-type")); + + // after forward declaring, we should be able to create typespec, but not do a full lookup + ts.forward_declare_type("test-type"); + + EXPECT_TRUE(ts.make_typespec("test-type").print() == "test-type"); + EXPECT_ANY_THROW(ts.lookup_type("test-type")); +} + +TEST(TypeSystem, DerefInfoNoLoadInfoOrStride) { + // test the parts of deref info, other than the part where it tells you how to load or the stride. + TypeSystem ts; + ts.add_builtin_types(); + + // can't dereference a non-pointer + EXPECT_FALSE(ts.get_deref_info(ts.make_typespec("string")).can_deref); + // can't deref a pointer with no type + EXPECT_FALSE(ts.get_deref_info(ts.make_typespec("pointer")).can_deref); + EXPECT_FALSE(ts.get_deref_info(ts.make_typespec("inline-array")).can_deref); + + // test pointer to reference type + auto type_spec = + ts.make_pointer_typespec(ts.make_function_typespec({"string", "symbol"}, "int32")); + auto info = ts.get_deref_info(type_spec); + EXPECT_TRUE(info.can_deref); + EXPECT_TRUE(info.mem_deref); + EXPECT_FALSE(info.sign_extend); // it's a memory address being loaded + EXPECT_EQ(info.reg, RegKind::GPR_64); + EXPECT_EQ(info.stride, 4); + EXPECT_EQ(info.result_type.print(), "(function string symbol int32)"); + EXPECT_EQ(info.load_size, 4); + + // test pointer to value type + type_spec = ts.make_pointer_typespec("int64"); + info = ts.get_deref_info(type_spec); + EXPECT_TRUE(info.can_deref); + EXPECT_TRUE(info.mem_deref); + EXPECT_EQ(info.load_size, 8); + EXPECT_EQ(info.stride, 8); + EXPECT_EQ(info.sign_extend, true); + EXPECT_EQ(info.reg, RegKind::GPR_64); + EXPECT_EQ(info.result_type.print(), "int64"); + + // test inline-array (won't work because type is dynamically sized) + type_spec = ts.make_inline_array_typespec("type"); + info = ts.get_deref_info(type_spec); + EXPECT_FALSE(info.can_deref); + + // TODO - replace with a better type. + // TODO - maybe block basic or structure from being inline-array-able? + type_spec = ts.make_inline_array_typespec("basic"); + auto type = ts.lookup_type("basic"); + info = ts.get_deref_info(type_spec); + EXPECT_TRUE(info.can_deref); + EXPECT_FALSE(info.mem_deref); + EXPECT_EQ(info.stride, (type->get_size_in_memory() + 15) & (~15)); + EXPECT_EQ(info.result_type.print(), "basic"); + EXPECT_EQ(info.load_size, -1); +} + +TEST(TypeSystem, AddMethodAndLookupMethod) { + TypeSystem ts; + ts.add_builtin_types(); + + auto parent_info = ts.add_method(ts.lookup_type("structure"), "test-method-1", + ts.make_function_typespec({"integer"}, "string")); + + // when trying to add the same method to a child, should return the parent's method + auto child_info_same = ts.add_method(ts.lookup_type("basic"), "test-method-1", + ts.make_function_typespec({"integer"}, "string")); + + EXPECT_EQ(parent_info.id, child_info_same.id); + EXPECT_EQ(parent_info.id, GOAL_MEMUSAGE_METHOD + 1); + + // any amount of fiddling with method types should cause an error + EXPECT_ANY_THROW(ts.add_method(ts.lookup_type("basic"), "test-method-1", + ts.make_function_typespec({"integer"}, "integer"))); + EXPECT_ANY_THROW(ts.add_method(ts.lookup_type("basic"), "test-method-1", + ts.make_function_typespec({}, "string"))); + EXPECT_ANY_THROW(ts.add_method(ts.lookup_type("basic"), "test-method-1", + ts.make_function_typespec({"integer", "string"}, "string"))); + EXPECT_ANY_THROW(ts.add_method(ts.lookup_type("basic"), "test-method-1", + ts.make_function_typespec({"string"}, "string"))); + + ts.add_method(ts.lookup_type("basic"), "test-method-2", + ts.make_function_typespec({"integer"}, "string")); + + EXPECT_EQ(parent_info.id, ts.lookup_method("basic", "test-method-1").id); + EXPECT_EQ(parent_info.id, ts.lookup_method("structure", "test-method-1").id); + EXPECT_EQ(parent_info.id + 1, ts.lookup_method("basic", "test-method-2").id); + EXPECT_ANY_THROW(ts.lookup_method("structure", "test-method-2")); + + EXPECT_EQ(ts.lookup_method("basic", "test-method-1").defined_in_type, "structure"); + EXPECT_EQ(ts.lookup_method("basic", "test-method-1").type.print(), "(function integer string)"); + EXPECT_EQ(ts.lookup_method("basic", "test-method-1").name, "test-method-1"); +} + +TEST(TypeSystem, NewMethod) { + TypeSystem ts; + ts.add_builtin_types(); + ts.add_type("test-1", std::make_unique("basic", "test-1")); + ts.add_method(ts.lookup_type("test-1"), "new", + ts.make_function_typespec({"symbol", "string"}, "test-1")); + ts.add_type("test-2", std::make_unique("test-1", "test-2")); + ts.add_method(ts.lookup_type("test-2"), "new", + ts.make_function_typespec({"symbol", "string", "symbol"}, "test-2")); + + EXPECT_EQ(ts.lookup_method("test-1", "new").type.print(), "(function symbol string test-1)"); + EXPECT_EQ(ts.lookup_method("test-2", "new").type.print(), + "(function symbol string symbol test-2)"); + + ts.add_type("test-3", std::make_unique("test-1", "test-3")); + EXPECT_EQ(ts.lookup_method("test-3", "new").type.print(), "(function symbol string test-1)"); + + ts.add_type("test-4", std::make_unique("test-2", "test-4")); + EXPECT_EQ(ts.lookup_method("test-4", "new").type.print(), + "(function symbol string symbol test-2)"); +} + +TEST(TypeSystem, MethodSubstitute) { + TypeSystem ts; + ts.add_builtin_types(); + ts.add_type("test-1", std::make_unique("basic", "test-1")); + ts.add_method(ts.lookup_type("test-1"), "new", + ts.make_function_typespec({"symbol", "string", "_type_"}, "_type_")); + + auto final_type = ts.lookup_method("test-1", "new").type.substitute_for_method_call("test-1"); + EXPECT_EQ(final_type.print(), "(function symbol string test-1 test-1)"); +} + +namespace { +bool ts_name_name(TypeSystem& ts, const std::string& ex, const std::string& act) { + return ts.typecheck(ts.make_typespec(ex), ts.make_typespec(act), "", false, false); +} +} // namespace + +TEST(TypeSystem, TypeCheck) { + TypeSystem ts; + ts.add_builtin_types(); + + EXPECT_TRUE(ts_name_name(ts, "none", + "none")); // none - none _shouldn't_ fail (for function return types!) + EXPECT_TRUE(ts_name_name(ts, "object", "object")); + EXPECT_TRUE(ts_name_name(ts, "object", "type")); + EXPECT_TRUE(ts_name_name(ts, "basic", "type")); + EXPECT_FALSE(ts_name_name(ts, "type", "basic")); + EXPECT_TRUE(ts_name_name(ts, "type", "type")); + + auto f = ts.make_typespec("function"); + auto f_s_n = ts.make_function_typespec({"string"}, "none"); + auto f_b_n = ts.make_function_typespec({"basic"}, "none"); + + // complex + EXPECT_TRUE(ts.typecheck(f, f_s_n)); + EXPECT_TRUE(ts.typecheck(f_s_n, f_s_n)); + EXPECT_TRUE(ts.typecheck(f_b_n, f_s_n)); + + EXPECT_FALSE(ts.typecheck(f_s_n, f, "", false, false)); + EXPECT_FALSE(ts.typecheck(f_s_n, f_b_n, "", false, false)); + + // number of parameter mismatch + auto f_s_s_n = ts.make_function_typespec({"string", "string"}, "none"); + EXPECT_FALSE(ts.typecheck(f_s_n, f_s_s_n, "", false, false)); + EXPECT_FALSE(ts.typecheck(f_s_s_n, f_s_n, "", false, false)); +} + +TEST(TypeSystem, FieldLookup) { + // note - this test isn't testing the specific needs_deref, type of the returned info. Until more + // stuff is set up that test is kinda useless - it would just be testing against the exact + // implementation of lookup_field_info + + TypeSystem ts; + ts.add_builtin_types(); + + EXPECT_EQ(ts.lookup_field_info("type", "parent").field.offset(), 8); + EXPECT_EQ(ts.lookup_field_info("string", "data").type.print(), "(pointer uint8)"); + + EXPECT_ANY_THROW(ts.lookup_field_info("type", "not-a-real-field")); +} + +TEST(TypeSystem, get_path_up_tree) { + TypeSystem ts; + ts.add_builtin_types(); + EXPECT_EQ(ts.get_path_up_tree("type"), + std::vector({"type", "basic", "structure", "object"})); +} + +TEST(TypeSystem, lca) { + TypeSystem ts; + ts.add_builtin_types(); + EXPECT_EQ( + ts.lowest_common_ancestor(ts.make_typespec("string"), ts.make_typespec("basic")).print(), + "basic"); + EXPECT_EQ( + ts.lowest_common_ancestor(ts.make_typespec("basic"), ts.make_typespec("string")).print(), + "basic"); + EXPECT_EQ( + ts.lowest_common_ancestor(ts.make_typespec("string"), ts.make_typespec("int32")).print(), + "object"); + EXPECT_EQ( + ts.lowest_common_ancestor(ts.make_typespec("int32"), ts.make_typespec("string")).print(), + "object"); + EXPECT_EQ( + ts.lowest_common_ancestor({ts.make_typespec("int32"), ts.make_typespec("string")}).print(), + "object"); + EXPECT_EQ(ts.lowest_common_ancestor({ts.make_typespec("int32")}).print(), "int32"); + EXPECT_EQ(ts.lowest_common_ancestor({ts.make_typespec("string"), ts.make_typespec("kheap"), + ts.make_typespec("pointer")}) + .print(), + "object"); + + EXPECT_EQ(ts.lowest_common_ancestor(ts.make_pointer_typespec("int32"), + ts.make_pointer_typespec("string")) + .print(), + "(pointer object)"); +} +// TODO - a big test to make sure all the builtin types are what we expect.