Skip to content

Commit

Permalink
add easy interning functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
dragoncoder047 authored Mar 13, 2024
1 parent a8e5909 commit bfcab27
Show file tree
Hide file tree
Showing 9 changed files with 3,267 additions and 876 deletions.
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,10 @@
"ttstflag",
"ttype",
"vpid"
]
],
"files.associations": {
"*.tmpl": "jinja-html",
"*.html": "html",
"complex": "cpp"
}
}
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,34 @@ Any of them can be NULL if nothing needs to be done there.
Tinobsy has a simple mark-and-sweep collector, based partly off of Bob Nystrom's [mark-sweep](https://github.com/munificent/mark-sweep) collector and partly off of David Johnson-Davies' [uLisp](http://www.ulisp.com/show?1BD3) garbage collector.
<!-- #### interning
#### interning
Allocating an object with `alloc()` doesn't actually initialize the fields of the object -- you have to write your own functions to do that yourself. If the object is an "atomic" type such as a string or number that doesn't point to any other Tinobsy objects (i.e. the mark function is/can be NULL), you can intern the object. The helper function `vm->get_existing_object<type>(schema, value, cmp_func)` returns the existing object, that has the same schema as the one passed in, and also the same value (the `as_ptr` member is cast to the templated-in type and compared to the value using the function), or NULL if the object doesn't exist yet. There is a convenience function `op_eq<type>(type, type)` that can be used as the comparison function for types that store their values inline. For example:
```cpp
object_schema Example("example", NULL, free_example, print_example);
object* make_example(vm* vm, struct example value) {
object* x = vm->get_existing_object<struct example>(&Example, value, op_eq<struct example>);
if (!x) {
x = vm->alloc(&Example);
/* magic stuff to initialize the object */
}
return x;
}
```

If an object type has the second (comparison) function defined, then objects of that type are automatically interned. If an object is allocated that would compare equal with an existing object of the same type, the new object is immediately deleted and the older object is returned instead. This is intended for primitive types such as strings and numbers. -->
There is also a preprocessor macro `INTERN(vm, typ, sch, val)` and `INTERN_PTR(vm, typ, sch, val, cmp)` that are just sugar for checking this function and then returning early if the object exists -- the above function could be simplified to:

```cpp
object_schema Example("example", NULL, free_example, print_example);
object* make_example(vm* vm, struct example value) {
INTERN(vm, struct example, &Example, value);
/* if we get here it means a new object must be created */
object* x = vm->alloc(&Example);
/* magic stuff to initialize the object */
return x;
}
```
#### mark-and-sweep
Expand Down
2,002 changes: 1,590 additions & 412 deletions test/out32.txt

Large diffs are not rendered by default.

2,002 changes: 1,590 additions & 412 deletions test/out64.txt

Large diffs are not rendered by default.

28 changes: 14 additions & 14 deletions test/valgrind32.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
==11957== Memcheck, a memory error detector
==11957== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==11957== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==11957== Command: ./ttest32
==11957==
==19747== Memcheck, a memory error detector
==19747== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==19747== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==19747== Command: ./ttest32
==19747==
0 tests failed
==11957==
==11957== HEAP SUMMARY:
==11957== in use at exit: 0 bytes in 0 blocks
==11957== total heap usage: 15 allocs, 15 frees, 28,832 bytes allocated
==11957==
==11957== All heap blocks were freed -- no leaks are possible
==11957==
==11957== For lists of detected and suppressed errors, rerun with: -s
==11957== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==19747==
==19747== HEAP SUMMARY:
==19747== in use at exit: 0 bytes in 0 blocks
==19747== total heap usage: 15 allocs, 15 frees, 28,832 bytes allocated
==19747==
==19747== All heap blocks were freed -- no leaks are possible
==19747==
==19747== For lists of detected and suppressed errors, rerun with: -s
==19747== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
28 changes: 14 additions & 14 deletions test/valgrind64.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
==11930== Memcheck, a memory error detector
==11930== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==11930== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==11930== Command: ./ttest64
==11930==
==19722== Memcheck, a memory error detector
==19722== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==19722== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==19722== Command: ./ttest64
==19722==
0 tests failed
==11930==
==11930== HEAP SUMMARY:
==11930== in use at exit: 0 bytes in 0 blocks
==11930== total heap usage: 15 allocs, 15 frees, 85,696 bytes allocated
==11930==
==11930== All heap blocks were freed -- no leaks are possible
==11930==
==11930== For lists of detected and suppressed errors, rerun with: -s
==11930== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==19722==
==19722== HEAP SUMMARY:
==19722== in use at exit: 0 bytes in 0 blocks
==19722== total heap usage: 15 allocs, 15 frees, 85,696 bytes allocated
==19722==
==19722== All heap blocks were freed -- no leaks are possible
==19722==
==19722== For lists of detected and suppressed errors, rerun with: -s
==19722== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
17 changes: 11 additions & 6 deletions tinobsy.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include "tinobsy.hpp"
#include <functional>

namespace tinobsy {

Expand Down Expand Up @@ -124,21 +123,27 @@ vm::~vm() {
DBG("}");
}

void vm::iter_objects(bool (*func)(object*, void*), void* arg, bool all) {
template <class field_type>
object* vm::get_existing_object(object_type* sch, field_type val, bool (*cmp)(field_type, field_type)) {
chunk* c = this->chunks;
while (c) {
for (size_t i = 0; i < TINOBSY_CHUNK_SIZE; i++) {
if (!all && c->d[i].type == NULL) continue;
bool done = func(&c->d[i], arg);
if (done) return;
object* obj = &c->d[i];
DBG("Interning, searching a %s", obj->type ? obj->type->name : "(uninitialized)");
if (obj->type != sch) continue;
if (cmp(val, (field_type)(obj->as_ptr))) return obj;
}
c = c->next;
}
return NULL;
}

template <class type>
bool op_eq(type a, type b) {
return a == b;
}

object* markcons(vm* vm, object* self) {
// Error on this line WTF??
vm->markobject(car(self));
return cdr(self);
}
Expand Down
14 changes: 13 additions & 1 deletion tinobsy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,18 +137,30 @@ class vm {
virtual void mark_globals() {};

void markobject(object*);
void iter_objects(bool (*)(object*, void*), void*, bool = true);

template <class field_type>
object* get_existing_object(object_type*, field_type, bool (*)(field_type, field_type));

private:
void release(object*);
};

template <class type>
bool op_eq(type, type);

// Default functions
object* markcons(vm*, object*);

#define car(x) ((x)->car)
#define cdr(x) ((x)->cdr)

#define INTERN(vm, typ, sch, val) INTERN_PTR((vm), typ, (sch), (val), op_eq<typ>)

#define INTERN_PTR(vm, typ, sch, val, cmp) do { \
object* maybe = (vm)->get_existing_object<typ>((::tinobsy::object_type*)(sch), (val), (cmp)); \
if (maybe) return maybe; \
} while (0)

}

#include "tinobsy.cpp"
16 changes: 2 additions & 14 deletions tinobsy_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,27 +101,15 @@ void test_reference_cycle() {
DBG("Test unreachable reference cycle gets collected");
size_t oldobj = VM->freespace;
object* a = VM->alloc(&cons_type);
a->car = a;
a->cdr = a;
car(a) = cdr(a) = a;
VM->gc();
TEST(VM->freespace == oldobj, "a was not collected");
}

const object_type Integer("int", NULL, NULL, NULL);

object* makeint(vm* vm, int64_t z) {
typedef struct { object* found; int64_t num; } f;
f foo = {.num = z};
vm->iter_objects([](object* obj, void* arg) -> bool {
f* foo = (f*)arg;
DBG("Interning, searching a %s", obj->type->name);
if (obj->type == &Integer && foo->num == obj->as_big_int) {
foo->found = obj;
return true;
}
return false;
}, &foo, false);
if (foo.found) return foo.found;
INTERN(vm, int64_t, &Integer, z);
object* x = vm->alloc(&Integer);
x->as_big_int = z;
return x;
Expand Down

0 comments on commit bfcab27

Please sign in to comment.