typedef uint32_t l2_word; | typedef uint32_t l2_word; | ||||
enum l2_opcode { | enum l2_opcode { | ||||
/* | |||||
* Do nothing. | |||||
*/ | |||||
L2_OP_NOP, | L2_OP_NOP, | ||||
/* | |||||
* Push a value to the stack. | |||||
* Push <word> | |||||
*/ | |||||
L2_OP_PUSH, | L2_OP_PUSH, | ||||
/* | |||||
* Discard the top element from the stack. | |||||
* Pop <word> | |||||
*/ | |||||
L2_OP_POP, | L2_OP_POP, | ||||
/* | |||||
* Duplicate the top element on the stack. | |||||
* Push <word at <sptr> - 1> | |||||
*/ | |||||
L2_OP_DUP, | L2_OP_DUP, | ||||
/* | |||||
* Add two words. | |||||
* Pop <word1> | |||||
* Pop <word2> | |||||
* Push <word1> + <word2> | |||||
*/ | |||||
L2_OP_ADD, | L2_OP_ADD, | ||||
L2_OP_JUMP, | |||||
/* | |||||
* Call a function. | |||||
* Pop <word> | |||||
* Push <iptr> + 1 | |||||
* Jump to <word> | |||||
*/ | |||||
L2_OP_CALL, | L2_OP_CALL, | ||||
/* | |||||
* Return from a function. | |||||
* Pop <word> | |||||
* Jump to <word> | |||||
*/ | |||||
L2_OP_RET, | L2_OP_RET, | ||||
/* | |||||
* Allocate an integer from one word. | |||||
* Pop <word> | |||||
* Alloc integer <var> from <word> | |||||
* Push <var> | |||||
*/ | |||||
L2_OP_ALLOC_INTEGER_32, | 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, | 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, | 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_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_ARRAY, | ||||
L2_OP_ALLOC_MAP, | |||||
/* | |||||
* Allocate an integer->value map. | |||||
* Alloc namespace <var> | |||||
* Push <var> | |||||
*/ | |||||
L2_OP_ALLOC_NAMESPACE, | |||||
/* | |||||
* Halt execution. | |||||
*/ | |||||
L2_OP_HALT, | L2_OP_HALT, | ||||
}; | }; | ||||
L2_VAL_TYPE_REAL, | L2_VAL_TYPE_REAL, | ||||
L2_VAL_TYPE_BUFFER, | L2_VAL_TYPE_BUFFER, | ||||
L2_VAL_TYPE_ARRAY, | L2_VAL_TYPE_ARRAY, | ||||
L2_VAL_TYPE_MAP, | |||||
L2_VAL_TYPE_NAMESPACE, | |||||
L2_VAL_MARKED = 1 << 7, | L2_VAL_MARKED = 1 << 7, | ||||
L2_VAL_CONST = 1 << 8, | L2_VAL_CONST = 1 << 8, | ||||
} flags; | } flags; | ||||
l2_word data[]; | 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 len; | ||||
size_t size; | size_t size; | ||||
l2_word mask; | |||||
l2_word data[]; | 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 { | struct l2_vm { | ||||
l2_word *ops; | l2_word *ops; | ||||
size_t opcount; | size_t opcount; |
case L2_VAL_TYPE_ARRAY: | 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; | struct l2_vm_array *arr = (struct l2_vm_array *)val->data; | ||||
printf("ARRAY, len %zu\n", arr->len); | printf("ARRAY, len %zu\n", arr->len); | ||||
for (size_t i = 0; i < arr->len; ++i) { | for (size_t i = 0; i < arr->len; ++i) { | ||||
case L2_VAL_TYPE_BUFFER: | 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; | struct l2_vm_buffer *buf = (struct l2_vm_buffer *)val->data; | ||||
printf("BUFFER, len %zu\n", buf->len); | printf("BUFFER, len %zu\n", buf->len); | ||||
for (size_t i = 0; i < buf->len; ++i) { | for (size_t i = 0; i < buf->len; ++i) { | ||||
} | } | ||||
} | } | ||||
break; | 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; | |||||
} | } | ||||
} | } | ||||
L2_OP_PUSH, 100, | L2_OP_PUSH, 100, | ||||
L2_OP_ADD, | L2_OP_ADD, | ||||
L2_OP_ALLOC_INTEGER_32, | L2_OP_ALLOC_INTEGER_32, | ||||
L2_OP_PUSH, 20 /* offset */, | |||||
L2_OP_PUSH, 21 /* offset */, | |||||
L2_OP_PUSH, 5 /* length */, | L2_OP_PUSH, 5 /* length */, | ||||
L2_OP_ALLOC_BUFFER_CONST, | |||||
L2_OP_ALLOC_BUFFER_STATIC, | |||||
L2_OP_POP, | L2_OP_POP, | ||||
L2_OP_PUSH, 16, | L2_OP_PUSH, 16, | ||||
L2_OP_JUMP, | |||||
L2_OP_CALL, | |||||
L2_OP_HALT, | L2_OP_HALT, | ||||
L2_OP_PUSH, 53, | L2_OP_PUSH, 53, | ||||
L2_OP_ALLOC_INTEGER_32, | L2_OP_ALLOC_INTEGER_32, | ||||
L2_OP_ALLOC_NAMESPACE, | |||||
L2_OP_HALT, | L2_OP_HALT, | ||||
0, 0, | 0, 0, | ||||
}; | }; | ||||
memcpy(&ops[20], "Hello", 5); | |||||
memcpy(&ops[21], "Hello", 5); | |||||
struct l2_vm vm; | struct l2_vm vm; | ||||
l2_vm_init(&vm, ops, sizeof(ops) / sizeof(*ops)); | l2_vm_init(&vm, ops, sizeof(ops) / sizeof(*ops)); |
#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); | |||||
} |
int typ = val->flags & 0x0f; | int typ = val->flags & 0x0f; | ||||
if (typ == L2_VAL_TYPE_ARRAY) { | if (typ == L2_VAL_TYPE_ARRAY) { | ||||
if (val->data == NULL) { | |||||
return; | |||||
} | |||||
struct l2_vm_array *arr = (struct l2_vm_array *)val->data; | struct l2_vm_array *arr = (struct l2_vm_array *)val->data; | ||||
for (size_t i = 0; i < arr->len; ++i) { | for (size_t i = 0; i < arr->len; ++i) { | ||||
gc_mark(vm, arr->data[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]); | |||||
} | |||||
} | } | ||||
} | } | ||||
l2_bitset_unset(&vm->valueset, id); | l2_bitset_unset(&vm->valueset, id); | ||||
int typ = val->flags & 0x0f; | 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); | free(val->data); | ||||
// Don't need to do anything more; the next round of GC will free | // Don't need to do anything more; the next round of GC will free | ||||
// whichever values were only referenced by the array | // whichever values were only referenced by the array | ||||
vm->sptr -= 1; | vm->sptr -= 1; | ||||
break; | 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: | case L2_OP_CALL: | ||||
word = vm->stack[vm->sptr - 1]; | word = vm->stack[vm->sptr - 1]; | ||||
vm->stack[vm->sptr - 1] = vm->iptr + 1; | vm->stack[vm->sptr - 1] = vm->iptr + 1; | ||||
vm->sptr += 1; | vm->sptr += 1; | ||||
break; | 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); | word = alloc_val(vm); | ||||
l2_word length = vm->stack[--vm->sptr]; | l2_word length = vm->stack[--vm->sptr]; | ||||
} | } | ||||
break; | 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: | case L2_OP_ALLOC_ARRAY: | ||||
word = alloc_val(vm); | word = alloc_val(vm); | ||||
vm->values[word].flags = L2_VAL_TYPE_ARRAY; | 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->stack[vm->sptr] = word; | ||||
vm->stackflags[vm->sptr] = 1; | vm->stackflags[vm->sptr] = 1; | ||||
vm->sptr += 1; | vm->sptr += 1; | ||||
break; | break; | ||||
case L2_OP_ALLOC_MAP: | |||||
case L2_OP_ALLOC_NAMESPACE: | |||||
word = alloc_val(vm); | 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->stack[vm->sptr] = word; | ||||
vm->stackflags[vm->sptr] = 1; | vm->stackflags[vm->sptr] = 1; | ||||
vm->sptr += 1; | vm->sptr += 1; |