| #include "vm/vm.h" | #include "vm/vm.h" | ||||
| #include "vm/print.h" | |||||
| #include "bitset.h" | #include "bitset.h" | ||||
| #include <stdio.h> | #include <stdio.h> | ||||
| #include <string.h> | #include <string.h> | ||||
| void print_var(struct l2_vm_value *val) { | |||||
| switch (val->flags & 0x0f) { | |||||
| case L2_VAL_TYPE_NONE: | |||||
| printf("NONE\n"); | |||||
| break; | |||||
| case L2_VAL_TYPE_INTEGER: | |||||
| printf("INTEGER %zi\n", val->integer); | |||||
| break; | |||||
| case L2_VAL_TYPE_REAL: | |||||
| printf("REAL %f\n", val->real); | |||||
| break; | |||||
| 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) { | |||||
| printf(" %zu: %u\n", i, arr->data[i]); | |||||
| } | |||||
| } | |||||
| break; | |||||
| 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) { | |||||
| printf(" %zu: %c\n", i, buf->data[i]); | |||||
| } | |||||
| } | |||||
| 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; | |||||
| } | |||||
| } | |||||
| int main() { | int main() { | ||||
| l2_word ops[] = { | l2_word ops[] = { | ||||
| L2_OP_PUSH, 100, | L2_OP_PUSH, 100, | ||||
| l2_vm_run(&vm); | l2_vm_run(&vm); | ||||
| printf("Stack:\n"); | |||||
| for (l2_word i = 0; i < vm.sptr; ++i) { | |||||
| printf(" %i: %i\n", i, vm.stack[i]); | |||||
| } | |||||
| printf("Heap:\n"); | |||||
| for (l2_word i = 0; i < vm.valuessize; ++i) { | |||||
| if (l2_bitset_get(&vm.valueset, i)) { | |||||
| printf(" %u: ", i); | |||||
| print_var(&vm.values[i]); | |||||
| } | |||||
| } | |||||
| l2_vm_print_state(&vm); | |||||
| l2_vm_gc(&vm); | l2_vm_gc(&vm); | ||||
| printf("Heap:\n"); | printf("Heap:\n"); | ||||
| for (l2_word i = 0; i < vm.valuessize; ++i) { | |||||
| if (l2_bitset_get(&vm.valueset, i)) { | |||||
| printf(" %u: ", i); | |||||
| print_var(&vm.values[i]); | |||||
| } | |||||
| } | |||||
| l2_vm_print_stack(&vm); | |||||
| l2_vm_free(&vm); | l2_vm_free(&vm); | ||||
| } | } |
| */ | */ | ||||
| L2_OP_CALL, | L2_OP_CALL, | ||||
| /* | |||||
| * Generate a stack frame. | |||||
| * Alloc namespace <var> | |||||
| * NSPush <var> | |||||
| */ | |||||
| L2_OP_GEN_STACK_FRAME, | |||||
| /* | |||||
| * Look up a value from the current stack frame. | |||||
| * Pop <word> | |||||
| * Find <val> in stack frame using <word> | |||||
| * Push <val> | |||||
| */ | |||||
| L2_OP_STACK_FRAME_LOOKUP, | |||||
| /* | |||||
| * Set a value in the current stack frame. | |||||
| * Pop <key> | |||||
| * Read <val> | |||||
| * Assign <val> to stack frame | |||||
| */ | |||||
| L2_OP_STACK_FRAME_SET, | |||||
| /* | /* | ||||
| * Return from a function. | * Return from a function. | ||||
| * NSPop | |||||
| * Pop <word> | * Pop <word> | ||||
| * Jump to <word> | * Jump to <word> | ||||
| */ | */ | ||||
| /* | /* | ||||
| * Allocate a real from two words. | * Allocate a real from two words. | ||||
| * Pop <word1> | |||||
| * Pop <word2> | |||||
| * Alloc real <var> from <word1> << 32 | <word2> | |||||
| * Pop <high> | |||||
| * Pop <low> | |||||
| * Alloc real <var> from <high> << 32 | <low> | |||||
| * Push <var> | * Push <var> | ||||
| */ | */ | ||||
| L2_OP_ALLOC_REAL_64, | L2_OP_ALLOC_REAL_64, | ||||
| /* | /* | ||||
| * Set a namespace's name to a value. | * Set a namespace's name to a value. | ||||
| * Pop <key> | * Pop <key> | ||||
| * Pop <val> | |||||
| * Read <val> | |||||
| * Read <ns> | * Read <ns> | ||||
| * Assign <val> to <ns[<key>]> | * Assign <val> to <ns[<key>]> | ||||
| * Push <val> | |||||
| */ | */ | ||||
| L2_OP_NAMESPACE_SET, | L2_OP_NAMESPACE_SET, | ||||
| struct l2_bufio_writer writer; | struct l2_bufio_writer writer; | ||||
| }; | }; | ||||
| void l2_gen_init(struct l2_generator *gen); | |||||
| void l2_gen_init(struct l2_generator *gen, struct l2_io_writer *w); | |||||
| void l2_gen_flush(struct l2_generator *gen); | |||||
| void l2_gen_free(struct l2_generator *gen); | void l2_gen_free(struct l2_generator *gen); | ||||
| void l2_gen_halt(struct l2_generator *gen); | |||||
| void l2_gen_stack_frame(struct l2_generator *gen); | void l2_gen_stack_frame(struct l2_generator *gen); | ||||
| void l2_gen_assignment(struct l2_generator *gen, char **ident); | void l2_gen_assignment(struct l2_generator *gen, char **ident); | ||||
| void l2_gen_number(struct l2_generator *gen, double num); | void l2_gen_number(struct l2_generator *gen, double num); | ||||
| void l2_gen_namespace_lookup(struct l2_generator *gen, char **ident); | |||||
| #endif | #endif |
| }; | }; | ||||
| void l2_token_free(struct l2_token *tok); | void l2_token_free(struct l2_token *tok); | ||||
| struct l2_token l2_token_move(struct l2_token *tok); | |||||
| char *l2_token_extract_str(struct l2_token *tok); | |||||
| void l2_token_print(struct l2_token *tok, struct l2_io_writer *w); | void l2_token_print(struct l2_token *tok, struct l2_io_writer *w); | ||||
| struct l2_lexer { | struct l2_lexer { | ||||
| struct l2_token currtok; | struct l2_token currtok; | ||||
| struct l2_token toks[2]; | |||||
| struct l2_token toks[4]; | |||||
| int tokidx; | int tokidx; | ||||
| int line; | int line; | ||||
| int ch; | int ch; |
| #include "lex.h" | #include "lex.h" | ||||
| #include "gen/gen.h" | #include "gen/gen.h" | ||||
| int l2_parse_program(struct l2_lexer *lexer, struct l2_generator *gen); | |||||
| struct l2_parse_error { | |||||
| int line; | |||||
| int ch; | |||||
| char *message; | |||||
| }; | |||||
| void l2_parse_err(struct l2_parse_error *err, struct l2_token *tok, const char *fmt, ...); | |||||
| void l2_parse_error_free(struct l2_parse_error *err); | |||||
| int l2_parse_program( | |||||
| struct l2_lexer *lexer, struct l2_generator *gen, struct l2_parse_error *err); | |||||
| #endif | #endif |
| #ifndef L2_VM_PRINT_H | |||||
| #define L2_VM_PRINT_H | |||||
| #include "vm.h" | |||||
| void l2_vm_print_val(struct l2_vm_value *val); | |||||
| void l2_vm_print_state(struct l2_vm *vm); | |||||
| void l2_vm_print_heap(struct l2_vm *vm); | |||||
| void l2_vm_print_stack(struct l2_vm *vm); | |||||
| void l2_vm_print_nstack(struct l2_vm *vm); | |||||
| void l2_vm_print_op(l2_word *ops, size_t opcount, size_t *ptr); | |||||
| void l2_vm_print_bytecode(l2_word *ops, size_t opcount); | |||||
| #endif |
| }; | }; | ||||
| }; | }; | ||||
| #define l2_vm_value_type(val) ((val).flags & 0x0f) | |||||
| struct l2_vm_buffer { | struct l2_vm_buffer { | ||||
| size_t len; | size_t len; | ||||
| char data[]; | char data[]; | ||||
| struct l2_vm { | struct l2_vm { | ||||
| l2_word *ops; | l2_word *ops; | ||||
| size_t opcount; | size_t opcount; | ||||
| l2_word iptr; | |||||
| struct l2_vm_value *values; | struct l2_vm_value *values; | ||||
| size_t valuessize; | size_t valuessize; | ||||
| struct l2_bitset valueset; | struct l2_bitset valueset; | ||||
| l2_word stack[1024]; | l2_word stack[1024]; | ||||
| unsigned char stackflags[1024]; | |||||
| l2_word iptr; | |||||
| l2_word sptr; | l2_word sptr; | ||||
| l2_word nstack[1024]; | |||||
| l2_word nsptr; | |||||
| }; | }; | ||||
| void l2_vm_init(struct l2_vm *vm, l2_word *ops, size_t opcount); | void l2_vm_init(struct l2_vm *vm, l2_word *ops, size_t opcount); |
| l2_bufio_put_n(&gen->writer, &word, sizeof(word)); | l2_bufio_put_n(&gen->writer, &word, sizeof(word)); | ||||
| } | } | ||||
| void l2_gen_init(struct l2_generator *gen) { | |||||
| void l2_gen_init(struct l2_generator *gen, struct l2_io_writer *w) { | |||||
| l2_strset_init(&gen->atoms); | l2_strset_init(&gen->atoms); | ||||
| l2_strset_init(&gen->strings); | l2_strset_init(&gen->strings); | ||||
| l2_bufio_writer_init(&gen->writer, w); | |||||
| } | |||||
| void l2_gen_flush(struct l2_generator *gen) { | |||||
| l2_bufio_flush(&gen->writer); | |||||
| } | } | ||||
| void l2_gen_free(struct l2_generator *gen) { | void l2_gen_free(struct l2_generator *gen) { | ||||
| l2_strset_free(&gen->strings); | l2_strset_free(&gen->strings); | ||||
| } | } | ||||
| // Postconditions: | |||||
| // * Execution is halted | |||||
| void l2_gen_halt(struct l2_generator *gen) { | |||||
| put(gen, L2_OP_HALT); | |||||
| } | |||||
| // Postconditions: | |||||
| // * NStack(0) is a namespace value | |||||
| void l2_gen_stack_frame(struct l2_generator *gen) { | void l2_gen_stack_frame(struct l2_generator *gen) { | ||||
| put(gen, L2_OP_ALLOC_NAMESPACE); | |||||
| put(gen, L2_OP_GEN_STACK_FRAME); | |||||
| } | } | ||||
| // Preconditions: | |||||
| // * Stack(0) is any value | |||||
| // Postconditions: | |||||
| // * The namespace contains the new value under key 'ident' | |||||
| // * Stack(0) is untouched | |||||
| void l2_gen_assignment(struct l2_generator *gen, char **ident) { | void l2_gen_assignment(struct l2_generator *gen, char **ident) { | ||||
| size_t atom_id = l2_strset_put(&gen->atoms, ident); | size_t atom_id = l2_strset_put(&gen->atoms, ident); | ||||
| put(gen, L2_OP_PUSH); | put(gen, L2_OP_PUSH); | ||||
| put(gen, atom_id); | put(gen, atom_id); | ||||
| put(gen, L2_OP_NAMESPACE_SET); | |||||
| put(gen, L2_OP_STACK_FRAME_SET); | |||||
| } | } | ||||
| // Postconditions; | |||||
| // * Stack(0) is changed to a number value | |||||
| void l2_gen_number(struct l2_generator *gen, double num) { | void l2_gen_number(struct l2_generator *gen, double num) { | ||||
| uint64_t n; | uint64_t n; | ||||
| memcpy(&n, &num, sizeof(num)); | memcpy(&n, &num, sizeof(num)); | ||||
| put(gen, L2_OP_PUSH_2); | put(gen, L2_OP_PUSH_2); | ||||
| put(gen, n >> 32); | |||||
| put(gen, n); | put(gen, n); | ||||
| put(gen, n >> 32); | |||||
| put(gen, L2_OP_ALLOC_REAL_64); | put(gen, L2_OP_ALLOC_REAL_64); | ||||
| } | } | ||||
| // Postconditions: | |||||
| // * Stack(0) is any value | |||||
| void l2_gen_namespace_lookup(struct l2_generator *gen, char **ident) { | |||||
| size_t atom_id = l2_strset_put(&gen->atoms, ident); | |||||
| put(gen, L2_OP_PUSH); | |||||
| put(gen, atom_id); | |||||
| put(gen, L2_OP_STACK_FRAME_LOOKUP); | |||||
| } |
| #include "parse/parse.h" | |||||
| #include <stdio.h> | |||||
| #include <stdarg.h> | |||||
| void l2_parse_err(struct l2_parse_error *err, struct l2_token *tok, const char *fmt, ...) { | |||||
| err->line = tok->line; | |||||
| err->ch = tok->ch; | |||||
| char buf[256]; | |||||
| va_list va; | |||||
| va_start(va, fmt); | |||||
| int n = vsnprintf(buf, sizeof(buf), fmt, va); | |||||
| if (n < 0) { | |||||
| const char *message = "Failed to generate error message!"; | |||||
| err->message = malloc(strlen(message) + 1); | |||||
| strcpy(err->message, message); | |||||
| va_end(va); | |||||
| return; | |||||
| } else if (n + 1 < sizeof(buf)) { | |||||
| err->message = malloc(n + 1); | |||||
| strcpy(err->message, buf); | |||||
| va_end(va); | |||||
| return; | |||||
| } | |||||
| // Need to allocate for this one | |||||
| err->message = malloc(n + 1); | |||||
| vsnprintf(err->message, n + 1, fmt, va); | |||||
| va_end(va); | |||||
| } | |||||
| void l2_parse_error_free(struct l2_parse_error *err) { | |||||
| free(err->message); | |||||
| } |
| #include <stdlib.h> | #include <stdlib.h> | ||||
| static int parse_number(const char *str, double *num) { | |||||
| size_t len = strlen(str); | |||||
| *num = 0; | |||||
| int power = 1; | |||||
| for (int i = (int)len - 1; i >= 0; --i) { | |||||
| char ch = str[i]; | |||||
| if (ch >= '0' && ch <= '9') { | |||||
| *num += (ch - '0') * power; | |||||
| power *= 10; | |||||
| } else { | |||||
| return -1; | |||||
| } | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| const char *l2_token_kind_name(enum l2_token_kind kind) { | const char *l2_token_kind_name(enum l2_token_kind kind) { | ||||
| switch (kind) { | switch (kind) { | ||||
| case L2_TOK_OPEN_PAREN: | case L2_TOK_OPEN_PAREN: | ||||
| } | } | ||||
| } | } | ||||
| struct l2_token l2_token_move(struct l2_token *tok) { | |||||
| struct l2_token dup = *tok; | |||||
| if (tok->kind == L2_TOK_STRING) { | |||||
| tok->v.str = NULL; | |||||
| } | |||||
| return dup; | |||||
| char *l2_token_extract_str(struct l2_token *tok) { | |||||
| char *str = tok->v.str; | |||||
| tok->v.str = NULL; | |||||
| return str; | |||||
| } | } | ||||
| void l2_lexer_init(struct l2_lexer *lexer, struct l2_io_reader *r) { | void l2_lexer_init(struct l2_lexer *lexer, struct l2_io_reader *r) { | ||||
| tok->line = lexer->line; | tok->line = lexer->line; | ||||
| tok->ch = lexer->ch; | tok->ch = lexer->ch; | ||||
| int ch = read_ch(lexer); | |||||
| int ch = peek_ch(lexer); | |||||
| switch (ch) { | switch (ch) { | ||||
| case '(': | case '(': | ||||
| read_ch(lexer); | |||||
| tok->kind = L2_TOK_OPEN_PAREN; | tok->kind = L2_TOK_OPEN_PAREN; | ||||
| break; | break; | ||||
| case ')': | case ')': | ||||
| read_ch(lexer); | |||||
| tok->kind = L2_TOK_CLOSE_PAREN; | tok->kind = L2_TOK_CLOSE_PAREN; | ||||
| break; | break; | ||||
| case '{': | case '{': | ||||
| read_ch(lexer); | |||||
| tok->kind = L2_TOK_OPEN_BRACE; | tok->kind = L2_TOK_OPEN_BRACE; | ||||
| break; | break; | ||||
| case '}': | case '}': | ||||
| read_ch(lexer); | |||||
| tok->kind = L2_TOK_CLOSE_BRACE; | tok->kind = L2_TOK_CLOSE_BRACE; | ||||
| break; | break; | ||||
| case '[': | case '[': | ||||
| read_ch(lexer); | |||||
| tok->kind = L2_TOK_OPEN_BRACKET; | tok->kind = L2_TOK_OPEN_BRACKET; | ||||
| break; | break; | ||||
| case ']': | case ']': | ||||
| read_ch(lexer); | |||||
| tok->kind = L2_TOK_CLOSE_BRACKET; | tok->kind = L2_TOK_CLOSE_BRACKET; | ||||
| break; | break; | ||||
| case ',': | case ',': | ||||
| read_ch(lexer); | |||||
| tok->kind = L2_TOK_COMMA; | tok->kind = L2_TOK_COMMA; | ||||
| break; | break; | ||||
| case '.': | case '.': | ||||
| read_ch(lexer); | |||||
| tok->kind = L2_TOK_PERIOD; | tok->kind = L2_TOK_PERIOD; | ||||
| break; | break; | ||||
| case ':': | case ':': | ||||
| read_ch(lexer); | |||||
| { | { | ||||
| ch = read_ch(lexer); | ch = read_ch(lexer); | ||||
| switch (ch) { | switch (ch) { | ||||
| break; | break; | ||||
| case '"': | case '"': | ||||
| read_ch(lexer); | |||||
| read_string(lexer, tok); | read_string(lexer, tok); | ||||
| break; | break; | ||||
| default: | default: | ||||
| read_ident(lexer, tok); | read_ident(lexer, tok); | ||||
| if (tok->kind != L2_TOK_IDENT) { | |||||
| break; | |||||
| } | |||||
| double num; | |||||
| if (parse_number(tok->v.str, &num) >= 0) { | |||||
| free(tok->v.str); | |||||
| tok->kind = L2_TOK_NUMBER; | |||||
| tok->v.num = num; | |||||
| } | |||||
| break; | break; | ||||
| } | } | ||||
| } | } |
| #include "gen/gen.h" | #include "gen/gen.h" | ||||
| static int parse_expression(struct l2_lexer *lexer, struct l2_generator *gen) { | |||||
| static int parse_expression( | |||||
| struct l2_lexer *lexer, struct l2_generator *gen, struct l2_parse_error *err) { | |||||
| struct l2_token *tok = l2_lexer_peek(lexer, 1); | struct l2_token *tok = l2_lexer_peek(lexer, 1); | ||||
| struct l2_token *tok2 = l2_lexer_peek(lexer, 2); | struct l2_token *tok2 = l2_lexer_peek(lexer, 2); | ||||
| if (tok->kind == L2_TOK_IDENT && tok2->kind == L2_TOK_COLON_EQ) { | if (tok->kind == L2_TOK_IDENT && tok2->kind == L2_TOK_COLON_EQ) { | ||||
| parse_expression(lexer, gen); | |||||
| l2_gen_assignment(gen, &tok->v.str); | |||||
| char *ident = l2_token_extract_str(tok); | |||||
| l2_lexer_get(lexer); // ident | |||||
| l2_lexer_get(lexer); // := | |||||
| if (parse_expression(lexer, gen, err) < 0) { | |||||
| free(ident); | |||||
| return -1; | |||||
| } | |||||
| l2_gen_assignment(gen, &ident); | |||||
| return 0; | return 0; | ||||
| } else if (tok->kind == L2_TOK_NUMBER) { | } else if (tok->kind == L2_TOK_NUMBER) { | ||||
| l2_gen_number(gen, tok->v.num); | l2_gen_number(gen, tok->v.num); | ||||
| l2_lexer_get(lexer); // number | |||||
| return 0; | |||||
| } else if (tok->kind == L2_TOK_IDENT) { | |||||
| char *ident = l2_token_extract_str(tok); | |||||
| l2_lexer_get(lexer); // ident | |||||
| l2_gen_namespace_lookup(gen, &ident); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| l2_parse_err(err, tok, "In expression: Unexpected tokens %s, %s", | |||||
| l2_token_kind_name(tok->kind), l2_token_kind_name(tok2->kind)); | |||||
| return -1; | return -1; | ||||
| } | } | ||||
| int l2_parse_program(struct l2_lexer *lexer, struct l2_generator *gen) { | |||||
| int l2_parse_program( | |||||
| struct l2_lexer *lexer, struct l2_generator *gen, struct l2_parse_error *err) { | |||||
| l2_gen_stack_frame(gen); | l2_gen_stack_frame(gen); | ||||
| while (1) { | while (1) { | ||||
| break; | break; | ||||
| } | } | ||||
| if (parse_expression(lexer, gen) < 0) { | |||||
| if (parse_expression(lexer, gen, err) < 0) { | |||||
| l2_gen_halt(gen); | |||||
| l2_gen_flush(gen); | |||||
| return -1; | return -1; | ||||
| } | } | ||||
| } | } | ||||
| l2_gen_halt(gen); | |||||
| l2_gen_flush(gen); | |||||
| return 0; | return 0; | ||||
| } | } |
| #include "vm/print.h" | |||||
| #include <stdio.h> | |||||
| void l2_vm_print_val(struct l2_vm_value *val) { | |||||
| switch (val->flags & 0x0f) { | |||||
| case L2_VAL_TYPE_NONE: | |||||
| printf("NONE\n"); | |||||
| break; | |||||
| case L2_VAL_TYPE_INTEGER: | |||||
| printf("INTEGER %zi\n", val->integer); | |||||
| break; | |||||
| case L2_VAL_TYPE_REAL: | |||||
| printf("REAL %f\n", val->real); | |||||
| break; | |||||
| 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) { | |||||
| printf(" %zu: %u\n", i, arr->data[i]); | |||||
| } | |||||
| } | |||||
| break; | |||||
| 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) { | |||||
| printf(" %zu: %c\n", i, buf->data[i]); | |||||
| } | |||||
| } | |||||
| 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; | |||||
| } | |||||
| } | |||||
| void l2_vm_print_state(struct l2_vm *vm) { | |||||
| printf("Stack:\n"); | |||||
| l2_vm_print_stack(vm); | |||||
| printf("Heap:\n"); | |||||
| l2_vm_print_heap(vm); | |||||
| printf("NStack:\n"); | |||||
| l2_vm_print_nstack(vm); | |||||
| } | |||||
| void l2_vm_print_heap(struct l2_vm *vm) { | |||||
| for (l2_word i = 0; i < vm->valuessize; ++i) { | |||||
| if (l2_bitset_get(&vm->valueset, i)) { | |||||
| printf(" %u: ", i); | |||||
| l2_vm_print_val(&vm->values[i]); | |||||
| } | |||||
| } | |||||
| } | |||||
| void l2_vm_print_stack(struct l2_vm *vm) { | |||||
| for (l2_word i = 0; i < vm->sptr; ++i) { | |||||
| printf(" %i: %i\n", i, vm->stack[i]); | |||||
| } | |||||
| } | |||||
| void l2_vm_print_nstack(struct l2_vm *vm) { | |||||
| for (l2_word i = 0; i < vm->nsptr; ++i) { | |||||
| printf(" %i: %i\n", i, vm->nstack[i]); | |||||
| } | |||||
| } | |||||
| static void print_op_num(l2_word *ops, size_t opcount, size_t ptr) { | |||||
| if (ptr >= opcount) { | |||||
| printf("<EOF>"); | |||||
| } else { | |||||
| printf("%i", ops[ptr]); | |||||
| } | |||||
| } | |||||
| void l2_vm_print_op(l2_word *ops, size_t opcount, size_t *ptr) { | |||||
| enum l2_opcode opcode = (enum l2_opcode)ops[(*ptr)++]; | |||||
| switch (opcode) { | |||||
| case L2_OP_NOP: | |||||
| printf("NOP\n"); | |||||
| break; | |||||
| case L2_OP_PUSH: | |||||
| printf("PUSH "); | |||||
| print_op_num(ops, opcount, (*ptr)++); | |||||
| printf("\n"); | |||||
| break; | |||||
| case L2_OP_PUSH_2: | |||||
| printf("PUSH2 "); | |||||
| print_op_num(ops, opcount, (*ptr)++); | |||||
| printf(" "); | |||||
| print_op_num(ops, opcount, (*ptr)++); | |||||
| printf("\n"); | |||||
| break; | |||||
| case L2_OP_POP: | |||||
| printf("POP\n"); | |||||
| break; | |||||
| case L2_OP_DUP: | |||||
| printf("DUP\n"); | |||||
| break; | |||||
| case L2_OP_ADD: | |||||
| printf("ADD\n"); | |||||
| break; | |||||
| case L2_OP_CALL: | |||||
| printf("CALL\n"); | |||||
| break; | |||||
| case L2_OP_GEN_STACK_FRAME: | |||||
| printf("GEN_STACK_FRAME\n"); | |||||
| break; | |||||
| case L2_OP_STACK_FRAME_LOOKUP: | |||||
| printf("STACK_FRAME_LOOKUP\n"); | |||||
| break; | |||||
| case L2_OP_STACK_FRAME_SET: | |||||
| printf("STACK_FRAME_SET\n"); | |||||
| break; | |||||
| case L2_OP_RET: | |||||
| printf("RET\n"); | |||||
| break; | |||||
| case L2_OP_ALLOC_INTEGER_32: | |||||
| printf("ALLOC_INTEGER_32\n"); | |||||
| break; | |||||
| case L2_OP_ALLOC_INTEGER_64: | |||||
| printf("ALLOC_INTEGER_64\n"); | |||||
| break; | |||||
| case L2_OP_ALLOC_REAL_32: | |||||
| printf("ALLOC_REAL_32\n"); | |||||
| break; | |||||
| case L2_OP_ALLOC_REAL_64: | |||||
| printf("ALLOC_REAL_64\n"); | |||||
| break; | |||||
| case L2_OP_ALLOC_BUFFER_STATIC: | |||||
| printf("ALLOC_BUFFER_STATIC\n"); | |||||
| break; | |||||
| case L2_OP_ALLOC_BUFFER_ZERO: | |||||
| printf("ALLOC_BUFFER_ZERO\n"); | |||||
| break; | |||||
| case L2_OP_ALLOC_ARRAY: | |||||
| printf("ALLOC_ARRAY\n"); | |||||
| break; | |||||
| case L2_OP_ALLOC_NAMESPACE: | |||||
| printf("ALLOC_NAMESPACE\n"); | |||||
| break; | |||||
| case L2_OP_NAMESPACE_SET: | |||||
| printf("NAMESPACE_SET\n"); | |||||
| break; | |||||
| case L2_OP_HALT: | |||||
| printf("HALT\n"); | |||||
| break; | |||||
| } | |||||
| } | |||||
| void l2_vm_print_bytecode(l2_word *ops, size_t opcount) { | |||||
| size_t ptr = 0; | |||||
| while (ptr < opcount) { | |||||
| l2_vm_print_op(ops, opcount, &ptr); | |||||
| } | |||||
| } |
| return d; | return d; | ||||
| } | } | ||||
| static void gc_mark_array(struct l2_vm *vm, struct l2_vm_value *val); | |||||
| static void gc_mark_namespace(struct l2_vm *vm, struct l2_vm_value *val); | |||||
| static void gc_mark(struct l2_vm *vm, l2_word id) { | static void gc_mark(struct l2_vm *vm, l2_word id) { | ||||
| printf("GC MARK %i\n", id); | printf("GC MARK %i\n", id); | ||||
| struct l2_vm_value *val = &vm->values[id]; | struct l2_vm_value *val = &vm->values[id]; | ||||
| val->flags |= L2_VAL_MARKED; | val->flags |= L2_VAL_MARKED; | ||||
| int typ = val->flags & 0x0f; | |||||
| int typ = l2_vm_value_type(*val); | |||||
| 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; | |||||
| for (size_t i = 0; i < arr->len; ++i) { | |||||
| gc_mark(vm, arr->data[i]); | |||||
| } | |||||
| gc_mark_array(vm, val); | |||||
| } else if (typ == L2_VAL_TYPE_NAMESPACE) { | } else if (typ == L2_VAL_TYPE_NAMESPACE) { | ||||
| if (val->data == NULL) { | |||||
| return; | |||||
| } | |||||
| gc_mark_namespace(vm, val); | |||||
| } | |||||
| } | |||||
| 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; | |||||
| } | |||||
| static void gc_mark_array(struct l2_vm *vm, struct l2_vm_value *val) { | |||||
| if (val->data == NULL) { | |||||
| return; | |||||
| } | |||||
| gc_mark(vm, ns->data[ns->size + i]); | |||||
| 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]); | |||||
| } | |||||
| } | |||||
| static void gc_mark_namespace(struct l2_vm *vm, struct l2_vm_value *val) { | |||||
| 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]); | |||||
| } | } | ||||
| } | } | ||||
| struct l2_vm_value *val = &vm->values[id]; | struct l2_vm_value *val = &vm->values[id]; | ||||
| l2_bitset_unset(&vm->valueset, id); | l2_bitset_unset(&vm->valueset, id); | ||||
| int typ = val->flags & 0x0f; | |||||
| int typ = l2_vm_value_type(*val); | |||||
| if (typ == L2_VAL_TYPE_ARRAY || typ == L2_VAL_TYPE_BUFFER || typ == L2_VAL_TYPE_NAMESPACE) { | 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 | ||||
| vm->opcount = opcount; | vm->opcount = opcount; | ||||
| vm->iptr = 0; | vm->iptr = 0; | ||||
| vm->sptr = 0; | vm->sptr = 0; | ||||
| vm->nsptr = 0; | |||||
| vm->values = NULL; | vm->values = NULL; | ||||
| vm->valuessize = 0; | vm->valuessize = 0; | ||||
| } | } | ||||
| size_t l2_vm_gc(struct l2_vm *vm) { | size_t l2_vm_gc(struct l2_vm *vm) { | ||||
| for (l2_word sptr = 0; sptr < vm->sptr; ++sptr) { | |||||
| if (vm->stackflags[sptr]) { | |||||
| gc_mark(vm, vm->stack[sptr]); | |||||
| } | |||||
| for (l2_word nsptr = 0; nsptr < vm->nsptr; ++nsptr) { | |||||
| struct l2_vm_value *val = &vm->values[vm->nstack[nsptr]]; | |||||
| val->flags |= L2_VAL_MARKED; | |||||
| gc_mark_namespace(vm, val); | |||||
| } | } | ||||
| return gc_sweep(vm); | return gc_sweep(vm); | ||||
| case L2_OP_PUSH: | case L2_OP_PUSH: | ||||
| vm->stack[vm->sptr] = vm->ops[vm->iptr]; | vm->stack[vm->sptr] = vm->ops[vm->iptr]; | ||||
| vm->stackflags[vm->sptr] = 0; | |||||
| vm->sptr += 1; | vm->sptr += 1; | ||||
| vm->iptr += 1; | vm->iptr += 1; | ||||
| break; | break; | ||||
| case L2_OP_PUSH_2: | case L2_OP_PUSH_2: | ||||
| vm->stack[vm->sptr] = vm->ops[vm->iptr]; | vm->stack[vm->sptr] = vm->ops[vm->iptr]; | ||||
| vm->stack[vm->sptr + 1] = vm->ops[vm->iptr + 1]; | vm->stack[vm->sptr + 1] = vm->ops[vm->iptr + 1]; | ||||
| vm->stackflags[vm->sptr] = 0; | |||||
| vm->stackflags[vm->sptr + 1] = 0; | |||||
| vm->sptr += 2; | vm->sptr += 2; | ||||
| vm->iptr += 2; | vm->iptr += 2; | ||||
| break; | break; | ||||
| case L2_OP_DUP: | case L2_OP_DUP: | ||||
| vm->stack[vm->sptr] = vm->ops[vm->sptr - 1]; | vm->stack[vm->sptr] = vm->ops[vm->sptr - 1]; | ||||
| vm->stackflags[vm->sptr] = 0; | |||||
| vm->sptr += 1; | vm->sptr += 1; | ||||
| break; | break; | ||||
| case L2_OP_ADD: | case L2_OP_ADD: | ||||
| vm->stack[vm->sptr - 2] += vm->stack[vm->sptr - 1]; | vm->stack[vm->sptr - 2] += vm->stack[vm->sptr - 1]; | ||||
| vm->stackflags[vm->sptr - 2] = 0; | |||||
| vm->sptr -= 1; | vm->sptr -= 1; | ||||
| break; | 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->stackflags[vm->sptr - 1] = 0; | |||||
| vm->iptr = word; | vm->iptr = word; | ||||
| break; | break; | ||||
| case L2_OP_GEN_STACK_FRAME: | |||||
| word = alloc_val(vm); | |||||
| vm->values[word].flags = L2_VAL_TYPE_NAMESPACE; | |||||
| vm->values[word].data = NULL; // Will be allocated on first insert | |||||
| vm->nstack[vm->nsptr] = word; | |||||
| vm->nsptr += 1; | |||||
| break; | |||||
| case L2_OP_STACK_FRAME_LOOKUP: | |||||
| { | |||||
| l2_word key = vm->stack[vm->sptr - 1]; | |||||
| struct l2_vm_value *ns = &vm->values[vm->nstack[vm->nsptr - 1]]; | |||||
| vm->stack[vm->sptr - 1] = l2_vm_namespace_get(ns, key); | |||||
| } | |||||
| break; | |||||
| case L2_OP_STACK_FRAME_SET: | |||||
| { | |||||
| l2_word key = vm->stack[vm->sptr - 1]; | |||||
| l2_word val = vm->stack[vm->sptr - 2]; | |||||
| struct l2_vm_value *ns = &vm->values[vm->nstack[vm->nsptr - 1]]; | |||||
| l2_vm_namespace_set(ns, key, val); | |||||
| vm->sptr -= 1; | |||||
| } | |||||
| break; | |||||
| case L2_OP_RET: | case L2_OP_RET: | ||||
| vm->nsptr -= 1; | |||||
| vm->iptr = vm->stack[vm->sptr - 1]; | vm->iptr = vm->stack[vm->sptr - 1]; | ||||
| vm->sptr -= 1; | vm->sptr -= 1; | ||||
| vm->nsptr -= 1; | |||||
| break; | break; | ||||
| case L2_OP_ALLOC_INTEGER_32: | case L2_OP_ALLOC_INTEGER_32: | ||||
| vm->values[word].flags = L2_VAL_TYPE_INTEGER; | vm->values[word].flags = L2_VAL_TYPE_INTEGER; | ||||
| vm->values[word].integer = vm->stack[--vm->sptr]; | vm->values[word].integer = vm->stack[--vm->sptr]; | ||||
| vm->stack[vm->sptr] = word; | vm->stack[vm->sptr] = word; | ||||
| vm->stackflags[vm->sptr] = 1; | |||||
| vm->sptr += 1; | vm->sptr += 1; | ||||
| break; | break; | ||||
| (uint64_t)vm->stack[vm->sptr - 2]); | (uint64_t)vm->stack[vm->sptr - 2]); | ||||
| vm->sptr -= 2; | vm->sptr -= 2; | ||||
| vm->stack[vm->sptr] = word; | vm->stack[vm->sptr] = word; | ||||
| vm->stackflags[vm->sptr] = 1; | |||||
| vm->sptr += 1; | vm->sptr += 1; | ||||
| break; | break; | ||||
| vm->values[word].flags = L2_VAL_TYPE_REAL; | vm->values[word].flags = L2_VAL_TYPE_REAL; | ||||
| vm->values[word].real = u32_to_float(vm->stack[--vm->sptr]); | vm->values[word].real = u32_to_float(vm->stack[--vm->sptr]); | ||||
| vm->stack[vm->sptr] = word; | vm->stack[vm->sptr] = word; | ||||
| vm->stackflags[vm->sptr] = 1; | |||||
| vm->sptr += 1; | vm->sptr += 1; | ||||
| break; | break; | ||||
| vm->values[word].real = u32s_to_double(vm->stack[vm->sptr - 1], vm->stack[vm->sptr - 2]); | vm->values[word].real = u32s_to_double(vm->stack[vm->sptr - 1], vm->stack[vm->sptr - 2]); | ||||
| vm->sptr -= 2; | vm->sptr -= 2; | ||||
| vm->stack[vm->sptr] = word; | vm->stack[vm->sptr] = word; | ||||
| vm->stackflags[vm->sptr] = 1; | |||||
| vm->sptr += 1; | vm->sptr += 1; | ||||
| break; | break; | ||||
| (unsigned char *)vm->values[word].data + sizeof(struct l2_vm_buffer), | (unsigned char *)vm->values[word].data + sizeof(struct l2_vm_buffer), | ||||
| vm->ops + offset, length); | vm->ops + offset, length); | ||||
| vm->stack[vm->sptr] = word; | vm->stack[vm->sptr] = word; | ||||
| vm->stackflags[vm->sptr] = 1; | |||||
| vm->sptr += 1; | vm->sptr += 1; | ||||
| } | } | ||||
| break; | break; | ||||
| vm->values[word].data = calloc(1, sizeof(struct l2_vm_buffer) + length); | vm->values[word].data = calloc(1, sizeof(struct l2_vm_buffer) + length); | ||||
| ((struct l2_vm_buffer *)vm->values[word].data)->len = length; | ((struct l2_vm_buffer *)vm->values[word].data)->len = length; | ||||
| vm->stack[vm->sptr] = word; | vm->stack[vm->sptr] = word; | ||||
| vm->stackflags[vm->sptr] = 1; | |||||
| vm->sptr += 1; | vm->sptr += 1; | ||||
| } | } | ||||
| break; | break; | ||||
| vm->values[word].flags = L2_VAL_TYPE_ARRAY; | vm->values[word].flags = L2_VAL_TYPE_ARRAY; | ||||
| vm->values[word].data = NULL; // Will be allocated on first insert | 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->sptr += 1; | vm->sptr += 1; | ||||
| break; | break; | ||||
| vm->values[word].flags = L2_VAL_TYPE_NAMESPACE; | vm->values[word].flags = L2_VAL_TYPE_NAMESPACE; | ||||
| vm->values[word].data = NULL; // Will be allocated on first insert | 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->sptr += 1; | vm->sptr += 1; | ||||
| break; | break; | ||||
| files := src ../lib | files := src ../lib | ||||
| includes := snow ../include/lang2 | includes := snow ../include/lang2 | ||||
| defines := SNOW_ENABLED | defines := SNOW_ENABLED | ||||
| sanitizers := address | |||||
| cflags := -g -O3 | |||||
| #sanitizers := address undefined | |||||
| cflags := -g |
| #include "parse/parse.h" | |||||
| #include "vm/vm.h" | |||||
| #include "vm/print.h" | |||||
| #include <stdio.h> | |||||
| #include <snow/snow.h> | |||||
| static struct l2_lexer lex; | |||||
| static struct l2_io_mem_reader r; | |||||
| static struct l2_generator gen; | |||||
| static struct l2_io_mem_writer w; | |||||
| static struct l2_vm vm; | |||||
| static struct l2_parse_error err; | |||||
| static struct l2_vm_value *var_lookup(const char *name) { | |||||
| l2_word atom_id = l2_strset_get(&gen.atoms, name); | |||||
| l2_word id = l2_vm_namespace_get(&vm.values[vm.nstack[0]], atom_id); | |||||
| return &vm.values[id]; | |||||
| } | |||||
| static void init(const char *str) { | |||||
| r.r.read = l2_io_mem_read; | |||||
| r.idx = 0; | |||||
| r.len = strlen(str); | |||||
| r.mem = str; | |||||
| l2_lexer_init(&lex, &r.r); | |||||
| w.w.write = l2_io_mem_write; | |||||
| w.len = 0; | |||||
| w.mem = NULL; | |||||
| l2_gen_init(&gen, (struct l2_io_writer *)&w); | |||||
| } | |||||
| static void done() { | |||||
| l2_gen_free(&gen); | |||||
| free(w.mem); | |||||
| } | |||||
| describe(parse) { | |||||
| test("parse program") { | |||||
| init("foo := 10"); | |||||
| defer(done()); | |||||
| assert(l2_parse_program(&lex, &gen, &err) >= 0); | |||||
| l2_vm_init(&vm, (l2_word *)w.mem, w.len / sizeof(l2_word)); | |||||
| l2_vm_run(&vm); | |||||
| assert(l2_vm_value_type(*var_lookup("foo")) == L2_VAL_TYPE_REAL); | |||||
| assert(var_lookup("foo")->real == 10); | |||||
| } | |||||
| } |