#include "vm.h" #include #include #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.gstack = da_create(struct global, 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); da_free(vm.gstack); } 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; } static void index_val(struct us_val idx_val, struct us_val idxee_val) { switch (idxee_val.type) { case VAL_ARR: { if (idx_val.type != VAL_NUM) log_fatal(1, "arrays must be indexed by numbers"); int idx = (int)get_num(idx_val); struct us_arr *arr = get_arr(idxee_val); if (idx < 0) idx += da_len(arr->e); if (idx < 0 || idx >= da_len(arr->e)) { log_fatal( 1, "index out of range (%d/%d)", idx, da_len(arr->e) - 1 ); } vm_push(arr->e[idx]); break; } case VAL_STR: { if (idx_val.type != VAL_NUM) log_fatal(1, "strings must be indexed by numbers"); int idx = (int)get_num(idx_val); struct us_str *str = get_str(idxee_val); if (idx < 0) idx += str->len; if (idx < 0 || (size_t)idx >= str->len) { log_fatal( 1, "index out of range (%d/%d)", idx, str->len - 1 ); } vm_push(wrap_str(copy_str(&str->chars[idx], 1))); break; } default: log_fatal(1, "cannot index that value"); break; } } static void call_val(struct us_val callee, int argc) { switch (callee.type) { case VAL_FUNC: { 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 VAL_CFUNC: { struct us_cfunc *cfunc = get_cfunc(callee); if (argc != cfunc->argc) { log_fatal( 1, "wrong number of arguments to '%s()' (%d/%d)", cfunc->name->chars, argc, cfunc->argc ); } vm.cf++; vm.cf->func = wrap_cfunc(cfunc); vm.cf->stackbot = vm.stacktop - argc; cfunc->c(argc); struct us_val ret_val = vm_pop(); vm.stacktop = vm.cf->stackbot - 1; vm.cf--; vm_push(ret_val); break; } default: log_fatal(1, "cannot call that value"); break; } } 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 = wrap_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_ARR: { struct us_arr *arr = create_arr(); u8 arr_len = read_byte(); // elements are in reverse order, so we add them in the // correct order, and pop them all off at the end for (u8 i = 0; i < arr_len; i++) { da_append( struct us_val, &arr->e, *(vm.stacktop - arr_len + i) ); } vm.stacktop -= arr_len; vm_push(wrap_arr(arr)); break; } case BC_GET_LOCAL: vm_push(vm.cf->stackbot[read_byte()]); break; case BC_SET_LOCAL: vm.cf->stackbot[read_byte()] = vm_peek(); break; case BC_GET_GLOBAL: vm_push(vm.gstack[read_short(func->proto, &i)].val); break; case BC_SET_GLOBAL: vm.gstack[read_short(func->proto, &i)].val = vm_peek(); 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_GET_INDEX: { struct us_val idx_val = vm_pop(); struct us_val idxee_val = vm_pop(); index_val(idx_val, idxee_val); break; } case BC_SET_INDEX: { struct us_val set_val = vm_pop(); struct us_val idx_val = vm_pop(); struct us_val arr_val = vm_pop(); if (idx_val.type != VAL_NUM) log_fatal(1, "index type must be number"); if (arr_val.type != VAL_ARR) log_fatal(1, "only arrays can be indexed"); int idx = (int)get_num(idx_val); struct us_arr *arr = get_arr(arr_val); if (idx < 0) idx += da_len(arr->e); if (idx < 0 || idx >= da_len(arr->e)) { log_fatal( 1, "index out of range (%d/%d)", idx, da_len(arr->e) - 1 ); } arr->e[idx] = set_val; vm_push(set_val); break; } case BC_POP: vm_pop(); break; case BC_POP_UPVAL: close_upvals(vm.stacktop - 1); 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_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_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_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_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_CONCAT: { struct us_val b = vm_pop(); struct us_val a = vm_pop(); vm_push(wrap_str(concat(a, b))); break; } case BC_JMP: i += read_short(func->proto, &i); break; case BC_FALSEY_JMP: { u16 jmp = read_short(func->proto, &i); if (as_bool(vm_pop())) break; i += jmp; break; } case BC_LOOP: i -= read_short(func->proto, &i); break; case BC_CALL: { int argc = read_byte(); struct us_val callee = vm.stacktop[-argc - 1]; call_val(callee, argc); 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; } } } int declare_global(struct us_str *name) { int idx = da_len(vm.gstack); struct global global; global.name = name; global.val = create_zilch(); da_append(struct global, &vm.gstack, global); return idx; }