1
0
mirror of https://github.com/janet-lang/janet synced 2025-12-05 16:18:08 +00:00

Refactor code. Separate code into different modules to

separate the minimum runtime from auxiliary functions.
Change makefile to allow building static libraries.
This commit is contained in:
Calvin Rose
2017-03-15 20:56:37 -04:00
parent 91f2766dd0
commit 9856142fef
25 changed files with 90 additions and 812 deletions

264
core/dict.c Normal file
View File

@@ -0,0 +1,264 @@
#include "dict.h"
#include "util.h"
#include "value.h"
#include "vm.h"
/****/
/* Bag implementation */
/****/
/* Find a kv pair in a bag */
static GstValue *gst_object_bag_find(GstDict *obj, GstValue key) {
GstValue *start = obj->data;
GstValue *end = obj->data + obj->count * 2;
while (start < end) {
if (gst_equals(*start, key))
return start;
start += 2;
}
return NULL;
}
/* Check for string equality */
static int str_equal_value(GstValue v, const char *str, uint32_t len, uint32_t hash) {
uint32_t i;
if (v.type != GST_STRING) return 0;
if (gst_string_length(str) != len) return 0;
if (!gst_string_hash(str))
gst_string_hash(str) = gst_string_calchash((uint8_t *)str);
if (gst_string_hash(str) != hash) return 0;
for (i = 0; i < len; ++i)
if (str[1] != v.data.string[i]) return 0;
return 1;
}
/* Find key value pair with c string key */
static GstValue *gst_object_bag_findcstring(GstDict *obj, const char *key) {
uint32_t len, hash;
for (len = 0; key[len]; ++len);
hash = gst_cstring_calchash((uint8_t *)key, len);
GstValue *start = obj->data;
GstValue *end = obj->data + obj->count * 2;
while (start < end) {
if (start->type == GST_STRING) {
uint8_t *str = start->data.string;
if (gst_string_length(str) == len) {
if (!gst_string_hash(str))
gst_string_hash(str) = gst_string_calchash(str);
if (gst_string_hash(str) == hash) {
return start
}
}
}
start += 2;
}
return NULL;
}
/* Remove a key from a bag */
static void gst_object_bag_remove(GstDict *obj, GstValue key) {
GstValue *kv = gst_object_bag_find(obj, key);
if (kv != NULL) {
GstValue *lastKv = obj->data + --obj->count * 2;
kv[0] = lastKv[0];
kv[1] = lastKv[1];
}
}
/* Add a key to a bag */
static void gst_object_bag_put(Gst *vm, GstDict *obj, GstValue key, GstValue value) {
GstValue *kv = gst_object_bag_find(obj, key);
if (kv != NULL) {
/* Replace value */
kv[1] = value;
} else {
/* Check for need to resize */
if (obj->count + 1 > obj->capacity) {
uint32_t newCap = 2 * obj->count + 2;
GstValue *newData = gst_alloc(vm, sizeof(GstValue) * 2 * newCap);
gst_memcpy(newData, obj->data, obj->count * 2 * sizeof(GstValue));
obj->data = newData;
obj->capacity = newCap;
}
/* Push to end */
kv = obj->data + obj->count * 2;
kv[0] = key;
kv[1] = value;
++obj->count;
}
}
/****/
/* Hashtable implementaion */
/****/
/* Add a key value pair to a given array. Returns if key successfully added. */
static void hash_putkv(GstValue *data, uint32_t cap, GstValue key, GstValue value) {
GstValue *end = data + 2 * cap;
GstValue *start = data + (gst_hash(key) % cap) * 2;
GstValue *bucket;
/* Check second half of array */
for (bucket = start; bucket < end; bucket += 2) {
if (bucket[0].type == GST_NIL) {
bucket[0] = key;
bucket[1] = value;
return;
}
}
/* Check first half of array */
for (bucket = data; bucket < start; bucket += 2) {
if (bucket[0].type == GST_NIL) {
bucket[0] = key;
bucket[1] = value;
return;
}
}
/* Should never reach here - data would be full */
}
/* Find a bucket in the hastable */
static GstValue *hash_findkv(GstValue *data, uint32_t cap, GstValue key, GstValue **out) {
GstValue *end = data + 2 * cap;
GstValue *start = data + (gst_hash(key) % cap) * 2;
GstValue *bucket;
/* Check second half of array */
for (bucket = start; bucket < end; bucket += 2)
if (bucket[0].type == GST_NIL)
if (bucket[1].type == GST_BOOLEAN) /* Check if just marked deleted */
continue;
else {
*out = bucket;
return NULL;
}
else if (gst_equals(bucket[0], key))
return bucket;
/* Check first half of array */
for (bucket = data; bucket < start; bucket += 2)
if (bucket[0].type == GST_NIL)
if (bucket[1].type == GST_BOOLEAN) /* Check if just marked deleted */
continue;
else {
*out = bucket;
return NULL;
}
else if (gst_equals(bucket[0], key))
return bucket;
/* Should never reach here - data would be full */
*out = bucket;
return NULL;
}
/* Resize internal hashtable. Also works if currently a bag. */
static void gst_object_rehash(Gst *vm, GstDict *obj, uint32_t capacity) {
GstValue *toData, *fromBucket, *toBucket, *toStart *fromEnd, *toEnd;
toData = gst_alloc(vm, capacity * 2 * sizeof(GstValue));
toEnd = toData + 2 * capacity;
fromBucket = obj->data;
fromEnd = fromBucket + obj->count * 2;
for (; fromBucket < fromEnd; fromBucket += 2) {
if (fromBucket[0].type == GST_NIL) continue;
toStart = toData + (gst_hash(fromBucket[0]) % capacity) * 2;
/* Check second half of array */
for (toBucket = toStart; toBucket < toEnd; toBucket += 2) {
if (toBucket[0].type == GST_NIL) {
toBucket[0] = fromBucket[0];
toBucket[1] = fromBucket[1];
goto finish_put;
}
}
/* Check first half of array */
for (toBucket = toData; toBucket < toStart; toBucket += 2) {
if (toBucket[0].type == GST_NIL) {
toBucket[0] = fromBucket[0];
toBucket[1] = fromBucket[1];
goto finish_put;
}
}
/* Error if we got here - backing array to small. */
;
/* Continue. */
finish_put: continue;
}
obj->capacity = capacity;
obj->data = toData;
}
/****/
/* Interface */
/****/
/* Initialize a dictionary */
GstDict *gst_dict(Gst *vm, uint32_t capacity) {
GstDict *dict = gst_alloc(vm, sizeof(GstDict));
GstValue *data = gst_zalloc(vm, sizeof(GstValue) * 2 * capacity);
dict->data = data;
dict->capacity = capacity;
dict->count = 0;
dict->flags = (capacity < GST_OBJECT_BAG_THRESHOLD) ? GST_OBJECT_FLAG_ISBAG : 0;
return dict;
}
/* Get item from dictionary */
GstValue gst_dict_get(GstDict *dict, GstValue key) {
GstValue *bucket *notused;
if (dict->flags & GST_OBJECT_FLAG_ISBAG) {
bucket = gst_object_bag_find(dict, key);
} else {
bucket = hash_findkv(obj->data, obj->capacity, key, &notused);
}
if (bucket != NULL) {
return bucket[1];
} else {
GstValue ret;
ret.type = GST_NIL;
return ret;
}
}
/* Get item with c string key */
GstValue gst_dict_get_cstring(GstDict *dict, const char *key);
/* Add item to dictionary */
void gst_dict_put(Gst *vm, GstDict *obj, GstValue key, GstValue value) {
if (obj->flags & GST_OBJECT_FLAG_ISBAG) {
if (obj->count > GST_OBJECT_BAG_THRESHOLD) {
/* Change to hashtable */
obj->flags |= GST_OBJECT_FLAG_ISBAG;
gst_object_rehash(vm, obj, 4 * obj->count);
goto put_hash;
}
gst_object_bag_put(vm, obj, key, value);
} else {
GstValue *bucket, *out;
put_hash:
bucket = hash_findkv(obj->data, obj->capacity, key, &out);
if (bucket != NULL) {
bucket[1] = value;
} else {
/* Check for resize */
if (obj->count + 1 > obj->capacity) {
gst_object_rehash(vm, obj, 2 * (obj->count + 1));
bucket = hash_findkv(obj->data, obj->capacity, key, &out);
}
out[0] = key;
out[1] = value;
++obj->count;
}
}
}
/* Remove item from dictionary */
void gst_dict_remove(GstDict *obj, GstValue key) {
if (obj->flags & GST_OBJECT_FLAG_ISBAG) {
gst_object_bag_remove(obj, key);
} else {
GstValue *bucket, *out;
bucket = hash_findkv(obj->data, obj->capacity, key, &out);
if (bucket != NULL) {
--obj->count;
bucket[0].type = GST_NIL;
bucket[1].type = GST_BOOLEAN;
}
}
}

334
core/ds.c Normal file
View File

@@ -0,0 +1,334 @@
#include <gst/util.h>
#include <gst/ds.h>
#include <gst/value.h>
#include <gst/vm.h>
/****/
/* Buffer functions */
/****/
/* Create a new Buffer */
GstBuffer *gst_buffer(Gst *vm, uint32_t capacity) {
GstBuffer *buffer = gst_alloc(vm, sizeof(GstBuffer));
uint8_t *data = gst_alloc(vm, sizeof(uint8_t) * capacity);
buffer->data = data;
buffer->count = 0;
buffer->capacity = capacity;
return buffer;
}
/* Ensure that the buffer has enough internal capacity */
void gst_buffer_ensure(Gst *vm, GstBuffer *buffer, uint32_t capacity) {
uint8_t * newData;
if (capacity <= buffer->capacity) return;
newData = gst_alloc(vm, capacity * sizeof(uint8_t));
gst_memcpy(newData, buffer->data, buffer->count * sizeof(uint8_t));
buffer->data = newData;
buffer->capacity = capacity;
}
/* Get a byte from an index in the buffer */
int gst_buffer_get(GstBuffer *buffer, uint32_t index) {
if (index < buffer->count) {
return buffer->data[index];
} else {
return -1;
}
}
/* Push a byte into the buffer */
void gst_buffer_push(Gst *vm, GstBuffer * buffer, uint8_t c) {
if (buffer->count >= buffer->capacity) {
gst_buffer_ensure(vm, buffer, 2 * buffer->count);
}
buffer->data[buffer->count++] = c;
}
/* Push multiple bytes into the buffer */
void gst_buffer_append(Gst *vm, GstBuffer *buffer, uint8_t *string, uint32_t length) {
uint32_t newSize = buffer->count + length;
if (newSize > buffer->capacity) {
gst_buffer_ensure(vm, buffer, 2 * newSize);
}
gst_memcpy(buffer->data + buffer->count, string, length);
buffer->count = newSize;
}
/* Convert the buffer to a string */
uint8_t *gst_buffer_to_string(Gst *vm, GstBuffer *buffer) {
uint8_t *data = gst_alloc(vm, buffer->count + 2 * sizeof(uint32_t));
data += 2 * sizeof(uint32_t);
gst_string_length(data) = buffer->count;
gst_string_hash(data) = 0;
gst_memcpy(data, buffer->data, buffer->count * sizeof(uint8_t));
return data;
}
/****/
/* Array functions */
/****/
/* Creates a new array */
GstArray *gst_array(Gst * vm, uint32_t capacity) {
GstArray *array = gst_alloc(vm, sizeof(GstArray));
GstValue *data = gst_alloc(vm, capacity * sizeof(GstValue));
array->data = data;
array->count = 0;
array->capacity = capacity;
return array;
}
/* Ensure the array has enough capacity for capacity elements */
void gst_array_ensure(Gst *vm, GstArray *array, uint32_t capacity) {
GstValue *newData;
if (capacity <= array->capacity) return;
newData = gst_alloc(vm, capacity * sizeof(GstValue));
gst_memcpy(newData, array->data, array->capacity * sizeof(GstValue));
array->data = newData;
array->capacity = capacity;
}
/* Get a value of an array with bounds checking. */
GstValue gst_array_get(GstArray *array, uint32_t index) {
if (index < array->count) {
return array->data[index];
} else {
GstValue v;
v.type = GST_NIL;
return v;
}
}
/* Try to set an index in the array. Return 1 if successful, 0
* on failiure */
int gst_array_set(GstArray *array, uint32_t index, GstValue x) {
if (index < array->count) {
array->data[index] = x;
return 1;
} else {
return 0;
}
}
/* Add an item to the end of the array */
void gst_array_push(Gst *vm, GstArray *array, GstValue x) {
if (array->count >= array->capacity) {
gst_array_ensure(vm, array, 2 * array->count);
}
array->data[array->count++] = x;
}
/* Remove the last item from the Array and return it */
GstValue gst_array_pop(GstArray *array) {
if (array->count) {
return array->data[--array->count];
} else {
GstValue v;
v.type = GST_NIL;
return v;
}
}
/* Look at the last item in the Array */
GstValue gst_array_peek(GstArray *array) {
if (array->count) {
return array->data[array->count - 1];
} else {
GstValue v;
v.type = GST_NIL;
return v;
}
}
/****/
/* Tuple functions */
/****/
/* Create a new emoty tuple of the given size. Expected to be
* mutated immediately */
GstValue *gst_tuple(Gst *vm, uint32_t length) {
char *data = gst_alloc(vm, 2 * sizeof(uint32_t) + length * sizeof(GstValue));
GstValue *tuple = (GstValue *)(data + (2 * sizeof(uint32_t)));
gst_tuple_length(tuple) = length;
gst_tuple_hash(tuple) = 0;
return tuple;
}
/****/
/* Userdata functions */
/****/
/* Create new userdata */
void *gst_userdata(Gst *vm, uint32_t size, GstObject *meta) {
char *data = gst_alloc(vm, sizeof(GstUserdataHeader) + size);
GstUserdataHeader *header = (GstUserdataHeader *)data;
void *user = data + sizeof(GstUserdataHeader);
header->size = size;
header->meta = meta;
return user;
}
/****/
/* Dictionary functions */
/****/
/* Create a new dictionary */
GstObject* gst_object(Gst *vm, uint32_t capacity) {
GstObject *o = gst_alloc(vm, sizeof(GstObject));
GstBucket **buckets = gst_zalloc(vm, capacity * sizeof(GstBucket *));
o->buckets = buckets;
o->capacity = capacity;
o->count = 0;
o->meta = NULL;
return o;
}
/* Resize the dictionary table. */
static void gst_object_rehash(Gst *vm, GstObject *o, uint32_t size) {
GstBucket **newBuckets = gst_zalloc(vm, size * sizeof(GstBucket *));
uint32_t i, count;
for (i = 0, count = o->capacity; i < count; ++i) {
GstBucket *bucket = o->buckets[i];
while (bucket) {
uint32_t index;
GstBucket *next = bucket->next;
index = gst_hash(bucket->key) % size;
bucket->next = newBuckets[index];
newBuckets[index] = bucket;
bucket = next;
}
}
o->buckets = newBuckets;
o->capacity = size;
}
/* Find the bucket that contains the given key */
static GstBucket *gst_object_find(GstObject *o, GstValue key) {
uint32_t index = gst_hash(key) % o->capacity;
GstBucket *bucket = o->buckets[index];
while (bucket) {
if (gst_equals(bucket->key, key))
return bucket;
bucket = bucket->next;
}
return (GstBucket *)0;
}
/* Get a value out of the object */
GstValue gst_object_get(GstObject *o, GstValue key) {
GstBucket *bucket = gst_object_find(o, key);
if (bucket) {
return bucket->value;
} else {
GstValue nil;
nil.type = GST_NIL;
return nil;
}
}
/* Get a value of the object with a cstring key */
GstValue gst_object_get_cstring(GstObject *obj, const char *key) {
uint32_t len;
for (len = 0; key[len]; ++len);
uint32_t hash = gst_cstring_calchash((uint8_t *)key, len);
uint32_t index = hash % obj->capacity;
GstBucket *bucket = obj->buckets[index];
while (bucket) {
if (bucket->key.type == GST_STRING) {
uint8_t *s = bucket->key.data.string;
if (gst_string_length(s) == len) {
if (!gst_string_hash(s))
gst_string_hash(s) = gst_string_calchash(s);
if (gst_string_hash(s) == hash) {
uint32_t i;
for (i = 0; i < len; ++i)
if (s[i] != key[i])
goto notequal;
return bucket->value;
}
}
}
notequal:
bucket = bucket->next;
}
/* Return nil */
{
GstValue ret;
ret.type = GST_NIL;
return ret;
}
}
/* Remove an entry from the dictionary */
GstValue gst_object_remove(Gst * vm, GstObject *o, GstValue key) {
GstBucket *bucket, *previous;
uint32_t index = gst_hash(key) % o->capacity;
bucket = o->buckets[index];
previous = (GstBucket *)0;
while (bucket) {
if (gst_equals(bucket->key, key)) {
if (previous) {
previous->next = bucket->next;
} else {
o->buckets[index] = bucket->next;
}
if (o->count < o->capacity / 4) {
gst_object_rehash(vm, o, o->capacity / 2);
}
--o->count;
return bucket->value;
}
previous = bucket;
bucket = bucket->next;
}
/* Return nil if we found nothing */
{
GstValue nil;
nil.type = GST_NIL;
return nil;
}
}
/* Put a value into the dictionary. Returns 1 if successful, 0 if out of memory.
* The VM pointer is needed for memory allocation. */
void gst_object_put(Gst *vm, GstObject *o, GstValue key, GstValue value) {
GstBucket *bucket, *previous;
uint32_t index = gst_hash(key) % o->capacity;
if (key.type == GST_NIL) return;
/* Do a removal if value is nil */
if (value.type == GST_NIL) {
bucket = o->buckets[index];
previous = (GstBucket *)0;
while (bucket) {
if (gst_equals(bucket->key, key)) {
if (previous) {
previous->next = bucket->next;
} else {
o->buckets[index] = bucket->next;
}
if (o->count < o->capacity / 4) {
gst_object_rehash(vm, o, o->capacity / 2);
}
--o->count;
return;
}
previous = bucket;
bucket = bucket->next;
}
} else {
bucket = gst_object_find(o, key);
if (bucket) {
bucket->value = value;
} else {
if (o->count >= 2 * o->capacity) {
gst_object_rehash(vm, o, 2 * o->capacity);
}
bucket = gst_alloc(vm, sizeof(GstBucket));
bucket->next = o->buckets[index];
bucket->value = value;
bucket->key = key;
o->buckets[index] = bucket;
++o->count;
}
}
}

320
core/gc.c Normal file
View File

@@ -0,0 +1,320 @@
#include <gst/datatypes.h>
#include <gst/gc.h>
#include <gst/vm.h>
#include <gst/util.h>
/* The metadata header associated with an allocated block of memory */
#define gc_header(mem) ((GCMemoryHeader *)(mem) - 1)
/* Memory header struct. Node of a linked list of memory blocks. */
typedef struct GCMemoryHeader GCMemoryHeader;
struct GCMemoryHeader {
GCMemoryHeader * next;
uint32_t color : 1;
};
/* Helper to mark function environments */
static void gst_mark_funcenv(Gst *vm, GstFuncEnv *env) {
if (gc_header(env)->color != vm->black) {
GstValue temp;
gc_header(env)->color = vm->black;
if (env->thread) {
temp.type = GST_THREAD;
temp.data.thread = env->thread;
gst_mark(vm, &temp);
}
if (env->values) {
uint32_t count = env->stackOffset;
uint32_t i;
gc_header(env->values)->color = vm->black;
for (i = 0; i < count; ++i)
gst_mark(vm, env->values + i);
}
}
}
/* GC helper to mark a FuncDef */
static void gst_mark_funcdef(Gst *vm, GstFuncDef *def) {
if (gc_header(def)->color != vm->black) {
gc_header(def)->color = vm->black;
gc_header(def->byteCode)->color = vm->black;
uint32_t count, i;
if (def->literals) {
count = def->literalsLen;
gc_header(def->literals)->color = vm->black;
for (i = 0; i < count; ++i) {
/* If the literal is a NIL type, it actually
* contains a FuncDef */
if (def->literals[i].type == GST_NIL) {
gst_mark_funcdef(vm, (GstFuncDef *) def->literals[i].data.pointer);
} else {
gst_mark(vm, def->literals + i);
}
}
}
}
}
/* Helper to mark a stack frame. Returns the next stackframe. */
static GstValue *gst_mark_stackframe(Gst *vm, GstValue *stack) {
uint32_t i;
gst_mark(vm, &gst_frame_callee(stack));
if (gst_frame_env(stack) != NULL)
gst_mark_funcenv(vm, gst_frame_env(stack));
for (i = 0; i < gst_frame_size(stack); ++i)
gst_mark(vm, stack + i);
return stack + gst_frame_size(stack) + GST_FRAME_SIZE;
}
/* Mark allocated memory associated with a value. This is
* the main function for doing the garbage collection mark phase. */
void gst_mark(Gst *vm, GstValue *x) {
switch (x->type) {
case GST_NIL:
case GST_BOOLEAN:
case GST_NUMBER:
case GST_CFUNCTION:
break;
case GST_STRING:
gc_header(gst_string_raw(x->data.string))->color = vm->black;
break;
case GST_BYTEBUFFER:
gc_header(x->data.buffer)->color = vm->black;
gc_header(x->data.buffer->data)->color = vm->black;
break;
case GST_ARRAY:
if (gc_header(x->data.array)->color != vm->black) {
uint32_t i, count;
count = x->data.array->count;
gc_header(x->data.array)->color = vm->black;
gc_header(x->data.array->data)->color = vm->black;
for (i = 0; i < count; ++i)
gst_mark(vm, x->data.array->data + i);
}
break;
case GST_TUPLE:
if (gc_header(gst_tuple_raw(x->data.tuple))->color != vm->black) {
uint32_t i, count;
count = gst_tuple_length(x->data.tuple);
gc_header(gst_tuple_raw(x->data.tuple))->color = vm->black;
for (i = 0; i < count; ++i)
gst_mark(vm, x->data.tuple + i);
}
break;
case GST_THREAD:
if (gc_header(x->data.thread)->color != vm->black) {
GstThread *thread = x->data.thread;
GstValue *frame = thread->data + GST_FRAME_SIZE;
GstValue *end = thread->data + thread->count;
gc_header(thread)->color = vm->black;
gc_header(thread->data)->color = vm->black;
while (frame <= end)
frame = gst_mark_stackframe(vm, frame);
}
break;
case GST_FUNCTION:
if (gc_header(x->data.function)->color != vm->black) {
GstFunction *f = x->data.function;
gc_header(f)->color = vm->black;
gst_mark_funcdef(vm, f->def);
if (f->env)
gst_mark_funcenv(vm, f->env);
if (f->parent) {
GstValue temp;
temp.type = GST_FUNCTION;
temp.data.function = f->parent;
gst_mark(vm, &temp);
}
}
break;
case GST_OBJECT:
if (gc_header(x->data.object)->color != vm->black) {
uint32_t i;
GstBucket *bucket;
gc_header(x->data.object)->color = vm->black;
gc_header(x->data.object->buckets)->color = vm->black;
for (i = 0; i < x->data.object->capacity; ++i) {
bucket = x->data.object->buckets[i];
while (bucket) {
gc_header(bucket)->color = vm->black;
gst_mark(vm, &bucket->key);
gst_mark(vm, &bucket->value);
bucket = bucket->next;
}
}
if (x->data.object->meta != NULL) {
GstValue temp;
temp.type = GST_OBJECT;
temp.data.object = x->data.object->meta;
gst_mark(vm, &temp);
}
}
break;
case GST_USERDATA:
if (gc_header(x->data.string - sizeof(GstUserdataHeader))->color != vm->black) {
GstUserdataHeader *userHeader = (GstUserdataHeader *)x->data.string - 1;
gc_header(userHeader)->color = vm->black;
GstValue temp;
temp.type = GST_OBJECT;
temp.data.object = userHeader->meta;
gst_mark(vm, &temp);
}
}
}
/* Iterate over all allocated memory, and free memory that is not
* marked as reachable. Flip the gc color flag for next sweep. */
void gst_sweep(Gst *vm) {
GCMemoryHeader *previous = NULL;
GCMemoryHeader *current = vm->blocks;
GCMemoryHeader *next;
while (current) {
next = current->next;
if (current->color != vm->black) {
if (previous) {
previous->next = next;
} else {
vm->blocks = next;
}
gst_raw_free(current);
} else {
previous = current;
}
current = next;
}
/* Rotate flag */
vm->black = !vm->black;
}
/* Prepare a memory block */
static void *gst_alloc_prepare(Gst *vm, char *rawBlock, uint32_t size) {
GCMemoryHeader *mdata;
if (rawBlock == NULL) {
GST_OUT_OF_MEMORY;
}
vm->nextCollection += size;
mdata = (GCMemoryHeader *)rawBlock;
mdata->next = vm->blocks;
vm->blocks = mdata;
mdata->color = !vm->black;
return rawBlock + sizeof(GCMemoryHeader);
}
/* Allocate some memory that is tracked for garbage collection */
void *gst_alloc(Gst *vm, uint32_t size) {
uint32_t totalSize = size + sizeof(GCMemoryHeader);
return gst_alloc_prepare(vm, gst_raw_alloc(totalSize), totalSize);
}
/* Allocate some zeroed memory that is tracked for garbage collection */
void *gst_zalloc(Gst *vm, uint32_t size) {
uint32_t totalSize = size + sizeof(GCMemoryHeader);
return gst_alloc_prepare(vm, gst_raw_calloc(1, totalSize), totalSize);
}
/* Run garbage collection */
void gst_collect(Gst *vm) {
GstValue temp;
/* Thread can be null */
if (vm->thread) {
temp.type = GST_THREAD;
temp.data.thread = vm->thread;
gst_mark(vm, &temp);
}
gst_mark(vm, &vm->rootenv);
gst_mark(vm, &vm->ret);
gst_sweep(vm);
vm->nextCollection = 0;
}
/* Run garbage collection if needed */
void gst_maybe_collect(Gst *vm) {
if (vm->nextCollection >= vm->memoryInterval)
gst_collect(vm);
}
/* Free all allocated memory */
void gst_clear_memory(Gst *vm) {
GCMemoryHeader *current = vm->blocks;
while (current) {
GCMemoryHeader *next = current->next;
gst_raw_free(current);
current = next;
}
vm->blocks = NULL;
}
/* Header for managed memory blocks */
struct MMHeader {
struct MMHeader *next;
struct MMHeader *previous;
};
/* Initialize managed memory */
void gst_mm_init(GstManagedMemory *mm) {
*mm = NULL;
}
/* Allocate some managed memory */
void *gst_mm_alloc(GstManagedMemory *mm, uint32_t size) {
struct MMHeader *mem = gst_raw_alloc(size + sizeof(struct MMHeader));
if (mem == NULL)
return NULL;
mem->next = *mm;
mem->previous = NULL;
*mm = mem;
return mem + 1;
}
/* Intialize zeroed managed memory */
void *gst_mm_zalloc(GstManagedMemory *mm, uint32_t size) {
struct MMHeader *mem = gst_raw_calloc(1, size + sizeof(struct MMHeader));
if (mem == NULL)
return NULL;
mem->next = *mm;
mem->previous = NULL;
*mm = mem;
return mem + 1;
}
/* Free a memory block used in managed memory */
void gst_mm_free(GstManagedMemory *mm, void *block) {
struct MMHeader *mem = (struct MMHeader *)(((char *)block) - sizeof(struct MMHeader));
if (mem->previous != NULL) {
mem->previous->next = mem->next;
} else {
*mm = mem->next;
}
gst_raw_free(mem);
}
/* Free all memory in managed memory */
void gst_mm_clear(GstManagedMemory *mm) {
struct MMHeader *block = (struct MMHeader *)(*mm);
struct MMHeader *next;
while (block != NULL) {
next = block->next;
free(block);
block = next;
};
*mm = NULL;
}
/* Analog to realloc */
void *gst_mm_realloc(GstManagedMemory *mm, void *block, uint32_t nsize) {
struct MMHeader *mem = gst_raw_realloc(block, nsize + sizeof(struct MMHeader));
if (mem == NULL)
return NULL;
mem->next = *mm;
mem->previous = NULL;
*mm = mem;
return mem + 1;
}

239
core/thread.c Normal file
View File

@@ -0,0 +1,239 @@
#include <gst/datatypes.h>
#include <gst/thread.h>
#include <gst/vm.h>
#include <gst/util.h>
#include <gst/ds.h>
/* Create a new thread */
GstThread *gst_thread(Gst *vm, GstValue callee, uint32_t capacity) {
GstThread *thread = gst_alloc(vm, sizeof(GstThread));
GstValue *data, *stack;
if (capacity < GST_FRAME_SIZE) capacity = GST_FRAME_SIZE;
data = gst_alloc(vm, sizeof(GstValue) * capacity);
thread->capacity = capacity;
thread->count = GST_FRAME_SIZE;
thread->data = data;
thread->status = GST_THREAD_PENDING;
stack = data + GST_FRAME_SIZE;
gst_frame_size(stack) = 0;
gst_frame_prevsize(stack) = 0;
gst_frame_ret(stack) = 0;
gst_frame_errloc(stack) = 0;
gst_frame_pc(stack) = NULL;
gst_frame_env(stack) = NULL;
gst_frame_errjmp(stack) = NULL;
gst_thread_expand_callable(vm, thread, callee);
gst_thread_endframe(vm, thread);
return thread;
}
/* Ensure that the thread has enough EXTRA capacity */
void gst_thread_ensure_extra(Gst *vm, GstThread *thread, uint32_t extra) {
GstValue *newData, *stack;
uint32_t usedCapacity, neededCapacity, newCapacity;
stack = thread->data + thread->count;
usedCapacity = thread->count + gst_frame_size(stack) + GST_FRAME_SIZE;
neededCapacity = usedCapacity + extra;
if (thread->capacity >= neededCapacity) return;
newCapacity = 2 * neededCapacity;
newData = gst_alloc(vm, sizeof(GstValue) * newCapacity);
gst_memcpy(newData, thread->data, sizeof(GstValue) * usedCapacity);
thread->data = newData;
thread->capacity = newCapacity;
}
/* Push a value on the current stack frame*/
void gst_thread_push(Gst *vm, GstThread *thread, GstValue x) {
GstValue *stack;
gst_thread_ensure_extra(vm, thread, 1);
stack = thread->data + thread->count;
stack[gst_frame_size(stack)++] = x;
}
/* Push n nils onto the stack */
void gst_thread_pushnil(Gst *vm, GstThread *thread, uint32_t n) {
GstValue *stack, *current, *end;
gst_thread_ensure_extra(vm, thread, n);
stack = thread->data + thread->count;
current = stack + gst_frame_size(stack);
end = current + n;
for (; current < end; ++current) {
current->type = GST_NIL;
}
gst_frame_size(stack) += n;
}
/* Package up extra args after and including n into tuple at n*/
void gst_thread_tuplepack(Gst *vm, GstThread *thread, uint32_t n) {
GstValue *stack = thread->data + thread->count;
uint32_t size = gst_frame_size(stack);
if (n >= size) {
gst_thread_pushnil(vm, thread, n - size + 1);
stack = thread->data + thread->count;
stack[n].type = GST_TUPLE;
stack[n].data.tuple = gst_tuple(vm, 0);
} else {
uint32_t i;
GstValue *tuple = gst_tuple(vm, size - n);
for (i = n; i < size; ++i)
tuple[i - n] = stack[i];
stack[n].type = GST_TUPLE;
stack[n].data.tuple = tuple;
gst_frame_size(stack) = n + 1;
}
}
/* Expand a callee on the stack frame to its delegate function. This means that
* objects and userdata that have a "call" attribut in their class will be
* replaced with their delegate function. Call this before pushing any
* arguments to the stack. Returns the new stack. */
GstValue *gst_thread_expand_callable(Gst *vm, GstThread *thread, GstValue callee) {
uint32_t i;
GstValue *stack;
GstObject *meta;
for (i = 0; i < 200; ++i) {
switch(callee.type) {
default:
return NULL;
case GST_FUNCTION:
stack = thread->data + thread->count;
gst_frame_callee(stack) = callee;
gst_frame_pc(stack) = callee.data.function->def->byteCode;
return stack;
case GST_CFUNCTION:
stack = thread->data + thread->count;
gst_frame_callee(stack) = callee;
gst_frame_pc(stack) = NULL;
return stack;
case GST_OBJECT:
meta = callee.data.object->meta;
if (meta == NULL) return NULL;
gst_thread_push(vm, thread, callee);
callee = gst_object_get_cstring(meta, "call");
continue;
case GST_USERDATA:
meta = ((GstUserdataHeader *)callee.data.pointer - 1)->meta;
gst_thread_push(vm, thread, callee);
callee = gst_object_get_cstring(meta, "call");
continue;
}
}
/* Callables nested to deeply */
return NULL;
}
/* Push a stack frame to a thread, with space for arity arguments. Returns the new
* stack. */
GstValue *gst_thread_beginframe(Gst *vm, GstThread *thread, GstValue callee, uint32_t arity) {
uint32_t frameOffset;
GstValue *oldStack, *newStack;
/* Push the frame */
gst_thread_ensure_extra(vm, thread, GST_FRAME_SIZE + arity + 4);
oldStack = thread->data + thread->count;
frameOffset = gst_frame_size(oldStack) + GST_FRAME_SIZE;
newStack = oldStack + frameOffset;
gst_frame_prevsize(newStack) = gst_frame_size(oldStack);
gst_frame_env(newStack) = NULL;
gst_frame_errjmp(newStack) = NULL;
gst_frame_size(newStack) = 0;
thread->count += frameOffset;
/* Get real callable */
if (gst_thread_expand_callable(vm, thread, callee) == NULL)
return NULL;
/* Ensure the extra space and initialize to nil */
gst_thread_pushnil(vm, thread, arity);
/* Return ok */
return thread->data + thread->count;
}
/* After pushing arguments to a stack frame created with gst_thread_beginframe, call this
* to finalize the frame before starting a function call. */
void gst_thread_endframe(Gst *vm, GstThread *thread) {
GstValue *stack = thread->data + thread->count;
GstValue callee = gst_frame_callee(stack);
if (callee.type == GST_FUNCTION) {
GstFunction *fn = callee.data.function;
gst_frame_pc(stack) = fn->def->byteCode;
if (fn->def->flags & GST_FUNCDEF_FLAG_VARARG) {
uint32_t arity = fn->def->arity;
gst_thread_tuplepack(vm, thread, arity);
} else {
uint32_t locals = fn->def->locals;
if (gst_frame_size(stack) < locals) {
gst_thread_pushnil(vm, thread, locals - gst_frame_size(stack));
}
}
}
}
/* Pop a stack frame from the thread. Returns the new stack frame, or
* NULL if there are no more frames */
GstValue *gst_thread_popframe(Gst *vm, GstThread *thread) {
GstValue *stack = thread->data + thread->count;
uint32_t prevsize = gst_frame_prevsize(stack);
GstValue *nextstack = stack - GST_FRAME_SIZE - prevsize;
GstFuncEnv *env = gst_frame_env(stack);
/* Check for closures */
if (env != NULL) {
uint32_t size = gst_frame_size(stack);
env->thread = NULL;
env->stackOffset = size;
env->values = gst_alloc(vm, sizeof(GstValue) * size);
gst_memcpy(env->values, stack, sizeof(GstValue) * size);
}
/* Shrink stack */
thread->count -= GST_FRAME_SIZE + prevsize;
/* Check if the stack is empty, and if so, return null */
if (thread->count)
return nextstack;
else
return NULL;
}
/* Move the current stack frame over its parent stack frame, allowing
* for primitive tail calls. */
GstValue *gst_thread_tail(Gst *vm, GstThread *thread) {
GstFuncEnv *env;
GstValue *stack = thread->data + thread->count;
GstValue *nextStack = gst_thread_popframe(vm, thread);
uint32_t i;
if (nextStack == NULL) return NULL;
env = gst_frame_env(nextStack);
/* Check for old closures */
if (env != NULL) {
uint32_t size = gst_frame_size(stack);
env->thread = NULL;
env->stackOffset = size;
env->values = gst_alloc(vm, sizeof(GstValue) * size);
gst_memcpy(env->values, stack, sizeof(GstValue) * size);
}
/* Modify new closure */
env = gst_frame_env(stack);
if (env != NULL) {
env->stackOffset = thread->count;
}
/* Copy over (some of) stack frame. Leave ret and prevsize untouched. */
gst_frame_env(nextStack) = env;
gst_frame_size(nextStack) = gst_frame_size(stack);
gst_frame_pc(nextStack) = gst_frame_pc(stack);
gst_frame_errjmp(nextStack) = gst_frame_errjmp(stack);
gst_frame_errloc(nextStack) = gst_frame_errloc(stack);
gst_frame_callee(nextStack) = gst_frame_callee(stack);
/* Copy stack arguments */
for (i = 0; i < gst_frame_size(nextStack); ++i)
nextStack[i] = stack[i];
return nextStack;
}

462
core/value.c Normal file
View File

@@ -0,0 +1,462 @@
#include <gst/util.h>
#include <gst/value.h>
#include <gst/ds.h>
#include <gst/vm.h>
#include <stdio.h>
/* Boolean truth definition */
int gst_truthy(GstValue v) {
return v.type != GST_NIL && !(v.type == GST_BOOLEAN && !v.data.boolean);
}
static uint8_t *load_cstring(Gst *vm, const char *string, uint32_t len) {
uint8_t *data = gst_alloc(vm, len + 1 + 2 * sizeof(uint32_t));
data += 2 * sizeof(uint32_t);
gst_string_hash(data) = 0;
gst_string_length(data) = len;
gst_memcpy(data, string, len);
data[len] = 0;
return data;
}
GstValue gst_load_cstring(Gst *vm, const char *string) {
GstValue ret;
ret.type = GST_STRING;
ret.data.string = load_cstring(vm, string, strlen(string));
return ret;
}
static uint8_t * number_to_string(Gst *vm, GstNumber x) {
static const uint32_t SIZE = 20;
uint8_t *data = gst_alloc(vm, SIZE + 1 + 2 * sizeof(uint32_t));
data += 2 * sizeof(uint32_t);
/* TODO - not depend on stdio */
snprintf((char *) data, SIZE, "%.21g", x);
gst_string_hash(data) = 0;
gst_string_length(data) = strlen((char *) data);
return data;
}
static const char *HEX_CHARACTERS = "0123456789abcdef";
#define HEX(i) (((uint8_t *) HEX_CHARACTERS)[(i)])
/* Returns a string description for a pointer */
static uint8_t *string_description(Gst *vm, const char *title, uint32_t titlelen, void *pointer) {
uint32_t len = 5 + titlelen + sizeof(void *) * 2;
uint32_t i;
uint8_t *data = gst_alloc(vm, len + 1 + 2 * sizeof(uint32_t));
uint8_t *c;
union {
uint8_t bytes[sizeof(void *)];
void *p;
} buf;
buf.p = pointer;
data += 2 * sizeof(uint32_t);
c = data;
*c++ = '<';
for (i = 0; i < titlelen; ++i) {
*c++ = ((uint8_t *)title) [i];
}
*c++ = ' ';
*c++ = '0';
*c++ = 'x';
for (i = sizeof(void *); i > 0; --i) {
uint8_t byte = buf.bytes[i - 1];
if (!byte) continue;
*c++ = HEX(byte >> 4);
*c++ = HEX(byte & 0xF);
}
*c++ = '>';
gst_string_hash(data) = 0;
gst_string_length(data) = c - data;
*c = 0;
return data;
}
/* Returns a string pointer or NULL if could not allocate memory. */
uint8_t *gst_to_string(Gst *vm, GstValue x) {
switch (x.type) {
case GST_NIL:
return load_cstring(vm, "nil", 3);
case GST_BOOLEAN:
if (x.data.boolean) {
return load_cstring(vm, "true", 4);
} else {
return load_cstring(vm, "false", 5);
}
case GST_NUMBER:
return number_to_string(vm, x.data.number);
case GST_ARRAY:
return string_description(vm, "array", 5, x.data.pointer);
case GST_TUPLE:
return string_description(vm, "tuple", 5, x.data.pointer);
case GST_OBJECT:
return string_description(vm, "object", 6, x.data.pointer);
case GST_STRING:
return x.data.string;
case GST_BYTEBUFFER:
return string_description(vm, "buffer", 6, x.data.pointer);
case GST_CFUNCTION:
return string_description(vm, "cfunction", 9, x.data.pointer);
case GST_FUNCTION:
return string_description(vm, "function", 8, x.data.pointer);
case GST_THREAD:
return string_description(vm, "thread", 6, x.data.pointer);
case GST_USERDATA:
return string_description(vm, "userdata", 8, x.data.pointer);
}
return NULL;
}
/* GST string version */
uint32_t gst_string_calchash(const uint8_t *str) {
return gst_cstring_calchash(str, gst_string_length(str));
}
/* Simple hash function (djb2) */
uint32_t gst_cstring_calchash(const uint8_t *str, uint32_t len) {
const uint8_t *end = str + len;
uint32_t hash = 5381;
while (str < end)
hash = (hash << 5) + hash + *str++;
return hash;
}
/* Simple hash function to get tuple hash */
static uint32_t tuple_calchash(GstValue *tuple) {
uint32_t i;
uint32_t count = gst_tuple_length(tuple);
uint32_t hash = 5387;
for (i = 0; i < count; ++i)
hash = (hash << 5) + hash + gst_hash(tuple[i]);
return hash;
}
/* Check if two values are equal. This is strict equality with no conversion. */
int gst_equals(GstValue x, GstValue y) {
int result = 0;
if (x.type != y.type) {
result = 0;
} else {
switch (x.type) {
case GST_NIL:
result = 1;
break;
case GST_BOOLEAN:
result = (x.data.boolean == y.data.boolean);
break;
case GST_NUMBER:
result = (x.data.number == y.data.number);
break;
/* Assume that when strings are created, equal strings
* are set to the same string */
case GST_STRING:
if (x.data.string == y.data.string) {
result = 1;
break;
}
if (gst_hash(x) != gst_hash(y) ||
gst_string_length(x.data.string) != gst_string_length(y.data.string)) {
result = 0;
break;
}
if (!strncmp((char *) x.data.string, (char *) y.data.string, gst_string_length(x.data.string))) {
result = 1;
break;
}
result = 0;
break;
case GST_TUPLE:
if (x.data.tuple == y.data.tuple) {
result = 1;
break;
}
if (gst_hash(x) != gst_hash(y) ||
gst_tuple_length(x.data.string) != gst_tuple_length(y.data.string)) {
result = 0;
break;
}
result = 1;
{
uint32_t i;
for (i = 0; i < gst_tuple_length(x.data.tuple); ++i) {
if (!gst_equals(x.data.tuple[i], y.data.tuple[i])) {
result = 0;
break;
}
}
}
break;
default:
/* compare pointers */
result = (x.data.array == y.data.array);
break;
}
}
return result;
}
/* Computes a hash value for a function */
uint32_t gst_hash(GstValue x) {
uint32_t hash = 0;
switch (x.type) {
case GST_NIL:
hash = 0;
break;
case GST_BOOLEAN:
hash = x.data.boolean;
break;
case GST_NUMBER:
{
union {
uint32_t hash;
GstNumber number;
} u;
u.number = x.data.number;
hash = u.hash;
}
break;
/* String hashes */
case GST_STRING:
/* Assume 0 is not hashed. */
if (gst_string_hash(x.data.string))
hash = gst_string_hash(x.data.string);
else
hash = gst_string_hash(x.data.string) = gst_string_calchash(x.data.string);
break;
case GST_TUPLE:
if (gst_tuple_hash(x.data.tuple))
hash = gst_tuple_hash(x.data.tuple);
else
hash = gst_tuple_hash(x.data.tuple) = tuple_calchash(x.data.tuple);
break;
default:
/* Cast the pointer */
{
union {
void * pointer;
uint32_t hash;
} u;
u.pointer = x.data.pointer;
hash = u.hash;
}
break;
}
return hash;
}
/* Compares x to y. If they are equal retuns 0. If x is less, returns -1.
* If y is less, returns 1. All types are comparable
* and should have strict ordering. */
int gst_compare(GstValue x, GstValue y) {
if (x.type == y.type) {
switch (x.type) {
case GST_NIL:
return 0;
case GST_BOOLEAN:
if (x.data.boolean == y.data.boolean) {
return 0;
} else {
return x.data.boolean ? 1 : -1;
}
case GST_NUMBER:
/* TODO: define behavior for NaN and infinties. */
if (x.data.number == y.data.number) {
return 0;
} else {
return x.data.number > y.data.number ? 1 : -1;
}
case GST_STRING:
if (x.data.string == y.data.string) {
return 0;
} else {
uint32_t xlen = gst_string_length(x.data.string);
uint32_t ylen = gst_string_length(y.data.string);
uint32_t len = xlen > ylen ? ylen : xlen;
uint32_t i;
for (i = 0; i < len; ++i) {
if (x.data.string[i] == y.data.string[i]) {
continue;
} else if (x.data.string[i] < y.data.string[i]) {
return 1; /* x is less then y */
} else {
return -1; /* y is less than x */
}
}
if (xlen == ylen) {
return 0;
} else {
return xlen < ylen ? -1 : 1;
}
}
/* Lower indices are most significant */
case GST_TUPLE:
{
uint32_t i;
uint32_t xlen = gst_tuple_length(x.data.tuple);
uint32_t ylen = gst_tuple_length(y.data.tuple);
uint32_t count = xlen < ylen ? xlen : ylen;
for (i = 0; i < count; ++i) {
int comp = gst_compare(x.data.tuple[i], y.data.tuple[i]);
if (comp != 0) return comp;
}
if (xlen < ylen)
return -1;
else if (xlen > ylen)
return 1;
return 0;
}
break;
default:
if (x.data.string == y.data.string) {
return 0;
} else {
return x.data.string > y.data.string ? 1 : -1;
}
}
} else if (x.type < y.type) {
return -1;
}
return 1;
}
/* Allow negative indexing to get from end of array like structure */
/* This probably isn't very fast - look at Lua conversion function.
* I would like to keep this standard C for as long as possible, though. */
static int32_t to_index(GstNumber raw, int64_t len) {
int32_t toInt = raw;
if ((GstNumber) toInt == raw) {
/* We were able to convert */
if (toInt < 0 && len > 0) {
/* Index from end */
if (toInt < -len) return -1;
return len + toInt;
} else {
/* Normal indexing */
if (toInt >= len) return -1;
return toInt;
}
} else {
return -1;
}
}
/* Convert a number into a byte. */
static uint8_t to_byte(GstNumber raw) {
if (raw > 255) return 255;
if (raw < 0) return 0;
return (uint8_t) raw;
}
/* Get a value out af an associated data structure.
* Returns possible c error message, and NULL for no error. The
* useful return value is written to out on success */
const char *gst_get(GstValue ds, GstValue key, GstValue *out) {
int32_t index;
GstValue ret;
switch (ds.type) {
case GST_ARRAY:
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, ds.data.array->count);
if (index == -1) return "invalid array access";
ret = ds.data.array->data[index];
break;
case GST_TUPLE:
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, gst_tuple_length(ds.data.tuple));
if (index < 0) return "invalid tuple access";
ret = ds.data.tuple[index];
break;
case GST_BYTEBUFFER:
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, ds.data.buffer->count);
if (index == -1) return "invalid buffer access";
ret.type = GST_NUMBER;
ret.data.number = ds.data.buffer->data[index];
break;
case GST_STRING:
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, gst_string_length(ds.data.string));
if (index == -1) return "invalid string access";
ret.type = GST_NUMBER;
ret.data.number = ds.data.string[index];
break;
case GST_OBJECT:
ret = gst_object_get(ds.data.object, key);
break;
default:
return "cannot get";
}
*out = ret;
return NULL;
}
/* Set a value in an associative data structure. Returns possible
* error message, and NULL if no error. */
const char *gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value) {
int32_t index;
switch (ds.type) {
case GST_ARRAY:
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, ds.data.array->count);
if (index == -1) return "invalid array access";
ds.data.array->data[index] = value;
break;
case GST_BYTEBUFFER:
if (key.type != GST_NUMBER) return "expected numeric key";
if (value.type != GST_NUMBER) return "expected numeric value";
index = to_index(key.data.number, ds.data.buffer->count);
if (index == -1) return "invalid buffer access";
ds.data.buffer->data[index] = to_byte(value.data.number);
break;
case GST_OBJECT:
if (ds.data.object->meta != NULL) {
}
gst_object_put(vm, ds.data.object, key, value);
break;
default:
return "cannot set";
}
return NULL;
}
/* Get the class object of a value */
GstValue gst_get_class(GstValue x) {
GstValue ret;
ret.type = GST_NIL;
switch (x.type) {
case GST_OBJECT:
if (x.data.object->meta != NULL) {
ret.type = GST_OBJECT;
ret.data.object = x.data.object->meta;
}
break;
case GST_USERDATA:
{
GstUserdataHeader *header = (GstUserdataHeader *)x.data.pointer - 1;
if (header->meta != NULL) {
ret.type = GST_OBJECT;
ret.data.object = header->meta;
}
}
break;
default:
break;
}
return ret;
}
/* Set the class object of a value. Returns possible c error string */
const char *gst_set_class(GstValue x, GstValue class) {
switch (x.type) {
case GST_OBJECT:
if (class.type != GST_OBJECT) return "class must be of type object";
/* TODO - check for class immutability */
x.data.object->meta = class.data.object;
break;
default:
return "cannot set class object";
}
return NULL;
}

553
core/vm.c Normal file
View File

@@ -0,0 +1,553 @@
#include <gst/vm.h>
#include <gst/util.h>
#include <gst/value.h>
#include <gst/ds.h>
#include <gst/gc.h>
#include <gst/thread.h>
/* Macros for errors in the vm */
/* Exit from the VM normally */
#define gst_exit(vm, r) return ((vm)->ret = (r), GST_RETURN_OK)
/* Bail from the VM with an error string. */
#define gst_error(vm, e) do { (vm)->ret = gst_load_cstring((vm), (e)); goto vm_error; } while (0)
/* Crash. Not catchable, unlike error. */
#define gst_crash(vm, e) return ((vm)->crash = (e), GST_RETURN_CRASH)
/* Error if the condition is false */
#define gst_assert(vm, cond, e) do {if (!(cond)){gst_error((vm), (e));}} while (0)
static const char GST_NO_UPVALUE[] = "no upvalue";
static const char GST_EXPECTED_FUNCTION[] = "expected function";
static const char GST_EXPECTED_NUMBER_ROP[] = "expected right operand to be number";
static const char GST_EXPECTED_NUMBER_LOP[] = "expected left operand to be number";
/* Contextual macro to state in function with VM */
#define GST_STATE_SYNC() do { \
thread = *vm->thread; \
stack = thread.data + thread.count; \
} while (0)
/* Write local state back to VM */
#define GST_STATE_WRITE() do { \
*vm->thread = thread; \
} while (0)
/* Start running the VM from where it left off. Continue running
* until the stack size is smaller than minStackSize. */
static int gst_continue_size(Gst *vm, uint32_t stackBase) {
/* VM state */
GstThread thread;
GstValue *stack;
GstValue temp, v1, v2;
uint16_t *pc;
/* Intialize local state */
GST_STATE_SYNC();
pc = gst_frame_pc(stack);
/* Main interpreter loop */
for (;;) {
switch (*pc) {
default:
gst_error(vm, "unknown opcode");
break;
#define OP_BINARY_MATH(op) \
v1 = stack[pc[2]]; \
v2 = stack[pc[3]]; \
gst_assert(vm, v1.type == GST_NUMBER, GST_EXPECTED_NUMBER_LOP); \
gst_assert(vm, v2.type == GST_NUMBER, GST_EXPECTED_NUMBER_ROP); \
temp.type = GST_NUMBER; \
temp.data.number = v1.data.number op v2.data.number; \
stack[pc[1]] = temp; \
pc += 4; \
continue;
case GST_OP_ADD: /* Addition */
OP_BINARY_MATH(+)
case GST_OP_SUB: /* Subtraction */
OP_BINARY_MATH(-)
case GST_OP_MUL: /* Multiplication */
OP_BINARY_MATH(*)
case GST_OP_DIV: /* Division */
OP_BINARY_MATH(/)
#undef OP_BINARY_MATH
case GST_OP_NOT: /* Boolean unary (Boolean not) */
temp.type = GST_BOOLEAN;
temp.data.boolean = !gst_truthy(stack[pc[2]]);
stack[pc[1]] = temp;
pc += 3;
continue;
case GST_OP_NEG: /* Unary negation */
v1 = stack[pc[2]];
gst_assert(vm, v1.type == GST_NUMBER, GST_EXPECTED_NUMBER_LOP);
temp.type = GST_NUMBER;
temp.data.number = -v1.data.number;
stack[pc[1]] = temp;
pc += 3;
continue;
case GST_OP_INV: /* Unary multiplicative inverse */
v1 = stack[pc[2]];
gst_assert(vm, v1.type == GST_NUMBER, GST_EXPECTED_NUMBER_LOP);
temp.type = GST_NUMBER;
temp.data.number = 1 / v1.data.number;
stack[pc[1]] = temp;
pc += 3;
continue;
case GST_OP_FLS: /* Load False */
temp.type = GST_BOOLEAN;
temp.data.boolean = 0;
stack[pc[1]] = temp;
pc += 2;
continue;
case GST_OP_TRU: /* Load True */
temp.type = GST_BOOLEAN;
temp.data.boolean = 1;
stack[pc[1]] = temp;
pc += 2;
continue;
case GST_OP_NIL: /* Load Nil */
temp.type = GST_NIL;
stack[pc[1]] = temp;
pc += 2;
continue;
case GST_OP_I16: /* Load Small Integer */
temp.type = GST_NUMBER;
temp.data.number = ((int16_t *)(pc))[2];
stack[pc[1]] = temp;
pc += 3;
continue;
case GST_OP_UPV: /* Load Up Value */
case GST_OP_SUV: /* Set Up Value */
{
GstValue *upv;
GstFunction *fn;
GstFuncEnv *env;
uint16_t level = pc[2];
temp = gst_frame_callee(stack);
gst_assert(vm, temp.type == GST_FUNCTION, GST_EXPECTED_FUNCTION);
fn = temp.data.function;
if (level == 0)
upv = stack + pc[3];
else {
while (fn && --level)
fn = fn->parent;
gst_assert(vm, fn, GST_NO_UPVALUE);
env = fn->env;
if (env->thread)
upv = env->thread->data + env->stackOffset + pc[3];
else
upv = env->values + pc[3];
}
if (pc[0] == GST_OP_UPV) {
stack[pc[1]] = *upv;
} else {
*upv = stack[pc[1]];
}
pc += 4;
}
continue;
case GST_OP_JIF: /* Jump If */
if (gst_truthy(stack[pc[1]])) {
pc += 4;
} else {
pc += *((int32_t *)(pc + 2));
}
continue;
case GST_OP_JMP: /* Jump */
pc += *((int32_t *)(pc + 1));
continue;
case GST_OP_CST: /* Load constant value */
v1 = gst_frame_callee(stack);
gst_assert(vm, v1.type == GST_FUNCTION, GST_EXPECTED_FUNCTION);
if (pc[2] > v1.data.function->def->literalsLen)
gst_error(vm, GST_NO_UPVALUE);
stack[pc[1]] = v1.data.function->def->literals[pc[2]];
pc += 3;
continue;
case GST_OP_I32: /* Load 32 bit integer */
temp.type = GST_NUMBER;
temp.data.number = *((int32_t *)(pc + 2));
stack[pc[1]] = temp;
pc += 4;
continue;
case GST_OP_F64: /* Load 64 bit float */
temp.type = GST_NUMBER;
temp.data.number = (GstNumber) *((double *)(pc + 2));
stack[pc[1]] = temp;
pc += 6;
continue;
case GST_OP_MOV: /* Move Values */
stack[pc[1]] = stack[pc[2]];
pc += 3;
continue;
case GST_OP_CLN: /* Create closure from constant FuncDef */
{
GstFunction *fn;
v1 = gst_frame_callee(stack);
if (v1.type != GST_FUNCTION)
gst_error(vm, GST_EXPECTED_FUNCTION);
if (gst_frame_env(stack) == NULL) {
gst_frame_env(stack) = gst_alloc(vm, sizeof(GstFuncEnv));
*vm->thread = thread;
gst_frame_env(stack)->thread = vm->thread;
gst_frame_env(stack)->stackOffset = thread.count;
gst_frame_env(stack)->values = NULL;
}
if (pc[2] > v1.data.function->def->literalsLen)
gst_error(vm, GST_NO_UPVALUE);
temp = v1.data.function->def->literals[pc[2]];
if (temp.type != GST_NIL)
gst_error(vm, "cannot create closure");
fn = gst_alloc(vm, sizeof(GstFunction));
fn->def = (GstFuncDef *) temp.data.pointer;
fn->parent = v1.data.function;
fn->env = gst_frame_env(stack);
temp.type = GST_FUNCTION;
temp.data.function = fn;
stack[pc[1]] = temp;
pc += 3;
}
break;
case GST_OP_EQL: /* Equality */
temp.type = GST_BOOLEAN;
temp.data.boolean = gst_equals(stack[pc[2]], stack[pc[3]]);
stack[pc[1]] = temp;
pc += 4;
continue;
case GST_OP_LTN: /* Less Than */
temp.type = GST_BOOLEAN;
temp.data.boolean = (gst_compare(stack[pc[2]], stack[pc[3]]) == -1);
stack[pc[1]] = temp;
pc += 4;
continue;
case GST_OP_LTE: /* Less Than or Equal to */
temp.type = GST_BOOLEAN;
temp.data.boolean = (gst_compare(stack[pc[2]], stack[pc[3]]) != 1);
stack[pc[1]] = temp;
pc += 4;
continue;
case GST_OP_ARR: /* Array literal */
{
uint32_t i;
uint32_t arrayLen = pc[2];
GstArray *array = gst_array(vm, arrayLen);
array->count = arrayLen;
for (i = 0; i < arrayLen; ++i)
array->data[i] = stack[pc[3 + i]];
temp.type = GST_ARRAY;
temp.data.array = array;
stack[pc[1]] = temp;
pc += 3 + arrayLen;
}
break;
case GST_OP_DIC: /* Object literal */
{
uint32_t i = 3;
uint32_t kvs = pc[2];
GstObject *o = gst_object(vm, kvs + 2);
kvs = kvs + 3;
while (i < kvs) {
v1 = stack[pc[i++]];
v2 = stack[pc[i++]];
gst_object_put(vm, o, v1, v2);
}
temp.type = GST_OBJECT;
temp.data.object = o;
stack[pc[1]] = temp;
pc += kvs;
}
break;
case GST_OP_TUP: /* Tuple literal */
{
uint32_t i;
uint32_t len = pc[2];
GstValue *tuple = gst_tuple(vm, len);
for (i = 0; i < len; ++i)
tuple[i] = stack[pc[3 + i]];
temp.type = GST_TUPLE;
temp.data.tuple = tuple;
stack[pc[1]] = temp;
pc += 3 + len;
}
break;
case GST_OP_GET: /* Associative get */
{
const char *err;
err = gst_get(stack[pc[2]], stack[pc[3]], stack + pc[1]);
if (err != NULL)
gst_error(vm, err);
pc += 4;
}
continue;
case GST_OP_SET: /* Associative set */
{
const char *err;
err = gst_set(vm, stack[pc[1]], stack[pc[2]], stack[pc[3]]);
if (err != NULL)
gst_error(vm, err);
pc += 4;
}
break;
case GST_OP_ERR: /* Throw error */
vm->ret = stack[pc[1]];
goto vm_error;
case GST_OP_TRY: /* Begin try block */
gst_frame_errloc(stack) = pc[1];
gst_frame_errjmp(stack) = pc + *(uint32_t *)(pc + 2);
pc += 4;
continue;
case GST_OP_UTY: /* End try block */
gst_frame_errjmp(stack) = NULL;
pc++;
continue;
case GST_OP_RTN: /* Return nil */
stack = gst_thread_popframe(vm, &thread);
if (thread.count < stackBase) {
vm->ret.type = GST_NIL;
GST_STATE_WRITE();
return GST_RETURN_OK;
}
pc = gst_frame_pc(stack);
stack[gst_frame_ret(stack)].type = GST_NIL;
continue;
case GST_OP_RET: /* Return */
temp = stack[pc[1]];
stack = gst_thread_popframe(vm, &thread);
if (thread.count < stackBase) {
vm->ret = temp;
GST_STATE_WRITE();
return GST_RETURN_OK;
}
pc = gst_frame_pc(stack);
stack[gst_frame_ret(stack)] = temp;
continue;
case GST_OP_CAL: /* Call */
case GST_OP_TCL: /* Tail call */
{
GstValue *oldStack;
temp = stack[pc[1]];
int isTCall = *pc == GST_OP_TCL;
uint32_t i, arity, offset, size;
uint16_t ret = pc[2];
offset = isTCall ? 3 : 4;
arity = pc[offset - 1];
/* Push new frame */
stack = gst_thread_beginframe(vm, &thread, temp, arity);
oldStack = stack - GST_FRAME_SIZE - gst_frame_prevsize(stack);
/* Write arguments */
size = gst_frame_size(stack);
for (i = 0; i < arity; ++i)
stack[i + size - arity] = oldStack[pc[offset + i]];
/* Finish new frame */
gst_thread_endframe(vm, &thread);
/* Check tail call - if so, replace frame. */
if (isTCall) {
stack = gst_thread_tail(vm, &thread);
} else {
gst_frame_ret(oldStack) = ret;
}
/* Call function */
temp = gst_frame_callee(stack);
if (temp.type == GST_FUNCTION) {
/* Save pc and set new pc */
if (!isTCall)
gst_frame_pc(oldStack) = pc + offset + arity;
pc = temp.data.function->def->byteCode;
} else {
int status;
GST_STATE_WRITE();
vm->ret.type = GST_NIL;
status = temp.data.cfunction(vm);
GST_STATE_SYNC();
stack = gst_thread_popframe(vm, &thread);
if (status == GST_RETURN_OK)
if (thread.count < stackBase) {
GST_STATE_WRITE();
return status;
} else {
stack[gst_frame_ret(stack)] = vm->ret;
if (isTCall)
pc = gst_frame_pc(stack);
else
pc += offset + arity;
}
else
goto vm_error;
}
}
break;
/* Handle errors from c functions and vm opcodes */
vm_error:
while (gst_frame_errjmp(stack) == NULL) {
stack = gst_thread_popframe(vm, &thread);
if (thread.count < stackBase)
return GST_RETURN_ERROR;
}
pc = gst_frame_errjmp(stack);
stack[gst_frame_errloc(stack)] = vm->ret;
break;
} /* end switch */
/* TODO: Move collection only to places that allocate memory */
/* This, however, is good for testing to ensure no memory leaks */
*vm->thread = thread;
gst_maybe_collect(vm);
} /* end for */
}
/* Continue running the VM after it has stopped */
int gst_continue(Gst *vm) {
return gst_continue_size(vm, vm->thread->count);
}
/* Run the vm with a given function */
int gst_run(Gst *vm, GstValue callee) {
GstValue *stack;
vm->thread = gst_thread(vm, callee, 64);
if (vm->thread == NULL)
return GST_RETURN_CRASH;
stack = gst_thread_stack(vm->thread);
/* If callee was not actually a function, get the delegate function */
callee = gst_frame_callee(stack);
if (callee.type == GST_CFUNCTION) {
int status;
vm->ret.type = GST_NIL;
status = callee.data.cfunction(vm);
gst_thread_popframe(vm, vm->thread);
return status;
} else {
return gst_continue(vm);
}
}
/* Call a gst function */
int gst_call(Gst *vm, GstValue callee, uint32_t arity, GstValue *args) {
GstValue *stack;
uint32_t i, size;
int status;
/* Set the return position */
stack = gst_thread_stack(vm->thread);
gst_frame_ret(stack) = gst_frame_size(stack);
/* Add extra space for returning value */
gst_thread_pushnil(vm, vm->thread, 1);
stack = gst_thread_beginframe(vm, vm->thread, callee, arity);
/* Write args to stack */
size = gst_frame_size(stack) - arity;
for (i = 0; i < arity; ++i)
stack[i + size] = args[i];
gst_thread_endframe(vm, vm->thread);
/* Call function */
callee = gst_frame_callee(stack);
if (callee.type == GST_FUNCTION) {
gst_frame_pc(stack) = callee.data.function->def->byteCode;
status = gst_continue(vm);
} else {
vm->ret.type = GST_NIL;
status = callee.data.cfunction(vm);
gst_thread_popframe(vm, vm->thread);
}
/* Pop the extra nil */
--gst_frame_size(gst_thread_stack(vm->thread));
return status;
}
/* Get an argument from the stack */
GstValue gst_arg(Gst *vm, uint16_t index) {
GstValue *stack = gst_thread_stack(vm->thread);
uint16_t frameSize = gst_frame_size(stack);
if (frameSize <= index) {
GstValue ret;
ret.type = GST_NIL;
return ret;
}
return stack[index];
}
/* Put a value on the stack */
void gst_set_arg(Gst* vm, uint16_t index, GstValue x) {
GstValue *stack = gst_thread_stack(vm->thread);
uint16_t frameSize = gst_frame_size(stack);
if (frameSize <= index) return;
stack[index] = x;
}
/* Get the size of the VMStack */
uint16_t gst_count_args(Gst *vm) {
GstValue *stack = gst_thread_stack(vm->thread);
return gst_frame_size(stack);
}
/* Initialize the VM */
void gst_init(Gst *vm) {
vm->ret.type = GST_NIL;
vm->crash = NULL;
/* Garbage collection */
vm->blocks = NULL;
vm->nextCollection = 0;
/* Setting memoryInterval to zero currently forces
* a collection pretty much every cycle, which is
* obviously horrible for performance. It helps ensure
* there are no memory bugs during dev */
vm->memoryInterval = 2000;
vm->black = 0;
/* Add thread */
vm->thread = NULL;
vm->rootenv.type = GST_NIL;
}
/* Clear all memory associated with the VM */
void gst_deinit(Gst *vm) {
gst_clear_memory(vm);
vm->thread = NULL;
vm->rootenv.type = GST_NIL;
vm->ret.type = GST_NIL;
}