From 0560136f0894856bf541c3bcbbccd7f2a2608686 Mon Sep 17 00:00:00 2001 From: ManDude <7569514+ManDude@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:29:38 +0100 Subject: [PATCH] Allow enum names to serve as types (using the enum's type) (#372) * Allow enum names to serve as types (using the enum's type) * Formatting * add doc to `get_enum_type_name` * Ban enum forward-declaring, and remove unneeded checks --- common/CMakeLists.txt | 1 + {goalc/compiler => common/type_system}/Enum.h | 3 +- common/type_system/TypeSystem.cpp | 53 +++++- common/type_system/TypeSystem.h | 5 + common/type_system/defenum.cpp | 154 ++++++++++++++++++ common/type_system/defenum.h | 13 ++ common/type_system/deftype.h | 1 - decompiler/config/all-types.gc | 12 +- decompiler/util/DecompilerTypeSystem.cpp | 8 +- doc/goal_doc.md | 2 +- goal_src/engine/gfx/hw/gs.gc | 5 +- goalc/compiler/Compiler.h | 2 +- goalc/compiler/compilation/Type.cpp | 81 +++------ 13 files changed, 268 insertions(+), 72 deletions(-) rename {goalc/compiler => common/type_system}/Enum.h (93%) create mode 100644 common/type_system/defenum.cpp create mode 100644 common/type_system/defenum.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 40f409c49f..f521402727 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(common goos/TextDB.cpp goos/ReplUtils.cpp log/log.cpp + type_system/defenum.cpp type_system/deftype.cpp type_system/Type.cpp type_system/TypeFieldLookup.cpp diff --git a/goalc/compiler/Enum.h b/common/type_system/Enum.h similarity index 93% rename from goalc/compiler/Enum.h rename to common/type_system/Enum.h index f9568082ec..caedddd06c 100644 --- a/goalc/compiler/Enum.h +++ b/common/type_system/Enum.h @@ -6,10 +6,11 @@ #include "common/type_system/TypeSpec.h" struct GoalEnum { + std::string name; TypeSpec base_type; bool is_bitfield = false; std::unordered_map entries; bool operator==(const GoalEnum& other) const; bool operator!=(const GoalEnum& other) const; -}; \ No newline at end of file +}; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 5622fcd61f..e6e77356b5 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -30,7 +30,7 @@ Type* TypeSystem::add_type(const std::string& name, std::unique_ptr type) if (*kv->second != *type) { // exists, and we are trying to change it! - fmt::print("[TypeSystem] type {} was originally\n{}\nand is redefined as\n{}\n", + fmt::print("[TypeSystem] Type {} was originally\n{}\nand is redefined as\n{}\n", kv->second->get_name(), kv->second->print(), type->print()); if (m_allow_redefinition) { @@ -70,6 +70,37 @@ Type* TypeSystem::add_type(const std::string& name, std::unique_ptr type) return m_types[name].get(); } +/*! + * Add a new 'enum type'. This maps enum names to the their type's name, allowing the enum name to + * be used as if it were a type name. + */ +Type* TypeSystem::add_enum_type(const std::string& name, const std::string& type) { + auto kv = m_enum_types.find(name); + if (kv != m_enum_types.end()) { + // exists already + + if (kv->second != type) { + // exists, and we are trying to change it! + fmt::print("[TypeSystem] Enum {} was originally\n{}\nand is redefined as\n{}\n", name, + kv->second, type); + + throw std::runtime_error("Enum type was redefined."); + } + } else { + // an enum has been forward declared, which is only allowed for types + if (m_forward_declared_types.find(name) != m_forward_declared_types.end()) { + fmt::print("[TypeSystem] Enum {} was forward-declared, enums cannot be forward-declared\n", + name); + + throw std::runtime_error("Enum was forward-declared."); + } + + m_enum_types[name] = type; + } + + return lookup_type(m_enum_types[name]); +} + /*! * Inform the type system that there will eventually be a type named "name". * This will allow the type system to generate TypeSpecs for this type, but not access detailed @@ -185,6 +216,9 @@ TypeSpec TypeSystem::make_typespec(const std::string& name) const { if (m_types.find(name) != m_types.end() || m_forward_declared_types.find(name) != m_forward_declared_types.end()) { return TypeSpec(name); + } else if (m_enum_types.find(name) != m_enum_types.end()) { + // simply return the enum's type instead + return TypeSpec(m_enum_types.at(name)); } else { fmt::print("[TypeSystem] The type {} is unknown.\n", name); throw std::runtime_error("make_typespec failed"); @@ -199,6 +233,10 @@ bool TypeSystem::partially_defined_type_exists(const std::string& name) const { return m_forward_declared_types.find(name) != m_forward_declared_types.end(); } +bool TypeSystem::enum_type_exists(const std::string& name) const { + return m_enum_types.find(name) != m_enum_types.end(); +} + TypeSpec TypeSystem::make_array_typespec(const TypeSpec& element_type) const { return TypeSpec("array", {element_type}); } @@ -321,6 +359,17 @@ MethodInfo TypeSystem::add_method(const std::string& type_name, return add_method(lookup_type(make_typespec(type_name)), method_name, ts, allow_new_method); } +/*! + * Return the type name of an enum. Throws if the enum does not exist. + */ +std::string TypeSystem::get_enum_type_name(const std::string& name) const { + if (m_enum_types.find(name) != m_enum_types.end()) { + return m_enum_types.at(name); + } else { + throw std::runtime_error("get_enum_type_name failed"); + } +} + /*! * Add a method, if it doesn't exist. If the method already exists (possibly in a parent), checks to * see if this is an identical definition. If not, it's an error, and if so, nothing happens. @@ -1474,4 +1523,4 @@ std::string TypeSystem::generate_deftype(const Type* type) const { return fmt::format( ";; cannot generate deftype for {}, it is not a structure, basic, or bitfield (parent {})\n", type->get_name(), type->get_parent()); -} \ No newline at end of file +} diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index 8fa8491ce1..6229ed6dea 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -91,6 +91,7 @@ class TypeSystem { TypeSystem(); Type* add_type(const std::string& name, std::unique_ptr type); + Type* add_enum_type(const std::string& name, const std::string& type); void forward_declare_type(const std::string& name); void forward_declare_type_as_basic(const std::string& name); void forward_declare_type_as_structure(const std::string& name); @@ -101,6 +102,7 @@ class TypeSystem { bool fully_defined_type_exists(const std::string& name) const; bool partially_defined_type_exists(const std::string& name) const; + bool enum_type_exists(const std::string& name) const; TypeSpec make_typespec(const std::string& name) const; TypeSpec make_array_typespec(const TypeSpec& element_type) const; TypeSpec make_function_typespec(const std::vector& arg_types, @@ -117,6 +119,8 @@ class TypeSystem { Type* lookup_type_allow_partial_def(const TypeSpec& ts) const; Type* lookup_type_allow_partial_def(const std::string& name) const; + std::string get_enum_type_name(const std::string& name) const; + MethodInfo add_method(const std::string& type_name, const std::string& method_name, const TypeSpec& ts, @@ -229,6 +233,7 @@ class TypeSystem { enum ForwardDeclareKind { TYPE, STRUCTURE, BASIC }; std::unordered_map> m_types; + std::unordered_map m_enum_types; std::unordered_map m_forward_declared_types; std::vector> m_old_types; diff --git a/common/type_system/defenum.cpp b/common/type_system/defenum.cpp new file mode 100644 index 0000000000..fc1bb69a60 --- /dev/null +++ b/common/type_system/defenum.cpp @@ -0,0 +1,154 @@ +/*! + * @file defenum.cpp + * Parser for the GOAL "defenum" form. + * This is used both in the compiler and in the decompiler for the type definition file. + */ + +#include "Enum.h" +#include "defenum.h" +#include "deftype.h" +#include "third-party/fmt/core.h" + +namespace { +const goos::Object& car(const goos::Object* x) { + if (!x->is_pair()) { + throw std::runtime_error("invalid defenum form"); + } + + return x->as_pair()->car; +} + +const goos::Object* cdr(const goos::Object* x) { + if (!x->is_pair()) { + throw std::runtime_error("invalid defenum form"); + } + + return &x->as_pair()->cdr; +} + +bool is_type(const std::string& expected, const TypeSpec& actual, const TypeSystem* ts) { + return ts->tc(ts->make_typespec(expected), actual); +} + +bool integer_fits(s64 in, int size, bool is_signed) { + switch (size) { + case 1: + if (is_signed) { + return in >= INT8_MIN && in <= INT8_MAX; + } else { + return in >= 0 && in <= UINT8_MAX; + } + case 2: + if (is_signed) { + return in >= INT16_MIN && in <= INT16_MAX; + } else { + return in >= 0 && in <= UINT16_MAX; + } + case 4: + if (is_signed) { + return in >= INT32_MIN && in <= INT32_MAX; + } else { + return in >= 0 && in <= UINT32_MAX; + } + case 8: + return true; + default: + assert(false); + return 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"); +} + +} // namespace + +void parse_defenum(const goos::Object& defenum, TypeSystem* ts, GoalEnum& goalenum) { + // default enum type will be int32. + goalenum.base_type = ts->make_typespec("int32"); + goalenum.is_bitfield = false; + + auto iter = &defenum; + + auto& enum_name_obj = car(iter); + iter = cdr(iter); + + if (!enum_name_obj.is_symbol()) { + throw std::runtime_error("defenum must be given a symbol as its name"); + } + goalenum.name = enum_name_obj.as_symbol()->name; + + auto current = car(iter); + while (current.is_symbol() && symbol_string(current).at(0) == ':') { + auto option_name = symbol_string(current); + iter = cdr(iter); + auto option_value = car(iter); + iter = cdr(iter); + current = car(iter); + + if (option_name == ":type") { + goalenum.base_type = parse_typespec(ts, option_value); + } else if (option_name == ":bitfield") { + if (symbol_string(option_value) == "#t") { + goalenum.is_bitfield = true; + } else if (symbol_string(option_value) == "#f") { + goalenum.is_bitfield = false; + } else { + fmt::print("Invalid option {} to :bitfield option.\n", option_value.print()); + throw std::runtime_error("invalid bitfield option"); + } + } else { + fmt::print("Unknown option {} for defenum.\n", option_name); + throw std::runtime_error("unknown option for defenum"); + } + } + + auto type = ts->lookup_type(goalenum.base_type); + while (!iter->is_empty_list()) { + auto field = car(iter); + auto name = symbol_string(car(&field)); + auto rest = cdr(&field); + auto& value = car(rest); + if (!value.is_int()) { + fmt::print("Expected integer for enum value, got {}\n", value.print()); + } + + auto entry_val = value.integer_obj.value; + if (!integer_fits(entry_val, type->get_load_size(), type->get_load_signed())) { + fmt::print("Integer {} does not fit inside a {}\n", entry_val, type->get_name()); + } + + rest = cdr(rest); + if (!rest->is_empty_list()) { + fmt::print("Got too many items in defenum {} entry {}\n", goalenum.name, name); + } + + goalenum.entries[name] = entry_val; + iter = cdr(iter); + } + + if (is_type("integer", goalenum.base_type, ts)) { + ts->add_enum_type(goalenum.name, goalenum.base_type.base_type()); + } else { + throw std::runtime_error("Creating an enum with type " + goalenum.base_type.print() + + " is not allowed or not supported yet."); + } +} diff --git a/common/type_system/defenum.h b/common/type_system/defenum.h new file mode 100644 index 0000000000..4c5d329576 --- /dev/null +++ b/common/type_system/defenum.h @@ -0,0 +1,13 @@ +#pragma once + +/*! + * @file defenum.h + * Parser for the GOAL "defenum" form. + * This is used both in the compiler and in the decompiler for the type definition file. + */ + +#include "TypeSystem.h" +#include "Enum.h" +#include "common/goos/Object.h" + +void parse_defenum(const goos::Object& defenum, TypeSystem* ts, GoalEnum& goalenum); diff --git a/common/type_system/deftype.h b/common/type_system/deftype.h index bf8b31582b..cd24b005bd 100644 --- a/common/type_system/deftype.h +++ b/common/type_system/deftype.h @@ -8,7 +8,6 @@ #include "TypeSystem.h" #include "common/goos/Object.h" -#include "common/type_system/TypeSystem.h" struct DeftypeResult { TypeFlags flags; diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index 57e78a195f..0415d3c367 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -2931,10 +2931,20 @@ ) :flag-assert #x900000008 ) +(defenum gs-prim-type + :type uint8 + (point 0) + (line 1) + (line-strip 2) + (tri 3) + (tri-strip 4) + (tri-fan 5) + (sprite 6) + ) ;; the GS's PRIM register specifies the types of drawing primitives and various attributes, and ;; initializes the contents of the vertex queue. (deftype gs-prim (uint64) - ((prim uint8 :offset 0 :size 3) + ((prim gs-prim-type :offset 0 :size 3) (iip uint8 :offset 3 :size 1) (tme uint8 :offset 4 :size 1) (fge uint8 :offset 5 :size 1) diff --git a/decompiler/util/DecompilerTypeSystem.cpp b/decompiler/util/DecompilerTypeSystem.cpp index a67018a776..6a4f56d4c5 100644 --- a/decompiler/util/DecompilerTypeSystem.cpp +++ b/decompiler/util/DecompilerTypeSystem.cpp @@ -1,7 +1,9 @@ #include "DecompilerTypeSystem.h" #include "common/goos/Reader.h" +#include "common/type_system/defenum.h" #include "common/type_system/deftype.h" #include "decompiler/Disasm/Register.h" +#include "common/type_system/Enum.h" #include "common/log/log.h" #include "TP_Type.h" @@ -77,6 +79,10 @@ void DecompilerTypeSystem::parse_type_defs(const std::vector& file_ } else { throw std::runtime_error("bad declare-type"); } + } else if (car(o).as_symbol()->name == "defenum") { + GoalEnum new_enum; + parse_defenum(cdr(o), &ts, new_enum); + // TODO we do nothing with the enum for now } else { throw std::runtime_error("Decompiler cannot parse " + car(o).print()); } @@ -395,4 +401,4 @@ TypeSpec DecompilerTypeSystem::lookup_symbol_type(const std::string& name) const return kv->second; } } -} // namespace decompiler \ No newline at end of file +} // namespace decompiler diff --git a/doc/goal_doc.md b/doc/goal_doc.md index 9717558fdd..340091393c 100644 --- a/doc/goal_doc.md +++ b/doc/goal_doc.md @@ -1264,7 +1264,7 @@ Get the size of a type, in bytes. ```lisp (size-of ) ``` -Get the size of a type, by name. The type must be a plain name, like `pointer` or `dma-bucket`. Compound types are not supported. It works on value and structure types and returns the size in memory. For dynamic types, it returns the size if there if the dynamic part has 0 size. For weird types like `none` it throws an error. +Get the size of a type, by name. The type must be a plain name, like `pointer` or `dma-bucket`. Compound types are not supported. It works on value and structure types and returns the size in memory. For dynamic types, it returns the size as if the dynamic part has 0 size. For weird types like `none` it throws an error. This value can be used in most places where the compiler is expecting a constant integer as well, such as the size of a stack array, which must be known at compile time. diff --git a/goal_src/engine/gfx/hw/gs.gc b/goal_src/engine/gfx/hw/gs.gc index b5e9e4a715..405dd88293 100644 --- a/goal_src/engine/gfx/hw/gs.gc +++ b/goal_src/engine/gfx/hw/gs.gc @@ -242,7 +242,8 @@ :flag-assert #x900000008 ) -(defenum gs-prim-type :bitfield #f +(defenum gs-prim-type + :type uint8 (point 0) (line 1) (line-strip 2) @@ -254,7 +255,7 @@ ;; the GS's PRIM register specifies the types of drawing primitives and various attributes, and ;; initializes the contents of the vertex queue. (deftype gs-prim (uint64) - ((prim uint8 :offset 0 :size 3) + ((prim gs-prim-type :offset 0 :size 3) (iip uint8 :offset 3 :size 1) (tme uint8 :offset 4 :size 1) (fge uint8 :offset 5 :size 1) diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 6b5146a769..26112efb22 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -17,7 +17,7 @@ #include "third-party/fmt/color.h" #include "CompilerException.h" #include "goalc/compiler/SymbolInfo.h" -#include "Enum.h" +#include "common/type_system/Enum.h" #include "common/goos/ReplUtils.h" enum MathMode { MATH_INT, MATH_BINT, MATH_FLOAT, MATH_INVALID }; diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index 873fa4e092..c5239563f3 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -1,7 +1,8 @@ #include "goalc/compiler/Compiler.h" #include "third-party/fmt/core.h" +#include "common/type_system/defenum.h" #include "common/type_system/deftype.h" -#include "goalc/compiler/Enum.h" +#include "common/type_system/Enum.h" namespace { @@ -230,7 +231,7 @@ Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& res auto kv = m_symbol_types.find(result.type.base_type()); if (kv != m_symbol_types.end() && kv->second.base_type() != "type") { // we already have something that's not a type with the same name, this is bad. - fmt::print("[Warning] deftype will redefined {} from {} to a type.\n", result.type.base_type(), + fmt::print("[Warning] deftype will redefine {} from {} to a type.\n", result.type.base_type(), kv->second.print()); } // remember that this is a type @@ -993,69 +994,20 @@ Val* Compiler::compile_none(const goos::Object& form, const goos::Object& rest, return get_none(); } -Val* Compiler::compile_defenum(const goos::Object& form, const goos::Object& _rest, Env* env) { +Val* Compiler::compile_defenum(const goos::Object& form, const goos::Object& rest, Env* env) { // format is (defenum name [options] [entries]) + (void)form; (void)env; - auto* rest = &_rest; - - // name - auto enum_name = symbol_string(pair_car(*rest)); - rest = &pair_cdr(*rest); - - // default enum type will be int32. - auto enum_type = m_ts.make_typespec("int32"); - bool is_bitfield = false; - - auto current = pair_car(*rest); - while (current.is_symbol() && symbol_string(current).at(0) == ':') { - auto option_name = symbol_string(current); - rest = &pair_cdr(*rest); - auto option_value = pair_car(*rest); - rest = &pair_cdr(*rest); - current = pair_car(*rest); - - if (option_name == ":type") { - enum_type = parse_typespec(option_value); - } else if (option_name == ":bitfield") { - if (symbol_string(option_value) == "#t") { - is_bitfield = true; - } else if (symbol_string(option_value) == "#f") { - is_bitfield = false; - } else { - throw_compiler_error(form, "Invalid option {} to :bitfield option.", option_value.print()); - } - } else { - throw_compiler_error(form, "Unknown option {} for defenum.", option_name); - } - } GoalEnum new_enum; - new_enum.base_type = enum_type; - new_enum.is_bitfield = is_bitfield; + parse_defenum(rest, &m_ts, new_enum); - while (!rest->is_empty_list()) { - auto def = pair_car(*rest); - auto name = symbol_string(pair_car(def)); - def = pair_cdr(def); - auto value = pair_car(def); - if (!value.is_int()) { - throw_compiler_error(def, "Expected integer for enum value, got {}", value.print()); - } - - def = pair_cdr(def); - if (!def.is_empty_list()) { - throw_compiler_error(def, "Got too many items in defenum defintion."); - } - - new_enum.entries[name] = value.integer_obj.value; - rest = &pair_cdr(*rest); - } - - auto existing_kv = m_enums.find(enum_name); + auto existing_kv = m_enums.find(new_enum.name); if (existing_kv != m_enums.end() && existing_kv->second != new_enum) { - print_compiler_warning("defenum changes the definition of existing enum {}", enum_name.c_str()); + print_compiler_warning("defenum changes the definition of existing enum {}", + new_enum.name.c_str()); } - m_enums[enum_name] = new_enum; + m_enums[new_enum.name] = new_enum; return get_none(); } @@ -1144,13 +1096,18 @@ int Compiler::get_size_for_size_of(const goos::Object& form, const goos::Object& auto args = get_va(form, rest); va_check(form, args, {goos::ObjectType::SYMBOL}, {}); - if (!m_ts.fully_defined_type_exists(args.unnamed.at(0).as_symbol()->name)) { + auto type_to_look_for = args.unnamed.at(0).as_symbol()->name; + if (m_ts.enum_type_exists(type_to_look_for)) { + type_to_look_for = m_ts.get_enum_type_name(type_to_look_for); + } + + if (!m_ts.fully_defined_type_exists(type_to_look_for)) { throw_compiler_error( - form, "The type {} given to size-of could not be found, or was not fully defined", + form, "The type or enum {} given to size-of could not be found, or was not fully defined", args.unnamed.at(0).print()); } - auto type = m_ts.lookup_type(args.unnamed.at(0).as_symbol()->name); + auto type = m_ts.lookup_type(type_to_look_for); auto as_value = dynamic_cast(type); auto as_structure = dynamic_cast(type); @@ -1168,4 +1125,4 @@ int Compiler::get_size_for_size_of(const goos::Object& form, const goos::Object& Val* Compiler::compile_size_of(const goos::Object& form, const goos::Object& rest, Env* env) { return compile_integer(get_size_for_size_of(form, rest), env); -} \ No newline at end of file +}