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:
264
core/dict.c
Normal file
264
core/dict.c
Normal 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, ¬used);
|
||||
}
|
||||
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
334
core/ds.c
Normal 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
320
core/gc.c
Normal 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
239
core/thread.c
Normal 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
462
core/value.c
Normal 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
553
core/vm.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user