about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2021-05-14 11:54:42 -0700
committerKartik K. Agaram <vc@akkartik.com>2021-05-14 11:54:42 -0700
commitff8ec9bcff7577ba923fe7868ea62ecceed55ee7 (patch)
tree23039cf2da691aa221f64e7054f9ff035c537251
parent2df1d1a73046a0a7b6523552b53f407651ed36df (diff)
downloadmu-ff8ec9bcff7577ba923fe7868ea62ecceed55ee7.tar.gz
insert a compile phase to emit some debug info
-rwxr-xr-xlinux/labels_baremetalbin0 -> 51490 bytes
-rw-r--r--linux/labels_baremetal.subx2831
-rwxr-xr-xlinux/survey_baremetalbin53111 -> 47436 bytes
-rw-r--r--linux/survey_baremetal.subx2353
-rwxr-xr-xlinux/test_apps4
-rwxr-xr-xlinux/test_apps_emulated4
-rwxr-xr-xtranslate_subx17
-rwxr-xr-xtranslate_subx_emulated26
8 files changed, 2918 insertions, 2317 deletions
diff --git a/linux/labels_baremetal b/linux/labels_baremetal
new file mode 100755
index 00000000..f6fc997e
--- /dev/null
+++ b/linux/labels_baremetal
Binary files differdiff --git a/linux/labels_baremetal.subx b/linux/labels_baremetal.subx
new file mode 100644
index 00000000..fa94a5a8
--- /dev/null
+++ b/linux/labels_baremetal.subx
@@ -0,0 +1,2831 @@
+# Assign addresses (co-ordinates) to instructions (landmarks) in a program
+# (landscape).
+# Read a list of addresses and labels from a file called 'labels',
+# then replace labels in stdin with their addresses.
+#
+# To build:
+#   $ bootstrap/bootstrap translate [01]*.subx subx-params.subx labels_baremetal.subx  -o labels_baremetal
+#
+# Stdin should be a stream of bytes and some interspersed labels. Comments and
+# '==' segment headers are allowed, but names are ignored. The emitted code
+# will all lie in a single contiguous address range starting at address 0x7c00.
+# Addresses in segment headers are optional. If provided, this program will
+# insert padding in the output until the desired address is reached.
+#
+#   $ cat x
+#   == code
+#   l1:
+#   aa bb l1/imm8
+#   cc dd l2/disp32
+#   l2:
+#   ee foo/imm32
+#   == data 0x7c10
+#   foo:
+#     34
+#
+# The output is the stream of bytes without segment headers or label definitions,
+# and with label references replaced with numeric values/displacements.
+#
+#   $ cat x  |bootstrap/bootstrap run survey_baremetal labels
+#   # 0x7c00
+#   aa bb nn  # some computed address
+#   cc dd nn nn nn nn  # some computed displacement
+#   ee nn nn nn nn  # address right after this instruction
+#   # 0x7c0e
+#   00 00  # padding
+#   # 0x7c10
+#   34  # data segment interleaved with code
+
+== code
+#   instruction                     effective address                                                   register    displacement    immediate
+# . 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, convert stdin if not
+    # . prologue
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+
+    # Heap = new-segment(Heap-size)
+    # . . push args
+    68/push  Heap/imm32
+    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
+    # . . call
+    e8/call  new-segment/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # initialize-trace-stream(Trace-size)
+    # . . push args
+    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-size/disp32                 # push *Heap-size
+    # . . call
+    e8/call  initialize-trace-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+
+    # - if argc > 1 and argv[1] == "test", then return run_tests()
+    # if (argc <= 1) goto interactive
+    81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
+    7e/jump-if-<=  $subx-labels-main:interactive/disp8
+    # if (!kernel-string-equal?(argv[1], "test")) goto interactive
+    # . eax = kernel-string-equal?(argv[1], "test")
+    # . . push args
+    68/push  "test"/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  kernel-string-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax == false) goto interactive
+    3d/compare-eax-and  0/imm32/false
+    74/jump-if-=  $subx-labels-main:interactive/disp8
+    # run-tests()
+#?     e8/call  test-emit-output-with-padding/disp32
+    e8/call  run-tests/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
+    eb/jump  $subx-labels-main:end/disp8
+$subx-labels-main:interactive:
+    # - otherwise convert stdin
+    # var labels-file/esi: (addr buffered-file) from syscall_open("labels", READ)
+    # . data
+    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x1000/imm32      # subtract from esp
+    # . size
+    68/push  0x1000/imm32
+    # . read
+    68/push  0/imm32/read
+    # . write
+    68/push  0/imm32/write
+    # . fd = syscall_open("labels", READ)
+    bb/copy-to-ebx  Label-file/imm32
+    b9/copy-to-ecx  0/imm32/read-mode
+    ba/copy-to-edx  0x180/imm32/permissions
+    e8/call syscall_open/disp32
+    50/push-eax
+    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
+    # subx-labels(Stdin, label-fd, Stdout)
+    # . . push args
+    68/push  Stdout/imm32
+    56/push-esi
+    68/push  Stdin/imm32
+    # . . call
+    e8/call  subx-labels/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-stream(2/stderr, Trace-stream)
+#?     # . . push args
+#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # syscall(exit, 0)
+    bb/copy-to-ebx  0/imm32
+$subx-labels-main:end:
+    e8/call  syscall_exit/disp32
+
+== data
+
+Label-file:
+  6c/l 61/a 62/b 65/e 6c/l 73/s 00/nul
+
+== code
+
+subx-labels:  # infile: (addr buffered-file), labels-file: (addr buffered-file), out: (addr buffered-file)
+    # pseudocode
+    #   var labels: (stream {label-name, address} Max-labels)
+    #   load-labels(labels-file, labels)
+    #   var in: (stream byte Input-size)
+    #   slurp(infile, in)
+    #   emit-output(in, out, labels)
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    52/push-edx
+    56/push-esi
+    # var labels/edx: (stream {label-name, address} Max-labels)
+    # (we get more rows than Max-labels advertises because row size is smaller than in survey_elf)
+    # . data
+    2b/subtract                     0/mod/indirect  5/rm32/.disp32            .             .           4/r32/esp   Max-labels/disp32                 # subtract *Max-labels from esp
+    # . size
+    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Max-labels/disp32                 # push *Max-labels
+    # . read
+    68/push  0/imm32/read
+    # . write
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
+    # load-labels(labels-file, labels)
+    # . . push args
+    52/push-edx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  load-labels/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # var in/esi: (stream byte Input-size)
+    # . data
+    2b/subtract                     0/mod/indirect  5/rm32/.disp32            .             .           4/r32/esp   Input-size/disp32                 # subtract *Input-size from esp
+    # . size
+    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Input-size/disp32                 # push *Input-size
+    # . read
+    68/push  0/imm32/read
+    # . write
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
+    # slurp(infile, in)
+    # . . push args
+    56/push-esi
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  slurp/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # emit-output(in, out, labels)
+    # . . push args
+    52/push-edx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
+    56/push-esi
+    # . . call
+    e8/call  emit-output/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # flush(out)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+$subx-labels:end:
+    # . reclaim locals
+    03/add                          0/mod/indirect  5/rm32/.disp32            .             .           4/r32/esp   Max-labels/disp32                 # add *Max-labels to esp
+    03/add                          0/mod/indirect  5/rm32/.disp32            .             .           4/r32/esp   Input-size/disp32                 # add *Input-size to esp
+    # . restore registers
+    5e/pop-to-esi
+    5a/pop-to-edx
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+load-labels:  # in: (addr buffered-file), labels: (stream {label-name, address} Max-labels)
+    # pseudocode
+    #   var line: (stream byte 512)
+    #   while true
+    #     clear-stream(line)
+    #     read-line-buffered(in, line)
+    #     if (line->write == 0) break               # end of file
+    #     var word-slice/ecx: (addr slice) = next-word(line)
+    #     var dest/edi: (addr int) = get-or-insert-slice(labels, word-slice, 12 bytes/row)
+    #     word-slice = next-word(line)
+    #     var address/eax: int = parse-hex-int-from-slice(word-slice)
+    #     *dest = address
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    52/push-edx
+    56/push-esi
+    57/push-edi
+    # var line/ecx: (stream byte 512)
+    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x200/imm32       # subtract from esp
+    68/push  0x200/imm32/size
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # var word-slice/edx: slice
+    68/push  0/imm32/end
+    68/push  0/imm32/start
+    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
+$load-labels:loop:
+    # clear-stream(line)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # read-line-buffered(in, line)
+    # . . push args
+    51/push-ecx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  read-line-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # dump line {{{
+#?     # . write(2/stderr, "LL: ")
+#?     # . . push args
+#?     68/push  "LL: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # write-stream(2/stderr, line)
+#?     # . . push args
+#?     51/push-ecx
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . rewind-stream(line)
+#?     # . . push args
+#?     51/push-ecx
+#?     # . . call
+#?     e8/call  rewind-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # }}}
+    # if (line->write == 0) break
+    81          7/subop/compare     0/mod/indirect  1/rm32/ecx    .           .             .           .           .               0/imm32           # compare *ecx
+    0f 84/jump-if-=  $load-labels:end/disp32
+    # next-word(line, word-slice)
+    # . . push args
+    52/push-edx
+    51/push-ecx
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # dump word-slice {{{
+#?     # . write(2/stderr, "w: ")
+#?     # . . push args
+#?     68/push  "w: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-slice-buffered(Stderr, word-slice)
+#?     # . . push args
+#?     52/push-edx
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-slice-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . flush(Stderr)
+#?     # . . push args
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  flush/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # }}}
+    # var dest/edi: (addr int) = get-or-insert-slice(labels, word-slice, 12 bytes/row, Heap)
+    # . eax = get-or-insert-slice(labels, word-slice, 12 bytes/row, Heap)
+    # . . push args
+    68/push  Heap/imm32
+    68/push  0xc/imm32/row-size
+    52/push-edx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  get-or-insert-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
+    # . edi = eax
+    89/copy                         3/mod/direct    7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to edi
+    # next-word(line, word-slice)
+    # . . push args
+    52/push-edx
+    51/push-ecx
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # var address/esi: int = parse-hex-int-from-slice(word-slice)
+    # . . push args
+    52/push-edx
+    # . . call
+    e8/call  parse-hex-int-from-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . esi = eax
+    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy eax to esi
+    # *dest = address
+    89/copy                         0/mod/indirect  7/rm32/edi    .           .                         6/r32/esi   .               .                 # copy esi to *edi
+    #
+    e9/jump  $load-labels:loop/disp32
+$load-labels:end:
+    # . reclaim locals
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x214/imm32       # add to esp
+    # . restore registers
+    5f/pop-to-edi
+    5e/pop-to-esi
+    5a/pop-to-edx
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+# global scratch space for emit-output
+== data
+
+emit-output:datum:  # slice
+  0/imm32/start
+  0/imm32/end
+
+== code
+
+emit-output:  # in: (addr stream byte), out: (addr buffered-file), labels: (addr stream {(handle array byte), address})
+    # pseudocode:
+    #   var address-of-next-instruction = 0x7c00
+    #   var line: (stream byte 512)
+    #   line-loop:
+    #   while true
+    #     clear-stream(line)
+    #     read-line(in, line)
+    #     if (line->write == 0) break               # end of file
+    #     address-of-next-instruction += num-bytes(line)
+    #     var far-jump-or-call? = far-jump-or-call?(line)
+    #     rewind-stream(line)
+    #     while true
+    #       var word-slice = next-word(line)
+    #       if slice-empty?(word-slice)             # end of line
+    #         break
+    #       if slice-starts-with?(word-slice, "#")  # comment
+    #         break
+    #       if label?(word-slice)                # no need for label declarations anymore
+    #         goto line-loop                        # don't insert empty lines
+    #       if slice-equal?(word-slice, "==")       # no need for segment header lines
+    #         word-slice = next-word(line)          # skip segment name
+    #         word-slice = next-word(line)
+    #         if !slice-empty?(word-slice)
+    #           new-address = parse-hex-int-from-slice(word-slice)
+    #           write-buffered(out, "# " address-of-next-instruction "\n")
+    #           while address-of-next-instruction < new-address
+    #             write-buffered("00")
+    #             ++address-of-next-instruction
+    #           write-buffered(out, "# " address-of-next-instruction "\n")
+    #           goto line-loop                      # don't insert empty lines
+    #       if length(word-slice) == 2
+    #         write-slice-buffered(out, word-slice)
+    #         write-buffered(out, " ")
+    #         continue
+    #       var datum: (addr slice) = next-token-from-slice(word-slice->start, word-slice->end, "/")
+    #       var address: (addr int) = get-slice(labels, datum)
+    #       if has-metadata?(word-slice, "imm8")
+    #         emit(out, *address, 1)
+    #       else if has-metadata?(word-slice, "imm16")
+    #         emit(out, *address, 2)
+    #       else if has-metadata?(word-slice, "imm32")
+    #         emit(out, *address, 4)
+    #       else if has-metadata?(word-slice, "disp8")
+    #         value = *address - address-of-next-instruction
+    #         emit(out, value, 1)
+    #       else if has-metadata?(word-slice, "disp32")
+    #         if far-jump-or-call?
+    #           value = *address - address-of-next-instruction
+    #         else
+    #           value = *address
+    #         emit(out, value, 4)
+    #       else
+    #         abort
+    #     write-buffered(out, "\n")
+    #
+    # registers:
+    #   line: ecx
+    #   word-slice: edx
+    #   address-of-next-instruction: ebx
+    #   far-jump-or-call?: edi
+    #   address: esi (inner loop only)
+    #   temporaries: eax, esi (outer loop)
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    52/push-edx
+    53/push-ebx
+    56/push-esi
+    57/push-edi
+    # var line/ecx: (stream byte 512)
+    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x200/imm32       # subtract from esp
+    68/push  0x200/imm32/size
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # var word-slice/edx: slice
+    68/push  0/imm32/end
+    68/push  0/imm32/start
+    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
+    # var address-of-next-instruction/ebx = 0x7c00
+    bb/copy-to-ebx  0x7c00/imm32
+$emit-output:line-loop:
+    # clear-stream(line)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # read-line(in, line)
+    # . . push args
+    51/push-ecx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  read-line/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # dump line {{{
+#?     # . write(2/stderr, "LL: ")
+#?     # . . push args
+#?     68/push  "LL: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # write-stream(2/stderr, line)
+#?     # . . push args
+#?     51/push-ecx
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . rewind-stream(line)
+#?     # . . push args
+#?     51/push-ecx
+#?     # . . call
+#?     e8/call  rewind-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # }}}
+$emit-output:check-for-end-of-input:
+    # if (line->write == 0) break
+    81          7/subop/compare     0/mod/indirect  1/rm32/ecx    .           .             .           .           .               0/imm32           # compare *ecx
+    0f 84/jump-if-=  $emit-output:end/disp32
+    # address-of-next-instruction += num-bytes(line)
+    # . eax = num-bytes(line)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  num-bytes/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . ebx += eax
+    01/add                          3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # add eax to ebx
+    # var far-jump-or-call?/edi: boolean = far-jump-or-call?(line)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  far-jump-or-call?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # rewind-stream(line)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  rewind-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+$emit-output:word-loop:
+    # next-word(line, word-slice)
+    # . . push args
+    52/push-edx
+    51/push-ecx
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # dump word-slice {{{
+#?     # . write(2/stderr, "w: ")
+#?     # . . push args
+#?     68/push  "w: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-slice-buffered(Stderr, word-slice)
+#?     # . . push args
+#?     52/push-edx
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-slice-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . flush(Stderr)
+#?     # . . push args
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  flush/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # }}}
+$emit-output:check-for-end-of-line:
+    # if (slice-empty?(word-slice)) break
+    # . eax = slice-empty?(word-slice)
+    # . . push args
+    52/push-edx
+    # . . call
+    e8/call  slice-empty?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . if (eax != 0) break
+    3d/compare-eax-and  0/imm32/false
+    0f 85/jump-if-!=  $emit-output:next-line/disp32
+$emit-output:check-for-comment:
+    # if (slice-starts-with?(word-slice, "#")) break
+    # . start/esi = word-slice->start
+    8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           6/r32/esi   .               .                 # copy *edx to esi
+    # . c/eax = *start
+    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
+    8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
+    # . if (eax == '#') break
+    3d/compare-eax-and  0x23/imm32/hash
+    0f 84/jump-if-=  $emit-output:next-line/disp32
+$emit-output:check-for-label:
+    # if label?(word-slice) break
+    # . eax = label?(word-slice)
+    # . . push args
+    52/push-edx
+    # . . call
+    e8/call  label?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . if (eax != false) break
+    3d/compare-eax-and  0/imm32/false
+    0f 85/jump-if-!=  $emit-output:line-loop/disp32
+$emit-output:check-for-segment-header:
+    # if !slice-equal?(word-slice, "==") goto next check
+    # . eax = slice-equal?(word-slice, "==")
+    # . . push args
+    68/push  "=="/imm32
+    52/push-edx
+    # . . call
+    e8/call  slice-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax == false) goto next check
+    3d/compare-eax-and  0/imm32/false
+    0f 84/jump-if-=  $emit-output:2-character/disp32
+    # skip segment name
+    # . next-word(line, word-slice)
+    # . . push args
+    52/push-edx
+    51/push-ecx
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # compute segment address if it exists
+    # . next-word(line, word-slice)
+    # . . push args
+    52/push-edx
+    51/push-ecx
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if slice-empty?(word-slice) goto padding-done
+    # . . push args
+    52/push-edx
+    # . . call
+    e8/call  slice-empty?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . .
+    3d/compare-eax-and  0/imm32/false
+    0f 85/jump-if-!=  $emit-output:padding-done/disp32
+    # . var new-address/eax: int = parse-hex-int-from-slice(word-slice)
+    # . . push args
+    52/push-edx
+    # . . call
+    e8/call  parse-hex-int-from-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # write-buffered(out, "# " address-of-next-instruction "\n")
+    # . write-buffered(out, "# ")
+    # . . push args
+    68/push  "# "/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write-int32-hex-buffered(out, address-of-next-instruction)
+    # . . push args
+    53/push-ebx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-int32-hex-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write-buffered(out, "\n")
+    # . . push args
+    68/push  Newline/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+$emit-output:padding-loop:
+    # if (address-of-next-instruction >= new-address) goto padding-loop-done
+    39/compare                      3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # compare ebx with eax
+    73/jump-if-addr>=  $emit-output:padding-loop-done/disp8
+    # if (address-of-next-instruction % 8 == 0) write-buffered("\n")
+    53/push-ebx
+    81          4/subop/and         3/mod/direct    3/rm32/ebx    .           .             .           .           .               7/imm32           # bitwise and of ebx
+    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0/imm32           # compare ebx
+    5b/pop-to-ebx
+    75/jump-if-!=  $emit-output:padding-core/disp8
+    # . write-buffered(out, "\n")
+    # . . push args
+    68/push  Newline/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+$emit-output:padding-core:
+    # write-buffered("00")
+    # . . push args
+    68/push  "00 "/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # ++address-of-next-instruction
+    43/increment-ebx
+    # loop
+    eb/jump $emit-output:padding-loop/disp8
+$emit-output:padding-loop-done:
+    # . write-buffered(out, "\n")
+    # . . push args
+    68/push  Newline/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+$emit-output:padding-done:
+    # write-buffered(out, "# " address-of-next-instruction "\n")
+    # . write-buffered(out, "# ")
+    # . . push args
+    68/push  "# "/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write-int32-hex-buffered(out, address-of-next-instruction)
+    # . . push args
+    53/push-ebx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-int32-hex-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write-buffered(out, "\n")
+    # . . push args
+    68/push  Newline/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    #
+    e9/jump  $emit-output:line-loop/disp32
+$emit-output:2-character:
+    # if (size(word-slice) != 2) goto next check
+    # . eax = size(word-slice)
+    8b/copy                         1/mod/*+disp8   2/rm32/edx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(edx+4) to eax
+    2b/subtract                     0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # subtract *edx from eax
+    # . if (eax != 2) goto next check
+    3d/compare-eax-and  2/imm32
+    75/jump-if-!=  $emit-output:check-metadata/disp8
+    # write-slice-buffered(out, word-slice)
+    # . . push args
+    52/push-edx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-slice-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # write-buffered(out, " ")
+    # . . push args
+    68/push  Space/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # continue
+    e9/jump  $emit-output:word-loop/disp32
+$emit-output:check-metadata:
+    # - if we get here, 'word-slice' must be a label to be looked up
+    # datum = next-token-from-slice(word-slice->start, word-slice->end, "/")
+    # . . push args
+    68/push  emit-output:datum/imm32
+    68/push  0x2f/imm32/slash
+    ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
+    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
+    # . . call
+    e8/call  next-token-from-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
+#?     # dump datum {{{
+#?     # . write(2/stderr, "datum: ")
+#?     # . . push args
+#?     68/push  "datum: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-slice-buffered(Stderr, datum)
+#?     # . . push args
+#?     68/push  emit-output:datum/imm32
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-slice-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . flush(Stderr)
+#?     # . . push args
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  flush/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # }}}
+    # address/esi: (addr int) = get-slice(labels, datum, row-size=12, "label table")
+    # . eax = get-slice(labels, datum, row-size=24, "label table")
+    # . . push args
+    68/push  "label table"/imm32
+    68/push  0xc/imm32/row-size
+    68/push  emit-output:datum/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
+    # . . call
+    e8/call  get-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
+    # . esi = eax
+    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy eax to esi
+$emit-output:check-imm8:
+    # if (!has-metadata?(word-slice, "imm8")) goto next check
+    # . eax = has-metadata?(edx, "imm8")
+    # . . push args
+    68/push  "imm8"/imm32
+    52/push-edx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax != false) abort
+    3d/compare-eax-and  0/imm32/false
+    74/jump-if-=  $emit-output:check-imm16/disp8
+$emit-output:emit-imm8:
+    # emit-hex(out, *address, 1)
+    # . . push args
+    68/push  1/imm32
+    ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # continue
+    e9/jump  $emit-output:word-loop/disp32
+$emit-output:check-imm16:
+    # if (!has-metadata?(word-slice, "imm16")) goto next check
+    # . eax = has-metadata?(edx, "imm16")
+    # . . push args
+    68/push  "imm16"/imm32
+    52/push-edx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax == false) goto next check
+    3d/compare-eax-and  0/imm32/false
+    74/jump-if-=  $emit-output:check-imm32/disp8
+#?     # dump *address {{{
+#?     # . write(2/stderr, "*address: ")
+#?     # . . push args
+#?     68/push  "*address: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-int32-hex-buffered(Stderr, *address)
+#?     # . . push args
+#?     ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-int32-hex-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . flush(Stderr)
+#?     # . . push args
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  flush/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # }}}
+$emit-output:emit-imm16:
+    # emit-hex(out, *address, 2)
+    # . . push args
+    68/push  2/imm32
+    ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # TODO: ensure that the higher 2 bytes are zero
+    # continue
+    e9/jump  $emit-output:word-loop/disp32
+$emit-output:check-imm32:
+    # if (!has-metadata?(word-slice, "imm32")) goto next check
+    # . eax = has-metadata?(edx, "imm32")
+    # . . push args
+    68/push  "imm32"/imm32
+    52/push-edx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax == false) goto next check
+    3d/compare-eax-and  0/imm32/false
+    74/jump-if-=  $emit-output:check-disp8/disp8
+#?     # dump *address {{{
+#?     # . write(2/stderr, "*address: ")
+#?     # . . push args
+#?     68/push  "*address: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-int32-hex-buffered(Stderr, *address)
+#?     # . . push args
+#?     ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-int32-hex-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . flush(Stderr)
+#?     # . . push args
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  flush/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # }}}
+$emit-output:emit-imm32:
+    # emit-hex(out, *address, 4)
+    # . . push args
+    68/push  4/imm32
+    ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # continue
+    e9/jump  $emit-output:word-loop/disp32
+$emit-output:check-disp8:
+    # if (!has-metadata?(word-slice, "disp8")) goto next check
+    # . eax = has-metadata?(edx, "disp8")
+    # . . push args
+    68/push  "disp8"/imm32
+    52/push-edx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax == false) goto next check
+    3d/compare-eax-and  0/imm32/false
+    74/jump-if-=  $emit-output:check-disp16/disp8
+$emit-output:emit-disp8:
+    # emit-hex(out, *address - address-of-next-instruction, 1)
+    # . . push args
+    68/push  1/imm32
+    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
+    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
+    50/push-eax
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # continue
+    e9/jump  $emit-output:word-loop/disp32
+$emit-output:check-disp16:
+    # if (!has-metadata?(word-slice, "disp16")) goto next check
+    # . eax = has-metadata?(edx, "disp16")
+    # . . push args
+    68/push  "disp16"/imm32
+    52/push-edx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax == false) goto next check
+    3d/compare-eax-and  0/imm32/false
+    74/jump-if-=  $emit-output:check-disp32/disp8
+$emit-output:emit-disp16:
+    # emit-hex(out, *address - address-of-next-instruction, 2)
+    # . . push args
+    68/push  2/imm32
+    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
+    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
+    50/push-eax
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # continue
+    e9/jump  $emit-output:word-loop/disp32
+$emit-output:check-disp32:
+    # if (!has-metadata?(word-slice, "disp32")) abort
+    # . eax = has-metadata?(edx, "disp32")
+    # . . push args
+    68/push  "disp32"/imm32
+    52/push-edx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax == false) abort
+    3d/compare-eax-and  0/imm32/false
+    0f 84/jump-if-=  $emit-output:abort/disp32
+$emit-output:emit-disp32:
+    # var value/eax = *address
+    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
+    # if (far-jump-or-call?) value -= address-of-next-instruction
+    81          7/subop/compare     3/mod/direct    7/rm32/edi    .           .             .           .           .               0/imm32/false     # compare edi
+    74/jump-if-=  $emit-output:really-emit-disp32/disp8
+    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
+$emit-output:really-emit-disp32:
+    # emit-hex(out, value, 4)
+    # . . push args
+    68/push  4/imm32
+    50/push-eax
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # continue
+    e9/jump  $emit-output:word-loop/disp32
+$emit-output:next-line:
+    # write-buffered(out, "\n")
+    # . . push args
+    68/push  Newline/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    # . . call
+    e8/call  write-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # loop
+    e9/jump  $emit-output:line-loop/disp32
+$emit-output:end:
+    # . reclaim locals
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x214/imm32       # add to esp
+    # . restore registers
+    5f/pop-to-edi
+    5e/pop-to-esi
+    5b/pop-to-ebx
+    5a/pop-to-edx
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+$emit-output:abort:
+    # print(stderr, "missing metadata in " word-slice)
+    # . _write(2/stderr, "missing metadata in word ")
+    # . . push args
+    68/push  "emit-output: missing metadata in "/imm32
+    68/push  2/imm32/stderr
+    # . . call
+    e8/call  _write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write-slice-buffered(Stderr, word-slice)
+    # . . push args
+    52/push-edx
+    68/push  Stderr/imm32
+    # . . call
+    e8/call  write-slice-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . flush(Stderr)
+    # . . push args
+    68/push  Stderr/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . syscall(exit, 1)
+    bb/copy-to-ebx  1/imm32
+    e8/call  syscall_exit/disp32
+    # never gets here
+
+test-emit-output-non-far-control-flow:
+    # labels turn into absolute addresses if opcodes are not far jumps or calls
+    #
+    # input:
+    #   in:
+    #     == code
+    #     ab cd ef gh
+    #     ij x/disp32
+    #     == data
+    #     00
+    #     34
+    #   labels:
+    #     - 'x': 0x11223344
+    #
+    # output:
+    #   ab cd ef gh
+    #   ij 44 33 22 11
+    #   00
+    #   34
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream($_test-output-buffered-file->buffer)
+    # . . push args
+    68/push  $_test-output-buffered-file->buffer/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . var labels/edx: (stream byte 8*24)
+    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
+    68/push  0xc0/imm32/size
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
+    # . var h/ebx: (handle array byte)
+    68/push  0/imm32
+    68/push  0/imm32
+    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
+    # initialize input
+    # . write(_test-input-stream, "== code\n")
+    # . . push args
+    68/push  "== code\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "ab cd ef gh\n")
+    # . . push args
+    68/push  "ab cd ef gh\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "ij x/disp32\n")
+    # . . push args
+    68/push  "ij x/disp32\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "== data\n")
+    # . . push args
+    68/push  "== data\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "00\n")
+    # . . push args
+    68/push  "00\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "34\n")
+    # . . push args
+    68/push  "34\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . stream-add2(labels, "x", 0x11223344)
+    68/push  0x11223344/imm32/label-address
+    # . . push handle for "x"
+    53/push-ebx
+    68/push  "x"/imm32
+    68/push  Heap/imm32
+    e8/call  copy-array/disp32
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
+    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
+    # . . push labels
+    52/push-edx
+    # . . call
+    e8/call  stream-add2/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
+    # component under test
+    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
+    # . . push args
+    52/push-edx
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  emit-output/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # checks
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # dump output {{{
+#?     # . write(2/stderr, "result: ^")
+#?     # . . push args
+#?     68/push  "result: ^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . rewind-stream(_test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     # . . call
+#?     e8/call  rewind-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # }}}
+    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c00", msg)
+    # . . push args
+    68/push  "F - test-emit-output-non-far-control-flow/0"/imm32
+    68/push  "# 0x00007c00"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "ab cd ef gh ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-non-far-control-flow/1"/imm32
+    68/push  "ab cd ef gh "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "ij 44 33 22 11 ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-non-far-control-flow/2"/imm32
+    68/push  "ij 44 33 22 11 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c09", msg)
+    # . . push args
+    68/push  "F - test-emit-output-non-far-control-flow/3"/imm32
+    68/push  "# 0x00007c09"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "00 ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-non-far-control-flow/3"/imm32
+    68/push  "00 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "34 ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-non-far-control-flow/4"/imm32
+    68/push  "34 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-emit-output-with-padding:
+    # labels turn into absolute addresses if opcodes are not far jumps or calls
+    #
+    # input:
+    #   in:
+    #     == code
+    #     ab cd ef gh
+    #     == data 0x7c10
+    #     34
+    #
+    # output:
+    #   ab cd ef gh
+    #   # 0x7c04
+    #   00 00 00 00
+    #   00 00 00 00 00 00 00 00
+    #   # 0x7c10
+    #   34
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream($_test-output-buffered-file->buffer)
+    # . . push args
+    68/push  $_test-output-buffered-file->buffer/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . var labels/edx: (stream byte 8*24)
+    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
+    68/push  0xc0/imm32/size
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
+    # . var h/ebx: (handle array byte)
+    68/push  0/imm32
+    68/push  0/imm32
+    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
+    # initialize input
+    # . write(_test-input-stream, "== code\n")
+    # . . push args
+    68/push  "== code\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "ab cd ef gh\n")
+    # . . push args
+    68/push  "ab cd ef gh\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "== data 0x7c10\n")
+    # . . push args
+    68/push  "== data 0x7c10\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "34\n")
+    # . . push args
+    68/push  "34\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # component under test
+    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
+    # . . push args
+    52/push-edx
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  emit-output/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # checks
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # dump output {{{
+#?     # . write(2/stderr, "result: ^")
+#?     # . . push args
+#?     68/push  "result: ^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . rewind-stream(_test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     # . . call
+#?     e8/call  rewind-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # }}}
+    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c00", msg)
+    # . . push args
+    68/push  "F - test-emit-output-with-padding/0"/imm32
+    68/push  "# 0x00007c00"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "ab cd ef gh ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-with-padding/1"/imm32
+    68/push  "ab cd ef gh "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c04", msg)
+    # . . push args
+    68/push  "F - test-emit-output-with-padding/0"/imm32
+    68/push  "# 0x00007c04"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "00 00 00 00 ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-with-padding/2"/imm32
+    68/push  "00 00 00 00 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "00 00 00 00 ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-with-padding/3"/imm32
+    68/push  "00 00 00 00 00 00 00 00 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c10", msg)
+    # . . push args
+    68/push  "F - test-emit-output-with-padding/0"/imm32
+    68/push  "# 0x00007c10"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "34 ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-with-padding/4"/imm32
+    68/push  "34 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-emit-output-code-label:
+    # labels turn into PC-relative addresses if opcodes are far jumps or calls
+    #
+    # input:
+    #   in:
+    #     == code
+    #     ab cd
+    #     ef gh
+    #     e8 l1/disp32
+    #   labels:
+    #     - 'l1': 0x7c10
+    #
+    # output:
+    #   ab cd
+    #   ef gh
+    #   e8 07 00 00 00  # 0x7c10 - 0x7c09 = 7
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream($_test-output-buffered-file->buffer)
+    # . . push args
+    68/push  $_test-output-buffered-file->buffer/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . var labels/edx: (stream byte 8*24)
+    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
+    68/push  0xc0/imm32/size
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
+    # . var h/ebx: (handle array byte)
+    68/push  0/imm32
+    68/push  0/imm32
+    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
+    # initialize input
+    # . write(_test-input-stream, "== code\n")
+    # . . push args
+    68/push  "== code\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "ab cd\n")
+    # . . push args
+    68/push  "ab cd\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "ef gh\n")
+    # . . push args
+    68/push  "ef gh\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "e8 l1/disp32\n")
+    # . . push args
+    68/push  "e8 l1/disp32\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . stream-add2(labels, "l1", 0x7c10)
+    68/push  0x7c10/imm32/label-address
+    # . . push handle for "l1"
+    53/push-ebx
+    68/push  "l1"/imm32
+    68/push  Heap/imm32
+    e8/call  copy-array/disp32
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
+    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
+    # . . push labels
+    52/push-edx
+    # . . call
+    e8/call  stream-add2/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
+    # component under test
+    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
+    # . . push args
+    52/push-edx
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  emit-output/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # checks
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # dump output {{{
+#?     # . write(2/stderr, "result: ^")
+#?     # . . push args
+#?     68/push  "result: ^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . rewind-stream(_test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     # . . call
+#?     e8/call  rewind-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # }}}
+    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c00", msg)
+    # . . push args
+    68/push  "F - test-emit-output-code-label/0"/imm32
+    68/push  "# 0x00007c00"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-code-label/1"/imm32
+    68/push  "ab cd "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-code-label/2"/imm32
+    68/push  "ef gh "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "e8 07 00 00 00 ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-code-label/3"/imm32
+    68/push  "e8 07 00 00 00 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-emit-output-code-label-absolute:
+    # labels can also convert to absolute addresses
+    #
+    # input:
+    #   in:
+    #     == code
+    #     ab cd
+    #     ef gh
+    #     ij l1/imm32
+    #   labels:
+    #     - 'l1': 0x1056
+    #
+    # output:
+    #   ab cd
+    #   ef gh
+    #   ij 56 10 00 00
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream($_test-output-buffered-file->buffer)
+    # . . push args
+    68/push  $_test-output-buffered-file->buffer/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . var labels/edx: (stream byte 8*24)
+    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
+    68/push  0xc0/imm32/size
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
+    # . var h/ebx: (handle array byte)
+    68/push  0/imm32
+    68/push  0/imm32
+    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
+    # initialize input
+    # . write(_test-input-stream, "== code 0x1000\n")
+    # . . push args
+    68/push  "== code\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "ab cd\n")
+    # . . push args
+    68/push  "ab cd\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "ef gh\n")
+    # . . push args
+    68/push  "ef gh\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write(_test-input-stream, "ij l1/imm32\n")
+    # . . push args
+    68/push  "ij l1/imm32\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . stream-add2(labels, "l1", 0x1056)
+    68/push  0x1056/imm32/label-address
+    # . . push handle for "l1"
+    53/push-ebx
+    68/push  "l1"/imm32
+    68/push  Heap/imm32
+    e8/call  copy-array/disp32
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
+    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
+    # . . push labels
+    52/push-edx
+    # . . call
+    e8/call  stream-add2/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
+    # component under test
+    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
+    # . . push args
+    52/push-edx
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  emit-output/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # checks
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # dump output {{{
+#?     # . write(2/stderr, "result: ^")
+#?     # . . push args
+#?     68/push  "result: ^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . rewind-stream(_test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     # . . call
+#?     e8/call  rewind-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # }}}
+    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c00", msg)
+    # . . push args
+    68/push  "F - test-emit-output-code-label-absolute/0"/imm32
+    68/push  "# 0x00007c00"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-code-label-absolute/1"/imm32
+    68/push  "ab cd "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-code-label-absolute/2"/imm32
+    68/push  "ef gh "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . check-next-stream-line-equal(_test-output-stream, "ij f9 ff ff ff ", msg)
+    # . . push args
+    68/push  "F - test-emit-output-code-label-absolute/3"/imm32
+    68/push  "ij 56 10 00 00 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+# reads line to make some checks
+# don't assume the read state of line after calling this function
+far-jump-or-call?:  # line: (addr stream byte) -> result/edi: boolean
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    52/push-edx
+    53/push-ebx
+    # ecx = line
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
+    # var word-slice/edx: slice
+    68/push  0/imm32/end
+    68/push  0/imm32/start
+    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
+    # var datum-slice/ebx: slice
+    68/push  0/imm32/end
+    68/push  0/imm32/start
+    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
+    # result = false
+    bf/copy-to-edi  0/imm32/false
+$far-jump-or-call?:check-first-word:
+    # next-word(line, word-slice)
+    # . . push args
+    52/push-edx
+    51/push-ecx
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # if (slice-empty?(word-slice)) return false
+    # . eax = slice-empty?(word-slice)
+    # . . push args
+    52/push-edx
+    # . . call
+    e8/call  slice-empty?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . if (eax != 0) return
+    3d/compare-eax-and  0/imm32/false
+    0f 85/jump-if-!=  $far-jump-or-call?:end/disp32
+    # datum = next-token-from-slice(word-slice->start, word-slice->end, "/")
+    # . . push args
+    53/push-ebx
+    68/push  0x2f/imm32/slash
+    ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
+    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
+    # . . call
+    e8/call  next-token-from-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
+    # if (datum-slice == "e8") return true
+    # . eax = slice-equal?(datum-slice, "e8")
+    # . . push args
+    68/push  "e8"/imm32
+    53/push-ebx
+    # . . call
+    e8/call  slice-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax != false) return true
+    3d/compare-eax-and  0/imm32/false
+    75/jump-if-!=  $far-jump-or-call?:return-true/disp8
+    # if (datum-slice == "e9") return true
+    # . eax = slice-equal?(datum-slice, "e9")
+    # . . push args
+    68/push  "e9"/imm32
+    53/push-ebx
+    # . . call
+    e8/call  slice-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax != false) return true
+    3d/compare-eax-and  0/imm32/false
+    75/jump-if-!=  $far-jump-or-call?:return-true/disp8
+    # if (datum-slice != "0f") return false
+    # . eax = slice-equal?(datum-slice, "0f")
+    # . . push args
+    68/push  "0f"/imm32
+    53/push-ebx
+    # . . call
+    e8/call  slice-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax == false) return
+    3d/compare-eax-and  0/imm32/false
+    74/jump-if-=  $far-jump-or-call?:end/disp8
+$far-jump-or-call?:check-second-word:
+    # next-word(line, word-slice)
+    # . . push args
+    52/push-edx
+    51/push-ecx
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # if (slice-empty?(word-slice)) return false
+    # . eax = slice-empty?(word-slice)
+    # . . push args
+    52/push-edx
+    # . . call
+    e8/call  slice-empty?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . if (eax != 0) return
+    3d/compare-eax-and  0/imm32/false
+    75/jump-if-!=  $far-jump-or-call?:end/disp8
+    # if datum of word-slice does not start with "8", return false
+    # . start/eax = word-slice->start
+    8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy *edx to eax
+    # . c/eax = *start
+    8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           0/r32/AL    .               .                 # copy byte at *eax to AL
+    81          4/subop/and         3/mod/direct    0/rm32/eax    .           .             .           .           .               0xff/imm32        # bitwise and of eax
+    # . if (eax != '8') return
+    3d/compare-eax-and  0x38/imm32/8
+    75/jump-if-!=  $far-jump-or-call?:end/disp8
+    # otherwise return true
+$far-jump-or-call?:return-true:
+    bf/copy-to-edi  1/imm32/true
+$far-jump-or-call?:end:
+    # . reclaim locals
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
+    # . restore registers
+    5b/pop-to-ebx
+    5a/pop-to-edx
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+# - some helpers for tests
+
+stream-add2:  # in: (addr stream byte), key: handle, val: int
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    52/push-edx
+    56/push-esi
+    # esi = in
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
+    # curr/eax = &in->data[in->write]
+    # . eax = in->write
+    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
+    # . eax = esi+eax+12
+    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  0/index/eax   .           0/r32/eax   0xc/disp8       .                 # copy esi+eax+12 to eax
+    # max/edx = &in->data[in->size]
+    # . edx = in->size
+    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           2/r32/edx   8/disp8         .                 # copy *(esi+8) to edx
+    # . edx = esi+edx+12
+    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  2/index/edx   .           2/r32/edx   0xc/disp8       .                 # copy esi+edx+12 to edx
+    # if (curr >= max) abort
+    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax with edx
+    73/jump-if-addr>=  $stream-add2:abort/disp8
+    # *curr = key->alloc-id
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
+    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
+    # curr += 4
+    05/add-to-eax  4/imm32
+    # if (curr >= max) abort
+    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax with edx
+    73/jump-if-addr>=  $stream-add2:abort/disp8
+    # *curr = key->payload
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   0x10/disp8      .                 # copy *(ebp+16) to ecx
+    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
+    # curr += 4
+    05/add-to-eax  4/imm32
+    # if (curr >= max) abort
+    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax with edx
+    73/jump-if-addr>=  $stream-add2:abort/disp8
+    # *curr = val
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   0x14/disp8      .                 # copy *(ebp+20) to ecx
+    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
+    # in->write += 0xc
+    81          0/subop/add         0/mod/indirect  6/rm32/esi    .           .             .           .           .               0xc/imm32         # add to *esi
+$stream-add2:end:
+    # . restore registers
+    5e/pop-to-esi
+    5a/pop-to-edx
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+$stream-add2:abort:
+    # . _write(2/stderr, error)
+    # . . push args
+    68/push  "overflow in stream-add2\n"/imm32
+    68/push  2/imm32/stderr
+    # . . call
+    e8/call  _write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . syscall(exit, 1)
+    bb/copy-to-ebx  1/imm32
+    e8/call  syscall_exit/disp32
+    # never gets here
+
+# some variants of 'trace' that take multiple arguments in different combinations of types:
+#   n: int
+#   c: character [4-bytes, will eventually be UTF-8]
+#   s: (addr array byte)
+#   l: (addr slice)
+# one gotcha: 's5' must not be empty
+
+trace-slsns:  # s1: (addr array byte), l2: (addr slice), s3: (addr array byte), n4: int, s5: (addr array byte)
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # write(*Trace-stream, s1)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # write-slice(*Trace-stream, l2)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
+    # . . call
+    e8/call  write-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # write(*Trace-stream, s3)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
+    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # write-int32-hex(*Trace-stream, n4)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
+    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
+    # . . call
+    e8/call  write-int32-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # trace(s5)  # implicitly adds a newline and finalizes the trace line
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
+    # . . call
+    e8/call  trace/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+$trace-slsns:end:
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-trace-slsns:
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . *Trace-stream->write = 0
+    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy *Trace-stream to eax
+    c7          0/subop/copy        0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm32           # clear *eax
+    # (eax..ecx) = "b"
+    b8/copy-to-eax  "b"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
+    05/add-to-eax  4/imm32
+    # var b/ebx: slice = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
+    # trace-slsls("A" b "c " 3 " e")
+    # . . push args
+    68/push  " e"/imm32
+    68/push  3/imm32
+    68/push  "c "/imm32
+    53/push-ebx
+    68/push  "A"/imm32
+    # . . call
+    e8/call  trace-slsns/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x14/imm32        # add to esp
+#?     # dump *Trace-stream {{{
+#?     # . write(2/stderr, "^")
+#?     # . . push args
+#?     68/push  "^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write-stream(2/stderr, *Trace-stream)
+#?     # . . push args
+#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # }}}
+    # check-trace-contains("Abc 0x00000003 e")
+    # . . push args
+    68/push  "F - test-trace-slsls"/imm32
+    68/push  "Abc 0x00000003 e"/imm32
+    # . . call
+    e8/call  check-trace-contains/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+num-bytes:  # line: (addr stream byte) -> eax: int
+    # pseudocode:
+    #   result = 0
+    #   while true
+    #     var word-slice = next-word(line)
+    #     if slice-empty?(word-slice)             # end of line
+    #       break
+    #     if slice-starts-with?(word-slice, "#")  # comment
+    #       break
+    #     if label?(word-slice)                # no need for label declarations anymore
+    #       break
+    #     if slice-equal?(word-slice, "==")
+    #       break                                 # no need for segment header lines
+    #     result += compute-width(word-slice)
+    #   return result
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    51/push-ecx
+    52/push-edx
+    53/push-ebx
+    # var result/eax = 0
+    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
+    # var word-slice/ecx: slice
+    68/push  0/imm32/end
+    68/push  0/imm32/start
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+#?     # dump line {{{
+#?     # . write(2/stderr, "LL: ")
+#?     # . . push args
+#?     68/push  "LL: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # write-stream(2/stderr, line)
+#?     # . . push args
+#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # }}}
+    # . rewind-stream(line)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  rewind-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+$num-bytes:loop:
+    # next-word(line, word-slice)
+    # . . push args
+    51/push-ecx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # dump word-slice {{{
+#?     # . write(2/stderr, "AA: ")
+#?     # . . push args
+#?     68/push  "AA: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . clear-stream($Stderr->buffer)
+#?     # . . push args
+#?     68/push  $Stderr->buffer/imm32
+#?     # . . call
+#?     e8/call  clear-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # . write-slice-buffered(Stderr, word-slice)
+#?     # . . push args
+#?     51/push-ecx
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-slice-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . flush(Stderr)
+#?     # . . push args
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  flush/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # }}}
+$num-bytes:check0:
+    # if (slice-empty?(word-slice)) break
+    # . save result
+    50/push-eax
+    # . eax = slice-empty?(word-slice)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  slice-empty?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . if (eax != false) break
+    3d/compare-eax-and  0/imm32/false
+    # . restore result now that ZF is set
+    58/pop-to-eax
+    75/jump-if-!=  $num-bytes:end/disp8
+$num-bytes:check-for-comment:
+    # if (slice-starts-with?(word-slice, "#")) break
+    # . start/edx = word-slice->start
+    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # copy *ecx to edx
+    # . c/ebx = *start
+    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
+    8a/copy-byte                    0/mod/indirect  2/rm32/edx    .           .             .           3/r32/BL    .               .                 # copy byte at *edx to BL
+    # . if (ebx == '#') break
+    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x23/imm32/hash   # compare ebx
+    74/jump-if-=  $num-bytes:end/disp8
+$num-bytes:check-for-label:
+    # if (slice-ends-with?(word-slice, ":")) break
+    # . end/edx = word-slice->end
+    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
+    # . c/ebx = *(end-1)
+    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
+    8a/copy-byte                    1/mod/*+disp8   2/rm32/edx    .           .             .           3/r32/BL    -1/disp8        .                 # copy byte at *ecx to BL
+    # . if (ebx == ':') break
+    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x3a/imm32/colon  # compare ebx
+    74/jump-if-=  $num-bytes:end/disp8
+$num-bytes:check-for-segment-header:
+    # if (slice-equal?(word-slice, "==")) break
+    # . push result
+    50/push-eax
+    # . eax = slice-equal?(word-slice, "==")
+    # . . push args
+    68/push  "=="/imm32
+    51/push-ecx
+    # . . call
+    e8/call  slice-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax != false) break
+    3d/compare-eax-and  0/imm32/false
+    # . restore result now that ZF is set
+    58/pop-to-eax
+    75/jump-if-!=  $num-bytes:end/disp8
+$num-bytes:loop-body:
+    # result += compute-width-of-slice(word-slice)
+    # . copy result to edx
+    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to edx
+    # . eax = compute-width-of-slice(word-slice)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call compute-width-of-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . eax += result
+    01/add                          3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # add edx to eax
+    e9/jump  $num-bytes:loop/disp32
+$num-bytes:end:
+    # . rewind-stream(line)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  rewind-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . reclaim locals
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . restore registers
+    5b/pop-to-ebx
+    5a/pop-to-edx
+    59/pop-to-ecx
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-num-bytes-handles-empty-string:
+    # if a line starts with '#', return 0
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # no contents in input
+    # eax = num-bytes(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  num-bytes/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-num-bytes-handles-empty-string"/imm32
+    68/push  0/imm32/true
+    50/push-eax
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-num-bytes-ignores-comments:
+    # if a line starts with '#', return 0
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # initialize input
+    # . write(_test-input-stream, "# abcd")
+    # . . push args
+    68/push  "# abcd"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # eax = num-bytes(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  num-bytes/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-num-bytes-ignores-comments"/imm32
+    68/push  0/imm32/true
+    50/push-eax
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-num-bytes-ignores-labels:
+    # if the first word ends with ':', return 0
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # initialize input
+    # . write(_test-input-stream, "ab: # cd")
+    # . . push args
+    68/push  "ab: # cd"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # eax = num-bytes(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  num-bytes/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-num-bytes-ignores-labels"/imm32
+    68/push  0/imm32/true
+    50/push-eax
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-num-bytes-ignores-segment-headers:
+    # if the first word is '==', return 0
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # initialize input
+    # . write(_test-input-stream, "== ab cd")
+    # . . push args
+    68/push  "== ab cd"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # eax = num-bytes(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  num-bytes/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-num-bytes-ignores-segment-headers"/imm32
+    68/push  0/imm32/true
+    50/push-eax
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-num-bytes-counts-words-by-default:
+    # without metadata, count words
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # initialize input
+    # . write(_test-input-stream, "ab cd ef")
+    # . . push args
+    68/push  "ab cd ef"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # eax = num-bytes(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  num-bytes/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 3, msg)
+    # . . push args
+    68/push  "F - test-num-bytes-counts-words-by-default"/imm32
+    68/push  3/imm32/true
+    50/push-eax
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-num-bytes-ignores-trailing-comment:
+    # trailing comments appropriately ignored
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # initialize input
+    # . write(_test-input-stream, "ab cd # ef")
+    # . . push args
+    68/push  "ab cd # ef"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # eax = num-bytes(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  num-bytes/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 2, msg)
+    # . . push args
+    68/push  "F - test-num-bytes-ignores-trailing-comment"/imm32
+    68/push  2/imm32/true
+    50/push-eax
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-num-bytes-handles-imm32:
+    # if a word has the /imm32 metadata, count it as 4 bytes
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # initialize input
+    # . write(_test-input-stream, "ab cd/imm32 ef")
+    # . . push args
+    68/push  "ab cd/imm32 ef"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # eax = num-bytes(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  num-bytes/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 6, msg)
+    # . . push args
+    68/push  "F - test-num-bytes-handles-imm32"/imm32
+    68/push  6/imm32/true
+    50/push-eax
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+# . . vim:nowrap:textwidth=0
diff --git a/linux/survey_baremetal b/linux/survey_baremetal
index 91e0c575..46de25b7 100755
--- a/linux/survey_baremetal
+++ b/linux/survey_baremetal
Binary files differdiff --git a/linux/survey_baremetal.subx b/linux/survey_baremetal.subx
index 016757a8..d1d62906 100644
--- a/linux/survey_baremetal.subx
+++ b/linux/survey_baremetal.subx
@@ -1,6 +1,5 @@
 # Assign addresses (co-ordinates) to instructions (landmarks) in a program
 # (landscape).
-# Use the addresses assigned to replace labels.
 #
 # To build:
 #   $ bootstrap/bootstrap translate [01]*.subx subx-params.subx survey_baremetal.subx  -o survey_baremetal
@@ -23,18 +22,12 @@
 #   foo:
 #     34
 #
-# The output is the stream of bytes without segment headers or label definitions,
-# and with label references replaced with numeric values/displacements.
+# The output is a list of labels and their computed addresses.
 #
 #   $ cat x  |bootstrap/bootstrap run survey_baremetal
-#   # 0x7c00
-#   aa bb nn  # some computed address
-#   cc dd nn nn nn nn  # some computed displacement
-#   ee nn nn nn nn  # address right after this instruction
-#   # 0x7c0e
-#   00 00  # padding
-#   # 0x7c10
-#   34  # data segment interleaved with code
+#   0x7c00 l1
+#   0x7c09 l2
+#   0x7c10 foo
 
 == code
 #   instruction                     effective address                                                   register    displacement    immediate
@@ -78,7 +71,6 @@ Entry:  # run tests if necessary, convert stdin if not
     3d/compare-eax-and  0/imm32/false
     74/jump-if-=  $subx-survey-main:interactive/disp8
     # run-tests()
-#?     e8/call  test-emit-output-with-padding/disp32
     e8/call  run-tests/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
@@ -112,8 +104,7 @@ subx-survey:  # infile: (addr buffered-file), out: (addr buffered-file)
     #   slurp(infile, in)
     #   var labels: (stream {label-name, address} Max-labels)
     #   compute-addresses(in, labels)
-    #   rewind-stream(in)
-    #   emit-output(in, out, labels)
+    #   emit-labels(out, labels)
     #
     # . prologue
     55/push-ebp
@@ -159,22 +150,14 @@ subx-survey:  # infile: (addr buffered-file), out: (addr buffered-file)
     e8/call  compute-addresses/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # rewind-stream(in)
-    # . . push args
-    56/push-esi
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # emit-output(in, out, labels)
+    # emit-labels(out, labels)
     # . . push args
     52/push-edx
     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    56/push-esi
     # . . call
-    e8/call  emit-output/disp32
+    e8/call  emit-labels/disp32
     # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # flush(out)
     # . . push args
     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
@@ -936,78 +919,19 @@ test-compute-addresses:
     5d/pop-to-ebp
     c3/return
 
-# global scratch space for emit-output
-== data
-
-emit-output:datum:  # slice
-  0/imm32/start
-  0/imm32/end
-
-== code
-
-emit-output:  # in: (addr stream byte), out: (addr buffered-file), labels: (addr stream {(handle array byte), address})
+emit-labels:  # out: (addr buffered-file), labels: (addr stream {(handle array byte), address})
     # pseudocode:
-    #   var address-of-next-instruction = 0x7c00
-    #   var line: (stream byte 512)
-    #   line-loop:
-    #   while true
-    #     clear-stream(line)
-    #     read-line(in, line)
-    #     if (line->write == 0) break               # end of file
-    #     address-of-next-instruction += num-bytes(line)
-    #     var far-jump-or-call? = far-jump-or-call?(line)
-    #     rewind-stream(line)
-    #     while true
-    #       var word-slice = next-word(line)
-    #       if slice-empty?(word-slice)             # end of line
-    #         break
-    #       if slice-starts-with?(word-slice, "#")  # comment
-    #         break
-    #       if label?(word-slice)                # no need for label declarations anymore
-    #         goto line-loop                        # don't insert empty lines
-    #       if slice-equal?(word-slice, "==")       # no need for segment header lines
-    #         word-slice = next-word(line)          # skip segment name
-    #         word-slice = next-word(line)
-    #         if !slice-empty?(word-slice)
-    #           new-address = parse-hex-int-from-slice(word-slice)
-    #           write-buffered(out, "# " address-of-next-instruction "\n")
-    #           while address-of-next-instruction < new-address
-    #             write-buffered("00")
-    #             ++address-of-next-instruction
-    #           write-buffered(out, "# " address-of-next-instruction "\n")
-    #           goto line-loop                      # don't insert empty lines
-    #       if length(word-slice) == 2
-    #         write-slice-buffered(out, word-slice)
-    #         write-buffered(out, " ")
-    #         continue
-    #       var datum: (addr slice) = next-token-from-slice(word-slice->start, word-slice->end, "/")
-    #       var address: (addr int) = get-slice(labels, datum)
-    #       if has-metadata?(word-slice, "imm8")
-    #         emit(out, *address, 1)
-    #       else if has-metadata?(word-slice, "imm16")
-    #         emit(out, *address, 2)
-    #       else if has-metadata?(word-slice, "imm32")
-    #         emit(out, *address, 4)
-    #       else if has-metadata?(word-slice, "disp8")
-    #         value = *address - address-of-next-instruction
-    #         emit(out, value, 1)
-    #       else if has-metadata?(word-slice, "disp32")
-    #         if far-jump-or-call?
-    #           value = *address - address-of-next-instruction
-    #         else
-    #           value = *address
-    #         emit(out, value, 4)
-    #       else
-    #         abort
-    #     write-buffered(out, "\n")
-    #
-    # registers:
-    #   line: ecx
-    #   word-slice: edx
-    #   address-of-next-instruction: ebx
-    #   far-jump-or-call?: edi
-    #   address: esi (inner loop only)
-    #   temporaries: eax, esi (outer loop)
+    #   curr = table->data
+    #   max = &table->data[table->write]
+    #   while curr < max
+    #     var label: (addr array byte) = lookup(*curr)
+    #     curr += 8
+    #     write-buffered(out, label)
+    #     write-buffered(out, ' ')
+    #     write-buffered(out, *curr)
+    #     curr += 4
+    #     write(out, '\n')
+    #   return 0
     #
     # . prologue
     55/push-ebp
@@ -1016,671 +940,44 @@ emit-output:  # in: (addr stream byte), out: (addr buffered-file), labels: (addr
     50/push-eax
     51/push-ecx
     52/push-edx
-    53/push-ebx
     56/push-esi
-    57/push-edi
-    # var line/ecx: (stream byte 512)
-    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x200/imm32       # subtract from esp
-    68/push  0x200/imm32/size
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # var word-slice/edx: slice
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # var address-of-next-instruction/ebx = 0x7c00
-    bb/copy-to-ebx  0x7c00/imm32
-$emit-output:line-loop:
-    # clear-stream(line)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # read-line(in, line)
-    # . . push args
-    51/push-ecx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  read-line/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # write-stream(2/stderr, line)
-#?     # . . push args
-#?     51/push-ecx
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . rewind-stream(line)
-#?     # . . push args
-#?     51/push-ecx
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # }}}
-$emit-output:check-for-end-of-input:
-    # if (line->write == 0) break
-    81          7/subop/compare     0/mod/indirect  1/rm32/ecx    .           .             .           .           .               0/imm32           # compare *ecx
-    0f 84/jump-if-=  $emit-output:end/disp32
-    # address-of-next-instruction += num-bytes(line)
-    # . eax = num-bytes(line)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . ebx += eax
-    01/add                          3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # add eax to ebx
-    # var far-jump-or-call?/edi: boolean = far-jump-or-call?(line)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  far-jump-or-call?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # rewind-stream(line)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$emit-output:word-loop:
-    # next-word(line, word-slice)
-    # . . push args
-    52/push-edx
-    51/push-ecx
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "w: ")
-#?     # . . push args
-#?     68/push  "w: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     52/push-edx
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # }}}
-$emit-output:check-for-end-of-line:
-    # if (slice-empty?(word-slice)) break
-    # . eax = slice-empty?(word-slice)
-    # . . push args
-    52/push-edx
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . if (eax != 0) break
-    3d/compare-eax-and  0/imm32/false
-    0f 85/jump-if-!=  $emit-output:next-line/disp32
-$emit-output:check-for-comment:
-    # if (slice-starts-with?(word-slice, "#")) break
-    # . start/esi = word-slice->start
-    8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           6/r32/esi   .               .                 # copy *edx to esi
-    # . c/eax = *start
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
-    # . if (eax == '#') break
-    3d/compare-eax-and  0x23/imm32/hash
-    0f 84/jump-if-=  $emit-output:next-line/disp32
-$emit-output:check-for-label:
-    # if label?(word-slice) break
-    # . eax = label?(word-slice)
+    # esi = table
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
+    # var curr/ecx: (addr handle array byte) = table->data
+    8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
+    # var max/edx: (addr byte) = &table->data[table->write]
+    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
+    8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
+$emit-labels:loop:
+    # if (curr >= max) return null
+    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
+    0f 83/jump-if-addr>=  $emit-labels:end/disp32
+    # var label/eax: (addr array byte) = lookup(*curr)
     # . . push args
-    52/push-edx
+    ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
+    ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
     # . . call
-    e8/call  label?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . if (eax != false) break
-    3d/compare-eax-and  0/imm32/false
-    0f 85/jump-if-!=  $emit-output:line-loop/disp32
-$emit-output:check-for-segment-header:
-    # if !slice-equal?(word-slice, "==") goto next check
-    # . eax = slice-equal?(word-slice, "==")
-    # . . push args
-    68/push  "=="/imm32
-    52/push-edx
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == false) goto next check
-    3d/compare-eax-and  0/imm32/false
-    0f 84/jump-if-=  $emit-output:2-character/disp32
-    # skip segment name
-    # . next-word(line, word-slice)
-    # . . push args
-    52/push-edx
-    51/push-ecx
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # compute segment address if it exists
-    # . next-word(line, word-slice)
-    # . . push args
-    52/push-edx
-    51/push-ecx
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if slice-empty?(word-slice) goto padding-done
-    # . . push args
-    52/push-edx
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . .
-    3d/compare-eax-and  0/imm32/false
-    0f 85/jump-if-!=  $emit-output:padding-done/disp32
-    # . var new-address/eax: int = parse-hex-int-from-slice(word-slice)
-    # . . push args
-    52/push-edx
-    # . . call
-    e8/call  parse-hex-int-from-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # write-buffered(out, "# " address-of-next-instruction "\n")
-    # . write-buffered(out, "# ")
-    # . . push args
-    68/push  "# "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write-int32-hex-buffered(out, address-of-next-instruction)
-    # . . push args
-    53/push-ebx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-int32-hex-buffered/disp32
+    e8/call  lookup/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write-buffered(out, "\n")
+    # curr += 8
+    81          0/subop/add         3/mod/direct    1/rm32/ecx    .           .             .           .           .               8/imm32           # add to ecx
+    # write-buffered(out, label)
     # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$emit-output:padding-loop:
-    # if (address-of-next-instruction >= new-address) goto padding-loop-done
-    39/compare                      3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # compare ebx with eax
-    73/jump-if-addr>=  $emit-output:padding-loop-done/disp8
-    # if (address-of-next-instruction % 8 == 0) write-buffered("\n")
-    53/push-ebx
-    81          4/subop/and         3/mod/direct    3/rm32/ebx    .           .             .           .           .               7/imm32           # bitwise and of ebx
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0/imm32           # compare ebx
-    5b/pop-to-ebx
-    75/jump-if-!=  $emit-output:padding-core/disp8
-    # . write-buffered(out, "\n")
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$emit-output:padding-core:
-    # write-buffered("00")
-    # . . push args
-    68/push  "00 "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # ++address-of-next-instruction
-    43/increment-ebx
-    # loop
-    eb/jump $emit-output:padding-loop/disp8
-$emit-output:padding-loop-done:
-    # . write-buffered(out, "\n")
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$emit-output:padding-done:
-    # write-buffered(out, "# " address-of-next-instruction "\n")
-    # . write-buffered(out, "# ")
-    # . . push args
-    68/push  "# "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write-int32-hex-buffered(out, address-of-next-instruction)
-    # . . push args
-    53/push-ebx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-int32-hex-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write-buffered(out, "\n")
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    50/push-eax
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
     # . . call
     e8/call  write-buffered/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    #
-    e9/jump  $emit-output:line-loop/disp32
-$emit-output:2-character:
-    # if (size(word-slice) != 2) goto next check
-    # . eax = size(word-slice)
-    8b/copy                         1/mod/*+disp8   2/rm32/edx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(edx+4) to eax
-    2b/subtract                     0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # subtract *edx from eax
-    # . if (eax != 2) goto next check
-    3d/compare-eax-and  2/imm32
-    75/jump-if-!=  $emit-output:check-metadata/disp8
-    # write-slice-buffered(out, word-slice)
-    # . . push args
-    52/push-edx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # write-buffered(out, " ")
+    # write-buffered(out, ' ')
     # . . push args
     68/push  Space/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # continue
-    e9/jump  $emit-output:word-loop/disp32
-$emit-output:check-metadata:
-    # - if we get here, 'word-slice' must be a label to be looked up
-    # datum = next-token-from-slice(word-slice->start, word-slice->end, "/")
-    # . . push args
-    68/push  emit-output:datum/imm32
-    68/push  0x2f/imm32/slash
-    ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
-    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
-    # . . call
-    e8/call  next-token-from-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-#?     # dump datum {{{
-#?     # . write(2/stderr, "datum: ")
-#?     # . . push args
-#?     68/push  "datum: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write-slice-buffered(Stderr, datum)
-#?     # . . push args
-#?     68/push  emit-output:datum/imm32
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # }}}
-    # address/esi: (addr int) = get-slice(labels, datum, row-size=12, "label table")
-    # . eax = get-slice(labels, datum, row-size=24, "label table")
-    # . . push args
-    68/push  "label table"/imm32
-    68/push  0xc/imm32/row-size
-    68/push  emit-output:datum/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
-    # . . call
-    e8/call  get-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-    # . esi = eax
-    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy eax to esi
-$emit-output:check-imm8:
-    # if (!has-metadata?(word-slice, "imm8")) goto next check
-    # . eax = has-metadata?(edx, "imm8")
-    # . . push args
-    68/push  "imm8"/imm32
-    52/push-edx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax != false) abort
-    3d/compare-eax-and  0/imm32/false
-    74/jump-if-=  $emit-output:check-imm16/disp8
-$emit-output:emit-imm8:
-    # emit-hex(out, *address, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # continue
-    e9/jump  $emit-output:word-loop/disp32
-$emit-output:check-imm16:
-    # if (!has-metadata?(word-slice, "imm16")) goto next check
-    # . eax = has-metadata?(edx, "imm16")
-    # . . push args
-    68/push  "imm16"/imm32
-    52/push-edx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == false) goto next check
-    3d/compare-eax-and  0/imm32/false
-    74/jump-if-=  $emit-output:check-imm32/disp8
-#?     # dump *address {{{
-#?     # . write(2/stderr, "*address: ")
-#?     # . . push args
-#?     68/push  "*address: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write-int32-hex-buffered(Stderr, *address)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-int32-hex-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # }}}
-$emit-output:emit-imm16:
-    # emit-hex(out, *address, 2)
-    # . . push args
-    68/push  2/imm32
-    ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # TODO: ensure that the higher 2 bytes are zero
-    # continue
-    e9/jump  $emit-output:word-loop/disp32
-$emit-output:check-imm32:
-    # if (!has-metadata?(word-slice, "imm32")) goto next check
-    # . eax = has-metadata?(edx, "imm32")
-    # . . push args
-    68/push  "imm32"/imm32
-    52/push-edx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == false) goto next check
-    3d/compare-eax-and  0/imm32/false
-    74/jump-if-=  $emit-output:check-disp8/disp8
-#?     # dump *address {{{
-#?     # . write(2/stderr, "*address: ")
-#?     # . . push args
-#?     68/push  "*address: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write-int32-hex-buffered(Stderr, *address)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-int32-hex-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # }}}
-$emit-output:emit-imm32:
-    # emit-hex(out, *address, 4)
-    # . . push args
-    68/push  4/imm32
-    ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # continue
-    e9/jump  $emit-output:word-loop/disp32
-$emit-output:check-disp8:
-    # if (!has-metadata?(word-slice, "disp8")) goto next check
-    # . eax = has-metadata?(edx, "disp8")
-    # . . push args
-    68/push  "disp8"/imm32
-    52/push-edx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == false) goto next check
-    3d/compare-eax-and  0/imm32/false
-    74/jump-if-=  $emit-output:check-disp16/disp8
-$emit-output:emit-disp8:
-    # emit-hex(out, *address - address-of-next-instruction, 1)
-    # . . push args
-    68/push  1/imm32
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
-    50/push-eax
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # continue
-    e9/jump  $emit-output:word-loop/disp32
-$emit-output:check-disp16:
-    # if (!has-metadata?(word-slice, "disp16")) goto next check
-    # . eax = has-metadata?(edx, "disp16")
-    # . . push args
-    68/push  "disp16"/imm32
-    52/push-edx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == false) goto next check
-    3d/compare-eax-and  0/imm32/false
-    74/jump-if-=  $emit-output:check-disp32/disp8
-$emit-output:emit-disp16:
-    # emit-hex(out, *address - address-of-next-instruction, 2)
-    # . . push args
-    68/push  2/imm32
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
-    50/push-eax
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # continue
-    e9/jump  $emit-output:word-loop/disp32
-$emit-output:check-disp32:
-    # if (!has-metadata?(word-slice, "disp32")) abort
-    # . eax = has-metadata?(edx, "disp32")
-    # . . push args
-    68/push  "disp32"/imm32
-    52/push-edx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == false) abort
-    3d/compare-eax-and  0/imm32/false
-    0f 84/jump-if-=  $emit-output:abort/disp32
-$emit-output:emit-disp32:
-    # var value/eax = *address
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    # if (far-jump-or-call?) value -= address-of-next-instruction
-    81          7/subop/compare     3/mod/direct    7/rm32/edi    .           .             .           .           .               0/imm32/false     # compare edi
-    74/jump-if-=  $emit-output:really-emit-disp32/disp8
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
-$emit-output:really-emit-disp32:
-    # emit-hex(out, value, 4)
-    # . . push args
-    68/push  4/imm32
-    50/push-eax
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # continue
-    e9/jump  $emit-output:word-loop/disp32
-$emit-output:next-line:
-    # write-buffered(out, "\n")
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
     # . . call
     e8/call  write-buffered/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # loop
-    e9/jump  $emit-output:line-loop/disp32
-$emit-output:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x214/imm32       # add to esp
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    5b/pop-to-ebx
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-$emit-output:abort:
-    # print(stderr, "missing metadata in " word-slice)
-    # . _write(2/stderr, "missing metadata in word ")
-    # . . push args
-    68/push  "emit-output: missing metadata in "/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write-slice-buffered(Stderr, word-slice)
-    # . . push args
-    52/push-edx
-    68/push  Stderr/imm32
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . flush(Stderr)
     # . . push args
     68/push  Stderr/imm32
@@ -1688,1014 +985,27 @@ $emit-output:abort:
     e8/call  flush/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . syscall(exit, 1)
-    bb/copy-to-ebx  1/imm32
-    e8/call  syscall_exit/disp32
-    # never gets here
-
-test-emit-output-non-far-control-flow:
-    # labels turn into absolute addresses if opcodes are not far jumps or calls
-    #
-    # input:
-    #   in:
-    #     == code
-    #     ab cd ef gh
-    #     ij x/disp32
-    #     == data
-    #     00
-    #     34
-    #   labels:
-    #     - 'x': 0x11223344
-    #
-    # output:
-    #   ab cd ef gh
-    #   ij 44 33 22 11
-    #   00
-    #   34
-    #
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream($_test-output-buffered-file->buffer)
-    # . . push args
-    68/push  $_test-output-buffered-file->buffer/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . var labels/edx: (stream byte 8*24)
-    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
-    68/push  0xc0/imm32/size
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # . var h/ebx: (handle array byte)
-    68/push  0/imm32
-    68/push  0/imm32
-    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
-    # initialize input
-    # . write(_test-input-stream, "== code\n")
-    # . . push args
-    68/push  "== code\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "ab cd ef gh\n")
-    # . . push args
-    68/push  "ab cd ef gh\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "ij x/disp32\n")
-    # . . push args
-    68/push  "ij x/disp32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "== data\n")
-    # . . push args
-    68/push  "== data\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "00\n")
-    # . . push args
-    68/push  "00\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "34\n")
-    # . . push args
-    68/push  "34\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . stream-add2(labels, "x", 0x11223344)
-    68/push  0x11223344/imm32/label-address
-    # . . push handle for "x"
-    53/push-ebx
-    68/push  "x"/imm32
-    68/push  Heap/imm32
-    e8/call  copy-array/disp32
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
-    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
-    # . . push labels
-    52/push-edx
-    # . . call
-    e8/call  stream-add2/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-    # component under test
-    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
-    # . . push args
-    52/push-edx
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  emit-output/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # checks
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c00", msg)
-    # . . push args
-    68/push  "F - test-emit-output-non-far-control-flow/0"/imm32
-    68/push  "# 0x00007c00"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "ab cd ef gh ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-non-far-control-flow/1"/imm32
-    68/push  "ab cd ef gh "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "ij 44 33 22 11 ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-non-far-control-flow/2"/imm32
-    68/push  "ij 44 33 22 11 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c09", msg)
-    # . . push args
-    68/push  "F - test-emit-output-non-far-control-flow/3"/imm32
-    68/push  "# 0x00007c09"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "00 ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-non-far-control-flow/3"/imm32
-    68/push  "00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "34 ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-non-far-control-flow/4"/imm32
-    68/push  "34 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-emit-output-with-padding:
-    # labels turn into absolute addresses if opcodes are not far jumps or calls
-    #
-    # input:
-    #   in:
-    #     == code
-    #     ab cd ef gh
-    #     == data 0x7c10
-    #     34
-    #
-    # output:
-    #   ab cd ef gh
-    #   # 0x7c04
-    #   00 00 00 00
-    #   00 00 00 00 00 00 00 00
-    #   # 0x7c10
-    #   34
-    #
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream($_test-output-buffered-file->buffer)
-    # . . push args
-    68/push  $_test-output-buffered-file->buffer/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . var labels/edx: (stream byte 8*24)
-    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
-    68/push  0xc0/imm32/size
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # . var h/ebx: (handle array byte)
-    68/push  0/imm32
-    68/push  0/imm32
-    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
-    # initialize input
-    # . write(_test-input-stream, "== code\n")
-    # . . push args
-    68/push  "== code\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "ab cd ef gh\n")
-    # . . push args
-    68/push  "ab cd ef gh\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "== data 0x7c10\n")
-    # . . push args
-    68/push  "== data 0x7c10\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "34\n")
-    # . . push args
-    68/push  "34\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # component under test
-    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
-    # . . push args
-    52/push-edx
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  emit-output/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # checks
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c00", msg)
-    # . . push args
-    68/push  "F - test-emit-output-with-padding/0"/imm32
-    68/push  "# 0x00007c00"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "ab cd ef gh ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-with-padding/1"/imm32
-    68/push  "ab cd ef gh "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c04", msg)
-    # . . push args
-    68/push  "F - test-emit-output-with-padding/0"/imm32
-    68/push  "# 0x00007c04"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "00 00 00 00 ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-with-padding/2"/imm32
-    68/push  "00 00 00 00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "00 00 00 00 ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-with-padding/3"/imm32
-    68/push  "00 00 00 00 00 00 00 00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c10", msg)
-    # . . push args
-    68/push  "F - test-emit-output-with-padding/0"/imm32
-    68/push  "# 0x00007c10"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "34 ", msg)
+    # write-int32-hex-buffered(out, *curr)
     # . . push args
-    68/push  "F - test-emit-output-with-padding/4"/imm32
-    68/push  "34 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-emit-output-code-label:
-    # labels turn into PC-relative addresses if opcodes are far jumps or calls
-    #
-    # input:
-    #   in:
-    #     == code
-    #     ab cd
-    #     ef gh
-    #     e8 l1/disp32
-    #   labels:
-    #     - 'l1': 0x7c10
-    #
-    # output:
-    #   ab cd
-    #   ef gh
-    #   e8 07 00 00 00  # 0x7c10 - 0x7c09 = 7
-    #
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream($_test-output-buffered-file->buffer)
-    # . . push args
-    68/push  $_test-output-buffered-file->buffer/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . var labels/edx: (stream byte 8*24)
-    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
-    68/push  0xc0/imm32/size
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # . var h/ebx: (handle array byte)
-    68/push  0/imm32
-    68/push  0/imm32
-    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
-    # initialize input
-    # . write(_test-input-stream, "== code\n")
-    # . . push args
-    68/push  "== code\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "ab cd\n")
-    # . . push args
-    68/push  "ab cd\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "ef gh\n")
-    # . . push args
-    68/push  "ef gh\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "e8 l1/disp32\n")
-    # . . push args
-    68/push  "e8 l1/disp32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . stream-add2(labels, "l1", 0x7c10)
-    68/push  0x7c10/imm32/label-address
-    # . . push handle for "l1"
-    53/push-ebx
-    68/push  "l1"/imm32
-    68/push  Heap/imm32
-    e8/call  copy-array/disp32
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
-    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
-    # . . push labels
-    52/push-edx
-    # . . call
-    e8/call  stream-add2/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-    # component under test
-    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
-    # . . push args
-    52/push-edx
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  emit-output/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # checks
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c00", msg)
-    # . . push args
-    68/push  "F - test-emit-output-code-label/0"/imm32
-    68/push  "# 0x00007c00"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-code-label/1"/imm32
-    68/push  "ab cd "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-code-label/2"/imm32
-    68/push  "ef gh "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "e8 07 00 00 00 ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-code-label/3"/imm32
-    68/push  "e8 07 00 00 00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-emit-output-code-label-absolute:
-    # labels can also convert to absolute addresses
-    #
-    # input:
-    #   in:
-    #     == code
-    #     ab cd
-    #     ef gh
-    #     ij l1/imm32
-    #   labels:
-    #     - 'l1': 0x1056
-    #
-    # output:
-    #   ab cd
-    #   ef gh
-    #   ij 56 10 00 00
-    #
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream($_test-output-buffered-file->buffer)
-    # . . push args
-    68/push  $_test-output-buffered-file->buffer/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . var labels/edx: (stream byte 8*24)
-    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
-    68/push  0xc0/imm32/size
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # . var h/ebx: (handle array byte)
-    68/push  0/imm32
-    68/push  0/imm32
-    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
-    # initialize input
-    # . write(_test-input-stream, "== code 0x1000\n")
-    # . . push args
-    68/push  "== code\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "ab cd\n")
-    # . . push args
-    68/push  "ab cd\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "ef gh\n")
-    # . . push args
-    68/push  "ef gh\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write(_test-input-stream, "ij l1/imm32\n")
-    # . . push args
-    68/push  "ij l1/imm32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . stream-add2(labels, "l1", 0x1056)
-    68/push  0x1056/imm32/label-address
-    # . . push handle for "l1"
-    53/push-ebx
-    68/push  "l1"/imm32
-    68/push  Heap/imm32
-    e8/call  copy-array/disp32
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
-    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
-    # . . push labels
-    52/push-edx
-    # . . call
-    e8/call  stream-add2/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-    # component under test
-    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
-    # . . push args
-    52/push-edx
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  emit-output/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # checks
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "# 0x00007c00", msg)
-    # . . push args
-    68/push  "F - test-emit-output-code-label-absolute/0"/imm32
-    68/push  "# 0x00007c00"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-code-label-absolute/1"/imm32
-    68/push  "ab cd "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-code-label-absolute/2"/imm32
-    68/push  "ef gh "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . check-next-stream-line-equal(_test-output-stream, "ij f9 ff ff ff ", msg)
-    # . . push args
-    68/push  "F - test-emit-output-code-label-absolute/3"/imm32
-    68/push  "ij 56 10 00 00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-# reads line to make some checks
-# don't assume the read state of line after calling this function
-far-jump-or-call?:  # line: (addr stream byte) -> result/edi: boolean
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    # ecx = line
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # var word-slice/edx: slice
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # var datum-slice/ebx: slice
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
-    # result = false
-    bf/copy-to-edi  0/imm32/false
-$far-jump-or-call?:check-first-word:
-    # next-word(line, word-slice)
-    # . . push args
-    52/push-edx
-    51/push-ecx
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # if (slice-empty?(word-slice)) return false
-    # . eax = slice-empty?(word-slice)
-    # . . push args
-    52/push-edx
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . if (eax != 0) return
-    3d/compare-eax-and  0/imm32/false
-    0f 85/jump-if-!=  $far-jump-or-call?:end/disp32
-    # datum = next-token-from-slice(word-slice->start, word-slice->end, "/")
-    # . . push args
-    53/push-ebx
-    68/push  0x2f/imm32/slash
-    ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
-    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
-    # . . call
-    e8/call  next-token-from-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-    # if (datum-slice == "e8") return true
-    # . eax = slice-equal?(datum-slice, "e8")
-    # . . push args
-    68/push  "e8"/imm32
-    53/push-ebx
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax != false) return true
-    3d/compare-eax-and  0/imm32/false
-    75/jump-if-!=  $far-jump-or-call?:return-true/disp8
-    # if (datum-slice == "e9") return true
-    # . eax = slice-equal?(datum-slice, "e9")
-    # . . push args
-    68/push  "e9"/imm32
-    53/push-ebx
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax != false) return true
-    3d/compare-eax-and  0/imm32/false
-    75/jump-if-!=  $far-jump-or-call?:return-true/disp8
-    # if (datum-slice != "0f") return false
-    # . eax = slice-equal?(datum-slice, "0f")
-    # . . push args
-    68/push  "0f"/imm32
-    53/push-ebx
+    ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
     # . . call
-    e8/call  slice-equal?/disp32
+    e8/call  write-int32-hex-buffered/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == false) return
-    3d/compare-eax-and  0/imm32/false
-    74/jump-if-=  $far-jump-or-call?:end/disp8
-$far-jump-or-call?:check-second-word:
-    # next-word(line, word-slice)
+    # curr += 4
+    81          0/subop/add         3/mod/direct    1/rm32/ecx    .           .             .           .           .               4/imm32           # add to ecx
+    # write-buffered(out, '\n')
     # . . push args
-    52/push-edx
-    51/push-ecx
+    68/push  Newline/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
     # . . call
-    e8/call  next-word/disp32
+    e8/call  write-buffered/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # if (slice-empty?(word-slice)) return false
-    # . eax = slice-empty?(word-slice)
-    # . . push args
-    52/push-edx
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . if (eax != 0) return
-    3d/compare-eax-and  0/imm32/false
-    75/jump-if-!=  $far-jump-or-call?:end/disp8
-    # if datum of word-slice does not start with "8", return false
-    # . start/eax = word-slice->start
-    8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy *edx to eax
-    # . c/eax = *start
-    8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           0/r32/AL    .               .                 # copy byte at *eax to AL
-    81          4/subop/and         3/mod/direct    0/rm32/eax    .           .             .           .           .               0xff/imm32        # bitwise and of eax
-    # . if (eax != '8') return
-    3d/compare-eax-and  0x38/imm32/8
-    75/jump-if-!=  $far-jump-or-call?:end/disp8
-    # otherwise return true
-$far-jump-or-call?:return-true:
-    bf/copy-to-edi  1/imm32/true
-$far-jump-or-call?:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-    # . restore registers
-    5b/pop-to-ebx
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-# - some helpers for tests
-
-stream-add2:  # in: (addr stream byte), key: handle, val: int
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    56/push-esi
-    # esi = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # curr/eax = &in->data[in->write]
-    # . eax = in->write
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    # . eax = esi+eax+12
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  0/index/eax   .           0/r32/eax   0xc/disp8       .                 # copy esi+eax+12 to eax
-    # max/edx = &in->data[in->size]
-    # . edx = in->size
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           2/r32/edx   8/disp8         .                 # copy *(esi+8) to edx
-    # . edx = esi+edx+12
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  2/index/edx   .           2/r32/edx   0xc/disp8       .                 # copy esi+edx+12 to edx
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax with edx
-    73/jump-if-addr>=  $stream-add2:abort/disp8
-    # *curr = key->alloc-id
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
-    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
-    # curr += 4
-    05/add-to-eax  4/imm32
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax with edx
-    73/jump-if-addr>=  $stream-add2:abort/disp8
-    # *curr = key->payload
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   0x10/disp8      .                 # copy *(ebp+16) to ecx
-    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
-    # curr += 4
-    05/add-to-eax  4/imm32
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax with edx
-    73/jump-if-addr>=  $stream-add2:abort/disp8
-    # *curr = val
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   0x14/disp8      .                 # copy *(ebp+20) to ecx
-    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
-    # in->write += 0xc
-    81          0/subop/add         0/mod/indirect  6/rm32/esi    .           .             .           .           .               0xc/imm32         # add to *esi
-$stream-add2:end:
+    # loop
+    e9/jump  $emit-labels:loop/disp32
+$emit-labels:end:
     # . restore registers
     5e/pop-to-esi
     5a/pop-to-edx
@@ -2706,19 +1016,7 @@ $stream-add2:end:
     5d/pop-to-ebp
     c3/return
 
-$stream-add2:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "overflow in stream-add2\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . syscall(exit, 1)
-    bb/copy-to-ebx  1/imm32
-    e8/call  syscall_exit/disp32
-    # never gets here
+# - some helpers for tests
 
 # some variants of 'trace' that take multiple arguments in different combinations of types:
 #   n: int
@@ -2843,543 +1141,4 @@ test-trace-slsns:
     5d/pop-to-ebp
     c3/return
 
-num-bytes:  # line: (addr stream byte) -> eax: int
-    # pseudocode:
-    #   result = 0
-    #   while true
-    #     var word-slice = next-word(line)
-    #     if slice-empty?(word-slice)             # end of line
-    #       break
-    #     if slice-starts-with?(word-slice, "#")  # comment
-    #       break
-    #     if label?(word-slice)                # no need for label declarations anymore
-    #       break
-    #     if slice-equal?(word-slice, "==")
-    #       break                                 # no need for segment header lines
-    #     result += compute-width(word-slice)
-    #   return result
-    #
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    # var result/eax = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # var word-slice/ecx: slice
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # write-stream(2/stderr, line)
-#?     # . . push args
-#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # }}}
-    # . rewind-stream(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$num-bytes:loop:
-    # next-word(line, word-slice)
-    # . . push args
-    51/push-ecx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . clear-stream($Stderr->buffer)
-#?     # . . push args
-#?     68/push  $Stderr->buffer/imm32
-#?     # . . call
-#?     e8/call  clear-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     51/push-ecx
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # }}}
-$num-bytes:check0:
-    # if (slice-empty?(word-slice)) break
-    # . save result
-    50/push-eax
-    # . eax = slice-empty?(word-slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . if (eax != false) break
-    3d/compare-eax-and  0/imm32/false
-    # . restore result now that ZF is set
-    58/pop-to-eax
-    75/jump-if-!=  $num-bytes:end/disp8
-$num-bytes:check-for-comment:
-    # if (slice-starts-with?(word-slice, "#")) break
-    # . start/edx = word-slice->start
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # copy *ecx to edx
-    # . c/ebx = *start
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-    8a/copy-byte                    0/mod/indirect  2/rm32/edx    .           .             .           3/r32/BL    .               .                 # copy byte at *edx to BL
-    # . if (ebx == '#') break
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x23/imm32/hash   # compare ebx
-    74/jump-if-=  $num-bytes:end/disp8
-$num-bytes:check-for-label:
-    # if (slice-ends-with?(word-slice, ":")) break
-    # . end/edx = word-slice->end
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
-    # . c/ebx = *(end-1)
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-    8a/copy-byte                    1/mod/*+disp8   2/rm32/edx    .           .             .           3/r32/BL    -1/disp8        .                 # copy byte at *ecx to BL
-    # . if (ebx == ':') break
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x3a/imm32/colon  # compare ebx
-    74/jump-if-=  $num-bytes:end/disp8
-$num-bytes:check-for-segment-header:
-    # if (slice-equal?(word-slice, "==")) break
-    # . push result
-    50/push-eax
-    # . eax = slice-equal?(word-slice, "==")
-    # . . push args
-    68/push  "=="/imm32
-    51/push-ecx
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax != false) break
-    3d/compare-eax-and  0/imm32/false
-    # . restore result now that ZF is set
-    58/pop-to-eax
-    75/jump-if-!=  $num-bytes:end/disp8
-$num-bytes:loop-body:
-    # result += compute-width-of-slice(word-slice)
-    # . copy result to edx
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to edx
-    # . eax = compute-width-of-slice(word-slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call compute-width-of-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . eax += result
-    01/add                          3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # add edx to eax
-    e9/jump  $num-bytes:loop/disp32
-$num-bytes:end:
-    # . rewind-stream(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . restore registers
-    5b/pop-to-ebx
-    5a/pop-to-edx
-    59/pop-to-ecx
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-num-bytes-handles-empty-string:
-    # if a line starts with '#', return 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # no contents in input
-    # eax = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-handles-empty-string"/imm32
-    68/push  0/imm32/true
-    50/push-eax
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-num-bytes-ignores-comments:
-    # if a line starts with '#', return 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # initialize input
-    # . write(_test-input-stream, "# abcd")
-    # . . push args
-    68/push  "# abcd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # eax = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-ignores-comments"/imm32
-    68/push  0/imm32/true
-    50/push-eax
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-num-bytes-ignores-labels:
-    # if the first word ends with ':', return 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # initialize input
-    # . write(_test-input-stream, "ab: # cd")
-    # . . push args
-    68/push  "ab: # cd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # eax = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-ignores-labels"/imm32
-    68/push  0/imm32/true
-    50/push-eax
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-num-bytes-ignores-segment-headers:
-    # if the first word is '==', return 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # initialize input
-    # . write(_test-input-stream, "== ab cd")
-    # . . push args
-    68/push  "== ab cd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # eax = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-ignores-segment-headers"/imm32
-    68/push  0/imm32/true
-    50/push-eax
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-num-bytes-counts-words-by-default:
-    # without metadata, count words
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # initialize input
-    # . write(_test-input-stream, "ab cd ef")
-    # . . push args
-    68/push  "ab cd ef"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # eax = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 3, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-counts-words-by-default"/imm32
-    68/push  3/imm32/true
-    50/push-eax
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-num-bytes-ignores-trailing-comment:
-    # trailing comments appropriately ignored
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # initialize input
-    # . write(_test-input-stream, "ab cd # ef")
-    # . . push args
-    68/push  "ab cd # ef"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # eax = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 2, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-ignores-trailing-comment"/imm32
-    68/push  2/imm32/true
-    50/push-eax
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-num-bytes-handles-imm32:
-    # if a word has the /imm32 metadata, count it as 4 bytes
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # initialize input
-    # . write(_test-input-stream, "ab cd/imm32 ef")
-    # . . push args
-    68/push  "ab cd/imm32 ef"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # eax = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 6, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-handles-imm32"/imm32
-    68/push  6/imm32/true
-    50/push-eax
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
 # . . vim:nowrap:textwidth=0
diff --git a/linux/test_apps b/linux/test_apps
index 9d8e5683..1719b5fd 100755
--- a/linux/test_apps
+++ b/linux/test_apps
@@ -216,7 +216,7 @@ bootstrap/bootstrap translate [01]*.subx random.subx  -o random
 
 # Phases of the self-hosted SubX translator.
 
-for phase in hex survey_elf survey_baremetal pack assort dquotes tests
+for phase in hex survey_elf survey_baremetal labels_baremetal pack assort dquotes tests
 do
   echo $phase
   bootstrap/bootstrap translate [01]*.subx subx-params.subx $phase.subx -o $phase
@@ -314,7 +314,7 @@ done
 
 # Phases of the self-hosted SubX translator.
 
-for app in hex survey_elf survey_baremetal pack assort dquotes tests
+for app in hex survey_elf survey_baremetal labels_baremetal pack assort dquotes tests
 do
   echo $app
   ./translate_subx [01]*.subx subx-params.subx $app.subx
diff --git a/linux/test_apps_emulated b/linux/test_apps_emulated
index cb74c1ae..f12ada06 100755
--- a/linux/test_apps_emulated
+++ b/linux/test_apps_emulated
@@ -137,7 +137,7 @@ bootstrap/bootstrap translate [01]*.subx random.subx  -o random
 
 # Phases of the self-hosted SubX translator.
 
-for phase in hex survey_elf survey_baremetal pack assort dquotes tests
+for phase in hex survey_elf survey_baremetal labels_baremetal pack assort dquotes tests
 do
   echo $phase
   bootstrap/bootstrap translate [01]*.subx subx-params.subx $phase.subx -o $phase
@@ -211,7 +211,7 @@ done
 
 # Phases of the self-hosted SubX translator.
 
-for app in hex survey_elf survey_baremetal pack assort dquotes tests
+for app in hex survey_elf survey_baremetal labels_baremetal pack assort dquotes tests
 do
   echo $app
   ./translate_subx_emulated [01]*.subx subx-params.subx $app.subx
diff --git a/translate_subx b/translate_subx
index f4c461f6..70400f45 100755
--- a/translate_subx
+++ b/translate_subx
@@ -12,23 +12,24 @@
 
 set -e
 
-cat $*          |linux/braces            > a.braces
+cat $*          |linux/braces                                   > a.braces
 
-cat a.braces    |linux/calls             > a.calls
+cat a.braces    |linux/calls                                    > a.calls
 
-cat a.calls     |linux/sigils            > a.sigils
+cat a.calls     |linux/sigils                                   > a.sigils
 
-cat a.sigils    |linux/tests             > a.tests
+cat a.sigils    |linux/tests                                    > a.tests
 
 # no assort since baremetal SubX doesn't have segments yet
 
-cat a.tests     |linux/dquotes           > a.dquotes
+cat a.tests     |linux/dquotes                                  > a.dquotes
 
-cat a.dquotes   |linux/pack              > a.pack
+cat a.dquotes   |linux/pack                                     > a.pack
 
-cat a.pack      |linux/survey_baremetal  > a.survey
+cat a.pack      |linux/survey_baremetal   > labels
+cat a.pack      |linux/labels_baremetal     labels              > a.survey
 
-cat a.survey    |linux/hex               > a.bin
+cat a.survey    |linux/hex                                      > a.bin
 
 # Create code.img containing a.bin
 dd if=/dev/zero of=code.img count=20160  # 512-byte sectors, so 10MB
diff --git a/translate_subx_emulated b/translate_subx_emulated
index 85b56436..26dd5364 100755
--- a/translate_subx_emulated
+++ b/translate_subx_emulated
@@ -3,27 +3,37 @@
 #
 # This script uses emulation, so it does not require x86 or Linux. However it
 # is slow.
+#
+# A couple of gotchas:
+# * Many phases here have no error-checking. Perhaps I should use a
+#   version of translate_subx_debug for baremetal.
+# * Don't pass in numbered .subx files without translated .mu files. Our test
+#   harness is in test.mu, and only Mu programs can run tests in baremetal.
+#
+# The top level is in general not as rigorous about avoiding dependency cycles
+# as the lower-level tools in linux/
 
 set -e
 set -v
 
-cat $*            |linux/bootstrap/bootstrap run linux/braces             > a.braces
+cat $*            |linux/bootstrap/bootstrap run linux/braces                         > a.braces
 
-cat a.braces      |linux/bootstrap/bootstrap run linux/calls              > a.calls
+cat a.braces      |linux/bootstrap/bootstrap run linux/calls                          > a.calls
 
-cat a.calls       |linux/bootstrap/bootstrap run linux/sigils             > a.sigils
+cat a.calls       |linux/bootstrap/bootstrap run linux/sigils                         > a.sigils
 
-cat a.sigils      |linux/bootstrap/bootstrap run linux/tests              > a.tests
+cat a.sigils      |linux/bootstrap/bootstrap run linux/tests                          > a.tests
 
 # no assort since baremetal SubX doesn't have segments yet
 
-cat a.tests       |linux/bootstrap/bootstrap run linux/dquotes            > a.dquotes
+cat a.tests       |linux/bootstrap/bootstrap run linux/dquotes                        > a.dquotes
 
-cat a.dquotes     |linux/bootstrap/bootstrap run linux/pack               > a.pack
+cat a.dquotes     |linux/bootstrap/bootstrap run linux/pack                           > a.pack
 
-cat a.pack        |linux/bootstrap/bootstrap run linux/survey_baremetal   > a.survey
+cat a.pack        |linux/bootstrap/bootstrap linux/survey_baremetal   > labels
+cat a.pack        |linux/bootstrap/bootstrap linux/labels_baremetal     labels        > a.survey
 
-cat a.survey      |linux/bootstrap/bootstrap run linux/hex                > a.bin
+cat a.survey      |linux/bootstrap/bootstrap run linux/hex                            > a.bin
 
 # Create code.img containing a.bin
 dd if=/dev/zero of=code.img count=20160  # 512-byte sectors, so 10MB