split out ir printing; mutual + recursive function calling
authorcassowarii <cassowary@cassowary.me>
Thu, 2 Jul 2026 04:55:03 +0000 (21:55 -0700)
committercassowarii <cassowary@cassowary.me>
Thu, 2 Jul 2026 04:55:03 +0000 (21:55 -0700)
src/compile/emit.c
src/compile/ir.c
src/compile/print_ir.c [new file with mode: 0644]
src/vm/bytecode.h
src/vm/exec.c

index 249daa8..071edfd 100644 (file)
@@ -255,11 +255,17 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
              * a second time */
             EMIT(BC_LD_UPREF);
           } else if ((*var)->closed_over) {
-            /* note: BC_LD_VAR here, not BC_LD_IND! this will push
+            /* note: BC_LD_REF here, not BC_LD_IND! this will push
              * the pointer value, not the variable itself, because
              * we want to put the pointer (well, the sbRef) inside
              * the closure, not the value */
-            EMIT(BC_LD_VAR);
+            /* BC_LD_REF is actually almost the same as BC_LD_VAR,
+             * except that if a variable hasn't been initialized
+             * it will create a new reference to nil. (this is
+             * necessary for mutually calling functions, who want to
+             * close over each other before all of them have been
+             * defined.) */
+            EMIT(BC_LD_REF);
           } else {
             PANIC("cannot have a direct variable in closure!");
           }
index 16aca5d..79d7cee 100644 (file)
@@ -16,7 +16,6 @@ typedef struct varmapentry {
 static sbIrChunk *compile_ast_function(hIrProgram ir, sbAst params, sbAst seqast);
 static sbIrChunk *new_chunk(hIrProgram ir);
 static void chunk_deinitialize(hIrChunk ck);
-static void print_stmt(sbIrStmt *s);
 
 void sbIrProgram_initialize(hIrProgram ir, usize initial_arena_size) {
   *ir = (sbIrProgram) {0};
@@ -43,27 +42,6 @@ void sbIrProgram_compile_ast(hIrProgram ir, sbAst ast) {
   compile_ast_function(ir, NO_NODE, ast);
 }
 
-void sbIrProgram_print(hIrProgram ir) {
-  usize nchunks = ir->chunks.size / sizeof(sbIrChunk*);
-
-  for (usize i = 0; i < nchunks; i++) {
-    sbIrChunk *ck = ((sbIrChunk**)ir->chunks.data)[i];
-    debug("\nCHUNK %d with %d variables", ck->id, ck->variable_count);
-    if (ck->num_upvalues > 0) {
-      debug(" and %d upvalues", ck->num_upvalues);
-    }
-    debug("\n");
-
-    usize nstmts = ck->stmts.size / sizeof(sbIrStmt*);
-    for (usize j = 0; j < nstmts; j++) {
-      sbIrStmt *s = ((sbIrStmt**)ck->stmts.data)[j];
-      print_stmt(s);
-    }
-
-    debug("\n");
-  }
-}
-
 /* --- */
 
 static sbIrExpr SENTINEL_NIL_EXPR = {
@@ -446,7 +424,6 @@ static void put_expr(hIrChunk ck, sbIrExpr *expr) {
   });
 }
 
-static void print_expr(sbIrExpr *e);
 static void put_assign(hIrChunk ck, sbIrVariable *assign_to, sbIrExpr *expr) {
   assign_to->initialized = TRUE;
   put_ir_stmt(ck, &(sbIrStmt) {
@@ -535,6 +512,19 @@ static void compile_ast_stmtseq(hIrChunk ck, sbAst seqast, flag implicit_return)
 
   sbAst considering = seqast;
   while (considering != NO_NODE) {
+    sbAst node = considering->seq.left;
+    if (considering->seq.left->type == AST_NODE_DEF) {
+      /* DEF is guaranteed to have a value bound to it always. to allow functions
+       * to call each other forward and backward, we'll scan each scope for DEF nodes
+       * and make sure those names are available through the whole scope. */
+      const char *vname = sbSymbol_name(node->seq.left->symb);
+      create_var(ck, vname, strlen(vname));
+    }
+    considering = considering->seq.right;
+  }
+
+  considering = seqast;
+  while (considering != NO_NODE) {
     /* walk down the tree a line at a time */
     if (considering->type != AST_NODE_SEQ) {
       PANIC("compile_ast_stmtseq expects a SEQ node! (received instead #%d)", seqast->type);
@@ -731,7 +721,7 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) {
       if (body->type != AST_NODE_SEQ) PANIC("expected SEQ node as function body!");
 
       const char *vname = sbSymbol_name(node->seq.left->symb);
-      V1 = create_var(ck, vname, strlen(vname));
+      V1 = var_name(ck, vname, strlen(vname));
       C1 = compile_ast_function(ck->program, params, body);
       E1 = expr_func(ck, C1);
       put_assign(ck, V1, E1);
@@ -928,116 +918,3 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node) {
     PANIC("expr todo! (%d)", node->type);
   }
 }
-
-static void print_var(sbIrVariable *v) {
-  if (v->closed_over) {
-    debug("special ");
-  }
-
-  if (v->is_upvalue) {
-    debug("upvalue %zu", v->slot_id);
-  } else {
-    debug("variable %zu", v->slot_id);
-  }
-}
-
-static void print_expr(sbIrExpr *e) {
-  debug("(");
-  switch(e->type) {
-    case IR_E_VAR:
-      print_var(e->var);
-      break;
-    case IR_E_FUNC:
-      debug("{ chunk %d ", e->func.chunk->id);
-      if (e->func.bound.size > 0) {
-        debug("closing over ");
-        BUFFER_ITER(e->func.bound, sbIrVariable*, var) {
-          print_var(*var);
-          debug(", ");
-        }
-      }
-      debug("}");
-      break;
-    case IR_E_VALUE:
-      if (e->value.type == IT_NIL) {
-        debug("nil");
-      } else if (e->value.type == IT_INTEGER) {
-        debug("%lld", e->value.integer);
-      } else {
-        debug("some value");
-      }
-      break;
-    case IR_E_OP:
-      print_expr(e->op.left);
-      debug(" %c ", e->op.type);
-      if (e->op.right) {
-        print_expr(e->op.right);
-      }
-      break;
-    case IR_E_CALL:
-      debug("CALL: ");
-      print_expr(e->call.func);
-      debug(" with params ");
-      sbIrExpr *param = e->call.param;
-      while (param) {
-        print_expr(param->list.this);
-        debug(",");
-        param = param->list.next;
-      }
-      break;
-    default:
-      debug("something");
-  }
-  debug(")");
-}
-
-static void print_stmt(sbIrStmt *s) {
-  switch (s->type) {
-    case IR_S_ARG:
-      debug("  bind function argument to ");
-      print_var(s->arg.var);
-      debug("\n");
-      if (s->arg.last) {
-        debug("  (no function arguments left on the stack now)\n");
-      }
-      break;
-    case IR_S_LABEL:
-      debug("label %zu:\n", s->label->id);
-      break;
-    case IR_S_JUMP:
-      debug("  jump to label %zu", s->jump.label->id);
-      if (s->jump.condition) {
-        if (s->jump.inverted) {
-          debug(" unless ");
-        } else {
-          debug(" if ");
-        }
-        print_expr(s->jump.condition);
-      }
-      debug("\n");
-      break;
-    case IR_S_RETURN:
-      debug("  return ");
-      if (s->expr) {
-        print_expr(s->expr);
-      } else {
-        debug("NOTHING");
-      }
-      debug("\n");
-      break;
-    case IR_S_EXPR:
-      debug("  ");
-      print_expr(s->expr);
-      debug("\n");
-      break;
-    case IR_S_ASSIGN:
-      debug("  ");
-      print_var(s->assign.var);
-      debug(" = ");
-      print_expr(s->assign.expr);
-      debug("\n");
-      break;
-    default:
-      debug("  (do something)\n");
-  }
-}
diff --git a/src/compile/print_ir.c b/src/compile/print_ir.c
new file mode 100644 (file)
index 0000000..bb5cbb8
--- /dev/null
@@ -0,0 +1,139 @@
+#include "compile/ir.h"
+
+static void print_stmt(sbIrStmt *s);
+
+void sbIrProgram_print(hIrProgram ir) {
+  usize nchunks = ir->chunks.size / sizeof(sbIrChunk*);
+
+  for (usize i = 0; i < nchunks; i++) {
+    sbIrChunk *ck = ((sbIrChunk**)ir->chunks.data)[i];
+    debug("\nCHUNK %d with %d variables", ck->id, ck->variable_count);
+    if (ck->num_upvalues > 0) {
+      debug(" and %d upvalues", ck->num_upvalues);
+    }
+    debug("\n");
+
+    usize nstmts = ck->stmts.size / sizeof(sbIrStmt*);
+    for (usize j = 0; j < nstmts; j++) {
+      sbIrStmt *s = ((sbIrStmt**)ck->stmts.data)[j];
+      print_stmt(s);
+    }
+
+    debug("\n");
+  }
+}
+
+/* --- */
+
+static void print_var(sbIrVariable *v) {
+  if (v->closed_over) {
+    debug("special ");
+  }
+
+  if (v->is_upvalue) {
+    debug("upvalue %zu", v->slot_id);
+  } else {
+    debug("variable %zu", v->slot_id);
+  }
+}
+
+static void print_expr(sbIrExpr *e) {
+  debug("(");
+  switch(e->type) {
+    case IR_E_VAR:
+      print_var(e->var);
+      break;
+    case IR_E_FUNC:
+      debug("{ chunk %d ", e->func.chunk->id);
+      if (e->func.bound.size > 0) {
+        debug("closing over ");
+        BUFFER_ITER(e->func.bound, sbIrVariable*, var) {
+          print_var(*var);
+          debug(", ");
+        }
+      }
+      debug("}");
+      break;
+    case IR_E_VALUE:
+      if (e->value.type == IT_NIL) {
+        debug("nil");
+      } else if (e->value.type == IT_INTEGER) {
+        debug("%lld", e->value.integer);
+      } else {
+        debug("some value");
+      }
+      break;
+    case IR_E_OP:
+      print_expr(e->op.left);
+      debug(" %c ", e->op.type);
+      if (e->op.right) {
+        print_expr(e->op.right);
+      }
+      break;
+    case IR_E_CALL:
+      debug("CALL: ");
+      print_expr(e->call.func);
+      debug(" with params ");
+      sbIrExpr *param = e->call.param;
+      while (param) {
+        print_expr(param->list.this);
+        debug(",");
+        param = param->list.next;
+      }
+      break;
+    default:
+      debug("something");
+  }
+  debug(")");
+}
+
+static void print_stmt(sbIrStmt *s) {
+  switch (s->type) {
+    case IR_S_ARG:
+      debug("  bind function argument to ");
+      print_var(s->arg.var);
+      debug("\n");
+      if (s->arg.last) {
+        debug("  (no function arguments left on the stack now)\n");
+      }
+      break;
+    case IR_S_LABEL:
+      debug("label %zu:\n", s->label->id);
+      break;
+    case IR_S_JUMP:
+      debug("  jump to label %zu", s->jump.label->id);
+      if (s->jump.condition) {
+        if (s->jump.inverted) {
+          debug(" unless ");
+        } else {
+          debug(" if ");
+        }
+        print_expr(s->jump.condition);
+      }
+      debug("\n");
+      break;
+    case IR_S_RETURN:
+      debug("  return ");
+      if (s->expr) {
+        print_expr(s->expr);
+      } else {
+        debug("NOTHING");
+      }
+      debug("\n");
+      break;
+    case IR_S_EXPR:
+      debug("  ");
+      print_expr(s->expr);
+      debug("\n");
+      break;
+    case IR_S_ASSIGN:
+      debug("  ");
+      print_var(s->assign.var);
+      debug(" = ");
+      print_expr(s->assign.expr);
+      debug("\n");
+      break;
+    default:
+      debug("  (do something)\n");
+  }
+}
index 6eb6cb5..d6ae47b 100644 (file)
@@ -9,6 +9,7 @@ typedef enum sbOpcode {
   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_REF,            // push reference value onto stack from variable
   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
index fd2f688..bef5fa2 100644 (file)
@@ -199,6 +199,16 @@ void execute_instruction(hVm vm) {
       param = get_param(vm);
       push_stack(vm, &vm->fp->locals[param]);
       break;
+    case BC_LD_REF:
+      param = get_param(vm);
+      w = &vm->fp->locals[param];
+      if (w->type == IT_NOTHING) {
+        /* create new ref */
+        store_local(vm, param, &HVREF(sbRef_create(&HVNIL)));
+        w = &vm->fp->locals[param];
+      }
+      push_stack(vm, w);
+      break;
     case BC_LD_UPVAL:
       /* Hey, was there upval in there? */
       param = get_param(vm);