diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 74fde36fd..20bafccee 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -194,6 +194,10 @@ bool TypeSystem::partially_defined_type_exists(const std::string& name) const { return m_forward_declared_types.find(name) != m_forward_declared_types.end(); } +TypeSpec TypeSystem::make_array_typespec(const TypeSpec& element_type) const { + return TypeSpec("array", {element_type}); +} + /*! * Create a typespec for a function. If the function doesn't return anything, use "none" as the * return type. diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index e4efe9a57..ea3cc90d2 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -89,6 +89,7 @@ class TypeSystem { bool fully_defined_type_exists(const std::string& name) const; bool partially_defined_type_exists(const std::string& name) const; TypeSpec make_typespec(const std::string& name) const; + TypeSpec make_array_typespec(const TypeSpec& element_type) const; TypeSpec make_function_typespec(const std::vector& arg_types, const std::string& return_type) const; diff --git a/doc/changelog.md b/doc/changelog.md index 83128c4b8..b8306b8fd 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -86,4 +86,6 @@ - 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 +- Fixed a bug where accessing an inline basic field did not apply the 4-byte basic offset. +- Made string/float constants go in the main segment when they are declared in the top-level segment, instead of the top-level segment. This is what GOAL seems to do (not 100% sure yet) and avoids issues where you set something to a string constant in the top-level. This avoids the possibility of memory bugs at the cost of more memory usage (likely very little additional memory). +- Added support for boxed arrays. They can be created with `new` and indexed with `->`. The compound type `(array )` is used to describe an array with a given content type. diff --git a/goal_src/kernel/gcommon.gc b/goal_src/kernel/gcommon.gc index 79a934a5e..2b96e69b4 100644 --- a/goal_src/kernel/gcommon.gc +++ b/goal_src/kernel/gcommon.gc @@ -549,16 +549,41 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; array (todo) +;; array ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; boxed "pointer like" array. +;; unlike inline-array-class, all arrays are type array, but the content-type field stores the element type. +;; the stride of an array is 4, unless the element is a number, in which case the stride is the "size" -#| -(defun segfault-function () - "Function which segfaults." - ;; this is added only to test debugging stuff and isn't in the game. - (segfault) +(defmethod new array ((allocation symbol) (type-to-make type) (content-type type) (size int)) + "Create a new array. The length and allocated-length are both set to size." + (let ((obj (object-new (* size (if (type-type? content-type number) + (-> content-type size) + 4 + ))))) + (set! (-> obj length) size) + (set! (-> obj allocated-length) size) + (set! (-> obj content-type) content-type) + obj + ) ) -|# + +;; todo array print and array inspect + +(defmethod length array ((obj array)) + "Get the length field of an array." + (-> obj length) + ) + +(defmethod asize-of array ((obj array)) + "Get the size in memory of an array." + (the int (+ (-> array size) (* (-> obj allocated-length) + (if (type-type? (-> obj content-type) number) + (-> obj content-type size) + 4)))) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; memcpy and similar diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 2b09b5e7b..359242b02 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -302,8 +302,11 @@ Val* Compiler::compile_symbol(const goos::Object& form, Env* env) { * Compile a string constant. The constant is placed in the same segment as the parent function. */ Val* Compiler::compile_string(const goos::Object& form, Env* env) { - return compile_string(form.as_string()->data, env, - get_parent_env_of_type(env)->segment); + auto segment = get_parent_env_of_type(env)->segment; + if (segment == TOP_LEVEL_SEGMENT) { + segment = MAIN_SEGMENT; + } + return compile_string(form.as_string()->data, env, segment); } /*! @@ -324,9 +327,12 @@ Val* Compiler::compile_string(const std::string& str, Env* env, int seg) { * of the code, at least in Jak 1. */ Val* Compiler::compile_float(const goos::Object& code, Env* env) { + auto segment = get_parent_env_of_type(env)->segment; + if (segment == TOP_LEVEL_SEGMENT) { + segment = MAIN_SEGMENT; + } assert(code.is_float()); - return compile_float(code.float_obj.value, env, - get_parent_env_of_type(env)->segment); + return compile_float(code.float_obj.value, env, segment); } /*! diff --git a/goalc/compiler/compilation/Static.cpp b/goalc/compiler/compilation/Static.cpp index 5d65acb22..06b5dfa1d 100644 --- a/goalc/compiler/compilation/Static.cpp +++ b/goalc/compiler/compilation/Static.cpp @@ -433,25 +433,92 @@ StaticResult Compiler::compile_static(const goos::Object& form, Env* env) { 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"); + if (unquote(args.at(1)).as_symbol()->name == "boxed-array") { + // (new 'static 'boxed-array ...) + // get all arguments now + args = get_list_as_vector(rest, &constructor_args); + if (args.size() < 4) { + throw_compiler_error(form, + "new static boxed array must have type and min-size arguments"); } - } 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); + auto content_type = parse_typespec(args.at(2)); + s64 min_size; + if (!try_getting_constant_integer(args.at(3), &min_size, env)) { + throw_compiler_error(form, "The length {} is not valid.", args.at(3).print()); + } + s32 length = std::max(min_size, s64(args.size() - 4)); + // todo - generalize this array stuff if we ever need other types of static arrays. + auto pointer_type = m_ts.make_pointer_typespec(content_type); + auto deref_info = m_ts.get_deref_info(pointer_type); + assert(deref_info.can_deref); + assert(deref_info.mem_deref); + auto array_size_bytes = length * deref_info.stride; + // todo, segments + auto obj = std::make_unique(MAIN_SEGMENT, "array"); + obj->data.resize(16 + array_size_bytes); + // 0 - 4 : type tag (set automatically) + // 4 - 8 : length + memcpy(obj->data.data() + 4, &length, 4); + // 8 - 12 allocated length + memcpy(obj->data.data() + 8, &length, 4); + // 12 - 16 content type + obj->add_type_record(content_type.base_type(), 12); + + // now add arguments: + for (size_t i = 4; i < args.size(); i++) { + int arg_idx = i - 4; + int elt_offset = 16 + arg_idx * deref_info.stride; + auto sr = compile_static(args.at(i), env); + if (is_integer(content_type)) { + typecheck(form, TypeSpec("integer"), sr.typespec()); + } else { + typecheck(form, content_type, sr.typespec()); + } + if (sr.is_symbol()) { + assert(deref_info.stride == 4); + obj->add_symbol_record(sr.symbol_name(), elt_offset); + u32 symbol_placeholder = 0xffffffff; + memcpy(obj->data.data() + elt_offset, &symbol_placeholder, 4); + } else if (sr.is_reference()) { + assert(deref_info.stride == 4); + obj->add_pointer_record(elt_offset, sr.reference(), sr.reference()->get_addr_offset()); + } else if (sr.is_constant_data()) { + if (!integer_fits(sr.constant_data(), deref_info.load_size, deref_info.sign_extend)) { + throw_compiler_error(form, "The integer {} doesn't fit in element {} of array of {}", + sr.constant_data(), arg_idx, content_type.print()); + } + u64 data = sr.constant_data(); + memcpy(obj->data.data() + elt_offset, &data, deref_info.load_size); + } else { + assert(false); + } + } + auto result = StaticResult::make_structure_reference( + obj.get(), m_ts.make_array_typespec(content_type)); + fie->add_static(std::move(obj)); + return result; } else { - throw_compiler_error(form, "Cannot construct a static {}.", ts.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 diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index ed577f246..4dc0cf9e6 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -474,8 +474,19 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest auto struct_type = dynamic_cast(type_info); if (struct_type) { - result = get_field_of_structure(struct_type, result, field_name, env); - continue; + if (result->type().base_type() == "array") { + // this is an ugly hack, but I don't know a better way. For things that are both + // array-indexable and a structure, we treat it like a structure only if the + // deref thing is one of the field names. Otherwise, array. + if (field_name == "content-type" || field_name == "length" || + field_name == "allocated-length") { + result = get_field_of_structure(struct_type, result, field_name, env); + continue; + } + } else { + result = get_field_of_structure(struct_type, result, field_name, env); + continue; + } } auto bitfield_type = dynamic_cast(type_info); @@ -511,6 +522,34 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest auto loc = fe->alloc_val(result->type(), result, offset); result = fe->alloc_val(di.result_type, loc, MemLoadInfo(di)); result->mark_as_settable(); + } else if (result->type().base_type() == "array") { + // accessing an array. this is a bit weird. + if (!result->type().has_single_arg()) { + throw_compiler_error(form, "The array type {} is invalid or cannot be indexed.", + result->type().print()); + } + // the (pointer element-type) + auto loc_type = m_ts.make_pointer_typespec(result->type().get_single_arg()); + // figure out how to access this... + auto di = m_ts.get_deref_info(loc_type); + // and the result + auto base_type = di.result_type; + assert(base_type == result->type().get_single_arg()); + assert(di.mem_deref); + assert(di.can_deref); + // the total offset is 12 + stride * idx + auto offset = compile_integer(12, env)->to_gpr(env); + auto stride = compile_integer(di.stride, env)->to_gpr(env); + env->emit(std::make_unique(IntegerMathKind::IMUL_32, stride, index_value)); + env->emit_ir(IntegerMathKind::ADD_64, offset, stride); + // offset now contains the total offset. + + // create a location to deref (so we can do address-of and get this), with pointer type + auto loc = fe->alloc_val(loc_type, result, offset); + // and result type. + result = fe->alloc_val(di.result_type, loc, MemLoadInfo(di)); + // array values should be settable + result->mark_as_settable(); } else { throw_compiler_error(form, "Cannot access array of type {}.", result->type().print()); } @@ -605,7 +644,12 @@ Val* Compiler::compile_heap_new(const goos::Object& form, const goos::Object& type, const goos::Object* rest, Env* env) { - auto main_type = parse_typespec(unquote(type)); + bool making_boxed_array = unquote(type).as_symbol()->name == "boxed-array"; + TypeSpec main_type; + if (!making_boxed_array) { + main_type = parse_typespec(unquote(type)); + } + if (main_type == TypeSpec("inline-array") || main_type == TypeSpec("array")) { bool is_inline = main_type == TypeSpec("inline-array"); auto elt_type = quoted_sym_as_string(pair_car(*rest)); @@ -647,6 +691,12 @@ Val* Compiler::compile_heap_new(const goos::Object& form, array->set_type(ts); return array; } else { + bool got_content_type = false; // for boxed array + std::string content_type; // for boxed array. + if (making_boxed_array) { + main_type = TypeSpec("array"); + } + if (!m_ts.lookup_type(main_type)->is_reference()) { throw_compiler_error(form, "Cannot heap allocate the value type {}.", main_type.print()); } @@ -657,12 +707,27 @@ Val* Compiler::compile_heap_new(const goos::Object& form, args.push_back(compile_get_symbol_value(form, main_type.base_type(), env)->to_reg(env)); // the other arguments for_each_in_list(*rest, [&](const goos::Object& o) { - args.push_back(compile_error_guard(o, env)->to_reg(env)); + if (making_boxed_array && !got_content_type) { + got_content_type = true; + if (o.is_symbol()) { + content_type = o.as_symbol()->name; + args.push_back(compile_get_symbol_value(form, content_type, env)->to_reg(env)); + } else { + throw_compiler_error(form, "Invalid boxed-array type {}", o.print()); + } + } else { + args.push_back(compile_error_guard(o, env)->to_reg(env)); + } }); auto new_method = compile_get_method_of_type(form, main_type, "new", env); auto new_obj = compile_real_function_call(form, new_method, args, env); - new_obj->set_type(main_type); + if (making_boxed_array) { + new_obj->set_type(m_ts.make_array_typespec(m_ts.make_typespec(content_type))); + } else { + new_obj->set_type(main_type); + } + return new_obj; } } @@ -671,13 +736,21 @@ Val* Compiler::compile_static_new(const goos::Object& form, const goos::Object& type, const goos::Object* rest, Env* env) { - auto type_of_object = parse_typespec(unquote(type)); - if (is_structure(type_of_object)) { - return compile_new_static_structure_or_basic(form, type_of_object, *rest, env); - } + auto unquoted = unquote(type); + if (unquoted.is_symbol() && unquoted.as_symbol()->name == "boxed-array") { + auto fe = get_parent_env_of_type(env); + auto sr = compile_static(form, env); + auto result = fe->alloc_val(sr.reference(), sr.typespec()); + return result; + } else { + auto type_of_object = parse_typespec(unquote(type)); + if (is_structure(type_of_object)) { + return compile_new_static_structure_or_basic(form, type_of_object, *rest, env); + } - if (is_bitfield(type_of_object)) { - return compile_new_static_bitfield(form, type_of_object, *rest, env); + if (is_bitfield(type_of_object)) { + return compile_new_static_bitfield(form, type_of_object, *rest, env); + } } throw_compiler_error(form, diff --git a/test/goalc/source_templates/with_game/test-integer-boxed-array.gc b/test/goalc/source_templates/with_game/test-integer-boxed-array.gc new file mode 100644 index 000000000..1d3498ecc --- /dev/null +++ b/test/goalc/source_templates/with_game/test-integer-boxed-array.gc @@ -0,0 +1,13 @@ +(let ((arr (new 'global 'boxed-array int16 12))) + (dotimes (i 12) + (set! (-> arr i) (* i 2)) + ) + (dotimes (i 12) + (format #t "~D ~D " i (-> arr i)) + ) + (format #t "~D ~D ~D~%" (length arr) (asize-of arr) (&- (&-> arr 4) (&-> arr 1))) + ) + +;; asize should be... +;; 16 + 12 * 2 = 40 +;; ptr diff is 3 * 2 = 6 \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-static-boxed-array.gc b/test/goalc/source_templates/with_game/test-static-boxed-array.gc new file mode 100644 index 000000000..f9830e328 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-static-boxed-array.gc @@ -0,0 +1,8 @@ +(let ((arr (new 'static 'boxed-array object 12 32 'asdf "test" '( a b )))) + (dotimes (i 5) + (format #t "~A " (-> arr i)) + ) + (format #t "~A " (-> arr content-type)) + (format #t "~D ~D~%" (-> arr length) (-> arr allocated-length)) + 0 + ) \ No newline at end of file diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 01767e2b3..0e2150c15 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -334,6 +334,17 @@ TEST_F(WithGameTests, FancyStatic) { {"\"name\" 12 12.3400 (a b c) 5 33 4 kernel-context asdf\n0\n"}); } +TEST_F(WithGameTests, IntegerBoxedArray) { + runner.run_static_test( + env, testCategory, "test-integer-boxed-array.gc", + {"0 0 1 2 2 4 3 6 4 8 5 10 6 12 7 14 8 16 9 18 10 20 11 22 12 40 6\n0\n"}); +} + +TEST_F(WithGameTests, StaticBoxedArray) { + runner.run_static_test(env, testCategory, "test-static-boxed-array.gc", + {"4 asdf \"test\" (a b) 0 object 12 12\n0\n"}); +} + TEST(TypeConsistency, TypeConsistency) { Compiler compiler; compiler.enable_throw_on_redefines();