summaryrefslogtreecommitdiff
path: root/uscript/vm.c
diff options
context:
space:
mode:
authoriamcheeseman <[email protected]>2026-04-06 17:04:05 -0400
committeriamcheeseman <[email protected]>2026-04-06 17:06:53 -0400
commit957c64c7b8b5e98d8a03dd84c7e27e7991fb9dbc (patch)
treef5fc230703791cee8d8e7851fb87eaef07ae63a2 /uscript/vm.c
Initial commit
Diffstat (limited to 'uscript/vm.c')
-rw-r--r--uscript/vm.c306
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;
+ }
+ }
+}