git » larquil.git » commit c0815ab

Generate runtime classes from Larquil

author Alan
2026-05-01 22:20:51 UTC
committer Alan
2026-05-01 22:20:51 UTC
parent 22547bfb54299952962df70faf5e71da02b2f870

Generate runtime classes from Larquil

Makefile +9 -7
bootstrap/stage1-compiler.jar +0 -0
specs/stage0.md +20 -2
specs/stage1.md +4 -21
stage1/classfile.lql +132 -1
stage1/compiler.lql +4 -0
stage1/emit.lql +7 -0
stage1/runtime.lql +704 -0
test/run.sh +1 -1

diff --git a/Makefile b/Makefile
index 5b2214a..208afd8 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,6 @@ BOOTSTRAP_TEST_DIR := $(BUILD_DIR)/bootstrap-test
 BOOTSTRAP_DIR := bootstrap
 BOOTSTRAP_JAR := $(BOOTSTRAP_DIR)/stage1-compiler.jar
 MAIN_CLASS := com.tailrecursion.larquil.stage0.Stage1Main
-RUNTIME_CLASSES := com/tailrecursion/larquil/stage0/IFunction.class com/tailrecursion/larquil/stage0/Symbol.class com/tailrecursion/larquil/stage0/Stage1Main.class
 
 .PHONY: all build test clean compile-example run-example stage1-build stage1-test stage1-self-host bootstrap-artifact bootstrap-test
 
@@ -27,7 +26,7 @@ stage1-build: build
 	for f in stage1/core.lql stage1/munge.lql; do \
 		$(JAVA) -cp $(BOOTSTRAP_JAR) $(MAIN_CLASS) $$f $(STAGE1_DIR); \
 	done
-	cat stage1/core.lql stage1/munge.lql stage1/forms.lql stage1/reader.lql stage1/classfile.lql stage1/emit.lql stage1/instructions.lql stage1/backend.lql stage1/compiler.lql > $(STAGE1_DIR)/compiler.lql
+	cat stage1/core.lql stage1/munge.lql stage1/forms.lql stage1/reader.lql stage1/classfile.lql stage1/emit.lql stage1/instructions.lql stage1/backend.lql stage1/runtime.lql stage1/compiler.lql > $(STAGE1_DIR)/compiler.lql
 	$(JAVA) -cp $(BOOTSTRAP_JAR) $(MAIN_CLASS) $(STAGE1_DIR)/compiler.lql $(STAGE1_DIR)
 
 stage1-test: stage1-build
@@ -43,18 +42,21 @@ stage1-self-host: stage1-build
 
 bootstrap-artifact: stage1-self-host
 	mkdir -p $(BOOTSTRAP_DIR)
-	tmp=$$(mktemp -d); \
-	(cd $$tmp && jar xf $(abspath $(BOOTSTRAP_JAR)) $(RUNTIME_CLASSES)); \
 	rm -f $(BOOTSTRAP_JAR); \
-	jar --create --file $(BOOTSTRAP_JAR) -C $$tmp . -C $(STAGE3_DIR) .; \
-	rm -rf $$tmp
+	jar --create --file $(BOOTSTRAP_JAR) -C $(STAGE3_DIR) .
 
 bootstrap-test: build
 	rm -rf $(BOOTSTRAP_TEST_DIR)
 	mkdir -p $(BOOTSTRAP_TEST_DIR)
-	cat stage1/core.lql stage1/munge.lql stage1/forms.lql stage1/reader.lql stage1/classfile.lql stage1/emit.lql stage1/instructions.lql stage1/backend.lql stage1/compiler.lql > $(BOOTSTRAP_TEST_DIR)/compiler.lql
+	cat stage1/core.lql stage1/munge.lql stage1/forms.lql stage1/reader.lql stage1/classfile.lql stage1/emit.lql stage1/instructions.lql stage1/backend.lql stage1/runtime.lql stage1/compiler.lql > $(BOOTSTRAP_TEST_DIR)/compiler.lql
 	$(JAVA) -cp $(BOOTSTRAP_JAR) $(MAIN_CLASS) $(BOOTSTRAP_TEST_DIR)/compiler.lql $(BOOTSTRAP_TEST_DIR)
 	sh test/run-stage1.sh $(BOOTSTRAP_TEST_DIR)
+	for c in com/tailrecursion/larquil/stage0/IFunction.class com/tailrecursion/larquil/stage0/Symbol.class com/tailrecursion/larquil/stage0/Stage1Main.class; do \
+		jar tf $(BOOTSTRAP_JAR) | grep -qx $$c; \
+	done
+	if javap -classpath $(BOOTSTRAP_JAR) -v com.tailrecursion.larquil.stage0.IFunction com.tailrecursion.larquil.stage0.Symbol com.tailrecursion.larquil.stage0.Stage1Main | grep -E 'SourceFile|Compiled from'; then \
+		exit 1; \
+	fi
 
 compile-example: build
 	rm -rf $(GEN_DIR)
diff --git a/bootstrap/stage1-compiler.jar b/bootstrap/stage1-compiler.jar
index 275ff03..29389ec 100644
Binary files a/bootstrap/stage1-compiler.jar and b/bootstrap/stage1-compiler.jar differ
diff --git a/specs/stage0.md b/specs/stage0.md
index 3f80046..89d5eb9 100644
--- a/specs/stage0.md
+++ b/specs/stage0.md
@@ -148,7 +148,7 @@ There is no general Larquil expression evaluation inside stage 0 function bodies
 
 # Compilation Model
 
-For `foo.lql`, the compiler emits:
+For `foo.lql`, the source-level class mapping is:
 
 - loader class: `com.tailrecursion.larquil.stage0.foo`
 - one helper function class per named top-level function, for example:
@@ -160,11 +160,29 @@ Class files are written under the package path `com/tailrecursion/larquil/stage0
 File names and function names are converted to JVM class names with deterministic Clojure-style munging.
 Path separators are not part of the generated class name.
 
-Stage 0-compatible generated classes depend on these runtime support classes from the bootstrap artifact:
+Stage 0-compatible generated classes depend on these runtime support classes:
 
 - `IFunction.class`
 - `Symbol.class`
 
+These runtime classes are reserved names under `com/tailrecursion/larquil/stage0/`.
+They are not produced by adding source-level `class` or `interface` forms to Stage 0.
+
+## Bootstrap Artifact Generation
+
+The ordinary source mapping above remains limited to loader and helper function classes.
+To make the bootstrap artifact self-contained, the compiler may also write reserved runtime and launcher classes into its output directory:
+
+- `IFunction.class`
+- `Symbol.class`
+- `Stage1Main.class`
+
+Those classes are produced by Larquil-compiled generator functions in the compiler.
+The generator functions use the Larquil-owned classfile writer and run during compiler output assembly.
+
+This is a bootstrap mechanism, not a general source-level class-definition feature.
+No ordinary `.lql` source form maps directly to `IFunction`, `Symbol`, or `Stage1Main`.
+
 Example loader shape:
 
 ```java
diff --git a/specs/stage1.md b/specs/stage1.md
index 1c44d02..219f789 100644
--- a/specs/stage1.md
+++ b/specs/stage1.md
@@ -106,24 +106,6 @@ Current temporary support:
   Replacement: Larquil-shaped list or simple record representation
   Removal condition: Stage 1 form analysis and instruction lowering agree on the Larquil representation
 
-- Name: `IFunction`
-  Step: runtime invocation ABI
-  Reason: generated function classes still implement the Java bootstrap interface
-  Replacement: Larquil-generated `IFunction.class`
-  Removal condition: the bootstrap artifact ships an equivalent generated interface
-
-- Name: `Symbol`
-  Step: reader/runtime symbol values
-  Reason: parsed symbols still use the Java runtime class
-  Replacement: Larquil-generated `Symbol.class`
-  Removal condition: generated `Symbol` preserves interning, `name`, equality, hash, and string conversion
-
-- Name: `Stage1Main`
-  Step: command-line launcher
-  Reason: Makefile/tests still need a small Java entry point to call the compiler
-  Replacement: Larquil-generated launcher class
-  Removal condition: build/test targets invoke the generated launcher without compiling Java sources
-
 - Name: CLI/file bridge
   Step: Stage 1 entrypoint
   Reason: Stage 0 source does not yet model `String[] args` and file IO cleanly
@@ -639,12 +621,13 @@ Done when:
 
 # Current Chunk
 
-The classfile backend is now Larquil-owned:
+The bootstrap artifact is now fully Larquil-generated:
 
 - `stage1/classfile.lql` owns class records, constant-pool helpers, method records, labels, branch patching, and classfile writing
 - `stage1/emit.lql` emits through those Larquil records
-- the refreshed bootstrap artifact has no references to the removed Java classfile backend
+- `stage1/runtime.lql` emits `IFunction`, `Symbol`, and `Stage1Main` as reserved runtime/launcher classes
+- `bootstrap-artifact` jars stage3 output directly instead of preserving seeded runtime classes
 
 Next best chunk:
 
-- replace the runtime `.class` seeds in `bootstrap/stage1-compiler.jar` with Larquil-generated equivalents
+- continue shrinking the host boundary around file IO and command-line entry
diff --git a/stage1/classfile.lql b/stage1/classfile.lql
index 47005da..c966cc2 100644
--- a/stage1/classfile.lql
+++ b/stage1/classfile.lql
@@ -232,6 +232,11 @@
   (invoke 0)
   (invoke 2)
   (pop)
+  (load-function rec_add)
+  (load cf)
+  (lconst 49)
+  (invoke 2)
+  (pop)
   (load-function cp_class)
   (load cf)
   (load name)
@@ -293,6 +298,21 @@
   (checkcast "java/util/ArrayList")
   (return))
 
+(function class_access (cf)
+  (load-function rec_get)
+  (load cf)
+  (lconst 6)
+  (invoke 2)
+  (return))
+
+(function class_set_access (cf access)
+  (load-function rec_set)
+  (load cf)
+  (lconst 6)
+  (load access)
+  (invoke 3)
+  (return))
+
 (function cp_lookup (cf key)
   (load-function class_cp_index)
   (load cf)
@@ -688,6 +708,18 @@
   (aconst-null)
   (return))
 
+(function class_add_abstract_method (cf access name desc)
+  (load-function class_add_method)
+  (load cf)
+  (load-function method_abstract)
+  (load cf)
+  (load access)
+  (load name)
+  (load desc)
+  (invoke 4)
+  (invoke 2)
+  (return))
+
 (function writer ()
   (new "java/io/ByteArrayOutputStream")
   (dup)
@@ -957,7 +989,9 @@
   (label cp_done)
   (load-function write_u2)
   (load w)
-  (lconst 49)
+  (load-function class_access)
+  (load cf)
+  (invoke 1)
   (invoke 2)
   (pop)
   (load-function write_u2)
@@ -1228,6 +1262,72 @@
   (load m)
   (return))
 
+(function method_abstract (cf access name desc)
+  (new "java/util/ArrayList")
+  (dup)
+  (invokespecial "java/util/ArrayList" "<init>" "()V")
+  (store m)
+  (load-function rec_add)
+  (load m)
+  (load cf)
+  (invoke 2)
+  (pop)
+  (load-function rec_add)
+  (load m)
+  (load access)
+  (invoke 2)
+  (pop)
+  (load-function rec_add)
+  (load m)
+  (load-function cp_utf8)
+  (load cf)
+  (load name)
+  (invoke 2)
+  (invoke 2)
+  (pop)
+  (load-function rec_add)
+  (load m)
+  (load-function cp_utf8)
+  (load cf)
+  (load desc)
+  (invoke 2)
+  (invoke 2)
+  (pop)
+  (load-function rec_add)
+  (load m)
+  (aconst-null)
+  (invoke 2)
+  (pop)
+  (load-function rec_add)
+  (load m)
+  (load-function new_list)
+  (invoke 0)
+  (invoke 2)
+  (pop)
+  (load-function rec_add)
+  (load m)
+  (load-function new_list)
+  (invoke 0)
+  (invoke 2)
+  (pop)
+  (load-function rec_add)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function rec_add)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function rec_add)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load m)
+  (return))
+
 (function method_cf (m)
   (load-function rec_get)
   (load m)
@@ -1552,6 +1652,10 @@
   (return))
 
 (function method_write (m w)
+  (load-function method_code_buffer)
+  (load m)
+  (invoke 1)
+  (jump-if-false write_abstract)
   (load-function finish_labels)
   (load m)
   (invoke 1)
@@ -1640,4 +1744,31 @@
   (load w)
   (lconst 0)
   (invoke 2)
+  (return)
+  (label write_abstract)
+  (load-function write_u2)
+  (load w)
+  (load-function method_access)
+  (load m)
+  (invoke 1)
+  (invoke 2)
+  (pop)
+  (load-function write_u2)
+  (load w)
+  (load-function method_name_index)
+  (load m)
+  (invoke 1)
+  (invoke 2)
+  (pop)
+  (load-function write_u2)
+  (load w)
+  (load-function method_desc_index)
+  (load m)
+  (invoke 1)
+  (invoke 2)
+  (pop)
+  (load-function write_u2)
+  (load w)
+  (lconst 0)
+  (invoke 2)
   (return))
diff --git a/stage1/compiler.lql b/stage1/compiler.lql
index eb6274a..baf0114 100644
--- a/stage1/compiler.lql
+++ b/stage1/compiler.lql
@@ -55,6 +55,10 @@
   (return))
 
 (function compile_forms (forms input outdir)
+  (load-function write_runtime_classes)
+  (load outdir)
+  (invoke 1)
+  (pop)
   (load-function file_base)
   (load input)
   (invoke 1)
diff --git a/stage1/emit.lql b/stage1/emit.lql
index f464735..f1a7328 100644
--- a/stage1/emit.lql
+++ b/stage1/emit.lql
@@ -346,6 +346,13 @@
   (invoke 2)
   (return))
 
+(function emit_ireturn (m)
+  (load-function emit_u1)
+  (load m)
+  (lconst 172)
+  (invoke 2)
+  (return))
+
 (function emit_pop (m)
   (load-function emit_u1)
   (load m)
diff --git a/stage1/runtime.lql b/stage1/runtime.lql
new file mode 100644
index 0000000..f1b6bc9
--- /dev/null
+++ b/stage1/runtime.lql
@@ -0,0 +1,704 @@
+; Bootstrap runtime and launcher class generators.
+; These produce reserved artifact classes by running Larquil-compiled code.
+
+(function emit_runtime_constructor (cf class_name access)
+  (load-function method_code)
+  (load cf)
+  (load access)
+  (sconst "<init>")
+  (sconst "(Ljava/lang/String;)V")
+  (invoke 4)
+  (store m)
+  (load-function method_set_max_locals)
+  (load m)
+  (lconst 2)
+  (invoke 2)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_invokespecial)
+  (load m)
+  (sconst "java/lang/Object")
+  (sconst "<init>")
+  (sconst "()V")
+  (invoke 4)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_putfield)
+  (load m)
+  (load class_name)
+  (sconst "name")
+  (sconst "Ljava/lang/String;")
+  (invoke 4)
+  (pop)
+  (load-function emit_return_void)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function class_add_method)
+  (load cf)
+  (load m)
+  (invoke 2)
+  (return))
+
+(function emit_runtime_ifunction ()
+  (load-function class_file)
+  (sconst "com/tailrecursion/larquil/stage0/IFunction")
+  (invoke 1)
+  (store cf)
+  (load-function class_set_access)
+  (load cf)
+  (lconst 1537)
+  (invoke 2)
+  (pop)
+  (load-function class_add_abstract_method)
+  (load cf)
+  (lconst 1025)
+  (sconst "invoke")
+  (sconst "([Ljava/lang/Object;)Ljava/lang/Object;")
+  (invoke 4)
+  (pop)
+  (load cf)
+  (return))
+
+(function emit_symbol_clinit (cf class_name)
+  (load-function method_code)
+  (load cf)
+  (lconst 8)
+  (sconst "<clinit>")
+  (sconst "()V")
+  (invoke 4)
+  (store m)
+  (load-function emit_new)
+  (load m)
+  (sconst "java/util/HashMap")
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 89)
+  (invoke 2)
+  (pop)
+  (load-function emit_invokespecial)
+  (load m)
+  (sconst "java/util/HashMap")
+  (sconst "<init>")
+  (sconst "()V")
+  (invoke 4)
+  (pop)
+  (load-function emit_putstatic)
+  (load m)
+  (load class_name)
+  (sconst "INTERNED")
+  (sconst "Ljava/util/HashMap;")
+  (invoke 4)
+  (pop)
+  (load-function emit_return_void)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function class_add_method)
+  (load cf)
+  (load m)
+  (invoke 2)
+  (return))
+
+(function emit_symbol_intern (cf class_name)
+  (load-function method_code)
+  (load cf)
+  (lconst 9)
+  (sconst "intern")
+  (sconst "(Ljava/lang/String;)Lcom/tailrecursion/larquil/stage0/Symbol;")
+  (invoke 4)
+  (store m)
+  (load-function method_set_max_locals)
+  (load m)
+  (lconst 3)
+  (invoke 2)
+  (pop)
+  (load-function emit_getstatic)
+  (load m)
+  (load class_name)
+  (sconst "INTERNED")
+  (sconst "Ljava/util/HashMap;")
+  (invoke 4)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_invokevirtual)
+  (load m)
+  (sconst "java/util/HashMap")
+  (sconst "get")
+  (sconst "(Ljava/lang/Object;)Ljava/lang/Object;")
+  (invoke 4)
+  (pop)
+  (load-function emit_checkcast)
+  (load m)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load-function emit_astore)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_branch)
+  (load m)
+  (lconst 198)
+  (sconst "create")
+  (invoke 3)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_areturn)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function emit_label)
+  (load m)
+  (sconst "create")
+  (invoke 2)
+  (pop)
+  (load-function emit_new)
+  (load m)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 89)
+  (invoke 2)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_invokespecial)
+  (load m)
+  (load class_name)
+  (sconst "<init>")
+  (sconst "(Ljava/lang/String;)V")
+  (invoke 4)
+  (pop)
+  (load-function emit_astore)
+  (load m)
+  (lconst 2)
+  (invoke 2)
+  (pop)
+  (load-function emit_getstatic)
+  (load m)
+  (load class_name)
+  (sconst "INTERNED")
+  (sconst "Ljava/util/HashMap;")
+  (invoke 4)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 2)
+  (invoke 2)
+  (pop)
+  (load-function emit_invokevirtual)
+  (load m)
+  (sconst "java/util/HashMap")
+  (sconst "put")
+  (sconst "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")
+  (invoke 4)
+  (pop)
+  (load-function emit_pop)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 2)
+  (invoke 2)
+  (pop)
+  (load-function emit_areturn)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function class_add_method)
+  (load cf)
+  (load m)
+  (invoke 2)
+  (return))
+
+(function emit_symbol_equals (cf class_name)
+  (load-function method_code)
+  (load cf)
+  (lconst 1)
+  (sconst "equals")
+  (sconst "(Ljava/lang/Object;)Z")
+  (invoke 4)
+  (store m)
+  (load-function emit_aload)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_instanceof)
+  (load m)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load-function emit_branch)
+  (load m)
+  (lconst 153)
+  (sconst "false")
+  (invoke 3)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_getfield)
+  (load m)
+  (load class_name)
+  (sconst "name")
+  (sconst "Ljava/lang/String;")
+  (invoke 4)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_checkcast)
+  (load m)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load-function emit_getfield)
+  (load m)
+  (load class_name)
+  (sconst "name")
+  (sconst "Ljava/lang/String;")
+  (invoke 4)
+  (pop)
+  (load-function emit_invokevirtual)
+  (load m)
+  (sconst "java/lang/String")
+  (sconst "equals")
+  (sconst "(Ljava/lang/Object;)Z")
+  (invoke 4)
+  (pop)
+  (load-function emit_branch)
+  (load m)
+  (lconst 153)
+  (sconst "false")
+  (invoke 3)
+  (pop)
+  (load-function emit_push_int)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_ireturn)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function emit_label)
+  (load m)
+  (sconst "false")
+  (invoke 2)
+  (pop)
+  (load-function emit_push_int)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_ireturn)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function class_add_method)
+  (load cf)
+  (load m)
+  (invoke 2)
+  (return))
+
+(function emit_symbol_hash_code (cf class_name)
+  (load-function method_code)
+  (load cf)
+  (lconst 1)
+  (sconst "hashCode")
+  (sconst "()I")
+  (invoke 4)
+  (store m)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_getfield)
+  (load m)
+  (load class_name)
+  (sconst "name")
+  (sconst "Ljava/lang/String;")
+  (invoke 4)
+  (pop)
+  (load-function emit_invokevirtual)
+  (load m)
+  (sconst "java/lang/String")
+  (sconst "hashCode")
+  (sconst "()I")
+  (invoke 4)
+  (pop)
+  (load-function emit_ireturn)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function class_add_method)
+  (load cf)
+  (load m)
+  (invoke 2)
+  (return))
+
+(function emit_symbol_to_string (cf class_name)
+  (load-function method_code)
+  (load cf)
+  (lconst 1)
+  (sconst "toString")
+  (sconst "()Ljava/lang/String;")
+  (invoke 4)
+  (store m)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_getfield)
+  (load m)
+  (load class_name)
+  (sconst "name")
+  (sconst "Ljava/lang/String;")
+  (invoke 4)
+  (pop)
+  (load-function emit_areturn)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function class_add_method)
+  (load cf)
+  (load m)
+  (invoke 2)
+  (return))
+
+(function emit_runtime_symbol ()
+  (sconst "com/tailrecursion/larquil/stage0/Symbol")
+  (store class_name)
+  (load-function class_file)
+  (load class_name)
+  (invoke 1)
+  (store cf)
+  (load-function class_add_field)
+  (load cf)
+  (lconst 26)
+  (sconst "INTERNED")
+  (sconst "Ljava/util/HashMap;")
+  (invoke 4)
+  (pop)
+  (load-function class_add_field)
+  (load cf)
+  (lconst 17)
+  (sconst "name")
+  (sconst "Ljava/lang/String;")
+  (invoke 4)
+  (pop)
+  (load-function emit_runtime_constructor)
+  (load cf)
+  (load class_name)
+  (lconst 2)
+  (invoke 3)
+  (pop)
+  (load-function emit_symbol_clinit)
+  (load cf)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load-function emit_symbol_intern)
+  (load cf)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load-function emit_symbol_equals)
+  (load cf)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load-function emit_symbol_hash_code)
+  (load cf)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load-function emit_symbol_to_string)
+  (load cf)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load cf)
+  (return))
+
+(function emit_runtime_stage1main_constructor (cf)
+  (load-function method_code)
+  (load cf)
+  (lconst 1)
+  (sconst "<init>")
+  (sconst "()V")
+  (invoke 4)
+  (store m)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_invokespecial)
+  (load m)
+  (sconst "java/lang/Object")
+  (sconst "<init>")
+  (sconst "()V")
+  (invoke 4)
+  (pop)
+  (load-function emit_return_void)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function class_add_method)
+  (load cf)
+  (load m)
+  (invoke 2)
+  (return))
+
+(function emit_runtime_stage1main_main (cf class_name)
+  (load-function method_code)
+  (load cf)
+  (lconst 9)
+  (sconst "main")
+  (sconst "([Ljava/lang/String;)V")
+  (invoke 4)
+  (store m)
+  (load-function method_set_max_locals)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 190)
+  (invoke 2)
+  (pop)
+  (load-function emit_push_int)
+  (load m)
+  (lconst 2)
+  (invoke 2)
+  (pop)
+  (load-function emit_branch)
+  (load m)
+  (lconst 159)
+  (sconst "arity_ok")
+  (invoke 3)
+  (pop)
+  (load-function emit_new)
+  (load m)
+  (sconst "java/lang/IllegalArgumentException")
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 89)
+  (invoke 2)
+  (pop)
+  (load-function emit_sconst)
+  (load m)
+  (sconst "usage: Stage1Main input.lql outdir")
+  (invoke 2)
+  (pop)
+  (load-function emit_invokespecial)
+  (load m)
+  (sconst "java/lang/IllegalArgumentException")
+  (sconst "<init>")
+  (sconst "(Ljava/lang/String;)V")
+  (invoke 4)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 191)
+  (invoke 2)
+  (pop)
+  (load-function emit_label)
+  (load m)
+  (sconst "arity_ok")
+  (invoke 2)
+  (pop)
+  (load-function emit_getstatic)
+  (load m)
+  (sconst "com/tailrecursion/larquil/stage0/compiler__compile_file")
+  (sconst "INSTANCE")
+  (sconst "Lcom/tailrecursion/larquil/stage0/compiler__compile_file;")
+  (invoke 4)
+  (pop)
+  (load-function emit_push_int)
+  (load m)
+  (lconst 2)
+  (invoke 2)
+  (pop)
+  (load-function emit_anewarray)
+  (load m)
+  (sconst "java/lang/Object")
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 89)
+  (invoke 2)
+  (pop)
+  (load-function emit_push_int)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_push_int)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 50)
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 83)
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 89)
+  (invoke 2)
+  (pop)
+  (load-function emit_push_int)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_aload)
+  (load m)
+  (lconst 0)
+  (invoke 2)
+  (pop)
+  (load-function emit_push_int)
+  (load m)
+  (lconst 1)
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 50)
+  (invoke 2)
+  (pop)
+  (load-function emit_u1)
+  (load m)
+  (lconst 83)
+  (invoke 2)
+  (pop)
+  (load-function emit_invokeinterface)
+  (load m)
+  (sconst "com/tailrecursion/larquil/stage0/IFunction")
+  (sconst "invoke")
+  (sconst "([Ljava/lang/Object;)Ljava/lang/Object;")
+  (lconst 2)
+  (invoke 5)
+  (pop)
+  (load-function emit_pop)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function emit_return_void)
+  (load m)
+  (invoke 1)
+  (pop)
+  (load-function class_add_method)
+  (load cf)
+  (load m)
+  (invoke 2)
+  (return))
+
+(function emit_runtime_stage1main ()
+  (sconst "com/tailrecursion/larquil/stage0/Stage1Main")
+  (store class_name)
+  (load-function class_file)
+  (load class_name)
+  (invoke 1)
+  (store cf)
+  (load-function emit_runtime_stage1main_constructor)
+  (load cf)
+  (invoke 1)
+  (pop)
+  (load-function emit_runtime_stage1main_main)
+  (load cf)
+  (load class_name)
+  (invoke 2)
+  (pop)
+  (load cf)
+  (return))
+
+(function write_runtime_classes (outdir)
+  (load-function write_class)
+  (load outdir)
+  (load-function emit_runtime_ifunction)
+  (invoke 0)
+  (invoke 2)
+  (pop)
+  (load-function write_class)
+  (load outdir)
+  (load-function emit_runtime_symbol)
+  (invoke 0)
+  (invoke 2)
+  (pop)
+  (load-function write_class)
+  (load outdir)
+  (load-function emit_runtime_stage1main)
+  (invoke 0)
+  (invoke 2)
+  (return))
diff --git a/test/run.sh b/test/run.sh
index 9177041..74506f2 100755
--- a/test/run.sh
+++ b/test/run.sh
@@ -106,7 +106,7 @@ ok "stage1 munge class files"
 
 aggregate="$OUT/stage1_compiler_src/compiler.lql"
 mkdir -p "$OUT/stage1_compiler_src"
-cat "$ROOT/stage1/core.lql" "$ROOT/stage1/munge.lql" "$ROOT/stage1/forms.lql" "$ROOT/stage1/reader.lql" "$ROOT/stage1/classfile.lql" "$ROOT/stage1/emit.lql" "$ROOT/stage1/instructions.lql" "$ROOT/stage1/backend.lql" "$ROOT/stage1/compiler.lql" > "$aggregate"
+cat "$ROOT/stage1/core.lql" "$ROOT/stage1/munge.lql" "$ROOT/stage1/forms.lql" "$ROOT/stage1/reader.lql" "$ROOT/stage1/classfile.lql" "$ROOT/stage1/emit.lql" "$ROOT/stage1/instructions.lql" "$ROOT/stage1/backend.lql" "$ROOT/stage1/runtime.lql" "$ROOT/stage1/compiler.lql" > "$aggregate"
 compile_ok stage1_compiler "$aggregate"
 test -f "$OUT/stage1_compiler/com/tailrecursion/larquil/stage0/compiler__is_named_function.class" || fail "stage1 aggregate helper class exists"
 ok "stage1 aggregate class files"