mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 11:26:18 -04:00
parent
11a82bbf08
commit
3355df809a
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
)
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue