diff --git a/common/type_system/TypeSpec.cpp b/common/type_system/TypeSpec.cpp index c78def6cd..2adaafeb5 100644 --- a/common/type_system/TypeSpec.cpp +++ b/common/type_system/TypeSpec.cpp @@ -43,4 +43,21 @@ TypeSpec TypeSpec::substitute_for_method_call(const std::string& method_type) co result.m_arguments.push_back(x.substitute_for_method_call(method_type)); } return result; +} + +bool TypeSpec::is_compatible_child_method(const TypeSpec& implementation, + const std::string& child_type) const { + bool ok = implementation.m_type == m_type || + (m_type == "_type_" && implementation.m_type == child_type); + if (!ok || implementation.m_arguments.size() != m_arguments.size()) { + return false; + } + + for (size_t i = 0; i < m_arguments.size(); i++) { + if (!m_arguments[i].is_compatible_child_method(implementation.m_arguments[i], child_type)) { + return false; + } + } + + return true; } \ No newline at end of file diff --git a/common/type_system/TypeSpec.h b/common/type_system/TypeSpec.h index 91aaebf3b..47d46eb8f 100644 --- a/common/type_system/TypeSpec.h +++ b/common/type_system/TypeSpec.h @@ -30,6 +30,8 @@ class TypeSpec { bool operator!=(const TypeSpec& other) const; bool operator==(const TypeSpec& other) const; + bool is_compatible_child_method(const TypeSpec& implementation, + const std::string& child_type) const; std::string print() const; void add_arg(const TypeSpec& ts) { m_arguments.push_back(ts); } diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 1904a6adf..94b1b4253 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -228,6 +228,12 @@ Type* TypeSystem::lookup_type(const TypeSpec& ts) const { return lookup_type(ts.base_type()); } +MethodInfo TypeSystem::add_method(const std::string& type_name, + const std::string& method_name, + const TypeSpec& ts) { + return add_method(lookup_type(make_typespec(type_name)), method_name, ts); +} + /*! * Add a method, if it doesn't exist. If the method already exists (possibly in a parent), checks to * see if this is an identical definition. If not, it's an error, and if so, nothing happens. @@ -263,7 +269,7 @@ MethodInfo TypeSystem::add_method(Type* type, const std::string& method_name, co if (got_existing) { // make sure we aren't changing anything. - if (ts != existing_info.type) { + if (!existing_info.type.is_compatible_child_method(ts, type->get_name())) { fmt::print( "[TypeSystem] The method {} of type {} was originally defined as {}, but has been " "redefined as {}\n", diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index 24b3fae0b..0e156de78 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -51,6 +51,9 @@ class TypeSystem { Type* lookup_type(const TypeSpec& ts) const; Type* lookup_type(const std::string& name) const; + MethodInfo add_method(const std::string& type_name, + const std::string& method_name, + const TypeSpec& ts); MethodInfo add_method(Type* type, const std::string& method_name, const TypeSpec& ts); MethodInfo add_new_method(Type* type, const TypeSpec& ts); MethodInfo lookup_method(const std::string& type_name, const std::string& method_name); diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index b5e48ebd2..89cb46f6f 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -173,4 +173,32 @@ (else ,(first others)) ) ) + ) + +;;;;;;;;;;;;;;;;;;; +;; Math Macros +;;;;;;;;;;;;;;;;;;; + +(defmacro +1 (var) + `(+ ,var 1) + ) + +(defmacro +! (place amount) + `(set! ,place (+ ,place ,amount)) + ) + +(defmacro +1! (place) + `(set! ,place (+ 1 ,place)) + ) + +(defmacro -! (place amount) + `(set! ,place (- ,place ,amount)) + ) + +(defmacro *! (place amount) + `(set! ,place (* ,place ,amount)) + ) + +(defmacro 1- (var) + `(- ,var 1) ) \ No newline at end of file diff --git a/goal_src/kernel/gcommon.gc b/goal_src/kernel/gcommon.gc index 26a7b272a..b12f23105 100644 --- a/goal_src/kernel/gcommon.gc +++ b/goal_src/kernel/gcommon.gc @@ -208,41 +208,59 @@ ;; TODO - vec4s -;; The "boxed float" type bfloat is just a float wrapped in a basic (structure type that has runtime type information) +;; The "boxed float" type "bfloat" is just a float wrapped in a basic (structure type that has runtime type information) +;; it's a way to have a floating point number that knows its a floating point number and can print/inspect itself +;; Compared to a normal float, it's much less efficient, so this is used extremely rarely. + +;; a GOAL deftype contains the following: +;; - type name +;; - parent type name +;; - field list +;; - method declarations +;; - additional options +;; It has "asserts" that can be used to make sure that the type is laid out in memory in the same way as the game. +;; You provide the actual offsets/sizes/method ids, and if there is a mismatch, it throws a compiler error. +;; The decompile will generate these automatically in the future. + +;; Type Name: should be a unique name. Can't be the name of a function or global variable. In this case, it's bfloat +;; Parent Type: Should be the name of the parent type ("basic" in this case). Will inherit fields and methods from the parent. +;; children of "basic" are structure types with runtime type information. +;; Field List: each field of the type, listed as (name type-name [options]) +;; use the :offset-assert X to do a check at comile-time that the OpenGOAL compiler places the field at the given offset. +;; if the compiler came up with a different offset, it will create an error. This used to make sure the memory layout matches +;; the original game. +;; Method Declarations: Any methods which are defined in this type but not the parent must be declared here. +;; you may optionally declare methods defined only in the parent, or defined in both the parent and child (overridden methods) +;; the method declarations is (method-name (arg-list) return-type [optional-id-assert]) +;; the optional id assert is used to check that the compiler places the method in the given slot of the method table. +;; like the offset-assert, it's used to make sure the type hierarchy matches the game. +;; Note that the special type "_type_" can be used in methods args/returns to indicate "the type of the object method is called on". +;; this is used for 2 things: +;; 1. Child who overrides it can use their own type as an argument, rather than a less specific parent type. +;; 2. Caller who calls an overriden method and knows it at compile time can know a return type more specifically. + -;; define a type called "bfloat" which is a child of "basic". -;; "basic" is just a type with structures and runtime type information (deftype bfloat (basic) - ;; the field list. - ;; there is a single field named data, of type float. - ;; the :offset-assert makes sure that OpenGOAL's type layout places the field at the given offset. - ;; if not, it creates a compiler error. This is used to make sure we exactly copy the game's memory layout, - ;; as we can get the exact offset of fields from the disassembly - ((data float :offset-assert 4)) - - ;; declare methods. If you are overriding a parent method, you don't have to declare it, but you can if you want - ;; The number after the return type is the method ID, that can be checked against the disassembly to make sure - ;; the type and method hierarchy is correct. If OpenGOAL's method table layout doesn't match, it will create - ;; compiler error. - - (:methods (print (_type_) _type_ 2) ;; we will override print later on - (inspect (_type_) _type_ 3) ;; this is a parent method we won't override. It's fine to put it here anyway. + ;; fields + ((data float :offset-assert 4)) ;; field "data" is a float. + ;; methods + (:methods (print (_type_) _type_ 2) ;; we will override print later on. This is optional to include + (inspect (_type_) _type_ 3) ;; this is a parent method we won't override. This is also optional to inlcude ) - - ;; Note that the special type "_type_" can be used in methods to indicate "the type of the object method is called on". - ;; this is used for 2 things: - ;; 1. Child who overrides it can use their own type as an argument, rather than a less specific parent type. - ;; 2. Caller who calls an overriden method and knows it at compile time can know a return type more specifically. - + ;; options - ;; make sure the size of the type is correct (this is stored in the type structure, so we can check it) + ;; make sure the size of the type is correct (compare to value from game) :size-assert 8 - ;; make sure method count is correct (again, stored in the type structure) + ;; make sure method count is correct (again, compare to value from game) :method-count-assert 9 - ;; flags passed to the new_type function in the runtime. + ;; flags passed to the new_type function in the runtime, compare from game :flag-assert #x900000008 ) -;; todo print bfloat \ No newline at end of file +(defmethod print bfloat ((obj bfloat)) + "Override the default print method to print a bfloat like a normal float" + (format #t "~f" (-> obj data)) + obj + ) \ No newline at end of file diff --git a/goal_src/test/test-deref-simple.gc b/goal_src/test/test-deref-simple.gc new file mode 100644 index 000000000..58057af93 --- /dev/null +++ b/goal_src/test/test-deref-simple.gc @@ -0,0 +1 @@ +(print (-> type parent parent)) 0 \ No newline at end of file diff --git a/goal_src/test/test-float-function.gc b/goal_src/test/test-float-function.gc new file mode 100644 index 000000000..bc8f695f8 --- /dev/null +++ b/goal_src/test/test-float-function.gc @@ -0,0 +1,8 @@ +(defun float-testing-function ((x float) (y float)) + (* x y (* x x)) + ) + +(let ((x (float-testing-function (* 1.2 1.2) 3.4))) + (format #t "~,,3f~%" x) + ) +0 \ No newline at end of file diff --git a/goal_src/test/test-float-in-symbol.gc b/goal_src/test/test-float-in-symbol.gc new file mode 100644 index 000000000..4f1f42467 --- /dev/null +++ b/goal_src/test/test-float-in-symbol.gc @@ -0,0 +1,2 @@ +(define float-symbol 2345.6) +(format #t "~f~%" float-symbol) \ No newline at end of file diff --git a/goal_src/test/test-float-pow-function.gc b/goal_src/test/test-float-pow-function.gc new file mode 100644 index 000000000..eeb7db897 --- /dev/null +++ b/goal_src/test/test-float-pow-function.gc @@ -0,0 +1,12 @@ +(defun pow-test ((base float) (exponent integer)) + (let ((result base)) + (while (> exponent 1) + (*! result base) + (-! exponent 1) + ) + result + ) + ) + +(format #t "~,,0f~%" (pow-test 2.0 8)) +0 \ No newline at end of file diff --git a/goal_src/test/test-float-product.gc b/goal_src/test/test-float-product.gc new file mode 100644 index 000000000..76f414e72 --- /dev/null +++ b/goal_src/test/test-float-product.gc @@ -0,0 +1 @@ +(format #t "~f~%" (* 1.2 25.0 4.0)) \ No newline at end of file diff --git a/goal_src/test/test-function-return-constant-float.gc b/goal_src/test/test-function-return-constant-float.gc new file mode 100644 index 000000000..521fbd537 --- /dev/null +++ b/goal_src/test/test-function-return-constant-float.gc @@ -0,0 +1,5 @@ +(defun return-const-float () + 3.1415 + ) +(format #t "~,,5f~%" (return-const-float)) +0 \ No newline at end of file diff --git a/goal_src/test/test-min-max.gc b/goal_src/test/test-min-max.gc new file mode 100644 index 000000000..f0ae6c12e --- /dev/null +++ b/goal_src/test/test-min-max.gc @@ -0,0 +1 @@ +(+ (min 1 2) (min 2 (min -1 -4)) (max 10 (min 2 23)) (max 3 1)) \ No newline at end of file diff --git a/goal_src/test/test-nested-float-functions.gc b/goal_src/test/test-nested-float-functions.gc new file mode 100644 index 000000000..d00a5d793 --- /dev/null +++ b/goal_src/test/test-nested-float-functions.gc @@ -0,0 +1,15 @@ +(define-extern _format function) +(define format _format) + +(defun float-testing-function-2 ((x float) (y float)) + (format #t "i ~f ~f~%" x y) + (let ((result (* x y (* x x)))) + (format #t "r ~f~%" result) + result + ) + ) + +(let ((x (float-testing-function-2 (* 1.2 1.2) 3.4))) + (format #t "~,,3f ~,,3f~%" (float-testing-function-2 1.2000 x) x) + ) +0 \ No newline at end of file diff --git a/goal_src/test/test-quote-symbol.gc b/goal_src/test/test-quote-symbol.gc new file mode 100644 index 000000000..c97a0473e --- /dev/null +++ b/goal_src/test/test-quote-symbol.gc @@ -0,0 +1,4 @@ +(define my-thing 'apple) +(set! my-thing 'banana) +(format #t "~A~%" my-thing) +0 \ No newline at end of file diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index c47962b60..7c45a5824 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -151,6 +151,13 @@ void Compiler::color_object_file(FileEnv* env) { input.instructions.push_back(i->to_rai()); input.debug_instruction_names.push_back(i->print()); } + + // temp hack + { + for (auto& arg : f->params) { + input.instructions.front().write.push_back(arg.second->ireg()); + } + } input.max_vars = f->max_vars(); input.constraints = f->constraints(); diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index af97fae84..cebdc0dab 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -170,6 +170,8 @@ class Compiler { // Type Val* compile_deftype(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_defmethod(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_deref(const goos::Object& form, const goos::Object& rest, Env* env); }; #endif // JAK_COMPILER_H diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index dcd079df3..24d59fcae 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -219,6 +219,8 @@ void IR_RegSet::do_codegen(emitter::ObjectGenerator* gen, gen->add_instr(IGen::movd_gpr32_xmm32(dest_reg, val_reg), irec); } else if (val_reg.is_gpr() && dest_reg.is_xmm()) { gen->add_instr(IGen::movd_xmm32_gpr32(dest_reg, val_reg), irec); + } else if (val_reg.is_xmm() && dest_reg.is_xmm()) { + gen->add_instr(IGen::mov_xmm32_xmm32(dest_reg, val_reg), irec); } else { assert(false); } @@ -504,6 +506,8 @@ std::string IR_FloatMath::print() { switch (m_kind) { case FloatMathKind::DIV_SS: return fmt::format("divss {}, {}", m_dest->print(), m_arg->print()); + case FloatMathKind::MUL_SS: + return fmt::format("mulss {}, {}", m_dest->print(), m_arg->print()); default: throw std::runtime_error("Unsupported FloatMathKind"); } @@ -525,6 +529,10 @@ void IR_FloatMath::do_codegen(emitter::ObjectGenerator* gen, gen->add_instr( IGen::divss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; + case FloatMathKind::MUL_SS: + gen->add_instr( + IGen::mulss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); + break; default: assert(false); } diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index 05917f933..c49221feb 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -223,7 +223,7 @@ class IR_IntegerMath : public IR { RegVal* m_arg; }; -enum class FloatMathKind { DIV_SS }; +enum class FloatMathKind { DIV_SS, MUL_SS }; class IR_FloatMath : public IR { public: diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index b9c932f73..8ad1b1006 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -94,7 +94,20 @@ RegVal* FloatConstantVal::to_reg(Env* fe) { } RegVal* MemoryOffsetConstantVal::to_reg(Env* fe) { - auto re = fe->make_gpr(deref_type); - fe->emit(std::make_unique(re, offset, base, info)); - return re; + (void)fe; + assert(false); + throw std::runtime_error("MemoryOffsetConstantVal::to_reg not yet implemented"); +} + +RegVal* MemoryDerefVal::to_reg(Env* fe) { + auto base_as_co = dynamic_cast(base); + if (base_as_co) { + auto re = fe->make_gpr(m_ts); + fe->emit(std::make_unique(re, base_as_co->offset, + base_as_co->base->to_gpr(fe), info)); + return re; + } else { + assert(false); + throw std::runtime_error("MemoryDerefVal::to_reg not yet implemented for this case"); + } } \ No newline at end of file diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index 5fc61f273..ebb3ae2db 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -126,35 +126,45 @@ class StaticVal : public Val { }; struct MemLoadInfo { + MemLoadInfo() = default; + explicit MemLoadInfo(const DerefInfo& di) { + assert(di.can_deref); + assert(di.mem_deref); + sign_extend = di.sign_extend; + size = di.load_size; + reg = di.reg; + } + + RegKind reg = RegKind::INVALID; bool sign_extend = false; int size = -1; }; class MemoryOffsetConstantVal : public Val { public: - MemoryOffsetConstantVal(TypeSpec ts, - RegVal* _base, - int _offset, - MemLoadInfo _info, - TypeSpec _deref_type) - : Val(std::move(ts)), - base(_base), - offset(_offset), - info(_info), - deref_type(std::move(_deref_type)) {} + MemoryOffsetConstantVal(TypeSpec ts, Val* _base, int _offset) + : Val(std::move(ts)), base(_base), offset(_offset) {} std::string print() const override { return "(" + base->print() + " + " + std::to_string(offset) + ")"; } RegVal* to_reg(Env* fe) override; - RegVal* base = nullptr; + Val* base = nullptr; int offset = 0; - MemLoadInfo info; - TypeSpec deref_type; }; // MemOffConstant // MemOffVar -// MemDeref + +class MemoryDerefVal : public Val { + public: + MemoryDerefVal(TypeSpec ts, Val* _base, MemLoadInfo _info) + : Val(std::move(ts)), base(_base), info(_info) {} + std::string print() const override { return "[" + base->print() + "]"; } + RegVal* to_reg(Env* fe) override; + Val* base = nullptr; + MemLoadInfo info; +}; + // PairEntry // Alias diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 7cf2c5e9a..24123e436 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -57,8 +57,9 @@ static const std::unordered_map< // // TYPE {"deftype", &Compiler::compile_deftype}, + {"defmethod", &Compiler::compile_defmethod}, // {"defenum", &Compiler::compile_defenum}, - // {"->", &Compiler::compile_deref}, + {"->", &Compiler::compile_deref}, // {"&", &Compiler::compile_addr_of}, // // @@ -83,7 +84,7 @@ static const std::unordered_map< // {"the", &Compiler::compile_the}, // {"the-as", &Compiler::compile_the_as}, // - // {"defmethod", &Compiler::compile_defmethod}, + // // // {"current-method-type", &Compiler::compile_current_method_type}, // {"new", &Compiler::compile_new}, diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index ff300401e..459874e7d 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -91,9 +91,8 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest if (!inline_only) { // compile a function! First create env - // auto new_func_env = fe->alloc_env(env, lambda.debug_name); auto new_func_env = std::make_unique(env, lambda.debug_name); - new_func_env->set_segment(MAIN_SEGMENT); + new_func_env->set_segment(MAIN_SEGMENT); // todo, how do we set debug? // set up arguments assert(lambda.params.size() < 8); // todo graceful error @@ -216,7 +215,7 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en head_as_lambda = dynamic_cast(head); } - if (!head_as_lambda) { + if (!head_as_lambda && !is_method_call) { head = head->to_gpr(env); } @@ -282,12 +281,13 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en } else { // not an inline call if (is_method_call) { - // determine the method to call by looking at the type of first argument - if (eval_args.empty()) { - throw_compile_error(form, "0 argument method call is impossible to figure out"); - } - printf("BAD %s\n", uneval_head.print().c_str()); - assert(false); // nyi + throw_compile_error(form, "Unrecognized symbol " + uneval_head.print() + " as head of form"); + // // determine the method to call by looking at the type of first argument + // if (eval_args.empty()) { + // + // } + // printf("BAD %s\n", uneval_head.print().c_str()); + // assert(false); // nyi // head = compile_get_method_of_object(eval_args.front(), symbol_string(uneval_head), env); } diff --git a/goalc/compiler/compilation/Math.cpp b/goalc/compiler/compilation/Math.cpp index b78ff28b1..4a5db0dbd 100644 --- a/goalc/compiler/compilation/Math.cpp +++ b/goalc/compiler/compilation/Math.cpp @@ -148,6 +148,18 @@ Val* Compiler::compile_mul(const goos::Object& form, const goos::Object& rest, E } return result; } + case MATH_FLOAT: { + auto result = env->make_xmm(first_type); + env->emit(std::make_unique(result, first_val->to_xmm(env))); + + for (size_t i = 1; i < args.unnamed.size(); i++) { + env->emit(std::make_unique( + FloatMathKind::MUL_SS, result, + to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env) + ->to_xmm(env))); + } + return result; + } case MATH_INVALID: throw_compile_error( form, "Cannot determine the math mode for object of type " + first_type.print()); diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index ad07d6c42..e611aaa2e 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -13,9 +13,16 @@ RegVal* Compiler::compile_get_method_of_type(const TypeSpec& type, load_info.sign_extend = false; load_info.size = POINTER_SIZE; - return fe - ->alloc_val(typ->type(), typ, offset_of_method, load_info, info.type) - ->to_reg(env); + auto loc_type = m_ts.make_pointer_typespec(info.type); + auto loc = fe->alloc_val(loc_type, typ, offset_of_method); + auto di = m_ts.get_deref_info(loc_type); + assert(di.can_deref); + assert(di.mem_deref); + assert(di.sign_extend == false); + assert(di.load_size == 4); + + auto deref = fe->alloc_val(di.result_type, loc, MemLoadInfo(di)); + return deref->to_reg(env); } Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& rest, Env* env) { @@ -38,3 +45,180 @@ Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& res return compile_real_function_call(form, new_type_method, {new_type_symbol, parent_type, flags_int}, env); } + +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; + + auto& method_name = pair_car(*rest); + rest = &pair_cdr(*rest); + auto& type_name = pair_car(*rest); + rest = &pair_cdr(*rest); + auto& arg_list = pair_car(*rest); + auto body = &pair_cdr(*rest); + + if (!method_name.is_symbol()) { + throw_compile_error(form, "method name must be a symbol, got " + method_name.print()); + } + if (!type_name.is_symbol()) { + throw_compile_error(form, "method type must be a symbol, got " + method_name.print()); + } + + auto place = fe->alloc_val(get_none()->type()); + auto& lambda = place->lambda; + auto lambda_ts = m_ts.make_typespec("function"); + + // parse the argument list. + 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 { + 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)); + lambda_ts.add_arg(parm.type); + parm.type = parm.type.substitute_for_method_call(symbol_string(type_name)); + lambda.params.push_back(parm); + } + }); + assert(lambda.params.size() == lambda_ts.arg_count()); + // todo, verify argument list types (check that first arg is _type_ for methods that aren't "new") + lambda.debug_name = fmt::format("(method {} {})", method_name.print(), type_name.print()); + + // skip docstring + if (body->as_pair()->car.is_string() && !body->as_pair()->cdr.is_empty_list()) { + body = &pair_cdr(*body); + } + + lambda.body = *body; + place->func = nullptr; + + auto new_func_env = std::make_unique(env, lambda.debug_name); + new_func_env->set_segment(MAIN_SEGMENT); // todo, how do we set debug? + + // set up arguments + assert(lambda.params.size() < 8); // todo graceful error + for (u32 i = 0; i < lambda.params.size(); i++) { + IRegConstraint constr; + constr.instr_idx = 0; // constraint at function start + auto ireg = new_func_env->make_ireg(lambda.params.at(i).type, emitter::RegKind::GPR); + constr.ireg = ireg->ireg(); + constr.desired_register = emitter::gRegInfo.get_arg_reg(i); + new_func_env->params[lambda.params.at(i).name] = ireg; + new_func_env->constrain(constr); + } + + place->func = new_func_env.get(); + + // nasty function block env setup + auto return_reg = new_func_env->make_ireg(get_none()->type(), emitter::RegKind::GPR); + auto func_block_env = new_func_env->alloc_env(new_func_env.get(), "#f"); + func_block_env->return_value = return_reg; + func_block_env->end_label = Label(new_func_env.get()); + + // compile the function! + Val* result = nullptr; + bool first_thing = true; + for_each_in_list(lambda.body, [&](const goos::Object& o) { + result = compile_error_guard(o, func_block_env); + if (first_thing) { + first_thing = false; + // you could probably cheat and do a (begin (blorp) (declare ...)) to get around this. + new_func_env->settings.is_set = true; + } + }); + if (result) { + auto final_result = result->to_gpr(new_func_env.get()); + new_func_env->emit(std::make_unique(return_reg, final_result)); + // new_func_env->emit(std::make_unique())??? + new_func_env->finish(); + lambda_ts.add_arg(final_result->type()); + } else { + lambda_ts.add_arg(m_ts.make_typespec("none")); + } + func_block_env->end_label.idx = new_func_env->code().size(); + + auto obj_env = get_parent_env_of_type(new_func_env.get()); + assert(obj_env); + if (new_func_env->settings.save_code) { + obj_env->add_function(std::move(new_func_env)); + } + place->set_type(lambda_ts); + + auto info = m_ts.add_method(symbol_string(type_name), symbol_string(method_name), lambda_ts); + auto type_obj = compile_get_symbol_value(symbol_string(type_name), env)->to_gpr(env); + auto id_val = compile_integer(info.id, env)->to_gpr(env); + auto method_val = place->to_gpr(env); + auto method_set_val = compile_get_symbol_value("method-set!", env)->to_gpr(env); + return compile_real_function_call(form, method_set_val, {type_obj, id_val, method_val}, env); +} + +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()) { + throw_compile_error(form, "-> must get at least one argument"); + } + + auto& first_arg = pair_car(_rest); + auto rest = &pair_cdr(_rest); + + // eval the first thing + auto result = compile_error_guard(first_arg, env); + + if (rest->is_empty_list()) { + // one argument, do a pointer deref + auto deref_info = m_ts.get_deref_info(result->type()); + if (!deref_info.can_deref) { + throw_compile_error(form, "Cannot dereference a " + result->type().print()); + } + + if (deref_info.mem_deref) { + result = + fe->alloc_val(deref_info.result_type, result, MemLoadInfo(deref_info)); + } else { + assert(false); + } + return result; + } + + // compound, is field access/nested access + while (!rest->is_empty_list()) { + auto field_obj = pair_car(*rest); + rest = &pair_cdr(*rest); + auto type_info = m_ts.lookup_type(result->type()); + + // attempt to treat it as a field. May not succeed if we're actually an array. + if (field_obj.is_symbol()) { + auto field_name = symbol_string(field_obj); + 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)); + } else { + assert(false); + } + continue; + } + + // todo try bitfield + } + + // todo array or other + assert(false); + } + return result; +} \ No newline at end of file diff --git a/goalc/emitter/IGen.h b/goalc/emitter/IGen.h index 467b3d20a..8fe44f384 100644 --- a/goalc/emitter/IGen.h +++ b/goalc/emitter/IGen.h @@ -115,6 +115,7 @@ class IGen { instr.set_op2(0x0f); instr.set_op3(0x10); instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, false); + instr.swap_op0_rex(); return instr; } @@ -996,7 +997,8 @@ class IGen { static Instruction store128_gpr64_xmm128(Register gpr_addr, Register xmm_value) { assert(gpr_addr.is_gpr()); assert(xmm_value.is_xmm()); - Instruction instr(0x66); + // Instruction instr(0x66); + Instruction instr(0xf3); instr.set_op2(0x0f); instr.set_op3(0x7f); instr.set_modrm_and_rex_for_reg_addr(xmm_value.hw_id(), gpr_addr.hw_id(), false); @@ -1007,7 +1009,8 @@ class IGen { static Instruction load128_xmm128_gpr64(Register xmm_dest, Register gpr_addr) { assert(gpr_addr.is_gpr()); assert(xmm_dest.is_xmm()); - Instruction instr(0x66); + // Instruction instr(0x66); + Instruction instr(0xf3); instr.set_op2(0x0f); instr.set_op3(0x6f); instr.set_modrm_and_rex_for_reg_addr(xmm_dest.hw_id(), gpr_addr.hw_id(), false); diff --git a/goalc/emitter/Register.cpp b/goalc/emitter/Register.cpp index 7f0648cc2..1999622c7 100644 --- a/goalc/emitter/Register.cpp +++ b/goalc/emitter/Register.cpp @@ -23,6 +23,23 @@ RegisterInfo RegisterInfo::make_register_info() { info.m_info[R14] = {-1, false, true, "r14"}; // st? info.m_info[R15] = {-1, false, true, "r15"}; // offset. + info.m_info[XMM0] = {-1, false, false, "xmm0"}; + info.m_info[XMM1] = {-1, false, false, "xmm1"}; + info.m_info[XMM2] = {-1, false, false, "xmm2"}; + info.m_info[XMM3] = {-1, false, false, "xmm3"}; + info.m_info[XMM4] = {-1, false, false, "xmm4"}; + info.m_info[XMM5] = {-1, false, false, "xmm5"}; + info.m_info[XMM6] = {-1, false, false, "xmm6"}; + info.m_info[XMM7] = {-1, false, false, "xmm7"}; + info.m_info[XMM8] = {-1, true, false, "xmm8"}; + info.m_info[XMM9] = {-1, true, false, "xmm9"}; + info.m_info[XMM10] = {-1, true, false, "xmm10"}; + info.m_info[XMM11] = {-1, true, false, "xmm11"}; + info.m_info[XMM12] = {-1, true, false, "xmm12"}; + info.m_info[XMM13] = {-1, true, false, "xmm13"}; + info.m_info[XMM14] = {-1, true, false, "xmm14"}; + info.m_info[XMM15] = {-1, true, false, "xmm15"}; + info.m_arg_regs = std::array({RDI, RSI, RDX, RCX, R8, R9, R10, R11}); info.m_saved_gprs = std::array({RBX, RBP, R10, R11, R12}); info.m_saved_xmms = diff --git a/test/test_CodeTester.cpp b/test/test_CodeTester.cpp index 04a56ab8a..8441e8259 100644 --- a/test/test_CodeTester.cpp +++ b/test/test_CodeTester.cpp @@ -57,36 +57,68 @@ TEST(CodeTester, xmm_store_128) { // movdqa [r14], xmm3 // movdqa [rbx], xmm14 // movdqa [r14], xmm13 + // tester.emit(IGen::store128_gpr64_xmm128(RBX, XMM3)); + // tester.emit(IGen::store128_gpr64_xmm128(R14, XMM3)); + // tester.emit(IGen::store128_gpr64_xmm128(RBX, XMM14)); + // tester.emit(IGen::store128_gpr64_xmm128(R14, XMM13)); + // EXPECT_EQ(tester.dump_to_hex_string(), + // "66 0f 7f 1b 66 41 0f 7f 1e 66 44 0f 7f 33 66 45 0f 7f 2e"); + // + // tester.clear(); + // tester.emit(IGen::store128_gpr64_xmm128(RSP, XMM1)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 7f 0c 24"); // requires SIB byte. + // + // tester.clear(); + // tester.emit(IGen::store128_gpr64_xmm128(R12, XMM13)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 7f 2c 24"); // requires SIB byte and REX + // byte + // + // tester.clear(); + // tester.emit(IGen::store128_gpr64_xmm128(RBP, XMM1)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 7f 4d 00"); + // + // tester.clear(); + // tester.emit(IGen::store128_gpr64_xmm128(RBP, XMM11)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 44 0f 7f 5d 00"); + // + // tester.clear(); + // tester.emit(IGen::store128_gpr64_xmm128(R13, XMM2)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 41 0f 7f 55 00"); + // + // tester.clear(); + // tester.emit(IGen::store128_gpr64_xmm128(R13, XMM12)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 7f 65 00"); + tester.emit(IGen::store128_gpr64_xmm128(RBX, XMM3)); tester.emit(IGen::store128_gpr64_xmm128(R14, XMM3)); tester.emit(IGen::store128_gpr64_xmm128(RBX, XMM14)); tester.emit(IGen::store128_gpr64_xmm128(R14, XMM13)); EXPECT_EQ(tester.dump_to_hex_string(), - "66 0f 7f 1b 66 41 0f 7f 1e 66 44 0f 7f 33 66 45 0f 7f 2e"); + "f3 0f 7f 1b f3 41 0f 7f 1e f3 44 0f 7f 33 f3 45 0f 7f 2e"); tester.clear(); tester.emit(IGen::store128_gpr64_xmm128(RSP, XMM1)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 7f 0c 24"); // requires SIB byte. + EXPECT_EQ(tester.dump_to_hex_string(), "f3 0f 7f 0c 24"); // requires SIB byte. tester.clear(); tester.emit(IGen::store128_gpr64_xmm128(R12, XMM13)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 7f 2c 24"); // requires SIB byte and REX byte + EXPECT_EQ(tester.dump_to_hex_string(), "f3 45 0f 7f 2c 24"); // requires SIB byte and REX byte tester.clear(); tester.emit(IGen::store128_gpr64_xmm128(RBP, XMM1)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 7f 4d 00"); + EXPECT_EQ(tester.dump_to_hex_string(), "f3 0f 7f 4d 00"); tester.clear(); tester.emit(IGen::store128_gpr64_xmm128(RBP, XMM11)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 44 0f 7f 5d 00"); + EXPECT_EQ(tester.dump_to_hex_string(), "f3 44 0f 7f 5d 00"); tester.clear(); tester.emit(IGen::store128_gpr64_xmm128(R13, XMM2)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 41 0f 7f 55 00"); + EXPECT_EQ(tester.dump_to_hex_string(), "f3 41 0f 7f 55 00"); tester.clear(); tester.emit(IGen::store128_gpr64_xmm128(R13, XMM12)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 7f 65 00"); + EXPECT_EQ(tester.dump_to_hex_string(), "f3 45 0f 7f 65 00"); } TEST(CodeTester, sub_gpr64_imm8) { @@ -114,36 +146,68 @@ TEST(CodeTester, add_gpr64_imm8) { TEST(CodeTester, xmm_load_128) { CodeTester tester; tester.init_code_buffer(256); + tester.emit(IGen::load128_xmm128_gpr64(XMM3, RBX)); tester.emit(IGen::load128_xmm128_gpr64(XMM3, R14)); tester.emit(IGen::load128_xmm128_gpr64(XMM14, RBX)); tester.emit(IGen::load128_xmm128_gpr64(XMM13, R14)); EXPECT_EQ(tester.dump_to_hex_string(), - "66 0f 6f 1b 66 41 0f 6f 1e 66 44 0f 6f 33 66 45 0f 6f 2e"); + "f3 0f 6f 1b f3 41 0f 6f 1e f3 44 0f 6f 33 f3 45 0f 6f 2e"); tester.clear(); tester.emit(IGen::load128_xmm128_gpr64(XMM1, RSP)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 6f 0c 24"); // requires SIB byte. + EXPECT_EQ(tester.dump_to_hex_string(), "f3 0f 6f 0c 24"); // requires SIB byte. tester.clear(); tester.emit(IGen::load128_xmm128_gpr64(XMM13, R12)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 6f 2c 24"); // requires SIB byte and REX byte + EXPECT_EQ(tester.dump_to_hex_string(), "f3 45 0f 6f 2c 24"); // requires SIB byte and REX byte tester.clear(); tester.emit(IGen::load128_xmm128_gpr64(XMM1, RBP)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 6f 4d 00"); + EXPECT_EQ(tester.dump_to_hex_string(), "f3 0f 6f 4d 00"); tester.clear(); tester.emit(IGen::load128_xmm128_gpr64(XMM11, RBP)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 44 0f 6f 5d 00"); + EXPECT_EQ(tester.dump_to_hex_string(), "f3 44 0f 6f 5d 00"); tester.clear(); tester.emit(IGen::load128_xmm128_gpr64(XMM2, R13)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 41 0f 6f 55 00"); + EXPECT_EQ(tester.dump_to_hex_string(), "f3 41 0f 6f 55 00"); tester.clear(); tester.emit(IGen::load128_xmm128_gpr64(XMM12, R13)); - EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 6f 65 00"); + EXPECT_EQ(tester.dump_to_hex_string(), "f3 45 0f 6f 65 00"); + // tester.emit(IGen::load128_xmm128_gpr64(XMM3, RBX)); + // tester.emit(IGen::load128_xmm128_gpr64(XMM3, R14)); + // tester.emit(IGen::load128_xmm128_gpr64(XMM14, RBX)); + // tester.emit(IGen::load128_xmm128_gpr64(XMM13, R14)); + // EXPECT_EQ(tester.dump_to_hex_string(), + // "66 0f 6f 1b 66 41 0f 6f 1e 66 44 0f 6f 33 66 45 0f 6f 2e"); + // + // tester.clear(); + // tester.emit(IGen::load128_xmm128_gpr64(XMM1, RSP)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 6f 0c 24"); // requires SIB byte. + // + // tester.clear(); + // tester.emit(IGen::load128_xmm128_gpr64(XMM13, R12)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 6f 2c 24"); // requires SIB byte and REX + // byte + // + // tester.clear(); + // tester.emit(IGen::load128_xmm128_gpr64(XMM1, RBP)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 0f 6f 4d 00"); + // + // tester.clear(); + // tester.emit(IGen::load128_xmm128_gpr64(XMM11, RBP)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 44 0f 6f 5d 00"); + // + // tester.clear(); + // tester.emit(IGen::load128_xmm128_gpr64(XMM2, R13)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 41 0f 6f 55 00"); + // + // tester.clear(); + // tester.emit(IGen::load128_xmm128_gpr64(XMM12, R13)); + // EXPECT_EQ(tester.dump_to_hex_string(), "66 45 0f 6f 65 00"); } TEST(CodeTester, push_pop_xmms) { diff --git a/test/test_compiler_and_runtime.cpp b/test/test_compiler_and_runtime.cpp index 9e40bf42a..164809396 100644 --- a/test/test_compiler_and_runtime.cpp +++ b/test/test_compiler_and_runtime.cpp @@ -125,6 +125,10 @@ TEST(CompilerAndRuntime, BuildGameAndTest) { } // todo, tests after loading the game. + CompilerTestRunner runner; + runner.c = &compiler; + + runner.run_test("test-min-max.gc", {"10\n"}); compiler.shutdown_target(); runtime_thread.join(); @@ -254,6 +258,7 @@ TEST(CompilerAndRuntime, CompilerTests) { runner.run_test("test-protect.gc", {"33\n"}); runner.run_test("test-format-reg-order.gc", {"test 1 2 3 4 5 6\n0\n"}); + runner.run_test("test-quote-symbol.gc", {"banana\n0\n"}); // expected = // "test newline\nnewline\ntest tilde ~ \ntest A print boxed-string: \"boxed string!\"\ntest @@ -270,6 +275,15 @@ TEST(CompilerAndRuntime, CompilerTests) { // // todo, finish format testing. // runner.run_test("test-format.gc", {expected}, expected.size()); + runner.run_test("test-float-product.gc", {"120.0000\n0\n"}); + runner.run_test("test-float-in-symbol.gc", {"2345.6000\n0\n"}); + runner.run_test("test-function-return-constant-float.gc", {"3.14149\n0\n"}); + runner.run_test("test-float-function.gc", {"10.152\n0\n"}); + runner.run_test("test-float-pow-function.gc", {"256\n0\n"}); + runner.run_test("test-nested-float-functions.gc", + {"i 1.4400 3.4000\nr 10.1523\ni 1.2000 10.1523\nr 17.5432\n17.543 10.152\n0\n"}); + runner.run_test("test-deref-simple.gc", {"structure\n0\n"}); + compiler.shutdown_target(); runtime_thread.join(); runner.print_summary();