janky beginnings of emitting bytecode
authorcassowarii <cassowary@cassowary.me>
Tue, 30 Jun 2026 01:55:42 +0000 (18:55 -0700)
committercassowarii <cassowary@cassowary.me>
Tue, 30 Jun 2026 01:55:42 +0000 (18:55 -0700)
src/compile/emit.c [new file with mode: 0644]
src/compile/emit.h [new file with mode: 0644]
src/compile/ir.c
src/main.c
src/vm/block.c
src/vm/block.h

diff --git a/src/compile/emit.c b/src/compile/emit.c
new file mode 100644 (file)
index 0000000..66b18cc
--- /dev/null
@@ -0,0 +1,151 @@
+#include "compile/emit.h"
+
+#define STRING1(...) #__VA_ARGS__
+#define STRING2(...) STRING1(__VA_ARGS__)
+#define EMIT(...) do { printf(STRING2(__VA_ARGS__) "\n"); sbVmCompiler_write_code(cm, (u8[]) { __VA_ARGS__ }, sizeof((u8[]) { __VA_ARGS__ })); } while (0)
+#define EARG(x) (emit_arg(cm, x))
+
+struct labelpos {
+  sbIrLabel *label;
+  u32 offset;
+};
+
+void compile_chunk(sbVmCompiler *cm, sbIrChunk *chunk);
+void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt);
+void compile_expr(sbVmCompiler *cm, sbIrExpr *expr);
+
+void sbEmit_compile_program(sbVmProgram *vp, sbIrProgram *ir) {
+  sbVmCompiler cm = sbVmCompiler_create(4096, 4096);
+
+  int nchunks = ir->chunks.size / sizeof(sbIrChunk*);
+  for (int i = 0; i < nchunks; i++) {
+    sbIrChunk *chunk = ((sbIrChunk**)ir->chunks.data)[i];
+
+    compile_chunk(&cm, chunk);
+
+    sbVmProgram_add_block(vp, &cm);
+    sbVmCompiler_reset(&cm);
+  }
+
+  sbVmCompiler_deinitialize(&cm);
+}
+
+void compile_chunk(sbVmCompiler *cm, sbIrChunk *chunk) {
+  //sbVmCompiler_add_constant(&cm, &HVINT(5));
+  //sbVmCompiler_add_constant(&cm, &HVINT(9));
+
+  printf("\n--block--\n");
+
+  int nstmts = chunk->stmts.size / sizeof(sbIrStmt*);
+  for (int i = 0; i < nstmts; i++) {
+    sbIrStmt *stmt = ((sbIrStmt**)chunk->stmts.data)[i];
+
+    compile_stmt(cm, stmt);
+  }
+}
+
+/* when compiling one line at a time, we don't know the position
+ * of labels we haven't seen yet. we leave a 4-byte slot for a blank
+ * address, and say "please put the address of this label here later!" */
+void record_labelpos(sbVmCompiler *cm, sbIrLabel *label, u32 offset) {
+  struct labelpos lp = {
+  };
+
+  sbBuffer_append(&cm->label_positions, &lp, sizeof(struct labelpos));
+}
+
+void emit_arg(sbVmCompiler *cm, usize number) {
+  printf("  arg %zu\n", number);
+  u8 buf[9];
+  if (number < 256) {
+    buf[0] = number;
+    sbVmCompiler_write_code(cm, buf, 1);
+  } else if (number < 65536) {
+    buf[0] = BC_LONG_NUM;
+    buf[1] = number >> 8;
+    buf[2] = number & 0xFF;
+    sbVmCompiler_write_code(cm, buf, 3);
+  } else if (number < (1LL << 32)) {
+    buf[0] = BC_VLONG_NUM;
+    buf[1] = (number >> 24) & 0xFF;;
+    buf[2] = (number >> 16) & 0xFF;
+    buf[3] = (number >> 8)  & 0xFF;
+    buf[4] = number & 0xFF;
+    sbVmCompiler_write_code(cm, buf, 5);
+  } else {
+    buf[0] = BC_VVLONG_NUM;
+    buf[1] = (number >> 56) & 0xFF;;
+    buf[2] = (number >> 48) & 0xFF;;
+    buf[3] = (number >> 40) & 0xFF;;
+    buf[4] = (number >> 32) & 0xFF;;
+    buf[5] = (number >> 24) & 0xFF;;
+    buf[6] = (number >> 16) & 0xFF;
+    buf[7] = (number >> 8)  & 0xFF;
+    buf[8] = number & 0xFF;
+    sbVmCompiler_write_code(cm, buf, 9);
+  }
+}
+
+void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt) {
+  printf("%zu ", sbVmCompiler_get_position(cm));
+  switch (stmt->type) {
+    case IR_S_EXPR:
+      compile_expr(cm, stmt->expr);
+      EMIT(BC_POP);
+      break;
+    case IR_S_JUMP:
+      if (stmt->jump.label->found_yet) {
+        if (stmt->jump.condition == NULL) {
+          EMIT(BC_JMP);
+        } else {
+          compile_expr(cm, stmt->jump.condition);
+          if (stmt->jump.inverted) {
+            EMIT(BC_JF);
+          } else {
+            EMIT(BC_JT);
+          }
+        }
+        EARG(stmt->jump.label->block_position);
+      } else {
+        printf("haven't implemented forward jump yet!\n");
+      }
+      break;
+    case IR_S_LABEL:
+      stmt->label->found_yet = TRUE;
+      stmt->label->block_position = sbVmCompiler_get_position(cm);
+      printf("\n");
+      break;
+    case IR_S_RETURN:
+      if (stmt->expr) {
+        compile_expr(cm, stmt->expr);
+      }
+      EMIT(BC_RET);
+      break;
+    default:
+      printf("haven't implemented this yet!\n");
+  }
+}
+
+void compile_op(sbVmCompiler *cm, sbAstOp op);
+void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
+  switch(expr->type) {
+    case IR_E_OP:
+      compile_expr(cm, expr->op.left);
+      if (expr->op.right) {
+        compile_expr(cm, expr->op.right);
+      }
+      compile_op(cm, expr->op.type);
+      break;
+    default:
+      printf("(compile an expr)\n");
+  }
+}
+
+void compile_op(sbVmCompiler *cm, sbAstOp op) {
+  switch (op) {
+    case AST_OP_ADD: EMIT(BC_OP_ADD); break;
+    case AST_OP_SUB: EMIT(BC_OP_SUB); break;
+    default:
+      printf("unknown operation!\n");
+  }
+}
diff --git a/src/compile/emit.h b/src/compile/emit.h
new file mode 100644 (file)
index 0000000..f909625
--- /dev/null
@@ -0,0 +1,23 @@
+#include "common.h"
+
+#include "compile/ir.h"
+#include "vm/block.h"
+
+/* Stage 6 of the compiler. The Final Stage.
+ * Take the IR that was produced from the ir module, and
+ * convert it into bytecode that our VM can understand. */
+
+/* Since control flow and variable scope was already
+ * linearized by the previous step, emitting these is
+ * fairly straightforward (except that we have to compile
+ * each block in two passes in order to compute the
+ * position of forward jumps.) */
+
+/* Expressions coming from the IR are still nested in a
+ * tree structure like the AST, because it's easier to
+ * flatten them in this step, where we are producing
+ * code for a stack machine. This is pretty simple; we just
+ * have to essentially convert the tree to "reverse polish
+ * notation." */
+
+void sbEmit_compile_program(sbVmProgram *vp, sbIrProgram *ir);
index ba06457..4a3150c 100644 (file)
@@ -768,10 +768,9 @@ static void print_expr(sbIrExpr *e) {
 static void print_stmt(sbIrStmt *s) {
   switch (s->type) {
     case IR_S_ARG:
+      printf("  bind function argument to variable %zu\n", s->arg.var->slot_id);
       if (s->arg.last) {
-        printf("  bind last function argument to variable %zu\n", s->arg.var->slot_id);
-      } else {
-        printf("  bind function argument to variable %zu\n", s->arg.var->slot_id);
+        printf("  (no function arguments left on the stack now)\n");
       }
       break;
     case IR_S_LABEL:
index 272ae2e..7490375 100644 (file)
@@ -5,6 +5,7 @@
 #include "data/hashtable.h"
 #include "data/symbol.h"
 #include "compile/ir.h"
+#include "compile/emit.h"
 #include "vm/exec.h"
 
 int main(int argc, char **argv) {
@@ -45,8 +46,14 @@ int main(int argc, char **argv) {
           sbIrProgram_print(&ir);
         }
 
+        sbVmProgram pm;
+        sbVmProgram_initialize(&pm, 65536);
+
+        sbEmit_compile_program(&pm, &ir);
+
         sbIrProgram_deinitialize(&ir);
 
+        sbVmProgram_deinitialize(&pm);
         sbSymbol_sys_deinit();
         sbHash_sys_deinit();
         sbString_sys_deinit();
@@ -60,7 +67,7 @@ int main(int argc, char **argv) {
     sbVmProgram pm;
     sbVmProgram_initialize(&pm, 65536);
 
-    sbVmPartialBlock pb = sbVmBlock_create(4096, 4096);
+    sbVmCompiler pb = sbVmCompiler_create(4096, 4096);
 
     u8 code1[] = {
       BC_ALLOC_VARS, 0x02,           /* 0 1 */
@@ -84,12 +91,12 @@ int main(int argc, char **argv) {
       BC_HALT,
     };
 
-    sbVmBlock_add_constant(&pb, &HVINT(5));
-    sbVmBlock_add_constant(&pb, &HVINT(9));
+    sbVmCompiler_add_constant(&pb, &HVINT(5));
+    sbVmCompiler_add_constant(&pb, &HVINT(9));
 
-    sbVmBlock_write_code(&pb, code1, sizeof(code1));
+    sbVmCompiler_write_code(&pb, code1, sizeof(code1));
     sbVmProgram_add_block(&pm, &pb);
-    sbVmBlock_reset(&pb);
+    sbVmCompiler_reset(&pb);
 
     u8 code2[] = {
       BC_LD_CONST, 0x00,
@@ -97,11 +104,13 @@ int main(int argc, char **argv) {
       BC_RET,
     };
 
-    sbVmBlock_add_constant(&pb, &HVINT(7));
+    sbVmCompiler_add_constant(&pb, &HVINT(7));
 
-    sbVmBlock_write_code(&pb, code2, sizeof(code2));
+    sbVmCompiler_write_code(&pb, code2, sizeof(code2));
     sbVmProgram_add_block(&pm, &pb);
-    sbVmBlock_reset(&pb);
+    sbVmCompiler_reset(&pb);
+
+    sbVmCompiler_deinitialize(&pb);
 
     sbVm_execute(&vm, &pm);
 
@@ -110,6 +119,4 @@ int main(int argc, char **argv) {
       printf("%02X ", *p);
     }
     printf("\n");
-
-    sbVmBlock_deinitialize(&pb);
 }
index fe6757e..804f476 100644 (file)
@@ -2,31 +2,37 @@
 
 #define INITIAL_PROGRAM_BLOCK_SIZE 32
 
-sbVmPartialBlock sbVmBlock_create(usize initial_bytecode_size, usize initial_constant_size) {
-  sbVmPartialBlock pb = {0};
-  sbBuffer_initialize(&pb.bytecode, initial_bytecode_size);
-  sbBuffer_initialize(&pb.constants, initial_constant_size);
-  return pb;
+sbVmCompiler sbVmCompiler_create(usize initial_bytecode_size, usize initial_constant_size) {
+  sbVmCompiler cm = {0};
+  sbBuffer_initialize(&cm.bytecode, initial_bytecode_size);
+  sbBuffer_initialize(&cm.constants, initial_constant_size);
+  sbBuffer_initialize(&cm.label_positions, 1024);
+  return cm;
 }
 
-void sbVmBlock_reset(sbVmPartialBlock *pb) {
-  sbBuffer_reset(&pb->bytecode);
-  sbBuffer_reset(&pb->constants);
+void sbVmCompiler_reset(sbVmCompiler *cm) {
+  sbBuffer_reset(&cm->bytecode);
+  sbBuffer_reset(&cm->constants);
 }
 
-void sbVmBlock_deinitialize(sbVmPartialBlock *pb) {
-  sbBuffer_deinitialize(&pb->bytecode);
-  sbBuffer_deinitialize(&pb->constants);
+void sbVmCompiler_deinitialize(sbVmCompiler *cm) {
+  sbBuffer_deinitialize(&cm->bytecode);
+  sbBuffer_deinitialize(&cm->constants);
+  sbBuffer_deinitialize(&cm->label_positions);
 }
 
-void sbVmBlock_write_code(sbVmPartialBlock *pb, const u8 *data, usize length) {
-  sbBuffer_append(&pb->bytecode, data, length);
+void sbVmCompiler_write_code(sbVmCompiler *cm, const u8 *data, usize length) {
+  sbBuffer_append(&cm->bytecode, data, length);
 }
 
-void sbVmBlock_add_constant(sbVmPartialBlock *pb, hV *constant) {
+usize sbVmCompiler_get_position(sbVmCompiler *cm) {
+  return cm->bytecode.size;
+}
+
+void sbVmCompiler_add_constant(sbVmCompiler *cm, hV *constant) {
   sbV_retain(constant);
 
-  sbBuffer_append(&pb->constants, constant, sizeof(hV));
+  sbBuffer_append(&cm->constants, constant, sizeof(hV));
 }
 
 void sbVmProgram_initialize(sbVmProgram *pm, usize initial_arena_size) {
@@ -45,18 +51,18 @@ void sbVmProgram_deinitialize(sbVmProgram *pm) {
 /* finalize a partial-block and add it to the program.
  * copies the data, so the same partial-block object can be reset
  * and reused after calling this function. */
-sbBlockId sbVmProgram_add_block(sbVmProgram *pm, sbVmPartialBlock *pb) {
-  usize bytecode_length = pb->bytecode.size;
-  usize constants_length = pb->constants.size;
+sbBlockId sbVmProgram_add_block(sbVmProgram *pm, sbVmCompiler *cm) {
+  usize bytecode_length = cm->bytecode.size;
+  usize constants_length = cm->constants.size;
   while (bytecode_length % 8 != 0) bytecode_length ++;
 
   u8 *block_data = sbArena_alloc(&pm->arena, bytecode_length + constants_length);
 
   u8 *bytecode = &block_data[0];
-  memcpy(bytecode, pb->bytecode.data, bytecode_length);
+  memcpy(bytecode, cm->bytecode.data, bytecode_length);
 
   u8 *constants = &block_data[bytecode_length];
-  memcpy(constants, pb->constants.data, constants_length);
+  memcpy(constants, cm->constants.data, constants_length);
 
   sbVmBlock bk = {
     .bytecode = bytecode,
index 6e9fb71..2e63326 100644 (file)
@@ -1,3 +1,6 @@
+#ifndef __SARABANDE_BLOCK_H__
+#define __SARABANDE_BLOCK_H__
+
 #include "common.h"
 
 #define TEST_VM_BLK(bc, cn) ((sbVmBlock) { \
 
 /* dynamic sized block that we can more easily add
  * things to while compiling */
-typedef struct sbVmPartialBlock {
+typedef struct sbVmCompiler {
   sbBuffer bytecode;
   sbBuffer constants;
-} sbVmPartialBlock;
+  sbBuffer label_positions;
+} sbVmCompiler;
 
 /* fixed size / frozen block of bytecode */
 typedef struct sbVmBlock {
@@ -31,15 +35,17 @@ typedef struct sbVmProgram {
 
 typedef usize sbBlockId;
 
-sbVmPartialBlock sbVmBlock_create(usize initial_bytecode_size, usize initial_constant_size);
+sbVmCompiler sbVmCompiler_create(usize initial_bytecode_size, usize initial_constant_size);
+
+void sbVmCompiler_reset(sbVmCompiler *pb);
 
-void sbVmBlock_reset(sbVmPartialBlock *pb);
+void sbVmCompiler_deinitialize(sbVmCompiler *pb);
 
-void sbVmBlock_deinitialize(sbVmPartialBlock *pb);
+void sbVmCompiler_write_code(sbVmCompiler *pb, const u8 *data, usize length);
 
-void sbVmBlock_write_code(sbVmPartialBlock *pb, const u8 *data, usize length);
+usize sbVmCompiler_get_position(sbVmCompiler *cm);
 
-void sbVmBlock_add_constant(sbVmPartialBlock *pb, hV *constant);
+void sbVmCompiler_add_constant(sbVmCompiler *pb, hV *constant);
 
 void sbVmProgram_initialize(sbVmProgram *pm, usize initial_arena_size);
 
@@ -48,4 +54,6 @@ void sbVmProgram_deinitialize(sbVmProgram *pm);
 /* finalize a partial-block and add it to the program.
  * copies the data, so the same partial-block object can be reset
  * and reused after calling this function. */
-sbBlockId sbVmProgram_add_block(sbVmProgram *pm, sbVmPartialBlock *pb);
+sbBlockId sbVmProgram_add_block(sbVmProgram *pm, sbVmCompiler *pb);
+
+#endif