conditional jumps and such in VM
authorcassowarii <cassowary@cassowary.me>
Sun, 28 Jun 2026 19:13:23 +0000 (12:13 -0700)
committercassowarii <cassowary@cassowary.me>
Sun, 28 Jun 2026 19:13:23 +0000 (12:13 -0700)
src/data/hashtable.c
src/data/operations.c [new file with mode: 0644]
src/data/operations.h [new file with mode: 0644]
src/data/value.c
src/data/value.h
src/main.c
src/vm/bytecode.h
src/vm/exec.c
src/vm/exec.h

index 89244f6..6cef9a3 100644 (file)
@@ -207,7 +207,7 @@ static hashentry *find_entry_in_array(hashentry *entries, usize length, hV *key)
     move ++;
   }
   usize index = start % length;
-  while (!sbV_eq(&entries[index].key, key) && entries[index].key.type != IT_NOTHING) {
+  while (!sbV_c_eq(&entries[index].key, key) && entries[index].key.type != IT_NOTHING) {
     index += move;
     index %= length;
   }
diff --git a/src/data/operations.c b/src/data/operations.c
new file mode 100644 (file)
index 0000000..aca8317
--- /dev/null
@@ -0,0 +1,59 @@
+#include "data/operations.h"
+
+#include "data/integer.h"
+
+hV sbV_add(const hV *a, const hV *b) {
+  if (a->type == IT_INTEGER && b->type == IT_INTEGER) {
+    return sbV_int(sbInteger_sum(a->integer, b->integer));
+  } else {
+    PANIC("todo");
+  }
+}
+
+hV sbV_sub(const hV *a, const hV *b) {
+  if (a->type == IT_INTEGER && b->type == IT_INTEGER) {
+    return sbV_int(sbInteger_diff(a->integer, b->integer));
+  } else {
+    PANIC("todo");
+  }
+}
+
+hV sbV_mul(const hV *a, const hV *b) {
+  if (a->type == IT_INTEGER && b->type == IT_INTEGER) {
+    return sbV_int(sbInteger_mul(a->integer, b->integer));
+  } else {
+    PANIC("todo");
+  }
+}
+
+hV sbV_floordiv(const hV *a, const hV *b) {
+  if (a->type == IT_INTEGER && b->type == IT_INTEGER) {
+    return sbV_int(sbInteger_floordiv(a->integer, b->integer));
+  } else {
+    PANIC("todo");
+  }
+}
+
+hV sbV_incr(const hV *a) {
+  if (a->type == IT_INTEGER) {
+    return sbV_int(a->integer + 1);
+  } else {
+    PANIC("todo");
+  }
+}
+
+hV sbV_decr(const hV *a) {
+  if (a->type == IT_INTEGER) {
+    return sbV_int(a->integer - 1);
+  } else {
+    PANIC("todo");
+  }
+}
+
+hV sbV_eq(const hV *a, const hV *b) {
+  if (sbV_c_eq(a, b)) {
+    return HVBOOL(TRUE);
+  } else {
+    return HVBOOL(FALSE);
+  }
+}
diff --git a/src/data/operations.h b/src/data/operations.h
new file mode 100644 (file)
index 0000000..ed97ea2
--- /dev/null
@@ -0,0 +1,15 @@
+#include "common.h"
+
+hV sbV_add(const hV *a, const hV *b);
+
+hV sbV_sub(const hV *a, const hV *b);
+
+hV sbV_mul(const hV *a, const hV *b);
+
+hV sbV_floordiv(const hV *a, const hV *b);
+
+hV sbV_incr(const hV *a);
+
+hV sbV_decr(const hV *a);
+
+hV sbV_eq(const hV *a, const hV *b);
index 68f40d0..8fdb686 100644 (file)
@@ -51,7 +51,7 @@ hV sbV_int(hInteger i) {
   };
 }
 
-flag sbV_eq(const hV *a, const hV *b) {
+flag sbV_c_eq(const hV *a, const hV *b) {
   if (a->type != b->type) return FALSE;
   if (a->type == ITX_TOMBSTONE || b->type == ITX_TOMBSTONE) return FALSE;
   if (a->type == IT_NOTHING || b->type == IT_NOTHING) return FALSE;
@@ -72,6 +72,9 @@ flag sbV_eq(const hV *a, const hV *b) {
     case IT_STRING:
       /* strings can be compared for equality character by character */
       return sbString_eq(a->string, b->string);
+    case IT_INTEGER:
+      /* TODO: we need to handle bigints properly. for now, just check int values normally */
+      return a->integer == b->integer;
     case IT_HASH:
       /* hashes should (but currently don't) check that they are the same by value.
        * right now, we'll just see if they point to the same hash ("is"). */
@@ -81,6 +84,13 @@ flag sbV_eq(const hV *a, const hV *b) {
   }
 }
 
+flag sbV_c_falsy(const hV *a) {
+  /* only nil and boolean false are false */
+  if (a->type == IT_NIL) return TRUE;
+  if (a->type == IT_BOOLEAN && !a->boolean) return TRUE;
+  return FALSE;
+}
+
 void sbV_retain(const hV *a) {
   /* for some classes (e.g. nil, float), we don't need to retain them at all
    * because they are trivially copiable. some classes (integer, string) need
index 94a032d..7526178 100644 (file)
@@ -5,6 +5,7 @@
 
 #define HVINT(n) ((hV) { .type = IT_INTEGER, .integer = n })
 #define HVSTR(s) ((hV) { .type = IT_STRING, .string = OBJSL(s) })
+#define HVBOOL(b) ((hV) { .type = IT_BOOLEAN, .boolean = b })
 
 typedef u64 hHash;
 typedef u64 hString;
@@ -45,8 +46,10 @@ hV sbV_symbol(hSymbol sym);
 hV sbV_float(double fl);
 hV sbV_hash(hHash hash);
 hV sbV_int(hInteger i);
+hV sbV_boolean(flag b);
 
-flag sbV_eq(const hV *a, const hV *b);
+flag sbV_c_eq(const hV *a, const hV *b);
+flag sbV_c_falsy(const hV *a);
 
 void sbV_retain(const hV *a);
 void sbV_release(const hV *a);
index d4d09e9..fd2caf7 100644 (file)
@@ -42,19 +42,46 @@ int main(int argc, char **argv) {
 
     sbVmPartialBlock pb = sbVmBlock_create(4096, 4096);
 
-    u8 code[] = {
-      BC_LD_CONST, 0x00,
-      BC_LD_CONST, 0x01,
-      BC_OP_ADD,
+    u8 code1[] = {
+      BC_ALLOC_VARS, 0x02,           /* 0 1 */
+      BC_LD_IMM, 0x00,    /* var1 = 0   2 3 */
+      BC_ST_VAR, 0x01,    /* ...        4 5 */
+      BC_LD_CONST, 0x01,  /* var0 = 9   6 7 */
+      BC_ST_VAR, 0x00,    /* ...        8 9 */
+      BC_LD_CONST, 0x00,  /* (LABEL) var1 += 5 ; 10 11 */
+      BC_LD_VAR, 0x01,    /* ... */
+      BC_OP_ADD,          /* ... */
+      BC_ST_VAR, 0x01,    /* ... */
+      BC_LD_VAR, 0x00,    /* var0 = var0 - 1 */
+      BC_OP_DECR,         /* ... */
+      BC_ST_VAR, 0x00,    /* ... */
+      BC_LD_VAR, 0x00,    /* if var0 != 0 */
+      BC_LD_IMM, 0x00,    /* ... */
+      BC_OP_EQ,           /* ... */
+      BC_JF, 0x0A,        /* ...then goto LABEL */
+      BC_LD_VAR, 0x01,    /* return var1 */
+      BC_LD_IMM, 0x01,    /* ... */
       BC_HALT,
     };
 
-    sbVmBlock_write_code(&pb, code, sizeof(code));
-
     sbVmBlock_add_constant(&pb, &HVINT(5));
-    sbVmBlock_add_constant(&pb, &HVINT(11));
+    sbVmBlock_add_constant(&pb, &HVINT(9));
+
+    sbVmBlock_write_code(&pb, code1, sizeof(code1));
+    sbVmProgram_add_block(&pm, &pb);
+    sbVmBlock_reset(&pb);
+
+    u8 code2[] = {
+      BC_LD_CONST, 0x00,
+      BC_OP_ADD,
+      BC_RET,
+    };
+
+    sbVmBlock_add_constant(&pb, &HVINT(7));
 
+    sbVmBlock_write_code(&pb, code2, sizeof(code2));
     sbVmProgram_add_block(&pm, &pb);
+    sbVmBlock_reset(&pb);
 
     sbVm_execute(&vm, &pm);
 
index 6f3fe4f..299434d 100644 (file)
@@ -4,36 +4,42 @@
 #include "global.h"
 
 typedef enum sbOpcode {
-  BC_NOP = 0,
-  BC_LD_IMM,    /* push immediate value to stack */
-  BC_LD_CONST,  /* push constants[index] to stack */
-  BC_LD_CTX,    /* look up key in context object and push result */
-  BC_LD_VAR,    /* push value onto stack from variable */
-  BC_LD_UPVAL,  /* push value onto stack from closure */
-  BC_ST_VAR,    /* pop value from stack into variable */
-  BC_ST_UPVAL,  /* pop value from stack into closure */
-  BC_POP,       /* pop value from stack */
-  BC_CALL,      /* push return address and jump to new block */
-  BC_JMP,       /* local jump inside current block */
-  BC_RET,       /* return to calling block */
-  BC_SEND,      /* like call, but jump to object method (maybe this is the same as call?) */
-  BC_OP_ADD,
-  BC_OP_SUB,
-  BC_OP_MUL,
-  BC_OP_DIV = 0x10,
-  BC_OP_FLDIV,
-  BC_OP_NEG,
-  BC_OP_MOD,
-  BC_OP_POW,
-  BC_OP_DEREF,
-  BC_ALLOC_VARS,
-  BC_LIST_NEW,
-  BC_HASH_NEW,
-  BC_LIST_ADD,
-  BC_HASH_ADD,
-  BC_LONG_NUM = 253,
-  BC_VLONG_NUM = 254,
-  BC_HALT = 255,
+  BC_NOP        = 0x00, // skip me
+  BC_LD_IMM,            // push immediate value to stack
+  BC_LD_CONST,          // push constants[index] to stack
+  BC_LD_CTX,            // look up key in context object and push result
+  BC_LD_VAR,            // push value onto stack from variable
+  BC_LD_UPVAL,          // push value onto stack from closure
+  BC_ST_VAR,            // pop value from stack into variable
+  BC_ST_UPVAL,          // pop value from stack into closure
+  BC_POP        = 0x08, // pop value from stack
+  BC_NPOP,              // pop value from stack
+  BC_CALL,              // push return address and jump to new block
+  BC_RET,               // return to calling block
+  BC_JMP,               // unconditional local jump inside current block
+  BC_JT,                // jump if true
+  BC_JF,                // jump if false
+  BC_SEND,              // like call, but jump to object method
+  BC_OP_EQ      = 0x10, // ==
+  BC_OP_NOT,            // boolean not
+  BC_OP_ADD,            // add
+  BC_OP_SUB,            // subtract
+  BC_OP_MUL,            // multiply
+  BC_OP_DIV,            // true divide
+  BC_OP_FLDIV,          // floor divide
+  BC_OP_NEG,            // negative
+  BC_OP_MOD     = 0x18, // mod
+  BC_OP_POW,            // power
+  BC_OP_INCR,           // increment
+  BC_OP_DECR,           // decrement
+  BC_OP_DEREF,          // dereference pointer
+  BC_ALLOC_VARS,        // create space in rstack for local variables
+  BC_LIST_GATHER,       // create list from count + list of values on stack
+  BC_HASH_GATHER,       // create hash from count + list of pairs of keys/values on stack
+  BC_LONG_NUM   = 0xFC, // next two bytes are a 16-bit number
+  BC_VLONG_NUM  = 0xFD, // next four bytes are a 32-bit number
+  BC_VVLONG_NUM = 0xFE, // next four bytes are a 32-bit number
+  BC_HALT       = 0xFF, // terminate execution
 } sbOpcode;
 
 #endif
index f485622..fef3fd1 100644 (file)
@@ -1,6 +1,6 @@
 #include "vm/exec.h"
 
-#include "data/integer.h"
+#include "data/operations.h"
 
 void call_block(hVm vm, usize block_id);
 void execute_instruction(hVm vm);
@@ -56,6 +56,10 @@ void call_block(hVm vm, usize block_id) {
 }
 
 void return_from_block(hVm vm) {
+  for (usize i = 0; i < vm->fp->num_locals; i++) {
+    sbV_release(&vm->fp->locals[i]);
+  }
+
   sbVmStackFrame *frame = (sbVmStackFrame*)vm->fp;
 
   vm->fp = frame->last_fp;
@@ -63,6 +67,17 @@ void return_from_block(hVm vm) {
   vm->ip = frame->return_addr;
 }
 
+void debug_print_hv(const hV *value) {
+  printf("%016llx %016llx\n", *(u64*)value, *((u64*)value+1));
+}
+
+void store_local(hVm vm, usize local_index, const hV *value) {
+  /* release whatever was in the slot before */
+  sbV_release(&vm->fp->locals[local_index]);
+  sbV_retain(value);
+  vm->fp->locals[local_index] = *value;
+}
+
 void push_stack(hVm vm, const hV *value) {
   sbV_retain(value);
   *(hV*)vm->sp = *value;
@@ -75,17 +90,30 @@ hV *pop_stack(hVm vm) {
   return (hV*)vm->sp;
 }
 
+hV *npop_stack(hVm vm, usize count) {
+  vm->sp -= count * sizeof(hV);
+  for (usize i = 0; i < count; i++) {
+    sbV_release(&((hV*)vm->sp)[i]);
+  }
+  return (hV*)vm->sp;
+}
+
+hV *peek_stack(hVm vm, usize offset) {
+  return (hV*)(vm->sp - (offset + 1) * sizeof(hV));
+}
+
 sbOpcode get_opcode(hVm vm) {
   return *(vm->ip++);
 }
 
 /* small numbers that are parameters to opcodes can
  * just be encoded directly as 1 byte. numbers of 16-bit
- * scale can be encoded as FE + byte + byte, numbers of
- * 32 bit scale can be FF + byte + byte + byte + byte,
+ * scale can be encoded as FC + bytebyte, numbers of
+ * 32 bit scale can be FD + bytebytebytebyte,
+ * 64 bit can be FE + bytebytebytebytebytebytebytebyte,
  * in big-endian format */
-u32 get_param(hVm vm) {
-  u32 result = *((u8*)vm->ip++);
+u64 get_param(hVm vm) {
+  u64 result = *((u8*)vm->ip++);
 
   if (result == BC_LONG_NUM) {
     result = *(vm->ip++);
@@ -99,6 +127,22 @@ u32 get_param(hVm vm) {
     result |= *(vm->ip++);
     result <<= 8;
     result |= *(vm->ip++);
+  } else if (result == BC_VVLONG_NUM) {
+    result = *(vm->ip++);
+    result <<= 8;
+    result |= *(vm->ip++);
+    result <<= 8;
+    result |= *(vm->ip++);
+    result <<= 8;
+    result |= *(vm->ip++);
+    result <<= 8;
+    result |= *(vm->ip++);
+    result <<= 8;
+    result |= *(vm->ip++);
+    result <<= 8;
+    result |= *(vm->ip++);
+    result <<= 8;
+    result |= *(vm->ip++);
   }
 
   return result;
@@ -108,7 +152,7 @@ u32 get_param(hVm vm) {
 void execute_instruction(hVm vm) {
   sbOpcode op = get_opcode(vm);
   u32 param;
-  hV *v, a, b, c;
+  hV *v, *w, res;
 
   switch (op) {
     case BC_NOP:
@@ -135,82 +179,119 @@ void execute_instruction(hVm vm) {
       PANIC("todo");
     case BC_ST_VAR:
       param = get_param(vm);
-      vm->fp->locals[param] = *pop_stack(vm);
+      v = peek_stack(vm, 0);
+      store_local(vm, param, v);
+      pop_stack(vm);
       return;
     case BC_ST_UPVAL:
       PANIC("todo");
     case BC_POP:
       v = pop_stack(vm);
-      sbV_release(v);
       return;
-    case BC_CALL:
+    case BC_NPOP:
       param = get_param(vm);
-      call_block(vm, param);
+      npop_stack(vm, param);
+      return;
+    case BC_CALL:
+      v = pop_stack(vm);
+      call_block(vm, v->integer);
       return;
     case BC_JMP:
       param = get_param(vm);
       vm->ip = &vm->fp->block->bytecode[param];
       return;
+    case BC_JT:
+      param = get_param(vm);
+      v = pop_stack(vm);
+      if (!sbV_c_falsy(v)) {
+        vm->ip = &vm->fp->block->bytecode[param];
+      }
+      return;
+    case BC_JF:
+      param = get_param(vm);
+      v = pop_stack(vm);
+      if (sbV_c_falsy(v)) {
+        vm->ip = &vm->fp->block->bytecode[param];
+      }
+      return;
     case BC_RET:
       return_from_block(vm);
       return;
     case BC_SEND:
       PANIC("todo");
-    case BC_OP_ADD:
-      b = *pop_stack(vm);
-      a = *pop_stack(vm);
-      if (a.type == IT_INTEGER && b.type == IT_INTEGER) {
-        c = sbV_int(sbInteger_sum(a.integer, b.integer));
-        push_stack(vm, &c);
+    case BC_OP_EQ:
+      v = peek_stack(vm, 1);
+      w = peek_stack(vm, 0);
+      res = sbV_eq(v, w);
+      npop_stack(vm, 2);
+      push_stack(vm, &res);
+      return;
+    case BC_OP_NOT:
+      v = pop_stack(vm);
+      if (sbV_c_falsy(v)) {
+        push_stack(vm, &HVBOOL(TRUE));
       } else {
-        PANIC("todo");
+        push_stack(vm, &HVBOOL(FALSE));
       }
       return;
+    case BC_OP_ADD:
+      v = peek_stack(vm, 1);
+      w = peek_stack(vm, 0);
+      res = sbV_add(v, w);
+      npop_stack(vm, 2);
+      push_stack(vm, &res);
+      return;
     case BC_OP_SUB:
-      b = *pop_stack(vm);
-      a = *pop_stack(vm);
-      if (a.type == IT_INTEGER && b.type == IT_INTEGER) {
-        c = sbV_int(sbInteger_diff(a.integer, b.integer));
-        push_stack(vm, &c);
-      } else {
-        PANIC("todo");
-      }
+      v = peek_stack(vm, 1);
+      w = peek_stack(vm, 0);
+      res = sbV_sub(v, w);
+      npop_stack(vm, 2);
+      push_stack(vm, &res);
       return;
     case BC_OP_MUL:
-      b = *pop_stack(vm);
-      a = *pop_stack(vm);
-      if (a.type == IT_INTEGER && b.type == IT_INTEGER) {
-        c = sbV_int(sbInteger_mul(a.integer, b.integer));
-        push_stack(vm, &c);
-      } else {
-        PANIC("todo");
-      }
+      v = peek_stack(vm, 1);
+      w = peek_stack(vm, 0);
+      res = sbV_mul(v, w);
+      npop_stack(vm, 2);
+      push_stack(vm, &res);
       return;
     case BC_OP_DIV:
       PANIC("todo");
     case BC_OP_FLDIV:
-      b = *pop_stack(vm);
-      a = *pop_stack(vm);
-      if (a.type == IT_INTEGER && b.type == IT_INTEGER) {
-        c = sbV_int(sbInteger_floordiv(a.integer, b.integer));
-        push_stack(vm, &c);
-      } else {
-        PANIC("todo");
-      }
+      v = peek_stack(vm, 1);
+      w = peek_stack(vm, 0);
+      res = sbV_floordiv(v, w);
+      npop_stack(vm, 2);
+      push_stack(vm, &res);
       return;
     case BC_OP_NEG:
     case BC_OP_MOD:
     case BC_OP_POW:
+      PANIC("todo");
+    case BC_OP_INCR:
+      v = peek_stack(vm, 0);
+      res = sbV_incr(v);
+      pop_stack(vm);
+      push_stack(vm, &res);
+      return;
+    case BC_OP_DECR:
+      v = peek_stack(vm, 0);
+      res = sbV_decr(v);
+      pop_stack(vm);
+      push_stack(vm, &res);
+      return;
     case BC_OP_DEREF:
       PANIC("todo");
     case BC_ALLOC_VARS:
       param = get_param(vm);
+      /* have to set new rstack space to 0 so we don't accidentally decrement
+       * the ref count of variables from a previous stack frame (or of just garbage)  */
+      memset(vm->rp, 0, param * sizeof(hV));
       vm->rp += param * sizeof(hV);
+      vm->fp->num_locals += param;
       return;
-    case BC_LIST_NEW:
-    case BC_HASH_NEW:
-    case BC_LIST_ADD:
-    case BC_HASH_ADD:
+    case BC_LIST_GATHER:
+    case BC_HASH_GATHER:
       PANIC("todo");
     case BC_LONG_NUM:
     case BC_VLONG_NUM:
index c3341f7..6fa4406 100644 (file)
@@ -47,6 +47,7 @@ typedef struct sbVmStackFrame {
   struct sbVmStackFrame *last_fp;
   u8 *last_rp;
   const sbVmBlock *block;
+  usize num_locals;
   hV locals[];
 } sbVmStackFrame;