EMIT and various util cleanup
authorcassowarii <cassowary@cassowary.me>
Thu, 23 Jul 2026 17:41:55 +0000 (10:41 -0700)
committercassowarii <cassowary@cassowary.me>
Thu, 23 Jul 2026 17:41:55 +0000 (10:41 -0700)
src/compile/emit.c
src/global.h
src/parse/token.h
src/vm/bytecode.c [new file with mode: 0644]
src/vm/bytecode.h
src/vm/exec.c

index d2eef01..b73152b 100644 (file)
@@ -2,10 +2,7 @@
 
 #define STRING1(...) #__VA_ARGS__
 #define STRING2(...) STRING1(__VA_ARGS__)
-#define EMIT(...) do { \
-  if (cm->debugmode) debug(STRING2(__VA_ARGS__) "\n"); \
-  sbVmCompiler_write_code(cm, (u8[]) { __VA_ARGS__ }, sizeof((u8[]) { __VA_ARGS__ })); \
-} while (0)
+#define EMIT(x) (emit(cm, x))
 #define EARG(x) (emit_arg(cm, x))
 
 struct labelpos {
@@ -17,6 +14,7 @@ void compile_chunk(sbVmCompiler *cm, sbIrChunk *chunk);
 void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt);
 void compile_expr(sbVmCompiler *cm, sbIrExpr *expr);
 void compile_ref(sbVmCompiler *cm, sbIrExpr *expr, flag is_lref);
+void compile_maybe_ref(sbVmCompiler *cm, sbIrExpr *expr, flag is_lref, sbOpcode op, sbOpcode op_ind);
 void compile_op(sbVmCompiler *cm, sbAstOp op);
 
 void sbEmit_compile_program(sbVmProgram *vp, sbIrProgram *ir, flag debugmode) {
@@ -37,6 +35,11 @@ void sbEmit_compile_program(sbVmProgram *vp, sbIrProgram *ir, flag debugmode) {
 
 /* --- */
 
+void emit(sbVmCompiler *cm, sbOpcode opcode) {
+  if (cm->debugmode) debug("%s\n", g_opcode_names[opcode]);
+  sbVmCompiler_write_code(cm, (u8[]) { opcode }, 1);
+}
+
 void emit_arg(sbVmCompiler *cm, i64 actual_number) {
   if (cm->debugmode) debug("    arg %lld\n", (long long)actual_number);
 
@@ -172,7 +175,10 @@ void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt) {
         record_labelpos(cm, stmt->jump.label, sbVmCompiler_get_position(cm));
         /* leave behind four zeroes at this offset that we can later put the
          * address into */
-        EMIT(0, 0, 0, 0);
+        EARG(0);
+        EARG(0);
+        EARG(0);
+        EARG(0);
       }
       break;
     case IR_S_LABEL:
@@ -220,18 +226,8 @@ void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt) {
          * The reason for this has to do with assigning through nested data structures.
          * Doing it this way just makes the whole semantics feel more natural. It's similar
          * to returning a reference from indexing in C++, I guess. */
-        if (is_implicit_ref(stmt->assign.where->op.left)) {
-          compile_ref(cm, stmt->assign.where->op.left, TRUE);
-        } else {
-          compile_expr(cm, stmt->assign.where->op.left);
-        }
         compile_expr(cm, stmt->assign.where->op.right);     /* index we are using */
-        if (is_implicit_ref(stmt->assign.where->op.left)) {
-          EMIT(BC_OP_INDEXLREF_IND);
-        } else {
-          /* THE PROLIFERATION OF THESE!!!!!!!!!! I DO NOT LIKE!!!! */
-          EMIT(BC_OP_INDEXLREF);
-        }
+        compile_maybe_ref(cm, stmt->assign.where->op.left, TRUE, BC_OP_INDEXLREF, BC_OP_INDEXLREF_IND);
         EMIT(BC_REF_PUT);
       } else if (stmt->assign.where->type == IR_E_DOT) {
         /* same as op::index, essentially. the dot returns a reference to the inside of the
@@ -239,13 +235,7 @@ void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt) {
         compile_expr(cm, stmt->assign.where->dot.param);
         EMIT(BC_LD_IMM);
         EARG(1);
-        if (is_implicit_ref(stmt->assign.where->dot.target)) {
-          compile_ref(cm, stmt->assign.where->dot.target, TRUE);
-          EMIT(BC_DOT_IND);
-        } else {
-          compile_expr(cm, stmt->assign.where->dot.target);
-          EMIT(BC_DOT);
-        }
+        compile_maybe_ref(cm, stmt->assign.where->dot.target, TRUE, BC_DOT, BC_DOT_IND);
         EMIT(BC_REF_PUT);
       } else {
         PANIC("This type of assignment operation is not supported!");
@@ -302,7 +292,8 @@ void compile_list(sbVmCompiler *cm, sbIrExpr *expr) {
        * by one */
       compile_expr(cm, elem);
       /* swap to put the number on top, then increment */
-      EMIT(BC_SWAP, BC_OP_INCR);
+      EMIT(BC_SWAP);
+      EMIT(BC_OP_INCR);
     } else {
       /* blissfully unaware of the splat */
       compile_expr(cm, elem);
@@ -389,11 +380,6 @@ void compile_bind_list(sbVmCompiler *cm, sbIrBindList *list) {
 }
 
 void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
-  if (cm->debugmode) {
-    sbIr_print_expr(expr);
-    debug("\n");
-  }
-
   switch(expr->type) {
     case IR_E_OP:
       if (expr->op.type == AST_OP_REF) {
@@ -411,20 +397,7 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
     case IR_E_CALL:
       /* calling convention: store argument count on stack */
       compile_list(cm, expr->call.param);
-      if (is_implicit_ref(expr->call.func)) {
-        /* for expressions with reference type, call 'through' the reference */
-        compile_ref(cm, expr->call.func, TRUE);
-        EMIT(BC_CALL_IND);
-      } else if (expr->call.func->type == IR_E_OP && expr->call.func->op.type == AST_OP_DEREF) {
-        /* for something like (*a)(...), call 'through' the reference as well
-         * (that is, evaluate the thing inside the * and call indirectly, instead
-         * of dereferencing it first) */
-        compile_expr(cm, expr->call.func->op.left);
-        EMIT(BC_CALL_IND);
-      } else {
-        compile_expr(cm, expr->call.func);
-        EMIT(BC_CALL);
-      }
+      compile_maybe_ref(cm, expr->call.func, TRUE, BC_CALL, BC_CALL_IND);
       break;
     case IR_E_DOT:
       compile_expr(cm, expr->dot.param);
@@ -521,46 +494,37 @@ void compile_ref(sbVmCompiler *cm, sbIrExpr *expr, flag is_lref) {
     }
     EARG(expr->var->slot_id);
   } else if (expr->type == IR_E_OP && expr->op.type == AST_OP_INDEX) {
-    /* TODO: Oh my god, it's 90F with no air conditioning, but can we PLEASE figure out
-     * a better way to structure this??!!?!?!?! */
-    if (is_implicit_ref(expr->op.left)) {
-      compile_ref(cm, expr->op.left, is_lref);  /* thing we are indexing into */
-    } else {
-      compile_expr(cm, expr->op.left);  /* thing we are indexing into */
-    }
     compile_expr(cm, expr->op.right);         /* index we are using */
     if (is_lref) {
-      /* a[0] = ? / a::b = ? */
-      if (is_implicit_ref(expr->op.left)) {
-        /* THIS SUCKS!!! */
-        EMIT(BC_OP_INDEXLREF_IND);
-      } else {
-        EMIT(BC_OP_INDEXLREF);
-      }
+      compile_maybe_ref(cm, expr->op.left, TRUE, BC_OP_INDEXLREF, BC_OP_INDEXLREF_IND);
     } else {
-      /* &a[0] / &a::b */
-      if (is_implicit_ref(expr->op.left)) {
-        EMIT(BC_OP_INDEXRREF_IND);
-      } else {
-        EMIT(BC_OP_INDEXRREF);
-      }
+      compile_maybe_ref(cm, expr->op.left, FALSE, BC_OP_INDEXRREF, BC_OP_INDEXRREF_IND);
     }
   } else if (expr->type == IR_E_DOT) {
     compile_expr(cm, expr->dot.param);
     EMIT(BC_LD_IMM);
     EARG(1);
-    if (is_implicit_ref(expr->dot.target)) {
-      compile_ref(cm, expr->dot.target, is_lref);
-      EMIT(BC_DOT_IND);
-    } else {
-      compile_expr(cm, expr->dot.target);
-      EMIT(BC_DOT);
-    }
+    compile_maybe_ref(cm, expr->dot.target, is_lref, BC_DOT, BC_DOT_IND);
   } else {
     PANIC("This type of reference operation is not supported!");
   }
 }
 
+void compile_maybe_ref(sbVmCompiler *cm, sbIrExpr *expr, flag is_lref, sbOpcode op, sbOpcode op_ind) {
+  if (is_implicit_ref(expr->dot.target)) {
+    compile_ref(cm, expr, is_lref);
+    EMIT(op_ind);
+  } else if (expr->type == IR_E_OP && expr->op.type == AST_OP_DEREF) {
+    /* explicit *<whatever>: do thing indirectly 'through' the reference,
+     * instead of dereferencing and making a copy */
+    compile_expr(cm, expr->op.left);
+    EMIT(op_ind);
+  } else {
+    compile_expr(cm, expr);
+    EMIT(op);
+  }
+}
+
 void compile_op(sbVmCompiler *cm, sbAstOp op) {
   switch (op) {
     case AST_OP_ADD: EMIT(BC_OP_ADD); break;
@@ -569,11 +533,11 @@ void compile_op(sbVmCompiler *cm, sbAstOp op) {
     case AST_OP_FLDIV: EMIT(BC_OP_FLDIV); break;
     case AST_OP_MOD: EMIT(BC_OP_MOD); break;
     case AST_OP_EQ: EMIT(BC_OP_EQ); break;
-    case AST_OP_NE: EMIT(BC_OP_EQBC_OP_NOT); break;
+    case AST_OP_NE: EMIT(BC_OP_EQ); EMIT(BC_OP_NOT); break;
     case AST_OP_LT: EMIT(BC_OP_LT); break;
-    case AST_OP_GT: EMIT(BC_OP_LEBC_OP_NOT); break;
+    case AST_OP_GT: EMIT(BC_OP_LE); EMIT(BC_OP_NOT); break;
     case AST_OP_LE: EMIT(BC_OP_LE); break;
-    case AST_OP_GE: EMIT(BC_OP_LTBC_OP_NOT); break;
+    case AST_OP_GE: EMIT(BC_OP_LT); EMIT(BC_OP_NOT); break;
     case AST_OP_NOT: EMIT(BC_OP_NOT); break;
     case AST_OP_AND: EMIT(BC_OP_AND); break;
     case AST_OP_OR: EMIT(BC_OP_OR); break;
@@ -582,7 +546,7 @@ void compile_op(sbVmCompiler *cm, sbAstOp op) {
     case AST_OP_DEREF: EMIT(BC_OP_DEREF); break;
     /* op range is currently only used in rangeindex; just pass them to it directly */
     case AST_OP_RANGE: break;
-    case AST_OP_DIVBY: EMIT(BC_OP_MODBC_LD_IMM); EARG(0); EMIT(BC_OP_EQ); break;
+    case AST_OP_DIVBY: EMIT(BC_OP_MOD); EMIT(BC_LD_IMM); EARG(0); EMIT(BC_OP_EQ); break;
     default:
       PANIC("unknown operation! (%lld / %c)", (long long)op, op);
   }
index b4adf4b..46731f5 100644 (file)
@@ -2,11 +2,25 @@
 #define __SB_GLOBAL_H__
 
 #ifdef DEBUG
-#define PANIC(...) do { fprintf(stderr, "PANIC: " __VA_ARGS__); fprintf(stderr, "\n       at " __FILE__ ":%d\n", __LINE__); abort(); } while (0)
-#define CHECK(...) do { fprintf(stderr, "BUGCHECK: " __VA_ARGS__); fprintf(stderr, "\n       at " __FILE__ ":%d\n", __LINE__); abort(); } while (0)
+#define PANIC(...) do { \
+  fflush(stdout); \
+  fprintf(stderr, "PANIC: " __VA_ARGS__); \
+  fprintf(stderr, "\n       at " __FILE__ ":%d\n", __LINE__); \
+  abort(); \
+} while (0)
+#define CHECK(...) do { \
+  fflush(stdout); \
+  fprintf(stderr, "BUGCHECK: " __VA_ARGS__); \
+  fprintf(stderr, "\n       at " __FILE__ ":%d\n", __LINE__); \
+  abort(); \
+} while (0)
 #define debug(...) printf(__VA_ARGS__)
 #else
-#define PANIC(...) do { fprintf(stderr, "PANIC: " __VA_ARGS__); fprintf(stderr, "\n"); abort(); } while (0)
+#define PANIC(...) do { \
+  fprintf(stderr, "PANIC: " __VA_ARGS__); \
+  fprintf(stderr, "\n"); \
+  abort(); \
+} while (0)
 #define CHECK(...) ((void)0)
 #define debug(...) ((void)0)
 #endif
@@ -15,6 +29,7 @@
 #include <stdlib.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <stdbool.h>
 
 typedef uint64_t u64;
 typedef int64_t i64;
@@ -24,7 +39,7 @@ typedef uint16_t u16;
 typedef int16_t i16;
 typedef uint8_t u8;
 typedef int8_t i8;
-typedef uint8_t flag;
+typedef bool flag;
 typedef size_t usize;
 typedef ptrdiff_t isize;
 
index 7a63b4b..c56eb70 100644 (file)
@@ -91,9 +91,9 @@ typedef enum sbTokenType {
 } sbTokenType;
 
 typedef struct sbLexToken {
-    sbTokenType type;
+    sbTokenType type : 8;
+    i8 invisible;
     usize size;
-    flag invisible;
     i32 line;
     i32 start_col;
     i32 end_col;
diff --git a/src/vm/bytecode.c b/src/vm/bytecode.c
new file mode 100644 (file)
index 0000000..da63e67
--- /dev/null
@@ -0,0 +1,7 @@
+#include "vm/bytecode.h"
+
+const char *g_opcode_names[] = {
+#define X(A) #A,
+  BYTECODES
+#undef X
+};
index e96e17e..9fb4e14 100644 (file)
 
 #include "global.h"
 
+#define BYTECODES \
+  /* skip me */ \
+  X(BC_NOP)\
+  /* push immediate value to stack */ \
+  X(BC_LD_IMM)\
+  /* push constants[index] to stack */ \
+  X(BC_LD_CONST)\
+  /* look up key in context object and push result */ \
+  X(BC_LD_CTX)\
+  /* push value onto stack from variable */ \
+  X(BC_LD_VAR)\
+  /* push lref to variable onto stack */ \
+  X(BC_LD_LREF)\
+  /* push rref to variable onto stack */ \
+  X(BC_LD_RREF)\
+  /* push value onto stack from closure */ \
+  X(BC_LD_UPVAL)\
+  /* push pointer to closure value onto stack (to close over again) */ \
+  X(BC_LD_UPREF)\
+  /* push reference to function onto stack */ \
+  X(BC_LD_BLK)\
+  /* push nil onto stack */ \
+  X(BC_LD_NIL)\
+  /* push true onto stack */ \
+  X(BC_LD_TRUE)\
+  /* push false onto stack */ \
+  X(BC_LD_FALSE)\
+  /* pop value from stack into variable */ \
+  X(BC_ST_VAR)\
+  /* pop value from stack into closure */ \
+  X(BC_ST_UPVAL)\
+  /* decrement TOS and store NOS in variable */ \
+  X(BC_ST_ARG)\
+  /* pop value from stack */ \
+  X(BC_POP)\
+  /* pop N values from stack given by top number */ \
+  X(BC_NPOP)\
+  /* swap top two values on stack */ \
+  X(BC_SWAP)\
+  /* push return address and jump to new block */ \
+  X(BC_CALL)\
+  /* 'a->(...)' indirect call that can return a value */ \
+  X(BC_CALL_IND)\
+  /* 'a.b' call that should return a reference */ \
+  X(BC_DOT)\
+  /* 'a.b' indirect call that should return a reference */ \
+  X(BC_DOT_IND)\
+  /* return to calling block */ \
+  X(BC_RET)\
+  /* check number of function arguments = this */ \
+  X(BC_NUMARG)\
+  /* check number of function arguments > this */ \
+  X(BC_MINARG)\
+  /* unconditional local jump inside current block */ \
+  X(BC_JMP)\
+  /* jump if true */ \
+  X(BC_JT)\
+  /* jump if false */ \
+  X(BC_JF)\
+  /* like call)\ but jump to object method */ \
+  X(BC_SEND)\
+  /* assign through pointer */ \
+  X(BC_REF_PUT)\
+  /* == */ \
+  X(BC_OP_EQ)\
+  /* boolean not */ \
+  X(BC_OP_NOT)\
+  /* add */ \
+  X(BC_OP_ADD)\
+  /* subtract */ \
+  X(BC_OP_SUB)\
+  /* multiply */ \
+  X(BC_OP_MUL)\
+  /* true divide */ \
+  X(BC_OP_DIV)\
+  /* floor divide */ \
+  X(BC_OP_FLDIV)\
+  /* negative */ \
+  X(BC_OP_NEG)\
+  /* mod */ \
+  X(BC_OP_MOD)\
+  /* power */ \
+  X(BC_OP_POW)\
+  /* increment */ \
+  X(BC_OP_INCR)\
+  /* decrement */ \
+  X(BC_OP_DECR)\
+  /* dereference pointer */ \
+  X(BC_OP_DEREF)\
+  /* less than */ \
+  X(BC_OP_LT)\
+  /* less than or equal to */ \
+  X(BC_OP_LE)\
+  /* logical and */ \
+  X(BC_OP_AND)\
+  /* logical or */ \
+  X(BC_OP_OR)\
+  /* a[...] */ \
+  X(BC_OP_INDEXVAL)\
+  /* a[...] = ... */ \
+  X(BC_OP_INDEXLREF)\
+  /* a[...] = ... */ \
+  X(BC_OP_INDEXLREF_IND)\
+  /* &a[...] */ \
+  X(BC_OP_INDEXRREF)\
+  /* &a[...] */ \
+  X(BC_OP_INDEXRREF_IND)\
+  /* index [a..b] */ \
+  X(BC_OP_RANGEINDEX)\
+  /* create space in rstack for local variables */ \
+  X(BC_ALLOC_VARS)\
+  /* create closure from top elements of stack */ \
+  X(BC_CLOSURE)\
+  /* create list from count + list of values on stack */ \
+  X(BC_LIST_GATHER)\
+  /* create hash from count + list of pairs of keys/values on stack */ \
+  X(BC_HASH_GATHER)\
+  /* splat a list into a context with a count */ \
+  X(BC_LIST_SPILL)\
+  /* next two bytes are a 16-bit number */ \
+  X(BC_LONG_NUM)\
+  /* next four bytes are a 32-bit number */ \
+  X(BC_VLONG_NUM)\
+  /* next four bytes are a 32-bit number */ \
+  X(BC_VVLONG_NUM)\
+  /* terminate execution */ \
+  X(BC_HALT)
+
 typedef enum sbOpcode {
-  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_LREF,           // push lref to variable onto stack
-  BC_LD_RREF,           // push rref to variable onto stack
-  BC_LD_UPVAL,          // push value onto stack from closure
-  BC_LD_UPREF,          // push pointer to closure value onto stack (to close over again)
-  BC_LD_BLK,            // push reference to function onto stack
-  BC_LD_NIL,            // push nil onto stack
-  BC_LD_TRUE,           // push true onto stack
-  BC_LD_FALSE,          // push false onto stack
-  BC_ST_VAR,            // pop value from stack into variable
-  BC_ST_UPVAL,          // pop value from stack into closure
-  BC_ST_ARG,            // decrement TOS and store NOS in variable
-  BC_POP,               // pop value from stack
-  BC_NPOP,              // pop N values from stack given by top number
-  BC_SWAP,              // swap top two values on stack
-  BC_CALL,              // push return address and jump to new block
-  BC_CALL_IND,          // 'a->(...)' indirect call that can return a value
-  BC_DOT,               // 'a.b' call that should return a reference
-  BC_DOT_IND,           // 'a.b' indirect call that should return a reference
-  BC_RET,               // return to calling block
-  BC_NUMARG,            // check number of function arguments = this
-  BC_MINARG,            // check number of function arguments > this
-  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_REF_PUT,           // assign through pointer
-  BC_OP_EQ,             // ==
-  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,            // mod
-  BC_OP_POW,            // power
-  BC_OP_INCR,           // increment
-  BC_OP_DECR,           // decrement
-  BC_OP_DEREF,          // dereference pointer
-  BC_OP_LT,             // less than
-  BC_OP_LE,             // less than or equal to
-  BC_OP_AND,            // logical and
-  BC_OP_OR,             // logical or
-  BC_OP_INDEXVAL,       // a[...]
-  BC_OP_INDEXLREF,      // a[...] = ...
-  BC_OP_INDEXLREF_IND,  // a[...] = ...
-  BC_OP_INDEXRREF,      // &a[...]
-  BC_OP_INDEXRREF_IND,  // &a[...]
-  BC_OP_RANGEINDEX,     // index [a..b]
-  BC_ALLOC_VARS,        // create space in rstack for local variables
-  BC_CLOSURE,           // create closure from top elements of stack
-  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_LIST_SPILL,        // splat a list into a context with a count
-  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
+#define X(A) A,
+  BYTECODES
+#undef X
 } sbOpcode;
 
+extern const char *g_opcode_names[];
+
 #endif
index 24d93d0..6b75f94 100644 (file)
@@ -357,7 +357,9 @@ void execute_instruction(hVm vm) {
   }
 
   sbOpcode op = get_opcode(vm);
-  if (vm->debugmode) debug("op %02X ", op);
+#ifdef DEBUG
+  if (vm->debugmode) debug("op %s ", g_opcode_names[op]);
+#endif
   u64 param;
   usize count;
   hVal *v, *w, *x, res;