@@ -6,22 +6,120 @@ | |||
typedef uint32_t l2_word; | |||
enum l2_opcode { | |||
/* | |||
* Do nothing. | |||
*/ | |||
L2_OP_NOP, | |||
/* | |||
* Push a value to the stack. | |||
* Push <word> | |||
*/ | |||
L2_OP_PUSH, | |||
/* | |||
* Discard the top element from the stack. | |||
* Pop <word> | |||
*/ | |||
L2_OP_POP, | |||
/* | |||
* Duplicate the top element on the stack. | |||
* Push <word at <sptr> - 1> | |||
*/ | |||
L2_OP_DUP, | |||
/* | |||
* Add two words. | |||
* Pop <word1> | |||
* Pop <word2> | |||
* Push <word1> + <word2> | |||
*/ | |||
L2_OP_ADD, | |||
L2_OP_JUMP, | |||
/* | |||
* Call a function. | |||
* Pop <word> | |||
* Push <iptr> + 1 | |||
* Jump to <word> | |||
*/ | |||
L2_OP_CALL, | |||
/* | |||
* Return from a function. | |||
* Pop <word> | |||
* Jump to <word> | |||
*/ | |||
L2_OP_RET, | |||
/* | |||
* Allocate an integer from one word. | |||
* Pop <word> | |||
* Alloc integer <var> from <word> | |||
* Push <var> | |||
*/ | |||
L2_OP_ALLOC_INTEGER_32, | |||
/* | |||
* Allocate an integer from two words. | |||
* Pop <word1> | |||
* Pop <word2> | |||
* Alloc integer <var> from <word1> << 32 | <word2> | |||
* Push <var> | |||
*/ | |||
L2_OP_ALLOC_INTEGER_64, | |||
/* | |||
* Allocate a real from one word. | |||
* Pop <word> | |||
* Alloc real <var> from <word> | |||
* Push <var> | |||
*/ | |||
L2_OP_ALLOC_REAL_32, | |||
/* | |||
* Allocate a real from two words. | |||
* Pop <word1> | |||
* Pop <word2> | |||
* Alloc real <var> from <word1> << 32 | <word2> | |||
* Push <var> | |||
*/ | |||
L2_OP_ALLOC_REAL_64, | |||
L2_OP_ALLOC_BUFFER, | |||
L2_OP_ALLOC_BUFFER_CONST, | |||
/* | |||
* Allocate a buffer from static data. | |||
* Pop <word1> | |||
* Pop <word2> | |||
* Alloc buffer <var> with length=<word1>, offset=<word2> | |||
* Push <var> | |||
*/ | |||
L2_OP_ALLOC_BUFFER_STATIC, | |||
/* | |||
* Allocate a zeroed buffer. | |||
* Pop <word> | |||
* Alloc buffer <var> with length=<word> | |||
* Push <var> | |||
*/ | |||
L2_OP_ALLOC_BUFFER_ZERO, | |||
/* | |||
* Allocate an array. | |||
* Alloc array <var> | |||
* Push <var> | |||
*/ | |||
L2_OP_ALLOC_ARRAY, | |||
L2_OP_ALLOC_MAP, | |||
/* | |||
* Allocate an integer->value map. | |||
* Alloc namespace <var> | |||
* Push <var> | |||
*/ | |||
L2_OP_ALLOC_NAMESPACE, | |||
/* | |||
* Halt execution. | |||
*/ | |||
L2_OP_HALT, | |||
}; | |||
@@ -13,7 +13,7 @@ struct l2_vm_value { | |||
L2_VAL_TYPE_REAL, | |||
L2_VAL_TYPE_BUFFER, | |||
L2_VAL_TYPE_ARRAY, | |||
L2_VAL_TYPE_MAP, | |||
L2_VAL_TYPE_NAMESPACE, | |||
L2_VAL_MARKED = 1 << 7, | |||
L2_VAL_CONST = 1 << 8, | |||
} flags; | |||
@@ -35,12 +35,20 @@ struct l2_vm_array { | |||
l2_word data[]; | |||
}; | |||
struct l2_vm_map { | |||
void l2_vm_array_mark(struct l2_vm_value *arr); | |||
struct l2_vm_namespace { | |||
struct l2_vm_value *parent; | |||
size_t len; | |||
size_t size; | |||
l2_word mask; | |||
l2_word data[]; | |||
}; | |||
void l2_vm_namespace_mark(struct l2_vm_value *ns); | |||
void l2_vm_namespace_set(struct l2_vm_value *ns, l2_word key, l2_word val); | |||
l2_word l2_vm_namespace_get(struct l2_vm_value *ns, l2_word key); | |||
struct l2_vm { | |||
l2_word *ops; | |||
size_t opcount; |
@@ -20,6 +20,11 @@ void print_var(struct l2_vm_value *val) { | |||
case L2_VAL_TYPE_ARRAY: | |||
{ | |||
if (val->data == NULL) { | |||
printf("ARRAY, empty\n"); | |||
return; | |||
} | |||
struct l2_vm_array *arr = (struct l2_vm_array *)val->data; | |||
printf("ARRAY, len %zu\n", arr->len); | |||
for (size_t i = 0; i < arr->len; ++i) { | |||
@@ -30,6 +35,11 @@ void print_var(struct l2_vm_value *val) { | |||
case L2_VAL_TYPE_BUFFER: | |||
{ | |||
if (val->data == NULL) { | |||
printf("BUFFER, empty\n"); | |||
return; | |||
} | |||
struct l2_vm_buffer *buf = (struct l2_vm_buffer *)val->data; | |||
printf("BUFFER, len %zu\n", buf->len); | |||
for (size_t i = 0; i < buf->len; ++i) { | |||
@@ -37,6 +47,18 @@ void print_var(struct l2_vm_value *val) { | |||
} | |||
} | |||
break; | |||
case L2_VAL_TYPE_NAMESPACE: | |||
{ | |||
if (val->data == NULL) { | |||
printf("NAMESPACE, empty\n"); | |||
return; | |||
} | |||
struct l2_vm_namespace *ns = (struct l2_vm_namespace *)val->data; | |||
printf("NAMESPACE, len %zu\n", ns->len); | |||
} | |||
break; | |||
} | |||
} | |||
@@ -46,19 +68,20 @@ int main() { | |||
L2_OP_PUSH, 100, | |||
L2_OP_ADD, | |||
L2_OP_ALLOC_INTEGER_32, | |||
L2_OP_PUSH, 20 /* offset */, | |||
L2_OP_PUSH, 21 /* offset */, | |||
L2_OP_PUSH, 5 /* length */, | |||
L2_OP_ALLOC_BUFFER_CONST, | |||
L2_OP_ALLOC_BUFFER_STATIC, | |||
L2_OP_POP, | |||
L2_OP_PUSH, 16, | |||
L2_OP_JUMP, | |||
L2_OP_CALL, | |||
L2_OP_HALT, | |||
L2_OP_PUSH, 53, | |||
L2_OP_ALLOC_INTEGER_32, | |||
L2_OP_ALLOC_NAMESPACE, | |||
L2_OP_HALT, | |||
0, 0, | |||
}; | |||
memcpy(&ops[20], "Hello", 5); | |||
memcpy(&ops[21], "Hello", 5); | |||
struct l2_vm vm; | |||
l2_vm_init(&vm, ops, sizeof(ops) / sizeof(*ops)); |
@@ -0,0 +1,117 @@ | |||
#include "vm/vm.h" | |||
#include <stdio.h> | |||
static const l2_word tombstone = ~(l2_word)0; | |||
static struct l2_vm_namespace *set(struct l2_vm_namespace *ns, l2_word key, l2_word val); | |||
static struct l2_vm_namespace *alloc(size_t size, l2_word mask) { | |||
printf("alloc, size %zu, mask allows up to [%u]\n", size, mask); | |||
struct l2_vm_namespace *ns = calloc( | |||
1, sizeof(struct l2_vm_namespace) + sizeof(l2_word) * size * 2); | |||
ns->size = size; | |||
ns->mask = mask; | |||
return ns; | |||
} | |||
static struct l2_vm_namespace *grow(struct l2_vm_namespace *ns) { | |||
struct l2_vm_namespace *newns = alloc(ns->size * 2, (ns->mask << 1) | ns->mask); | |||
for (size_t i = 0; i < ns->size; ++i) { | |||
l2_word key = ns->data[i]; | |||
if (key == 0 || key == tombstone) { | |||
continue; | |||
} | |||
l2_word val = ns->data[ns->size + i]; | |||
for (l2_word i = 0; ; ++i) { | |||
l2_word hash = (key + i) & newns->mask; | |||
if (newns->data[hash] == 0) { | |||
newns->data[hash] = key; | |||
newns->data[newns->size + hash] = val; | |||
newns->len += 1; | |||
break; | |||
} | |||
} | |||
} | |||
free(ns); | |||
return newns; | |||
} | |||
static void del(struct l2_vm_namespace *ns, l2_word key) { | |||
if (ns == NULL) { | |||
return; | |||
} | |||
for (l2_word i = 0; ; ++i) { | |||
l2_word hash = (key + i) & ns->mask; | |||
l2_word k = ns->data[hash]; | |||
if (k == 0) { | |||
return; | |||
} else if (k == key) { | |||
ns->data[hash] = tombstone; | |||
return; | |||
} | |||
} | |||
} | |||
static struct l2_vm_namespace *set(struct l2_vm_namespace *ns, l2_word key, l2_word val) { | |||
if (ns == NULL) { | |||
ns = alloc(16, 0x0f); | |||
printf("alloc to %zu\n", ns->size); | |||
} else if (ns->len >= ns->size / 2) { | |||
ns = grow(ns); | |||
printf("grew to %zu\n", ns->size); | |||
} | |||
for (l2_word i = 0; ; ++i) { | |||
l2_word hash = (key + i) & ns->mask; | |||
l2_word k = ns->data[hash]; | |||
if (k == tombstone) { | |||
ns->data[hash] = key; | |||
ns->data[ns->size + hash] = val; | |||
break; | |||
} else if (k == key) { | |||
ns->data[ns->size + hash] = val; | |||
break; | |||
} else if (k == 0) { | |||
ns->len += 1; | |||
ns->data[hash] = key; | |||
ns->data[ns->size + hash] = val; | |||
break; | |||
} | |||
} | |||
return ns; | |||
} | |||
static l2_word get(struct l2_vm_namespace *ns, l2_word key) { | |||
if (ns == NULL) { | |||
return 0; | |||
} | |||
for (l2_word i = 0; ; ++i) { | |||
l2_word hash = (key + i) & ns->mask; | |||
l2_word k = ns->data[hash]; | |||
if (k == 0) { | |||
return 0; | |||
} else if (k == key) { | |||
printf("found, %i collisions\n", i); | |||
return ns->data[ns->size + hash]; | |||
} | |||
} | |||
} | |||
void l2_vm_namespace_set(struct l2_vm_value *ns, l2_word key, l2_word val) { | |||
if (val == 0) { | |||
del(ns->data, key); | |||
} else { | |||
ns->data = set(ns->data, key, val); | |||
} | |||
} | |||
l2_word l2_vm_namespace_get(struct l2_vm_value *ns, l2_word key) { | |||
return get(ns->data, key); | |||
} |
@@ -41,10 +41,28 @@ static void gc_mark(struct l2_vm *vm, l2_word id) { | |||
int typ = val->flags & 0x0f; | |||
if (typ == L2_VAL_TYPE_ARRAY) { | |||
if (val->data == NULL) { | |||
return; | |||
} | |||
struct l2_vm_array *arr = (struct l2_vm_array *)val->data; | |||
for (size_t i = 0; i < arr->len; ++i) { | |||
gc_mark(vm, arr->data[i]); | |||
} | |||
} else if (typ == L2_VAL_TYPE_NAMESPACE) { | |||
if (val->data == NULL) { | |||
return; | |||
} | |||
struct l2_vm_namespace *ns = (struct l2_vm_namespace *)val->data; | |||
for (size_t i = 0; i < ns->size; ++i) { | |||
l2_word key = ns->data[i]; | |||
if (key == 0 || key == ~(l2_word)0) { | |||
continue; | |||
} | |||
gc_mark(vm, ns->data[ns->size + i]); | |||
} | |||
} | |||
} | |||
@@ -54,7 +72,7 @@ static void gc_free(struct l2_vm *vm, l2_word id) { | |||
l2_bitset_unset(&vm->valueset, id); | |||
int typ = val->flags & 0x0f; | |||
if (typ == L2_VAL_TYPE_ARRAY || typ == L2_VAL_TYPE_BUFFER) { | |||
if (typ == L2_VAL_TYPE_ARRAY || typ == L2_VAL_TYPE_BUFFER || typ == L2_VAL_TYPE_NAMESPACE) { | |||
free(val->data); | |||
// Don't need to do anything more; the next round of GC will free | |||
// whichever values were only referenced by the array | |||
@@ -157,12 +175,6 @@ void l2_vm_step(struct l2_vm *vm) { | |||
vm->sptr -= 1; | |||
break; | |||
case L2_OP_JUMP: | |||
vm->iptr = vm->stack[vm->sptr - 1]; | |||
vm->stackflags[vm->sptr - 1] = 0; | |||
vm->sptr -= 1; | |||
break; | |||
case L2_OP_CALL: | |||
word = vm->stack[vm->sptr - 1]; | |||
vm->stack[vm->sptr - 1] = vm->iptr + 1; | |||
@@ -215,16 +227,7 @@ void l2_vm_step(struct l2_vm *vm) { | |||
vm->sptr += 1; | |||
break; | |||
case L2_OP_ALLOC_BUFFER: | |||
word = alloc_val(vm); | |||
vm->values[word].flags = L2_VAL_TYPE_BUFFER; | |||
vm->values[word].data = calloc(1, sizeof(struct l2_vm_buffer)); | |||
vm->stack[vm->sptr] = word; | |||
vm->stackflags[vm->sptr] = 1; | |||
vm->sptr += 1; | |||
break; | |||
case L2_OP_ALLOC_BUFFER_CONST: | |||
case L2_OP_ALLOC_BUFFER_STATIC: | |||
{ | |||
word = alloc_val(vm); | |||
l2_word length = vm->stack[--vm->sptr]; | |||
@@ -241,19 +244,31 @@ void l2_vm_step(struct l2_vm *vm) { | |||
} | |||
break; | |||
case L2_OP_ALLOC_BUFFER_ZERO: | |||
{ | |||
word = alloc_val(vm); | |||
l2_word length = vm->stack[--vm->sptr]; | |||
vm->values[word].flags = L2_VAL_TYPE_BUFFER; | |||
vm->values[word].data = calloc(1, sizeof(struct l2_vm_buffer) + length); | |||
((struct l2_vm_buffer *)vm->values[word].data)->len = length; | |||
vm->stack[vm->sptr] = word; | |||
vm->stackflags[vm->sptr] = 1; | |||
vm->sptr += 1; | |||
} | |||
case L2_OP_ALLOC_ARRAY: | |||
word = alloc_val(vm); | |||
vm->values[word].flags = L2_VAL_TYPE_ARRAY; | |||
vm->values[word].data = calloc(1, sizeof(struct l2_vm_array)); | |||
vm->values[word].data = NULL; // Will be allocated on first insert | |||
vm->stack[vm->sptr] = word; | |||
vm->stackflags[vm->sptr] = 1; | |||
vm->sptr += 1; | |||
break; | |||
case L2_OP_ALLOC_MAP: | |||
case L2_OP_ALLOC_NAMESPACE: | |||
word = alloc_val(vm); | |||
vm->values[word].flags = L2_VAL_TYPE_MAP; | |||
vm->values[word].data = calloc(1, sizeof(struct l2_vm_map)); | |||
vm->values[word].flags = L2_VAL_TYPE_NAMESPACE; | |||
vm->values[word].data = NULL; // Will be allocated on first insert | |||
vm->stack[vm->sptr] = word; | |||
vm->stackflags[vm->sptr] = 1; | |||
vm->sptr += 1; |