Support Behaviors (#678)

* temp

* working, but type pass got really slow

* clean up

* changelog and flip order

* clean up and add tests

* fix zero size array

* handle lambdas correctly

* another windows fix
This commit is contained in:
water111
2021-07-04 16:54:07 -04:00
committed by GitHub
parent 285c10fd1d
commit e251f8b2d9
28 changed files with 1517 additions and 328 deletions
+1 -1
View File
@@ -601,7 +601,7 @@ void insertSpecialBreaks(NodePool& pool, PrettyPrinterNode* node) {
}
if (name == "defun" || name == "defmethod" || name == "defun-debug" || name == "let" ||
name == "let*" || name == "rlet") {
name == "let*" || name == "rlet" || name == "defbehavior") {
auto* first_list = getNextListOrEmptyListOnLine(node);
if (first_list) {
if (first_list->tok->kind == FormToken::TokenKind::EMPTY_PAIR) {
+82 -19
View File
@@ -3,22 +3,30 @@
* A GOAL TypeSpec is a reference to a type or compound type.
*/
#include <stdexcept>
#include "TypeSpec.h"
#include "Type.h"
#include "third-party/fmt/core.h"
TypeSpec::TypeSpec(std::string type) : m_type(std::move(type)) {}
TypeSpec::TypeSpec(std::string type, std::vector<TypeSpec> arguments)
: m_type(std::move(type)), m_arguments(std::move(arguments)) {}
bool TypeTag::operator==(const TypeTag& other) const {
return name == other.name && value == other.value;
}
std::string TypeSpec::print() const {
if (m_arguments.empty()) {
if ((!m_arguments || m_arguments->empty()) && m_tags.empty()) {
return m_type;
} else {
std::string result = "(" + m_type;
for (auto& x : m_arguments) {
result += " " + x.print();
if (m_arguments) {
for (auto& x : *m_arguments) {
result += " " + x.print();
}
}
for (const auto& tag : m_tags) {
result += fmt::format(" :{} {}", tag.name, tag.value);
}
return result + ")";
}
}
@@ -28,25 +36,31 @@ bool TypeSpec::operator!=(const TypeSpec& other) const {
}
bool TypeSpec::operator==(const TypeSpec& other) const {
if (other.m_type != m_type || other.m_arguments.size() != m_arguments.size()) {
if (m_type != other.m_type) {
return false;
}
for (size_t i = 0; i < m_arguments.size(); i++) {
if (other.m_arguments[i] != m_arguments[i]) {
return false;
}
if (m_tags != other.m_tags) {
return false;
}
return true;
if (m_arguments && other.m_arguments) {
return *m_arguments == *other.m_arguments;
}
return empty() && other.empty();
}
TypeSpec TypeSpec::substitute_for_method_call(const std::string& method_type) const {
TypeSpec result;
result.m_type = (m_type == "_type_") ? method_type : m_type;
for (const auto& x : m_arguments) {
result.m_arguments.push_back(x.substitute_for_method_call(method_type));
if (m_arguments) {
result.m_arguments = new std::vector<TypeSpec>();
for (const auto& x : *m_arguments) {
result.m_arguments->push_back(x.substitute_for_method_call(method_type));
}
}
return result;
}
@@ -54,15 +68,64 @@ 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()) {
if (!ok || implementation.arg_count() != arg_count()) {
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)) {
for (size_t i = 0; i < arg_count(); i++) {
if (!get_arg(i).is_compatible_child_method(implementation.get_arg(i), child_type)) {
return false;
}
}
return true;
}
void TypeSpec::add_new_tag(const std::string& tag_name, const std::string& tag_value) {
for (auto& v : m_tags) {
if (v.name == tag_name) {
throw std::runtime_error(
fmt::format("Attempted to add a duplicate tag {} to typespec.", tag_name));
}
}
m_tags.push_back({tag_name, tag_value});
}
std::optional<std::string> TypeSpec::try_get_tag(const std::string& tag_name) const {
for (auto& tag : m_tags) {
if (tag.name == tag_name) {
return tag.value;
}
}
return {};
}
const std::string& TypeSpec::get_tag(const std::string& tag_name) const {
for (auto& tag : m_tags) {
if (tag.name == tag_name) {
return tag.value;
}
}
throw std::runtime_error(fmt::format("TypeSpec didn't have tag {}", tag_name));
}
void TypeSpec::modify_tag(const std::string& tag_name, const std::string& tag_value) {
for (auto& tag : m_tags) {
if (tag.name == tag_name) {
tag.value = tag_value;
return;
}
}
throw std::runtime_error(fmt::format("TypeSpec didn't have tag {}", tag_name));
}
void TypeSpec::add_or_modify_tag(const std::string& tag_name, const std::string& tag_value) {
for (auto& tag : m_tags) {
if (tag.name == tag_name) {
tag.value = tag_value;
return;
}
}
m_tags.push_back({tag_name, tag_value});
}
+106 -21
View File
@@ -5,14 +5,22 @@
* A GOAL TypeSpec is a reference to a type or compound type.
*/
#ifndef JAK_TYPESPEC_H
#define JAK_TYPESPEC_H
#include <vector>
#include <string>
#include <optional>
#include "common/util/assert.h"
#include "common/util/SmallVector.h"
class Type;
/*!
* A :name value modifier to apply to a type.
*/
struct TypeTag {
std::string name;
std::string value;
bool operator==(const TypeTag& other) const;
bool operator!=(const TypeTag& other) const { return !((*this) == other); }
};
/*!
* A TypeSpec is a reference to a Type, or possible a compound type. This is the best way to
@@ -24,10 +32,48 @@ class Type;
*/
class TypeSpec {
public:
// create a typespec for a single type
TypeSpec() = default;
TypeSpec(std::string type);
TypeSpec(std::string type, std::vector<TypeSpec> arguments);
TypeSpec(const std::string& type) : m_type(type) {}
TypeSpec(const std::string& type, const std::vector<TypeSpec>& arguments)
: m_type(type), m_arguments(new std::vector<TypeSpec>(arguments)) {}
TypeSpec(const TypeSpec& other) {
m_type = other.m_type;
m_tags = other.m_tags;
if (other.m_arguments) {
m_arguments = new std::vector<TypeSpec>(*other.m_arguments);
}
}
TypeSpec& operator=(const TypeSpec& other) {
if (this == &other) {
return *this;
}
if (m_arguments) {
delete m_arguments;
m_arguments = nullptr;
}
m_type = other.m_type;
m_tags = other.m_tags;
if (other.m_arguments) {
m_arguments = new std::vector<TypeSpec>(*other.m_arguments);
}
return *this;
}
~TypeSpec() { delete m_arguments; }
// TypeSpec(const std::string& type, const std::vector<TypeTag>& tags)
// : m_type(type), m_tags(tags) {}
//
// TypeSpec(const std::string type,
// const std::vector<TypeSpec>& arguments,
// const std::vector<TypeTag>& tags)
// : m_type(type), m_arguments(arguments), m_tags(tags) {}
bool operator!=(const TypeSpec& other) const;
bool operator==(const TypeSpec& other) const;
@@ -35,32 +81,71 @@ class TypeSpec {
const std::string& child_type) const;
std::string print() const;
void add_arg(const TypeSpec& ts) { m_arguments.push_back(ts); }
void add_arg(const TypeSpec& ts) {
if (!m_arguments) {
m_arguments = new std::vector<TypeSpec>();
}
m_arguments->push_back(ts);
}
void add_new_tag(const std::string& tag_name, const std::string& tag_value);
std::optional<std::string> try_get_tag(const std::string& tag_name) const;
const std::string& get_tag(const std::string& tag_name) const;
void modify_tag(const std::string& tag_name, const std::string& tag_value);
void add_or_modify_tag(const std::string& tag_name, const std::string& tag_value);
const std::string base_type() const { return m_type; }
bool has_single_arg() const { return m_arguments.size() == 1; }
bool has_single_arg() const {
if (m_arguments) {
return m_arguments->size() == 1;
}
return 0;
}
const TypeSpec& get_single_arg() const {
assert(m_arguments.size() == 1);
return m_arguments.front();
assert(m_arguments);
assert(m_arguments->size() == 1);
return m_arguments->front();
}
TypeSpec substitute_for_method_call(const std::string& method_type) const;
size_t arg_count() const { return m_arguments.size(); }
const TypeSpec& get_arg(int idx) const { return m_arguments.at(idx); }
TypeSpec& get_arg(int idx) { return m_arguments.at(idx); }
const TypeSpec& last_arg() const {
assert(!m_arguments.empty());
return m_arguments.back();
size_t arg_count() const {
if (!m_arguments) {
return 0;
}
return m_arguments->size();
}
const TypeSpec& get_arg(int idx) const {
assert(m_arguments);
return m_arguments->at(idx);
}
TypeSpec& get_arg(int idx) {
assert(m_arguments);
return m_arguments->at(idx);
}
const TypeSpec& last_arg() const {
assert(m_arguments);
assert(!m_arguments->empty());
return m_arguments->back();
}
bool empty() const {
if (!m_arguments) {
return true;
} else {
return m_arguments->empty();
}
}
const cu::SmallVector<TypeTag, 1>& tags() const { return m_tags; }
private:
friend class TypeSystem;
std::string m_type;
std::vector<TypeSpec> m_arguments;
// hiding this behind a pointer makes things faster in the case where we have no
// arguments (most of the time) and makes the type analysis pass in the decompiler 2x faster.
std::vector<TypeSpec>* m_arguments = nullptr;
cu::SmallVector<TypeTag, 1> m_tags;
};
#endif // JAK_TYPESPEC_H
+39 -13
View File
@@ -1295,18 +1295,18 @@ bool TypeSystem::typecheck_and_throw(const TypeSpec& expected,
}
// next argument checks:
if (expected.m_arguments.size() == actual.m_arguments.size()) {
for (size_t i = 0; i < expected.m_arguments.size(); i++) {
if (expected.arg_count() == actual.arg_count()) {
for (size_t i = 0; i < expected.arg_count(); i++) {
// don't print/throw because the error would be confusing. Better to fail only the
// outer most check and print a single error message.
if (!tc(expected.m_arguments[i], actual.m_arguments[i])) {
if (!tc(expected.get_arg(i), actual.get_arg(i))) {
success = false;
break;
}
}
} else {
// different sizes of arguments.
if (expected.m_arguments.empty()) {
if (expected.arg_count() == 0) {
// we expect zero arguments, but got some. The actual type is more specific, so this is fine.
} else {
// different sizes, and we expected arguments. No good!
@@ -1314,6 +1314,22 @@ bool TypeSystem::typecheck_and_throw(const TypeSpec& expected,
}
}
// next, tag checks. It's fine to throw away tags, but the child must match all parent tags
for (auto& tag : expected.tags()) {
if (tag.name == "behavior") {
auto got = actual.try_get_tag(tag.name);
if (!got) {
success = false;
} else {
if (*got != tag.value) {
success = false;
}
}
} else {
throw_typesystem_error("Unknown tag {}", tag.name);
}
}
if (!success) {
if (print_on_error) {
if (error_source_name.empty()) {
@@ -1431,16 +1447,14 @@ std::string TypeSystem::lca_base(const std::string& a, const std::string& b) con
*/
TypeSpec TypeSystem::lowest_common_ancestor(const TypeSpec& a, const TypeSpec& b) const {
auto result = make_typespec(lca_base(a.base_type(), b.base_type()));
if (result == TypeSpec("function") && a.m_arguments.size() == 2 && b.m_arguments.size() == 2 &&
(a.m_arguments.at(0) == TypeSpec("_varargs_") ||
b.m_arguments.at(0) == TypeSpec("_varargs_"))) {
if (result == TypeSpec("function") && a.arg_count() == 2 && b.arg_count() == 2 &&
(a.get_arg(0) == TypeSpec("_varargs_") || b.get_arg(0) == TypeSpec("_varargs_"))) {
return TypeSpec("function");
}
if (!a.m_arguments.empty() && !b.m_arguments.empty() &&
a.m_arguments.size() == b.m_arguments.size()) {
if (!a.empty() && !b.empty() && a.arg_count() == b.arg_count()) {
// recursively add arguments
for (size_t i = 0; i < a.m_arguments.size(); i++) {
result.add_arg(lowest_common_ancestor(a.m_arguments.at(i), b.m_arguments.at(i)));
for (size_t i = 0; i < a.arg_count(); i++) {
result.add_arg(lowest_common_ancestor(a.get_arg(i), b.get_arg(i)));
}
}
return result;
@@ -1594,8 +1608,15 @@ std::string TypeSystem::generate_deftype_footer(const Type* type) const {
methods_string.push_back(' ');
}
}
methods_string.append(fmt::format(
") {} {})\n ", new_info->type.get_arg(new_info->type.arg_count() - 1).print(), 0));
methods_string.append(
fmt::format(") {} ", new_info->type.get_arg(new_info->type.arg_count() - 1).print(), 0));
auto behavior = new_info->type.try_get_tag("behavior");
if (behavior) {
methods_string.append(fmt::format(":behavior {} ", *behavior));
}
methods_string.append("0)\n ");
}
for (auto& info : type->get_methods_defined_for_type()) {
@@ -1617,6 +1638,11 @@ std::string TypeSystem::generate_deftype_footer(const Type* type) const {
methods_string.append(":replace ");
}
auto behavior = info.type.try_get_tag("behavior");
if (behavior) {
methods_string.append(fmt::format(":behavior {} ", *behavior));
}
methods_string.append(fmt::format("{})\n ", info.id));
}
+39 -5
View File
@@ -189,6 +189,7 @@ void declare_method(Type* type, TypeSystem* type_system, const goos::Object& def
bool no_virtual = false;
bool replace_method = false;
TypeSpec function_typespec("function");
if (!obj->is_empty_list() && car(obj).is_symbol(":no-virtual")) {
obj = cdr(obj);
@@ -200,6 +201,12 @@ void declare_method(Type* type, TypeSystem* type_system, const goos::Object& def
replace_method = true;
}
if (!obj->is_empty_list() && car(obj).is_symbol(":behavior")) {
obj = cdr(obj);
function_typespec.add_new_tag("behavior", symbol_string(obj->as_pair()->car));
obj = cdr(obj);
}
int id = -1;
if (!obj->is_empty_list() && car(obj).is_int()) {
auto& id_obj = car(obj);
@@ -211,8 +218,6 @@ void declare_method(Type* type, TypeSystem* type_system, const goos::Object& def
throw std::runtime_error("too many things in method def: " + def.print());
}
TypeSpec function_typespec("function");
for_each_in_list(args, [&](const goos::Object& o) {
function_typespec.add_arg(parse_typespec(type_system, o));
});
@@ -441,10 +446,39 @@ TypeSpec parse_typespec(const TypeSystem* type_system, const goos::Object& src)
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);
const auto* rest = cdr(&src);
for_each_in_list(rest,
[&](const goos::Object& o) { ts.add_arg(parse_typespec(type_system, o)); });
while (rest->is_pair()) {
auto& it = rest->as_pair()->car;
if (it.is_symbol() && it.as_symbol()->name.at(0) == ':') {
auto tag_name = it.as_symbol()->name.substr(1);
rest = &rest->as_pair()->cdr;
if (!rest->is_pair()) {
throw std::runtime_error("TypeSpec missing tag value");
}
auto& tag_val = rest->as_pair()->car;
if (tag_name == "behavior") {
if (!type_system->fully_defined_type_exists(tag_val.as_symbol()->name) &&
!type_system->partially_defined_type_exists(tag_val.as_symbol()->name)) {
throw std::runtime_error(
fmt::format("Behavior tag uses an unknown type {}", tag_val.as_symbol()->name));
}
ts.add_new_tag(tag_name, tag_val.as_symbol()->name);
} else {
throw std::runtime_error(fmt::format("Type tag {} is unknown", tag_name));
}
} else {
// normal argument.
ts.add_arg(parse_typespec(type_system, it));
}
rest = &rest->as_pair()->cdr;
}
return ts;
} else {
+637
View File
@@ -0,0 +1,637 @@
#pragma once
#include <cassert>
#include <cstdint>
#include <iterator>
#include <new>
#include <type_traits>
#include <utility>
namespace cu {
// This might seem stupid, but compiling an empty file with #include <algorithm> takes 0.5 seconds.
// Avoiding the include for <algorithm> is a huge win for compile times.
template <typename T>
constexpr const T& max(const T& a, const T& b) {
return (a < b) ? b : a;
}
template <typename T>
constexpr const T& min(const T& a, const T& b) {
return (b < a) ? b : a;
}
/*!
* A SmallVector is a replacement for std::vector. It is optimized for the case where there
* are only a few elements by having a small inline array of objects. This uses more memory, but
* handles the very common case of having only a few elements much more efficiently.
* It will work correctly even with many elements by falling back to allocating on the heap.
* Even when it falls back to being a normal heap allocation, it is the same speed as a
* std::vector for most operations. Resizing operations may be a tiny bit slower, but this
* doesn't seem to cause issues in practice.
*
* You can specify the number of inline elements. If you don't exceed this number, it won't
* allocate. By default, it picks the largest nonzero number of elements that keeps the size
* under 128-bytes, which is somewhat arbitrary.
*
* It does not implement all features of a std::vector.
* Missing:
* - the "at" operator asserts instead of throwing an exception if the index is invalid.
* - no custom Allocators
* - no reverse iterators
* - max_size is not implemented
* - Can't have a SmallVector<X> as a member of X.
*
*/
template <typename T, std::size_t inline_elt_count = max(std::size_t(1), 128 / sizeof(T))>
class SmallVector {
private:
// how much to increase the storage amount when we run out.
static constexpr double GROW_AMOUNT = 1.5;
// by default, store stuff in here. C++ always uses sizeof(T) as the stride of an array, so this
// is just like an uninitialized array of T's, which starts at the proper alignment.
// We can't use an array of T's because that would default construct them, which we don't want.
typename std::aligned_storage<sizeof(T), alignof(T)>::type m_inline[inline_elt_count];
// get a T* at the beginning of our inline storage.
constexpr const T* inline_begin() const {
return std::launder(reinterpret_cast<const T*>(m_inline));
}
constexpr T* inline_begin() { return std::launder(reinterpret_cast<T*>(m_inline)); }
// regardless of our storage mode, these hold the beginning and end of the storage.
// by default, they are initialized to the inline storage.
T* m_storage_begin;
T* m_storage_end;
// the number of in-use elements
std::size_t m_size;
//// STORAGE HELPERS
/*!
* Allocate new heap storage and set it as the current.
* The objects in storage are uninitialized.
*/
void allocate_and_set_heap_storage(std::size_t elt_count) {
m_storage_begin = std::launder(reinterpret_cast<T*>(new uint8_t[elt_count * sizeof(T)]));
m_storage_end = m_storage_begin + elt_count;
}
/*!
* Free heap storage, without calling destructors of objects.
*/
void free_heap_storage(T* ptr) { delete[] std::launder(reinterpret_cast<uint8_t*>(ptr)); }
/*!
* Set the current storage to the inline memory.
* We rely on m_storage_begin == inline_begin() to determine if we are using the inline memory.
* This ends up being faster than storing a bool for the mode.
*/
void set_inline_storage() {
m_storage_begin = inline_begin();
m_storage_end = inline_begin() + inline_elt_count;
}
bool using_heap_storage() const { return m_storage_begin != inline_begin(); }
/*!
* Allocate (if needed) storage to hold size objects.
* Sets the container size.
* Assumes the current storage is stack (the default)
*/
void initialize_storage_and_size(std::size_t size) {
if (size > inline_elt_count) {
allocate_and_set_heap_storage(size);
} else {
set_inline_storage();
}
m_size = size;
}
void free_current_storage() {
if (using_heap_storage()) {
free_heap_storage(m_storage_begin);
}
}
//// CONSTRUCTION HELPERS
/*!
* Construct objects using the copy constructor, copying val for each.
*/
void construct_objects_by_filling(T* ptr, std::size_t count, const T& val) {
for (std::size_t i = 0; i < count; i++) {
new (ptr + i) T(val);
}
}
/*!
* Construct objects using the default constructor.
*/
void construct_objects_by_default_ctor(T* ptr, std::size_t count) {
for (std::size_t i = 0; i < count; i++) {
new (ptr + i) T();
}
}
/*!
* Call destructor on an array of objects.
*/
void destroy_objects(T* ptr, std::size_t count) {
for (std::size_t i = 0; i < count; i++) {
ptr[i].~T();
}
}
/*!
* Move objects from src to dst, deleting them in src.
* Doing this in a single pass is more cache friendly for huge vectors.
* Compilers seem to be smart enough to turn this into a memcpy for trivial types still.
*/
void move_and_destroy(T* dst, T* src, std::size_t count) {
for (std::size_t i = 0; i < count; i++) {
new (dst + i) T(std::move(src[i]));
src[i].~T();
}
}
/*!
* Move objects from src to dst, starting at the highest memory address. This is useful
* if src and dst overlap.
*/
void move_and_destroy_reverse(T* dst, T* src, std::size_t count) {
for (std::size_t i = count; i-- > 0;) {
new (dst + i) T(std::move(src[i]));
src[i].~T();
}
}
/*!
* Copy objects from src to dst, using the copy constructor.
*/
void copy_construct_objects(T* dst, const T* src, std::size_t count) {
for (std::size_t i = 0; i < count; i++) {
new (dst + i) T(src[i]);
}
}
template <typename InputIt>
void copy_objects_from_range(T* dst, InputIt first, InputIt last) {
auto iter = first;
while (iter != last) {
new (dst) T(*iter);
iter++;
dst++;
}
}
public:
using iterator = T*;
using const_iterator = const T*;
using reverse_iterator = std::reverse_iterator<T*>;
using const_reverse_iterator = std::reverse_iterator<const T*>;
//// CONSTRUCTORS
/*!
* Constructor a SmallVector. By default the size is 0 and no elements are constructed.
*/
SmallVector() { initialize_storage_and_size(0); }
/*!
* Initialize vector with count elements that are default initialized
*/
explicit SmallVector(std::size_t count) {
initialize_storage_and_size(count);
construct_objects_by_default_ctor(m_storage_begin, count);
}
/*!
* Initialize vector with count values that are copied from the given value.
*/
SmallVector(std::size_t count, const T& value) {
// set up storage:
initialize_storage_and_size(count);
construct_objects_by_filling(m_storage_begin, count, value);
}
/*!
* Initialize vector from iterators.
* In the case where your input iterator doesn't support random access, this ends up
* making two passes through your data. The first to check the size, then the second to copy.
*/
template <typename InputIt>
SmallVector(InputIt first, InputIt last) {
// this does a subtraction for random access iterators, or increments one-by-one for others.
initialize_storage_and_size(std::distance(first, last));
copy_objects_from_range(m_storage_begin, first, last);
}
/*!
* Initialize vector using values from another vector.
*/
SmallVector(const SmallVector<T, inline_elt_count>& other) {
initialize_storage_and_size(other.size());
copy_construct_objects(m_storage_begin, other.m_storage_begin, other.size());
}
/*!
* Initialize vector by moving from existing vector. Will leave other vector with size = 0.
*/
SmallVector(SmallVector<T, inline_elt_count>&& other) {
if (other.using_heap_storage()) {
// steal the storage from the existing vector
m_storage_begin = other.m_storage_begin;
m_storage_end = other.m_storage_end;
// don't let the other vector reuse the storage by setting it back to inline.
other.set_inline_storage();
} else {
set_inline_storage();
// move from inline to inline
// we're going to set the other's size to 0, so we have to destroy its objects.
move_and_destroy(m_storage_begin, other.m_storage_begin, other.size());
}
m_size = other.m_size;
other.m_size = 0;
}
/*!
* Initialize vector from an initializer list.
*/
SmallVector(std::initializer_list<T> lst) {
initialize_storage_and_size(lst.size());
copy_objects_from_range(m_storage_begin, lst.begin(), lst.end());
}
//// ASSIGNMENT
/*!
* Copy all data from another vector.
* Uses copy ctor in all cases, never copy assignment.
*/
SmallVector& operator=(const SmallVector<T, inline_elt_count>& other) {
if (&other == this) {
return *this;
}
// destroy all existing objects
destroy_objects(m_storage_begin, size());
// if we're bothering to copy, let's resize the storage to exactly what we need.
if (capacity() != other.size()) {
free_current_storage();
initialize_storage_and_size(other.size());
}
m_size = other.size();
copy_construct_objects(m_storage_begin, other.m_storage_begin, other.size());
return *this;
}
/*!
* Move all data from another vector. Leaves the other vector with nothing.
*/
SmallVector& operator=(SmallVector<T, inline_elt_count>&& other) {
if (&other == this) {
return *this;
}
// destroy existing objects
destroy_objects(m_storage_begin, size());
// kill old storage. We will either steal it from other or use stack.
free_current_storage();
if (other.size() > inline_elt_count) {
// steal the heap array
m_storage_begin = other.m_storage_begin;
m_storage_end = other.m_storage_end;
other.set_inline_storage();
} else {
// move from inline storage
set_inline_storage();
move_and_destroy(m_storage_begin, other.m_storage_begin, other.size());
}
m_size = other.size();
other.m_size = 0;
return *this;
}
/*!
* Set the contents to count copies of a given value.
* Note - this is not super efficient and could avoid reallocating the heap memory in some cases.
*/
void assign(std::size_t count, const T& value) {
*this = SmallVector<T, inline_elt_count>(count, value);
}
/*!
* See note on other assign.
*/
template <typename InputIt>
void assign(InputIt first, InputIt last) {
*this = SmallVector<T, inline_elt_count>(first, last);
}
/*!
* See note on other assign.
*/
void assign(std::initializer_list<T> lst) { *this = SmallVector<T, inline_elt_count>(lst); }
/*!
* Get the element at the index. Assert the index is valid.
*/
T& at(std::size_t idx) {
assert(idx < m_size);
return m_storage_begin[idx];
}
/*!
* Get the element at the index. Assert the index is valid.
*/
const T& at(std::size_t idx) const {
assert(idx < m_size);
return m_storage_begin[idx];
}
/*!
* Get the element at the index. No range checking.
*/
T& operator[](std::size_t idx) { return m_storage_begin[idx]; }
/*!
* Get the element at the index. No range checking.
*/
const T& operator[](std::size_t idx) const { return m_storage_begin[idx]; }
/*!
* Get the first element. Not valid if size == 0
*/
T& front() { return m_storage_begin[0]; }
/*!
* Get the first element. Not valid if size == 0
*/
const T& front() const { return m_storage_begin[0]; }
/*!
* Get the last element. Not valid if size == 0
*/
T& back() { return m_storage_begin[m_size - 1]; }
/*!
* Get the last element. Not valid if size == 0
*/
const T& back() const { return m_storage_begin[m_size - 1]; }
/*!
* Get a pointer to data. This may become invalid after any resize, just like a std::vector.
*/
T* data() { return m_storage_begin; }
/*!
* Get a pointer to data. This may become invalid after any resize, just like a std::vector.
*/
const T* data() const { return m_storage_begin; }
iterator begin() { return m_storage_begin; }
const_iterator begin() const { return m_storage_begin; }
const_iterator cbegin() const { return m_storage_begin; }
iterator end() { return m_storage_begin + m_size; }
const_iterator end() const { return m_storage_begin + m_size; }
const_iterator cend() const { return m_storage_begin + m_size; }
reverse_iterator rbegin() { return m_storage_begin + m_size; }
const_reverse_iterator rbegin() const { return m_storage_begin + m_size; }
const_reverse_iterator crbegin() const { return m_storage_begin + m_size; }
reverse_iterator rend() { return m_storage_begin; }
const_reverse_iterator rend() const { return m_storage_begin; }
const_reverse_iterator crend() const { return m_storage_begin; }
bool empty() const { return m_size == 0; }
std::size_t size() const { return m_size; }
/*!
* Make sure we have enough storage to hold desired_count without allocating.
*/
void reserve(std::size_t desired_count) {
auto old_count = std::size_t(m_storage_end - m_storage_begin);
if (desired_count > old_count) {
// we need more storage.
auto old_storage = m_storage_begin;
auto old_uses_heap = using_heap_storage();
// replace current storage
allocate_and_set_heap_storage(desired_count);
// move objects into the new location and destroy the old ones.
move_and_destroy(m_storage_begin, old_storage, size());
// free old, if needed.
if (old_uses_heap) {
free_heap_storage(old_storage);
}
}
}
/*!
* The number of elements we can hold without allocating more memory.
*/
std::size_t capacity() const { return m_storage_end - m_storage_begin; }
/*!
* Shrink our heap allocation to the smallest possible size that can possibly hold the
* current elements. This can shrink it to zero and move us back to inline storage.
*/
void shrink_to_fit() {
if (using_heap_storage() && capacity() > size()) {
auto old_storage = m_storage_begin;
if (size() > inline_elt_count) {
// go to smaller heap
allocate_and_set_heap_storage(size());
} else {
// go back to inline
set_inline_storage();
}
move_and_destroy(m_storage_begin, old_storage, size());
free_heap_storage(old_storage); // was heap.
}
}
/*!
* Destroy all objects and free all of our memory. Sets our size to 0.
*/
void clear() {
destroy_objects(m_storage_begin, m_size);
if (using_heap_storage()) {
free_heap_storage(m_storage_begin);
}
m_size = 0;
}
/*!
* Insert value _before_ pos.
*/
iterator insert(iterator pos, const T& value) {
auto objects_before_insert = pos - begin();
auto objects_after_insert = end() - pos;
iterator result;
if (size() + 1 > capacity()) {
// not enough room, need to reallocate
auto old_storage = m_storage_begin;
auto old_used_heap = using_heap_storage();
allocate_and_set_heap_storage(GROW_AMOUNT * (size() + 1)); // todo grow policy here.
// copy the objects before the insert
move_and_destroy(m_storage_begin, old_storage, objects_before_insert);
// copy the object to insert
result = new (m_storage_begin + objects_before_insert) T(value);
// copy the objects after the insert
move_and_destroy(m_storage_begin + objects_before_insert + 1,
old_storage + objects_before_insert, objects_after_insert);
if (old_used_heap) {
free_heap_storage(old_storage);
}
} else {
// we have enough room, just move.
move_and_destroy_reverse(pos + 1, pos, objects_after_insert);
// and insert!
result = new (pos) T(value);
}
m_size++;
return result;
}
// insert iterator T&& value
// insert iterator count value
// insert iterator (it, it)
// emplace
// erase
// push_back
void push_back(const T& value) {
if (size() + 1 > capacity()) {
// not enough room!
reserve(GROW_AMOUNT * (size() + 1));
}
// copy
new (m_storage_begin + size()) T(value);
m_size++;
}
void push_back(const T&& value) {
if (size() + 1 > capacity()) {
// not enough room!
reserve(GROW_AMOUNT * (size() + 1));
}
// move:
new (m_storage_begin + size()) T(std::move(value));
m_size++;
}
template <typename... Args>
void emplace_back(Args&&... args) {
if (size() + 1 > capacity()) {
// not enough room!
reserve(GROW_AMOUNT * (size() + 1));
}
new (m_storage_begin + size()) T(std::forward<Args>(args)...);
m_size++;
}
void pop_back() { m_size--; }
void relocate_some_and_destroy_rest(T* dst,
T* src,
std::size_t dst_count,
std::size_t src_count) {
std::size_t i = 0;
while (i < dst_count) {
new (dst + i) T(std::move(src[i]));
src[i].~T();
i++;
}
while (i < src_count) {
src[i].~T();
i++;
}
}
void resize(std::size_t new_size) {
if (size() == new_size) {
return;
}
auto copy_count = min(size(), new_size);
bool old_used_heap = using_heap_storage();
auto old_storage = m_storage_begin;
if (new_size < inline_elt_count) {
// heap to inline
set_inline_storage();
} else {
allocate_and_set_heap_storage(new_size);
}
relocate_some_and_destroy_rest(m_storage_begin, old_storage, copy_count, size());
if (old_used_heap) {
free_heap_storage(old_storage);
}
construct_objects_by_default_ctor(m_storage_begin + copy_count, new_size - copy_count);
m_size = new_size;
}
void resize(std::size_t new_size, const T& value) {
if (size() == new_size) {
return;
}
auto copy_count = min(size(), new_size);
bool old_used_heap = using_heap_storage();
auto old_storage = m_storage_begin;
if (new_size < inline_elt_count) {
// heap to inline
set_inline_storage();
} else {
allocate_and_set_heap_storage(new_size);
}
relocate_some_and_destroy_rest(m_storage_begin, old_storage, copy_count, size());
if (old_used_heap) {
free_heap_storage(old_storage);
}
construct_objects_by_filling(m_storage_begin + copy_count, new_size - copy_count, value);
m_size = new_size;
}
// swap
bool operator==(const SmallVector<T, inline_elt_count>& other) const {
if (other.size() != size()) {
return false;
}
for (std::size_t i = 0; i < size(); i++) {
if (operator[](i) != other[i]) {
return false;
}
}
return true;
}
bool operator!=(const SmallVector<T, inline_elt_count>& other) const {
return !((*this) == other);
}
// std::swap
~SmallVector() {
// destroy allocated objects
destroy_objects(m_storage_begin, m_size);
// free heap storage.
if (using_heap_storage()) {
free_heap_storage(m_storage_begin);
}
}
};
} // namespace cu
+24 -6
View File
@@ -9,7 +9,8 @@
#include "common/util/math_util.h"
namespace decompiler {
void Env::set_remap_for_function(int nargs) {
void Env::set_remap_for_function(const TypeSpec& ts) {
int nargs = ts.arg_count() - 1;
for (int i = 0; i < nargs; i++) {
std::string var_name;
var_name.push_back(i >= 4 ? 't' : 'a');
@@ -18,10 +19,16 @@ void Env::set_remap_for_function(int nargs) {
var_name.push_back('0');
m_var_remap[var_name] = ("arg" + std::to_string(i));
}
m_var_remap["s6-0"] = "pp";
if (ts.try_get_tag("behavior")) {
m_var_remap["s6-0"] = "self";
m_pp_mapped_by_behavior = true;
} else {
m_var_remap["s6-0"] = "pp";
}
}
void Env::set_remap_for_new_method(int nargs) {
void Env::set_remap_for_new_method(const TypeSpec& ts) {
int nargs = ts.arg_count() - 1;
m_var_remap["a0-0"] = "allocation";
m_var_remap["a1-0"] = "type-to-make";
for (int i = 2; i < nargs; i++) {
@@ -32,10 +39,16 @@ void Env::set_remap_for_new_method(int nargs) {
var_name.push_back('0');
m_var_remap[var_name] = ("arg" + std::to_string(i - 2));
}
m_var_remap["s6-0"] = "pp";
if (ts.try_get_tag("behavior")) {
m_var_remap["s6-0"] = "self";
m_pp_mapped_by_behavior = true;
} else {
m_var_remap["s6-0"] = "pp";
}
}
void Env::set_remap_for_method(int nargs) {
void Env::set_remap_for_method(const TypeSpec& ts) {
int nargs = ts.arg_count() - 1;
m_var_remap["a0-0"] = "obj";
for (int i = 1; i < nargs; i++) {
std::string var_name;
@@ -45,7 +58,12 @@ void Env::set_remap_for_method(int nargs) {
var_name.push_back('0');
m_var_remap[var_name] = ("arg" + std::to_string(i - 1));
}
m_var_remap["s6-0"] = "pp";
if (ts.try_get_tag("behavior")) {
m_var_remap["s6-0"] = "self";
m_pp_mapped_by_behavior = true;
} else {
m_var_remap["s6-0"] = "pp";
}
}
void Env::map_args_from_config(const std::vector<std::string>& args_names,
+6 -3
View File
@@ -149,9 +149,9 @@ class Env {
const std::unordered_map<int, StackTypeCast>& stack_casts() const { return m_stack_typecasts; }
void set_remap_for_function(int nargs);
void set_remap_for_method(int nargs);
void set_remap_for_new_method(int nargs);
void set_remap_for_function(const TypeSpec& ts);
void set_remap_for_method(const TypeSpec& ts);
void set_remap_for_new_method(const TypeSpec& ts);
void map_args_from_config(const std::vector<std::string>& args_names,
const std::unordered_map<std::string, std::string>& var_names);
void map_args_from_config(const std::vector<std::string>& args_names,
@@ -208,6 +208,8 @@ class Env {
// hacks:
bool aggressively_reject_cond_to_value_rewrite = false;
bool pp_mapped_by_behavior() const { return m_pp_mapped_by_behavior; }
private:
RegisterAccess m_end_var;
@@ -218,6 +220,7 @@ class Env {
VariableNames m_var_names;
bool m_has_types = false;
bool m_pp_mapped_by_behavior = false;
std::vector<TypeState> m_block_init_types;
std::vector<TypeState> m_op_end_types;
std::vector<TypeState*> m_op_init_types;
+5 -3
View File
@@ -23,12 +23,14 @@ bool convert_to_expressions(
// give us anything more specific.
if (f.guessed_name.kind == FunctionName::FunctionKind::GLOBAL ||
f.guessed_name.kind == FunctionName::FunctionKind::UNIDENTIFIED) {
f.ir2.env.set_remap_for_function(f.type.arg_count() - 1);
f.ir2.env.set_remap_for_function(f.type);
} else if (f.guessed_name.kind == FunctionName::FunctionKind::METHOD) {
auto method_type =
dts.ts.lookup_method(f.guessed_name.type_name, f.guessed_name.method_id).type;
if (f.guessed_name.method_id == GOAL_NEW_METHOD) {
f.ir2.env.set_remap_for_new_method(f.type.arg_count() - 1);
f.ir2.env.set_remap_for_new_method(method_type);
} else {
f.ir2.env.set_remap_for_method(f.type.arg_count() - 1);
f.ir2.env.set_remap_for_method(method_type);
}
}
+31 -10
View File
@@ -26,12 +26,13 @@ goos::Object get_arg_list_for_function(const Function& func, const Env& env) {
namespace {
void append_body_to_function_definition(goos::Object* top_form,
const std::vector<goos::Object>& inline_body,
const FunctionVariableDefinitions& var_dec) {
const FunctionVariableDefinitions& var_dec,
const TypeSpec& ts) {
if (var_dec.local_vars) {
pretty_print::append(*top_form, pretty_print::build_list(*var_dec.local_vars));
}
if (var_dec.had_pp) {
if (var_dec.had_pp && !ts.try_get_tag("behavior")) {
std::vector<goos::Object> body_with_pp;
body_with_pp.push_back(pretty_print::to_symbol("with-pp"));
body_with_pp.insert(body_with_pp.end(), inline_body.begin(), inline_body.end());
@@ -47,9 +48,18 @@ goos::Object final_output_lambda(const Function& func) {
std::vector<goos::Object> inline_body;
func.ir2.top_form->inline_forms(inline_body, func.ir2.env);
auto var_dec = func.ir2.env.local_var_type_list(func.ir2.top_form, func.type.arg_count() - 1);
auto result = pretty_print::build_list("lambda", get_arg_list_for_function(func, func.ir2.env));
append_body_to_function_definition(&result, inline_body, var_dec);
return result;
auto behavior = func.type.try_get_tag("behavior");
if (behavior) {
auto result = pretty_print::build_list(fmt::format("lambda :behavior {}", *behavior),
get_arg_list_for_function(func, func.ir2.env));
append_body_to_function_definition(&result, inline_body, var_dec, func.type);
return result;
} else {
auto result = pretty_print::build_list("lambda", get_arg_list_for_function(func, func.ir2.env));
append_body_to_function_definition(&result, inline_body, var_dec, func.type);
return result;
}
}
std::string final_defun_out(const Function& func,
@@ -75,13 +85,24 @@ std::string final_defun_out(const Function& func,
} else {
assert(special_mode == FunctionDefSpecials::NONE);
}
auto behavior = func.type.try_get_tag("behavior");
if (behavior) {
def_name = "defbehavior";
}
std::vector<goos::Object> top;
top.push_back(pretty_print::to_symbol(def_name));
top.push_back(pretty_print::to_symbol(func.guessed_name.to_string()));
if (behavior) {
top.push_back(pretty_print::to_symbol(func.guessed_name.to_string() + " " + *behavior));
} else {
top.push_back(pretty_print::to_symbol(func.guessed_name.to_string()));
}
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
append_body_to_function_definition(&top_form, inline_body, var_dec);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type);
return pretty_print::to_string(top_form);
}
@@ -96,7 +117,7 @@ std::string final_defun_out(const Function& func,
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
append_body_to_function_definition(&top_form, inline_body, var_dec);
append_body_to_function_definition(&top_form, inline_body, var_dec, method_info.type);
return pretty_print::to_string(top_form);
}
@@ -107,7 +128,7 @@ std::string final_defun_out(const Function& func,
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
append_body_to_function_definition(&top_form, inline_body, var_dec);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type);
return pretty_print::to_string(top_form);
}
@@ -120,7 +141,7 @@ std::string final_defun_out(const Function& func,
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
append_body_to_function_definition(&top_form, inline_body, var_dec);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type);
return pretty_print::to_string(top_form);
}
return "nyi";
+4 -4
View File
@@ -1244,8 +1244,8 @@
(define-extern load-package (function string kheap pair))
(define-extern unload-package (function string pair))
(define-extern malloc (function symbol int pointer)) ;; from kernel-defs.gc
(define-extern remove-exit (function stack-frame))
(define-extern stream<-process-mask (function object int object))
(define-extern remove-exit (function :behavior process stack-frame))
(define-extern stream<-process-mask (function object process-mask process-mask))
(define-extern return-from-thread (function none))
(define-extern return-from-thread-dead (function none))
(define-extern process-count (function process-tree int))
@@ -9666,7 +9666,7 @@
:size-assert #x134
:flag-assert #xe00000134
(:methods
(new (symbol type process) _type_ 0)
(new (symbol type process) _type_ :behavior process-drawable 0)
(dummy-9 () none 9)
(dummy-10 () none 10)
(dummy-11 () none 11)
@@ -17698,7 +17698,7 @@
(define-extern matrix-world->local (function matrix))
(define-extern camera-angle (function float))
(define-extern camera-teleport-to-entity (function entity-actor none))
(define-extern camera-teleport-to-entity (function entity-actor none :behavior process))
;; - Symbols
+4 -1
View File
@@ -172,4 +172,7 @@
- You can now set a field which has a forward declared structure or basic type
- `cdr` now returns an object of type `pair`.
- `lambda`s can now be used inside of a static object definition.
- Methods can now be `:replace`d to override their type from their parent. Use this with extreme care.
- Methods can now be `:replace`d to override their type from their parent. Use this with extreme care.
- TypeSpecs now support "tags". This can specify a `:behavior` tag for a function.
- Lambdas and methods now support `:behavior` to specify the current process type.
- `defbehavior` has been added to define a global behavior.
+1 -1
View File
@@ -21,7 +21,7 @@
:size-assert #x134
:flag-assert #xe00000134
(:methods
(new (symbol type process) _type_ 0)
(new (symbol type process) _type_ :behavior process-drawable 0)
(dummy-9 () none 9)
(dummy-10 () none 10)
(dummy-11 () none 11)
+15 -16
View File
@@ -63,22 +63,21 @@
;; definition for function camera-teleport-to-entity
;; INFO: Return type mismatch int vs none.
;; Used lq/sq
(defun camera-teleport-to-entity ((arg0 entity-actor))
(with-pp
(let ((gp-0 (new 'stack 'transformq)))
(let ((v1-1 (-> gp-0 trans)))
(set! (-> v1-1 quad) (-> (the-as transform (-> arg0 extra)) scale quad))
)
(quaternion-copy! (-> gp-0 quat) (-> arg0 quat))
(vector-identity! (-> gp-0 scale))
(let ((a1-2 (new 'stack-no-clear 'event-message-block)))
(set! (-> a1-2 from) pp)
(set! (-> a1-2 num-params) 1)
(set! (-> a1-2 message) 'teleport-to-transformq)
(set! (-> a1-2 param 0) (the-as uint gp-0))
(send-event-function *camera* a1-2)
)
(defbehavior camera-teleport-to-entity process ((arg0 entity-actor))
(let ((gp-0 (new 'stack 'transformq)))
(set! (-> gp-0 trans quad)
(-> (the-as transform (-> arg0 extra)) scale quad)
)
(quaternion-copy! (-> gp-0 quat) (-> arg0 quat))
(vector-identity! (-> gp-0 scale))
(let ((a1-2 (new 'stack-no-clear 'event-message-block)))
(set! (-> a1-2 from) self)
(set! (-> a1-2 num-params) 1)
(set! (-> a1-2 message) 'teleport-to-transformq)
(set! (-> a1-2 param 0) (the-as uint gp-0))
(send-event-function *camera* a1-2)
)
(none)
)
0
(none)
)
+3
View File
@@ -37,4 +37,7 @@
;; NOTE - I'm guessing there was a define-extern earlier in the build process
;; This is actually set in process-drawable.gc, but it's used by files earlier in the process
;; assuming (state process-drawable)
(define-extern process-drawable-art-error state)
(declare-type process-drawable process)
+12
View File
@@ -558,3 +558,15 @@
`(rlet ((pp :reg r13 :reset-here #t :type process))
,@body)
)
(defmacro defbehavior (name process-type bindings &rest body)
(if (and
(> (length body) 1) ;; more than one thing in function
(string? (first body)) ;; first thing is a string
)
;; then it's a docstring and we ignore it.
`(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@(cdr body)))
;; otherwise don't ignore it.
`(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@body))
)
)
+84 -84
View File
@@ -249,13 +249,10 @@
;; Remove Exit
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun remove-exit ()
"This is likely a defbehavior for process.
Pops a single stack frame, if there is one."
(rlet ((self :reg r13 :type process))
(when (-> self stack-frame-top)
(set! (-> self stack-frame-top) (-> self stack-frame-top next))
)
(defbehavior remove-exit process ()
"Pops a single stack frame, if there is one."
(when (-> self stack-frame-top)
(set! (-> self stack-frame-top) (-> self stack-frame-top next))
)
)
@@ -268,83 +265,86 @@
;; The base class of process is process-tree.
;; Each process-tree element has a process-mask which indicates what type of node it is.
(defun-debug stream<-process-mask (stream (mask int))
"Print out a process mask. This function may have been auto-generated?"
; 24
(if (not (eq? 0 (logand mask (process-mask death))))
(format stream "death "))
; 23
(if (not (eq? 0 (logand mask (process-mask attackable))))
(format stream "attackable "))
; 22
(if (not (eq? 0 (logand mask (process-mask projectile))))
(format stream "projectile "))
; 21
(if (not (eq? 0 (logand mask (process-mask entity))))
(format stream "entity "))
; 20
(if (not (eq? 0 (logand mask (process-mask ambient))))
(format stream "ambient "))
; 19
(if (not (eq? 0 (logand mask (process-mask platform))))
(format stream "platform "))
; 18
(if (not (eq? 0 (logand mask (process-mask camera))))
(format stream "camera "))
; 17
(if (not (eq? 0 (logand mask (process-mask enemy))))
(format stream "enemy "))
; 16
(if (not (eq? 0 (logand mask (process-mask collectable))))
(format stream "collectable "))
; 15
(if (not (eq? 0 (logand mask (process-mask crate))))
(format stream "crate "))
; 14
(if (not (eq? 0 (logand mask (process-mask sidekick))))
(format stream "sidekick "))
; 13
(if (not (eq? 0 (logand mask (process-mask target))))
(format stream "target "))
; 12
(if (not (eq? 0 (logand mask (process-mask movie-subject))))
(format stream "movie-subject "))
; 11
(if (not (eq? 0 (logand mask (process-mask movie))))
(format stream "movie "))
; 10
(if (not (eq? 0 (logand mask (process-mask going))))
(format stream "going "))
; 9
(if (not (eq? 0 (logand mask (process-mask heap-shrunk))))
(format stream "heap-shrunk "))
; 8
(if (not (eq? 0 (logand mask (process-mask process-tree))))
(format stream "process-tree "))
; 7
(if (not (eq? 0 (logand mask (process-mask sleep-code))))
(format stream "sleep-code "))
; 6
(if (not (eq? 0 (logand mask (process-mask sleep))))
(format stream "sleep "))
; 5
(if (not (eq? 0 (logand mask (process-mask actor-pause))))
(format stream "actor-pause "))
; 4
(if (not (eq? 0 (logand mask (process-mask progress))))
(format stream "progress "))
; 3
(if (not (eq? 0 (logand mask (process-mask menu))))
(format stream "menu "))
; 2
(if (not (eq? 0 (logand mask (process-mask pause))))
(format stream "pause "))
; 1
(if (not (eq? 0 (logand mask (process-mask draw))))
(format stream "draw "))
; 0
(if (not (eq? 0 (logand mask (process-mask execute))))
(format stream "execute "))
(defun-debug stream<-process-mask ((arg0 object) (arg1 process-mask))
(let ((s4-0 arg1))
(if (= (logand #x1000000 (the-as int s4-0)) (process-mask death))
(format arg0 "death ")
)
(if (= (logand #x800000 (the-as int s4-0)) (process-mask attackable))
(format arg0 "attackable ")
)
(if (= (logand #x400000 (the-as int s4-0)) (process-mask projectile))
(format arg0 "projectile ")
)
(if (= (logand #x200000 (the-as int s4-0)) (process-mask entity))
(format arg0 "entity ")
)
(if (= (logand #x100000 (the-as int s4-0)) (process-mask ambient))
(format arg0 "ambient ")
)
(if (= (logand #x80000 (the-as int s4-0)) (process-mask platform))
(format arg0 "platform ")
)
(if (= (logand #x40000 (the-as int s4-0)) (process-mask camera))
(format arg0 "camera ")
)
(if (= (logand #x20000 (the-as int s4-0)) (process-mask enemy))
(format arg0 "enemy ")
)
(if (= (logand #x10000 (the-as int s4-0)) (process-mask collectable))
(format arg0 "collectable ")
)
(if (= (logand s4-0 (process-mask crate)) (process-mask crate))
(format arg0 "crate ")
)
(if (= (logand s4-0 (process-mask sidekick)) (process-mask sidekick))
(format arg0 "sidekick ")
)
(if (= (logand s4-0 (process-mask target)) (process-mask target))
(format arg0 "target ")
)
(if
(= (logand s4-0 (process-mask movie-subject)) (process-mask movie-subject))
(format arg0 "movie-subject ")
)
(if (= (logand s4-0 (process-mask movie)) (process-mask movie))
(format arg0 "movie ")
)
(if (= (logand s4-0 (process-mask going)) (process-mask going))
(format arg0 "going ")
)
(if (= (logand s4-0 (process-mask heap-shrunk)) (process-mask heap-shrunk))
(format arg0 "heap-shrunk ")
)
(if (= (logand s4-0 (process-mask process-tree)) (process-mask process-tree))
(format arg0 "process-tree ")
)
(if (= (logand s4-0 (process-mask sleep-code)) (process-mask sleep-code))
(format arg0 "sleep-code ")
)
(if (= (logand s4-0 (process-mask sleep)) (process-mask sleep))
(format arg0 "sleep ")
)
(if (= (logand s4-0 (process-mask actor-pause)) (process-mask actor-pause))
(format arg0 "actor-pause ")
)
(if (= (logand s4-0 (process-mask progress)) (process-mask progress))
(format arg0 "progress ")
)
(if (= (logand s4-0 (process-mask menu)) (process-mask menu))
(format arg0 "menu ")
)
(if (= (logand s4-0 (process-mask pause)) (process-mask pause))
(format arg0 "pause ")
)
(if (= (logand s4-0 (process-mask draw)) (process-mask draw))
(format arg0 "draw ")
)
(if (= (logand s4-0 (process-mask execute)) (process-mask execute))
(format arg0 "execute ")
)
)
arg1
)
;; game state
+2 -14
View File
@@ -1,6 +1,7 @@
#include "goalc/compiler/Compiler.h"
#include "goalc/compiler/IR.h"
#include "common/goos/ParseHelpers.h"
#include "common/type_system/deftype.h"
/*!
* Parse arguments into a goos::Arguments format.
@@ -124,20 +125,7 @@ void Compiler::expect_empty_list(const goos::Object& o) {
}
TypeSpec Compiler::parse_typespec(const goos::Object& src) {
if (src.is_symbol()) {
return m_ts.make_typespec(symbol_string(src));
} else if (src.is_pair()) {
TypeSpec ts = m_ts.make_typespec(symbol_string(pair_car(src)));
const auto& rest = pair_cdr(src);
for_each_in_list(rest, [&](const goos::Object& o) { ts.add_arg(parse_typespec(o)); });
return ts;
} else {
throw_compiler_error(src, "Invalid typespec.");
}
assert(false);
return {};
return ::parse_typespec(&m_ts, src);
}
bool Compiler::is_local_symbol(const goos::Object& obj, Env* env) {
+26 -4
View File
@@ -116,7 +116,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
auto obj_env = get_parent_env_of_type<FileEnv>(env);
auto args = get_va(form, rest);
if (args.unnamed.empty() || !args.unnamed.front().is_list() ||
!args.only_contains_named({"name", "inline-only", "segment"})) {
!args.only_contains_named({"name", "inline-only", "segment", "behavior"})) {
throw_compiler_error(form, "Invalid lambda form");
}
@@ -196,7 +196,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
}
// set up argument register constraints.
std::vector<RegVal*> args_for_coloring;
std::vector<RegVal*> reset_args_for_coloring;
std::vector<TypeSpec> arg_types;
for (auto& parm : lambda.params) {
arg_types.push_back(parm.type);
@@ -213,7 +213,25 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
constr.desired_register = arg_regs.at(i);
new_func_env->params[lambda.params.at(i).name] = ireg;
new_func_env->constrain(constr);
args_for_coloring.push_back(ireg);
reset_args_for_coloring.push_back(ireg);
}
if (args.has_named("behavior")) {
const std::string behavior_type = symbol_string(args.get_named("behavior"));
auto self_var = new_func_env->make_gpr(m_ts.make_typespec(behavior_type));
IRegConstraint constr;
constr.contrain_everywhere = true;
constr.desired_register = emitter::gRegInfo.get_process_reg();
constr.ireg = self_var->ireg();
self_var->set_rlet_constraint(constr.desired_register);
new_func_env->constrain(constr);
if (new_func_env->params.find("self") != new_func_env->params.end()) {
throw_compiler_error(form, "Cannot have an argument named self in a behavior");
}
new_func_env->params["self"] = self_var;
reset_args_for_coloring.push_back(self_var);
lambda_ts.add_new_tag("behavior", behavior_type);
}
place->func = new_func_env.get();
@@ -224,7 +242,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
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());
func_block_env->emit(std::make_unique<IR_ValueReset>(args_for_coloring));
func_block_env->emit(std::make_unique<IR_ValueReset>(reset_args_for_coloring));
// compile the function, iterating through the body.
Val* result = nullptr;
@@ -287,6 +305,10 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
if (new_func_env->settings.save_code) {
obj_env->add_function(std::move(new_func_env));
}
} else {
if (args.has_named("behavior")) {
throw_compiler_error(form, "Inline behaviors are not yet implemented.");
}
}
place->set_type(lambda_ts);
+22 -3
View File
@@ -424,7 +424,7 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
if (lambda.params.size() > 8) {
throw_compiler_error(form, "Methods cannot have more than 8 arguments");
}
std::vector<RegVal*> args_for_coloring;
std::vector<RegVal*> reset_args_for_coloring;
std::vector<TypeSpec> arg_types;
for (auto& parm : lambda.params) {
arg_types.push_back(parm.type);
@@ -441,7 +441,26 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
constr.desired_register = arg_regs.at(i);
new_func_env->params[lambda.params.at(i).name] = ireg;
new_func_env->constrain(constr);
args_for_coloring.push_back(ireg);
reset_args_for_coloring.push_back(ireg);
}
auto method_info = m_ts.lookup_method(symbol_string(type_name), symbol_string(method_name));
auto behavior = method_info.type.try_get_tag("behavior");
if (behavior) {
auto self_var = new_func_env->make_gpr(m_ts.make_typespec(*behavior));
IRegConstraint constr;
constr.contrain_everywhere = true;
constr.desired_register = emitter::gRegInfo.get_process_reg();
constr.ireg = self_var->ireg();
self_var->set_rlet_constraint(constr.desired_register);
new_func_env->constrain(constr);
if (new_func_env->params.find("self") != new_func_env->params.end()) {
throw_compiler_error(form, "Cannot have an argument named self in a behavior");
}
new_func_env->params["self"] = self_var;
reset_args_for_coloring.push_back(self_var);
lambda_ts.add_new_tag("behavior", *behavior);
}
place->func = new_func_env.get();
@@ -451,7 +470,7 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
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());
func_block_env->emit(std::make_unique<IR_ValueReset>(args_for_coloring));
func_block_env->emit(std::make_unique<IR_ValueReset>(reset_args_for_coloring));
// compile the function!
Val* result = nullptr;
+52 -50
View File
@@ -30,59 +30,60 @@
(show-paren-mode 1)
;; the default color is hard to see - make it the most painful color possible.
(custom-set-faces
'(show-paren-match ((t (:foreground "white" :background "red")))))
'(show-paren-match ((t (:foreground "white" :background "red")))))
)
;; very simple syntax highlighting (sometimes fails)
;; these are added to lisp-mode's highlighting.
(font-lock-add-keywords 'lisp-mode
'(("set!" . font-lock-keyword-face)
("local-vars" . font-lock-keyword-face)
("until" . font-lock-keyword-face)
("car" . font-lock-keyword-face)
("cdr" . font-lock-keyword-face)
("->" . font-lock-keyword-face)
("else" . font-lock-constant-face)
("nop!" . font-lock-builtin-face)
;;("and" . font-lock-keyword-face)
;;("or" . font-lock-keyword-face)
'(("set!" . font-lock-keyword-face)
("local-vars" . font-lock-keyword-face)
("until" . font-lock-keyword-face)
("car" . font-lock-keyword-face)
("cdr" . font-lock-keyword-face)
("->" . font-lock-keyword-face)
("else" . font-lock-constant-face)
("nop!" . font-lock-builtin-face)
;;("and" . font-lock-keyword-face)
;;("or" . font-lock-keyword-face)
("format" . font-lock-builtin-face)
("defun-debug" . font-lock-keyword-face)
("defbehavior" . font-lock-keyword-face)
("defenum" . font-lock-keyword-face)
("begin" . font-lock-keyword-face)
("zero?" . font-lock-builtin-face)
("#f" . font-lock-constant-face)
("#t" . font-lock-constant-face)))
("begin" . font-lock-keyword-face)
("zero?" . font-lock-builtin-face)
("#f" . font-lock-constant-face)
("#t" . font-lock-constant-face)))
;; more advanced syntax highlighting. These still fail sometimes, but look nicer.
(defconst scheme-font-lock-keywords-1
(eval-when-compile
(list
;;
;; Declarations. Hannes Haug <hannes.haug@student.uni-tuebingen.de> says
;; this works for SOS, STklos, SCOOPS, Meroon and Tiny CLOS.
(list (concat "(\\(define\\*?\\("
;; Function names.
"\\(\\|-public\\|-method\\|-generic\\(-procedure\\)?\\)\\|"
;; Macro names, as variable names. A bit dubious, this.
"\\(-syntax\\|-macro\\)\\|"
;; Class names.
"-class"
;; Guile modules.
"\\|-module"
"\\)\\)\\>"
;; Any whitespace and declared object.
;; The "(*" is for curried definitions, e.g.,
;; (define ((sum a) b) (+ a b))
"[ \t]*(*"
"\\(\\sw+\\)?")
'(1 font-lock-keyword-face)
'(6 (cond ((match-beginning 3) font-lock-function-name-face)
((match-beginning 5) font-lock-variable-name-face)
(t font-lock-type-face))
nil t))
))
"Subdued expressions to highlight in Scheme modes.")
(eval-when-compile
(list
;;
;; Declarations. Hannes Haug <hannes.haug@student.uni-tuebingen.de> says
;; this works for SOS, STklos, SCOOPS, Meroon and Tiny CLOS.
(list (concat "(\\(define\\*?\\("
;; Function names.
"\\(\\|-public\\|-method\\|-generic\\(-procedure\\)?\\)\\|"
;; Macro names, as variable names. A bit dubious, this.
"\\(-syntax\\|-macro\\)\\|"
;; Class names.
"-class"
;; Guile modules.
"\\|-module"
"\\)\\)\\>"
;; Any whitespace and declared object.
;; The "(*" is for curried definitions, e.g.,
;; (define ((sum a) b) (+ a b))
"[ \t]*(*"
"\\(\\sw+\\)?")
'(1 font-lock-keyword-face)
'(6 (cond ((match-beginning 3) font-lock-function-name-face)
((match-beginning 5) font-lock-variable-name-face)
(t font-lock-type-face))
nil t))
))
"Subdued expressions to highlight in Scheme modes.")
(font-lock-add-keywords 'lisp-mode scheme-font-lock-keywords-1)
;; make gc files use lisp-mode
@@ -103,6 +104,7 @@
(put 'countdown 'common-lisp-indent-function 1)
(put 'defun-debug 'common-lisp-indent-function 2)
(put 'defenum 'common-lisp-indent-function 2)
(put 'defbehavior 'common-lisp-indent-function 3)
;; indent for common lisp, this makes if's look nicer
(custom-set-variables '(lisp-indent-function 'common-lisp-indent-function))
@@ -130,10 +132,10 @@
(let ((buffer (comint-check-proc "OpenGOAL")))
;; create or reuse a buffer and make it visible
(pop-to-buffer
(if (or buffer (not (derived-mode-p 'opengoal-mode))
(comint-check-proc (current-buffer)))
(get-buffer-create (or buffer "*OpenGOAL*"))
(current-buffer)))
(if (or buffer (not (derived-mode-p 'opengoal-mode))
(comint-check-proc (current-buffer)))
(get-buffer-create (or buffer "*OpenGOAL*"))
(current-buffer)))
(unless buffer
;; start the compiler
@@ -159,10 +161,10 @@
(defun opengoal-compile-current-file (mode)
"Check if the current file is saved. If so, compile it!"
(cond
((buffer-modified-p)
(message "Cannot compile current file: save your changes first!"))
(t
(opengoal-compile-file (buffer-file-name) mode))))
((buffer-modified-p)
(message "Cannot compile current file: save your changes first!"))
(t
(opengoal-compile-file (buffer-file-name) mode))))
(defun opengoal-m-file ()
"Make the current file."
@@ -102,3 +102,15 @@
)
)
)
(defmacro defbehavior (name process-type bindings &rest body)
(if (and
(> (length body) 1) ;; more than one thing in function
(string? (first body)) ;; first thing is a string
)
;; then it's a docstring and we ignore it.
`(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@(cdr body)))
;; otherwise don't ignore it.
`(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@body))
)
)
@@ -17,7 +17,7 @@
:size-assert #x134
:flag-assert #xe00000134
(:methods
(new (symbol type process) _type_ 0)
(new (symbol type process) _type_ :behavior process-drawable 0)
(dummy-9 () none 9)
(dummy-10 () none 10)
(dummy-11 () none 11)
@@ -47,23 +47,25 @@
new
align-control
((allocation symbol) (type-to-make type) (arg0 process))
(with-pp
(let
((obj
(object-new allocation type-to-make (the-as int (-> type-to-make size)))
)
(let
((obj
(object-new allocation type-to-make (the-as int (-> type-to-make size)))
)
(when (zero? obj)
(let ((t9-1 (the-as (function object object) enter-state))
(a0-1 "memory")
)
(set! (-> pp next-state) process-drawable-art-error)
(t9-1 a0-1)
)
(return (the-as align-control 0))
)
(set! (-> obj process) arg0)
obj
)
(when (zero? obj)
(let ((t9-1 (the-as (function object object) enter-state))
(a0-1 "memory")
)
(set! (-> self next-state) process-drawable-art-error)
(t9-1 a0-1)
)
(return (the-as align-control 0))
)
(set! (-> obj process) arg0)
obj
)
)
@@ -59,23 +59,22 @@
;; definition for function camera-teleport-to-entity
;; INFO: Return type mismatch int vs none.
;; Used lq/sq
(defun camera-teleport-to-entity ((arg0 entity-actor))
(with-pp (let ((gp-0 (new 'stack 'transformq)))
(set!
(-> gp-0 trans quad)
(-> (the-as transform (-> arg0 extra)) scale quad)
)
(quaternion-copy! (-> gp-0 quat) (-> arg0 quat))
(vector-identity! (-> gp-0 scale))
(let ((a1-2 (new 'stack-no-clear 'event-message-block)))
(set! (-> a1-2 from) pp)
(set! (-> a1-2 num-params) 1)
(set! (-> a1-2 message) 'teleport-to-transformq)
(set! (-> a1-2 param 0) (the-as uint gp-0))
(send-event-function *camera* a1-2)
)
)
0
(none)
(defbehavior camera-teleport-to-entity process ((arg0 entity-actor))
(let ((gp-0 (new 'stack 'transformq)))
(set!
(-> gp-0 trans quad)
(-> (the-as transform (-> arg0 extra)) scale quad)
)
(quaternion-copy! (-> gp-0 quat) (-> arg0 quat))
(vector-identity! (-> gp-0 scale))
(let ((a1-2 (new 'stack-no-clear 'event-message-block)))
(set! (-> a1-2 from) self)
(set! (-> a1-2 num-params) 1)
(set! (-> a1-2 message) 'teleport-to-transformq)
(set! (-> a1-2 param 0) (the-as uint gp-0))
(send-event-function *camera* a1-2)
)
)
0
(none)
)
+33 -34
View File
@@ -197,93 +197,92 @@
)
;; definition for function remove-exit
(defun remove-exit ()
(with-pp (when (-> pp stack-frame-top)
(let ((v0-0 (-> pp stack-frame-top next)))
(set! (-> pp stack-frame-top) v0-0)
v0-0
)
)
(defbehavior remove-exit process ()
(when (-> self stack-frame-top)
(let ((v0-0 (-> self stack-frame-top next)))
(set! (-> self stack-frame-top) v0-0)
v0-0
)
)
)
;; definition (debug) for function stream<-process-mask
;; INFO: Return type mismatch int vs object.
(defun-debug stream<-process-mask ((arg0 object) (arg1 int))
(defun-debug stream<-process-mask ((arg0 object) (arg1 process-mask))
(let ((s4-0 arg1))
(if (= (logand #x1000000 s4-0) #x1000000)
(if (= (logand #x1000000 (the-as int s4-0)) (process-mask death))
(format arg0 "death ")
)
(if (= (logand #x800000 s4-0) #x800000)
(if (= (logand #x800000 (the-as int s4-0)) (process-mask attackable))
(format arg0 "attackable ")
)
(if (= (logand #x400000 s4-0) #x400000)
(if (= (logand #x400000 (the-as int s4-0)) (process-mask projectile))
(format arg0 "projectile ")
)
(if (= (logand #x200000 s4-0) #x200000)
(if (= (logand #x200000 (the-as int s4-0)) (process-mask entity))
(format arg0 "entity ")
)
(if (= (logand #x100000 s4-0) #x100000)
(if (= (logand #x100000 (the-as int s4-0)) (process-mask ambient))
(format arg0 "ambient ")
)
(if (= (logand #x80000 s4-0) #x80000)
(if (= (logand #x80000 (the-as int s4-0)) (process-mask platform))
(format arg0 "platform ")
)
(if (= (logand #x40000 s4-0) #x40000)
(if (= (logand #x40000 (the-as int s4-0)) (process-mask camera))
(format arg0 "camera ")
)
(if (= (logand #x20000 s4-0) #x20000)
(if (= (logand #x20000 (the-as int s4-0)) (process-mask enemy))
(format arg0 "enemy ")
)
(if (= (logand #x10000 s4-0) #x10000)
(if (= (logand #x10000 (the-as int s4-0)) (process-mask collectable))
(format arg0 "collectable ")
)
(if (= (logand s4-0 #x8000) #x8000)
(if (= (logand s4-0 (process-mask crate)) (process-mask crate))
(format arg0 "crate ")
)
(if (= (logand s4-0 #x4000) #x4000)
(if (= (logand s4-0 (process-mask sidekick)) (process-mask sidekick))
(format arg0 "sidekick ")
)
(if (= (logand s4-0 8192) 8192)
(if (= (logand s4-0 (process-mask target)) (process-mask target))
(format arg0 "target ")
)
(if (= (logand s4-0 4096) 4096)
(if
(= (logand s4-0 (process-mask movie-subject)) (process-mask movie-subject))
(format arg0 "movie-subject ")
)
(if (= (logand s4-0 2048) 2048)
(if (= (logand s4-0 (process-mask movie)) (process-mask movie))
(format arg0 "movie ")
)
(if (= (logand s4-0 1024) 1024)
(if (= (logand s4-0 (process-mask going)) (process-mask going))
(format arg0 "going ")
)
(if (= (logand s4-0 512) 512)
(if (= (logand s4-0 (process-mask heap-shrunk)) (process-mask heap-shrunk))
(format arg0 "heap-shrunk ")
)
(if (= (logand s4-0 256) 256)
(if (= (logand s4-0 (process-mask process-tree)) (process-mask process-tree))
(format arg0 "process-tree ")
)
(if (= (logand s4-0 128) 128)
(if (= (logand s4-0 (process-mask sleep-code)) (process-mask sleep-code))
(format arg0 "sleep-code ")
)
(if (= (logand s4-0 64) 64)
(if (= (logand s4-0 (process-mask sleep)) (process-mask sleep))
(format arg0 "sleep ")
)
(if (= (logand s4-0 32) 32)
(if (= (logand s4-0 (process-mask actor-pause)) (process-mask actor-pause))
(format arg0 "actor-pause ")
)
(if (= (logand s4-0 16) 16)
(if (= (logand s4-0 (process-mask progress)) (process-mask progress))
(format arg0 "progress ")
)
(if (= (logand s4-0 8) 8)
(if (= (logand s4-0 (process-mask menu)) (process-mask menu))
(format arg0 "menu ")
)
(if (= (logand s4-0 4) 4)
(if (= (logand s4-0 (process-mask pause)) (process-mask pause))
(format arg0 "pause ")
)
(if (= (logand s4-0 2) 2)
(if (= (logand s4-0 (process-mask draw)) (process-mask draw))
(format arg0 "draw ")
)
(if (= (logand s4-0 1) 1)
(if (= (logand s4-0 (process-mask execute)) (process-mask execute))
(format arg0 "execute ")
)
)
@@ -0,0 +1,28 @@
(deftype behavior-test-type (basic)
((foo int))
(:methods
(test (_type_ basic) int :behavior behavior-test-type)
)
)
(defmethod test behavior-test-type ((obj behavior-test-type) (arg0 basic))
(format #t "method obj: ~D self: ~D~%" (-> obj foo) (-> self foo))
0
)
(defbehavior test-bfunc behavior-test-type ((arg0 basic))
(format #t "function self: ~D~%" (-> self foo))
)
(let ((obj1 (new 'static 'behavior-test-type :foo 123))
(obj2 (new 'static 'behavior-test-type :foo 456)))
(with-pp
(protect (pp)
(set! pp (the process obj1))
(test-bfunc #f)
(test obj2 #f)
)
)
)
0
+6
View File
@@ -780,6 +780,12 @@ TEST_F(WithGameTests, MethodReplace) {
{"relocate! foo: 123 heap: 1 name: 2\n0\n"});
}
TEST_F(WithGameTests, Behaviors) {
runner.run_static_test(env, testCategory, "test-behaviors.gc",
{"function self: 123\n"
"method obj: 456 self: 123\n0\n"});
}
TEST(TypeConsistency, TypeConsistency) {
Compiler compiler;
compiler.enable_throw_on_redefines();
+207 -1
View File
@@ -1,6 +1,7 @@
#include <string>
#include <vector>
#include <limits>
#include <unordered_set>
#include "common/util/FileUtil.h"
#include "common/util/Trie.h"
@@ -12,6 +13,7 @@
#include "third-party/fmt/core.h"
#include "common/util/print_float.h"
#include "common/util/CopyOnWrite.h"
#include "common/util/SmallVector.h"
TEST(CommonUtil, get_file_path) {
std::vector<std::string> test = {"cabbage", "banana", "apple"};
@@ -182,4 +184,208 @@ TEST(CommonUtil, CopyOnWrite) {
EXPECT_EQ(*x, 3);
EXPECT_EQ(*y, 3);
EXPECT_EQ(*z, 15);
}
}
namespace cu {
namespace test {
class ThrowOnDefaultConstruct {
public:
ThrowOnDefaultConstruct() {
throw std::runtime_error("ThrowOnDefaultConstruct was default constructed.");
}
};
class ThrowOnDestruct {
public:
~ThrowOnDestruct() {
// not a good idea to throw.
exit(-1);
}
};
struct RuleOfFiveExample {
// if we fail to call the destructor we'll leak memory, which will get caught with valgrind.
RuleOfFiveExample() { mem = new int; }
RuleOfFiveExample(const RuleOfFiveExample& other) {
if (&other != this) {
mem = new int;
}
}
RuleOfFiveExample(RuleOfFiveExample&& other) noexcept {
if (&other != this) {
mem = other.mem;
other.mem = nullptr;
}
}
RuleOfFiveExample& operator=(const RuleOfFiveExample& other) {
if (&other != this) {
delete mem;
mem = new int;
}
return *this;
}
RuleOfFiveExample& operator=(RuleOfFiveExample&& other) noexcept {
if (&other != this) {
mem = other.mem;
other.mem = nullptr;
}
return *this;
}
~RuleOfFiveExample() { delete mem; }
int value = 12;
int* mem;
};
TEST(SmallVector, NoConstruction) {
// Confirm that an empty vector constructs nothing.
SmallVector<ThrowOnDefaultConstruct, 128> empty;
EXPECT_EQ(empty.size(), 0);
EXPECT_TRUE(empty.empty());
// should also destroy nothing
SmallVector<ThrowOnDestruct> empty2;
}
TEST(SmallVector, ConstructWithSize) {
// Test construction calls default constructors.
SmallVector<RuleOfFiveExample, 1> heap_no_stack(12);
SmallVector<RuleOfFiveExample, 12> full_stack(12);
SmallVector<RuleOfFiveExample, 12> not_full_stack(11);
SmallVector<RuleOfFiveExample, 12> overflow_to_heap(13);
// size
EXPECT_EQ(heap_no_stack.size(), 12);
EXPECT_EQ(full_stack.size(), 12);
EXPECT_EQ(not_full_stack.size(), 11);
EXPECT_EQ(overflow_to_heap.size(), 13);
// capacity
EXPECT_EQ(heap_no_stack.capacity(), 12);
EXPECT_EQ(full_stack.capacity(), 12);
EXPECT_EQ(not_full_stack.capacity(), 12);
EXPECT_EQ(overflow_to_heap.capacity(), 13);
// were they constructed?
int i = 0;
for (auto& obj : heap_no_stack) {
EXPECT_EQ(obj.value, 12);
i++;
}
EXPECT_EQ(i, 12);
for (auto vec : {full_stack, not_full_stack, overflow_to_heap}) {
int j = 0;
for (auto& obj : heap_no_stack) {
EXPECT_EQ(obj.value, 12);
j++;
}
EXPECT_EQ(j, 12);
}
}
// small std::string's aren't heap allocated.
constexpr const char* long_string_1 = "this-is-a-string-thats-long-enough-to-go-on-the-heap!";
constexpr const char* long_string_2 = "another-string-thats-long-enough-to-go-on-the-heap!";
constexpr const char* long_string_3 = "also-long-enough-to-go-on-the-heap!";
TEST(SmallVector, ConstructByCopying) {
// test that we copy the input properly.
SmallVector<std::string> strings(20, long_string_1);
EXPECT_EQ(strings[0], long_string_1);
strings[0] = long_string_2;
EXPECT_EQ(strings[1], long_string_1);
EXPECT_EQ(strings.size(), 20);
}
TEST(SmallVector, ConstructFromIterator) {
std::unordered_set<std::string> stuff;
for (auto x : Range(10, 20)) {
stuff.insert(long_string_1 + std::to_string(x));
}
// iterators into unordered set can't be subtracted, but this should still work.
SmallVector<std::string> strings(stuff.begin(), stuff.end());
EXPECT_EQ(strings.size(), 10);
std::unordered_set<std::string> stuff2(strings.begin(), strings.end());
EXPECT_EQ(stuff, stuff2);
// these can be subtracted.
SmallVector<std::string> strings2(strings.begin(), strings.end());
EXPECT_EQ(strings, strings2);
EXPECT_EQ(strings.at(1), strings2.at(1));
strings.at(1) = long_string_2;
EXPECT_TRUE(strings.at(1) != strings2.at(1));
}
TEST(SmallVector, ConstructFromCopy) {
SmallVector<std::string> one = {long_string_1, long_string_2, long_string_3};
SmallVector<std::string> two(one);
EXPECT_EQ(two.at(2), long_string_3);
two.at(2) = "four";
EXPECT_EQ(one.at(2), long_string_3);
}
TEST(SmallVector, ConstructFromMoveInline) {
// stack move
SmallVector<std::string, 20> one = {long_string_1, long_string_2, long_string_3};
SmallVector<std::string, 20> two(std::move(one));
EXPECT_TRUE(one.empty()); // this is the convention of SmallVector.
EXPECT_EQ(two.at(2), long_string_3);
}
TEST(SmallVector, ConstructFromMoveHeap) {
// heap move
SmallVector<std::string, 1> one = {long_string_1, long_string_2, long_string_3};
SmallVector<std::string, 1> two(std::move(one));
EXPECT_TRUE(one.empty()); // this is the convention of SmallVector.
EXPECT_EQ(two.at(2), long_string_3);
EXPECT_EQ(two.size(), 3);
}
TEST(SmallVector, ConstructFromInitList) {
SmallVector<std::string, 1> one({long_string_1, long_string_2, long_string_3});
EXPECT_EQ(one.at(2), long_string_3);
EXPECT_EQ(one.size(), 3);
}
/*
TEST(SmallVector, SelfCopyAndMoveAssignment) {
SmallVector<std::string, 0> one({long_string_1, long_string_2, long_string_3});
one = one;
one = std::move(one);
EXPECT_EQ(one.at(2), long_string_3);
EXPECT_EQ(one.size(), 3);
}
*/
TEST(SmallVector, CopyAssign) {
// heap -> heap
SmallVector<std::string, 1> heap_one({long_string_1, long_string_2, long_string_3}),
heap_two({"a", "b"});
heap_one = heap_two;
EXPECT_TRUE(heap_one.size() == 2);
EXPECT_TRUE(heap_one[0] == "a");
EXPECT_TRUE(heap_one[1] == "b");
}
TEST(SmallVector, Construction) {
SmallVector<ThrowOnDefaultConstruct, 128> empty;
EXPECT_EQ(empty.size(), 0);
EXPECT_TRUE(empty.empty());
empty.reserve(256);
EXPECT_EQ(empty.size(), 0);
EXPECT_TRUE(empty.empty());
empty.shrink_to_fit();
EXPECT_EQ(empty.capacity(), 128);
SmallVector<int, 2> one(1);
EXPECT_EQ(one.size(), 1);
EXPECT_FALSE(one.empty());
}
} // namespace test
} // namespace cu