Add defmethod and some uses of the deref operator (#51)

* add tests for various xmms

* use unaligned stores and loads to back up and restore xmm128s and also fix argument spilling bug

* add deftype

* add deref and fix some method _type_ things
This commit is contained in:
water111 2020-09-18 22:02:27 -04:00 committed by GitHub
parent abcd444a3b
commit c7c342ce7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 534 additions and 76 deletions

View file

@ -44,3 +44,20 @@ TypeSpec TypeSpec::substitute_for_method_call(const std::string& method_type) co
} }
return result; 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;
}

View file

@ -30,6 +30,8 @@ class TypeSpec {
bool operator!=(const TypeSpec& other) const; bool operator!=(const TypeSpec& other) const;
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; std::string print() const;
void add_arg(const TypeSpec& ts) { m_arguments.push_back(ts); } void add_arg(const TypeSpec& ts) { m_arguments.push_back(ts); }

View file

@ -228,6 +228,12 @@ Type* TypeSystem::lookup_type(const TypeSpec& ts) const {
return lookup_type(ts.base_type()); 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 * 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. * 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) { if (got_existing) {
// make sure we aren't changing anything. // 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( fmt::print(
"[TypeSystem] The method {} of type {} was originally defined as {}, but has been " "[TypeSystem] The method {} of type {} was originally defined as {}, but has been "
"redefined as {}\n", "redefined as {}\n",

View file

@ -51,6 +51,9 @@ class TypeSystem {
Type* lookup_type(const TypeSpec& ts) const; Type* lookup_type(const TypeSpec& ts) const;
Type* lookup_type(const std::string& name) 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_method(Type* type, const std::string& method_name, const TypeSpec& ts);
MethodInfo add_new_method(Type* type, 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); MethodInfo lookup_method(const std::string& type_name, const std::string& method_name);

View file

@ -174,3 +174,31 @@
) )
) )
) )
;;;;;;;;;;;;;;;;;;;
;; 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)
)

View file

@ -208,41 +208,59 @@
;; TODO - vec4s ;; 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) (deftype bfloat (basic)
;; the field list. ;; fields
;; there is a single field named data, of type float. ((data float :offset-assert 4)) ;; field "data" is a float.
;; the :offset-assert makes sure that OpenGOAL's type layout places the field at the given offset. ;; methods
;; if not, it creates a compiler error. This is used to make sure we exactly copy the game's memory layout, (:methods (print (_type_) _type_ 2) ;; we will override print later on. This is optional to include
;; as we can get the exact offset of fields from the disassembly (inspect (_type_) _type_ 3) ;; this is a parent method we won't override. This is also optional to inlcude
((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.
) )
;; options
;; Note that the special type "_type_" can be used in methods to indicate "the type of the object method is called on". ;; make sure the size of the type is correct (compare to value from game)
;; 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.
;; make sure the size of the type is correct (this is stored in the type structure, so we can check it)
:size-assert 8 :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 :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 :flag-assert #x900000008
) )
;; todo print bfloat (defmethod print bfloat ((obj bfloat))
"Override the default print method to print a bfloat like a normal float"
(format #t "~f" (-> obj data))
obj
)

View file

@ -0,0 +1 @@
(print (-> type parent parent)) 0

View file

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

View file

@ -0,0 +1,2 @@
(define float-symbol 2345.6)
(format #t "~f~%" float-symbol)

View file

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

View file

@ -0,0 +1 @@
(format #t "~f~%" (* 1.2 25.0 4.0))

View file

@ -0,0 +1,5 @@
(defun return-const-float ()
3.1415
)
(format #t "~,,5f~%" (return-const-float))
0

View file

@ -0,0 +1 @@
(+ (min 1 2) (min 2 (min -1 -4)) (max 10 (min 2 23)) (max 3 1))

View file

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

View file

@ -0,0 +1,4 @@
(define my-thing 'apple)
(set! my-thing 'banana)
(format #t "~A~%" my-thing)
0

View file

@ -151,6 +151,13 @@ void Compiler::color_object_file(FileEnv* env) {
input.instructions.push_back(i->to_rai()); input.instructions.push_back(i->to_rai());
input.debug_instruction_names.push_back(i->print()); 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.max_vars = f->max_vars();
input.constraints = f->constraints(); input.constraints = f->constraints();

View file

@ -170,6 +170,8 @@ class Compiler {
// Type // Type
Val* compile_deftype(const goos::Object& form, const goos::Object& rest, Env* env); 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 #endif // JAK_COMPILER_H

View file

@ -219,6 +219,8 @@ void IR_RegSet::do_codegen(emitter::ObjectGenerator* gen,
gen->add_instr(IGen::movd_gpr32_xmm32(dest_reg, val_reg), irec); gen->add_instr(IGen::movd_gpr32_xmm32(dest_reg, val_reg), irec);
} else if (val_reg.is_gpr() && dest_reg.is_xmm()) { } else if (val_reg.is_gpr() && dest_reg.is_xmm()) {
gen->add_instr(IGen::movd_xmm32_gpr32(dest_reg, val_reg), irec); 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 { } else {
assert(false); assert(false);
} }
@ -504,6 +506,8 @@ std::string IR_FloatMath::print() {
switch (m_kind) { switch (m_kind) {
case FloatMathKind::DIV_SS: case FloatMathKind::DIV_SS:
return fmt::format("divss {}, {}", m_dest->print(), m_arg->print()); 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: default:
throw std::runtime_error("Unsupported FloatMathKind"); throw std::runtime_error("Unsupported FloatMathKind");
} }
@ -525,6 +529,10 @@ void IR_FloatMath::do_codegen(emitter::ObjectGenerator* gen,
gen->add_instr( gen->add_instr(
IGen::divss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); IGen::divss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
break; 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: default:
assert(false); assert(false);
} }

View file

@ -223,7 +223,7 @@ class IR_IntegerMath : public IR {
RegVal* m_arg; RegVal* m_arg;
}; };
enum class FloatMathKind { DIV_SS }; enum class FloatMathKind { DIV_SS, MUL_SS };
class IR_FloatMath : public IR { class IR_FloatMath : public IR {
public: public:

View file

@ -94,7 +94,20 @@ RegVal* FloatConstantVal::to_reg(Env* fe) {
} }
RegVal* MemoryOffsetConstantVal::to_reg(Env* fe) { RegVal* MemoryOffsetConstantVal::to_reg(Env* fe) {
auto re = fe->make_gpr(deref_type); (void)fe;
fe->emit(std::make_unique<IR_LoadConstOffset>(re, offset, base, info)); assert(false);
return re; throw std::runtime_error("MemoryOffsetConstantVal::to_reg not yet implemented");
}
RegVal* MemoryDerefVal::to_reg(Env* fe) {
auto base_as_co = dynamic_cast<MemoryOffsetConstantVal*>(base);
if (base_as_co) {
auto re = fe->make_gpr(m_ts);
fe->emit(std::make_unique<IR_LoadConstOffset>(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");
}
} }

View file

@ -126,35 +126,45 @@ class StaticVal : public Val {
}; };
struct MemLoadInfo { 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; bool sign_extend = false;
int size = -1; int size = -1;
}; };
class MemoryOffsetConstantVal : public Val { class MemoryOffsetConstantVal : public Val {
public: public:
MemoryOffsetConstantVal(TypeSpec ts, MemoryOffsetConstantVal(TypeSpec ts, Val* _base, int _offset)
RegVal* _base, : Val(std::move(ts)), base(_base), offset(_offset) {}
int _offset,
MemLoadInfo _info,
TypeSpec _deref_type)
: Val(std::move(ts)),
base(_base),
offset(_offset),
info(_info),
deref_type(std::move(_deref_type)) {}
std::string print() const override { std::string print() const override {
return "(" + base->print() + " + " + std::to_string(offset) + ")"; return "(" + base->print() + " + " + std::to_string(offset) + ")";
} }
RegVal* to_reg(Env* fe) override; RegVal* to_reg(Env* fe) override;
RegVal* base = nullptr; Val* base = nullptr;
int offset = 0; int offset = 0;
MemLoadInfo info;
TypeSpec deref_type;
}; };
// MemOffConstant // MemOffConstant
// MemOffVar // 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 // PairEntry
// Alias // Alias

View file

@ -57,8 +57,9 @@ static const std::unordered_map<
// //
// TYPE // TYPE
{"deftype", &Compiler::compile_deftype}, {"deftype", &Compiler::compile_deftype},
{"defmethod", &Compiler::compile_defmethod},
// {"defenum", &Compiler::compile_defenum}, // {"defenum", &Compiler::compile_defenum},
// {"->", &Compiler::compile_deref}, {"->", &Compiler::compile_deref},
// {"&", &Compiler::compile_addr_of}, // {"&", &Compiler::compile_addr_of},
// //
// //
@ -83,7 +84,7 @@ static const std::unordered_map<
// {"the", &Compiler::compile_the}, // {"the", &Compiler::compile_the},
// {"the-as", &Compiler::compile_the_as}, // {"the-as", &Compiler::compile_the_as},
// //
// {"defmethod", &Compiler::compile_defmethod}, //
// //
// {"current-method-type", &Compiler::compile_current_method_type}, // {"current-method-type", &Compiler::compile_current_method_type},
// {"new", &Compiler::compile_new}, // {"new", &Compiler::compile_new},

View file

@ -91,9 +91,8 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
if (!inline_only) { if (!inline_only) {
// compile a function! First create env // compile a function! First create env
// auto new_func_env = fe->alloc_env<FunctionEnv>(env, lambda.debug_name);
auto new_func_env = std::make_unique<FunctionEnv>(env, lambda.debug_name); auto new_func_env = std::make_unique<FunctionEnv>(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 // set up arguments
assert(lambda.params.size() < 8); // todo graceful error 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<LambdaVal*>(head); head_as_lambda = dynamic_cast<LambdaVal*>(head);
} }
if (!head_as_lambda) { if (!head_as_lambda && !is_method_call) {
head = head->to_gpr(env); head = head->to_gpr(env);
} }
@ -282,12 +281,13 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
} else { } else {
// not an inline call // not an inline call
if (is_method_call) { if (is_method_call) {
// determine the method to call by looking at the type of first argument throw_compile_error(form, "Unrecognized symbol " + uneval_head.print() + " as head of form");
if (eval_args.empty()) { // // determine the method to call by looking at the type of first argument
throw_compile_error(form, "0 argument method call is impossible to figure out"); // if (eval_args.empty()) {
} //
printf("BAD %s\n", uneval_head.print().c_str()); // }
assert(false); // nyi // 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); // head = compile_get_method_of_object(eval_args.front(), symbol_string(uneval_head), env);
} }

View file

@ -148,6 +148,18 @@ Val* Compiler::compile_mul(const goos::Object& form, const goos::Object& rest, E
} }
return result; return result;
} }
case MATH_FLOAT: {
auto result = env->make_xmm(first_type);
env->emit(std::make_unique<IR_RegSet>(result, first_val->to_xmm(env)));
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_FloatMath>(
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: case MATH_INVALID:
throw_compile_error( throw_compile_error(
form, "Cannot determine the math mode for object of type " + first_type.print()); form, "Cannot determine the math mode for object of type " + first_type.print());

View file

@ -13,9 +13,16 @@ RegVal* Compiler::compile_get_method_of_type(const TypeSpec& type,
load_info.sign_extend = false; load_info.sign_extend = false;
load_info.size = POINTER_SIZE; load_info.size = POINTER_SIZE;
return fe auto loc_type = m_ts.make_pointer_typespec(info.type);
->alloc_val<MemoryOffsetConstantVal>(typ->type(), typ, offset_of_method, load_info, info.type) auto loc = fe->alloc_val<MemoryOffsetConstantVal>(loc_type, typ, offset_of_method);
->to_reg(env); 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<MemoryDerefVal>(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) { 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, return compile_real_function_call(form, new_type_method,
{new_type_symbol, parent_type, flags_int}, env); {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<FunctionEnv>(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<LambdaVal>(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<FunctionEnv>(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<BlockEnv>(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<IR_Return>(return_reg, final_result));
// new_func_env->emit(std::make_unique<IR_Null>())???
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<FileEnv>(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<FunctionEnv>(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<MemoryDerefVal>(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<StructureType*>(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<MemoryOffsetConstantVal>(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<MemoryDerefVal>(di.result_type, loc, MemLoadInfo(di));
} else {
assert(false);
}
continue;
}
// todo try bitfield
}
// todo array or other
assert(false);
}
return result;
}

View file

@ -115,6 +115,7 @@ class IGen {
instr.set_op2(0x0f); instr.set_op2(0x0f);
instr.set_op3(0x10); instr.set_op3(0x10);
instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, false); instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, false);
instr.swap_op0_rex();
return instr; return instr;
} }
@ -996,7 +997,8 @@ class IGen {
static Instruction store128_gpr64_xmm128(Register gpr_addr, Register xmm_value) { static Instruction store128_gpr64_xmm128(Register gpr_addr, Register xmm_value) {
assert(gpr_addr.is_gpr()); assert(gpr_addr.is_gpr());
assert(xmm_value.is_xmm()); assert(xmm_value.is_xmm());
Instruction instr(0x66); // Instruction instr(0x66);
Instruction instr(0xf3);
instr.set_op2(0x0f); instr.set_op2(0x0f);
instr.set_op3(0x7f); instr.set_op3(0x7f);
instr.set_modrm_and_rex_for_reg_addr(xmm_value.hw_id(), gpr_addr.hw_id(), false); 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) { static Instruction load128_xmm128_gpr64(Register xmm_dest, Register gpr_addr) {
assert(gpr_addr.is_gpr()); assert(gpr_addr.is_gpr());
assert(xmm_dest.is_xmm()); assert(xmm_dest.is_xmm());
Instruction instr(0x66); // Instruction instr(0x66);
Instruction instr(0xf3);
instr.set_op2(0x0f); instr.set_op2(0x0f);
instr.set_op3(0x6f); instr.set_op3(0x6f);
instr.set_modrm_and_rex_for_reg_addr(xmm_dest.hw_id(), gpr_addr.hw_id(), false); instr.set_modrm_and_rex_for_reg_addr(xmm_dest.hw_id(), gpr_addr.hw_id(), false);

View file

@ -23,6 +23,23 @@ RegisterInfo RegisterInfo::make_register_info() {
info.m_info[R14] = {-1, false, true, "r14"}; // st? info.m_info[R14] = {-1, false, true, "r14"}; // st?
info.m_info[R15] = {-1, false, true, "r15"}; // offset. 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<Register, N_ARGS>({RDI, RSI, RDX, RCX, R8, R9, R10, R11}); info.m_arg_regs = std::array<Register, N_ARGS>({RDI, RSI, RDX, RCX, R8, R9, R10, R11});
info.m_saved_gprs = std::array<Register, N_SAVED_GPRS>({RBX, RBP, R10, R11, R12}); info.m_saved_gprs = std::array<Register, N_SAVED_GPRS>({RBX, RBP, R10, R11, R12});
info.m_saved_xmms = info.m_saved_xmms =

View file

@ -57,36 +57,68 @@ TEST(CodeTester, xmm_store_128) {
// movdqa [r14], xmm3 // movdqa [r14], xmm3
// movdqa [rbx], xmm14 // movdqa [rbx], xmm14
// movdqa [r14], xmm13 // 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(RBX, XMM3));
tester.emit(IGen::store128_gpr64_xmm128(R14, XMM3)); tester.emit(IGen::store128_gpr64_xmm128(R14, XMM3));
tester.emit(IGen::store128_gpr64_xmm128(RBX, XMM14)); tester.emit(IGen::store128_gpr64_xmm128(RBX, XMM14));
tester.emit(IGen::store128_gpr64_xmm128(R14, XMM13)); tester.emit(IGen::store128_gpr64_xmm128(R14, XMM13));
EXPECT_EQ(tester.dump_to_hex_string(), 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.clear();
tester.emit(IGen::store128_gpr64_xmm128(RSP, XMM1)); 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.clear();
tester.emit(IGen::store128_gpr64_xmm128(R12, XMM13)); 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.clear();
tester.emit(IGen::store128_gpr64_xmm128(RBP, XMM1)); 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.clear();
tester.emit(IGen::store128_gpr64_xmm128(RBP, XMM11)); 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.clear();
tester.emit(IGen::store128_gpr64_xmm128(R13, XMM2)); 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.clear();
tester.emit(IGen::store128_gpr64_xmm128(R13, XMM12)); 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) { TEST(CodeTester, sub_gpr64_imm8) {
@ -114,36 +146,68 @@ TEST(CodeTester, add_gpr64_imm8) {
TEST(CodeTester, xmm_load_128) { TEST(CodeTester, xmm_load_128) {
CodeTester tester; CodeTester tester;
tester.init_code_buffer(256); tester.init_code_buffer(256);
tester.emit(IGen::load128_xmm128_gpr64(XMM3, RBX)); tester.emit(IGen::load128_xmm128_gpr64(XMM3, RBX));
tester.emit(IGen::load128_xmm128_gpr64(XMM3, R14)); tester.emit(IGen::load128_xmm128_gpr64(XMM3, R14));
tester.emit(IGen::load128_xmm128_gpr64(XMM14, RBX)); tester.emit(IGen::load128_xmm128_gpr64(XMM14, RBX));
tester.emit(IGen::load128_xmm128_gpr64(XMM13, R14)); tester.emit(IGen::load128_xmm128_gpr64(XMM13, R14));
EXPECT_EQ(tester.dump_to_hex_string(), 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.clear();
tester.emit(IGen::load128_xmm128_gpr64(XMM1, RSP)); 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.clear();
tester.emit(IGen::load128_xmm128_gpr64(XMM13, R12)); 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.clear();
tester.emit(IGen::load128_xmm128_gpr64(XMM1, RBP)); 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.clear();
tester.emit(IGen::load128_xmm128_gpr64(XMM11, RBP)); 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.clear();
tester.emit(IGen::load128_xmm128_gpr64(XMM2, R13)); 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.clear();
tester.emit(IGen::load128_xmm128_gpr64(XMM12, R13)); 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) { TEST(CodeTester, push_pop_xmms) {

View file

@ -125,6 +125,10 @@ TEST(CompilerAndRuntime, BuildGameAndTest) {
} }
// todo, tests after loading the game. // todo, tests after loading the game.
CompilerTestRunner runner;
runner.c = &compiler;
runner.run_test("test-min-max.gc", {"10\n"});
compiler.shutdown_target(); compiler.shutdown_target();
runtime_thread.join(); runtime_thread.join();
@ -254,6 +258,7 @@ TEST(CompilerAndRuntime, CompilerTests) {
runner.run_test("test-protect.gc", {"33\n"}); 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-format-reg-order.gc", {"test 1 2 3 4 5 6\n0\n"});
runner.run_test("test-quote-symbol.gc", {"banana\n0\n"});
// expected = // expected =
// "test newline\nnewline\ntest tilde ~ \ntest A print boxed-string: \"boxed string!\"\ntest // "test newline\nnewline\ntest tilde ~ \ntest A print boxed-string: \"boxed string!\"\ntest
@ -270,6 +275,15 @@ TEST(CompilerAndRuntime, CompilerTests) {
// // todo, finish format testing. // // todo, finish format testing.
// runner.run_test("test-format.gc", {expected}, expected.size()); // 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(); compiler.shutdown_target();
runtime_thread.join(); runtime_thread.join();
runner.print_summary(); runner.print_summary();