about summary refs log tree commit diff stats
path: root/subx
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2019-02-18 21:48:19 -0800
committerKartik Agaram <vc@akkartik.com>2019-02-18 21:48:19 -0800
commit8188bbbc9408322c18cdc2d1049126900abf793c (patch)
tree2141b32bc1b7df6106980e12f0edbacc7dcfd329 /subx
parent6607a30415e2bede27c43b57ce3c5cbc42278fa9 (diff)
downloadmu-8188bbbc9408322c18cdc2d1049126900abf793c.tar.gz
4981 - no, go back to 3 phases
Considering how much trouble a merge phase would be (commit 4978), it seems
simpler to just add the extra syntax for controlling the entry point of
the generated ELF binary.

But I wouldn't have noticed this if I hadn't taken the time to write out
the commit messages of 4976 and 4978.

Even if we happened to already have linked list primitives built, this
may still be a good idea considering that I'm saving quite a lot of code
in duplicated entrypoints.
Diffstat (limited to 'subx')
-rw-r--r--subx/011run.cc16
-rw-r--r--subx/028translate.cc7
-rw-r--r--subx/034compute_segment_address.cc32
-rw-r--r--subx/035labels.cc23
-rw-r--r--subx/050_write.subx4
-rw-r--r--subx/051test.subx2
-rw-r--r--subx/052kernel-string-equal.subx2
-rw-r--r--subx/053new-segment.subx2
-rw-r--r--subx/054string-equal.subx3
-rw-r--r--subx/055stream.subx7
-rw-r--r--subx/056trace.subx8
-rw-r--r--subx/057write.subx7
-rw-r--r--subx/058stream-equal.subx11
-rw-r--r--subx/059stop.subx11
-rw-r--r--subx/060read.subx7
-rw-r--r--subx/061read-byte.subx11
-rw-r--r--subx/062write-stream.subx14
-rw-r--r--subx/063error.subx7
-rw-r--r--subx/064write-byte.subx7
-rw-r--r--subx/065hex.subx7
-rw-r--r--subx/066print-byte.subx7
-rw-r--r--subx/067write-buffered.subx11
-rw-r--r--subx/068error-byte.subx13
-rw-r--r--subx/069allocate.subx7
-rw-r--r--subx/070new-stream.subx7
-rw-r--r--subx/071read-line.subx7
-rw-r--r--subx/072slice.subx11
-rw-r--r--subx/073next-token.subx11
-rw-r--r--subx/Readme.md29
-rwxr-xr-xsubx/apps/crenshaw2-1bin18370 -> 18028 bytes
-rw-r--r--subx/apps/crenshaw2-1.subx3
-rwxr-xr-xsubx/apps/crenshaw2-1bbin18929 -> 18587 bytes
-rw-r--r--subx/apps/crenshaw2-1b.subx3
-rwxr-xr-xsubx/apps/factorialbin17288 -> 16946 bytes
-rw-r--r--subx/apps/factorial.subx7
-rwxr-xr-xsubx/apps/handlebin18081 -> 17721 bytes
-rw-r--r--subx/apps/handle.subx7
-rwxr-xr-xsubx/apps/hexbin21349 -> 21007 bytes
-rw-r--r--subx/apps/hex.subx5
-rw-r--r--subx/apps/merge.subx36
-rwxr-xr-xsubx/apps/packbin21526 -> 21184 bytes
-rw-r--r--subx/apps/pack.subx5
-rw-r--r--subx/examples/ex10.subx2
-rw-r--r--subx/examples/ex11.subx2
-rw-r--r--subx/examples/ex5.subx3
45 files changed, 130 insertions, 234 deletions
diff --git a/subx/011run.cc b/subx/011run.cc
index 61d93186..45a6699e 100644
--- a/subx/011run.cc
+++ b/subx/011run.cc
@@ -15,17 +15,13 @@ put_new(Help, "syntax",
   "Each segment starts with a header line: a '==' delimiter followed by the name of\n"
   "the segment.\n"
   "\n"
-  "Currently SubX assumes the first segment encountered contains executable code, and\n"
-  "the second contains global variables. By convention we call them 'code' and 'data'\n"
-  "respectively.\n"
-  "The first instruction executed in the resulting binary is always the first\n"
-  "instruction of the first segment.\n"
+  "The first segment contains code and should be called 'code'.\n"
+  "The second segment should be called 'data'.\n"
+  "The resulting binary starts running from the start of the code segment by default.\n"
+  "To start elsewhere in the code segment, define a special label called 'Entry'.\n"
   "\n"
   "Segments with the same name get merged together. This rule helps keep functions and\n"
   "their data close together in .subx files.\n"
-  "Later segments with the same name get their contents *prepended* to earlier ones.\n"
-  "This rule helps each .subx file to put forth a different entrypoint for the binary,\n"
-  "overriding previously loaded files.\n"
   "\n"
   "Lines consist of a series of words. Words can contain arbitrary metadata\n"
   "after a '/', but they can never contain whitespace. Metadata has no effect\n"
@@ -184,7 +180,7 @@ void flush(program& p, vector<line>& lines) {
     return;
   }
   // End flush(p, lines) Special-cases
-  trace(99, "parse") << "flushing to segment" << end();
+  trace(99, "parse") << "flushing segment" << end();
   p.segments.back().lines.swap(lines);
 }
 
@@ -228,6 +224,7 @@ typedef void (*transform_fn)(program&);
 :(before "End Globals")
 vector<transform_fn> Transform;
 
+:(code)
 void transform(program& p) {
   trace(99, "transform") << "begin" << end();
   for (int t = 0;  t < SIZE(Transform);  ++t)
@@ -267,6 +264,7 @@ void load(const program& p) {
     if (i == 0) End_of_program = addr;
   }
   EIP = p.segments.at(0).start;
+  // End Initialize EIP
   trace(99, "load") << "done" << end();
 }
 
diff --git a/subx/028translate.cc b/subx/028translate.cc
index 2104d57b..d4b1a3a5 100644
--- a/subx/028translate.cc
+++ b/subx/028translate.cc
@@ -97,13 +97,14 @@ void write_elf_header(ostream& out, const program& p) {
   // e_version
   O(0x01); O(0x00); O(0x00); O(0x00);
   // e_entry
-  int e_entry = p.segments.at(0).start;  // convention
+  uint32_t e_entry = p.segments.at(0).start;  // convention
+  // Override e_entry
   emit(e_entry);
   // e_phoff -- immediately after ELF header
-  int e_phoff = 0x34;
+  uint32_t e_phoff = 0x34;
   emit(e_phoff);
   // e_shoff; unused
-  int dummy32 = 0;
+  uint32_t dummy32 = 0;
   emit(dummy32);
   // e_flags; unused
   emit(dummy32);
diff --git a/subx/034compute_segment_address.cc b/subx/034compute_segment_address.cc
index d37060a4..2798e040 100644
--- a/subx/034compute_segment_address.cc
+++ b/subx/034compute_segment_address.cc
@@ -17,8 +17,6 @@
 //: Update the parser to handle non-numeric segment name.
 //:
 //: We'll also support repeated segments with non-numeric names.
-//: When we encounter a new reference to an existing segment we'll *prepend*
-//: the new data to existing data for the segment.
 
 :(before "End Globals")
 map</*name*/string, int> Segment_index;
@@ -46,7 +44,7 @@ if (!starts_with(segment_title, "0x")) {
     out.segments.push_back(segment());
   }
   else {
-    trace(99, "parse") << "prepending to segment '" << segment_title << "'" << end();
+    trace(99, "parse") << "appending to segment '" << segment_title << "'" << end();
   }
   Currently_parsing_segment_index = get(Segment_index, segment_title);
 }
@@ -54,9 +52,9 @@ if (!starts_with(segment_title, "0x")) {
 :(before "End flush(p, lines) Special-cases")
 if (Currently_parsing_named_segment) {
   assert(!p.segments.empty());
-  trace(99, "parse") << "flushing to segment" << end();
+  trace(99, "parse") << "flushing segment" << end();
   vector<line>& curr_segment_data = p.segments.at(Currently_parsing_segment_index).lines;
-  curr_segment_data.insert(curr_segment_data.begin(), lines.begin(), lines.end());
+  curr_segment_data.insert(curr_segment_data.end(), lines.begin(), lines.end());
   lines.clear();
   Currently_parsing_named_segment = false;
   Currently_parsing_segment_index = -1;
@@ -69,17 +67,19 @@ if (Currently_parsing_named_segment) {
 == code
 2d/subtract-from-EAX  0xddccbbaa/imm32
 +parse: new segment 'code'
-+parse: prepending to segment 'code'
-+load: 0x09000054 -> 2d
-+load: 0x09000055 -> aa
-+load: 0x09000056 -> bb
-+load: 0x09000057 -> cc
-+load: 0x09000058 -> dd
-+load: 0x09000059 -> 05
-+load: 0x0900005a -> 0a
-+load: 0x0900005b -> 0b
-+load: 0x0900005c -> 0c
-+load: 0x0900005d -> 0d
++parse: appending to segment 'code'
+# first segment
++load: 0x09000054 -> 05
++load: 0x09000055 -> 0a
++load: 0x09000056 -> 0b
++load: 0x09000057 -> 0c
++load: 0x09000058 -> 0d
+# second segment
++load: 0x09000059 -> 2d
++load: 0x0900005a -> aa
++load: 0x0900005b -> bb
++load: 0x0900005c -> cc
++load: 0x0900005d -> dd
 
 :(scenario error_on_missing_segment_header)
 % Hide_errors = true;
diff --git a/subx/035labels.cc b/subx/035labels.cc
index 5531910a..e86441cf 100644
--- a/subx/035labels.cc
+++ b/subx/035labels.cc
@@ -19,6 +19,25 @@
 //: Later layers may add more conventions partitioning the space of names. But
 //: the above rules will remain inviolate.
 
+//: One special label: the address to start running the program at.
+
+:(scenario entry_label)
+== 0x1
+05 0x0d0c0b0a/imm32
+Entry:
+05 0x0d0c0b0a/imm32
++run: inst: 0x00000006
+-run: inst: 0x00000001
+
+:(before "End Globals")
+uint32_t Entry_address = 0;
+:(before "End Reset")
+Entry_address = 0;
+:(before "End Initialize EIP")
+if (Entry_address) EIP = Entry_address;
+:(after "Override e_entry")
+if (Entry_address) e_entry = Entry_address;
+
 :(before "End looks_like_hex_int(s) Detectors")
 if (SIZE(s) == 2) return true;
 
@@ -87,6 +106,8 @@ void rewrite_labels(program& p) {
   drop_labels(code);
   if (trace_contains_errors()) return;
   replace_labels_with_displacements(code, byte_index);
+  if (contains_key(byte_index, "Entry"))
+    Entry_address = code.start + get(byte_index, "Entry");
 }
 
 void compute_byte_indices_for_labels(const segment& code, map<string, int32_t>& byte_index) {
@@ -125,7 +146,7 @@ void compute_byte_indices_for_labels(const segment& code, map<string, int32_t>&
           raise << "'" << to_string(inst) << "': labels can only be the first word in a line.\n" << end();
         if (Map_file.is_open())
           Map_file << "0x" << HEXWORD << (code.start + current_byte) << ' ' << label << '\n';
-        if (contains_key(byte_index, label)) {
+        if (contains_key(byte_index, label) && label != "Entry") {
           raise << "duplicate label '" << label << "'\n" << end();
           return;
         }
diff --git a/subx/050_write.subx b/subx/050_write.subx
index 58cfde7c..083adad6 100644
--- a/subx/050_write.subx
+++ b/subx/050_write.subx
@@ -5,8 +5,8 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    # syscall(exit, 0) -- can't test _write just yet
+Entry:  # just exit; can't test _write just yet
+    # . syscall(exit, 0)
     bb/copy-to-EBX  0/imm32
     b8/copy-to-EAX  1/imm32/exit
     cd/syscall  0x80/imm8
diff --git a/subx/051test.subx b/subx/051test.subx
index ceccc076..6f8cda7a 100644
--- a/subx/051test.subx
+++ b/subx/051test.subx
@@ -5,7 +5,7 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:  (manual test if this is the last file loaded)
+Entry:  # manual test
     # check-ints-equal(34, 34)
     # . . push args
     68/push  "error in check-ints-equal"/imm32
diff --git a/subx/052kernel-string-equal.subx b/subx/052kernel-string-equal.subx
index 47e1e296..55d6c50a 100644
--- a/subx/052kernel-string-equal.subx
+++ b/subx/052kernel-string-equal.subx
@@ -19,7 +19,7 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
+Entry:  # run all tests
     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
     # syscall(exit, Num-test-failures)
     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
diff --git a/subx/053new-segment.subx b/subx/053new-segment.subx
index fd7203b9..1669b097 100644
--- a/subx/053new-segment.subx
+++ b/subx/053new-segment.subx
@@ -5,7 +5,7 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:  (manual test if this is the last file loaded)
+Entry:   # manual test
     # EAX = new-segment(0x1000)
     # . . push args
     68/push  0x1000/imm32
diff --git a/subx/054string-equal.subx b/subx/054string-equal.subx
index 607a8447..f5818e79 100644
--- a/subx/054string-equal.subx
+++ b/subx/054string-equal.subx
@@ -5,8 +5,7 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    # run-tests()
+Entry:  # run all tests
 #?     e8/call test-compare-equal-strings/disp32
     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
     # syscall(exit, Num-test-failures)
diff --git a/subx/055stream.subx b/subx/055stream.subx
index f5d24876..92049634 100644
--- a/subx/055stream.subx
+++ b/subx/055stream.subx
@@ -14,13 +14,6 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 clear-stream:  # f : (address stream) -> <void>
     # . prolog
     55/push-EBP
diff --git a/subx/056trace.subx b/subx/056trace.subx
index 479569a7..f181e1d7 100644
--- a/subx/056trace.subx
+++ b/subx/056trace.subx
@@ -41,14 +41,6 @@ _test-trace-stream:
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    # run-tests()
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 # Allocate a new segment for the trace stream, initialize its length, and save its address to Trace-stream.
 # The Trace-stream segment will consist of variable-length lines separated by newlines (0x0a)
 initialize-trace-stream:
diff --git a/subx/057write.subx b/subx/057write.subx
index 2a111dd3..3135003b 100644
--- a/subx/057write.subx
+++ b/subx/057write.subx
@@ -20,13 +20,6 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 # TODO: come up with a way to signal when a write to disk fails
 write:  # f : fd or (address stream), s : (address array byte) -> <void>
     # . prolog
diff --git a/subx/058stream-equal.subx b/subx/058stream-equal.subx
index 203e5415..ee04d4eb 100644
--- a/subx/058stream-equal.subx
+++ b/subx/058stream-equal.subx
@@ -5,13 +5,12 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
+#? Entry:  # run a single test, while debugging
 #?     e8/call test-next-stream-line-equal-stops-at-newline/disp32
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
+#?     # syscall(exit, Num-test-failures)
+#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+#?     b8/copy-to-EAX  1/imm32/exit
+#?     cd/syscall  0x80/imm8
 
 # compare all the data in a stream (ignoring the read pointer)
 stream-data-equal?:  # f : (address stream), s : (address string) -> EAX : boolean
diff --git a/subx/059stop.subx b/subx/059stop.subx
index 4478dab3..eca66bcb 100644
--- a/subx/059stop.subx
+++ b/subx/059stop.subx
@@ -37,13 +37,12 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
+#? Entry:  # run a single test, while debugging
 #?     e8/call test-stop-skips-returns-on-exit/disp32
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
+#?     # syscall(exit, Num-test-failures)
+#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+#?     b8/copy-to-EAX  1/imm32/exit
+#?     cd/syscall  0x80/imm8
 
 # Configure an exit-descriptor for a call pushing 'nbytes' bytes of args to
 # the stack.
diff --git a/subx/060read.subx b/subx/060read.subx
index fcf38dde..9f5f7486 100644
--- a/subx/060read.subx
+++ b/subx/060read.subx
@@ -45,13 +45,6 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 read:  # f : fd or (address stream), s : (address stream) -> num-bytes-read/EAX
     # . prolog
     55/push-EBP
diff --git a/subx/061read-byte.subx b/subx/061read-byte.subx
index d3192ce1..26b63e63 100644
--- a/subx/061read-byte.subx
+++ b/subx/061read-byte.subx
@@ -31,14 +31,13 @@ Stdin:
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
+#? Entry:  # run a single test, while debugging
 #?     e8/call test-read-byte-multiple/disp32
 #?     e8/call test-read-byte-refills-buffer/disp32
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
+#?     # syscall(exit, Num-test-failures)
+#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+#?     b8/copy-to-EAX  1/imm32/exit
+#?     cd/syscall  0x80/imm8
 
 # return next byte value in EAX, with top 3 bytes cleared.
 # On EOF, return 0xffffffff.
diff --git a/subx/062write-stream.subx b/subx/062write-stream.subx
index b273336b..73766fe3 100644
--- a/subx/062write-stream.subx
+++ b/subx/062write-stream.subx
@@ -5,19 +5,15 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    # manual test
+#? Entry:  # manual test
 #?     # write-stream(stdout, _test-stream2)
 #?     68/push  _test-stream2/imm32
 #?     68/push  1/imm32/stdout
 #?     e8/call write-stream/disp32
-    # automatic test
-#?     e8/call test-write-stream-single/disp32
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
+#?     # syscall(exit, Num-test-failures)
+#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+#?     b8/copy-to-EAX  1/imm32/exit
+#?     cd/syscall  0x80/imm8
 
 write-stream:  # f : fd or (address stream), s : (address stream) -> <void>
     # . prolog
diff --git a/subx/063error.subx b/subx/063error.subx
index efda8ea3..db460db1 100644
--- a/subx/063error.subx
+++ b/subx/063error.subx
@@ -5,13 +5,6 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 # write(out, "Error: "+msg+"\n") then stop(ed, 1)
 error:  # ed : (address exit-descriptor), out : fd or (address stream), msg : (address array byte) -> <void>
     # . prolog
diff --git a/subx/064write-byte.subx b/subx/064write-byte.subx
index 61ed4197..9f97c336 100644
--- a/subx/064write-byte.subx
+++ b/subx/064write-byte.subx
@@ -27,13 +27,6 @@ Stdout:
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 # Write lower byte of 'n' to 'f'.
 write-byte:  # f : (address buffered-file), n : int -> <void>
     # . prolog
diff --git a/subx/065hex.subx b/subx/065hex.subx
index 9096680b..a5a4bdac 100644
--- a/subx/065hex.subx
+++ b/subx/065hex.subx
@@ -6,13 +6,6 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 is-hex-int?:  # in : (address slice) -> EAX : boolean
     # . prolog
     55/push-EBP
diff --git a/subx/066print-byte.subx b/subx/066print-byte.subx
index 3335bcbf..39d7793e 100644
--- a/subx/066print-byte.subx
+++ b/subx/066print-byte.subx
@@ -5,13 +5,6 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 print-byte:  # f : (address buffered-file), n : int -> <void>
     # . prolog
     55/push-EBP
diff --git a/subx/067write-buffered.subx b/subx/067write-buffered.subx
index 94ed4f8a..76ae4313 100644
--- a/subx/067write-buffered.subx
+++ b/subx/067write-buffered.subx
@@ -5,14 +5,13 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
+#? Entry:  # run a single test, while debugging
 #?     e8/call test-write-buffered/disp32
 #?     e8/call test-write-buffered-with-intermediate-flush/disp32
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
+#?     # syscall(exit, Num-test-failures)
+#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+#?     b8/copy-to-EAX  1/imm32/exit
+#?     cd/syscall  0x80/imm8
 
 write-buffered:  # f : (address buffered-file), msg : (address array byte) -> <void>
     # pseudocode:
diff --git a/subx/068error-byte.subx b/subx/068error-byte.subx
index 1bf77be5..252aff20 100644
--- a/subx/068error-byte.subx
+++ b/subx/068error-byte.subx
@@ -5,8 +5,7 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    # manual test
+#? Entry:  # manual test
 #?     # . var ed/EAX : exit-descriptor
 #?     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 #?     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
@@ -19,12 +18,10 @@
 #?     68/push  Stderr/imm32
 #?     50/push-EAX
 #?     e8/call  error-byte/disp32
-    # automatic test
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # . syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
+#?     # . syscall(exit, Num-test-failures)
+#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+#?     b8/copy-to-EAX  1/imm32/exit
+#?     cd/syscall  0x80/imm8
 
 # write(out, "Error: "+msg+": "+byte) then stop(ed, 1)
 error-byte:  # ed : (address exit-descriptor), out : (address buffered-file), msg : (address array byte), n : byte -> <void>
diff --git a/subx/069allocate.subx b/subx/069allocate.subx
index 7efeb678..fe471ed9 100644
--- a/subx/069allocate.subx
+++ b/subx/069allocate.subx
@@ -29,13 +29,6 @@ Heap:
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 # Claim the next 'n' bytes of memory starting at ad->curr and update ad->curr.
 # If there isn't enough memory before ad->limit, return 0 and leave 'ad' unmodified.
 allocate:  # ad : (address allocation-descriptor), n : int -> address-or-null/EAX
diff --git a/subx/070new-stream.subx b/subx/070new-stream.subx
index e6f2e0cb..ad6ab68d 100644
--- a/subx/070new-stream.subx
+++ b/subx/070new-stream.subx
@@ -5,13 +5,6 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 new-stream:  # ad : (address allocation-descriptor), length : int, elemsize : int -> address/EAX
     # . prolog
     55/push-EBP
diff --git a/subx/071read-line.subx b/subx/071read-line.subx
index 61513aa9..bb0c03be 100644
--- a/subx/071read-line.subx
+++ b/subx/071read-line.subx
@@ -3,13 +3,6 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
 # read bytes from 'f' until (and including) a newline and store them into 's'
 # return true if no data found, false otherwise
 # just abort if 's' is too small
diff --git a/subx/072slice.subx b/subx/072slice.subx
index 54049b6d..c8834719 100644
--- a/subx/072slice.subx
+++ b/subx/072slice.subx
@@ -6,14 +6,13 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
+#? Entry:  # run a single test, while debugging
 #?     e8/call test-slice-starts-with-fails/disp32
 #?     e8/call test-slice-starts-with-single-character/disp32
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
+#?     # syscall(exit, Num-test-failures)
+#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+#?     b8/copy-to-EAX  1/imm32/exit
+#?     cd/syscall  0x80/imm8
 
 slice-empty?:  # s : (address slice) -> EAX : boolean
     # . prolog
diff --git a/subx/073next-token.subx b/subx/073next-token.subx
index 41dd94f3..1d37ba70 100644
--- a/subx/073next-token.subx
+++ b/subx/073next-token.subx
@@ -3,13 +3,12 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
+#? Entry:  # run a single test, while debugging
 #? e8/call test-next-token-from-slice/disp32
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
+#?     # syscall(exit, Num-test-failures)
+#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+#?     b8/copy-to-EAX  1/imm32/exit
+#?     cd/syscall  0x80/imm8
 
 # extract the next run of characters that are different from a given 'delimiter' (skipping multiple delimiters if necessary)
 # on eof return an empty interval
diff --git a/subx/Readme.md b/subx/Readme.md
index f95be08b..8891e8ee 100644
--- a/subx/Readme.md
+++ b/subx/Readme.md
@@ -278,7 +278,7 @@ distinguish between code and data. Correspondingly, SubX programs consist of a
 series of segments, each starting with a header line: `==` followed by a name.
 The first segment must be named `code`; the second must be named `data`.
 
-Execution always begins at the start of the `code` segment.
+Execution begins at the start of the `code` segment by default.
 
 You can reuse segment names:
 
@@ -293,17 +293,9 @@ You can reuse segment names:
 ...C...
 ```
 
-The code segment now contains the instructions of `A` as well as `C`. `C`
-comes _before_ `A`. (Why this order? I'd like to organize SubX programs into
-sequences of [_layers_](http://akkartik.name/post/wart-layers) that permit
-incrementally building a subset of layers into a working program with a subset
-of functionality. This organization requires, among other things, letting each
-layer control the code that runs when the binary starts up. Letting each layer
-override the starting address would require additional syntax. Instead, I
-choose to always begin execution at the start of the code segment, but allow
-layers to prepend to segments.)
-
-Within the code segment, each line contains a comment, label or instruction.
+The `code` segment now contains the instructions of `A` as well as `C`.
+
+Within the `code` segment, each line contains a comment, label or instruction.
 Comments start with a `#` and are ignored. Labels should always be the first
 word on a line, and they end with a `:`.
 
@@ -346,11 +338,18 @@ The latter is mostly useful for `jump` and `call` instructions.
 
 Functions are defined using labels. By convention, labels internal to functions
 (that must only be jumped to) start with a `$`. Any other labels must only be
-called, never jumped to.
+called, never jumped to. All labels must be unique.
+
+A special label is `Entry`, which can be used to specify/override the entry
+point of the program. It doesn't have to be unique, and the latest definition
+will override earlier ones.
+
+(The `Entry` label, along with duplicate segment headers, allows programs to
+be built up incrementally out of multiple _layers_](http://akkartik.name/post/wart-layers).)
 
 The data segment consists of labels as before and byte values. Referring to
-data labels in either code segment instructions or data segment values (using
-the `imm32` metadata either way) yields their address.
+data labels in either `code` segment instructions or `data` segment values
+(using the `imm32` metadata either way) yields their address.
 
 Automatic tests are an important part of SubX, and there's a simple mechanism
 to provide a test harness: all functions that start with `test-` are called in
diff --git a/subx/apps/crenshaw2-1 b/subx/apps/crenshaw2-1
index 99d08ba4..6588d8e7 100755
--- a/subx/apps/crenshaw2-1
+++ b/subx/apps/crenshaw2-1
Binary files differdiff --git a/subx/apps/crenshaw2-1.subx b/subx/apps/crenshaw2-1.subx
index ad8dad46..8acdde59 100644
--- a/subx/apps/crenshaw2-1.subx
+++ b/subx/apps/crenshaw2-1.subx
@@ -30,11 +30,12 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
+Entry:  # run tests if necessary, call 'compile' if not
+
 #?     # for debugging: run a single test; don't bother setting status code
 #?     e8/call test-get-num-reads-single-digit/disp32
 #?     eb/jump  $main:end/disp8
 
-# main: run tests if necessary, call 'compile' if not
     # . prolog
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # - if argc > 1 and argv[1] == "test", then return run_tests()
diff --git a/subx/apps/crenshaw2-1b b/subx/apps/crenshaw2-1b
index ae66b7b4..08c5f45e 100755
--- a/subx/apps/crenshaw2-1b
+++ b/subx/apps/crenshaw2-1b
Binary files differdiff --git a/subx/apps/crenshaw2-1b.subx b/subx/apps/crenshaw2-1b.subx
index 044221eb..55689cca 100644
--- a/subx/apps/crenshaw2-1b.subx
+++ b/subx/apps/crenshaw2-1b.subx
@@ -30,11 +30,12 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
+Entry:  # run tests if necessary, call 'compile' if not
+
 #?     # for debugging: run a single test; don't bother setting status code
 #?     e8/call test-get-num-reads-single-digit/disp32
 #?     eb/jump  $main:end/disp8
 
-# main: run tests if necessary, call 'compile' if not
     # . prolog
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # - if argc > 1 and argv[1] == "test", then return run_tests()
diff --git a/subx/apps/factorial b/subx/apps/factorial
index 90193370..d8cd6766 100755
--- a/subx/apps/factorial
+++ b/subx/apps/factorial
Binary files differdiff --git a/subx/apps/factorial.subx b/subx/apps/factorial.subx
index 912285e2..8f4bf47d 100644
--- a/subx/apps/factorial.subx
+++ b/subx/apps/factorial.subx
@@ -18,7 +18,12 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
+Entry:  # run tests if necessary, compute `factorial(5)` if not
+
+#?     # for debugging: run a single test; don't bother setting status code
+#?     e8/call test-get-num-reads-single-digit/disp32
+#?     eb/jump  $main:end/disp8
+
     # . prolog
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # - if argc > 1 and argv[1] == "test", then return run_tests()
diff --git a/subx/apps/handle b/subx/apps/handle
index 5708c864..fe9e4cce 100755
--- a/subx/apps/handle
+++ b/subx/apps/handle
Binary files differdiff --git a/subx/apps/handle.subx b/subx/apps/handle.subx
index 3848eb76..8f82759a 100644
--- a/subx/apps/handle.subx
+++ b/subx/apps/handle.subx
@@ -25,12 +25,7 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
+# no Entry; the standard library runs all tests by default
 
 new:  # ad : (address allocation-descriptor), n : int, out : (address handle)
     # . prolog
diff --git a/subx/apps/hex b/subx/apps/hex
index dcc77d5c..8ace80be 100755
--- a/subx/apps/hex
+++ b/subx/apps/hex
Binary files differdiff --git a/subx/apps/hex.subx b/subx/apps/hex.subx
index 49ca3bd4..4f65d03b 100644
--- a/subx/apps/hex.subx
+++ b/subx/apps/hex.subx
@@ -17,12 +17,13 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-    # for debugging: run a single test
+Entry:  # run tests if necessary, convert stdin if not
+
+#?     # for debugging: run a single test
 #?     e8/call test-skip-until-newline/disp32
 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 #?     eb/jump  $main:end/disp8
 
-# main: run tests if necessary, convert stdin if not
     # . prolog
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # - if argc > 1 and argv[1] == "test", then return run_tests()
diff --git a/subx/apps/merge.subx b/subx/apps/merge.subx
deleted file mode 100644
index a5ecc987..00000000
--- a/subx/apps/merge.subx
+++ /dev/null
@@ -1,36 +0,0 @@
-# Read a text file of SubX segment 'fragments' with duplicate names, and emit
-# a list of 'merged' segments.
-#
-# Example input:
-#   == A
-#   a
-#   b
-#   c
-#   == B
-#   d
-#   e
-#   == A
-#   f
-#   g
-#   == A
-#   h
-#
-# Output:
-#   == A
-#   h
-#   f
-#   g
-#   a
-#   b
-#   c
-#   == B
-#   d
-#   e
-#
-# The output gives each segment the contents of all its fragments, with later
-# fragments *prepended* to earlier ones.
-#
-# Prepending necessitates buffering output until the end. We'll convert
-# fragments to distinct streams, maintain each segment as a linked list of
-# fragments that's easy to prepend to, and finally emit the linked lists in
-# order.
diff --git a/subx/apps/pack b/subx/apps/pack
index b4d04abe..a16daf5b 100755
--- a/subx/apps/pack
+++ b/subx/apps/pack
Binary files differdiff --git a/subx/apps/pack.subx b/subx/apps/pack.subx
index 755f3d79..fa383042 100644
--- a/subx/apps/pack.subx
+++ b/subx/apps/pack.subx
@@ -20,12 +20,13 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-    # for debugging: run a single test
+Entry:  # run tests if necessary, convert stdin if not
+
+#?     # for debugging: run a single test
 #?     e8/call test-convert-instruction-passes-labels-through/disp32
 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 #?     eb/jump  $main:end/disp8
 
-# main: run tests if necessary, convert stdin if not
     # . prolog
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # - if argc > 1 and argv[1] == "test", then return run_tests()
diff --git a/subx/examples/ex10.subx b/subx/examples/ex10.subx
index 7e37a54b..7b88f582 100644
--- a/subx/examples/ex10.subx
+++ b/subx/examples/ex10.subx
@@ -12,7 +12,7 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main: return argv-equal(argv[1], argv[2])
+Entry:  # return argv-equal(argv[1], argv[2])
 #       At the start of a SubX program:
 #         argc: *ESP
 #         argv[0]: *(ESP+4)
diff --git a/subx/examples/ex11.subx b/subx/examples/ex11.subx
index 071a855b..a40c68d4 100644
--- a/subx/examples/ex11.subx
+++ b/subx/examples/ex11.subx
@@ -19,7 +19,7 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
+Entry:  # run all tests
     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
     # syscall(exit, EAX)
     89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
diff --git a/subx/examples/ex5.subx b/subx/examples/ex5.subx
index 2e421fdf..f9ad9ea8 100644
--- a/subx/examples/ex5.subx
+++ b/subx/examples/ex5.subx
@@ -9,7 +9,8 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
-# main:
+Entry:
+
     # allocate x on the stack
     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # subtract from ESP