| @@ -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; | |||