mirror of
https://github.com/open-goal/jak-project
synced 2026-08-17 13:40:39 -04:00
Add deftype (#48)
* initial deftype implementation * fix library setup for windows * implement deftype * fix memory bug * fix formatting
This commit is contained in:
@@ -52,6 +52,9 @@ include_directories(./)
|
||||
# build asset packer/unpacker
|
||||
add_subdirectory(asset_tool)
|
||||
|
||||
# build goos
|
||||
add_subdirectory(common/goos)
|
||||
|
||||
# build type_system library for compiler/decompiler
|
||||
add_subdirectory(common/type_system)
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
#include <utility>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "goalc/goos/Object.h"
|
||||
#include "goalc/goos/TextDB.h"
|
||||
#include "common/goos/Object.h"
|
||||
#include "common/goos/TextDB.h"
|
||||
|
||||
namespace goos {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
||||
#include "goalc/goos/Object.h"
|
||||
#include "common/goos/Object.h"
|
||||
|
||||
namespace goos {
|
||||
/*!
|
||||
@@ -2,6 +2,7 @@ add_library(type_system
|
||||
SHARED
|
||||
TypeSystem.cpp
|
||||
Type.cpp
|
||||
TypeSpec.cpp)
|
||||
TypeSpec.cpp
|
||||
deftype.cpp)
|
||||
|
||||
target_link_libraries(type_system fmt)
|
||||
target_link_libraries(type_system fmt goos)
|
||||
@@ -467,7 +467,7 @@ void StructureType::inherit(StructureType* parent) {
|
||||
}
|
||||
|
||||
bool StructureType::operator==(const Type& other) const {
|
||||
if (typeid(StructureType) != typeid(other)) {
|
||||
if (typeid(*this) != typeid(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ class StructureType : public ReferenceType {
|
||||
bool pack = false);
|
||||
std::string print() const override;
|
||||
void inherit(StructureType* parent);
|
||||
const std::vector<Field>& fields() { return m_fields; }
|
||||
const std::vector<Field>& fields() const { return m_fields; }
|
||||
bool operator==(const Type& other) const override;
|
||||
int get_size_in_memory() const override;
|
||||
int get_offset() const override;
|
||||
|
||||
@@ -591,6 +591,7 @@ void TypeSystem::add_builtin_types() {
|
||||
|
||||
// TYPE
|
||||
builtin_structure_inherit(type_type);
|
||||
add_method(type_type, "new", make_function_typespec({"symbol", "type", "int"}, "_type_"));
|
||||
add_field_to_type(type_type, "symbol", make_typespec("symbol"));
|
||||
add_field_to_type(type_type, "parent", make_typespec("type"));
|
||||
add_field_to_type(type_type, "allocated-size", make_typespec("uint16")); // todo, u16 or s16?
|
||||
|
||||
@@ -76,6 +76,7 @@ class TypeSystem {
|
||||
bool print_on_error = true,
|
||||
bool throw_on_error = true) const;
|
||||
std::vector<std::string> get_path_up_tree(const std::string& type);
|
||||
int get_next_method_id(Type* type);
|
||||
|
||||
/*!
|
||||
* Get a type by name and cast to a child class of Type*. Must succeed.
|
||||
@@ -99,7 +100,6 @@ class TypeSystem {
|
||||
int get_size_in_type(const Field& field);
|
||||
int get_alignment_in_type(const Field& field);
|
||||
Field lookup_field(const std::string& type_name, const std::string& field_name);
|
||||
int get_next_method_id(Type* type);
|
||||
StructureType* add_builtin_structure(const std::string& parent,
|
||||
const std::string& type_name,
|
||||
bool boxed = false);
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
#include "deftype.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
/*!
|
||||
* Missing Features
|
||||
* - Bitfields
|
||||
* - Int128 children
|
||||
* - Refer to yourself (structure/basic only)
|
||||
* - Method List
|
||||
* - Evaluate constants.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace {
|
||||
const goos::Object& car(const goos::Object* x) {
|
||||
if (!x->is_pair()) {
|
||||
throw std::runtime_error("invalid deftype form");
|
||||
}
|
||||
|
||||
return x->as_pair()->car;
|
||||
}
|
||||
|
||||
const goos::Object* cdr(const goos::Object* x) {
|
||||
if (!x->is_pair()) {
|
||||
throw std::runtime_error("invalid deftype form");
|
||||
}
|
||||
|
||||
return &x->as_pair()->cdr;
|
||||
}
|
||||
|
||||
std::string deftype_parent_list(const goos::Object& list) {
|
||||
if (!list.is_pair()) {
|
||||
throw std::runtime_error("invalid parent list in deftype: " + list.print());
|
||||
}
|
||||
|
||||
auto parent = list.as_pair()->car;
|
||||
auto rest = list.as_pair()->cdr;
|
||||
if (!rest.is_empty_list()) {
|
||||
throw std::runtime_error("invalid parent list in deftype - can only have one parent");
|
||||
}
|
||||
|
||||
if (!parent.is_symbol()) {
|
||||
throw std::runtime_error("invalid parent in deftype parent list");
|
||||
}
|
||||
|
||||
return parent.as_symbol()->name;
|
||||
}
|
||||
|
||||
bool is_type(const std::string& expected, const TypeSpec& actual, const TypeSystem* ts) {
|
||||
return ts->typecheck(ts->make_typespec(expected), actual, "", false, false);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void for_each_in_list(const goos::Object& list, T f) {
|
||||
const goos::Object* iter = &list;
|
||||
while (iter->is_pair()) {
|
||||
auto lap = iter->as_pair();
|
||||
f(lap->car);
|
||||
iter = &lap->cdr;
|
||||
}
|
||||
|
||||
if (!iter->is_empty_list()) {
|
||||
throw std::runtime_error("invalid list in for_each_in_list: " + list.print());
|
||||
}
|
||||
}
|
||||
|
||||
std::string symbol_string(const goos::Object& obj) {
|
||||
if (obj.is_symbol()) {
|
||||
return obj.as_symbol()->name;
|
||||
}
|
||||
throw std::runtime_error(obj.print() + " was supposed to be a symbol, but isn't");
|
||||
}
|
||||
|
||||
int64_t get_int(const goos::Object& obj) {
|
||||
if (obj.is_int()) {
|
||||
return obj.integer_obj.value;
|
||||
}
|
||||
throw std::runtime_error(obj.print() + " was supposed to be an integer, but isn't");
|
||||
}
|
||||
|
||||
TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src) {
|
||||
if (src.is_symbol()) {
|
||||
return type_system->make_typespec(symbol_string(src));
|
||||
} else if (src.is_pair()) {
|
||||
TypeSpec ts = type_system->make_typespec(symbol_string(car(&src)));
|
||||
const auto& rest = *cdr(&src);
|
||||
|
||||
for_each_in_list(rest,
|
||||
[&](const goos::Object& o) { ts.add_arg(parse_typespec(type_system, o)); });
|
||||
|
||||
return ts;
|
||||
} else {
|
||||
throw std::runtime_error("invalid typespec: " + src.print());
|
||||
}
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
void add_field(StructureType* structure, TypeSystem* ts, const goos::Object& def) {
|
||||
auto rest = &def;
|
||||
|
||||
auto name = symbol_string(car(rest));
|
||||
rest = cdr(rest);
|
||||
|
||||
auto type = parse_typespec(ts, car(rest));
|
||||
rest = cdr(rest);
|
||||
|
||||
int array_size = -1;
|
||||
bool is_inline = false;
|
||||
bool is_dynamic = false;
|
||||
int offset_override = -1;
|
||||
int offset_assert = -1;
|
||||
|
||||
if (!rest->is_empty_list()) {
|
||||
if (car(rest).is_int()) {
|
||||
array_size = car(rest).integer_obj.value;
|
||||
rest = cdr(rest);
|
||||
}
|
||||
|
||||
while (!rest->is_empty_list()) {
|
||||
auto opt_name = symbol_string(car(rest));
|
||||
rest = cdr(rest);
|
||||
|
||||
if (opt_name == ":inline") {
|
||||
is_inline = true;
|
||||
} else if (opt_name == ":dynamic") {
|
||||
is_dynamic = true;
|
||||
} else if (opt_name == ":offset") {
|
||||
offset_override = get_int(car(rest));
|
||||
rest = cdr(rest);
|
||||
} else if (opt_name == ":offset-assert") {
|
||||
offset_assert = get_int(car(rest));
|
||||
if (offset_assert == -1) {
|
||||
throw std::runtime_error("Cannot use -1 as offset-assert");
|
||||
}
|
||||
rest = cdr(rest);
|
||||
} else {
|
||||
throw std::runtime_error("Invalid option in field specification: " + opt_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int actual_offset = ts->add_field_to_type(structure, name, type, is_inline, is_dynamic,
|
||||
array_size, offset_override);
|
||||
if (offset_assert != -1 && actual_offset != offset_assert) {
|
||||
throw std::runtime_error("Field " + name + " was placed at " + std::to_string(actual_offset) +
|
||||
" but offset-assert was set to " + std::to_string(offset_assert));
|
||||
}
|
||||
}
|
||||
|
||||
void declare_method(StructureType* type, TypeSystem* type_system, const goos::Object& def) {
|
||||
for_each_in_list(def, [&](const goos::Object& _obj) {
|
||||
auto obj = &_obj;
|
||||
// (name args return-type [id])
|
||||
auto method_name = symbol_string(car(obj));
|
||||
obj = cdr(obj);
|
||||
auto& args = car(obj);
|
||||
obj = cdr(obj);
|
||||
auto& return_type = car(obj);
|
||||
obj = cdr(obj);
|
||||
int id = -1;
|
||||
if (!obj->is_empty_list()) {
|
||||
auto& id_obj = car(obj);
|
||||
id = get_int(id_obj);
|
||||
if (!cdr(obj)->is_empty_list()) {
|
||||
throw std::runtime_error("too many things in method def: " + def.print());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> arg_types;
|
||||
for_each_in_list(args, [&](const goos::Object& o) {
|
||||
if (o.is_symbol()) {
|
||||
arg_types.emplace_back(symbol_string(o));
|
||||
} else {
|
||||
auto next = cdr(&o);
|
||||
arg_types.emplace_back(symbol_string(car(next)));
|
||||
if (!cdr(next)->is_empty_list()) {
|
||||
throw std::runtime_error("too many things in method def arg type: " + def.print());
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
auto info = type_system->add_method(
|
||||
type, method_name,
|
||||
type_system->make_function_typespec(arg_types, symbol_string(return_type)));
|
||||
|
||||
// check the method assert
|
||||
if (id != -1) {
|
||||
// method id assert!
|
||||
if (id != info.id) {
|
||||
printf("WARNING - ID assert failed on method %s of type %s (wanted %d got %d)\n",
|
||||
method_name.c_str(), type->get_name().c_str(), id, info.id);
|
||||
throw std::runtime_error("Method ID assert failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TypeFlags parse_structure_def(StructureType* type,
|
||||
TypeSystem* ts,
|
||||
const goos::Object& fields,
|
||||
const goos::Object& options) {
|
||||
(void)options;
|
||||
for_each_in_list(fields, [&](const goos::Object& o) { add_field(type, ts, o); });
|
||||
TypeFlags flags;
|
||||
flags.heap_base = 0;
|
||||
|
||||
flags.size = type->get_size_in_memory();
|
||||
flags.pad = 0;
|
||||
|
||||
auto* rest = &options;
|
||||
int size_assert = -1;
|
||||
int method_count_assert = -1;
|
||||
uint64_t flag_assert = 0;
|
||||
bool flag_assert_set = false;
|
||||
while (!rest->is_empty_list()) {
|
||||
if (car(rest).is_pair()) {
|
||||
auto opt_list = &car(rest);
|
||||
auto& first = car(opt_list);
|
||||
opt_list = cdr(opt_list);
|
||||
|
||||
if (symbol_string(first) == ":methods") {
|
||||
declare_method(type, ts, *opt_list);
|
||||
} else {
|
||||
throw std::runtime_error("Invalid option list in field specification: " +
|
||||
car(rest).print());
|
||||
}
|
||||
|
||||
rest = cdr(rest);
|
||||
} else {
|
||||
auto opt_name = symbol_string(car(rest));
|
||||
rest = cdr(rest);
|
||||
|
||||
if (opt_name == ":size-assert") {
|
||||
size_assert = get_int(car(rest));
|
||||
if (size_assert == -1) {
|
||||
throw std::runtime_error("Cannot use -1 as size-assert");
|
||||
}
|
||||
rest = cdr(rest);
|
||||
} else if (opt_name == ":method-count-assert") {
|
||||
method_count_assert = get_int(car(rest));
|
||||
if (method_count_assert == -1) {
|
||||
throw std::runtime_error("Cannot use -1 as method_count_assert");
|
||||
}
|
||||
rest = cdr(rest);
|
||||
} else if (opt_name == ":flag-assert") {
|
||||
flag_assert = get_int(car(rest));
|
||||
flag_assert_set = true;
|
||||
rest = cdr(rest);
|
||||
} else {
|
||||
throw std::runtime_error("Invalid option in field specification: " + opt_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (size_assert != -1 && flags.size != u16(size_assert)) {
|
||||
throw std::runtime_error("Type " + type->get_name() + " came out to size " +
|
||||
std::to_string(int(flags.size)) + " but size-assert was set to " +
|
||||
std::to_string(size_assert));
|
||||
}
|
||||
|
||||
flags.methods = ts->get_next_method_id(type);
|
||||
|
||||
if (method_count_assert != -1 && flags.methods != u16(method_count_assert)) {
|
||||
throw std::runtime_error(
|
||||
"Type " + type->get_name() + " has " + std::to_string(int(flags.methods)) +
|
||||
" methods, but method-count-assert was set to " + std::to_string(method_count_assert));
|
||||
}
|
||||
|
||||
if (flag_assert_set && (flags.flag != flag_assert)) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Type {} has flag 0x{:x} but flag-assert was set to 0x{:x}", type->get_name(),
|
||||
flags.flag, flag_assert));
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) {
|
||||
auto iter = &deftype;
|
||||
|
||||
auto& type_name_obj = car(iter);
|
||||
iter = cdr(iter);
|
||||
auto& parent_list_obj = car(iter);
|
||||
iter = cdr(iter);
|
||||
auto& field_list_obj = car(iter);
|
||||
iter = cdr(iter);
|
||||
auto& options_obj = *iter;
|
||||
|
||||
if (!type_name_obj.is_symbol()) {
|
||||
throw std::runtime_error("deftype must be given a symbol as the type name");
|
||||
}
|
||||
|
||||
auto name = type_name_obj.as_symbol()->name;
|
||||
auto parent_type_name = deftype_parent_list(parent_list_obj);
|
||||
auto parent_type = ts->make_typespec(parent_type_name);
|
||||
DeftypeResult result;
|
||||
|
||||
if (is_type("basic", parent_type, ts)) {
|
||||
auto new_type = std::make_unique<BasicType>(parent_type_name, name);
|
||||
auto pto = dynamic_cast<BasicType*>(ts->lookup_type(parent_type));
|
||||
assert(pto);
|
||||
new_type->inherit(pto);
|
||||
result.flags = parse_structure_def(new_type.get(), ts, field_list_obj, options_obj);
|
||||
ts->add_type(name, std::move(new_type));
|
||||
} else if (is_type("structure", parent_type, ts)) {
|
||||
auto new_type = std::make_unique<StructureType>(parent_type_name, name);
|
||||
auto pto = dynamic_cast<StructureType*>(ts->lookup_type(parent_type));
|
||||
assert(pto);
|
||||
new_type->inherit(pto);
|
||||
result.flags = parse_structure_def(new_type.get(), ts, field_list_obj, options_obj);
|
||||
ts->add_type(name, std::move(new_type));
|
||||
} else if (is_type("integer", parent_type, ts)) {
|
||||
throw std::runtime_error("Creating a child type of integer is not supported yet.");
|
||||
} else {
|
||||
throw std::runtime_error("Creating a child type from " + parent_type.print() +
|
||||
" is not allowed or not supported yet.");
|
||||
}
|
||||
|
||||
result.type = ts->make_typespec(name);
|
||||
result.type_info = ts->lookup_type(result.type);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "TypeSystem.h"
|
||||
#include "common/goos/Object.h"
|
||||
|
||||
struct TypeFlags {
|
||||
union {
|
||||
uint64_t flag = 0;
|
||||
struct {
|
||||
uint16_t size;
|
||||
uint16_t heap_base;
|
||||
uint16_t methods;
|
||||
uint16_t pad;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
struct DeftypeResult {
|
||||
TypeFlags flags;
|
||||
TypeSpec type;
|
||||
Type* type_info = nullptr;
|
||||
};
|
||||
|
||||
DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts);
|
||||
@@ -22,10 +22,11 @@ std::string file_util::get_project_path() {
|
||||
0, pos + 11); // + 12 to include "\jak-project" in the returned filepath
|
||||
#else
|
||||
// do Linux stuff
|
||||
char buffer[FILENAME_MAX];
|
||||
readlink("/proc/self/exe", buffer,
|
||||
FILENAME_MAX); // /proc/self acts like a "virtual folder" containing information about
|
||||
// the current process
|
||||
char buffer[FILENAME_MAX + 1];
|
||||
auto len = readlink("/proc/self/exe", buffer,
|
||||
FILENAME_MAX); // /proc/self acts like a "virtual folder" containing
|
||||
// information about the current process
|
||||
buffer[len] = '\0';
|
||||
std::string::size_type pos =
|
||||
std::string(buffer).rfind("jak-project"); // Strip file path down to /jak-project/ directory
|
||||
return std::string(buffer).substr(
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
(defun mod ((a integer) (b integer))
|
||||
"Compute mod. It does what you expect for positive numbers. For negative numbers, nobody knows what to expect.
|
||||
This is a 32-bit operation. It uses an idiv on x86 and gets the remainder."
|
||||
|
||||
|
||||
;; The original implementation is div, mfhi
|
||||
;; todo - verify this is exactly the same as the PS2.
|
||||
(mod a b)
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
(defun rem ((a integer) (b integer))
|
||||
"Compute remainder (32-bit). It is identical to mod. It uses a idiv and gets the remainder"
|
||||
|
||||
|
||||
;; The original implementation is div, mfhi
|
||||
;; todo - verify this is exactly the same as the PS2.
|
||||
(mod a b)
|
||||
@@ -206,5 +206,43 @@
|
||||
;; or 128-bit arguments (unimplemented in C Kernel), but both of these were never finished.
|
||||
(define format _format)
|
||||
|
||||
;; todo bfloat
|
||||
;; todo pinrt bfloat
|
||||
;; TODO - vec4s
|
||||
|
||||
;; The "boxed float" type bfloat is just a float wrapped in a basic (structure type that has runtime type information)
|
||||
|
||||
;; 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.
|
||||
)
|
||||
|
||||
|
||||
;; 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.
|
||||
|
||||
|
||||
;; make sure the size of the type is correct (this is stored in the type structure, so we can check it)
|
||||
:size-assert 8
|
||||
;; make sure method count is correct (again, stored in the type structure)
|
||||
:method-count-assert 9
|
||||
;; flags passed to the new_type function in the runtime.
|
||||
:flag-assert #x900000008
|
||||
)
|
||||
|
||||
|
||||
;; todo print bfloat
|
||||
@@ -1,4 +1,3 @@
|
||||
add_subdirectory(goos)
|
||||
add_subdirectory(listener)
|
||||
|
||||
add_library(compiler
|
||||
@@ -22,6 +21,7 @@ add_library(compiler
|
||||
compiler/compilation/Define.cpp
|
||||
compiler/compilation/Function.cpp
|
||||
compiler/compilation/ControlFlow.cpp
|
||||
compiler/compilation/Type.cpp
|
||||
compiler/Util.cpp
|
||||
logger/Logger.cpp
|
||||
regalloc/IRegister.cpp
|
||||
|
||||
@@ -200,7 +200,7 @@ std::vector<std::string> Compiler::run_test(const std::string& source_code) {
|
||||
return m_listener.stop_recording_messages();
|
||||
} catch (std::exception& e) {
|
||||
fmt::print("[Compiler] Failed to compile test program {}: {}\n", source_code, e.what());
|
||||
return {};
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "common/type_system/TypeSystem.h"
|
||||
#include "Env.h"
|
||||
#include "goalc/listener/Listener.h"
|
||||
#include "goalc/goos/Interpreter.h"
|
||||
#include "common/goos/Interpreter.h"
|
||||
#include "goalc/compiler/IR.h"
|
||||
#include "CompilerSettings.h"
|
||||
|
||||
@@ -105,6 +105,10 @@ class Compiler {
|
||||
|
||||
Val* compile_variable_shift(const RegVal* in, const RegVal* sa, Env* env, IntegerMathKind kind);
|
||||
|
||||
RegVal* compile_get_method_of_type(const TypeSpec& type,
|
||||
const std::string& method_name,
|
||||
Env* env);
|
||||
|
||||
public:
|
||||
// Atoms
|
||||
|
||||
@@ -163,6 +167,9 @@ class Compiler {
|
||||
Val* compile_lambda(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_inline(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_declare(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
|
||||
// Type
|
||||
Val* compile_deftype(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
};
|
||||
|
||||
#endif // JAK_COMPILER_H
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include "goalc/goos/Object.h"
|
||||
#include "common/goos/Object.h"
|
||||
|
||||
class CompilerSettings {
|
||||
public:
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <vector>
|
||||
#include "common/type_system/TypeSpec.h"
|
||||
#include "goalc/regalloc/allocate.h"
|
||||
#include "goalc/goos/Object.h"
|
||||
#include "common/goos/Object.h"
|
||||
#include "StaticObject.h"
|
||||
#include "Label.h"
|
||||
#include "Val.h"
|
||||
|
||||
@@ -665,4 +665,38 @@ void IR_ConditionalBranch::do_codegen(emitter::ObjectGenerator* gen,
|
||||
|
||||
auto jump_rec = gen->add_instr(jump_instr, irec);
|
||||
gen->link_instruction_jump(jump_rec, gen->get_future_ir_record_in_same_func(irec, label.idx));
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// LoadConstantOffset
|
||||
/////////////////////
|
||||
|
||||
IR_LoadConstOffset::IR_LoadConstOffset(const RegVal* dest,
|
||||
int offset,
|
||||
const RegVal* base,
|
||||
MemLoadInfo info)
|
||||
: m_dest(dest), m_offset(offset), m_base(base), m_info(info) {}
|
||||
|
||||
std::string IR_LoadConstOffset::print() {
|
||||
return fmt::format("mov {}, [{} + {}]\n", m_dest->print(), m_base->print(), m_offset);
|
||||
}
|
||||
|
||||
RegAllocInstr IR_LoadConstOffset::to_rai() {
|
||||
RegAllocInstr rai;
|
||||
rai.write.push_back(m_dest->ireg());
|
||||
rai.read.push_back(m_base->ireg());
|
||||
return rai;
|
||||
}
|
||||
|
||||
void IR_LoadConstOffset::do_codegen(emitter::ObjectGenerator* gen,
|
||||
const AllocationResult& allocs,
|
||||
emitter::IR_Record irec) {
|
||||
if (m_dest->ireg().kind == emitter::RegKind::GPR) {
|
||||
gen->add_instr(IGen::load_goal_gpr(get_reg(m_dest, allocs, irec), get_reg(m_base, allocs, irec),
|
||||
emitter::gRegInfo.get_offset_reg(), m_offset, m_info.size,
|
||||
m_info.sign_extend),
|
||||
irec);
|
||||
} else {
|
||||
throw std::runtime_error("IR_LoadConstOffset::do_codegen xmm not supported");
|
||||
}
|
||||
}
|
||||
@@ -270,4 +270,20 @@ class IR_ConditionalBranch : public IR {
|
||||
bool m_resolved = false;
|
||||
};
|
||||
|
||||
class IR_LoadConstOffset : public IR {
|
||||
public:
|
||||
IR_LoadConstOffset(const RegVal* dest, int offset, const RegVal* base, MemLoadInfo info);
|
||||
std::string print() override;
|
||||
RegAllocInstr to_rai() override;
|
||||
void do_codegen(emitter::ObjectGenerator* gen,
|
||||
const AllocationResult& allocs,
|
||||
emitter::IR_Record irec) override;
|
||||
|
||||
private:
|
||||
const RegVal* m_dest = nullptr;
|
||||
int m_offset = 0;
|
||||
const RegVal* m_base = nullptr;
|
||||
MemLoadInfo m_info;
|
||||
};
|
||||
|
||||
#endif // JAK_IR_H
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#ifndef JAK_LAMBDA_H
|
||||
#define JAK_LAMBDA_H
|
||||
|
||||
#include "goalc/goos/Object.h"
|
||||
#include "common/goos/Object.h"
|
||||
|
||||
// note - we cannot easily reuse the GOOS argument system because GOAL's is slightly different.
|
||||
// there's no rest or keyword support.
|
||||
|
||||
@@ -91,4 +91,10 @@ RegVal* FloatConstantVal::to_reg(Env* fe) {
|
||||
auto re = fe->make_xmm(m_ts);
|
||||
fe->emit(std::make_unique<IR_StaticVarLoad>(re, m_value));
|
||||
return re;
|
||||
}
|
||||
|
||||
RegVal* MemoryOffsetConstantVal::to_reg(Env* fe) {
|
||||
auto re = fe->make_gpr(deref_type);
|
||||
fe->emit(std::make_unique<IR_LoadConstOffset>(re, offset, base, info));
|
||||
return re;
|
||||
}
|
||||
@@ -125,6 +125,33 @@ class StaticVal : public Val {
|
||||
RegVal* to_reg(Env* fe) override;
|
||||
};
|
||||
|
||||
struct MemLoadInfo {
|
||||
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)) {}
|
||||
std::string print() const override {
|
||||
return "(" + base->print() + " + " + std::to_string(offset) + ")";
|
||||
}
|
||||
RegVal* to_reg(Env* fe) override;
|
||||
RegVal* base = nullptr;
|
||||
int offset = 0;
|
||||
MemLoadInfo info;
|
||||
TypeSpec deref_type;
|
||||
};
|
||||
|
||||
// MemOffConstant
|
||||
// MemOffVar
|
||||
// MemDeref
|
||||
|
||||
@@ -55,13 +55,9 @@ static const std::unordered_map<
|
||||
// {"defun-extern", &Compiler::compile_defun_extern},
|
||||
// {"declare-method", &Compiler::compile_declare_method},
|
||||
//
|
||||
// // DEFTYPE
|
||||
// {"deftype", &Compiler::compile_deftype},
|
||||
//
|
||||
// // ENUM
|
||||
// TYPE
|
||||
{"deftype", &Compiler::compile_deftype},
|
||||
// {"defenum", &Compiler::compile_defenum},
|
||||
//
|
||||
// // Field Access
|
||||
// {"->", &Compiler::compile_deref},
|
||||
// {"&", &Compiler::compile_addr_of},
|
||||
//
|
||||
@@ -72,7 +68,6 @@ static const std::unordered_map<
|
||||
{"inline", &Compiler::compile_inline},
|
||||
// {"with-inline", &Compiler::compile_with_inline},
|
||||
// {"rlet", &Compiler::compile_rlet},
|
||||
|
||||
// {"get-ra-ptr", &Compiler::compile_get_ra_ptr},
|
||||
//
|
||||
//
|
||||
|
||||
@@ -341,7 +341,8 @@ Val* Compiler::compile_real_function_call(const goos::Object& form,
|
||||
printf("got type %s\n", function->type().print().c_str());
|
||||
throw_compile_error(form, "invalid number of arguments to function call: got " +
|
||||
std::to_string(args.size()) + " and expected " +
|
||||
std::to_string(function->type().arg_count() - 1));
|
||||
std::to_string(function->type().arg_count() - 1) + " for " +
|
||||
function->type().print());
|
||||
}
|
||||
for (uint32_t i = 0; i < args.size(); i++) {
|
||||
typecheck(form, function->type().get_arg(i), args.at(i)->type(), "function argument");
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "goalc/compiler/Compiler.h"
|
||||
#include "common/type_system/deftype.h"
|
||||
|
||||
RegVal* Compiler::compile_get_method_of_type(const TypeSpec& type,
|
||||
const std::string& method_name,
|
||||
Env* env) {
|
||||
auto info = m_ts.lookup_method(type.base_type(), method_name);
|
||||
auto offset_of_method = 16 + 4 * info.id; // todo, something more flexible?
|
||||
|
||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||
auto typ = compile_get_symbol_value(type.base_type(), env)->to_gpr(env);
|
||||
MemLoadInfo load_info;
|
||||
load_info.sign_extend = false;
|
||||
load_info.size = POINTER_SIZE;
|
||||
|
||||
return fe
|
||||
->alloc_val<MemoryOffsetConstantVal>(typ->type(), typ, offset_of_method, load_info, info.type)
|
||||
->to_reg(env);
|
||||
}
|
||||
|
||||
Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||
(void)form;
|
||||
(void)env;
|
||||
|
||||
auto result = parse_deftype(rest, &m_ts);
|
||||
|
||||
auto kv = m_symbol_types.find(result.type.base_type());
|
||||
if (kv != m_symbol_types.end() && kv->second.base_type() != "type") {
|
||||
fmt::print("[Warning] deftype will redefined {} from {} to a type.\n", result.type.base_type(),
|
||||
kv->second.print());
|
||||
}
|
||||
m_symbol_types[result.type.base_type()] = m_ts.make_typespec("type");
|
||||
|
||||
auto new_type_method = compile_get_method_of_type(m_ts.make_typespec("type"), "new", env);
|
||||
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);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ add_executable(goalc-test
|
||||
test_emitter_integer_math.cpp
|
||||
test_common_util.cpp
|
||||
test_compiler_and_runtime.cpp
|
||||
test_deftype.cpp
|
||||
)
|
||||
|
||||
enable_testing()
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "common/type_system/TypeSystem.h"
|
||||
#include "common/type_system/deftype.h"
|
||||
#include "common/goos/Reader.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
TEST(Deftype, deftype) {
|
||||
TypeSystem ts;
|
||||
ts.add_builtin_types();
|
||||
std::string input =
|
||||
"(deftype my-type (basic) ((f1 int64) (f2 string) (f3 int8) (f4 type :inline)))";
|
||||
goos::Reader reader;
|
||||
auto in = reader.read_from_string(input).as_pair()->cdr.as_pair()->car.as_pair()->cdr;
|
||||
auto result = parse_deftype(in, &ts);
|
||||
|
||||
auto& f = dynamic_cast<StructureType*>(ts.lookup_type(result.type))->fields();
|
||||
EXPECT_EQ(f.size(), 5);
|
||||
|
||||
auto& tf = f.at(0);
|
||||
EXPECT_EQ(tf.name(), "type");
|
||||
EXPECT_EQ(tf.offset(), 0);
|
||||
EXPECT_EQ(tf.type().print(), "type");
|
||||
EXPECT_EQ(tf.is_inline(), false);
|
||||
|
||||
auto& f1 = f.at(1);
|
||||
EXPECT_EQ(f1.name(), "f1");
|
||||
EXPECT_EQ(f1.offset(), 8);
|
||||
EXPECT_EQ(f1.type().print(), "int64");
|
||||
EXPECT_EQ(f1.is_inline(), false);
|
||||
|
||||
auto& f2 = f.at(2);
|
||||
EXPECT_EQ(f2.name(), "f2");
|
||||
EXPECT_EQ(f2.offset(), 16);
|
||||
EXPECT_EQ(f2.type().print(), "string");
|
||||
EXPECT_EQ(f2.is_inline(), false);
|
||||
|
||||
auto& f3 = f.at(3);
|
||||
EXPECT_EQ(f3.name(), "f3");
|
||||
EXPECT_EQ(f3.offset(), 20);
|
||||
EXPECT_EQ(f3.type().print(), "int8");
|
||||
EXPECT_EQ(f3.is_inline(), false);
|
||||
|
||||
auto& f4 = f.at(4);
|
||||
EXPECT_EQ(f4.name(), "f4");
|
||||
EXPECT_EQ(f4.offset(), 32);
|
||||
EXPECT_EQ(f4.type().print(), "type");
|
||||
EXPECT_EQ(f4.is_inline(), true);
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "goalc/goos/Interpreter.h"
|
||||
#include "common/goos/Interpreter.h"
|
||||
|
||||
using namespace goos;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "goalc/goos/Reader.h"
|
||||
#include "common/goos/Reader.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
|
||||
using namespace goos;
|
||||
|
||||
@@ -315,4 +315,5 @@ TEST(TypeSystem, lca) {
|
||||
.print(),
|
||||
"(pointer object)");
|
||||
}
|
||||
|
||||
// TODO - a big test to make sure all the builtin types are what we expect.
|
||||
|
||||
Reference in New Issue
Block a user