diff --git a/common/link_types.h b/common/link_types.h index 9766c29b8..90315675c 100644 --- a/common/link_types.h +++ b/common/link_types.h @@ -14,6 +14,7 @@ enum LinkKind { LINK_TYPE_PTR = 2, //! link a pointer to a type. LINK_DISTANCE_TO_OTHER_SEG_64 = 3, //! link to another segment LINK_DISTANCE_TO_OTHER_SEG_32 = 4, //! link to another segment + LINK_PTR = 5, //! link a pointer within this segment }; enum SegmentTypes { MAIN_SEGMENT = 0, DEBUG_SEGMENT = 1, TOP_LEVEL_SEGMENT = 2 }; diff --git a/common/versions.h b/common/versions.h index d0180384e..47a654f16 100644 --- a/common/versions.h +++ b/common/versions.h @@ -11,9 +11,11 @@ #include "common/common_types.h" namespace versions { -// language version +// language version (OpenGOAL) constexpr s32 GOAL_VERSION_MAJOR = 0; -constexpr s32 GOAL_VERSION_MINOR = 3; +constexpr s32 GOAL_VERSION_MINOR = 4; + +// these versions are from the game constexpr u32 ART_FILE_VERSION = 6; constexpr u32 LEVEL_FILE_VERSION = 30; constexpr u32 DGO_FILE_VERSION = 1; @@ -21,7 +23,7 @@ constexpr u32 RES_FILE_VERSION = 1; constexpr u32 TX_PAGE_VERSION = 7; } // namespace versions -// GOAL kernel version +// GOAL kernel version (OpenGOAL changes this version from the game's version) constexpr int KERNEL_VERSION_MAJOR = 2; constexpr int KERNEL_VERSION_MINOR = 0; diff --git a/doc/changelog.md b/doc/changelog.md index 943213c44..83128c4b8 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -80,4 +80,10 @@ - Fixed a bug where the return instruction was still emitted and the overridden return type of `asm-func` was ignored for methods - Rearranged function stack frames so spilled register variable slots come after stack structures. - Added `stack` allocated and constructed basic/structure types. -- Fixed a bug where functions with exactly 8 parameters created a compiler error. \ No newline at end of file +- Fixed a bug where functions with exactly 8 parameters created a compiler error. + +## V0.4 +- Breaking change: added new link kind to link table format. Code compiled with previous versions will still work, but code compiled in V0.4 that uses static pairs will cause a "unknown link table code 5" error. +- Added support for static pairs and lists. Symbols, integers, and strings are supported. +- Added much better support for setting fields of statics. Now you can set constants, symbols, strings, pairs, integers, floats, or other statics, including inlined structures/basics! Also uses the full type system for typechecking +- Fixed a bug where accessing an inline basic field did not apply the 4-byte basic offset. \ No newline at end of file diff --git a/game/kernel/klink.cpp b/game/kernel/klink.cpp index 8da53c1a5..0682d4d44 100644 --- a/game/kernel/klink.cpp +++ b/game/kernel/klink.cpp @@ -343,7 +343,7 @@ uint32_t symlink_v3(Ptr link, Ptr data) { } /*! - * Link a single pointer. + * Link a single relative offset (used for RIP) */ uint32_t cross_seg_dist_link_v3(Ptr link, ObjectFileHeader* ofh, @@ -360,7 +360,8 @@ uint32_t cross_seg_dist_link_v3(Ptr link, // printf("link object in seg %d diff %d at %d (%d + %d)\n", target_seg, diff, offset_of_patch, // link_data[2], ofh->code_infos[current_seg].offset); - // both 32-bit and 64-bit pointer links are supported, though 64-bit ones should disappear soon. + // both 32-bit and 64-bit pointer links are supported, though 64-bit ones are no longer in use. + // we still support it just in case we want to run ancient code. if (size == 4) { *Ptr(offset_of_patch).c() = diff; } else if (size == 8) { @@ -372,6 +373,14 @@ uint32_t cross_seg_dist_link_v3(Ptr link, return 1 + 3 * 4; } +uint32_t ptr_link_v3(Ptr link, ObjectFileHeader* ofh, int current_seg) { + auto* link_data = link.cast().c(); + u32 patch_loc = link_data[0] + ofh->code_infos[current_seg].offset; + u32 patch_value = link_data[1] + ofh->code_infos[current_seg].offset; + *Ptr(patch_loc).c() = patch_value; + return 8; +} + /*! * Run the linker. For now, all linking is done in two runs. If this turns out to be too slow, * this should be modified to do incremental linking over multiple runs. @@ -468,16 +477,18 @@ uint32_t link_control::work_v3() { lp = lp + 1; // seek past id lp = lp + typelink_v3(lp, Ptr(ofh->code_infos[m_segment_process].offset)); break; - case LINK_DISTANCE_TO_OTHER_SEG_64: lp = lp + 1; lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 8); break; - case LINK_DISTANCE_TO_OTHER_SEG_32: lp = lp + 1; lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 4); break; + case LINK_PTR: + lp = lp + 1; + lp = lp + ptr_link_v3(lp, ofh, m_segment_process); + break; default: printf("unknown link table thing %d\n", *lp); exit(0); diff --git a/goal_src/kernel/gkernel.gc b/goal_src/kernel/gkernel.gc index 172c97d7d..a2db3eb44 100644 --- a/goal_src/kernel/gkernel.gc +++ b/goal_src/kernel/gkernel.gc @@ -2298,8 +2298,7 @@ (define *default-dead-pool* (the dead-pool *nk-dead-pool*)) (define *pickup-dead-pool* (the dead-pool *nk-dead-pool*)) -;; todo dead pool list - +(define *dead-pool-list* '(*4k-dead-pool* *8k-dead-pool* *16k-dead-pool* *nk-dead-pool* *target-dead-pool* *camera-dead-pool* *camera-master-dead-pool*)) ;; main active pool (define *active-pool* (new 'global 'process-tree 'active-pool)) diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index a48aaca42..0cd8bf73e 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -94,6 +94,10 @@ class Compiler { bool is_basic(const TypeSpec& ts); bool is_structure(const TypeSpec& ts); bool is_bitfield(const TypeSpec& ts); + bool is_pair(const TypeSpec& ts); + std::vector get_list_as_vector(const goos::Object& o, + goos::Object* rest_out = nullptr, + int max_length = -1); const goos::Object& pair_car(const goos::Object& o); const goos::Object& pair_cdr(const goos::Object& o); void expect_empty_list(const goos::Object& o); @@ -210,6 +214,24 @@ class Compiler { const TypeSpec& type, const goos::Object& field_defs, Env* env); + Val* compile_static_pair(const goos::Object& form, Env* env); + StaticResult compile_static(const goos::Object& form, Env* env); + StaticResult compile_static_no_eval_for_pairs(const goos::Object& form, Env* env); + StaticResult compile_static_bitfield(const goos::Object& form, + const TypeSpec& type, + const goos::Object& _field_defs, + Env* env); + StaticResult compile_new_static_structure(const goos::Object& form, + const TypeSpec& type, + const goos::Object& _field_defs, + Env* env); + + void compile_static_structure_inline(const goos::Object& form, + const TypeSpec& type, + const goos::Object& _field_defs, + StaticStructure* structure, + int offset, + Env* env); template void throw_compiler_error(const goos::Object& code, const std::string& str, Args&&... args) { diff --git a/goalc/compiler/StaticObject.cpp b/goalc/compiler/StaticObject.cpp index 828403876..ed37388df 100644 --- a/goalc/compiler/StaticObject.cpp +++ b/goalc/compiler/StaticObject.cpp @@ -16,19 +16,13 @@ uint32_t push_data_to_byte_vector(T data, std::vector& v) { //////////////// // StaticString //////////////// -StaticString::StaticString(std::string data, int _seg) : text(std::move(data)), seg(_seg) {} +StaticString::StaticString(std::string text_data, int _seg) + : StaticBasic(_seg, "string"), text(std::move(text_data)) {} std::string StaticString::print() const { return fmt::format("static-string \"{}\"", text); } -StaticObject::LoadInfo StaticString::get_load_info() const { - LoadInfo info; - info.requires_load = false; - info.prefer_xmm = false; - return info; -} - void StaticString::generate(emitter::ObjectGenerator* gen) { rec = gen->add_static_to_seg(seg, 16); auto& d = gen->get_static_data(rec); @@ -49,10 +43,6 @@ void StaticString::generate(emitter::ObjectGenerator* gen) { d.push_back(0); } -int StaticString::get_addr_offset() const { - return BASIC_OFFSET; -} - //////////////// // StaticFloat //////////////// @@ -110,6 +100,14 @@ void StaticStructure::generate_structure(emitter::ObjectGenerator* gen) { for (auto& sym : symbols) { gen->link_static_symbol_ptr(rec, sym.offset, sym.name); } + + for (auto& ptr : pointers) { + gen->link_static_pointer(rec, ptr.offset_in_this, ptr.dest->rec, ptr.offset_in_dest); + } + + for (auto& type : types) { + gen->link_static_type_ptr(rec, type.offset, type.name); + } } void StaticStructure::generate(emitter::ObjectGenerator* gen) { @@ -123,18 +121,94 @@ void StaticStructure::add_symbol_record(std::string name, int offset) { symbols.push_back(srec); } +void StaticStructure::add_pointer_record(int offset_in_this, + StaticStructure* dest, + int offset_in_dest) { + PointerRecord prec; + prec.offset_in_this = offset_in_this; + prec.dest = dest; + prec.offset_in_dest = offset_in_dest; + pointers.push_back(prec); +} + +void StaticStructure::add_type_record(std::string name, int offset) { + SymbolRecord srec; + srec.name = std::move(name); + srec.offset = offset; + types.push_back(srec); +} + /////////////////// // StaticBasic /////////////////// StaticBasic::StaticBasic(int _seg, std::string _type_name) - : StaticStructure(_seg), type_name(std::move(_type_name)) {} + : StaticStructure(_seg), type_name(std::move(_type_name)) { + add_type_record(type_name, 0); +} int StaticBasic::get_addr_offset() const { return BASIC_OFFSET; } -void StaticBasic::generate(emitter::ObjectGenerator* gen) { +/////////////////// +// StaticPair +/////////////////// + +StaticPair::StaticPair(StaticResult car, StaticResult cdr, int _seg) + : StaticStructure(_seg), m_car(std::move(car)), m_cdr(std::move(cdr)) {} + +int StaticPair::get_addr_offset() const { + return PAIR_OFFSET; +} + +void StaticPair::generate(emitter::ObjectGenerator* gen) { + data.resize(2 * POINTER_SIZE); // size of pair + generate_item(m_car, 0); + generate_item(m_cdr, 4); generate_structure(gen); - gen->link_static_type_ptr(rec, 0, type_name); +} + +void StaticPair::generate_item(const StaticResult& item, int offset) { + if (item.is_reference()) { + add_pointer_record(offset, item.reference(), item.reference()->get_addr_offset()); + } else if (item.is_symbol()) { + add_symbol_record(item.symbol_name(), offset); + u32 symbol_placeholder = 0xffffffff; + memcpy(data.data() + offset, &symbol_placeholder, POINTER_SIZE); + } else if (item.is_constant_data()) { + // if it's a constant data, it should always be a boxed integer for a pair. + // or I guess you could put a normal integer too. Either way, we assume signed here, + // though we may need to allow overflow so you can store either signed/unsigned things in pairs + s32 value = item.get_as_s32(); + memcpy(data.data() + offset, &value, POINTER_SIZE); + } +} + +/////////////////// +// StaticResult +/////////////////// + +StaticResult StaticResult::make_structure_reference(StaticStructure* structure, TypeSpec ts) { + StaticResult result; + result.m_kind = Kind::STRUCTURE_REFERENCE; + result.m_struct = structure; + result.m_ts = std::move(ts); + return result; +} + +StaticResult StaticResult::make_constant_data(u64 value, TypeSpec ts) { + StaticResult result; + result.m_kind = Kind::CONSTANT_DATA; + result.m_constant_data = value; + result.m_ts = std::move(ts); + return result; +} + +StaticResult StaticResult::make_symbol(const std::string& name) { + StaticResult result; + result.m_kind = Kind::SYMBOL; + result.m_symbol = name; + result.m_ts = TypeSpec("symbol"); + return result; } \ No newline at end of file diff --git a/goalc/compiler/StaticObject.h b/goalc/compiler/StaticObject.h index c2fde90ed..4d30f952b 100644 --- a/goalc/compiler/StaticObject.h +++ b/goalc/compiler/StaticObject.h @@ -5,6 +5,7 @@ #include #include +#include "common/type_system/TypeSpec.h" #include "goalc/emitter/ObjectGenerator.h" class StaticObject { @@ -26,17 +27,6 @@ class StaticObject { emitter::StaticRecord rec; }; -class StaticString : public StaticObject { - public: - explicit StaticString(std::string data, int _seg); - std::string text; - int seg = -1; - std::string print() const override; - LoadInfo get_load_info() const override; - void generate(emitter::ObjectGenerator* gen) override; - int get_addr_offset() const override; -}; - class StaticFloat : public StaticObject { public: explicit StaticFloat(float _value, int _seg); @@ -63,9 +53,20 @@ class StaticStructure : public StaticObject { int offset = -1; std::string name; }; + + struct PointerRecord { + int offset_in_this = -1; + StaticStructure* dest = nullptr; + int offset_in_dest = -1; + }; + + std::vector types; std::vector symbols; + std::vector pointers; void add_symbol_record(std::string name, int offset); + void add_pointer_record(int offset_in_this, StaticStructure* dest, int offset_in_dest); + void add_type_record(std::string name, int offset); }; class StaticBasic : public StaticStructure { @@ -73,7 +74,83 @@ class StaticBasic : public StaticStructure { std::string type_name; StaticBasic(int _seg, std::string _type_name); int get_addr_offset() const override; +}; + +class StaticString : public StaticBasic { + public: + explicit StaticString(std::string data, int _seg); + std::string text; + std::string print() const override; void generate(emitter::ObjectGenerator* gen) override; }; +/*! + * Represents a "static value". Like a reference to a static structure (including pair, string, + * basic), a symbol, or some constant (bitfield, integer, float). Cannot be used to store a static + * structure itself, just a reference to one, meaning you cannot set an inlined structure to a + * StaticResult. + */ +class StaticResult { + public: + StaticResult() = default; + + static StaticResult make_structure_reference(StaticStructure* structure, TypeSpec ts); + static StaticResult make_constant_data(u64 value, TypeSpec ts); + static StaticResult make_symbol(const std::string& name); + + std::string print() const; + + const TypeSpec& typespec() const { return m_ts; } + bool is_reference() const { return m_kind == Kind::STRUCTURE_REFERENCE; } + bool is_constant_data() const { return m_kind == Kind::CONSTANT_DATA; } + bool is_symbol() const { return m_kind == Kind::SYMBOL; } + + StaticStructure* reference() const { + assert(is_reference()); + return m_struct; + } + + s32 get_as_s32() const { + assert(is_constant_data()); + // todo, check that it fits. + return (s32)m_constant_data; + } + + const std::string& symbol_name() const { + assert(is_symbol()); + return m_symbol; + } + + u64 constant_data() const { + assert(is_constant_data()); + return m_constant_data; + } + + private: + // used for all types + TypeSpec m_ts; + + // used for only STRUCTURE_REFERENCE + StaticStructure* m_struct = nullptr; + + // used for only constant data + u64 m_constant_data = 0; + + // used for only symbol + std::string m_symbol; + + enum class Kind { STRUCTURE_REFERENCE, CONSTANT_DATA, SYMBOL, INVALID } m_kind = Kind::INVALID; +}; + +class StaticPair : public StaticStructure { + public: + StaticPair(StaticResult car, StaticResult cdr, int _seg); + int get_addr_offset() const override; + void generate(emitter::ObjectGenerator* gen) override; + void generate_item(const StaticResult& item, int offset); + + private: + StaticResult m_car, m_cdr; +}; + #endif // JAK_STATICOBJECT_H diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index d48977aa5..3b7b00ea8 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -163,6 +163,10 @@ bool Compiler::is_bitfield(const TypeSpec& ts) { return m_ts.is_bitfield_type(ts.base_type()); } +bool Compiler::is_pair(const TypeSpec& ts) { + return m_ts.typecheck(m_ts.make_typespec("pair"), ts, "", false, false); +} + bool Compiler::try_getting_constant_integer(const goos::Object& in, int64_t* out, Env* env) { (void)env; if (in.is_int()) { @@ -223,4 +227,32 @@ bool Compiler::get_true_or_false(const goos::Object& form, const goos::Object& b } throw_compiler_error(form, "The value {} cannot be used as a boolean.", boolean.print()); return false; +} + +std::vector Compiler::get_list_as_vector(const goos::Object& o, + goos::Object* rest_out, + int max_length) { + std::vector result; + + auto* cur = &o; + int n = 0; + while (true) { + if (max_length >= 0 && n >= max_length) { + if (rest_out) { + *rest_out = *cur; + } + return result; + } + + if (cur->is_pair()) { + result.push_back(cur->as_pair()->car); + cur = &cur->as_pair()->cdr; + n++; + } else if (cur->is_empty_list()) { + if (rest_out) { + *rest_out = goos::EmptyListObject::make_new(); + } + return result; + } + } } \ No newline at end of file diff --git a/goalc/compiler/compilation/Macro.cpp b/goalc/compiler/compilation/Macro.cpp index ab28eeae7..2fd326d73 100644 --- a/goalc/compiler/compilation/Macro.cpp +++ b/goalc/compiler/compilation/Macro.cpp @@ -106,7 +106,8 @@ Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest, empty_pair->set_type(m_ts.make_typespec("pair")); return empty_pair; } - // todo... + case goos::ObjectType::PAIR: + return compile_static_pair(thing, env); default: throw_compiler_error(form, "Quote is not yet implemented for {}.", thing.print()); } diff --git a/goalc/compiler/compilation/Static.cpp b/goalc/compiler/compilation/Static.cpp index 461947b45..5d65acb22 100644 --- a/goalc/compiler/compilation/Static.cpp +++ b/goalc/compiler/compilation/Static.cpp @@ -6,6 +6,7 @@ #include "goalc/compiler/Compiler.h" #include "third-party/fmt/core.h" +#include "common/goos/ParseHelpers.h" namespace { bool integer_fits(s64 in, int size, bool is_signed) { @@ -42,11 +43,193 @@ u32 float_as_u32(float x) { } } // namespace -Val* Compiler::compile_new_static_bitfield(const goos::Object& form, - const TypeSpec& type, - const goos::Object& _field_defs, - Env* env) { - auto fe = get_parent_env_of_type(env); +/*! + * Compile the fields of a static structure into the given StaticStructure*, applying an offset. + * This can be used to generate an entire structure (set offset to 0), or to fill out an inline + * structure within an existing one (set the offset to the offset of the inline field) + */ +void Compiler::compile_static_structure_inline(const goos::Object& form, + const TypeSpec& type, + const goos::Object& _field_defs, + StaticStructure* structure, + int offset, + Env* env) { + auto type_info = dynamic_cast(m_ts.lookup_type(type)); + assert(type_info); + + // make sure we have enough space + if (int(structure->data.size()) < offset + type_info->get_size_in_memory()) { + throw_compiler_error(form, "The structure does not fit in the type."); + } + + auto* field_defs = &_field_defs; + while (!field_defs->is_empty_list()) { + auto field_name_def = symbol_string(pair_car(*field_defs)); + field_defs = &pair_cdr(*field_defs); + + auto field_value = pair_car(*field_defs); + field_defs = &pair_cdr(*field_defs); + + if (field_name_def.at(0) != ':') { + throw_compiler_error( + form, "expected field def name to start with :, instead got " + field_name_def); + } + + field_name_def = field_name_def.substr(1); + auto field_info = m_ts.lookup_field_info(type_info->get_name(), field_name_def); + + if (field_info.field.is_dynamic() || field_info.field.is_array()) { + throw_compiler_error(form, "Static objects not yet implemented for dynamic/inline/array"); + } + + auto field_offset = field_info.field.offset() + offset; + + if (is_integer(field_info.type)) { + assert(field_info.needs_deref); // for now... + auto deref_info = m_ts.get_deref_info(m_ts.make_pointer_typespec(field_info.type)); + auto field_size = deref_info.load_size; + assert(field_offset + field_size <= int(structure->data.size())); + assert(!field_info.field.is_inline()); + s64 value = 0; + auto sr = compile_static(field_value, env); + if (!sr.is_constant_data()) { + throw_compiler_error(form, "Could not use {} for an integer field", field_value.print()); + } + // we are not strict with the type checking here, as long as you give an "integer" and it + // ends up fitting, it's okay. + typecheck(form, TypeSpec("integer"), sr.typespec()); + value = sr.constant_data(); + + if (!integer_fits(value, deref_info.load_size, deref_info.sign_extend)) { + throw_compiler_error(form, + "Field {} is set to a compile time integer value of {} which would " + "overflow (size {} signed {})", + field_name_def, value, deref_info.load_size, deref_info.sign_extend); + } + + if (field_size == 1 || field_size == 2 || field_size == 4 || field_size == 8) { + memcpy(structure->data.data() + field_offset, &value, field_size); + } else { + // not sure how we can create 128-bit integer constants at this point... + assert(false); + } + } else if (is_structure(field_info.type) || is_pair(field_info.type)) { + // todo - rewrite this to correctly handle structures within structures. + if (is_pair(field_info.type)) { + assert(!field_info.field.is_inline()); + } + + if (field_info.field.is_inline()) { + // for an inline field, we only accept (new 'static ' ...) + if (!field_value.is_list()) { + throw_compiler_error(field_value, "Inline field was not properly specified"); + } + + goos::Object constructor_args; + auto new_form = get_list_as_vector(field_value, &constructor_args, 3); + if (new_form.size() != 3) { + throw_compiler_error(field_value, + "Inline field must be defined with (new 'static 'type-name ...)"); + } + + if (!new_form.at(0).is_symbol() || new_form.at(0).as_symbol()->name != "new") { + throw_compiler_error(field_value, + "Inline field must be defined with (new 'static 'type-name ...)"); + } + + if (!is_quoted_sym(new_form.at(1)) || + unquote(new_form.at(1)).as_symbol()->name != "static") { + throw_compiler_error(field_value, + "Inline field must be defined with (new 'static 'type-name ...)"); + } + + auto inlined_type = parse_typespec(unquote(new_form.at(2))); + if (inlined_type != field_info.type) { + throw_compiler_error(field_value, "Cannot store a {} in an inline {}", + inlined_type.print(), field_info.type.print()); + } + compile_static_structure_inline(field_value, inlined_type, constructor_args, structure, + field_offset, env); + + if (is_basic(inlined_type)) { + structure->add_type_record(inlined_type.base_type(), field_offset); + } + + } else { + assert(field_info.needs_deref); + auto deref_info = m_ts.get_deref_info(m_ts.make_pointer_typespec(field_info.type)); + auto field_size = deref_info.load_size; + assert(field_offset + field_size <= int(structure->data.size())); + auto sr = compile_static(field_value, env); + if (sr.is_symbol()) { + if (sr.symbol_name() != "#f") { + typecheck(form, field_info.type, sr.typespec()); + } + structure->add_symbol_record(sr.symbol_name(), field_offset); + assert(deref_info.mem_deref); + assert(deref_info.can_deref); + assert(deref_info.load_size == 4); + // the linker needs to see a -1 in order to know to insert a symbol pointer + // instead of just the symbol table offset. + u32 linker_val = 0xffffffff; + memcpy(structure->data.data() + field_offset, &linker_val, 4); + } else if (sr.is_reference()) { + typecheck(form, field_info.type, sr.typespec()); + structure->add_pointer_record(field_offset, sr.reference(), + sr.reference()->get_addr_offset()); + } else { + throw_compiler_error(form, "Unsupported field value {}.", field_value.print()); + } + } + } else if (is_float(field_info.type)) { + assert(field_info.needs_deref); + auto deref_info = m_ts.get_deref_info(m_ts.make_pointer_typespec(field_info.type)); + auto field_size = deref_info.load_size; + assert(field_offset + field_size <= int(structure->data.size())); + assert(!field_info.field.is_inline()); + auto sr = compile_static(field_value, env); + if (!sr.is_constant_data()) { + throw_compiler_error(form, "Could not use {} for a float field", field_value.print()); + } + typecheck(form, TypeSpec("float"), sr.typespec()); + u64 value = sr.constant_data(); + memcpy(structure->data.data() + field_offset, &value, sizeof(float)); + } + + else { + assert(false); // for now + } + } +} + +StaticResult Compiler::compile_new_static_structure(const goos::Object& form, + const TypeSpec& type, + const goos::Object& _field_defs, + Env* env) { + std::unique_ptr obj; + if (is_basic(type)) { + obj = std::make_unique(MAIN_SEGMENT, type.base_type()); + } else { + // if we ever find this type of static data outside of MAIN_SEGMENT, we can create an option + // in the new form to pick the segment. + obj = std::make_unique(MAIN_SEGMENT); + } + + auto type_info = dynamic_cast(m_ts.lookup_type(type)); + assert(type_info); + + obj->data.resize(type_info->get_size_in_memory()); + compile_static_structure_inline(form, type, _field_defs, obj.get(), 0, env); + auto fie = get_parent_env_of_type(env); + auto result = StaticResult::make_structure_reference(obj.get(), type); + fie->add_static(std::move(obj)); + return result; +} + +StaticResult Compiler::compile_static_bitfield(const goos::Object& form, + const TypeSpec& type, + const goos::Object& _field_defs, + Env* env) { u64 as_int = 0; auto type_info = dynamic_cast(m_ts.lookup_type(type)); @@ -117,120 +300,197 @@ Val* Compiler::compile_new_static_bitfield(const goos::Object& form, } } - return fe->alloc_val(type, as_int); + return StaticResult::make_constant_data(as_int, type); +} + +/*! + * Handles stuff in static pairs. Integers must be s32's. + * - Pairs + * - Empty Lists + * - Symbols + * - Integers + * - Strings + */ +StaticResult Compiler::compile_static_no_eval_for_pairs(const goos::Object& form, Env* env) { + auto fie = get_parent_env_of_type(env); + auto fe = get_parent_env_of_type(env); + auto segment = fe->segment; + if (segment == TOP_LEVEL_SEGMENT) { + segment = MAIN_SEGMENT; + } + if (form.is_pair()) { + auto car = compile_static_no_eval_for_pairs(form.as_pair()->car, env); + auto cdr = compile_static_no_eval_for_pairs(form.as_pair()->cdr, env); + auto pair_structure = std::make_unique(car, cdr, segment); + auto result = + StaticResult::make_structure_reference(pair_structure.get(), m_ts.make_typespec("pair")); + fie->add_static(std::move(pair_structure)); + return result; + } else if (form.is_int()) { + if (!integer_fits(form.as_int(), 4, true)) { + throw_compiler_error( + form, "Cannot store {} (0x{:x}) in a pair because it overflows a signed 32-bit integer.", + form.as_int(), form.as_int()); + } + return StaticResult::make_constant_data(form.as_int(), TypeSpec("int32")); + } else if (form.is_symbol()) { + return StaticResult::make_symbol(form.as_symbol()->name); + } else if (form.is_empty_list()) { + return StaticResult::make_symbol("_empty_"); + } else if (form.is_string()) { + // todo - this should eventually work with a string pool + auto obj = std::make_unique(form.as_string()->data, segment); + auto result = StaticResult::make_structure_reference(obj.get(), m_ts.make_typespec("string")); + fie->add_static(std::move(obj)); + return result; + } else { + assert(false); // not yet implemented + } +} + +/*! + * Generic copmilation function to handle: + * - (new 'static ), a reference + * + * - (new 'static '), an integer constant + * - (new 'static 'string), a string (not in the string pool, safe to modify) + * - '(...) a quoted pair + * - "a string" (goes in the string pool) + * - 'a-symbol + * - an integer + * - a float + * - a constant + * - #t or #f + */ +StaticResult Compiler::compile_static(const goos::Object& form, Env* env) { + auto fie = get_parent_env_of_type(env); + auto fe = get_parent_env_of_type(env); + auto segment = fe->segment; + if (segment == TOP_LEVEL_SEGMENT) { + segment = MAIN_SEGMENT; + } + + if (form.is_symbol()) { + // constant, #t, or #f + auto& name = form.as_symbol()->name; + if (name == "#t" || name == "#f") { + return StaticResult::make_symbol(name); + } + + // as a constant + auto kv = m_global_constants.find(form.as_symbol()); + if (kv != m_global_constants.end()) { + // expand constant and compile again. + return compile_static(kv->second, env); + } else { + throw_compiler_error(form, "The symbol {} could not be evaluated at compile time", + form.print()); + } + } else if (form.is_float()) { + u64 value = float_as_u32(form.as_float()); + return StaticResult::make_constant_data(value, TypeSpec("float")); + } else if (form.is_int()) { + return StaticResult::make_constant_data(form.as_int(), TypeSpec("integer")); + } else if (is_quoted_sym(form)) { + return StaticResult::make_symbol(unquote(form).as_symbol()->name); + } else if (form.is_string()) { + // todo string pool + auto obj = std::make_unique(form.as_string()->data, segment); + auto result = StaticResult::make_structure_reference(obj.get(), m_ts.make_typespec("string")); + fie->add_static(std::move(obj)); + return result; + } else if (form.is_pair()) { + auto first = form.as_pair()->car; + auto rest = form.as_pair()->cdr; + if (first.is_symbol() && first.as_symbol()->name == "quote") { + if (rest.is_pair()) { + auto second = rest.as_pair()->car; + if (!rest.as_pair()->cdr.is_empty_list()) { + throw_compiler_error(form, "The form {} is an invalid quoted form.", form.print()); + } + if (second.is_pair()) { + return compile_static_no_eval_for_pairs(second, env); + } else { + throw_compiler_error(form, "Could not evaluate the quoted form {} at compile time.", + second.print()); + } + } + throw_compiler_error(form, "The quoted form {} has no argument.", form.print()); + } else if (first.is_symbol() && first.as_symbol()->name == "new") { + goos::Object constructor_args; + auto args = get_list_as_vector(rest, &constructor_args, 2); + if (args.size() < 2) { + throw_compiler_error(form, + "New form evaluated at compile must specify (new 'static ...)"); + } + if (!is_quoted_sym(args.at(0)) || unquote(args.at(0)).as_symbol()->name != "static") { + throw_compiler_error(form, "New form evaluated at compile time must use 'static. Got {}.", + args.at(0).print()); + } + + if (!is_quoted_sym(args.at(1))) { + throw_compiler_error(form, "New form evaluated at compile got an invalid type: {}", + args.at(1).print()); + } + + auto ts = parse_typespec(unquote(args.at(1))); + if (ts == TypeSpec("string")) { + // (new 'static 'string) + if (rest.is_pair() && rest.as_pair()->cdr.is_empty_list() && + rest.as_pair()->car.is_string()) { + auto obj = std::make_unique(rest.as_pair()->car.as_string()->data, segment); + auto result = + StaticResult::make_structure_reference(obj.get(), m_ts.make_typespec("string")); + fie->add_static(std::move(obj)); + return result; + } else { + throw_compiler_error(form, "Invalid new static string"); + } + } else if (is_bitfield(ts)) { + return compile_static_bitfield(form, ts, constructor_args, env); + } else if (is_structure(ts)) { + return compile_new_static_structure(form, ts, constructor_args, env); + } else { + throw_compiler_error(form, "Cannot construct a static {}.", ts.print()); + } + } else { + // maybe an enum + s64 int_out; + if (try_getting_constant_integer(form, &int_out, env)) { + return StaticResult::make_constant_data(int_out, TypeSpec("int")); + } + } + } + + throw_compiler_error(form, "Could not evaluate {} at compile time.", form.print()); + return {}; +} + +Val* Compiler::compile_new_static_bitfield(const goos::Object& form, + const TypeSpec& type, + const goos::Object& _field_defs, + Env* env) { + auto fe = get_parent_env_of_type(env); + auto sr = compile_static_bitfield(form, type, _field_defs, env); + assert(sr.is_constant_data()); + return fe->alloc_val(sr.typespec(), sr.constant_data()); +} + +Val* Compiler::compile_static_pair(const goos::Object& form, Env* env) { + assert(form.is_pair()); // (quote PAIR) + auto result = compile_static_no_eval_for_pairs(form, env); + assert(result.is_reference()); + auto fe = get_parent_env_of_type(env); + auto static_result = fe->alloc_val(result.reference(), result.typespec()); + return static_result; } Val* Compiler::compile_new_static_structure_or_basic(const goos::Object& form, const TypeSpec& type, - const goos::Object& _field_defs, + const goos::Object& field_defs, Env* env) { auto fe = get_parent_env_of_type(env); - std::unique_ptr obj; - if (is_basic(type)) { - obj = std::make_unique(MAIN_SEGMENT, type.base_type()); - } else { - // if we ever find this type of static data outside of MAIN_SEGMENT, we can create an option - // in the new form to pick the segment. - obj = std::make_unique(MAIN_SEGMENT); - } - - auto type_info = dynamic_cast(m_ts.lookup_type(type)); - assert(type_info); // should always be at least a structure. - obj->data.resize(type_info->get_size_in_memory()); - // the file env will end up owning the obj. - auto result = fe->alloc_val(obj.get(), type); - - auto* field_defs = &_field_defs; - while (!field_defs->is_empty_list()) { - auto field_name_def = symbol_string(pair_car(*field_defs)); - field_defs = &pair_cdr(*field_defs); - - auto field_value = pair_car(*field_defs); - field_defs = &pair_cdr(*field_defs); - - if (field_name_def.at(0) != ':') { - throw_compiler_error( - form, "expected field def name to start with :, instead got " + field_name_def); - } - - field_name_def = field_name_def.substr(1); - auto field_info = m_ts.lookup_field_info(type_info->get_name(), field_name_def); - - if (field_info.field.is_dynamic() || field_info.field.is_inline() || - field_info.field.is_array()) { - throw_compiler_error(form, "Static objects not yet implemented for dynamic/inline/array"); - } - - auto field_offset = field_info.field.offset(); - assert(field_info.needs_deref); // for now... - auto deref_info = m_ts.get_deref_info(m_ts.make_pointer_typespec(field_info.type)); - auto field_size = deref_info.load_size; - assert(field_offset + field_size <= int(obj->data.size())); - - if (is_integer(field_info.type)) { - s64 value = 0; - if (!try_getting_constant_integer(field_value, &value, env)) { - throw_compiler_error(form, - "Field {} is an integer, but the value given couldn't be " - "converted to an integer at compile time.", - field_name_def); - } - - if (!integer_fits(value, deref_info.load_size, deref_info.sign_extend)) { - throw_compiler_error(form, - "Field {} is set to a compile time integer value of {} which would " - "overflow (size {} signed {})", - field_name_def, value, deref_info.load_size, deref_info.sign_extend); - } - - if (field_size == 1 || field_size == 2 || field_size == 4 || field_size == 8) { - memcpy(obj->data.data() + field_offset, &value, field_size); - } else { - // not sure how we can create 128-bit integer constants at this point... - assert(false); - } - } else if (is_basic(field_info.type)) { - if (is_quoted_sym(field_value)) { - obj->add_symbol_record(quoted_sym_as_string(field_value), field_offset); - assert(deref_info.mem_deref); - assert(deref_info.can_deref); - assert(deref_info.load_size == 4); - - // the linker needs to see a -1 in order to know to insert a symbol pointer - // instead of just the symbol table offset. - u32 linker_val = 0xffffffff; - memcpy(obj->data.data() + field_offset, &linker_val, 4); - } else if (field_value.is_symbol() && - (field_value.as_symbol()->name == "#t" || field_value.as_symbol()->name == "#f")) { - obj->add_symbol_record(symbol_string(field_value), field_offset); - assert(deref_info.mem_deref); - assert(deref_info.can_deref); - assert(deref_info.load_size == 4); - - // the linker needs to see a -1 in order to know to insert a symbol pointer - // instead of just the symbol table offset. - u32 linker_val = 0xffffffff; - memcpy(obj->data.data() + field_offset, &linker_val, 4); - } else { - throw_compiler_error( - form, "Setting a basic field to anything other than a symbol is currently unsupported"); - } - } else if (is_float(field_info.type)) { - float value = 0.f; - if (!try_getting_constant_float(field_value, &value, env)) { - throw_compiler_error(form, fmt::format("Field {} is a float, but the value given couldn't " - "be converted to a float at compile time.", - field_name_def)); - } - memcpy(obj->data.data() + field_offset, &value, sizeof(float)); - } - - else { - assert(false); // for now - } - } - - auto fie = get_parent_env_of_type(env); - fie->add_static(std::move(obj)); + auto sr = compile_new_static_structure(form, type, field_defs, env); + auto result = fe->alloc_val(sr.reference(), type); return result; } \ No newline at end of file diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index abc37590e..ed577f246 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -412,8 +412,9 @@ Val* Compiler::get_field_of_structure(const StructureType* type, result = fe->alloc_val(di.result_type, loc, MemLoadInfo(di)); result->mark_as_settable(); } else { - result = - fe->alloc_val(field.type, object, field.field.offset() + offset); + auto field_type_info = m_ts.lookup_type(field.type); + result = fe->alloc_val( + field.type, object, field.field.offset() + offset + field_type_info->get_offset()); result->mark_as_settable(); } return result; diff --git a/goalc/emitter/ObjectGenerator.cpp b/goalc/emitter/ObjectGenerator.cpp index 740a2213b..81f3c0960 100644 --- a/goalc/emitter/ObjectGenerator.cpp +++ b/goalc/emitter/ObjectGenerator.cpp @@ -90,6 +90,7 @@ ObjectFileData ObjectGenerator::generate_data_v3() { handle_temp_instr_sym_links(seg); handle_temp_rip_func_links(seg); handle_temp_rip_data_links(seg); + handle_temp_static_ptr_links(seg); } // actual linking? @@ -264,6 +265,23 @@ void ObjectGenerator::link_static_symbol_ptr(StaticRecord rec, m_static_sym_temp_links_by_seg.at(rec.seg)[name].push_back({rec, offset}); } +/*! + * Insert a pointer to other static data. This patching will happen during runtime linking. + * The source and destination must be in the same segment. + */ +void ObjectGenerator::link_static_pointer(const StaticRecord& source, + int source_offset, + const StaticRecord& dest, + int dest_offset) { + StaticPointerLink link; + link.source = source; + link.dest = dest; + link.offset_in_source = source_offset; + link.offset_in_dest = dest_offset; + assert(link.source.seg == link.dest.seg); + m_static_temp_ptr_links_by_seg.at(source.seg).push_back(link); +} + void ObjectGenerator::link_instruction_static(const InstructionRecord& instr, const StaticRecord& target_static, int offset) { @@ -309,6 +327,21 @@ void ObjectGenerator::handle_temp_static_sym_links(int seg) { } } +/*! + * m_static_temp_ptr_links_by_seg -> m_pointer_links_by_seg + */ +void ObjectGenerator::handle_temp_static_ptr_links(int seg) { + for (const auto& link : m_static_temp_ptr_links_by_seg.at(seg)) { + const auto& source_object = m_static_data_by_seg.at(seg).at(link.source.static_id); + const auto& dest_object = m_static_data_by_seg.at(seg).at(link.dest.static_id); + PointerLink result_link; + result_link.segment = seg; + result_link.source = source_object.location + link.offset_in_source; + result_link.dest = dest_object.location + link.offset_in_dest; + m_pointer_links_by_seg.at(seg).push_back(result_link); + } +} + /*! * m_jump_temp_links_by_seg patching after memory layout is done */ @@ -444,6 +477,17 @@ void ObjectGenerator::emit_link_symbol(int seg) { } } +void ObjectGenerator::emit_link_ptr(int seg) { + auto& out = m_link_by_seg.at(seg); + for (auto& rec : m_pointer_links_by_seg.at(seg)) { + out.push_back(LINK_PTR); + assert(rec.dest >= 0); + assert(rec.source >= 0); + push_data(rec.source, out); + push_data(rec.dest, out); + } +} + void ObjectGenerator::emit_link_rip(int seg) { auto& out = m_link_by_seg.at(seg); for (auto& rec : m_rip_links_by_seg.at(seg)) { @@ -476,6 +520,7 @@ void ObjectGenerator::emit_link_table(int seg) { emit_link_symbol(seg); emit_link_type_pointer(seg); emit_link_rip(seg); + emit_link_ptr(seg); m_link_by_seg.at(seg).push_back(LINK_TABLE_END); } diff --git a/goalc/emitter/ObjectGenerator.h b/goalc/emitter/ObjectGenerator.h index fb53be067..53f184a4c 100644 --- a/goalc/emitter/ObjectGenerator.h +++ b/goalc/emitter/ObjectGenerator.h @@ -77,7 +77,10 @@ class ObjectGenerator { void link_instruction_symbol_mem(const InstructionRecord& rec, const std::string& name); void link_instruction_symbol_ptr(const InstructionRecord& rec, const std::string& name); void link_static_symbol_ptr(StaticRecord rec, int offset, const std::string& name); - + void link_static_pointer(const StaticRecord& source, + int source_offset, + const StaticRecord& dest, + int dest_offset); void link_instruction_static(const InstructionRecord& instr, const StaticRecord& target_static, int offset); @@ -91,11 +94,13 @@ class ObjectGenerator { void handle_temp_static_sym_links(int seg); void handle_temp_rip_data_links(int seg); void handle_temp_rip_func_links(int seg); + void handle_temp_static_ptr_links(int seg); void emit_link_table(int seg); void emit_link_type_pointer(int seg); void emit_link_symbol(int seg); void emit_link_rip(int seg); + void emit_link_ptr(int seg); std::vector generate_header_v3(); template @@ -139,6 +144,13 @@ class ObjectGenerator { int offset = -1; }; + struct StaticPointerLink { + StaticRecord source; + int offset_in_source = -1; + StaticRecord dest; + int offset_in_dest = -1; + }; + struct SymbolInstrLink { InstructionRecord rec; bool is_mem_access = false; @@ -166,6 +178,13 @@ class ObjectGenerator { IR_Record dest; }; + struct PointerLink { + int segment = -1; + // both in bytes. + int source = -1; + int dest = -1; + }; + template using seg_vector = std::array, N_SEG>; @@ -185,6 +204,7 @@ class ObjectGenerator { seg_vector m_jump_temp_links_by_seg; seg_map m_symbol_instr_temp_links_by_seg; seg_map m_static_sym_temp_links_by_seg; + seg_vector m_static_temp_ptr_links_by_seg; seg_vector m_rip_func_temp_links_by_seg; seg_vector m_rip_data_temp_links_by_seg; @@ -192,6 +212,7 @@ class ObjectGenerator { seg_map m_type_ptr_links_by_seg; seg_map m_sym_links_by_seg; seg_vector m_rip_links_by_seg; + seg_vector m_pointer_links_by_seg; std::vector m_all_function_records; }; diff --git a/test/goalc/source_templates/with_game/test-fancy-static-fields.gc b/test/goalc/source_templates/with_game/test-fancy-static-fields.gc new file mode 100644 index 000000000..10e92b07e --- /dev/null +++ b/test/goalc/source_templates/with_game/test-fancy-static-fields.gc @@ -0,0 +1,31 @@ +(deftype basic-field-test-bitfield (int32) + ((f1 uint8 :offset 0 :size 1) + (f2 uint8 :offset 1 :size 1) + (f3 uint8 :offset 2 :size 1) + ) + ) + +(deftype basic-field-test-type (basic) + ((name string) + (kc kernel-context) + (kci kernel-context :inline) + (pos float) + (bf int32) + (list pair)) + ) + + +(let ((obj (new 'static 'basic-field-test-type + :pos 12.34 + :list '(a b c ) + :name "name" + :bf (new 'static 'basic-field-test-bitfield :f1 1 :f3 1) + :kc (new 'static 'kernel-context :next-pid 12) + :kci (new 'static 'kernel-context :current-process 'asdf :relocating-min 33)))) + (format #t "~A ~D ~f ~A ~D" (-> obj name) (-> obj kc next-pid) (-> obj pos) (-> obj list) (-> obj bf)) + (format #t " ~D ~D ~A ~A~%" + (-> obj kci relocating-min) + (logand 15 (the int (-> obj kci))) + (-> obj kci type) + (-> obj kci current-process)) + ) \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-new-static-basic.gc b/test/goalc/source_templates/with_game/test-new-static-basic.gc index fc3790ca5..1e1976331 100644 --- a/test/goalc/source_templates/with_game/test-new-static-basic.gc +++ b/test/goalc/source_templates/with_game/test-new-static-basic.gc @@ -6,10 +6,11 @@ (thing basic) (thing-2 basic) (thing-3 basic) + (thing-4 float) (u64 uint64)) ) -(define test-static-basic (new 'static 'static-test-basic-type :s8 -122 :s16 -23123 :u64 434343 :thing 'bean :thing-2 #t :thing-3 #f)) +(define test-static-basic (new 'static 'static-test-basic-type :s8 -122 :s16 -23123 :thing-4 1.2 :u64 434343 :thing 'bean :thing-2 #t :thing-3 #f)) (expect-true (< (the int test-static-basic) (-> debug current))) ;; should be in debug segment? (expect-true (> (the int test-static-basic) (-> debug base))) @@ -23,5 +24,6 @@ (expect-true (neq? (-> test-static-basic thing) 'not-bean)) (expect-true (eq? (-> test-static-basic type symbol) 'static-test-basic-type)) (expect-true (eq? (-> test-static-basic type) static-test-basic-type)) +(expect-true (eq? (-> test-static-basic thing-4) 1.2)) (finish-test) \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-static-pair-1.gc b/test/goalc/source_templates/with_game/test-static-pair-1.gc new file mode 100644 index 000000000..12964bbc6 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-static-pair-1.gc @@ -0,0 +1,7 @@ +(defun test-static-pair-function () + (format #t "~A~%" '(8 ( w . a ) beans 16 (-8 -16) twelve ( a . "test"))) + 0 +) + +(test-static-pair-function) + diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 515f1fc82..01767e2b3 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -259,7 +259,7 @@ TEST_F(WithGameTests, NewStaticStructureIntegers) { TEST_F(WithGameTests, NewStaticBasic) { runner.run_static_test(env, testCategory, "test-new-static-basic.gc", - get_test_pass_string("new-static-basic", 11)); + get_test_pass_string("new-static-basic", 12)); } TEST_F(WithGameTests, VectorDot) { @@ -324,6 +324,16 @@ TEST_F(WithGameTests, Math) { runner.run_static_test(env, testCategory, "test-math.gc", get_test_pass_string("math", 31)); } +TEST_F(WithGameTests, StaticPairs) { + runner.run_static_test(env, testCategory, "test-static-pair-1.gc", + {"(1 (w . a) beans 2 (-1 -2) twelve (a . \"test\"))\n0\n"}); +} + +TEST_F(WithGameTests, FancyStatic) { + runner.run_static_test(env, testCategory, "test-fancy-static-fields.gc", + {"\"name\" 12 12.3400 (a b c) 5 33 4 kernel-context asdf\n0\n"}); +} + TEST(TypeConsistency, TypeConsistency) { Compiler compiler; compiler.enable_throw_on_redefines();