From 2ad041cfd7c4e8eeca952b6fdbdd6d5a8f1f5bb1 Mon Sep 17 00:00:00 2001 From: water Date: Wed, 14 Oct 2020 13:30:29 -0400 Subject: [PATCH 1/2] implement some array stuff and clean up field access --- common/goos/Object.h | 7 + goal_src/kernel-defs.gc | 2 +- goalc/compiler/Compiler.h | 8 + goalc/compiler/Util.cpp | 11 ++ goalc/compiler/compilation/Type.cpp | 165 +++++++++++++++--- .../with_game/test-new-array.gc | 37 ++++ test/goalc/test_with_game.cpp | 15 +- 7 files changed, 215 insertions(+), 30 deletions(-) create mode 100644 test/goalc/source_templates/with_game/test-new-array.gc diff --git a/common/goos/Object.h b/common/goos/Object.h index 8e50ae9e97..13b0e2584d 100644 --- a/common/goos/Object.h +++ b/common/goos/Object.h @@ -285,6 +285,13 @@ class Object { return integer_obj.value; } + const IntType& as_int() const { + if (type != ObjectType::INTEGER) { + throw std::runtime_error("as_int called on a " + object_type_to_string(type) + " " + print()); + } + return integer_obj.value; + } + FloatType& as_float() { if (type != ObjectType::FLOAT) { throw std::runtime_error("as_float called on a " + object_type_to_string(type) + " " + diff --git a/goal_src/kernel-defs.gc b/goal_src/kernel-defs.gc index 9a182a9a9b..cfaa8d9eec 100644 --- a/goal_src/kernel-defs.gc +++ b/goal_src/kernel-defs.gc @@ -72,7 +72,7 @@ (define-extern loado (function string kheap object)) (define-extern unload (function string none)) (define-extern _format (function _varargs_ object)) -(define-extern malloc (function kheap int pointer)) +(define-extern malloc (function symbol int pointer)) (define-extern kmalloc (function kheap int int string)) (define-extern new-dynamic-structure (function kheap type int structure)) (define-extern method-set! (function type int function none)) ;; may actually return function. diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 56d4b46281..1720f4e45c 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -50,6 +50,12 @@ class Compiler { Val* compile_string(const std::string& str, Env* env, int seg = MAIN_SEGMENT); Val* compile_get_symbol_value(const std::string& name, Env* env); Val* compile_function_or_method_call(const goos::Object& form, Env* env); + + Val* get_field_of_structure(const StructureType* type, + Val* object, + const std::string& field_name, + Env* env); + SymbolVal* compile_get_sym_obj(const std::string& name, Env* env); void color_object_file(FileEnv* env); std::vector codegen_object_file(FileEnv* env); @@ -84,6 +90,8 @@ class Compiler { Env* env, const std::string& method_type_name = ""); + bool try_getting_constant_integer(const goos::Object& in, int64_t* out, Env* env); + TypeSystem m_ts; std::unique_ptr m_global_env = nullptr; std::unique_ptr m_none = nullptr; diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index 0c37525b47..d2b4cf070d 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -184,4 +184,15 @@ bool Compiler::is_none(Val* in) { bool Compiler::is_basic(const TypeSpec& ts) { return m_ts.typecheck(m_ts.make_typespec("basic"), ts, "", false, false); +} + +bool Compiler::try_getting_constant_integer(const goos::Object& in, int64_t* out, Env* env) { + (void)env; + if (in.is_int()) { + *out = in.as_int(); + return true; + } + + // todo, try more things before giving up. + return false; } \ No newline at end of file diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index 5426811667..3ca6d014e4 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -2,6 +2,10 @@ #include "common/type_system/deftype.h" namespace { + +/*! + * Given a method id, get the offset in bytes from the GOAL type to the method pointer. + */ int get_offset_of_method(int id) { // todo - something that looks at the type system? // this will need changing if the layout of type ever changes. @@ -9,6 +13,10 @@ int get_offset_of_method(int id) { } } // namespace +/*! + * Given a type and method name (known at compile time), get the method. + * This can be used for method calls where the type is unknown at run time (non-virtual method call) + */ RegVal* Compiler::compile_get_method_of_type(const TypeSpec& type, const std::string& method_name, Env* env) { @@ -34,6 +42,11 @@ RegVal* Compiler::compile_get_method_of_type(const TypeSpec& type, return deref->to_reg(env); } +/*! + * Given an object, get a method. If at compile time we know it's a basic, we use its runtime + * type to look up the method at runtime (virtual call). If we don't know it's a basic, we get the + * method from the compile-time type. (fixed type non-virtual call) + */ RegVal* Compiler::compile_get_method_of_object(RegVal* object, const std::string& method_name, Env* env) { @@ -72,27 +85,41 @@ RegVal* Compiler::compile_get_method_of_object(RegVal* object, return deref->to_reg(env); } +/*! + * Compile a (deftype ... form) + */ Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& rest, Env* env) { (void)form; (void)env; + // parse the type definition and add to the type system auto result = parse_deftype(rest, &m_ts); + // look up the type name auto kv = m_symbol_types.find(result.type.base_type()); if (kv != m_symbol_types.end() && kv->second.base_type() != "type") { + // we already have something that's not a type with the same name, this is bad. fmt::print("[Warning] deftype will redefined {} from {} to a type.\n", result.type.base_type(), kv->second.print()); } + // remember that this is a type m_symbol_types[result.type.base_type()] = m_ts.make_typespec("type"); + // get the new method of type object. this is new_type in kscheme.cpp auto new_type_method = compile_get_method_of_type(m_ts.make_typespec("type"), "new", env); + // call (new 'type 'type-name parent-type flags) auto new_type_symbol = compile_get_sym_obj(result.type.base_type(), env)->to_gpr(env); auto parent_type = compile_get_symbol_value(result.type_info->get_parent(), env)->to_gpr(env); auto flags_int = compile_integer(result.flags.flag, env)->to_gpr(env); - return compile_real_function_call(form, new_type_method, - {new_type_symbol, parent_type, flags_int}, env); + compile_real_function_call(form, new_type_method, {new_type_symbol, parent_type, flags_int}, env); + + // return none, making the value of (deftype..) unusable + return get_none(); } +/*! + * Compile a (defmethod ...) form + */ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _rest, Env* env) { auto fe = get_parent_env_of_type(env); auto* rest = &_rest; @@ -115,20 +142,24 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ auto& lambda = place->lambda; auto lambda_ts = m_ts.make_typespec("function"); - // parse the argument list. + // parse the argument list. todo, we could check the type of the first argument here? for_each_in_list(arg_list, [&](const goos::Object& o) { if (o.is_symbol()) { // if it has no type, assume object. lambda.params.push_back({symbol_string(o), m_ts.make_typespec("object")}); lambda_ts.add_arg(m_ts.make_typespec("object")); } else { + // type of argument is specified auto param_args = get_va(o, o); va_check(o, param_args, {goos::ObjectType::SYMBOL, goos::ObjectType::SYMBOL}, {}); GoalArg parm; parm.name = symbol_string(param_args.unnamed.at(0)); parm.type = parse_typespec(param_args.unnamed.at(1)); + // before substituting _type_ lambda_ts.add_arg(parm.type); + + // replace _type_ as needed for inside this function. parm.type = parm.type.substitute_for_method_call(symbol_string(type_name)); lambda.params.push_back(parm); } @@ -150,7 +181,9 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ new_func_env->method_of_type_name = symbol_string(type_name); // set up arguments - assert(lambda.params.size() < 8); // todo graceful error + if (lambda.params.size() > 8) { + throw_compile_error(form, "Methods cannot have more than 8 arguments"); + } std::vector args_for_coloring; for (u32 i = 0; i < lambda.params.size(); i++) { IRegConstraint constr; @@ -210,6 +243,47 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _ return compile_real_function_call(form, method_set_val, {type_obj, id_val, method_val}, env); } +/*! + * Given a type, object, and field name, get the value of the field. + */ +Val* Compiler::get_field_of_structure(const StructureType* type, + Val* object, + const std::string& field_name, + Env* env) { + auto fe = get_parent_env_of_type(env); + Val* result = nullptr; + int offset = -type->get_offset(); + auto field = m_ts.lookup_field_info(type->get_name(), field_name); + if (field.needs_deref) { + TypeSpec loc_type = m_ts.make_pointer_typespec(field.type); + auto loc = + fe->alloc_val(loc_type, object, field.field.offset() + offset); + auto di = m_ts.get_deref_info(loc_type); + assert(di.can_deref); + assert(di.mem_deref); + 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); + result->mark_as_settable(); + } + return result; +} + +/*! + * Compile the (-> ...) form. + * This is kind of a mess because of the huge number of things you can do with this form: + * - dereference a pointer + * - Access a field of a type + * - Access an element of an array or inline-array + * and they nest in confusing and ambiguous ways. + * The current behavior is that there is exactly one dereference performed per thing in the list. + * so if field x has type (pointer y), (-> obj x) gives a (pointer y), not a y. + * + * The result of this should give something that has enough information to read/write the original + * location. Otherwise set! or & won't work. + */ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest, Env* env) { auto fe = get_parent_env_of_type(env); if (_rest.is_empty_list()) { @@ -251,23 +325,7 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest auto struct_type = dynamic_cast(type_info); if (struct_type) { - int offset = -struct_type->get_offset(); - auto field = m_ts.lookup_field_info(type_info->get_name(), field_name); - if (field.needs_deref) { - TypeSpec loc_type = m_ts.make_pointer_typespec(field.type); - auto loc = fe->alloc_val(loc_type, result, - field.field.offset() + offset); - auto di = m_ts.get_deref_info(loc_type); - assert(di.can_deref); - assert(di.mem_deref); - result = fe->alloc_val(di.result_type, loc, MemLoadInfo(di)); - result->mark_as_settable(); - } else { - result = fe->alloc_val(field.type, result, - field.field.offset() + offset); - result->mark_as_settable(); - // assert(false); - } + result = get_field_of_structure(struct_type, result, field_name, env); continue; } @@ -299,6 +357,9 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest return result; } +/*! + * Compile the (& x) form. + */ Val* Compiler::compile_addr_of(const goos::Object& form, const goos::Object& rest, Env* env) { auto args = get_va(form, rest); va_check(form, args, {{}}, {}); @@ -310,6 +371,10 @@ Val* Compiler::compile_addr_of(const goos::Object& form, const goos::Object& res return as_mem_deref->base; } +/*! + * Compile the (the-as x y) form. Like a reinterpret cast. + * Will always produce a alias (so setting the result of this affects the base). + */ Val* Compiler::compile_the_as(const goos::Object& form, const goos::Object& rest, Env* env) { auto args = get_va(form, rest); va_check(form, args, {{}, {}}, {}); @@ -322,6 +387,11 @@ Val* Compiler::compile_the_as(const goos::Object& form, const goos::Object& rest return result; } +/*! + * Compile the (the x y) form. Like reinterpret case, but numbers (int bint float) will be + * converted. In the case of numeric version it won't alias. But all other cases alias, which is + * confusing. + */ Val* Compiler::compile_the(const goos::Object& form, const goos::Object& rest, Env* env) { auto args = get_va(form, rest); va_check(form, args, {{}, {}}, {}); @@ -351,6 +421,9 @@ Val* Compiler::compile_the(const goos::Object& form, const goos::Object& rest, E return result; } +/*! + * Debug util (print-type x) to compile x then print the type name at compile time. + */ Val* Compiler::compile_print_type(const goos::Object& form, const goos::Object& rest, Env* env) { auto args = get_va(form, rest); va_check(form, args, {{}}, {}); @@ -359,6 +432,8 @@ Val* Compiler::compile_print_type(const goos::Object& form, const goos::Object& } Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest, Env* env) { + // todo - support compound types. + auto allocation = quoted_sym_as_string(pair_car(_rest)); auto rest = &pair_cdr(_rest); @@ -367,10 +442,50 @@ Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest, rest = &pair_cdr(*rest); if (allocation == "global" || allocation == "debug") { - if (type_as_string == "inline-array") { - assert(false); - } else if (type_as_string == "array") { - assert(false); + // allocate on a named heap + + if (type_as_string == "inline-array" || type_as_string == "array") { + bool is_inline = type_as_string == "inline-array"; + auto elt_type = quoted_sym_as_string(pair_car(*rest)); + rest = &pair_cdr(*rest); + + auto count_obj = pair_car(*rest); + rest = &pair_cdr(*rest); + // try to get the size as a compile time constant. + int64_t constant_count = 0; + bool is_constant_size = try_getting_constant_integer(count_obj, &constant_count, env); + + if (!rest->is_empty_list()) { + // got extra arguments + throw_compile_error(form, "new array form got more arguments than expected"); + } + + auto ts = is_inline ? m_ts.make_inline_array_typespec(elt_type) + : m_ts.make_pointer_typespec(elt_type); + auto info = m_ts.get_deref_info(ts); + if (!info.can_deref) { + throw_compile_error(form, + fmt::format("Cannot make an {} of {}\n", type_as_string, ts.print())); + } + + auto malloc_func = compile_get_symbol_value("malloc", env)->to_reg(env); + std::vector args; + args.push_back(compile_get_sym_obj(allocation, env)->to_reg(env)); + + if (is_constant_size) { + auto array_size = constant_count * info.stride; + args.push_back(compile_integer(array_size, env)->to_reg(env)); + } else { + auto array_size = compile_integer(info.stride, env)->to_reg(env); + env->emit( + std::make_unique(IntegerMathKind::IMUL_32, array_size, + compile_error_guard(count_obj, env)->to_gpr(env))); + args.push_back(array_size); + } + + auto array = compile_real_function_call(form, malloc_func, args, env); + array->set_type(ts); + return array; } else { auto type_of_obj = m_ts.make_typespec(type_as_string); std::vector args; diff --git a/test/goalc/source_templates/with_game/test-new-array.gc b/test/goalc/source_templates/with_game/test-new-array.gc new file mode 100644 index 0000000000..2784307d4b --- /dev/null +++ b/test/goalc/source_templates/with_game/test-new-array.gc @@ -0,0 +1,37 @@ +(start-test "new-array") + +;; align the heap. +(new 'global 'array 'uint8 16) + +;; get the current alignment +(let ((current (-> global current))) + (expect-true (= current (new 'global 'array 'uint16 3))) ;; 6 bytes + (expect-true (= (&+ current 6) (-> global current))) + ) + +;; align the heap. +(new 'global 'array 'uint8 16) + +;; get the current alignment +(let ((current (-> global current))) + (expect-true (= current (new 'global 'array 'bfloat 3))) ;; 3 * 4 = 12 bytes + (expect-true (= (&+ current 12) (-> global current))) + ) + +;; align the heap. +(new 'global 'array 'uint8 16) + +;; get the current alignment +(let ((current (-> global current))) + (expect-true (= current (new 'global 'inline-array 'bfloat 3))) ;; 3*4 = 12 bytes + (expect-true (= (&+ current 48) (-> global current))) + ) + +;; get the current alignment +(let ((current (-> global current)) + (three 3)) + (expect-true (= current (new 'global 'inline-array 'bfloat (* three 4)))) ;; 12*16 = 36 bytes + (expect-true (= (&+ current 192) (-> global current))) + ) + +(finish-test) \ No newline at end of file diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index ba5a19f9c0..65f55c0f94 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -62,6 +62,12 @@ std::thread WithGameTests::runtime_thread; Compiler WithGameTests::compiler; GoalTest::CompilerTestRunner WithGameTests::runner; +namespace { +std::vector get_test_pass_string(const std::string& name, int count) { + return {fmt::format("Test \"{}\": {} Passes\n0\n", name, count)}; +} +} // namespace + // TODO - havn't done anything with these really yet TEST_F(WithGameTests, All) { runner.run_static_test(env, testCategory, "defun-return-constant.static.gc", {"12\n"}); @@ -109,15 +115,16 @@ TEST_F(WithGameTests, All) { {"Test \"test-type-arrays\": 3 Passes\n0\n"}); runner.run_static_test(env, testCategory, "test-number-comparison.gc", {"Test \"number-comparison\": 14 Passes\n0\n"}); - /*runner.run_static_test(env, testCategory, "test-approx-pi.gc", + runner.run_static_test(env, testCategory, "test-approx-pi.gc", get_test_pass_string("approx-pi", 4)); runner.run_static_test(env, testCategory, "test-dynamic-type.gc", get_test_pass_string("dynamic-type", 4)); runner.run_static_test(env, testCategory, "test-string-type.gc", get_test_pass_string("string-type", 4)); runner.run_static_test(env, testCategory, "test-new-string.gc", - get_test_pass_string("new-string", 5));*/ - // runner.run_static_test(env, testCategory, "test-addr-of.gc", get_test_pass_string("addr-of", - // 2)); + get_test_pass_string("new-string", 5)); + runner.run_static_test(env, testCategory, "test-addr-of.gc", get_test_pass_string("addr-of", 2)); runner.run_static_test(env, testCategory, "test-set-self.gc", {"#t\n0\n"}); + runner.run_static_test(env, testCategory, "test-new-array.gc", + get_test_pass_string("new-array", 8)); } From 2c0ea96343101914dbd451441402eb66cc868c0a Mon Sep 17 00:00:00 2001 From: water Date: Wed, 14 Oct 2020 13:34:06 -0400 Subject: [PATCH 2/2] update goal change log --- doc/changelog.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/changelog.md b/doc/changelog.md index d48f86c0a0..c63901eac8 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -18,4 +18,7 @@ - Using `&+` and `&+!` now produces a pointer with the same type as the original. - There is a `&-` which returns a `uint` and works with basically any input types - The `&` operator works on fields and elements in arrays -- The `&->` operator has been added \ No newline at end of file +- The `&->` operator has been added +- The `new` operator can create arrays and inline arrays on heaps +- The value of `deftype` is now `none` +- Creating a method with more than 8 arguments is an error instead of a crash. \ No newline at end of file