diff options
Diffstat (limited to 'uscript/vm.c')
| -rw-r--r-- | uscript/vm.c | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/uscript/vm.c b/uscript/vm.c new file mode 100644 index 0000000..f9f1fe6 --- /dev/null +++ b/uscript/vm.c @@ -0,0 +1,306 @@ +#include "vm.h" + +#include <math.h> +#include <string.h> + +#include "dyn_arr.h" +#include "us_debug.h" +#include "uscript.h" + +struct vm vm; + +void init_vm(void) +{ + vm.objs = da_create(struct us_val, 128); + vm.cf = vm.cf_stack; + vm.stacktop = vm.stack; +} + +void deinit_vm(void) +{ + for (int i = 0; i < da_len(vm.objs); i++) { + free_val(vm.objs[i]); + } + da_clear(vm.objs); // not needed, but makes me feel better :) + da_free(vm.objs); +} + +static +bool as_bool(struct us_val v) +{ + if (v.type == VAL_ZILCH) + return false; + if (v.type == VAL_BOOL) + return get_bool(v); + return true; +} + +static +struct us_str *concat(struct us_val a, struct us_val b) +{ + int a_len; + char *a_str = val_to_str(a, &a_len); + int b_len; + char *b_str = val_to_str(b, &b_len); + + int len = a_len + b_len; + char *chars = mem_alloc(sizeof(char) * (len + 1)); + memcpy(chars, a_str, a_len); + memcpy(chars + a_len, b_str, b_len); + chars[len] = '\0'; + + mem_free(a_str); + mem_free(b_str); + + return take_str(chars, len); +} + +static +u16 read_short(struct us_proto *proto, int *i) +{ + return (u16)(proto->bytecode[++*i] << 8) | proto->bytecode[++*i]; +} + +static +void close_upvals(struct us_val *to) +{ + struct us_upval *upval = vm.open_upvals; + while (upval && upval->loc > to) { + upval->closed = *upval->loc; + upval->loc = &upval->closed; + upval = upval->next; + } + vm.open_upvals = upval; +} + +void us_exec(struct us_func *func) +{ +#define read_byte() (func->proto->bytecode[++i]) +#define read_const() (func->proto->constants[read_byte()]) + vm.cf++; + vm.cf->func = func; + vm.cf->stackbot = vm.stacktop - func->proto->argc; + + for (int i = 0; i < da_len(func->proto->bytecode); i++) { + enum bytecode instruction = func->proto->bytecode[i]; + // putc('>', stderr); + // for (struct us_val *val = vm.stack; val < vm.stacktop; val++) { + // char *val_str = val_to_str(*val, NULL); + // if (val == vm.cf->stackbot - 1) + // fprintf(stderr, " %s >", val_str); + // else + // fprintf(stderr, " %s |", val_str); + // mem_free(val_str); + // } + // putc('\n', stderr); + // putc('>', stderr); + // print_instruction(func->proto, i); + + switch (instruction) { + case BC_LOAD: + vm_push(read_const()); + break; + case BC_LOAD_FUNC: { + struct us_proto *proto = get_proto(read_const()); + + struct us_func *new_func = create_func(proto); + + for (int j = 0; j < proto->upvalc; j++) { + u8 is_local = read_byte(); + u8 index = read_byte(); + + if (is_local) { + struct us_upval *upval = create_upval( + vm.cf->stackbot + index + ); + upval->next = vm.open_upvals; + vm.open_upvals = upval; + + new_func->upvals[j] = upval; + } else { + new_func->upvals[j] = + func->upvals[index]; + } + } + + vm_push(wrap_func(new_func)); + break; + } + case BC_SMALL_INT: + vm_push(create_num(read_byte())); + break; + case BC_FALSE: vm_push(create_bool(false)); break; + case BC_TRUE: vm_push(create_bool(true)); break; + case BC_ZILCH: vm_push(create_zilch()); break; + case BC_SET_LOCAL: + vm.cf->stackbot[read_byte()] = vm_peek(); + break; + case BC_GET_LOCAL: + vm_push(vm.cf->stackbot[read_byte()]); + break; + case BC_GET_UPVAL: + vm_push(*func->upvals[read_byte()]->loc); + break; + case BC_SET_UPVAL: + *func->upvals[read_byte()]->loc = vm_peek(); + break; + case BC_POP_UPVAL: + close_upvals(vm.stacktop - 1); + vm_pop(); + break; + case BC_POP: vm_pop(); break; + case BC_ADD: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + if (b.type != VAL_NUM || a.type != VAL_NUM) + log_fatal(1, "Invalid operands"); + vm_push(create_num(get_num(a) + get_num(b))); + break; + } + case BC_SUB: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + if (b.type != VAL_NUM || a.type != VAL_NUM) + log_fatal(1, "Invalid operands"); + vm_push(create_num(get_num(a) - get_num(b))); + break; + } + case BC_MULT: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + if (b.type != VAL_NUM || a.type != VAL_NUM) + log_fatal(1, "Invalid operands"); + vm_push(create_num(get_num(a) * get_num(b))); + break; + } + case BC_DIV: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + if (b.type != VAL_NUM || a.type != VAL_NUM) + log_fatal(1, "Invalid operands"); + vm_push(create_num(get_num(a) / get_num(b))); + break; + } + case BC_MOD: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + if (b.type != VAL_NUM || a.type != VAL_NUM) + log_fatal(1, "Invalid operands"); + vm_push(create_num(fmod(get_num(a), get_num(b)))); + break; + } + case BC_GT: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + if (b.type != VAL_NUM || a.type != VAL_NUM) + log_fatal(1, "Invalid operands"); + vm_push(create_bool(get_num(a) > get_num(b))); + break; + } + case BC_LT: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + if (b.type != VAL_NUM || a.type != VAL_NUM) + log_fatal(1, "Invalid operands"); + vm_push(create_bool(get_num(a) < get_num(b))); + break; + } + case BC_GTE: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + if (b.type != VAL_NUM || a.type != VAL_NUM) + log_fatal(1, "Invalid operands"); + vm_push(create_bool(get_num(a) >= get_num(b))); + break; + } + case BC_LTE: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + if (b.type != VAL_NUM || a.type != VAL_NUM) + log_fatal(1, "Invalid operands"); + vm_push(create_bool(get_num(a) <= get_num(b))); + break; + } + case BC_NEG: { + struct us_val a = vm_pop(); + if (a.type != VAL_NUM) + log_fatal(1, "Invalid operand"); + vm_push(create_num(-get_num(a))); + break; + } + case BC_NOT: { + bool negated = !as_bool(vm_pop()); + vm_push(create_bool(negated)); + break; + } + case BC_CONCAT: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + vm_push(wrap_str(concat(a, b))); + break; + } + case BC_EQL: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + vm_push(create_bool(vals_eql(a, b))); + break; + } + case BC_NEQL: { + struct us_val b = vm_pop(); + struct us_val a = vm_pop(); + vm_push(create_bool(!vals_eql(a, b))); + break; + } + case BC_FALSEY_JMP: { + u16 jmp = read_short(func->proto, &i); + if (as_bool(vm_pop())) + break; + i += jmp; + break; + } + case BC_JMP: + i += read_short(func->proto, &i); + break; + case BC_LOOP: + i -= read_short(func->proto, &i); + break; + case BC_PRINT: { + char *str = val_to_str(vm_pop(), NULL); + olog(str); + mem_free(str); + break; + } + case BC_CALL: { + int argc = read_byte(); + struct us_val callee = vm.stacktop[-argc - 1]; + if (callee.type != VAL_FUNC) + log_fatal(1, "can only call functions"); + struct us_func *func = get_func(callee); + if (argc != func->proto->argc) { + log_fatal( + 1, + "wrong number of arguments to '%s()' (%d/%d)", + func->proto->name->chars, + argc, + func->proto->argc + ); + } + us_exec(func); + break; + } + case BC_RET: { + struct us_val ret_val = vm_pop(); + + close_upvals(vm.cf->stackbot - 1); + + vm.stacktop = vm.cf->stackbot - 1; + vm.cf--; + vm_push(ret_val); + return; + } + default: + log_fatal(1, "unhandled instruction %d", instruction); + break; + } + } +} |
