Add boxed array type (#163)

* add array

* support static arrays
This commit is contained in:
water111 2020-12-19 21:05:18 -05:00 committed by GitHub
parent 11a82bbf08
commit 3355df809a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 250 additions and 40 deletions

View file

@ -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.

View file

@ -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<std::string>& arg_types,
const std::string& return_type) const;

View file

@ -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.
- 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 <elt-type>)` is used to describe an array with a given content type.

View file

@ -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

View file

@ -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<FunctionEnv>(env)->segment);
auto segment = get_parent_env_of_type<FunctionEnv>(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<FunctionEnv>(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<FunctionEnv>(env)->segment);
return compile_float(code.float_obj.value, env, segment);
}
/*!

View file

@ -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<StaticString>(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<StaticBasic>(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<StaticString>(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

View file

@ -474,8 +474,19 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest
auto struct_type = dynamic_cast<StructureType*>(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<BitFieldType*>(type_info);
@ -511,6 +522,34 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest
auto loc = fe->alloc_val<MemoryOffsetVal>(result->type(), result, offset);
result = fe->alloc_val<MemoryDerefVal>(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<IR_IntegerMath>(IntegerMathKind::IMUL_32, stride, index_value));
env->emit_ir<IR_IntegerMath>(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<MemoryOffsetVal>(loc_type, result, offset);
// and result type.
result = fe->alloc_val<MemoryDerefVal>(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<FunctionEnv>(env);
auto sr = compile_static(form, env);
auto result = fe->alloc_val<StaticVal>(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,

View file

@ -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

View file

@ -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
)

View file

@ -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();