about summary refs log tree commit diff stats
path: root/baremetal
diff options
context:
space:
mode:
Diffstat (limited to 'baremetal')
-rw-r--r--baremetal/101screen.subx42
-rw-r--r--baremetal/102keyboard.subx48
-rw-r--r--baremetal/103grapheme.subx173
-rw-r--r--baremetal/104test.subx32
-rw-r--r--baremetal/105string-equal.subx256
-rw-r--r--baremetal/106stream.subx77
-rw-r--r--baremetal/108write.subx220
-rw-r--r--baremetal/109stream-equal.subx595
-rw-r--r--baremetal/112read-byte.subx123
-rw-r--r--baremetal/113write-stream.subx173
-rw-r--r--baremetal/115write-byte.subx82
-rw-r--r--baremetal/117write-int-hex.subx202
-rw-r--r--baremetal/118parse-hex-int.subx897
-rw-r--r--baremetal/120allocate.subx914
-rw-r--r--baremetal/121new-stream.subx127
-rw-r--r--baremetal/123slice.subx1069
-rw-r--r--baremetal/124next-token.subx1772
-rw-r--r--baremetal/126write-int-decimal.subx428
-rw-r--r--baremetal/127next-word.subx362
-rw-r--r--baremetal/301array-equal.subx432
-rw-r--r--baremetal/302stack_allocate.subx61
-rw-r--r--baremetal/308allocate-array.subx25
-rw-r--r--baremetal/309stream.subx214
-rw-r--r--baremetal/310copy-bytes.subx157
-rw-r--r--baremetal/311decimal-int.subx633
-rw-r--r--baremetal/312copy.subx13
-rw-r--r--baremetal/313index-bounds-check.subx60
-rw-r--r--baremetal/314divide.subx17
-rw-r--r--baremetal/400.mu82
-rw-r--r--baremetal/403unicode.mu193
-rw-r--r--baremetal/408float.mu23
-rw-r--r--baremetal/411string.mu125
-rw-r--r--baremetal/412render-float-decimal.mu597
-rw-r--r--baremetal/500text-screen.mu298
-rw-r--r--baremetal/501draw-text.mu466
-rw-r--r--baremetal/502test.mu43
-rw-r--r--baremetal/503manhattan-line.mu28
-rw-r--r--baremetal/504test-screen.mu327
-rw-r--r--baremetal/README.md45
-rw-r--r--baremetal/boot.bochsrc15
-rw-r--r--baremetal/boot.hex1181
-rw-r--r--baremetal/boot0.hex344
-rw-r--r--baremetal/ex1.hex19
-rw-r--r--baremetal/ex1.subx18
-rw-r--r--baremetal/ex2.hex42
-rw-r--r--baremetal/ex2.mu31
-rw-r--r--baremetal/ex2.subx35
-rw-r--r--baremetal/ex3.hex58
-rw-r--r--baremetal/ex3.mu31
-rw-r--r--baremetal/ex4.mu14
-rw-r--r--baremetal/ex5.mu16
-rw-r--r--baremetal/ex6.mu32
-rw-r--r--baremetal/ex7.mu46
-rw-r--r--baremetal/ex8.mu6
-rw-r--r--baremetal/life.mu252
-rw-r--r--baremetal/mu-init.subx27
-rw-r--r--baremetal/rpn.mu152
-rw-r--r--baremetal/shell/cell.mu89
-rw-r--r--baremetal/shell/eval.mu0
-rw-r--r--baremetal/shell/gap-buffer.mu781
-rw-r--r--baremetal/shell/grapheme-stack.mu280
-rw-r--r--baremetal/shell/main.mu22
-rw-r--r--baremetal/shell/parse.mu136
-rw-r--r--baremetal/shell/print.mu260
-rw-r--r--baremetal/shell/read.mu15
-rw-r--r--baremetal/shell/sandbox.mu263
-rw-r--r--baremetal/shell/tokenize.mu422
-rw-r--r--baremetal/shell/trace.mu1449
-rw-r--r--baremetal/shell/vimrc.vim2
-rw-r--r--baremetal/vga_palette283
-rw-r--r--baremetal/vga_palette.c179
-rw-r--r--baremetal/vga_palette.pngbin1326889 -> 0 bytes
-rw-r--r--baremetal/vimrc.vim2
73 files changed, 0 insertions, 17933 deletions
diff --git a/baremetal/101screen.subx b/baremetal/101screen.subx
deleted file mode 100644
index 46cee777..00000000
--- a/baremetal/101screen.subx
+++ /dev/null
@@ -1,42 +0,0 @@
-# Primitives for screen control.
-#
-# We need to do this in machine code because Mu doesn't have global variables
-# yet (for the start of video memory).
-
-== code
-
-pixel-on-real-screen:  # x: int, y: int, color: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    # bounds checks
-    8b/-> *(ebp+8) 0/r32/eax
-    3d/compare-eax-and 0/imm32
-    7c/jump-if-< $pixel-on-real-screen:end/disp8
-    3d/compare-eax-and 0x400/imm32/screen-width=1024
-    7d/jump-if->= $pixel-on-real-screen:end/disp8
-    8b/-> *(ebp+0xc) 0/r32/eax
-    3d/compare-eax-and 0/imm32
-    7c/jump-if-< $pixel-on-real-screen:end/disp8
-    3d/compare-eax-and 0x300/imm32/screen-height=768
-    7d/jump-if->= $pixel-on-real-screen:end/disp8
-    # eax = y*1024 + x
-    8b/-> *(ebp+0xc) 0/r32/eax
-    c1/shift 4/subop/left %eax 0xa/imm8
-    03/add-> *(ebp+8) 0/r32/eax
-    # eax += location of frame buffer
-    03/add-> *0x8128 0/r32/eax  # unsafe
-    # *eax = color
-    8b/-> *(ebp+0x10) 1/r32/ecx
-    88/byte<- *eax 1/r32/CL
-$pixel-on-real-screen:end:
-    # . restore registers
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
diff --git a/baremetal/102keyboard.subx b/baremetal/102keyboard.subx
deleted file mode 100644
index df74c8e7..00000000
--- a/baremetal/102keyboard.subx
+++ /dev/null
@@ -1,48 +0,0 @@
-# check keyboard for a key
-# return 0 on no keypress or unrecognized key
-#
-# We need to do this in machine code because Mu doesn't have global variables
-# yet (for the keyboard buffer).
-
-== code
-
-read-key:  # kbd: (addr keyboard) -> result/eax: byte
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    # result = 0
-    b8/copy-to-eax 0/imm32
-    # ecx = keyboard
-    8b/-> *(ebp+8) 1/r32/ecx
-    81 7/subop/compare %ecx 0/imm32
-    {
-      75/jump-if-!= break/disp8
-      # var read/ecx: byte = keyboard buffer's read index
-      8b/-> *0x802c 1/r32/CL  # keyboard-buffer-read
-      # var next-key/eax: byte = *(keyboard buffer + ecx)
-      8a/byte-> *(ecx+0x8030) 0/r32/AL  # keyboard-buffer-data
-      # if (next-key != 0) lock and remove from keyboard buffer
-      81 7/subop/compare %eax 0/imm32
-      {
-        74/jump-if-= break/disp8
-        # TODO: add some instructions in this block to SubX if we ever want to
-        # use bootstrap on baremetal programs
-        fa/disable-interrupts
-        c6 0/subop/copy-byte *(ecx+0x8030) 0/imm8  # keyboard-buffer-data
-        ff 0/subop/increment *0x802c  # keyboard-buffer-read
-        81 4/subop/and *0x802c 0xf/imm32  # keyboard-buffer-read
-        fb/enable-interrupts
-      }
-      # return
-      eb $read-key:end/disp8
-    }
-    # TODO: fake keyboard
-$read-key:end:
-    # . restore registers
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
diff --git a/baremetal/103grapheme.subx b/baremetal/103grapheme.subx
deleted file mode 100644
index d26a0b58..00000000
--- a/baremetal/103grapheme.subx
+++ /dev/null
@@ -1,173 +0,0 @@
-# Use the built-in font to draw a grapheme to real screen.
-#
-# We need to do this in machine code because Mu doesn't have global variables
-# yet (for the start of video memory).
-#
-# There are uncomfortable assumptions baked in here about english/latin
-# script. We convert the grid of pixels into a fixed-width grid of graphemes,
-# which may not work well with other language families.
-
-== code
-
-# The Mu computer's screen is 1024px wide and 768px tall.
-# The Mu computer's font is 8px wide and 16px tall.
-# Therefore 'x' here is in [0, 128), and 'y' is in [0, 48)
-# Doesn't update the cursor; where the cursor should go after printing the
-# current grapheme is a higher-level concern.
-draw-grapheme-on-real-screen:  # g: grapheme, x: int, y: int, color: int, background-color: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    56/push-esi
-    # var letter-bitmap/esi = font[g]
-    8b/-> *(ebp+8) 6/r32/esi
-    c1 4/subop/shift-left %esi 4/imm8
-    8d/copy-address *(esi+0x8c00) 6/r32/esi  # font-start
-    # if (letter-bitmap >= 0x9400) return  # characters beyond ASCII currently not supported
-    81 7/subop/compare %esi 0x9400/imm32
-    7d/jump-if->= $draw-grapheme-on-real-screen:end/disp8
-    # var ycurr/edx: int = y*16
-    8b/-> *(ebp+0x10) 2/r32/edx
-    c1 4/subop/shift-left %edx 4/imm8
-    # var ymax/ebx: int = ycurr + 16
-    8b/-> *(ebp+0x10) 3/r32/ebx
-    c1 4/subop/shift-left %ebx 4/imm8
-    81 0/subop/add %ebx 0x10/imm32
-    {
-      # if (ycurr >= ymax) break
-      39/compare %edx 3/r32/ebx
-      7d/jump-if->= break/disp8
-      # var xcurr/eax: int = x*8 + 7
-      8b/-> *(ebp+0xc) 0/r32/eax  # font-width - 1
-      c1 4/subop/shift-left %eax 3/imm8
-      81 0/subop/add %eax 7/imm32
-      # var xmin/ecx: int = x*8
-      8b/-> *(ebp+0xc) 1/r32/ecx
-      c1 4/subop/shift-left %ecx 3/imm8
-      # var row-bitmap/ebx: int = *letter-bitmap
-      53/push-ebx
-      8b/-> *esi 3/r32/ebx
-      {
-        # if (xcurr < xmin) break
-        39/compare %eax 1/r32/ecx
-        7c/jump-if-< break/disp8
-        # shift LSB from row-bitmap into carry flag (CF)
-        c1 5/subop/shift-right-logical %ebx 1/imm8
-        # if LSB, draw a pixel in the given color
-        {
-          73/jump-if-not-CF break/disp8
-          (pixel-on-real-screen %eax %edx *(ebp+0x14))
-          eb/jump $draw-grapheme-on-real-screen:continue/disp8
-        }
-        # otherwise use the background color
-        (pixel-on-real-screen %eax %edx *(ebp+0x18))
-$draw-grapheme-on-real-screen:continue:
-        # --x
-        48/decrement-eax
-        #
-        eb/jump loop/disp8
-      }
-      # reclaim row-bitmap
-      5b/pop-to-ebx
-      # ++y
-      42/increment-edx
-      # next bitmap row
-      46/increment-esi
-      #
-      eb/jump loop/disp8
-    }
-$draw-grapheme-on-real-screen:end:
-    # . restore registers
-    5e/pop-to-esi
-    5b/pop-to-ebx
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-cursor-position-on-real-screen:  # -> _/eax: int, _/ecx: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # TODO: support fake screen; we currently assume 'screen' is always 0 (real)
-    8b/-> *Real-screen-cursor-x 0/r32/eax
-    8b/-> *Real-screen-cursor-y 1/r32/ecx
-$cursor-position-on-real-screen:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-set-cursor-position-on-real-screen:  # x: int, y: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    #
-    8b/-> *(ebp+8) 0/r32/eax
-    89/<- *Real-screen-cursor-x 0/r32/eax
-    8b/-> *(ebp+0xc) 0/r32/eax
-    89/<- *Real-screen-cursor-y 0/r32/eax
-$set-cursor-position-on-real-screen:end:
-    # . restore registers
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-# Draw cursor at current location. But this is rickety:
-#   - does not clear previous location cursor was shown at.
-#   - does not preserve what was at the cursor. Caller is responsible for
-#     tracking what was on the screen at this position before and passing it
-#     in again.
-#   - does not stop showing the cursor at this location when the cursor moves
-show-cursor-on-real-screen:  # g: grapheme
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    #
-    (cursor-position-on-real-screen)  # => eax, ecx
-    (draw-grapheme-on-real-screen *(ebp+8) %eax %ecx 0 7)
-$show-cursor-on-real-screen:end:
-    # . restore registers
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-== data
-
-# The cursor is where certain Mu functions (usually of the form
-# 'draw*cursor*') print to by default.
-#
-# We don't bother displaying the cursor when drawing. It only becomes visible
-# on show-cursor, which is quite rickety (see above)
-#
-# It's up to applications to manage cursor display:
-#   - clean up where it used to be
-#   - display the cursor before waiting for a key
-#   - ensure its location appropriately suggests the effect keystrokes will have
-#   - ensure its contents (and colors) appropriately reflect the state of the
-#     screen
-#
-# There's no blinking, etc. We aren't using any hardware-supported text mode
-# here.
-Real-screen-cursor-x:
-  0/imm32
-Real-screen-cursor-y:
-  0/imm32
diff --git a/baremetal/104test.subx b/baremetal/104test.subx
deleted file mode 100644
index 5b14049e..00000000
--- a/baremetal/104test.subx
+++ /dev/null
@@ -1,32 +0,0 @@
-# Some helpers needed only because Mu doesn't support globals at the moment.
-
-== code
-
-count-test-failure:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    ff 0/subop/increment *Num-test-failures
-$count-test-failure:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-num-test-failures:  # -> _/eax: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    8b/-> *Num-test-failures 0/r32/eax
-$num-test-failures:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-== data
-
-Num-test-failures:
-  0/imm32
diff --git a/baremetal/105string-equal.subx b/baremetal/105string-equal.subx
deleted file mode 100644
index 2289e514..00000000
--- a/baremetal/105string-equal.subx
+++ /dev/null
@@ -1,256 +0,0 @@
-# Comparing 'regular' size-prefixed strings.
-
-== 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
-
-string-equal?:  # s: (addr array byte), benchmark: (addr array byte) -> result/eax: boolean
-    # pseudocode:
-    #   if (s->size != benchmark->size) return false
-    #   return string-starts-with?(s, benchmark)
-    #
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    56/push-esi
-    57/push-edi
-    # esi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # edi = benchmark
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-    # ecx = s->size
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
-$string-equal?:sizes:
-    # if (ecx != benchmark->size) return false
-    39/compare                      0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # compare *edi and ecx
-    b8/copy-to-eax  0/imm32/false
-    75/jump-if-!=  $string-equal?:end/disp8
-$string-equal?:contents:
-    # string-starts-with?(s, benchmark)
-    # . . push args
-    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  string-starts-with?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$string-equal?:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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
-
-string-starts-with?:  # s: (addr array byte), benchmark: (addr array byte) -> result/eax: boolean
-    # pseudocode:
-    #   if (s->size < benchmark->size) return false
-    #   currs = s->data
-    #   currb = benchmark->data
-    #   maxb = &benchmark->data[benchmark->size]
-    #   while currb < maxb
-    #     c1 = *currs
-    #     c2 = *currb
-    #     if (c1 != c2) return false
-    #     ++currs, ++currb
-    #   return true
-    #
-    # registers:
-    #   currs: esi
-    #   maxs: ecx
-    #   currb: edi
-    #   c1: eax
-    #   c2: ebx
-    #
-    # . 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
-    56/push-esi
-    57/push-edi
-    # esi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # edi = benchmark
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-    # var bsize/ecx: int = benchmark->size
-    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
-$string-starts-with?:sizes:
-    # if (s->size < bsize) return false
-    39/compare                      0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare *esi with ecx
-    7c/jump-if-<  $string-starts-with?:false/disp8
-    # var currs/esi: (addr byte) = s->data
-    81          0/subop/add         3/mod/direct    6/rm32/esi    .           .             .           .           .               4/imm32           # add to esi
-    # var currb/edi: (addr byte) = benchmark->data
-    81          0/subop/add         3/mod/direct    7/rm32/edi    .           .             .           .           .               4/imm32           # add to edi
-    # var maxb/ecx: (addr byte) = &benchmark->data[benchmark->size]
-    01/add                          3/mod/direct    1/rm32/ecx    .           .             .           7/r32/edi   .               .                 # add edi to ecx
-    # var c1/eax: byte = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # var c2/edx: byte = 0
-    31/xor                          3/mod/direct    2/rm32/edx    .           .             .           2/r32/edx   .               .                 # clear edx
-$string-starts-with?:loop:
-    # if (currs >= maxs) return true
-    39/compare                      3/mod/direct    7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # compare edi with ecx
-    73/jump-if-addr>=  $string-starts-with?:true/disp8
-    # c1 = *currs
-    8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
-    # c2 = *currb
-    8a/copy-byte                    0/mod/indirect  7/rm32/edi    .           .             .           2/r32/DL    .               .                 # copy byte at *edi to DL
-    # if (c1 != c2) return false
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax and edx
-    75/jump-if-!=  $string-starts-with?:false/disp8
-    # ++currs
-    46/increment-esi
-    # ++currb
-    47/increment-edi
-    eb/jump  $string-starts-with?:loop/disp8
-$string-starts-with?:true:
-    b8/copy-to-eax  1/imm32
-    eb/jump  $string-starts-with?:end/disp8
-$string-starts-with?:false:
-    b8/copy-to-eax  0/imm32
-$string-starts-with?:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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
-
-# - tests
-
-test-compare-empty-with-empty-string:
-    # eax = string-equal?("", "")
-    # . . push args
-    68/push  ""/imm32
-    68/push  ""/imm32
-    # . . call
-    e8/call  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-compare-empty-with-empty-string"/imm32
-    68/push  1/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
-    c3/return
-
-test-compare-empty-with-non-empty-string:  # also checks size-mismatch code path
-    # eax = string-equal?("", "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  ""/imm32
-    # . . call
-    e8/call  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-compare-empty-with-non-empty-string"/imm32
-    68/push  0/imm32/false
-    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
-    c3/return
-
-test-compare-equal-strings:
-    # eax = string-equal?("Abc", "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  "Abc"/imm32
-    # . . call
-    e8/call  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-compare-equal-strings"/imm32
-    68/push  1/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
-    c3/return
-
-test-compare-inequal-strings-equal-sizes:
-    # eax = string-equal?("Abc", "Adc")
-    # . . push args
-    68/push  "Adc"/imm32
-    68/push  "Abc"/imm32
-    # . . call
-    e8/call  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-compare-inequal-strings-equal-sizes"/imm32
-    68/push  0/imm32/false
-    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
-    c3/return
-
-# helper for later tests
-check-strings-equal:  # s: (addr array byte), expected: (addr array byte), msg: (addr array byte)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    50/push-eax
-    # var eax: boolean = string-equal?(s, expected)
-    # . . push args
-    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  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
-    68/push  1/imm32
-    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
-$check-strings-equal:end:
-    # . restore registers
-    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
-
-# test the helper
-test-check-strings-equal:
-    # check-strings-equal("Abc", "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  "Abc"/imm32
-    # . . call
-    e8/call  check-strings-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    c3/return
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/106stream.subx b/baremetal/106stream.subx
deleted file mode 100644
index 9949ee7d..00000000
--- a/baremetal/106stream.subx
+++ /dev/null
@@ -1,77 +0,0 @@
-# streams: data structure for operating on arrays in a stateful manner
-#
-# A stream looks like this:
-#   write: int  # index at which writes go
-#   read: int  # index that we've read until
-#   data: (array byte)  # prefixed by size as usual
-#
-# some primitives for operating on streams:
-#   - clear-stream (clears everything but the data size)
-#   - rewind-stream (resets read pointer)
-#
-# We need to do this in machine code because streams need to be opaque types,
-# and we don't yet support opaque types in Mu.
-
-== 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
-
-clear-stream:  # f: (addr stream byte)
-    # . 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
-    # eax = f
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
-    # var count/ecx: int = f->size
-    8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(eax+8) to ecx
-    # var max/ecx: (addr byte) = &f->data[f->size]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   0xc/disp8       .                 # copy eax+ecx+12 to ecx
-    # f->write = 0
-    c7          0/subop/copy        0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm32           # copy to *eax
-    # f->read = 0
-    c7          0/subop/copy        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         0/imm32           # copy to *(eax+4)
-    # - clear all stream data
-    # - this isn't strictly necessary, and it can slow things down *a lot*, but better safe than sorry.
-    # var curr/eax: (addr byte) = f->data
-    81          0/subop/add         3/mod/direct    0/rm32/eax    .           .             .           .           .               0xc/imm32         # add to eax
-$clear-stream:loop:
-    # if (curr >= max) break
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
-    73/jump-if-addr>=  $clear-stream:end/disp8
-    # *curr = 0
-    c6          0/subop/copy-byte   0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm8            # copy byte to *eax
-    # ++curr
-    40/increment-eax
-    eb/jump  $clear-stream:loop/disp8
-$clear-stream:end:
-    # . restore registers
-    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
-
-rewind-stream:  # f: (addr stream byte)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    50/push-eax
-    # eax = f
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
-    # f->read = 0
-    c7          0/subop/copy        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         0/imm32           # copy to *(eax+4)
-$rewind-stream:end:
-    # . restore registers
-    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
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/108write.subx b/baremetal/108write.subx
deleted file mode 100644
index 43e9bf9b..00000000
--- a/baremetal/108write.subx
+++ /dev/null
@@ -1,220 +0,0 @@
-# write: write to in-memory streams
-#
-# We need to do this in machine code because streams need to be opaque types,
-# and we don't yet support opaque types in Mu.
-
-== 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
-
-write:  # f: (addr stream byte), s: (addr array byte)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # if (s == 0) return
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       0/imm32           # compare *(ebp+12)
-    74/jump-if-=  $write:end/disp8
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    # ecx = f
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # edx = f->write
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # copy *ecx to edx
-    # ebx = f->size
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           3/r32/ebx   8/disp8         .                 # copy *(ecx+8) to ebx
-    # eax = _append-3(&f->data[f->write], &f->data[f->size], s)
-    # . . push s
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . push &f->data[f->size]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  3/index/ebx   .           3/r32/ebx   0xc/disp8       .                 # copy ecx+ebx+12 to ebx
-    53/push-ebx
-    # . . push &f->data[f->write]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  2/index/edx   .           3/r32/ebx   0xc/disp8       .                 # copy ecx+edx+12 to ebx
-    53/push-ebx
-    # . . call
-    e8/call  _append-3/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # f->write += eax
-    01/add                          0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # add eax to *ecx
-    # . restore registers
-    5b/pop-to-ebx
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-$write:end:
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-write-single:
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(*_test-stream->data, 41/A 62/b 00 00, msg)
-    # . . push args
-    68/push  "F - test-write-single"/imm32
-    68/push  0x006241/imm32/Ab
-    # . . push *_test-stream->data
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-test-write-appends:
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "C")
-    # . . push args
-    68/push  "C"/imm32
-    68/push  _test-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-stream, "D")
-    # . . push args
-    68/push  "D"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(*_test-stream->data, 43/C 44/D 00 00, msg)
-    # . . push args
-    68/push  "F - test-write-appends"/imm32
-    68/push  0x00004443/imm32/C-D
-    # . . push *_test-stream->data
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-== data
-
-_test-stream:  # (stream byte)
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # size
-    0x10/imm32
-    # data (2 lines x 8 bytes/line)
-    00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00
-
-== code
-
-# 3-argument variant of _append
-_append-3:  # out: (addr byte), outend: (addr byte), s: (addr array byte) -> num_bytes_appended/eax
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    # eax = _append-4(out, outend, &s->data[0], &s->data[s->size])
-    # . . push &s->data[s->size]
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
-    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
-    51/push-ecx
-    # . . push &s->data[0]
-    8d/copy-address                 1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   4/disp8         .                 # copy eax+4 to ecx
-    51/push-ecx
-    # . . push outend
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    # . . push out
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  _append-4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-$_append-3:end:
-    # . restore registers
-    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
-
-# 4-argument variant of _append
-_append-4:  # out: (addr byte), outend: (addr byte), in: (addr byte), inend: (addr byte) -> num_bytes_appended/eax: int
-    # . 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
-    56/push-esi
-    57/push-edi
-    # num_bytes_appended = 0
-    b8/copy-to-eax  0/imm32
-    # edi = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
-    # edx = outend
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
-    # esi = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0x10/disp8      .                 # copy *(ebp+16) to esi
-    # ecx = inend
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x14/disp8      .                 # copy *(ebp+20) to ecx
-$_append-4:loop:
-    # if (in >= inend) break
-    39/compare                      3/mod/direct    6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare esi with ecx
-    73/jump-if-addr>=  $_append-4:end/disp8
-    # if (out >= outend) abort  # just to catch test failures fast
-    39/compare                      3/mod/direct    7/rm32/edi    .           .             .           2/r32/edx   .               .                 # compare edi with edx
-    73/jump-if-addr>=  $_append-4:end/disp8  # TODO: abort
-    # *out = *in
-    8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           3/r32/BL    .               .                 # copy byte at *esi to BL
-    88/copy-byte                    0/mod/indirect  7/rm32/edi    .           .             .           3/r32/BL    .               .                 # copy byte at BL to *edi
-    # ++num_bytes_appended
-    40/increment-eax
-    # ++in
-    46/increment-esi
-    # ++out
-    47/increment-edi
-    eb/jump  $_append-4:loop/disp8
-$_append-4:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/109stream-equal.subx b/baremetal/109stream-equal.subx
deleted file mode 100644
index 8f6cf1bf..00000000
--- a/baremetal/109stream-equal.subx
+++ /dev/null
@@ -1,595 +0,0 @@
-# some primitives for checking stream contents
-
-== 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
-
-# compare all the data in a stream (ignoring the read pointer)
-stream-data-equal?:  # f: (addr stream byte), s: (addr array byte) -> result/eax: boolean
-    # . 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
-    56/push-esi
-    57/push-edi
-    # esi = f
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # eax = f->write
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    # var maxf/edx: (addr byte) = &f->data[f->write]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  0/index/eax   .           2/r32/edx   0xc/disp8       .                 # copy esi+eax+12 to edx
-    # var currf/esi: (addr byte) = f->data
-    81          0/subop/add         3/mod/direct    6/rm32/esi    .           .             .           .           .               0xc/imm32         # add to esi
-    # edi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-$stream-data-equal?:compare-sizes:
-    # if (f->write != s->size) return false
-    39/compare                      0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # compare *edi and eax
-    75/jump-if-!=  $stream-data-equal?:false/disp8
-    # var currs/edi: (addr byte) = s->data
-    81          0/subop/add         3/mod/direct    7/rm32/edi    .           .             .           .           .               4/imm32           # add to edi
-    # var eax: byte = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # var ecx: byte = 0
-    31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
-$stream-data-equal?:loop:
-    # if (currf >= maxf) return true
-    39/compare                      3/mod/direct    6/rm32/esi    .           .             .           2/r32/edx   .               .                 # compare esi with edx
-    73/jump-if-addr>=  $stream-data-equal?:true/disp8
-    # AL = *currs
-    8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
-    # CL = *curr
-    8a/copy-byte                    0/mod/indirect  7/rm32/edi    .           .             .           1/r32/CL    .               .                 # copy byte at *edi to CL
-    # if (eax != ecx) return false
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax and ecx
-    75/jump-if-!=  $stream-data-equal?:false/disp8
-    # ++f
-    46/increment-esi
-    # ++curr
-    47/increment-edi
-    eb/jump $stream-data-equal?:loop/disp8
-$stream-data-equal?:false:
-    b8/copy-to-eax  0/imm32
-    eb/jump  $stream-data-equal?:end/disp8
-$stream-data-equal?:true:
-    b8/copy-to-eax  1/imm32
-$stream-data-equal?:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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-stream-data-equal:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-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 = stream-data-equal?(_test-stream, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  stream-data-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-stream-data-equal"/imm32
-    68/push  1/imm32
-    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-stream-data-equal-2:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-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 = stream-data-equal?(_test-stream, "Abd")
-    # . . push args
-    68/push  "Abd"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  stream-data-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-stream-data-equal-2"/imm32
-    68/push  0/imm32
-    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-stream-data-equal-size-check:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-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 = stream-data-equal?(_test-stream, "Abcd")
-    # . . push args
-    68/push  "Abcd"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  stream-data-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-stream-data-equal-size-check"/imm32
-    68/push  0/imm32
-    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
-
-# helper for later tests
-check-stream-equal:  # f: (addr stream byte), s: (addr array byte), msg: (addr array byte)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    50/push-eax
-    # eax = stream-data-equal?(f, s)
-    # . . push args
-    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  stream-data-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
-    68/push  1/imm32
-    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
-$check-stream-equal:end:
-    # . restore registers
-    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
-
-# scan the next line until newline starting from f->read and compare it with
-# 's' (ignoring the trailing newline)
-# on success, set f->read to after the next newline
-# on failure, leave f->read unmodified
-# this function is usually used only in tests, so we repeatedly write f->read
-next-stream-line-equal?:  # f: (addr stream byte), s: (addr array byte) -> result/eax: boolean
-    # pseudocode:
-    #   currf = f->read  # bound: f->write
-    #   currs = 0  # bound: s->size
-    #   while true
-    #     if currf >= f->write
-    #       return currs >= s->size
-    #     if f[currf] == '\n'
-    #       ++currf
-    #       return currs >= s->size
-    #     if (currs >= s->size) return false  # the current line of f still has data to match
-    #     if (f[currf] != s[currs]) return false
-    #     ++currf
-    #     ++currs
-    #
-    # collapsing the two branches that can return true:
-    #   currf = f->read  # bound: f->write
-    #   currs = 0  # bound: s->size
-    #   while true
-    #     if (currf >= f->write) break
-    #     if (f[currf] == '\n') break
-    #     if (currs >= s->size) return false  # the current line of f still has data to match
-    #     if (f[currf] != s[currs]) return false
-    #     ++currf
-    #     ++currs
-    #   ++currf  # skip '\n'
-    #   return currs >= s->size
-    # Here the final `++currf` is sometimes unnecessary (if we're already at the end of the stream)
-    #
-    # registers:
-    #   f: esi
-    #   s: edi
-    #   currf: ecx
-    #   currs: edx
-    #   f[currf]: eax
-    #   s[currs]: ebx
-    #
-    # . 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
-    56/push-esi
-    57/push-edi
-    # esi = f
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # var currf/ecx: int = f->read
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    # edi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-    # var currs/edx: int = 0
-    31/xor                          3/mod/direct    2/rm32/edx    .           .             .           2/r32/edx   .               .                 # clear edx
-    # var c1/eax: byte = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # var c2/ebx: byte = 0
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-$next-stream-line-equal?:loop:
-    # if (currf >= f->write) break
-    3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare ecx with *esi
-    7d/jump-if->=  $next-stream-line-equal?:break/disp8
-    # c1 = f->data[f->read]
-    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
-    # if (c1 == '\n') break
-    3d/compare-eax-and  0xa/imm32/newline
-    74/jump-if-=  $next-stream-line-equal?:break/disp8
-    # if (currs >= s->size) return false
-    3b/compare                      0/mod/indirect  7/rm32/edi    .           .             .           2/r32/edx   .               .                 # compare edx with *edi
-    7d/jump-if->=  $next-stream-line-equal?:false/disp8
-    # c2 = s->data[currs]
-    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/edi  2/index/edx   .           3/r32/BL    4/disp8         .                 # copy byte at *(edi+edx+4) to BL
-    # if (c1 != c2) return false
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # compare eax and ebx
-    75/jump-if-!=  $next-stream-line-equal?:false/disp8
-    # ++currf
-    41/increment-ecx
-    # ++currs
-    42/increment-edx
-    eb/jump $next-stream-line-equal?:loop/disp8
-$next-stream-line-equal?:break:
-    # ++currf
-    41/increment-ecx
-    # if (currs >= s->size) return true
-    3b/compare                      0/mod/indirect  7/rm32/edi    .           .             .           2/r32/edx   .               .                 # compare edx with *edi
-    7c/jump-if-<  $next-stream-line-equal?:false/disp8
-$next-stream-line-equal?:true:
-    b8/copy-to-eax  1/imm32
-    # persist f->read on success
-    89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(esi+4)
-    eb/jump  $next-stream-line-equal?:end/disp8
-$next-stream-line-equal?:false:
-    b8/copy-to-eax  0/imm32
-$next-stream-line-equal?:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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-next-stream-line-equal-stops-at-newline:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "Abc\ndef")
-    # . . push args
-    68/push  "Abc\ndef"/imm32
-    68/push  _test-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 = next-stream-line-equal?(_test-stream, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-stream-line-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-next-stream-line-equal-stops-at-newline"/imm32
-    68/push  1/imm32
-    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-next-stream-line-equal-stops-at-newline-2:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "Abc\ndef")
-    # . . push args
-    68/push  "Abc\ndef"/imm32
-    68/push  _test-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 = next-stream-line-equal?(_test-stream, "def")
-    # . . push args
-    68/push  "def"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-stream-line-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-next-stream-line-equal-stops-at-newline-2"/imm32
-    68/push  0/imm32
-    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-next-stream-line-equal-skips-newline:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "Abc\ndef\n")
-    # . . push args
-    68/push  "Abc\ndef\n"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # next-stream-line-equal?(_test-stream, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-stream-line-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # eax = next-stream-line-equal?(_test-stream, "def")
-    # . . push args
-    68/push  "def"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-stream-line-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-next-stream-line-equal-skips-newline"/imm32
-    68/push  1/imm32
-    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-next-stream-line-equal-handles-final-line:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "Abc\ndef")
-    # . . push args
-    68/push  "Abc\ndef"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # next-stream-line-equal?(_test-stream, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-stream-line-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # eax = next-stream-line-equal?(_test-stream, "def")
-    # . . push args
-    68/push  "def"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-stream-line-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-next-stream-line-equal-skips-newline"/imm32
-    68/push  1/imm32
-    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-next-stream-line-equal-always-fails-after-Eof:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write nothing
-    # eax = next-stream-line-equal?(_test-stream, "")
-    # . . push args
-    68/push  ""/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-stream-line-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-next-stream-line-equal-always-fails-after-Eof"/imm32
-    68/push  1/imm32
-    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
-    # eax = next-stream-line-equal?(_test-stream, "")
-    # . . push args
-    68/push  ""/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-stream-line-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-next-stream-line-equal-always-fails-after-Eof/2"/imm32
-    68/push  1/imm32
-    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
-
-# helper for later tests
-check-next-stream-line-equal:  # f: (addr stream byte), s: (addr array byte), msg: (addr array byte)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    50/push-eax
-    # eax = next-stream-line-equal?(f, s)
-    # . . push args
-    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  next-stream-line-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
-    68/push  1/imm32
-    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
-    # . restore registers
-    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
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/112read-byte.subx b/baremetal/112read-byte.subx
deleted file mode 100644
index 0c9af75b..00000000
--- a/baremetal/112read-byte.subx
+++ /dev/null
@@ -1,123 +0,0 @@
-# Read a single byte from a stream.
-#
-# We need to do this in machine code because streams need to be opaque types,
-# and we don't yet support opaque types in Mu.
-
-== 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
-
-# Return next byte value in eax, with top 3 bytes cleared.
-# Abort on reaching end of stream.
-read-byte:  # s: (addr stream byte) -> result/eax: byte
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    56/push-esi
-    # esi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # ecx = s->read
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    # if (f->read >= f->write) abort
-    3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare ecx with *esi
-    0f 8d/jump-if->=  $read-byte:abort/disp32
-    # result = f->data[f->read]
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
-    # ++f->read
-    ff          0/subop/increment   1/mod/*+disp8   6/rm32/esi    .           .             .           .           4/disp8         .                 # increment *(esi+4)
-$read-byte:end:
-    # . restore registers
-    5e/pop-to-esi
-    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
-
-$read-byte:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "read-byte: empty stream" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-== data
-
-_test-input-stream:  # (stream byte)
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # size
-    0x400/imm32  # 1024 bytes
-    # data (64 lines x 16 bytes/line)
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/113write-stream.subx b/baremetal/113write-stream.subx
deleted file mode 100644
index 7dd93fba..00000000
--- a/baremetal/113write-stream.subx
+++ /dev/null
@@ -1,173 +0,0 @@
-# write-stream: like write, but write streams rather than strings
-
-== 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
-
-write-stream:  # f: (addr stream byte), s: (addr stream byte)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    50/push-eax
-    56/push-esi
-    57/push-edi
-    # edi = f
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
-    # esi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
-    # eax = _append-4(&f->data[f->write], &f->data[f->size], &s->data[s->read], &s->data[s->write])
-    # . . push &s->data[s->write]
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    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
-    50/push-eax
-    # . . push &s->data[s->read]
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy *(esi+4) to eax
-    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
-    50/push-eax
-    # . . push &f->data[f->size]
-    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   8/disp8         .                 # copy *(edi+8) to eax
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  0/index/eax   .           0/r32/eax   0xc/disp8       .                 # copy edi+eax+12 to eax
-    50/push-eax
-    # . . push &f->data[f->write]
-    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy *edi to eax
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  0/index/eax   .           0/r32/eax   0xc/disp8       .                 # copy edi+eax+12 to eax
-    50/push-eax
-    # . . call
-    e8/call  _append-4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-    # f->write += eax
-    01/add                          0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # add eax to *edi
-    # s->read += eax
-    01/add                          1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # add eax to *(esi+4)
-$write-stream:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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
-
-test-write-stream-single:
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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-stream2)
-    # . . push args
-    68/push  _test-stream2/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(_test-stream2, "Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    68/push  _test-stream2/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # write-stream(_test-stream, _test-stream2)
-    # . . push args
-    68/push  _test-stream2/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "Ab", msg)
-    # . . push args
-    68/push  "F - test-write-stream-single"/imm32
-    68/push  "Ab"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-test-write-stream-appends:
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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-stream2)
-    # . . push args
-    68/push  _test-stream2/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(_test-stream2, "C")
-    # . . push args
-    68/push  "C"/imm32
-    68/push  _test-stream2/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # first write
-    # . write-stream(_test-stream, _test-stream2)
-    # . . push args
-    68/push  _test-stream2/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # second write
-    # . write(_test-stream2, "D")
-    # . . push args
-    68/push  "D"/imm32
-    68/push  _test-stream2/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write-stream(_test-stream, _test-stream2)
-    # . . push args
-    68/push  _test-stream2/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "CD", msg)
-    # . . push args
-    68/push  "F - test-write-stream-appends"/imm32
-    68/push  "CD"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-== data
-
-_test-stream2:  # (stream byte)
-    # current write index
-    4/imm32
-    # current read index
-    1/imm32
-    # size
-    8/imm32
-    # data
-    41/A 42/B 43/C 44/D 00 00 00 00  # 8 bytes
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/115write-byte.subx b/baremetal/115write-byte.subx
deleted file mode 100644
index 6935d65b..00000000
--- a/baremetal/115write-byte.subx
+++ /dev/null
@@ -1,82 +0,0 @@
-# Write a single byte to a stream.
-#
-# We need to do this in machine code because streams need to be opaque types,
-# and we don't yet support opaque types in Mu.
-
-== 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
-
-# Write lower byte of 'n' to 'f'.
-append-byte:  # f: (addr stream byte), n: 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
-    57/push-edi
-    # edi = f
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
-    # ecx = f->write
-    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
-    # if (f->write >= f->size) abort
-    3b/compare                      1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(edi+8)
-    7d/jump-if->=  $append-byte:abort/disp8
-$append-byte:to-stream:
-    # write to stream
-    # f->data[f->write] = LSB(n)
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ebp+12) to AL
-    88/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/edi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy AL to *(edi+ecx+12)
-    # ++f->write
-    ff          0/subop/increment   0/mod/indirect  7/rm32/edi    .           .             .           .           .               .                 # increment *edi
-$append-byte:end:
-    # . restore registers
-    5f/pop-to-edi
-    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
-
-$append-byte:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "append-byte: out of space\n" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-test-append-byte-single:
-    # - check that append-byte writes to first byte of 'file'
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # append-byte(_test-stream, 'A')
-    # . . push args
-    68/push  0x41/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  append-byte/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "A", msg)
-    # . . push args
-    68/push  "F - test-append-byte-single"/imm32
-    68/push  "A"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/117write-int-hex.subx b/baremetal/117write-int-hex.subx
deleted file mode 100644
index 091bd874..00000000
--- a/baremetal/117write-int-hex.subx
+++ /dev/null
@@ -1,202 +0,0 @@
-# Write out the (hex) textual representation of numbers.
-
-== 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
-
-# convert the lowest nibble of eax to ascii and return it in the lowest byte of eax
-to-hex-char:  # in/eax: int -> out/eax: int
-    # no error checking; accepts argument in eax
-    # if (eax <= 9) return eax + '0'
-    3d/compare-eax-with  0x9/imm32/9
-    7f/jump-if->  $to-hex-char:else/disp8
-    05/add-to-eax  0x30/imm32/0
-    c3/return
-$to-hex-char:else:
-    # otherwise return eax + 'a' - 10
-    05/add-to-eax  0x57/imm32/a-10
-    c3/return
-
-append-byte-hex:  # f: (addr stream byte), n: 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
-    # AL = convert upper nibble to hex
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
-    c1/shift    5/subop/logic-right 3/mod/direct    0/rm32/eax    .           .             .           .           .               4/imm8            # shift eax right by 4 bits, while padding zeroes
-    25/and-eax  0xf/imm32
-    # . AL = to-hex-char(AL)
-    e8/call  to-hex-char/disp32
-    # append-byte(f, AL)
-    # . . push args
-    50/push-eax
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  append-byte/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # AL = convert lower nibble to hex
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
-    25/and-eax  0xf/imm32
-    # . AL = to-hex-char(AL)
-    e8/call  to-hex-char/disp32
-    # append-byte(f, AL)
-    # . . push args
-    50/push-eax
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  append-byte/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$append-byte-hex:end:
-    # . restore registers
-    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
-
-test-append-byte-hex:
-    # - check that append-byte-hex adds the hex textual representation
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # append-byte-hex(_test-stream, 0xa)  # exercises digit, non-digit as well as leading zero
-    # . . push args
-    68/push  0xa/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  append-byte-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "0a", msg)
-    # . . push args
-    68/push  "F - test-append-byte-hex"/imm32
-    68/push  "0a"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-write-int32-hex:  # f: (addr stream byte), n: int
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-$write-int32-hex:hex-prefix:
-    # write(f, "0x")
-    # . . push args
-    68/push  "0x"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . 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:rest:
-    # write-int32-hex-bits(f, n, 32)
-    # . . push args
-    68/push  0x20/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-int32-hex-bits/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-$write-int32-hex:end:
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-# print rightmost 'bits' of 'n'
-# bits must be multiple of 4
-write-int32-hex-bits:  # f: (addr stream byte), n: int, bits: int
-    # pseudocode:
-    #  bits -= 4
-    #  while true
-    #    if (bits < 0) break
-    #    eax = n >> bits
-    #    eax = eax & 0xf
-    #    append-byte(f, AL)
-    #    bits -= 4
-    #
-    # . 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
-    # ecx = bits-4
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # copy *(ebp+16) to ecx
-    81          5/subop/subtract    3/mod/direct    1/rm32/ecx    .           .             .           .           .               4/imm32           # subtract from ecx
-$write-int32-hex-bits:loop:
-    # if (bits < 0) break
-    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0/imm32           # compare ecx
-    7c/jump-if-<  $write-int32-hex-bits:end/disp8
-    # eax = n >> bits
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
-    d3/>>ecx    5/subop/pad-zeroes  3/mod/direct    0/rm32/eax    .           .             .           .           .               .                 # shift eax right by ecx bits, padding zeroes
-    # eax = to-hex-char(AL)
-    25/and-eax  0xf/imm32
-    e8/call  to-hex-char/disp32
-    # append-byte(f, AL)
-    # . . push args
-    50/push-eax
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  append-byte/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # bits -= 4
-    81          5/subop/subtract    3/mod/direct    1/rm32/ecx    .           .             .           .           .               4/imm32           # subtract from ecx
-    eb/jump  $write-int32-hex-bits:loop/disp8
-$write-int32-hex-bits:end:
-    # . restore registers
-    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
-
-test-write-int32-hex:
-    # - check that write-int32-hex prints the hex textual representation
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write-int32-hex(_test-stream, 0x8899aa)
-    # . . push args
-    68/push  0x8899aa/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-int32-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "0x008899aa", msg)
-    # . . push args
-    68/push  "F - test-write-int32-hex"/imm32
-    68/push  "0x008899aa"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/118parse-hex-int.subx b/baremetal/118parse-hex-int.subx
deleted file mode 100644
index 153def3c..00000000
--- a/baremetal/118parse-hex-int.subx
+++ /dev/null
@@ -1,897 +0,0 @@
-# some utilities for converting numbers from hex
-# lowercase letters only for now
-
-== 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
-
-is-hex-int?:  # in: (addr slice) -> result/eax: boolean
-    # . 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
-    # ecx = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # edx = s->end
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
-    # var curr/ecx: (addr byte) = s->start
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # copy *ecx to ecx
-    # if s is empty return false
-    b8/copy-to-eax  0/imm32/false
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    73/jump-if-addr>=  $is-hex-int?:end/disp8
-    # skip past leading '-'
-    # . if (*curr == '-') ++curr
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/BL    .               .                 # copy byte at *ecx to BL
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x2d/imm32/-      # compare ebx
-    75/jump-if-!=  $is-hex-int?:initial-0/disp8
-    # . ++curr
-    41/increment-ecx
-    # skip past leading '0x'
-$is-hex-int?:initial-0:
-    # . if (*curr != '0') jump to loop
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/BL    .               .                 # copy byte at *ecx to BL
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x30/imm32/0      # compare ebx
-    75/jump-if-!=  $is-hex-int?:loop/disp8
-    # . ++curr
-    41/increment-ecx
-$is-hex-int?:initial-0x:
-    # . if (curr >= in->end) return true
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    73/jump-if-addr>=  $is-hex-int?:true/disp8
-    # . if (*curr != 'x') jump to loop  # the previous '0' is still valid so doesn't need to be checked again
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/BL    .               .                 # copy byte at *ecx to BL
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x78/imm32/x      # compare ebx
-    75/jump-if-!=  $is-hex-int?:loop/disp8
-    # . ++curr
-    41/increment-ecx
-$is-hex-int?:loop:
-    # if (curr >= in->end) return true
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    73/jump-if-addr>=  $is-hex-int?:true/disp8
-    # var eax: boolean = is-hex-digit?(*curr)
-    # . . push args
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-    50/push-eax
-    # . . call
-    e8/call  is-hex-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # if (eax == false) return false
-    3d/compare-eax-and  0/imm32/false
-    74/jump-if-=  $is-hex-int?:end/disp8
-    # ++curr
-    41/increment-ecx
-    # loop
-    eb/jump  $is-hex-int?:loop/disp8
-$is-hex-int?:true:
-    # return true
-    b8/copy-to-eax  1/imm32/true
-$is-hex-int?:end:
-    # . 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-is-hex-int:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "34"
-    b8/copy-to-eax  "34"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-hex-int?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-hex-int?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-hex-int"/imm32
-    68/push  1/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-is-hex-int-handles-letters:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "34a"
-    b8/copy-to-eax  "34a"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-hex-int?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-hex-int?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-hex-int-handles-letters"/imm32
-    68/push  1/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-is-hex-int-with-trailing-char:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "34q"
-    b8/copy-to-eax  "34q"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-hex-int?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-hex-int?/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-is-hex-int-with-trailing-char"/imm32
-    68/push  0/imm32/false
-    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-is-hex-int-with-leading-char:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "q34"
-    b8/copy-to-eax  "q34"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-hex-int?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-hex-int?/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-is-hex-int-with-leading-char"/imm32
-    68/push  0/imm32/false
-    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-is-hex-int-empty:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var slice/ecx: slice = ""
-    68/push  0/imm32
-    68/push  0/imm32
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-hex-int?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-hex-int?/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-is-hex-int-empty"/imm32
-    68/push  0/imm32/false
-    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-is-hex-int-handles-0x-prefix:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "0x3a"
-    b8/copy-to-eax  "0x3a"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-hex-int?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-hex-int?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-hex-int-handles-0x-prefix"/imm32
-    68/push  1/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-is-hex-int-handles-negative:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "-34a"
-    b8/copy-to-eax  "-34a"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-hex-int?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-hex-int?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-hex-int-handles-negative"/imm32
-    68/push  1/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-is-hex-int-handles-negative-0x-prefix:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "-0x3a"
-    b8/copy-to-eax  "-0x3a"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-hex-int?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-hex-int?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-hex-int-handles-negative-0x-prefix"/imm32
-    68/push  1/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
-
-parse-hex-int:  # in: (addr array byte) -> result/eax: int
-    # . 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
-    # eax = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
-    # var curr/ecx: (addr byte) = &in->data
-    8d/copy-address                 1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   4/disp8         .                 # copy eax+4 to ecx
-    # var max/edx: (addr byte) = &in->data[in->size]
-    # . edx = in->size
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
-    # . edx = in->data + in->size
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  2/index/edx   .           2/r32/edx   4/disp8         .                 # copy eax+edx+4 to edx
-    # return parse-hex-int-helper(curr, max)
-    # . . push args
-    52/push-edx
-    51/push-ecx
-    # . . call
-    e8/call  parse-hex-int-helper/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$parse-hex-int:end:
-    # . restore registers
-    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
-
-parse-hex-int-from-slice:  # in: (addr slice) -> result/eax: int
-    # . 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
-    # ecx = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # var max/edx: (addr byte) = in->end
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
-    # var curr/ecx: (addr byte) = in->start
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # copy *ecx to ecx
-    # return parse-hex-int-helper(curr, max)
-    # . . push args
-    52/push-edx
-    51/push-ecx
-    # . . call
-    e8/call  parse-hex-int-helper/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$parse-hex-int-from-slice:end:
-    # . restore registers
-    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
-
-parse-hex-int-helper:  # start: (addr byte), end: (addr byte) -> result/eax: int
-    # . 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
-    56/push-esi
-    # var curr/ecx: (addr byte) = start
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # edx = max
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
-    # var result/ebx: int = 0
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-    # var negate?/esi: boolean = false
-    31/xor                          3/mod/direct    6/rm32/esi    .           .             .           6/r32/esi   .               .                 # clear esi
-$parse-hex-int-helper:negative:
-    # if (*curr == '-') ++curr, negate = true
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-    3d/compare-eax-and  0x2d/imm32/-
-    75/jump-if-!=  $parse-hex-int-helper:initial-0/disp8
-    # . ++curr
-    41/increment-ecx
-    # . negate = true
-    be/copy-to-esi  1/imm32/true
-$parse-hex-int-helper:initial-0:
-    # skip past leading '0x'
-    # . if (*curr != '0') jump to loop
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-    3d/compare-eax-and  0x30/imm32/0
-    75/jump-if-!=  $parse-hex-int-helper:loop/disp8
-    # . ++curr
-    41/increment-ecx
-$parse-hex-int-helper:initial-0x:
-    # . if (curr >= in->end) return result
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    73/jump-if-addr>=  $parse-hex-int-helper:end/disp8
-    # . if (*curr != 'x') jump to loop  # the previous '0' is still valid so doesn't need to be checked again
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-    3d/compare-eax-and  0x78/imm32/x
-    75/jump-if-!=  $parse-hex-int-helper:loop/disp8
-    # . ++curr
-    41/increment-ecx
-$parse-hex-int-helper:loop:
-    # if (curr >= in->end) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    73/jump-if-addr>=  $parse-hex-int-helper:negate/disp8
-    # var eax: int = from-hex-char(*curr)
-    # . . copy arg to eax
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-    # . . call
-    e8/call  from-hex-char/disp32
-    # result = result * 16 + eax
-    c1/shift    4/subop/left        3/mod/direct    3/rm32/ebx    .           .             .           .           .               4/imm8            # shift ebx left by 4 bits
-    01/add                          3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # add eax to ebx
-    # ++curr
-    41/increment-ecx
-    # loop
-    eb/jump  $parse-hex-int-helper:loop/disp8
-$parse-hex-int-helper:negate:
-    # if (negate?) result = -result
-    81          7/subop/compare     3/mod/direct    6/rm32/esi    .           .             .           .           .               0/imm32/false     # compare esi
-    74/jump-if-=  $parse-hex-int-helper:end/disp8
-    f7          3/subop/negate      3/mod/direct    3/rm32/ebx    .           .             .           .           .               .                 # negate ebx
-$parse-hex-int-helper:end:
-    # return result
-    89/copy                         3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # copy ebx to eax
-    # . restore registers
-    5e/pop-to-esi
-    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-parse-hex-int-from-slice-single-digit:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "a"
-    b8/copy-to-eax  "a"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = parse-hex-int-from-slice(slice)
-    # . . push args
-    51/push-ecx
-    # . . 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
-    # check-ints-equal(eax, 0xa, msg)
-    # . . push args
-    68/push  "F - test-parse-hex-int-from-slice-single-digit"/imm32
-    68/push  0xa/imm32
-    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-parse-hex-int-from-slice-multi-digit:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "34a"
-    b8/copy-to-eax  "34a"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = parse-hex-int-from-slice(slice)
-    # . . push args
-    51/push-ecx
-    # . . 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
-    # check-ints-equal(eax, 0x34a, msg)
-    # . . push args
-    68/push  "F - test-parse-hex-int-from-slice-multi-digit"/imm32
-    68/push  0x34a/imm32
-    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-parse-hex-int-from-slice-0x-prefix:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "0x34"
-    b8/copy-to-eax  "0x34"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = parse-hex-int-from-slice(slice)
-    # . . push args
-    51/push-ecx
-    # . . 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
-    # check-ints-equal(eax, 0x34, msg)
-    # . . push args
-    68/push  "F - test-parse-hex-int-from-slice-0x-prefix"/imm32
-    68/push  0x34/imm32
-    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-parse-hex-int-from-slice-zero:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "0"
-    b8/copy-to-eax  "0"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = parse-hex-int-from-slice(slice)
-    # . . push args
-    51/push-ecx
-    # . . 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
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-parse-hex-int-from-slice-zero"/imm32
-    68/push  0/imm32
-    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-parse-hex-int-from-slice-0-prefix:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "03"
-    b8/copy-to-eax  "03"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = parse-hex-int-from-slice(slice)
-    # . . push args
-    51/push-ecx
-    # . . 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
-    # check-ints-equal(eax, 0x3, msg)
-    # . . push args
-    68/push  "F - test-parse-hex-int-from-slice-0-prefix"/imm32
-    68/push  0x3/imm32
-    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-parse-hex-int-from-slice-negative:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "-03"
-    b8/copy-to-eax  "-03"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = parse-hex-int-from-slice(slice)
-    # . . push args
-    51/push-ecx
-    # . . 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
-    # check-ints-equal(eax, -3, msg)
-    # . . push args
-    68/push  "F - test-parse-hex-int-from-slice-negative"/imm32
-    68/push  -3/imm32
-    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
-
-is-hex-digit?:  # c: byte -> result/eax: boolean
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    # ecx = c
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # return false if c < '0'
-    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x30/imm32        # compare ecx
-    7c/jump-if-<  $is-hex-digit?:false/disp8
-    # return true if c <= '9'
-    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x39/imm32        # compare ecx
-    7e/jump-if-<=  $is-hex-digit?:true/disp8
-    # drop case
-    25/and-eax-with 0x5f/imm32
-    # return false if c > 'f'
-    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x66/imm32        # compare ecx
-    7f/jump-if->  $is-hex-digit?:false/disp8
-    # return true if c >= 'a'
-    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x61/imm32        # compare ecx
-    7d/jump-if->=  $is-hex-digit?:true/disp8
-    # otherwise return false
-$is-hex-digit?:false:
-    b8/copy-to-eax  0/imm32/false
-    eb/jump $is-hex-digit?:end/disp8
-$is-hex-digit?:true:
-    b8/copy-to-eax  1/imm32/true
-$is-hex-digit?:end:
-    # . restore registers
-    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-hex-below-0:
-    # eax = is-hex-digit?(0x2f)
-    # . . push args
-    68/push  0x2f/imm32
-    # . . call
-    e8/call  is-hex-digit?/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-hex-below-0"/imm32
-    68/push  0/imm32/false
-    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
-    c3/return
-
-test-hex-0-to-9:
-    # eax = is-hex-digit?(0x30)
-    # . . push args
-    68/push  0x30/imm32
-    # . . call
-    e8/call  is-hex-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-hex-at-0"/imm32
-    68/push  1/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
-    # eax = is-hex-digit?(0x39)
-    # . . push args
-    68/push  0x39/imm32
-    # . . call
-    e8/call  is-hex-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-hex-at-9"/imm32
-    68/push  1/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
-    c3/return
-
-test-hex-above-9-to-a:
-    # eax = is-hex-digit?(0x3a)
-    # . . push args
-    68/push  0x3a/imm32
-    # . . call
-    e8/call  is-hex-digit?/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-hex-above-9-to-a"/imm32
-    68/push  0/imm32/false
-    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
-    c3/return
-
-test-hex-a-to-f:
-    # eax = is-hex-digit?(0x61)
-    # . . push args
-    68/push  0x61/imm32
-    # . . call
-    e8/call  is-hex-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-hex-at-a"/imm32
-    68/push  1/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
-    # eax = is-hex-digit?(0x66)
-    # . . push args
-    68/push  0x66/imm32
-    # . . call
-    e8/call  is-hex-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-hex-at-f"/imm32
-    68/push  1/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
-    c3/return
-
-test-hex-above-f:
-    # eax = is-hex-digit?(0x67)
-    # . . push args
-    68/push  0x67/imm32
-    # . . call
-    e8/call  is-hex-digit?/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-hex-above-f"/imm32
-    68/push  0/imm32/false
-    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
-    c3/return
-
-from-hex-char:  # in/eax: byte -> out/eax: nibble
-$from-hex-char:check0:
-    # if (eax < '0') goto abort
-    3d/compare-eax-with  0x30/imm32/0
-    7c/jump-if-<  $from-hex-char:abort/disp8
-$from-hex-char:check1:
-    # if (eax > 'f') goto abort
-    3d/compare-eax-with  0x66/imm32/f
-    7f/jump-if->  $from-hex-char:abort/disp8
-$from-hex-char:check2:
-    # if (eax > '9') goto next check
-    3d/compare-eax-with  0x39/imm32/9
-    7f/jump-if->  $from-hex-char:check3/disp8
-$from-hex-char:digit:
-    # return eax - '0'
-    2d/subtract-from-eax  0x30/imm32/0
-    c3/return
-$from-hex-char:check3:
-    # if (eax < 'a') goto abort
-    3d/compare-eax-with  0x61/imm32/a
-    7c/jump-if-<  $from-hex-char:abort/disp8
-$from-hex-char:letter:
-    # return eax - ('a'-10)
-    2d/subtract-from-eax  0x57/imm32/a-10
-    c3/return
-
-$from-hex-char:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "invalid hex char" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/120allocate.subx b/baremetal/120allocate.subx
deleted file mode 100644
index 38c4110b..00000000
--- a/baremetal/120allocate.subx
+++ /dev/null
@@ -1,914 +0,0 @@
-# Helper to dynamically allocate memory on the heap.
-#
-# We'd like to be able to write tests for functions that allocate memory,
-# making assertions on the precise addresses used. To achieve this we'll pass
-# in an *allocation descriptor* to allocate from.
-#
-# Allocation descriptors are also useful outside of tests. Assembly and machine
-# code are of necessity unsafe languages, and one of the most insidious kinds
-# of bugs unsafe languages expose us to are dangling pointers to memory that
-# has been freed and potentially even reused for something totally different.
-# To reduce the odds of such "use after free" errors, SubX programs tend to not
-# reclaim and reuse dynamically allocated memory. (Running out of memory is far
-# easier to debug.) Long-running programs that want to reuse memory are mostly
-# on their own to be careful. However, they do get one bit of help: they can
-# carve out chunks of memory and then allocate from them manually using this
-# very same 'allocate' helper. They just need a new allocation descriptor for
-# their book-keeping.
-
-== data
-
-# Allocations are returned in a handle, which consists of an alloc-id and a payload.
-# The alloc-id helps detect use-after-free errors.
-Handle-size:  # (addr int)
-  8/imm32
-
-# A default allocation descriptor for programs to use.
-Heap:  # allocation-descriptor
-  # curr
-  0x01000000/imm32  # 16 MB
-  # limit
-  0x02000000/imm32  # 32 MB
-
-Next-alloc-id:  # int
-  0x100/imm32  # save a few alloc ids for fake handles
-
-== 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
-
-# Allocate and clear 'n' bytes of memory from an allocation-descriptor 'ad'.
-# Abort if there isn't enough memory in 'ad'.
-allocate:  # ad: (addr allocation-descriptor), n: int, out: (addr handle _)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    50/push-eax
-    # allocate-raw(ad, n, out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
-    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  allocate-raw/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # eax = out->payload + 4
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
-    8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
-    05/add-to-eax  4/imm32
-    # zero-out(eax, n)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    50/push-eax
-    # . . call
-    e8/call  zero-out/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$allocate:end:
-    # . restore registers
-    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
-
-# Claim the next 'n' bytes of memory starting at ad->curr and update ad->curr.
-# Abort if there isn't enough memory in 'ad'.
-allocate-raw:  # ad: (addr allocation-descriptor), n: int, out: (addr handle _)
-    # . 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
-    # ecx = ad
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # edx = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8      .                 # copy *(ebp+16) to edx
-    # ebx = n
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           3/r32/ebx   0xc/disp8       .                 # copy *(ebp+12) to ebx
-    # out->alloc-id = Next-alloc-id
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Next-alloc-id/disp32              # copy *Next-alloc-id to eax
-    89/copy                         0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to *edx
-    # out->payload = ad->curr
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
-$allocate-raw:save-payload-in-eax:
-    89/copy                         1/mod/*+disp8   2/rm32/edx    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edx+4)
-    # *out->payload = Next-alloc-id
-    8b/copy                         1/mod/*+disp8   2/rm32/edx    .           .             .           7/r32/edi   4/disp8         .                 # copy *(edx+4) to edi
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           6/r32/esi   Next-alloc-id/disp32              # copy *Next-alloc-id to esi
-    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           6/r32/esi   .               .                 # copy esi to *edi
-$allocate-raw:increment-next-alloc-id:
-    # increment *Next-alloc-id
-    ff          0/subop/increment   0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # increment *Next-alloc-id
-    # check if there's enough space
-    # TODO: move this check up before any state updates when we support error recovery
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  3/index/ebx   .           0/r32/eax   4/disp8         .                 # copy eax+ebx+4 to eax
-    3b/compare                      1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # compare eax with *(ecx+4)
-    73/jump-if->=-signed  $allocate-raw:abort/disp8
-$allocate-raw:commit:
-    # ad->curr += n+4
-    89/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to *ecx
-$allocate-raw:end:
-    # . 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
-
-$allocate-raw:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "allocate: failed" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-test-allocate-raw-success:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var ad/ecx: allocation-descriptor containing 16 bytes
-    # . var end/ecx: (addr byte)
-    89/<- %ecx 4/r32/esp
-    # . var start/edx: (addr byte) = end - 16
-    81 5/subop/subtract %esp 0x10/imm32
-    89/<- %edx 4/r32/esp
-    # . ad = {start, end}
-    51/push-ecx
-    52/push-edx
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # var expected-payload/ebx = ad->curr
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # copy *ecx to ebx
-    # var h/edx: handle = {0, 0}
-    68/push  0/imm32
-    68/push  0/imm32
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # *Next-alloc-id = 0x34
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x34/imm32  # copy to *Next-alloc-id
-    # allocate-raw(ad, 3, h)
-    # . . push args
-    52/push-edx
-    68/push  3/imm32
-    51/push-ecx
-    # . . call
-    e8/call  allocate-raw/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->alloc-id, 0x34, msg)
-    # . . push args
-    68/push  "F - test-allocate-raw-success: sets alloc-id in handle"/imm32
-    68/push  0x34/imm32
-    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->payload, expected-payload, msg)
-    # . . push args
-    68/push  "F - test-allocate-raw-success: sets payload in handle"/imm32
-    53/push-ebx
-    ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->payload->alloc-id, 0x34, msg)
-    # . . push args
-    68/push  "F - test-allocate-raw-success: sets alloc-id in payload"/imm32
-    68/push  0x34/imm32
-    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(*Next-alloc-id, 0x35, msg)
-    # . . push args
-    68/push  "F - test-allocate-raw-success: increments Next-alloc-id"/imm32
-    68/push  0x35/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # push *Next-alloc-id
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(ad->curr - expected-payload, 3 + 4 for alloc-id, msg)
-    # . . push args
-    68/push  "F - test-allocate-raw-success: updates allocation descriptor"/imm32
-    68/push  7/imm32
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
-    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
-    # clean up
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x100/imm32 # copy to *Next-alloc-id
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x20/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
-
-lookup:  # h: (handle _T) -> result/eax: (addr _T)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    # eax = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # ecx = handle->alloc_id
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # if (ecx == 0) return 0
-    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0/imm32           # compare ecx
-    74/jump-if-=  $lookup:end/disp8
-    # eax = handle->address (payload)
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
-    # if (ecx != *eax) abort
-    39/compare                      0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare *eax and ecx
-    75/jump-if-!=  $lookup:abort/disp8
-    # add 4 to eax
-    05/add-to-eax  4/imm32
-$lookup:end:
-    # . restore registers
-    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
-
-$lookup:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "lookup: failed" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-test-lookup-success:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var ad/ebx: allocation-descriptor containing 16 bytes
-    # . var end/ecx: (addr byte)
-    89/<- %ecx 4/r32/esp
-    # . var start/edx: (addr byte) = end - 16
-    81 5/subop/subtract %esp 0x10/imm32
-    89/<- %edx 4/r32/esp
-    # . ad = {start, end}
-    51/push-ecx
-    52/push-edx
-    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
-    # var handle/ecx: handle
-    68/push  0/imm32/address
-    68/push  0/imm32/alloc-id
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # var old-top/edx = ad->curr
-    8b/copy                         0/mod/indirect  3/rm32/ebx    .           .             .           2/r32/edx   .               .                 # copy *ebx to edx
-    # allocate-raw(ad, 2, handle)
-    # . . push args
-    51/push-ecx
-    68/push  2/imm32/size
-    53/push-ebx
-    # . . call
-    e8/call  allocate-raw/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # eax = lookup(handle)
-    # . . push args
-    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  lookup/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # eax contains old top of heap, except skipping the alloc-id in the payload
-    # . check-ints-equal(eax, old-top+4, msg)
-    # . . push args
-    68/push  "F - test-lookup-success"/imm32
-    81          0/subop/add         3/mod/direct    2/rm32/edx    .           .             .           .           .               4/imm32           # add to edx
-    52/push-edx
-    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
-    # clean up
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x100/imm32 # copy to *Next-alloc-id
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x20/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-lookup-null-returns-null:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var handle/ecx: handle
-    68/push  0/imm32/address
-    68/push  0/imm32/alloc-id
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = lookup(handle)
-    # . . push args
-    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  lookup/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-lookup-null-returns-null"/imm32
-    68/push  0/imm32
-    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
-    # . reclaim locals
-    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
-
-_pending-test-lookup-failure:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var ad/ecx: allocation-descriptor containing 16 bytes
-    # . var end/ecx: (addr byte)
-    89/<- %ecx 4/r32/esp
-    # . var start/edx: (addr byte) = end - 16
-    81 5/subop/subtract %esp 0x10/imm32
-    89/<- %edx 4/r32/esp
-    # . ad = {start, end}
-    51/push-ecx
-    52/push-edx
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # var h1/ecx: handle
-    68/push  0/imm32/address
-    68/push  0/imm32/alloc-id
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # var old_top/ebx = ad->curr
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
-    # first allocation, to h1
-    # . allocate(ad, 2, h1)
-    # . . push args
-    51/push-ecx
-    68/push  2/imm32/size
-    56/push-esi
-    # . . call
-    e8/call  allocate/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # reset ad->curr to mimic reclamation
-    89/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy ebx to *esi
-    # second allocation that returns the same address as the first
-    # var h2/edx: handle
-    68/push  0/imm32/address
-    68/push  0/imm32/alloc-id
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # . allocate(ad, 2, h2)
-    # . . push args
-    52/push-edx
-    68/push  2/imm32/size
-    56/push-esi
-    # . . call
-    e8/call  allocate/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h1->address, h2->address, msg)
-    # . . push args
-    68/push  "F - test-lookup-failure"/imm32
-    ff          6/subop/push        1/mod/*+disp8   2/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(edx+4)
-    ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # lookup(h1) should crash
-    # . . push args
-    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  lookup/disp32
-    # should never get past this point
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # clean up
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x100/imm32 # copy to *Next-alloc-id
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x20/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
-
-# when comparing handles, just treat them as pure values
-handle-equal?:  # a: (handle _T), b: (handle _T) -> result/eax: boolean
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    # eax = false
-    b8/copy-to-eax  0/imm32/false
-$handle-equal?:compare-alloc-id:
-    # ecx = a->alloc_id
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # if (ecx != b->alloc_id) return false
-    39/compare                      1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # compare ecx and *(ebp+16)
-    75/jump-if-!=  $handle-equal?:end/disp8
-$handle-equal?:compare-address:
-    # ecx = handle->address
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
-    # if (ecx != b->address) return false
-    39/compare                      1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x14/disp8      .                 # compare ecx and *(ebp+20)
-    75/jump-if-!=  $handle-equal?:end/disp8
-$handle-equal?:return-true:
-    # return true
-    b8/copy-to-eax  1/imm32/true
-$handle-equal?:end:
-    # . restore registers
-    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
-
-copy-handle:  # src: (handle _T), dest: (addr handle _T)
-    # . 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
-    # ecx = dest
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # copy *(ebp+16) to ecx
-    # *dest = src
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
-    89/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to *ecx
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
-    89/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(ecx+4)
-$copy-handle:end:
-    # . restore registers
-    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
-
-# helper: create a nested allocation descriptor (useful for tests)
-allocate-region:  # ad: (addr allocation-descriptor), n: int, out: (addr handle allocation-descriptor)
-    # . 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
-    # allocate(ad, n, out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
-    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  allocate/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # eax = out->payload
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
-    8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
-    # skip payload->allocid
-    05/add-to-eax  4/imm32
-    # if (eax == 0) abort
-    3d/compare-eax-and  0/imm32
-    74/jump-if-=  $allocate-region:abort/disp8
-    # earmark 8 bytes at the start for a new allocation descriptor
-    # . *eax = eax + 8
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to ecx
-    81          0/subop/add         3/mod/direct    1/rm32/ecx    .           .             .           .           .               8/imm32           # add to ecx
-    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
-    # . *(eax+4) = eax + n
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to ecx
-    03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # add *(ebp+12) to ecx
-    89/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(eax+4)
-    # . restore registers
-    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
-
-# We could create a more general '$abort' jump target, but then we'd need to do
-# a conditional jump followed by loading the error message and an unconditional
-# jump. Or we'd need to unconditionally load the error message before a
-# conditional jump, even if it's unused the vast majority of the time. This way
-# we bloat a potentially cold segment in RAM so we can abort with a single
-# instruction.
-$allocate-region:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "allocate-region: failed to allocate" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-# Claim the next 'n+4' bytes of memory and initialize the first 4 to n.
-# Abort if there isn't enough memory in 'ad'.
-allocate-array:  # ad: (addr allocation-descriptor), n: int, out: (addr handle _)
-    # . 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
-    # ecx = n
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
-    # var size/edx: int = n+4
-    8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy ecx+4 to edx
-    # allocate(ad, size, out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
-    52/push-edx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  allocate/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # *out->payload = n
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
-    8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
-    # . skip payload->allocid
-    05/add-to-eax  4/imm32
-    # .
-    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
-$allocate-array:end:
-    # . restore registers
-    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
-
-test-allocate-array:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var ad/ecx: allocation-descriptor containing 16 bytes
-    # . var end/ecx: (addr byte)
-    89/<- %ecx 4/r32/esp
-    # . var start/edx: (addr byte) = end - 16
-    81 5/subop/subtract %esp 0x10/imm32
-    89/<- %edx 4/r32/esp
-    # . ad = {start, end}
-    51/push-ecx
-    52/push-edx
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # var expected-payload/ebx = ad->curr
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # copy *ecx to ebx
-    # var h/edx: handle = {0, 0}
-    68/push  0/imm32
-    68/push  0/imm32
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # *Next-alloc-id = 0x34
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x34/imm32  # copy to *Next-alloc-id
-    # allocate-array(ad, 3, h)
-    # . . push args
-    52/push-edx
-    68/push  3/imm32
-    51/push-ecx
-    # . . call
-    e8/call  allocate-array/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->alloc-id, 0x34, msg)
-    # . . push args
-    68/push  "F - test-allocate-array: sets alloc-id in handle"/imm32
-    68/push  0x34/imm32
-    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->payload, expected-payload, msg)
-    # . . push args
-    68/push  "F - test-allocate-array: sets payload in handle"/imm32
-    53/push-ebx
-    ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->payload->alloc-id, 0x34, msg)
-    # . . push args
-    68/push  "F - test-allocate-array: sets alloc-id in payload"/imm32
-    68/push  0x34/imm32
-    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->payload->size, 3, msg)
-    # . . push args
-    68/push  "F - test-allocate-array: sets array size in payload"/imm32
-    68/push  3/imm32
-    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(*Next-alloc-id, 0x35, msg)
-    # . . push args
-    68/push  "F - test-allocate-array: increments Next-alloc-id"/imm32
-    68/push  0x35/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # push *Next-alloc-id
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(ad->curr - expected-payload, 3 + 4 for alloc-id + 4 for array size, msg)
-    # . . push args
-    68/push  "F - test-allocate-array: updates allocation descriptor"/imm32
-    68/push  0xb/imm32
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
-    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
-    # clean up
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  1/imm32     # copy to *Next-alloc-id
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x20/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
-
-copy-array:  # ad: (addr allocation-descriptor), src: (addr array _T), out: (addr handle array _T)
-    # . 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 = src
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
-    # var size/ecx: int = src->size+4
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
-    81          0/subop/add         3/mod/direct    1/rm32/ecx    .           .             .           .           .               4/imm32           # add to ecx
-    # allocate(ad, size, out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
-    51/push-ecx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  allocate/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # var payload/eax: (addr byte) = out->payload
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
-    8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
-    # . skip payload->allocid
-    05/add-to-eax  4/imm32
-    # var max/ecx: (addr byte) = payload + size
-    01/add                          3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # add eax to ecx
-    # _append-4(payload, max, src, &src->data[src->size])
-    # . . push &src->data[src->size]
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  2/index/edx   .           2/r32/edx   4/disp8         .                 # copy esi+edx+4 to edx
-    52/push-edx
-    # . . push src
-    56/push-esi
-    # . . push max
-    51/push-ecx
-    # . . push payload
-    50/push-eax
-    # . . call
-    e8/call  _append-4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-$copy-array: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
-
-test-copy-array:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var src/esi: (addr array int) = [3, 4, 5]
-    68/push  5/imm32
-    68/push  4/imm32
-    68/push  3/imm32
-    68/push  0xc/imm32/size
-    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
-    # var ad/ecx: allocation-descriptor containing 32 bytes
-    # . var end/ecx: (addr byte)
-    89/<- %ecx 4/r32/esp
-    # . var start/edx: (addr byte) = end - 32
-    81 5/subop/subtract %esp 0x20/imm32
-    89/<- %edx 4/r32/esp
-    # . ad = {start, end}
-    51/push-ecx
-    52/push-edx
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # var expected-payload/ebx = ad->curr
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # copy *ecx to ebx
-    # var h/edx: handle = {0, 0}
-    68/push  0/imm32
-    68/push  0/imm32
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # *Next-alloc-id = 0x34
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x34/imm32  # copy to *Next-alloc-id
-    # copy-array(ad, src, h)
-    # . . push args
-    52/push-edx
-    56/push-esi
-    51/push-ecx
-    # . . call
-    e8/call  copy-array/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->alloc-id, 0x34, msg)
-    # . . push args
-    68/push  "F - test-copy-array: sets alloc-id in handle"/imm32
-    68/push  0x34/imm32
-    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->payload, expected-payload, msg)
-    # . . push args
-    68/push  "F - test-copy-array: sets payload in handle"/imm32
-    53/push-ebx
-    ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(h->payload->alloc-id, 0x34, msg)
-    # . . push args
-    68/push  "F - test-copy-array: sets alloc-id in payload"/imm32
-    68/push  0x34/imm32
-    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # var payload/eax: (addr int) = lookup(h)
-    # . . push args
-    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  lookup/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(payload->size, 0xc, msg)
-    # . . push args
-    68/push  "F - test-copy-array: sets array size in payload"/imm32
-    68/push  0xc/imm32
-    ff          6/subop/push        0/mod/indirect  0/rm32/eax    .           .             .           .           .               .                 # 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
-    # check-ints-equal(*Next-alloc-id, 0x35, msg)
-    # . . push args
-    68/push  "F - test-copy-array: increments Next-alloc-id"/imm32
-    68/push  0x35/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # push *Next-alloc-id
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(ad->curr - expected-payload, 12 + 4 for alloc-id + 4 for size, msg)
-    # . . push args
-    68/push  "F - test-copy-array: updates allocation descriptor"/imm32
-    68/push  0x14/imm32
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
-    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
-    # clean up
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  1/imm32     # copy to *Next-alloc-id
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x40/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
-
-# Fill a region of memory with zeroes.
-zero-out:  # start: (addr byte), size: int
-    # pseudocode:
-    #   curr/esi = start
-    #   i/ecx = 0
-    #   while true
-    #     if (i >= size) break
-    #     *curr = 0
-    #     ++curr
-    #     ++i
-    #
-    # . 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
-    # curr/esi = start
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # var i/ecx: int = 0
-    31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
-    # edx = size
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
-$zero-out:loop:
-    # if (i >= size) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    7d/jump-if->=  $zero-out:end/disp8
-    # *curr = 0
-    c6          0/subop/copy-byte   0/mod/direct    6/rm32/esi    .           .             .           .           .               0/imm8            # copy byte to *esi
-    # ++curr
-    46/increment-esi
-    # ++i
-    41/increment-ecx
-    eb/jump  $zero-out:loop/disp8
-$zero-out: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
-
-test-zero-out:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # region/ecx = 34, 35, 36, 37
-    68/push  0x37363534/imm32
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # zero-out(ecx, 3)
-    # . . push args
-    68/push  3/imm32/size
-    51/push-ecx
-    # . . call
-    e8/call  zero-out/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # first 3 bytes cleared, fourth left alone
-    # . check-ints-equal(*ecx, 0x37000000, msg)
-    # . . push args
-    68/push  "F - test-zero-out"/imm32
-    68/push  0x37000000/imm32
-    ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/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/baremetal/121new-stream.subx b/baremetal/121new-stream.subx
deleted file mode 100644
index 241f162e..00000000
--- a/baremetal/121new-stream.subx
+++ /dev/null
@@ -1,127 +0,0 @@
-# Helper to allocate a stream on the heap.
-
-== 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
-
-new-stream:  # ad: (addr allocation-descriptor), length: int, elemsize: int, out: (addr handle stream _)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    50/push-eax
-    52/push-edx
-    # var size/edx: int = elemsize*length (clobbering eax)
-    # . eax = elemsize
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
-    # . eax *= length
-    31/xor                          3/mod/direct    2/rm32/edx    .           .             .           2/r32/edx   .               .                 # clear edx
-    f7          4/subop/multiply    1/mod/*+disp8   5/rm32/ebp    .           .                                     0xc/disp8       .                 # multiply *(ebp+12) into edx:eax
-    # . if overflow abort
-    81          7/subop/compare     3/mod/direct    2/rm32/edx    .           .             .           .           .               0/imm32           # compare edx
-    75/jump-if-!=  $new-stream:abort/disp8
-    # . edx = elemsize*length
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to edx
-    # var n/eax: int = size + 12 (for read, write and size)
-    05/add-to-eax  0xc/imm32
-    # allocate(ad, n, out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
-    50/push-eax
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  allocate/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # eax = out->payload
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x14/disp8      .                 # copy *(ebp+20) to eax
-    8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
-    # skip payload->allocid
-    05/add-to-eax  4/imm32
-    # eax->size = size
-    89/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           2/r32/edx   8/disp8         .                 # copy edx to *(eax+8)
-    # clear-stream(eax)
-    # . . push args
-    50/push-eax
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$new-stream:end:
-    # . restore registers
-    5a/pop-to-edx
-    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
-
-$new-stream:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "new-stream: size too large" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-test-new-stream:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var ad/ecx: allocation-descriptor containing 16 bytes
-    # . var end/ecx: (addr byte)
-    89/<- %ecx 4/r32/esp
-    # . var start/edx: (addr byte) = end - 32
-    81 5/subop/subtract %esp 0x20/imm32
-    89/<- %edx 4/r32/esp
-    # . ad = {start, end}
-    51/push-ecx
-    52/push-edx
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # var start/edx = ad->curr
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # copy *ecx to edx
-    # var h/ebx: (handle stream byte)
-    68/push  0/imm32
-    68/push  0/imm32
-    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
-    # new-stream(ad, 3, 2, h)
-    # . . push args
-    53/push-ebx
-    68/push  2/imm32
-    68/push  3/imm32
-    51/push-ecx
-    # . . call
-    e8/call  new-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-    # eax = out->payload
-    8b/copy                         1/mod/*+disp8   3/rm32/ebx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ebx+4) to eax
-    # check-ints-equal(eax, edx, msg)
-    # . . push args
-    68/push  "F - test-new-stream: returns current pointer of allocation descriptor"/imm32
-    52/push-edx
-    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
-    # skip payload->allocid
-    05/add-to-eax  4/imm32
-    # check-ints-equal(eax->size, 6, msg)
-    # . . push args
-    68/push  "F - test-new-stream: sets size correctly"/imm32
-    68/push  6/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           .               8/disp8           # push *(eax+8)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # the rest is delegated to clear-stream() so we won't bother checking it
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x30/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/baremetal/123slice.subx b/baremetal/123slice.subx
deleted file mode 100644
index 01c29290..00000000
--- a/baremetal/123slice.subx
+++ /dev/null
@@ -1,1069 +0,0 @@
-# new data structure: a slice is an open interval of addresses [start, end)
-# that includes 'start' but not 'end'
-
-== 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
-
-slice-empty?:  # s: (addr slice) -> result/eax: boolean
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    # ecx = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # if (s->start >= s->end) return true
-    # . eax = s->start
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
-    # . if (eax >= s->end) return true
-    3b/compare                      1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # compare eax with *(ecx+4)
-    b8/copy-to-eax  1/imm32/true
-    73/jump-if-addr>=  $slice-empty?:end/disp8
-    b8/copy-to-eax  0/imm32/false
-$slice-empty?:end:
-    # . restore registers
-    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-slice-empty-true:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var slice/ecx: slice = {34, 34}
-    68/push  34/imm32/end
-    68/push  34/imm32/start
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # slice-empty?(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
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-slice-empty-true"/imm32
-    68/push  1/imm32
-    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-slice-empty-false:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var slice/ecx: slice = {32, 34}
-    68/push  34/imm32/end
-    68/push  32/imm32/start
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # slice-empty?(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
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-slice-empty-false"/imm32
-    68/push  0/imm32
-    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-slice-empty-if-start-greater-than-end:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var slice/ecx: slice = {34, 32}
-    68/push  32/imm32/end
-    68/push  34/imm32/start
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # slice-empty?(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
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-slice-empty-if-start-greater-than-end"/imm32
-    68/push  1/imm32
-    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
-
-slice-equal?:  # s: (addr slice), p: (addr array byte) -> result/eax: boolean
-    # pseudocode:
-    #   if (p == 0) return (s == 0)
-    #   currs = s->start
-    #   maxs = s->end
-    #   if (maxs - currs != p->size) return false
-    #   currp = p->data
-    #   while currs < maxs
-    #     if (*currs != *currp) return false
-    #     ++currs
-    #     ++currp
-    #   return true
-    #
-    # registers:
-    #   currs: edx
-    #   maxs: esi
-    #   currp: ebx
-    #   *currs: eax
-    #   *currp: ecx
-    #
-    # . 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
-    56/push-esi
-    # esi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # var currs/edx: (addr byte) = s->start
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
-    # var maxs/esi: (addr byte) = s->end
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           6/r32/esi   4/disp8         .                 # copy *(esi+4) to esi
-    # var ssize/eax: int = maxs - currs
-    89/copy                         3/mod/direct    0/rm32/eax    .           .             .           6/r32/esi   .               .                 # copy esi to eax
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # subtract edx from eax
-    # ebx = p
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           3/r32/ebx   0xc/disp8       .                 # copy *(ebp+12) to ebx
-    # if (p != 0) goto next check
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0/imm32           # compare ebx
-    75/jump-if-!=  $slice-equal?:nonnull-string/disp8
-$slice-equal?:null-string:
-    # return s->start == s->end
-    3d/compare-eax-and  0/imm32
-    74/jump-if-=  $slice-equal?:true/disp8
-    eb/jump  $slice-equal?:false/disp8
-$slice-equal?:nonnull-string:
-    # if (ssize != p->size) return false
-    39/compare                      0/mod/indirect  3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # compare *ebx and eax
-    75/jump-if-!=  $slice-equal?:false/disp8
-    # var currp/ebx: (addr byte) = p->data
-    81          0/subop/add         3/mod/direct    3/rm32/ebx    .           .             .           .           .               4/imm32           # add to ebx
-    # var c1/eax: byte = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # var c2/ecx: byte = 0
-    31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
-$slice-equal?:loop:
-    # if (currs >= maxs) return true
-    39/compare                      3/mod/direct    2/rm32/edx    .           .             .           6/r32/esi   .               .                 # compare edx with esi
-    73/jump-if-addr>=  $slice-equal?:true/disp8
-    # c1 = *currp
-    8a/copy-byte                    0/mod/indirect  3/rm32/ebx    .           .             .           0/r32/AL    .               .                 # copy byte at *ebx to AL
-    # c2 = *currs
-    8a/copy-byte                    0/mod/indirect  2/rm32/edx    .           .             .           1/r32/CL    .               .                 # copy byte at *edx to CL
-    # if (c1 != c2) return false
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax and ecx
-    75/jump-if-!=  $slice-equal?:false/disp8
-    # ++currp
-    43/increment-ebx
-    # ++currs
-    42/increment-edx
-    eb/jump $slice-equal?:loop/disp8
-$slice-equal?:false:
-    b8/copy-to-eax  0/imm32
-    eb/jump  $slice-equal?:end/disp8
-$slice-equal?:true:
-    b8/copy-to-eax  1/imm32
-$slice-equal?:end:
-    # . restore registers
-    5e/pop-to-esi
-    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-slice-equal:
-    # - slice-equal?(slice("Abc"), "Abc") == 1
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Abc"
-    b8/copy-to-eax  "Abc"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-equal?(ecx, "Abc")
-    # . . push args
-    68/push  "Abc"/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
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-slice-equal"/imm32
-    68/push  1/imm32
-    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-slice-equal-false:
-    # - slice-equal?(slice("bcd"), "Abc") == 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "bcd"
-    b8/copy-to-eax  "bcd"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-equal?(ecx, "Abc")
-    # . . push args
-    68/push  "Abc"/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
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-slice-equal-false"/imm32
-    68/push  0/imm32
-    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-slice-equal-too-long:
-    # - slice-equal?(slice("Abcd"), "Abc") == 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Abcd"
-    b8/copy-to-eax  "Abcd"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-equal?(ecx, "Abc")
-    # . . push args
-    68/push  "Abc"/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
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-slice-equal-too-long"/imm32
-    68/push  0/imm32
-    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-slice-equal-too-short:
-    # - slice-equal?(slice("A"), "Abc") == 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "A"
-    b8/copy-to-eax  "A"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-equal?(ecx, "Abc")
-    # . . push args
-    68/push  "Abc"/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
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-slice-equal-too-short"/imm32
-    68/push  0/imm32
-    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-slice-equal-empty:
-    # - slice-equal?(slice(""), "Abc") == 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var 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
-    # eax = slice-equal?(ecx, "Abc")
-    # . . push args
-    68/push  "Abc"/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
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-slice-equal-empty"/imm32
-    68/push  0/imm32
-    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-slice-equal-with-empty:
-    # - slice-equal?(slice("Ab"), "") == 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Ab"
-    b8/copy-to-eax  "Ab"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-equal?(ecx, "")
-    # . . 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
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-slice-equal-with-empty"/imm32
-    68/push  0/imm32
-    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-slice-equal-empty-with-empty:
-    # - slice-equal?(slice(""), "") == 1
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var 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
-    # eax = slice-equal?(ecx, "")
-    # . . 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
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-slice-equal-empty-with-empty"/imm32
-    68/push  1/imm32
-    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-slice-equal-with-null:
-    # - slice-equal?(slice("Ab"), null) == 0
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Ab"
-    b8/copy-to-eax  "Ab"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-equal?(ecx, 0)
-    # . . push args
-    68/push  0/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
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-slice-equal-with-null"/imm32
-    68/push  0/imm32
-    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
-
-slice-starts-with?:  # s: (addr slice), head: (addr array byte) -> result/eax: boolean
-    # pseudocode
-    #   hsize = head->size
-    #   if (hsize > s->end - s->start) return false
-    #   i = 0
-    #   currs = s->start
-    #   currp = head->data
-    #   while i < hsize
-    #     if (*currs != *currh) return false
-    #     ++i
-    #     ++currs
-    #     ++currh
-    #   return true
-    #
-    # registers:
-    #   currs: esi
-    #   currh: edi
-    #   *currs: eax
-    #   *currh: ebx
-    #   i: ecx
-    #   hsize: edx
-    #
-    # . 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
-    56/push-esi
-    57/push-edi
-    # esi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # var lens/ecx: int = s->end - s->start
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    2b/subtract                     0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # subtract *esi from ecx
-    # edi = head
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-    # var hsize/edx: int = head->size
-    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           2/r32/edx   .               .                 # copy *edi to edx
-    # if (hsize > lens) return false
-    39/compare                      3/mod/direct    2/rm32/edx    .           .             .           1/r32/ecx   .               .                 # compare edx with ecx
-    7f/jump-if->  $slice-starts-with?:false/disp8
-    # var currs/esi: (addr byte) = s->start
-    8b/subtract                     0/mod/indirect  6/rm32/esi    .           .             .           6/r32/esi   .               .                 # copy *esi to esi
-    # var currh/edi: (addr byte) = head->data
-    81          0/subop/add         3/mod/direct    7/rm32/edi    .           .             .           .           .               4/imm32           # add to edi
-    # var i/ecx: int = 0
-    31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
-    # var c1/eax: byte = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # var c2/ebx: byte = 0
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-$slice-starts-with?:loop:
-    # if (i >= hsize) return true
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    7d/jump-if->=  $slice-starts-with?:true/disp8
-    # c1 = *currs
-    8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
-    # c2 = *currh
-    8a/copy-byte                    0/mod/indirect  7/rm32/edi    .           .             .           3/r32/BL    .               .                 # copy byte at *edi to BL
-    # if (c1 != c2) return false
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # compare eax and ebx
-    75/jump-if-!=  $slice-starts-with?:false/disp8
-    # ++i
-    41/increment-ecx
-    # ++currs
-    46/increment-esi
-    # ++currh
-    47/increment-edi
-    eb/jump $slice-starts-with?:loop/disp8
-$slice-starts-with?:true:
-    b8/copy-to-eax  1/imm32
-    eb/jump  $slice-starts-with?:end/disp8
-$slice-starts-with?:false:
-    b8/copy-to-eax  0/imm32
-$slice-starts-with?:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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-slice-starts-with-single-character:
-    # - slice-starts-with?(slice("Abc"), "A") == 1
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Abc"
-    b8/copy-to-eax  "Abc"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-starts-with?(ecx, "A")
-    # . . push args
-    68/push  "A"/imm32
-    51/push-ecx
-    # . . call
-    e8/call  slice-starts-with?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-slice-starts-with-single-character"/imm32
-    68/push  1/imm32
-    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-slice-starts-with-empty-string:
-    # - slice-starts-with?(slice("Abc"), "") == 1
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Abc"
-    b8/copy-to-eax  "Abc"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-starts-with?(ecx, "")
-    # . . push args
-    68/push  ""/imm32
-    51/push-ecx
-    # . . call
-    e8/call  slice-starts-with?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-slice-starts-with-empty-string"/imm32
-    68/push  1/imm32
-    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-slice-starts-with-multiple-characters:
-    # - slice-starts-with?(slice("Abc"), "Ab") == 1
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Abc"
-    b8/copy-to-eax  "Abc"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-starts-with?(ecx, "Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    51/push-ecx
-    # . . call
-    e8/call  slice-starts-with?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-slice-starts-with-multiple-characters"/imm32
-    68/push  1/imm32
-    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-slice-starts-with-entire-string:
-    # - slice-starts-with?(slice("Abc"), "Abc") == 1
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Abc"
-    b8/copy-to-eax  "Abc"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-starts-with?(ecx, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    51/push-ecx
-    # . . call
-    e8/call  slice-starts-with?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-slice-starts-with-entire-string"/imm32
-    68/push  1/imm32
-    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-slice-starts-with-fails:
-    # - slice-starts-with?(slice("Abc"), "Abd") == 1
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Abc"
-    b8/copy-to-eax  "Abc"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-starts-with?(ecx, "Abd")
-    # . . push args
-    68/push  "Abd"/imm32
-    51/push-ecx
-    # . . call
-    e8/call  slice-starts-with?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-slice-starts-with-fails"/imm32
-    68/push  0/imm32
-    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-slice-starts-with-fails-2:
-    # - slice-starts-with?(slice("Abc"), "Ac") == 1
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "Abc"
-    b8/copy-to-eax  "Abc"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = slice-starts-with?(ecx, "Ac")
-    # . . push args
-    68/push  "Ac"/imm32
-    51/push-ecx
-    # . . call
-    e8/call  slice-starts-with?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-slice-starts-with-fails-2"/imm32
-    68/push  0/imm32
-    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
-
-# write a slice to a stream
-# abort if the stream doesn't have enough space
-write-slice:  # out: (addr stream byte), s: (addr slice)
-    # . 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
-    # esi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
-    # var curr/ecx: (addr byte) = s->start
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
-    # var max/esi: (addr byte) = s->end
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           6/r32/esi   4/disp8         .                 # copy *(esi+4) to esi
-    # edi = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
-    # edx = out->size
-    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           2/r32/edx   8/disp8         .                 # copy *(edi+8) to edx
-    # ebx = out->write
-    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           3/r32/ebx   .               .                 # copy *edi to ebx
-$write-slice:loop:
-    # if (curr >= max) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           6/r32/esi   .               .                 # compare ecx with esi
-    73/jump-if-addr>=  $write-slice:loop-end/disp8
-    # if (out->write >= out->size) abort
-    39/compare                      3/mod/direct    3/rm32/ebx    .           .             .           2/r32/edx   .               .                 # compare ebx with edx
-    7d/jump-if->=  $write-slice:abort/disp8
-    # out->data[out->write] = *in
-    # . AL = *in
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-    # . out->data[out->write] = AL
-    88/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/edi  3/index/ebx   .           0/r32/AL    0xc/disp8       .                 # copy AL to *(edi+ebx+12)
-    # ++out->write
-    43/increment-ebx
-    # ++in
-    41/increment-ecx
-    eb/jump  $write-slice:loop/disp8
-$write-slice:loop-end:
-    # persist out->write
-    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           3/r32/ebx   .               .                 # copy ebx to *edi
-$write-slice:end:
-    # . 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
-
-$write-slice:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "write-slice: out of space" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-test-write-slice:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # (eax..ecx) = "Abc"
-    b8/copy-to-eax  "Abc"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # write-slice(_test-stream, slice)
-    # . . push args
-    51/push-ecx
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "Abc", msg)
-    # . . push args
-    68/push  "F - test-write-slice"/imm32
-    68/push  "Abc"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-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
-
-# copy a slice into a new (dynamically allocated) string
-slice-to-string:  # ad: (addr allocation-descriptor), in: (addr slice), out: (addr handle array byte)
-    # . 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
-    # esi = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
-    # var curr/edx: (addr byte) = in->start
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
-    # var max/ebx: (addr byte) = in->end
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           3/r32/ebx   4/disp8         .                 # copy *(esi+4) to ebx
-    # var size/ecx: int = max - curr + 4  # total size of output string (including the initial 'size' field)
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # copy ebx to ecx
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # subtract edx from ecx
-    81          0/subop/add         3/mod/direct    1/rm32/ecx    .           .             .           .           .               4/imm32           # add to ecx
-    # allocate(ad, size, out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
-    51/push-ecx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  allocate/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # eax = out->payload
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
-    8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
-    # skip payload->allocid
-    05/add-to-eax  4/imm32
-    # if (eax == 0) abort
-    3d/compare-eax-and  0/imm32
-    74/jump-if-=  $slice-to-string:abort/disp8
-    # out->size = size-4
-    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
-    81          5/subop/subtract    0/mod/indirect  0/rm32/eax    .           .             .           .           .               4/imm32           # subtract 4 from *eax
-    # save out
-    50/push-eax
-$slice-to-string:initialize:
-    # eax = _append-4(eax+4, eax+size, curr, max)  # clobbering ecx
-    # . . push args
-    53/push-ebx
-    52/push-edx
-    # . . push eax+size (clobbering ecx)
-    01/add                          3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # add eax to ecx
-    51/push-ecx
-    # . . push eax+4 (clobbering eax)
-    81          0/subop/add         3/mod/direct    0/rm32/eax    .           .             .           .           .               4/imm32           # add to eax
-    50/push-eax
-    # . . call
-    e8/call  _append-4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
-    # restore out (assumes _append-4 can't error)
-    58/pop-to-eax
-$slice-to-string:end:
-    # . restore registers
-    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
-
-$slice-to-string:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "slice-to-string: out of space\n" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-test-slice-to-string:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var ad/edx: allocation-descriptor containing 16 bytes
-    # . var end/ecx: (addr byte)
-    89/<- %ecx 4/r32/esp
-    81 5/subop/subtract %esp 0x10/imm32
-    # . var start/edx: (addr byte) = end - 0x10
-    89/<- %edx 4/r32/esp
-    # . ad = {start, end}
-    51/push-ecx
-    52/push-edx
-    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
-    # (eax..ecx) = "Abc"
-    b8/copy-to-eax  "Abc"/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 slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # 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
-    # slice-to-string(ad, slice, h)
-    # . . push args
-    53/push-ebx
-    51/push-ecx
-    52/push-edx
-    # . . call
-    e8/call  slice-to-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # eax = h->payload
-    8b/copy                         1/mod/*+disp8   3/rm32/ebx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ebx+4) to eax
-    # skip payload->allocid
-    05/add-to-eax  4/imm32
-#?     # dump eax {{{
-#?     # . 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
-#?     # . write(2/stderr, eax)
-#?     # . . push args
-#?     50/push-eax
-#?     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(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
-#?     # }}}
-    # eax = string-equal?(eax, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    50/push-eax
-    # . . call
-    e8/call  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-slice-to-string"/imm32
-    68/push  1/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
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/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/baremetal/124next-token.subx b/baremetal/124next-token.subx
deleted file mode 100644
index 6ad00907..00000000
--- a/baremetal/124next-token.subx
+++ /dev/null
@@ -1,1772 +0,0 @@
-# Some tokenization primitives.
-
-== 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
-
-# extract the next run of characters that are different from a given 'delimiter' (skipping multiple delimiters if necessary)
-# on reaching end of file, return an empty interval
-next-token-from-slice:  # start: (addr byte), end: (addr byte), delimiter: byte, out: (addr slice)
-    # . 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
-    57/push-edi
-    # ecx = end
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
-    # edx = delimiter
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8      .                 # copy *(ebp+16) to edx
-    # edi = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0x14/disp8      .                 # copy *(ebp+20) to edi
-    # eax = skip-chars-matching-in-slice(start, end, delimiter)
-    # . . push args
-    52/push-edx
-    51/push-ecx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  skip-chars-matching-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # out->start = eax
-    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to *edi
-    # eax = skip-chars-not-matching-in-slice(eax, end, delimiter)
-    # . . push args
-    52/push-edx
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-chars-not-matching-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # out->end = eax
-    89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
-    # . restore registers
-    5f/pop-to-edi
-    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
-
-test-next-token-from-slice:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "  ab"
-    b8/copy-to-eax  "  ab"/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 out/edi: slice
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    7/rm32/edi    .           .             .           4/r32/esp   .               .                 # copy esp to edi
-    # next-token-from-slice(eax, ecx, 0x20/space, out)
-    # . . push args
-    57/push-edi
-    68/push  0x20/imm32
-    51/push-ecx
-    50/push-eax
-    # . . 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
-    # out->start should be at the 'a'
-    # . check-ints-equal(out->start - in->start, 2, msg)
-    # . . push args
-    68/push  "F - test-next-token-from-slice: start"/imm32
-    68/push  2/imm32
-    # . . push out->start - in->start
-    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
-    2b/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # out->end should be after the 'b'
-    # check-ints-equal(out->end - in->start, 4, msg)
-    # . . push args
-    68/push  "F - test-next-token-from-slice: end"/imm32
-    68/push  4/imm32
-    # . . push out->end - in->start
-    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
-    2b/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . 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-next-token-from-slice-Eof:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # var out/edi: slice
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    7/rm32/edi    .           .             .           4/r32/esp   .               .                 # copy esp to edi
-    # next-token-from-slice(0, 0, 0x20/space, out)
-    # . . push args
-    57/push-edi
-    68/push  0x20/imm32
-    68/push  0/imm32
-    68/push  0/imm32
-    # . . 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
-    # out should be empty
-    # . check-ints-equal(out->end - out->start, 0, msg)
-    # . . push args
-    68/push  "F - test-next-token-from-slice-Eof"/imm32
-    68/push  0/imm32
-    # . . push out->start - in->start
-    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
-    2b/subtract                     0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # subtract *edi from ecx
-    51/push-ecx
-    # . . 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-next-token-from-slice-nothing:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "    "
-    b8/copy-to-eax  "    "/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 out/edi: slice
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    7/rm32/edi    .           .             .           4/r32/esp   .               .                 # copy esp to edi
-    # next-token-from-slice(in, 0x20/space, out)
-    # . . push args
-    57/push-edi
-    68/push  0x20/imm32
-    51/push-ecx
-    50/push-eax
-    # . . 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
-    # out should be empty
-    # . check-ints-equal(out->end - out->start, 0, msg)
-    # . . push args
-    68/push  "F - test-next-token-from-slice-Eof"/imm32
-    68/push  0/imm32
-    # . . push out->start - in->start
-    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
-    2b/subtract                     0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # subtract *edi from ecx
-    51/push-ecx
-    # . . 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
-
-skip-chars-matching:  # in: (addr stream byte), delimiter: byte
-    # . 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
-    # esi = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # ecx = in->read
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    # ebx = in->write
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
-    # edx = delimiter
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
-$skip-chars-matching:loop:
-    # if (in->read >= in->write) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
-    7d/jump-if->=  $skip-chars-matching:end/disp8
-    # eax = in->data[in->read]
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
-    # if (eax != delimiter) break
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax and edx
-    75/jump-if-!=  $skip-chars-matching:end/disp8
-    # ++in->read
-    41/increment-ecx
-    eb/jump  $skip-chars-matching:loop/disp8
-$skip-chars-matching:end:
-    # persist in->read
-    89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(esi+4)
-    # . restore registers
-    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
-
-test-skip-chars-matching:
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "  ab")
-    # . . push args
-    68/push  "  ab"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # skip-chars-matching(_test-stream, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-chars-matching/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(_test-stream->read, 2, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-matching"/imm32
-    68/push  2/imm32
-    # . . push *_test-stream->read
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-test-skip-chars-matching-none:
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "ab")
-    # . . push args
-    68/push  "ab"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # skip-chars-matching(_test-stream, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-chars-matching/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(_test-stream->read, 0, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-matching-none"/imm32
-    68/push  0/imm32
-    # . . push *_test-stream->read
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-skip-chars-matching-whitespace:  # in: (addr stream byte)
-    # . 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
-    53/push-ebx
-    56/push-esi
-    # esi = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # ecx = in->read
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    # ebx = in->write
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
-$skip-chars-matching-whitespace:loop:
-    # if (in->read >= in->write) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
-    7d/jump-if->=  $skip-chars-matching-whitespace:end/disp8
-    # eax = in->data[in->read]
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
-    # if (eax == ' ') goto body
-    3d/compare-eax-and  0x20/imm32/space
-    74/jump-if-=  $skip-chars-matching-whitespace:body/disp8
-    # if (eax == '\n') goto body
-    3d/compare-eax-and  0x0a/imm32/newline
-    74/jump-if-=  $skip-chars-matching-whitespace:body/disp8
-    # if (eax == '\t') goto body
-    3d/compare-eax-and  0x09/imm32/tab
-    74/jump-if-=  $skip-chars-matching-whitespace:body/disp8
-    # if (eax != '\r') break
-    3d/compare-eax-and  0x0d/imm32/cr
-    75/jump-if-!=  $skip-chars-matching-whitespace:end/disp8
-$skip-chars-matching-whitespace:body:
-    # ++in->read
-    41/increment-ecx
-    eb/jump  $skip-chars-matching-whitespace:loop/disp8
-$skip-chars-matching-whitespace:end:
-    # persist in->read
-    89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(esi+4)
-    # . restore registers
-    5e/pop-to-esi
-    5b/pop-to-ebx
-    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
-
-test-skip-chars-matching-whitespace:
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, " \nab")
-    # . . push args
-    68/push  " \nab"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # skip-chars-matching-whitespace(_test-stream)
-    # . . push args
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-chars-matching-whitespace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(_test-stream->read, 2, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-matching-whitespace"/imm32
-    68/push  2/imm32
-    # . . push *_test-stream->read
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-# minor fork of 'skip-chars-matching'
-skip-chars-not-matching:  # in: (addr stream byte), delimiter: byte
-    # . 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
-    # esi = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # ecx = in->read
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    # ebx = in->write
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
-    # edx = delimiter
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
-$skip-chars-not-matching:loop:
-    # if (in->read >= in->write) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
-    7d/jump-if->=  $skip-chars-not-matching:end/disp8
-    # eax = in->data[in->read]
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
-    # if (eax == delimiter) break
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax and edx
-    74/jump-if-=  $skip-chars-not-matching:end/disp8
-    # ++in->read
-    41/increment-ecx
-    eb/jump  $skip-chars-not-matching:loop/disp8
-$skip-chars-not-matching:end:
-    # persist in->read
-    89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(esi+4)
-    # . restore registers
-    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
-
-test-skip-chars-not-matching:
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "ab ")
-    # . . push args
-    68/push  "ab "/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # skip-chars-not-matching(_test-stream, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-chars-not-matching/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(_test-stream->read, 2, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-not-matching"/imm32
-    68/push  2/imm32
-    # . . push *_test-stream->read
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-test-skip-chars-not-matching-none:
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, " ab")
-    # . . push args
-    68/push  " ab"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # skip-chars-not-matching(_test-stream, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-chars-not-matching/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(_test-stream->read, 0, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-not-matching-none"/imm32
-    68/push  0/imm32
-    # . . push *_test-stream->read
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-test-skip-chars-not-matching-all:
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "ab")
-    # . . push args
-    68/push  "ab"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # skip-chars-not-matching(_test-stream, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-chars-not-matching/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(_test-stream->read, 2, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-not-matching-all"/imm32
-    68/push  2/imm32
-    # . . push *_test-stream->read
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-skip-chars-not-matching-whitespace:  # in: (addr stream byte)
-    # . 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
-    53/push-ebx
-    56/push-esi
-    # esi = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # ecx = in->read
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    # ebx = in->write
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
-$skip-chars-not-matching-whitespace:loop:
-    # if (in->read >= in->write) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
-    7d/jump-if->=  $skip-chars-not-matching-whitespace:end/disp8
-    # eax = in->data[in->read]
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
-    # if (eax == ' ') break
-    3d/compare-eax-and  0x20/imm32/space
-    74/jump-if-=  $skip-chars-not-matching-whitespace:end/disp8
-    # if (eax == '\n') break
-    3d/compare-eax-and  0x0a/imm32/newline
-    74/jump-if-=  $skip-chars-not-matching-whitespace:end/disp8
-    # if (eax == '\t') break
-    3d/compare-eax-and  0x09/imm32/tab
-    74/jump-if-=  $skip-chars-not-matching-whitespace:end/disp8
-    # if (eax == '\r') break
-    3d/compare-eax-and  0x0d/imm32/cr
-    74/jump-if-=  $skip-chars-not-matching-whitespace:end/disp8
-    # ++in->read
-    41/increment-ecx
-    eb/jump  $skip-chars-not-matching-whitespace:loop/disp8
-$skip-chars-not-matching-whitespace:end:
-    # persist in->read
-    89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(esi+4)
-    # . restore registers
-    5e/pop-to-esi
-    5b/pop-to-ebx
-    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
-
-test-skip-chars-not-matching-whitespace:
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write(_test-stream, "ab\n")
-    # . . push args
-    68/push  "ab\n"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # skip-chars-not-matching-whitespace(_test-stream)
-    # . . push args
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-chars-not-matching-whitespace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(_test-stream->read, 2, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-not-matching-whitespace"/imm32
-    68/push  2/imm32
-    # . . push *_test-stream->read
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-skip-chars-matching-in-slice:  # curr: (addr byte), end: (addr byte), delimiter: byte -> curr/eax: (addr byte)
-    # . 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
-    # eax = curr
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
-    # ecx = end
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
-    # edx = delimiter
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8       .                 # copy *(ebp+16) to edx
-    # var c/ebx: byte = 0
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-$skip-chars-matching-in-slice:loop:
-    # if (curr >= end) break
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
-    73/jump-if-addr>=  $skip-chars-matching-in-slice:end/disp8
-    # c = *curr
-    8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           3/r32/BL    .               .                 # copy byte at *eax to BL
-    # if (c != delimiter) break
-    39/compare                      3/mod/direct    3/rm32/ebx    .           .             .           2/r32/edx   .               .                 # compare ebx and edx
-    75/jump-if-!=  $skip-chars-matching-in-slice:end/disp8
-    # ++curr
-    40/increment-eax
-    eb/jump  $skip-chars-matching-in-slice:loop/disp8
-$skip-chars-matching-in-slice:end:
-    # . 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-skip-chars-matching-in-slice:
-    # (eax..ecx) = "  ab"
-    b8/copy-to-eax  "  ab"/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
-    # eax = skip-chars-matching-in-slice(eax, ecx, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32/space
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-chars-matching-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(ecx-eax, 2, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-matching-in-slice"/imm32
-    68/push  2/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-test-skip-chars-matching-in-slice-none:
-    # (eax..ecx) = "ab"
-    b8/copy-to-eax  "ab"/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
-    # eax = skip-chars-matching-in-slice(eax, ecx, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32/space
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-chars-matching-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(ecx-eax, 2, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-matching-in-slice-none"/imm32
-    68/push  2/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-skip-chars-matching-whitespace-in-slice:  # curr: (addr byte), end: (addr byte) -> curr/eax: (addr byte)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    53/push-ebx
-    # eax = curr
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
-    # ecx = end
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
-    # var c/ebx: byte = 0
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-$skip-chars-matching-whitespace-in-slice:loop:
-    # if (curr >= end) break
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
-    0f 83/jump-if-addr>=  $skip-chars-matching-in-slice:end/disp32
-    # c = *curr
-    8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           3/r32/BL    .               .                 # copy byte at *eax to BL
-    # if (c == ' ') goto body
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x20/imm32/space  # compare ebx
-    74/jump-if-=  $skip-chars-matching-whitespace-in-slice:body/disp8
-    # if (c == '\n') goto body
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x0a/imm32/newline  # compare ebx
-    74/jump-if-=  $skip-chars-matching-whitespace-in-slice:body/disp8
-    # if (c == '\t') goto body
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x09/imm32/tab    # compare ebx
-    74/jump-if-=  $skip-chars-matching-whitespace-in-slice:body/disp8
-    # if (c != '\r') break
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x0d/imm32/cr     # compare ebx
-    75/jump-if-!=  $skip-chars-matching-whitespace-in-slice:end/disp8
-$skip-chars-matching-whitespace-in-slice:body:
-    # ++curr
-    40/increment-eax
-    eb/jump  $skip-chars-matching-whitespace-in-slice:loop/disp8
-$skip-chars-matching-whitespace-in-slice:end:
-    # . restore registers
-    5b/pop-to-ebx
-    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-skip-chars-matching-whitespace-in-slice:
-    # (eax..ecx) = " \nab"
-    b8/copy-to-eax  " \nab"/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
-    # eax = skip-chars-matching-whitespace-in-slice(eax, ecx)
-    # . . push args
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-chars-matching-whitespace-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(ecx-eax, 2, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-matching-whitespace-in-slice"/imm32
-    68/push  2/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-# minor fork of 'skip-chars-matching-in-slice'
-skip-chars-not-matching-in-slice:  # curr: (addr byte), end: (addr byte), delimiter: byte -> curr/eax: (addr byte)
-    # . 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
-    # eax = curr
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
-    # ecx = end
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
-    # edx = delimiter
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8       .                 # copy *(ebp+16) to edx
-    # var c/ebx: byte = 0
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-$skip-chars-not-matching-in-slice:loop:
-    # if (curr >= end) break
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
-    73/jump-if-addr>=  $skip-chars-not-matching-in-slice:end/disp8
-    # c = *curr
-    8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           3/r32/BL    .               .                 # copy byte at *eax to BL
-    # if (c == delimiter) break
-    39/compare                      3/mod/direct    3/rm32/ebx    .           .             .           2/r32/edx   .               .                 # compare ebx and edx
-    74/jump-if-=  $skip-chars-not-matching-in-slice:end/disp8
-    # ++curr
-    40/increment-eax
-    eb/jump  $skip-chars-not-matching-in-slice:loop/disp8
-$skip-chars-not-matching-in-slice:end:
-    # . 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-skip-chars-not-matching-in-slice:
-    # (eax..ecx) = "ab "
-    b8/copy-to-eax  "ab "/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
-    # eax = skip-chars-not-matching-in-slice(eax, ecx, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32/space
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-chars-not-matching-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(ecx-eax, 1, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-not-matching-in-slice"/imm32
-    68/push  1/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-test-skip-chars-not-matching-in-slice-none:
-    # (eax..ecx) = " ab"
-    b8/copy-to-eax  " ab"/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
-    # eax = skip-chars-not-matching-in-slice(eax, ecx, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32/space
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-chars-not-matching-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(ecx-eax, 3, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-not-matching-in-slice-none"/imm32
-    68/push  3/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-test-skip-chars-not-matching-in-slice-all:
-    # (eax..ecx) = "ab"
-    b8/copy-to-eax  "ab"/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
-    # eax = skip-chars-not-matching-in-slice(eax, ecx, 0x20/space)
-    # . . push args
-    68/push  0x20/imm32/space
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-chars-not-matching-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # check-ints-equal(ecx-eax, 0, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-not-matching-in-slice-all"/imm32
-    68/push  0/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-skip-chars-not-matching-whitespace-in-slice:  # curr: (addr byte), end: (addr byte) -> curr/eax: (addr byte)
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    53/push-ebx
-    # eax = curr
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
-    # ecx = end
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
-    # var c/ebx: byte = 0
-    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-$skip-chars-not-matching-whitespace-in-slice:loop:
-    # if (curr >= end) break
-    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
-    0f 83/jump-if-addr>=  $skip-chars-not-matching-in-slice:end/disp32
-    # c = *curr
-    8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           3/r32/BL    .               .                 # copy byte at *eax to BL
-    # if (c == ' ') break
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x20/imm32/space  # compare ebx
-    74/jump-if-=  $skip-chars-not-matching-whitespace-in-slice:end/disp8
-    # if (c == '\n') break
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x0a/imm32/newline  # compare ebx
-    74/jump-if-=  $skip-chars-not-matching-whitespace-in-slice:end/disp8
-    # if (c == '\t') break
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x09/imm32/tab    # compare ebx
-    74/jump-if-=  $skip-chars-not-matching-whitespace-in-slice:end/disp8
-    # if (c == '\r') break
-    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x0d/imm32/cr     # compare ebx
-    74/jump-if-=  $skip-chars-not-matching-whitespace-in-slice:end/disp8
-    # ++curr
-    40/increment-eax
-    eb/jump  $skip-chars-not-matching-whitespace-in-slice:loop/disp8
-$skip-chars-not-matching-whitespace-in-slice:end:
-    # . restore registers
-    5b/pop-to-ebx
-    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-skip-chars-not-matching-whitespace-in-slice:
-    # (eax..ecx) = "ab\n"
-    b8/copy-to-eax  "ab\n"/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
-    # eax = skip-chars-not-matching-whitespace-in-slice(eax, ecx)
-    # . . push args
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-chars-not-matching-whitespace-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(ecx-eax, 1, msg)
-    # . . push args
-    68/push  "F - test-skip-chars-not-matching-whitespace-in-slice"/imm32
-    68/push  1/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # end
-    c3/return
-
-# update line->read to end of string literal surrounded by double quotes
-# line->read must start out at a double-quote
-skip-string:  # line: (addr stream byte)
-    # . 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
-    # ecx = line
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # eax = skip-string-in-slice(&line->data[line->read], &line->data[line->write])
-    # . . push &line->data[line->write]
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .                         2/r32/edx   8/disp8         .                 # copy *(ecx+8) to edx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   0xc/disp8       .                 # copy ecx+edx+12 to edx
-    52/push-edx
-    # . . push &line->data[line->read]
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .                         2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   0xc/disp8       .                 # copy ecx+edx+12 to edx
-    52/push-edx
-    # . . call
-    e8/call  skip-string-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # line->read = eax - line->data
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
-    2d/subtract-from-eax  0xc/imm32
-    89/copy                         1/mod/*+disp8   1/rm32/ecx    .           .                         0/r32/eax   4/disp8         .                 # copy eax to *(ecx+4)
-$skip-string:end:
-    # . restore registers
-    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
-
-test-skip-string:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # . write(_test-stream, "\"abc\" def")
-    # .                   indices:  0123 45
-    # . . push args
-    68/push  "\"abc\" def"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # precondition: line->read == 0
-    # . . push args
-    68/push  "F - test-skip-string/precondition"/imm32
-    68/push  0/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # skip-string(_test-stream)
-    # . . push args
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(line->read, 5, msg)
-    # . . push args
-    68/push  "F - test-skip-string"/imm32
-    68/push  5/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . 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-skip-string-ignores-spaces:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # . write(_test-stream, "\"a b\"/yz")
-    # .                   indices:  0123 45
-    # . . push args
-    68/push  "\"a b\"/yz"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # precondition: line->read == 0
-    # . . push args
-    68/push  "F - test-skip-string-ignores-spaces/precondition"/imm32
-    68/push  0/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # skip-string(_test-stream)
-    # . . push args
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(line->read, 5, msg)
-    # . . push args
-    68/push  "F - test-skip-string-ignores-spaces"/imm32
-    68/push  5/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . 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-skip-string-ignores-escapes:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # . write(_test-stream, "\"a\\\"b\"/yz")
-    # .                   indices:  01 2 34 56
-    # . . push args
-    68/push  "\"a\\\"b\"/yz"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # precondition: line->read == 0
-    # . . push args
-    68/push  "F - test-skip-string-ignores-escapes/precondition"/imm32
-    68/push  0/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # skip-string(_test-stream)
-    # . . push args
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(line->read, 6, msg)
-    # . . push args
-    68/push  "F - test-skip-string-ignores-escapes"/imm32
-    68/push  6/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . 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-skip-string-works-from-mid-stream:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # . write(_test-stream, "0 \"a\\\"b\"/yz")
-    # .                   indices:  01 2 34 56
-    # . . push args
-    68/push  "0 \"a\\\"b\"/yz"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # precondition: line->read == 2
-    b8/copy-to-eax  _test-stream/imm32
-    c7          0/subop/copy        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         2/imm32           # copy to *(eax+4)
-    # skip-string(_test-stream)
-    # . . push args
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(line->read, 8, msg)
-    # . . push args
-    68/push  "F - test-skip-string-works-from-mid-stream"/imm32
-    68/push  8/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . 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
-
-skip-string-in-slice:  # curr: (addr byte), end: (addr byte) -> curr/eax: (addr byte)
-    # . 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
-    # ecx = curr
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # edx = end
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         2/r32/edx   0xc/disp8         .               # copy *(ebp+12) to edx
-    # var c/eax: byte = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # skip initial dquote
-    41/increment-ecx
-$skip-string-in-slice:loop:
-    # if (curr >= end) return curr
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    73/jump-if-addr>=  $skip-string-in-slice:return-curr/disp8
-    # c = *curr
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-$skip-string-in-slice:dquote:
-    # if (c == '"') break
-    3d/compare-eax-and  0x22/imm32/double-quote
-    74/jump-if-=  $skip-string-in-slice:break/disp8
-$skip-string-in-slice:check-for-escape:
-    # if (c == '\') escape next char
-    3d/compare-eax-and  0x5c/imm32/backslash
-    75/jump-if-!=  $skip-string-in-slice:continue/disp8
-$skip-string-in-slice:escape:
-    41/increment-ecx
-$skip-string-in-slice:continue:
-    # ++curr
-    41/increment-ecx
-    eb/jump  $skip-string-in-slice:loop/disp8
-$skip-string-in-slice:break:
-    # skip final dquote
-    41/increment-ecx
-$skip-string-in-slice:return-curr:
-    # return curr
-    89/copy                         3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to eax
-$skip-string-in-slice:end:
-    # . 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-skip-string-in-slice:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup: (eax..ecx) = "\"abc\" def"
-    b8/copy-to-eax  "\"abc\" def"/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
-    # eax = skip-string-in-slice(eax, ecx)
-    # . . push args
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-string-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(ecx-eax, 4, msg)  # number of chars remaining after the string literal
-    # . . push args
-    68/push  "F - test-skip-string-in-slice"/imm32
-    68/push  4/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . 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-skip-string-in-slice-ignores-spaces:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup: (eax..ecx) = "\"a b\"/yz"
-    b8/copy-to-eax  "\"a b\"/yz"/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
-    # eax = skip-string-in-slice(eax, ecx)
-    # . . push args
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-string-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(ecx-eax, 3, msg)  # number of chars remaining after the string literal
-    # . . push args
-    68/push  "F - test-skip-string-in-slice-ignores-spaces"/imm32
-    68/push  3/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . 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-skip-string-in-slice-ignores-escapes:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup: (eax..ecx) = "\"a\\\"b\"/yz"
-    b8/copy-to-eax  "\"a\\\"b\"/yz"/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
-    # eax = skip-string-in-slice(eax, ecx)
-    # . . push args
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-string-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(ecx-eax, 3, msg)  # number of chars remaining after the string literal
-    # . . push args
-    68/push  "F - test-skip-string-in-slice-ignores-escapes"/imm32
-    68/push  3/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . 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-skip-string-in-slice-stops-at-end:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup: (eax..ecx) = "\"abc"  # unbalanced dquote
-    b8/copy-to-eax  "\"abc"/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
-    # eax = skip-string-in-slice(eax, ecx)
-    # . . push args
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-string-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(ecx-eax, 0, msg)  # skipped to end of slice
-    # . . push args
-    68/push  "F - test-skip-string-in-slice-stops-at-end"/imm32
-    68/push  0/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . 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
-
-# update line->read to ')'
-# line->read ends at ')'
-skip-until-close-paren:  # line: (addr stream byte)
-    # . 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
-    # ecx = line
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # eax = skip-until-close-paren-in-slice(&line->data[line->read], &line->data[line->write])
-    # . . push &line->data[line->write]
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .                         2/r32/edx   8/disp8         .                 # copy *(ecx+8) to edx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   0xc/disp8       .                 # copy ecx+edx+12 to edx
-    52/push-edx
-    # . . push &line->data[line->read]
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .                         2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   0xc/disp8       .                 # copy ecx+edx+12 to edx
-    52/push-edx
-    # . . call
-    e8/call  skip-until-close-paren-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # line->read = eax - line->data
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
-    2d/subtract-from-eax  0xc/imm32
-    89/copy                         1/mod/*+disp8   1/rm32/ecx    .           .                         0/r32/eax   4/disp8         .                 # copy eax to *(ecx+4)
-$skip-until-close-paren:end:
-    # . restore registers
-    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
-
-test-skip-until-close-paren:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # . write(_test-stream, "*(abc) def")
-    # .                   indices:  0123 45
-    # . . push args
-    68/push  "*(abc) def"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # precondition: line->read == 0
-    # . . push args
-    68/push  "F - test-skip-until-close-paren/precondition"/imm32
-    68/push  0/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # skip-until-close-paren(_test-stream)
-    # . . push args
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-until-close-paren/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(line->read, 5, msg)
-    # . . push args
-    68/push  "F - test-skip-until-close-paren"/imm32
-    68/push  5/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . 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-skip-until-close-paren-ignores-spaces:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # . write(_test-stream, "*(a b)/yz")
-    # . . push args
-    68/push  "*(a b)/yz"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # precondition: line->read == 0
-    # . . push args
-    68/push  "F - test-skip-until-close-paren-ignores-spaces/precondition"/imm32
-    68/push  0/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # skip-until-close-paren(_test-stream)
-    # . . push args
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-until-close-paren/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(line->read, 5, msg)
-    # . . push args
-    68/push  "F - test-skip-until-close-paren-ignores-spaces"/imm32
-    68/push  5/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . 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-skip-until-close-paren-works-from-mid-stream:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # . write(_test-stream, "0 *(a b)/yz")
-    # . . push args
-    68/push  "0 *(a b)/yz"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # precondition: _test-stream->read == 2
-    b8/copy-to-eax  _test-stream/imm32
-    c7          0/subop/copy        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         2/imm32           # copy to *(eax+4)
-    # skip-until-close-paren(_test-stream)
-    # . . push args
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  skip-until-close-paren/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(_test-stream->read, 7, msg)
-    # . . push args
-    68/push  "F - test-skip-until-close-paren-works-from-mid-stream"/imm32
-    68/push  7/imm32
-    b8/copy-to-eax  _test-stream/imm32
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
-    # . . 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
-
-skip-until-close-paren-in-slice:  # curr: (addr byte), end: (addr byte) -> curr/eax: (addr byte)
-    # . 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
-    # ecx = curr
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # edx = end
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         2/r32/edx   0xc/disp8         .               # copy *(ebp+12) to edx
-    # var c/eax: byte = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # skip initial dquote
-    41/increment-ecx
-$skip-until-close-paren-in-slice:loop:
-    # if (curr >= end) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    73/jump-if-addr>=  $skip-until-close-paren-in-slice:break/disp8
-    # c = *curr
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-$skip-until-close-paren-in-slice:check-close:
-    # if (c == ')') break
-    3d/compare-eax-and  0x29/imm32/close-paren
-    74/jump-if-=  $skip-until-close-paren-in-slice:break/disp8
-    # ++curr
-    41/increment-ecx
-    eb/jump  $skip-until-close-paren-in-slice:loop/disp8
-$skip-until-close-paren-in-slice:break:
-    # return curr
-    89/copy                         3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to eax
-$skip-until-close-paren-in-slice:end:
-    # . restore registers
-    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-skip-until-close-paren-in-slice:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup: (eax..ecx) = "*(abc) def"
-    b8/copy-to-eax  "*(abc) def"/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
-    # eax = skip-until-close-paren-in-slice(eax, ecx)
-    # . . push args
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-until-close-paren-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(ecx-eax, 5, msg)  # eax is at the ')'
-    # . . push args
-    68/push  "F - test-skip-until-close-paren-in-slice"/imm32
-    68/push  5/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . 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-skip-until-close-paren-in-slice-ignores-spaces:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup: (eax..ecx) = "*(a b)/yz"
-    b8/copy-to-eax  "*(a b)/yz"/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
-    # eax = skip-until-close-paren-in-slice(eax, ecx)
-    # . . push args
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-until-close-paren-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(ecx-eax, 4, msg)  # eax is at the ')'
-    # . . push args
-    68/push  "F - test-skip-until-close-paren-in-slice-ignores-spaces"/imm32
-    68/push  4/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . 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-skip-until-close-paren-in-slice-stops-at-end:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup: (eax..ecx) = "*(abc"  # unbalanced dquote
-    b8/copy-to-eax  "*(abc"/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
-    # eax = skip-until-close-paren-in-slice(eax, ecx)
-    # . . push args
-    51/push-ecx
-    50/push-eax
-    # . . call
-    e8/call  skip-until-close-paren-in-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(ecx-eax, 0, msg)  # skipped to end of slice
-    # . . push args
-    68/push  "F - test-skip-until-close-paren-in-slice-stops-at-end"/imm32
-    68/push  0/imm32
-    # . . push ecx-eax
-    29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
-    51/push-ecx
-    # . . 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/baremetal/126write-int-decimal.subx b/baremetal/126write-int-decimal.subx
deleted file mode 100644
index 9f148248..00000000
--- a/baremetal/126write-int-decimal.subx
+++ /dev/null
@@ -1,428 +0,0 @@
-# Helper to print an int32 in decimal.
-
-== 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
-
-write-int32-decimal:  # out: (addr stream byte), n: int
-    # works by generating characters from lowest to highest and pushing them
-    # to the stack, before popping them one by one into the stream
-    #
-    # pseudocode:
-    #   push sentinel
-    #   eax = abs(n)
-    #   while true
-    #     sign-extend eax into edx
-    #     eax, edx = eax/10, eax%10
-    #     edx += '0'
-    #     push edx
-    #     if (eax == 0) break
-    #   if n < 0
-    #     push '-'
-    #   w = out->write
-    #   curr = &out->data[out->write]
-    #   max = &out->data[out->size]
-    #   while true
-    #     pop into eax
-    #     if (eax == sentinel) break
-    #     if (curr >= max) abort
-    #     *curr = AL
-    #     ++curr
-    #     ++w
-    #   out->write = w
-    # (based on K&R itoa: https://en.wikibooks.org/wiki/C_Programming/stdlib.h/itoa)
-    # (this pseudocode contains registers because operations like division
-    # require specific registers in x86)
-    #
-    # . 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
-    57/push-edi
-    # const ten/ecx = 10
-    b9/copy-to-ecx  0xa/imm32
-    # push sentinel
-    68/push  0/imm32/sentinel
-    # var eax: int = abs(n)
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
-    3d/compare-eax-with  0/imm32
-    7d/jump-if->=  $write-int32-decimal:read-loop/disp8
-$write-int32-decimal:negative:
-    f7          3/subop/negate      3/mod/direct    0/rm32/eax    .           .             .           .           .               .                 # negate eax
-$write-int32-decimal:read-loop:
-    # eax, edx = eax / 10, eax % 10
-    99/sign-extend-eax-into-edx
-    f7          7/subop/idiv        3/mod/direct    1/rm32/ecx    .           .             .           .           .               .                 # divide edx:eax by ecx, storing quotient in eax and remainder in edx
-    # edx += '0'
-    81          0/subop/add         3/mod/direct    2/rm32/edx    .           .             .           .           .               0x30/imm32        # add to edx
-    # push edx
-    52/push-edx
-    # if (eax == 0) break
-    3d/compare-eax-and  0/imm32
-    7f/jump-if->  $write-int32-decimal:read-loop/disp8
-$write-int32-decimal:read-break:
-    # if (n < 0) push('-')
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       0/imm32           # compare *(ebp+12)
-    7d/jump-if->=  $write-int32-decimal:write/disp8
-$write-int32-decimal:push-negative:
-    68/push  0x2d/imm32/-
-$write-int32-decimal:write:
-    # edi = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
-    # var w/edx: int = out->write
-    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           2/r32/edx   .               .                 # copy *edi to edx
-    # var curr/ecx: (addr byte) = &out->data[out->write]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  2/index/edx   .           1/r32/ecx   0xc/disp8       .                 # copy ebx+edx+12 to ecx
-    # var max/ebx: (addr byte) = &out->data[out->size]
-    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           3/r32/ebx   8/disp8         .                 # copy *(edi+8) to ebx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  3/index/ebx   .           3/r32/ebx   0xc/disp8       .                 # copy edi+ebx+12 to ebx
-$write-int32-decimal:write-loop:
-    # pop into eax
-    58/pop-to-eax
-    # if (eax == sentinel) break
-    3d/compare-eax-and  0/imm32/sentinel
-    74/jump-if-=  $write-int32-decimal:write-break/disp8
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
-    73/jump-if-addr>=  $write-int32-decimal:abort/disp8
-$write-int32-decimal:write-char:
-    # *curr = AL
-    88/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy AL to byte at *ecx
-    # ++curr
-    41/increment-ecx
-    # ++w
-    42/increment-edx
-    eb/jump  $write-int32-decimal:write-loop/disp8
-$write-int32-decimal:write-break:
-    # out->write = w
-    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           2/r32/edx   .               .                 # copy edx to *edi
-$write-int32-decimal:end:
-    # . restore registers
-    5f/pop-to-edi
-    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
-
-$write-int32-decimal:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "write-int32-decimal: stream out of space" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-test-write-int32-decimal:
-    # - check that a single-digit number converts correctly
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write-int32-decimal(_test-stream, 9)
-    # . . push args
-    68/push  9/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-int32-decimal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "9", msg)
-    # . . push args
-    68/push  "F - test-write-int32-decimal"/imm32
-    68/push  "9"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-test-write-int32-decimal-zero:
-    # - check that 0 converts correctly
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write-int32-decimal(_test-stream, 0)
-    # . . push args
-    68/push  0/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-int32-decimal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "0", msg)
-    # . . push args
-    68/push  "F - test-write-int32-decimal-zero"/imm32
-    68/push  "0"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-test-write-int32-decimal-multiple-digits:
-    # - check that a multi-digit number converts correctly
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write-int32-decimal(_test-stream, 10)
-    # . . push args
-    68/push  0xa/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-int32-decimal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "10", msg)
-    # . . push args
-    68/push  "F - test-write-int32-decimal-multiple-digits"/imm32
-    68/push  "10"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-test-write-int32-decimal-negative:
-    # - check that a negative single-digit number converts correctly
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write-int32-decimal(_test-stream, -9)
-    # . . push args
-    68/push  -9/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-int32-decimal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # dump _test-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, _test-stream)
-#?     # . . push args
-#?     68/push  _test-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
-#?     # }}}
-    # check-stream-equal(_test-stream, "-9", msg)
-    # . . push args
-    68/push  "F - test-write-int32-decimal-negative"/imm32
-    68/push  "-9"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-test-write-int32-decimal-negative-multiple-digits:
-    # - check that a multi-digit number converts correctly
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # write-int32-decimal(_test-stream, -10)
-    # . . push args
-    68/push  -0xa/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write-int32-decimal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-stream-equal(_test-stream, "-10", msg)
-    # . . push args
-    68/push  "F - test-write-int32-decimal-negative-multiple-digits"/imm32
-    68/push  "-10"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-is-decimal-digit?:  # c: grapheme -> result/eax: boolean
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    # ecx = c
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # result = false
-    b8/copy-to-eax  0/imm32/false
-    # return false if c < '0'
-    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x30/imm32        # compare ecx
-    7c/jump-if-<  $is-decimal-digit?:end/disp8
-    # return (c <= '9')
-    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x39/imm32        # compare ecx
-    7f/jump-if->  $is-decimal-digit?:end/disp8
-$is-decimal-digit?:true:
-    b8/copy-to-eax  1/imm32/true
-$is-decimal-digit?:end:
-    # . restore registers
-    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-is-decimal-digit-below-0:
-    # eax = is-decimal-digit?(0x2f)
-    # . . push args
-    68/push  0x2f/imm32
-    # . . call
-    e8/call  is-decimal-digit?/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-is-decimal-digit-below-0"/imm32
-    68/push  0/imm32/false
-    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
-    c3/return
-
-test-is-decimal-digit-0-to-9:
-    # eax = is-decimal-digit?(0x30)
-    # . . push args
-    68/push  0x30/imm32
-    # . . call
-    e8/call  is-decimal-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-decimal-digit-at-0"/imm32
-    68/push  1/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
-    # eax = is-decimal-digit?(0x39)
-    # . . push args
-    68/push  0x39/imm32
-    # . . call
-    e8/call  is-decimal-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-decimal-digit-at-9"/imm32
-    68/push  1/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
-    c3/return
-
-test-is-decimal-digit-above-9:
-    # eax = is-decimal-digit?(0x3a)
-    # . . push args
-    68/push  0x3a/imm32
-    # . . call
-    e8/call  is-decimal-digit?/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-is-decimal-digit-above-9"/imm32
-    68/push  0/imm32/false
-    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
-    c3/return
-
-to-decimal-digit:  # in: grapheme -> out/eax: int
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # eax = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
-$to-decimal-digit:check0:
-    # if (eax < '0') goto abort
-    3d/compare-eax-with  0x30/imm32/0
-    7c/jump-if-<  $to-decimal-digit:abort/disp8
-$to-decimal-digit:check1:
-    # if (eax > '9') goto abort
-    3d/compare-eax-with  0x39/imm32/f
-    7f/jump-if->  $to-decimal-digit:abort/disp8
-$to-decimal-digit:digit:
-    # return eax - '0'
-    2d/subtract-from-eax  0x30/imm32/0
-$to-decimal-digit:end:
-    # . epilogue
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-$to-decimal-digit:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "to-decimal-digit: not a digit character" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-# . . vim:nowrap:textwidth=0
diff --git a/baremetal/127next-word.subx b/baremetal/127next-word.subx
deleted file mode 100644
index 5af326d4..00000000
--- a/baremetal/127next-word.subx
+++ /dev/null
@@ -1,362 +0,0 @@
-# Tokenize by whitespace.
-
-== 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
-
-# (re)compute the bounds of the next word in the line (surrounded by whitespace,
-# treating '#' comments as a single word)
-# return empty string on reaching end of file
-next-word:  # line: (addr stream byte), out: (addr slice)
-    # . 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
-    56/push-esi
-    57/push-edi
-    # esi = line
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # edi = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-    # skip-chars-matching-whitespace(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  skip-chars-matching-whitespace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$next-word:check0:
-    # if (line->read >= line->write) clear out and return
-    # . eax = line->read
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy *(esi+4) to eax
-    # . if (eax < line->write) goto next check
-    3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # compare eax with *esi
-    7c/jump-if-<  $next-word:check-for-comment/disp8
-    # . return out
-    c7          0/subop/copy        0/mod/direct    7/rm32/edi    .           .             .           .           .               0/imm32           # copy to *edi
-    c7          0/subop/copy        1/mod/*+disp8   7/rm32/edi    .           .             .           .           4/disp8         0/imm32           # copy to *(edi+4)
-    eb/jump  $next-word:end/disp8
-$next-word:check-for-comment:
-    # out->start = &line->data[line->read]
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   0xc/disp8       .                 # copy esi+ecx+12 to eax
-    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to *edi
-    # if (line->data[line->read] == '#') out->end = &line->data[line->write]), skip rest of stream and return
-    # . eax = line->data[line->read]
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
-    # . compare
-    3d/compare-eax-and  0x23/imm32/pound
-    75/jump-if-!=  $next-word:regular-word/disp8
-$next-word:comment:
-    # . out->end = &line->data[line->write]
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    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
-    89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
-    # . line->read = line->write
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(esi+4)
-    # . return
-    eb/jump  $next-word:end/disp8
-$next-word:regular-word:
-    # otherwise skip-chars-not-matching-whitespace(line)  # including trailing newline
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  skip-chars-not-matching-whitespace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # out->end = &line->data[line->read]
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   0xc/disp8       .                 # copy esi+ecx+12 to eax
-    89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
-$next-word:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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
-
-test-next-word:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # var 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
-    # write(_test-stream, "  ab")
-    # . . push args
-    68/push  "  ab"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # next-word(_test-stream, slice)
-    # . . push args
-    51/push-ecx
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(slice->start - _test-stream->data, 2, msg)
-    # . check-ints-equal(slice->start - _test-stream, 14, msg)
-    # . . push args
-    68/push  "F - test-next-word: start"/imm32
-    68/push  0xe/imm32
-    # . . push slice->start - _test-stream
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
-    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
-    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
-    # check-ints-equal(slice->end - _test-stream->data, 4, msg)
-    # . check-ints-equal(slice->end - _test-stream, 16, msg)
-    # . . push args
-    68/push  "F - test-next-word: end"/imm32
-    68/push  0x10/imm32
-    # . . push slice->end - _test-stream
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
-    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
-    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-next-word-returns-whole-comment:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # var 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
-    # write(_test-stream, "  # a")
-    # . . push args
-    68/push  "  # a"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # next-word(_test-stream, slice)
-    # . . push args
-    51/push-ecx
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(slice->start - _test-stream->data, 2, msg)
-    # . check-ints-equal(slice->start - _test-stream, 14, msg)
-    # . . push args
-    68/push  "F - test-next-word-returns-whole-comment: start"/imm32
-    68/push  0xe/imm32
-    # . . push slice->start - _test-stream
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
-    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
-    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
-    # check-ints-equal(slice->end - _test-stream->data, 5, msg)
-    # . check-ints-equal(slice->end - _test-stream, 17, msg)
-    # . . push args
-    68/push  "F - test-next-word-returns-whole-comment: end"/imm32
-    68/push  0x11/imm32
-    # . . push slice->end - _test-stream
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
-    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
-    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-next-word-returns-empty-string-on-eof:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # var 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
-    # write nothing to _test-stream
-    # next-word(_test-stream, slice)
-    # . . push args
-    51/push-ecx
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(slice->end - slice->start, 0, msg)
-    # . . push args
-    68/push  "F - test-next-word-returns-empty-string-on-eof"/imm32
-    68/push  0/imm32
-    # . . push slice->end - slice->start
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
-    2b/subtract                     0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract *ecx from eax
-    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-next-word-returns-empty-string-on-newline:
-    # . prologue
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-stream)
-    # . . push args
-    68/push  _test-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
-    # var 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
-    # write some whitespace and a newline
-    # . . push args
-    68/push  "  \n"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # next-word(_test-stream, slice)
-    # . . push args
-    51/push-ecx
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(slice->end - slice->start, 0, msg)
-    # . . push args
-    68/push  "F - test-next-word-returns-empty-string-on-newline"/imm32
-    68/push  0/imm32
-    # . . push slice->end - slice->start
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
-    2b/subtract                     0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract *ecx from eax
-    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
-
-# (re)compute the bounds of the next word in the line (separated by whitespace)
-# return empty string on reaching end of file
-next-raw-word:  # line: (addr stream byte), out: (addr slice)
-    # . 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
-    56/push-esi
-    57/push-edi
-    # esi = line
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # edi = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-    # skip-chars-matching-whitespace(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  skip-chars-matching-whitespace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$next-raw-word:check0:
-    # if (line->read >= line->write) clear out and return
-    # . eax = line->read
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy *(esi+4) to eax
-    # . if (eax < line->write) goto next check
-    3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # compare eax with *esi
-    7c/jump-if-<  $next-raw-word:word-exists/disp8
-    # . return out
-    c7          0/subop/copy        0/mod/direct    7/rm32/edi    .           .             .           .           .               0/imm32           # copy to *edi
-    c7          0/subop/copy        1/mod/*+disp8   7/rm32/edi    .           .             .           .           4/disp8         0/imm32           # copy to *(edi+4)
-    eb/jump  $next-raw-word:end/disp8
-$next-raw-word:word-exists:
-    # out->start = &line->data[line->read]
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   0xc/disp8       .                 # copy esi+ecx+12 to eax
-    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to *edi
-    # skip-chars-not-matching-whitespace(line)  # including trailing newline
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  skip-chars-not-matching-whitespace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # out->end = &line->data[line->read]
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   0xc/disp8       .                 # copy esi+ecx+12 to eax
-    89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
-$next-raw-word:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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
diff --git a/baremetal/301array-equal.subx b/baremetal/301array-equal.subx
deleted file mode 100644
index fd46ccb6..00000000
--- a/baremetal/301array-equal.subx
+++ /dev/null
@@ -1,432 +0,0 @@
-# Comparing arrays of numbers.
-
-== code
-
-array-equal?:  # a: (addr array int), b: (addr array int) -> result/eax: boolean
-    # pseudocode:
-    #   asize = a->size
-    #   if (asize != b->size) return false
-    #   i = 0
-    #   curra = a->data
-    #   currb = b->data
-    #   while i < asize
-    #     i1 = *curra
-    #     i2 = *currb
-    #     if (c1 != c2) return false
-    #     i+=4, curra+=4, currb+=4
-    #   return true
-    #
-    # registers:
-    #   i: ecx
-    #   asize: edx
-    #   curra: esi
-    #   currb: edi
-    #   i1: eax
-    #   i2: ebx
-    #
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    56/push-esi
-    57/push-edi
-    # esi = a
-    8b/-> *(ebp+8) 6/r32/esi
-    # edi = b
-    8b/-> *(ebp+0xc) 7/r32/edi
-    # var asize/edx: int = a->size
-    8b/-> *esi 2/r32/edx
-$array-equal?:sizes:
-    # if (asize != b->size) return false
-    39/compare *edi 2/r32/edx
-    75/jump-if-!= $array-equal?:false/disp8
-    # var curra/esi: (addr byte) = a->data
-    81 0/subop/add %esi 4/imm32
-    # var currb/edi: (addr byte) = b->data
-    81 0/subop/add %edi 4/imm32
-    # var i/ecx: int = 0
-    31/xor-with %ecx 1/r32/ecx
-    # var vala/eax: int
-    # var valb/ebx: int
-$array-equal?:loop:
-    # if (i >= asize) return true
-    39/compare %ecx 2/r32/edx
-    7d/jump-if->= $array-equal?:true/disp8
-    # var vala/eax: int = *curra
-    8b/-> *esi 0/r32/eax
-    # var valb/ebx: int = *currb
-    8b/-> *edi 3/r32/ebx
-    # if (vala != valb) return false
-    39/compare %eax 3/r32/ebx
-    75/jump-if-!= $array-equal?:false/disp8
-    # i += 4
-    81 0/subop/add %ecx 4/imm32
-    # currs += 4
-    81 0/subop/add %esi 4/imm32
-    # currb += 4
-    81 0/subop/add %edi 4/imm32
-    eb/jump $array-equal?:loop/disp8
-$array-equal?:true:
-    b8/copy-to-eax 1/imm32
-    eb/jump $array-equal?:end/disp8
-$array-equal?:false:
-    b8/copy-to-eax 0/imm32
-$array-equal?:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    5b/pop-to-ebx
-    5a/pop-to-edx
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-compare-empty-with-empty-array:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var ecx: (array _) = []
-    68/push 0/imm32/size
-    89/<- %ecx 4/r32/esp
-    # var edx: (array _) = []
-    68/push 0/imm32/size
-    89/<- %edx 4/r32/esp
-    #
-    (array-equal? %ecx %edx)  # => eax
-    (check-ints-equal %eax 1 "F - test-compare-empty-with-empty-array")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-compare-empty-with-non-empty-array:  # also checks size-mismatch code path
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var ecx: (array int) = [1]
-    68/push 1/imm32
-    68/push 4/imm32/size
-    89/<- %ecx 4/r32/esp
-    # var edx: (array int) = []
-    68/push 0/imm32/size
-    89/<- %edx 4/r32/esp
-    #
-    (array-equal? %ecx %edx)  # => eax
-    (check-ints-equal %eax 0 "F - test-compare-empty-with-non-empty-array")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-compare-equal-arrays:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var ecx: (array int) = [1, 2, 3]
-    68/push 3/imm32
-    68/push 2/imm32
-    68/push 1/imm32
-    68/push 0xc/imm32/size
-    89/<- %ecx 4/r32/esp
-    # var edx: (array int) = [1, 2, 3]
-    68/push 3/imm32
-    68/push 2/imm32
-    68/push 1/imm32
-    68/push 0xc/imm32/size
-    89/<- %edx 4/r32/esp
-    #
-    (array-equal? %ecx %edx)  # => eax
-    (check-ints-equal %eax 1 "F - test-compare-equal-arrays")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-compare-inequal-arrays-equal-sizes:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var ecx: (array int) = [1, 4, 3]
-    68/push 3/imm32
-    68/push 4/imm32
-    68/push 1/imm32
-    68/push 0xc/imm32/size
-    89/<- %ecx 4/r32/esp
-    # var edx: (array int) = [1, 2, 3]
-    68/push 3/imm32
-    68/push 2/imm32
-    68/push 1/imm32
-    68/push 0xc/imm32/size
-    89/<- %edx 4/r32/esp
-    #
-    (array-equal? %ecx %edx)  # => eax
-    (check-ints-equal %eax 0 "F - test-compare-inequal-arrays-equal-sizes")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-_parse-array-of-ints:  # ad: (addr allocation-descriptor), s: (addr array byte), out: (addr handle array int)
-    # pseudocode
-    #   end = &s->data[s->size]
-    #   curr = s->data
-    #   size = 0
-    #   while true
-    #     if (curr >= end) break
-    #     curr = skip-chars-matching-in-slice(curr, end, ' ')
-    #     if (curr >= end) break
-    #     curr = skip-chars-not-matching-in-slice(curr, end, ' ')
-    #     ++size
-    #   allocate-array(ad, size*4, out)
-    #   var slice: slice = {s->data, 0}
-    #   curr = lookup(out)->data
-    #   while true
-    #     if (slice->start >= end) break
-    #     slice->start = skip-chars-matching-in-slice(slice->start, end, ' ')
-    #     if (slice->start >= end) break
-    #     slice->end = skip-chars-not-matching-in-slice(slice->start, end, ' ')
-    #     *curr = parse-hex-int-from-slice(slice)
-    #     curr += 4
-    #     slice->start = slice->end
-    #   return result
-    #
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    56/push-esi
-    57/push-edi
-    # esi = s
-    8b/-> *(ebp+0xc) 6/r32/esi
-    # var curr/ecx: (addr byte) = s->data
-    8d/copy-address *(esi+4) 1/r32/ecx
-    # var end/edx: (addr byte) = &s->data[s->size]
-    # . edx = s->size
-    8b/-> *esi 2/r32/edx
-    # . edx += curr
-    01/add-to %edx 1/r32/ecx
-    # var size/ebx: int = 0
-    31/xor-with %ebx 3/r32/ebx
-$_parse-array-of-ints:loop1:
-    # if (curr >= end) break
-    39/compare %ecx 2/r32/edx
-    73/jump-if-addr>= $_parse-array-of-ints:break1/disp8
-    # curr = skip-chars-matching-in-slice(curr, end, ' ')
-    (skip-chars-matching-in-slice %ecx %edx 0x20)  # => eax
-    89/<- %ecx 0/r32/eax
-    # if (curr >= end) break
-    39/compare %ecx 2/r32/edx
-    73/jump-if-addr>= $_parse-array-of-ints:break1/disp8
-    # curr = skip-chars-not-matching-in-slice(curr, end, ' ')
-    (skip-chars-not-matching-in-slice %ecx %edx 0x20)  # => eax
-    89/<- %ecx 0/r32/eax
-    # size += 4
-    81 0/subop/add %ebx 4/imm32
-    eb/jump $_parse-array-of-ints:loop1/disp8
-$_parse-array-of-ints:break1:
-    (allocate-array *(ebp+8) %ebx *(ebp+0x10))
-$_parse-array-of-ints:pass2:
-    # var slice/edi: slice = {s->data, 0}
-    68/push 0/imm32/end
-    8d/copy-address *(esi+4) 7/r32/edi
-    57/push-edi
-    89/<- %edi 4/r32/esp
-    # curr = lookup(out)->data
-    8b/-> *(ebp+0x10) 0/r32/eax
-    (lookup *eax *(eax+4))  # => eax
-    8d/copy-address *(eax+4) 1/r32/ecx
-$_parse-array-of-ints:loop2:
-    # if (slice->start >= end) break
-    39/compare *edi 2/r32/edx
-    73/jump-if-addr>= $_parse-array-of-ints:end/disp8
-    # slice->start = skip-chars-matching-in-slice(slice->start, end, ' ')
-    (skip-chars-matching-in-slice *edi %edx 0x20)  # => eax
-    89/<- *edi 0/r32/eax
-    # if (slice->start >= end) break
-    39/compare *edi 2/r32/edx
-    73/jump-if-addr>= $_parse-array-of-ints:end/disp8
-    # slice->end = skip-chars-not-matching-in-slice(slice->start, end, ' ')
-    (skip-chars-not-matching-in-slice *edi %edx 0x20)  # => eax
-    89/<- *(edi+4) 0/r32/eax
-    # *curr = parse-hex-int-from-slice(slice)
-    (parse-hex-int-from-slice %edi)
-    89/<- *ecx 0/r32/eax
-    # curr += 4
-    81 0/subop/add %ecx 4/imm32
-    # slice->start = slice->end
-    8b/-> *(edi+4) 0/r32/eax
-    89/<- *edi 0/r32/eax
-    eb/jump $_parse-array-of-ints:loop2/disp8
-$_parse-array-of-ints:end:
-    # . reclaim locals
-    81 0/subop/add %esp 8/imm32
-    # . 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/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-array-of-ints:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var h/esi: (handle array int)
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %esi 4/r32/esp
-    # var ecx: (array int) = [1, 2, 3]
-    68/push 3/imm32
-    68/push 2/imm32
-    68/push 1/imm32
-    68/push 0xc/imm32/size
-    89/<- %ecx 4/r32/esp
-    #
-    (_parse-array-of-ints Heap "1 2 3" %esi)
-    (lookup *esi *(esi+4))  # => eax
-    (array-equal? %ecx %eax)  # => eax
-    (check-ints-equal %eax 1 "F - test-parse-array-of-ints")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-array-of-ints-empty:
-    # - empty string = empty array
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var h/esi: handle
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %esi 4/r32/esp
-    #
-    (_parse-array-of-ints Heap "" %esi)
-    (lookup *esi *(esi+4))  # => eax
-    (check-ints-equal *eax 0 "F - test-parse-array-of-ints-empty")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-array-of-ints-just-whitespace:
-    # - just whitespace = empty array
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var h/esi: handle
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %esi 4/r32/esp
-    #
-    (_parse-array-of-ints Heap Space %esi)
-    (lookup *esi *(esi+4))  # => eax
-    (check-ints-equal *eax 0 "F - test-parse-array-of-ints-just-whitespace")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-array-of-ints-extra-whitespace:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var h/esi: handle
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %esi 4/r32/esp
-    # var ecx: (array int) = [1, 2, 3]
-    68/push 3/imm32
-    68/push 2/imm32
-    68/push 1/imm32
-    68/push 0xc/imm32/size
-    89/<- %ecx 4/r32/esp
-    #
-    (_parse-array-of-ints Heap " 1 2  3  " %esi)
-    (lookup *esi *(esi+4))  # => eax
-    (array-equal? %ecx %eax)  # => eax
-    (check-ints-equal %eax 1 "F - test-parse-array-of-ints-extra-whitespace")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-parse-array-of-ints:  # s: (addr array byte), out: (addr handle array int)
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    (_parse-array-of-ints Heap *(ebp+8) *(ebp+0xc))
-$parse-array-of-ints:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-# helper for later tests
-# compare an array with a string representation of an array literal
-check-array-equal:  # a: (addr array int), expected: (addr string), msg: (addr string)
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    56/push-esi
-    # var h/esi: handle
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %esi 4/r32/esp
-    # var b/eax: (addr array int) = parse-array-of-ints(Heap, expected)
-    (parse-array-of-ints *(ebp+0xc) %esi)
-    (lookup *esi *(esi+4))  # => eax
-    #
-    (array-equal? *(ebp+8) %eax)
-    (check-ints-equal %eax 1 *(ebp+0x10))
-$check-array-equal:end:
-    # . restore registers
-    5e/pop-to-esi
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-check-array-equal:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var ecx: (array int) = [1, 2, 3]
-    68/push 3/imm32
-    68/push 2/imm32
-    68/push 1/imm32
-    68/push 0xc/imm32/size
-    89/<- %ecx 4/r32/esp
-    #
-    (check-array-equal %ecx "1 2 3" "F - test-check-array-equal")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-== data
-
-# length-prefixed string containing just a single space
-Space:  # (array byte)
-    # size: int
-    1/imm32
-    # data
-    20/space
diff --git a/baremetal/302stack_allocate.subx b/baremetal/302stack_allocate.subx
deleted file mode 100644
index cd51d5ff..00000000
--- a/baremetal/302stack_allocate.subx
+++ /dev/null
@@ -1,61 +0,0 @@
-# A function which pushes n zeros on the stack.
-# Really only intended to be called from code generated by mu.subx (for array
-# vars on the stack).
-
-== code
-
-#? Entry:
-#?     # . prologue
-#?     89/<- %ebp 4/r32/esp
-#?     #
-#?     68/push 0xfcfdfeff/imm32
-#?     b8/copy-to-eax 0x34353637/imm32
-#? $dump-stack0:
-#?     (push-n-zero-bytes 4)
-#?     68/push 0x20/imm32
-#? $dump-stack9:
-#?     b8/copy-to-eax 1/imm32/exit
-#?     cd/syscall 0x80/imm8
-
-# This is not a regular function, so it won't be idiomatic.
-# Registers must be properly restored.
-# Registers can be spilled, but that modifies the stack and needs to be
-# cleaned up.
-
-# Overhead:
-#   62 + n*6 instructions to push n bytes.
-# If we just emitted code to push n zeroes, it would be:
-#   5 bytes for 4 zero bytes, so 1.25 bytes per zero. And that's not even
-#   instructions.
-# But on the other hand it would destroy the instruction cache, where this
-# approach requires 15 instructions, fixed.
-
-# n must be positive
-push-n-zero-bytes:  # n: int
-$push-n-zero-bytes:prologue:
-    89/<- *Push-n-zero-bytes-ebp 5/r32/ebp  # spill ebp without affecting stack
-    89/<- %ebp 4/r32/esp
-$push-n-zero-bytes:copy-ra:
-    # -- esp = ebp
-    89/<- *Push-n-zero-bytes-eax 0/r32/eax
-    8b/-> *esp 0/r32/eax
-    2b/subtract *(ebp+4) 4/r32/esp
-    # -- esp+n = ebp
-    89/<- *esp 0/r32/eax
-    8b/-> *Push-n-zero-bytes-eax 0/r32/eax
-$push-n-zero-bytes:bulk-cleaning:
-    89/<- *Push-n-zero-bytes-esp 4/r32/esp
-    81 0/subop/add *Push-n-zero-bytes-esp 4/imm32
-    81 0/subop/add *(ebp+4) 4/imm32
-    (zero-out *Push-n-zero-bytes-esp *(ebp+4))  # n+4
-$push-n-zero-bytes:epilogue:
-    8b/-> *Push-n-zero-bytes-ebp 5/r32/ebp  # restore spill
-    c3/return
-
-== data
-Push-n-zero-bytes-ebp:  # (addr int)
-  0/imm32
-Push-n-zero-bytes-esp:  # (addr int)
-  0/imm32
-Push-n-zero-bytes-eax:
-  0/imm32
diff --git a/baremetal/308allocate-array.subx b/baremetal/308allocate-array.subx
deleted file mode 100644
index 5cc0fe1b..00000000
--- a/baremetal/308allocate-array.subx
+++ /dev/null
@@ -1,25 +0,0 @@
-# 2-arg version of allocate-array.
-# Really only intended to be called from code generated by mu.subx.
-
-== code
-
-allocate-array2:  # ad: (addr allocation-descriptor), array-len: int, elem-size: int, out: (addr handle array _)
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    52/push-edx
-    #
-    8b/-> *(ebp+0xc) 0/r32/eax
-    f7 4/subop/multiply-into-edx-eax *(ebp+0x10)
-    # TODO: check edx for overflow
-    (allocate-array *(ebp+8) %eax *(ebp+0x14))
-$allocate-array2:end:
-    # . restore registers
-    5a/pop-to-edx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
diff --git a/baremetal/309stream.subx b/baremetal/309stream.subx
deleted file mode 100644
index 56b19272..00000000
--- a/baremetal/309stream.subx
+++ /dev/null
@@ -1,214 +0,0 @@
-# Some unsafe methods not intended to be used directly in SubX, only through
-# Mu after proper type-checking.
-
-== code
-
-stream-empty?:  # s: (addr stream _) -> result/eax: boolean
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    56/push-esi
-    # result = false
-    b8/copy-to-eax 0/imm32/false
-    # esi = s
-    8b/-> *(ebp+8) 6/r32/esi
-    # return s->read >= s->write
-    8b/-> *esi 1/r32/ecx
-    39/compare-with *(esi+4) 1/r32/ecx
-    0f 9d/set-if->= %al
-$stream-empty?:end:
-    # . restore registers
-    5e/pop-to-esi
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-stream-full?:  # s: (addr stream _) -> result/eax: boolean
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    56/push-esi
-    # result = false
-    b8/copy-to-eax 0/imm32/false
-    # esi = s
-    8b/-> *(ebp+8) 6/r32/esi
-    # return s->write >= s->size
-    8b/-> *(esi+8) 1/r32/ecx
-    39/compare-with *esi 1/r32/ecx
-    0f 9d/set-if->= %al
-$stream-full?:end:
-    # . restore registers
-    5e/pop-to-esi
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-write-to-stream:  # s: (addr stream _), in: (addr byte), n: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    57/push-edi
-    # edi = s
-    8b/-> *(ebp+8) 7/r32/edi
-    # var swrite/edx: int = s->write
-    8b/-> *edi 2/r32/edx
-    # if (swrite + n > s->size) abort
-    8b/-> *(ebp+0x10) 1/r32/ecx
-    01/add-to %ecx 2/r32/edx
-    3b/compare 1/r32/ecx *(edi+8)
-    0f 8f/jump-if-> $write-to-stream:abort/disp32
-    # var out/edx: (addr byte) = s->data + s->write
-    8d/copy-address *(edi+edx+0xc) 2/r32/edx
-    # var outend/ebx: (addr byte) = out + n
-    8b/-> *(ebp+0x10) 3/r32/ebx
-    8d/copy-address *(edx+ebx) 3/r32/ebx
-    # eax = in
-    8b/-> *(ebp+0xc) 0/r32/eax
-    # var inend/ecx: (addr byte) = in + n
-    8b/-> *(ebp+0x10) 1/r32/ecx
-    8d/copy-address *(eax+ecx) 1/r32/ecx
-    #
-    (_append-4  %edx %ebx  %eax %ecx)  # => eax
-    # s->write += n
-    8b/-> *(ebp+0x10) 1/r32/ecx
-    01/add-to *edi 1/r32/ecx
-$write-to-stream:end:
-    # . restore registers
-    5f/pop-to-edi
-    5b/pop-to-ebx
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-$write-to-stream:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "write-to-stream: stream full" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-read-from-stream:  # s: (addr stream _), out: (addr byte), n: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    56/push-esi
-    # esi = s
-    8b/-> *(ebp+8) 6/r32/esi
-    # var sread/edx: int = s->read
-    8b/-> *(esi+4) 2/r32/edx
-    # if (sread + n > s->write) abort
-    8b/-> *(ebp+0x10) 1/r32/ecx
-    01/add-to %ecx 2/r32/edx
-    3b/compare 1/r32/ecx *esi
-    0f 8f/jump-if-> $read-from-stream:abort/disp32
-    # var in/edx: (addr byte) = s->data + s->read
-    8d/copy-address *(esi+edx+0xc) 2/r32/edx
-    # var inend/ebx: (addr byte) = in + n
-    8b/-> *(ebp+0x10) 3/r32/ebx
-    8d/copy-address *(edx+ebx) 3/r32/ebx
-    # eax = out
-    8b/-> *(ebp+0xc) 0/r32/eax
-    # var outend/ecx: (addr byte) = out + n
-    8b/-> *(ebp+0x10) 1/r32/ecx
-    8d/copy-address *(eax+ecx) 1/r32/ecx
-    #
-    (_append-4  %eax %ecx  %edx %ebx)  # => eax
-    # s->read += n
-    8b/-> *(ebp+0x10) 1/r32/ecx
-    01/add-to *(esi+4) 1/r32/ecx
-$read-from-stream:end:
-    # . restore registers
-    5e/pop-to-esi
-    5b/pop-to-ebx
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-$read-from-stream:abort:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "read-from-stream: stream empty" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-
-stream-first:  # s: (addr stream byte) -> result/eax: byte
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    56/push-esi
-    # result = false
-    b8/copy-to-eax 0/imm32
-    # esi = s
-    8b/-> *(ebp+8) 6/r32/esi
-    # var idx/ecx: int = s->read
-    8b/-> *(esi+4) 1/r32/ecx
-    # if idx >= s->write return 0
-    3b/compare-with 1/r32/ecx *esi
-    7d/jump-if->= $stream-first:end/disp8
-    # result = s->data[idx]
-    8a/byte-> *(esi+ecx+0xc) 0/r32/AL
-$stream-first:end:
-    # . restore registers
-    5e/pop-to-esi
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-stream-final:  # s: (addr stream byte) -> result/eax: byte
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    56/push-esi
-    # result = false
-    b8/copy-to-eax 0/imm32
-    # esi = s
-    8b/-> *(ebp+8) 6/r32/esi
-    # var max/ecx: int = s->write
-    8b/-> *esi 1/r32/ecx
-    # if s->read >= max return 0
-    39/compare-with *(esi+4) 1/r32/ecx
-    7d/jump-if->= $stream-final:end/disp8
-    # var idx/ecx: int = max - 1
-    49/decrement-ecx
-    # result = s->data[idx]
-    8a/byte-> *(esi+ecx+0xc) 0/r32/AL
-$stream-final:end:
-    # . restore registers
-    5e/pop-to-esi
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
diff --git a/baremetal/310copy-bytes.subx b/baremetal/310copy-bytes.subx
deleted file mode 100644
index 2586a53f..00000000
--- a/baremetal/310copy-bytes.subx
+++ /dev/null
@@ -1,157 +0,0 @@
-# Some helpers for copying non-overlapping regions of memory.
-# Really only intended to be called from code generated by mu.subx.
-
-== code
-
-copy-bytes:  # src: (addr byte), dest: (addr byte), size: int
-    # pseudocode:
-    #   curr-src/esi = src
-    #   curr-dest/edi = dest
-    #   i/ecx = 0
-    #   while true
-    #     if (i >= size) break
-    #     *curr-dest = *curr-src
-    #     ++curr-src
-    #     ++curr-dest
-    #     ++i
-    #
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    56/push-esi
-    57/push-edi
-    # curr-src/esi = src
-    8b/-> *(ebp+8) 6/r32/esi
-    # curr-dest/edi = dest
-    8b/-> *(ebp+0xc) 7/r32/edi
-    # var i/ecx: int = 0
-    b9/copy-to-ecx 0/imm32
-    # edx = size
-    8b/-> *(ebp+0x10) 2/r32/edx
-    {
-      # if (i >= size) break
-      39/compare %ecx 2/r32/edx
-      7d/jump-if->=  break/disp8
-      # *curr-dest = *curr-src
-      8a/byte-> *esi 0/r32/AL
-      88/byte<- *edi 0/r32/AL
-      # update
-      46/increment-esi
-      47/increment-edi
-      41/increment-ecx
-      eb/jump loop/disp8
-    }
-$copy-bytes:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-stream-to-array:  # in: (addr stream _), out: (addr handle array _)
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    56/push-esi
-    # esi = s
-    8b/-> *(ebp+8) 6/r32/esi
-    # var len/ecx: int = s->write - s->read
-    8b/-> *esi 1/r32/ecx
-    2b/subtract *(esi+4) 1/r32/ecx
-    # allocate
-    (allocate-array Heap %ecx *(ebp+0xc))
-    # var in/edx: (addr byte) = s->data + s->read
-    8b/-> *(esi+4) 2/r32/edx
-    8d/copy-address *(esi+edx+0xc) 2/r32/edx
-    # var dest/eax: (addr byte) = data for out
-    8b/-> *(ebp+0xc) 0/r32/eax
-    (lookup *eax *(eax+4))  # => eax
-    8d/copy-address *(eax+4) 0/r32/eax
-    #
-    (copy-bytes %edx %eax %ecx)
-$stream-to-array:end:
-    # . restore registers
-    5e/pop-to-esi
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-stream-to-array:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # setup
-    (clear-stream _test-input-stream)
-    (write _test-input-stream "abc")
-    # skip something
-    (read-byte _test-input-stream)  # => eax
-    # var out/ecx: (handle array byte)
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %ecx 4/r32/esp
-    #
-    (stream-to-array _test-input-stream %ecx)
-    (lookup *ecx *(ecx+4))  # => eax
-    (check-strings-equal %eax "bc")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-# like stream-to-array but ignore surrounding quotes
-# we might do other stuff here later
-unquote-stream-to-array:  # in: (addr stream _), out: (addr handle array _)
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    56/push-esi
-    # esi = s
-    8b/-> *(ebp+8) 6/r32/esi
-    # var len/ecx: int = s->write - s->read - 2
-    8b/-> *esi 1/r32/ecx
-    2b/subtract *(esi+4) 1/r32/ecx
-    81 7/subop/compare %ecx 2/imm32
-    7c/jump-if-< $unquote-stream-to-array:end/disp8
-    81 5/subop/subtract %ecx 2/imm32
-    # allocate
-    (allocate-array Heap %ecx *(ebp+0xc))
-    # var in/edx: (addr byte) = s->data + s->read + 1
-    8b/-> *(esi+4) 2/r32/edx
-    8d/copy-address *(esi+edx+0xd) 2/r32/edx  # Stream-data + 1
-    # var dest/eax: (addr byte) = data for out
-    8b/-> *(ebp+0xc) 0/r32/eax
-    (lookup *eax *(eax+4))  # => eax
-    8d/copy-address *(eax+4) 0/r32/eax
-    #
-    (copy-bytes %edx %eax %ecx)
-$unquote-stream-to-array:end:
-    # . restore registers
-    5e/pop-to-esi
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
diff --git a/baremetal/311decimal-int.subx b/baremetal/311decimal-int.subx
deleted file mode 100644
index d0d8dde7..00000000
--- a/baremetal/311decimal-int.subx
+++ /dev/null
@@ -1,633 +0,0 @@
-# Helpers for decimal ints.
-
-# if slice doesn't contain a decimal number, return 0
-parse-decimal-int-from-slice:  # in: (addr slice) -> out/eax: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    # ecx = in
-    8b/-> *(ebp+8) 1/r32/ecx
-    #
-    (parse-decimal-int-helper *ecx *(ecx+4))  # => eax
-$parse-decimal-int-from-slice:end:
-    # . restore registers
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-# if slice doesn't contain a decimal number, return 0
-parse-decimal-int:  # in: (addr array byte) -> result/eax: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    52/push-edx
-    # eax = in
-    8b/-> *(ebp+8) 0/r32/eax
-    # var start/ecx: (addr byte) = &in->data
-    8d/copy-address *(eax+4) 1/r32/ecx
-    # var end/edx: (addr byte) = &in->data[in->size]
-    8b/-> *eax 2/r32/edx
-    8d/copy-address *(eax+edx+4) 2/r32/edx
-    #
-    (parse-decimal-int-helper %ecx %edx)  # => eax
-$parse-decimal-int:end:
-    # . restore registers
-    5a/pop-to-edx
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-parse-decimal-int-from-stream:  # in: (addr stream byte) -> result/eax: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    52/push-edx
-    # eax = in
-    8b/-> *(ebp+8) 0/r32/eax
-    # var start/ecx: (addr byte) = &in->data[in->read]
-    8b/-> *(eax+4) 1/r32/ecx
-    8d/copy-address *(eax+ecx+0xc) 1/r32/ecx
-    # var end/edx: (addr byte) = &in->data[in->write]
-    8b/-> *eax 2/r32/edx
-    8d/copy-address *(eax+edx+0xc) 2/r32/edx
-    # trim a trailing newline
-    {
-      # speculatively trim
-      4a/decrement-edx
-      # if it's a newline, break
-      8a/byte-> *edx 0/r32/eax
-      81 4/subop/and %eax 0xff/imm32
-      3d/compare-eax-and 0xa/imm32/newline
-      74/jump-if-= break/disp8
-      # not a newline, so restore it
-      42/increment-edx
-    }
-    #
-    (parse-decimal-int-helper %ecx %edx)  # => eax
-$parse-decimal-int-from-stream:end:
-    # . restore registers
-    5a/pop-to-edx
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-parse-decimal-int-helper:  # start: (addr byte), end: (addr byte) -> result/eax: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    56/push-esi
-    57/push-edi
-    # var curr/esi: (addr byte) = start
-    8b/-> *(ebp+8) 6/r32/esi
-    # edi = end
-    8b/-> *(ebp+0xc) 7/r32/edi
-    # var negate?/edx: boolean = false
-    ba/copy-to-edx 0/imm32/false
-    # if (*curr == '-') ++curr, negate = true
-    {
-$parse-decimal-int-helper:negative:
-      b8/copy-to-eax 0/imm32
-      8a/copy-byte *esi 0/r32/AL
-      3d/compare-eax-and 0x2d/imm32/-
-      75/jump-if-!= break/disp8
-      # . ++curr
-      46/increment-esi
-      # . negate = true
-      ba/copy-to-edx  1/imm32/true
-    }
-    # spill negate?
-    52/push-edx
-    # var result/eax: int = 0
-    b8/copy-to-eax 0/imm32
-    # var digit/ecx: int = 0
-    b9/copy-to-ecx 0/imm32
-    # const TEN/ebx: int = 10
-    bb/copy-to-ebx 0xa/imm32
-    {
-$parse-decimal-int-helper:loop:
-      # if (curr >= in->end) break
-      39/compare %esi 7/r32/edi
-      73/jump-if-addr>= break/disp8
-      # if !is-decimal-digit?(*curr) return 0
-      8a/copy-byte *esi 1/r32/CL
-      50/push-eax
-      (is-decimal-digit? %ecx)  # => eax
-      {
-        3d/compare-eax-and 0/imm32/false
-        75/jump-if-!= break/disp8
-        58/pop-to-eax
-        b8/copy-to-eax 0/imm32
-        eb/jump $parse-decimal-int-helper:negate/disp8
-      }
-      58/pop-to-eax
-      # digit = from-decimal-char(*curr)
-      81 5/subop/subtract %ecx 0x30/imm32/zero
-      # TODO: error checking
-      # result = result * 10 + digit
-      ba/copy-to-edx 0/imm32
-      f7 4/subop/multiply-into-edx-eax %ebx
-      # TODO: check edx for overflow
-      01/add %eax 1/r32/ecx
-      # ++curr
-      46/increment-esi
-      #
-      eb/jump loop/disp8
-    }
-$parse-decimal-int-helper:negate:
-    # if (negate?) result = -result
-    5a/pop-to-edx
-    {
-      81 7/subop/compare %edx 0/imm32/false
-      74/jump-if-= break/disp8
-      f7 3/subop/negate %eax
-    }
-$parse-decimal-int-helper:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    5b/pop-to-ebx
-    5a/pop-to-edx
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-decimal-int-from-slice-single-digit:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    # (eax..ecx) = "3"
-    b8/copy-to-eax "3"/imm32
-    8b/-> *eax 1/r32/ecx
-    8d/copy-address *(eax+ecx+4) 1/r32/ecx
-    05/add-to-eax 4/imm32
-    # var slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/<- %ecx 4/r32/esp
-    #
-    (parse-decimal-int-from-slice %ecx)  # => eax
-    (check-ints-equal %eax 3 "F - test-parse-decimal-int-from-slice-single-digit")
-$test-parse-decimal-int-helper-single-digit:end:
-    # . restore registers
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-decimal-int-from-slice-multi-digit:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    # (eax..ecx) = "34"
-    b8/copy-to-eax "34"/imm32
-    8b/-> *eax 1/r32/ecx
-    8d/copy-address *(eax+ecx+4) 1/r32/ecx
-    05/add-to-eax 4/imm32
-    # var slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/<- %ecx 4/r32/esp
-    #
-    (parse-decimal-int-from-slice %ecx)  # => eax
-    (check-ints-equal %eax 0x22 "F - test-parse-decimal-int-from-slice-multi-digit")  # 34 in hex
-$test-parse-decimal-int-helper-multi-digit:end:
-    # . restore registers
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-decimal-int-from-slice-zero:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    # (eax..ecx) = "00"
-    b8/copy-to-eax "00"/imm32
-    8b/-> *eax 1/r32/ecx
-    8d/copy-address *(eax+ecx+4) 1/r32/ecx
-    05/add-to-eax 4/imm32
-    # var slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/<- %ecx 4/r32/esp
-    #
-    (parse-decimal-int-from-slice %ecx)  # => eax
-    (check-ints-equal %eax 0 "F - test-parse-decimal-int-from-slice-zero")
-$test-parse-decimal-int-helper-zero:end:
-    # . restore registers
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-decimal-int-from-slice-negative:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    # (eax..ecx) = "-3"
-    b8/copy-to-eax "-3"/imm32
-    8b/-> *eax 1/r32/ecx
-    8d/copy-address *(eax+ecx+4) 1/r32/ecx
-    05/add-to-eax 4/imm32
-    # var slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/<- %ecx 4/r32/esp
-    #
-    (parse-decimal-int-from-slice %ecx)  # => eax
-    (check-ints-equal %eax -3 "F - test-parse-decimal-int-from-slice-negative")
-$test-parse-decimal-int-helper-negative:end:
-    # . restore registers
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-decimal-int-from-slice-multi-digit-negative:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    # (eax..ecx) = "-32"
-    b8/copy-to-eax "-32"/imm32
-    8b/-> *eax 1/r32/ecx
-    8d/copy-address *(eax+ecx+4) 1/r32/ecx
-    05/add-to-eax 4/imm32
-    # var slice/ecx: slice = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/<- %ecx 4/r32/esp
-    #
-    (parse-decimal-int-from-slice %ecx)  # => eax
-    (check-ints-equal %eax -0x20 "F - test-parse-decimal-int-from-slice-multi-digit-negative")  # -32 in hex
-$test-parse-decimal-int-helper-multi-digit-negative:end:
-    # . restore registers
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-decimal-size:  # n: int -> result/eax: int
-    # pseudocode:
-    #   edi = 0
-    #   eax = n
-    #   if eax < 0
-    #     ++edi  # for '-'
-    #     negate eax
-    #   while true
-    #     edx = 0
-    #     eax, edx = eax/10, eax%10
-    #     ++edi
-    #     if (eax == 0) break
-    #   eax = edi
-    #
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    51/push-ecx
-    52/push-edx
-    57/push-edi
-    # edi = 0
-    bf/copy-to-edi 0/imm32
-    # eax = n
-    8b/-> *(ebp+8) 0/r32/eax
-    # if (n < 0) negate n, increment edi
-    {
-      3d/compare-eax-with 0/imm32
-      7d/jump-if->= break/disp8
-      f7 3/subop/negate %eax
-      47/increment-edi
-    }
-    # const ten/ecx = 10
-    b9/copy-to-ecx  0xa/imm32
-    {
-      ba/copy-to-edx 0/imm32
-      f7 7/subop/idiv-edx-eax-by %ecx  # eax = edx:eax/10; edx = edx:eax%10
-      47/increment-edi
-      3d/compare-eax-and 0/imm32
-      75/jump-if-!= loop/disp8
-    }
-$decimal-size:end:
-    89/<- %eax 7/r32/edi
-    # . restore registers
-    5f/pop-to-edi
-    5a/pop-to-edx
-    59/pop-to-ecx
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-decimal-size-of-zero:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    (decimal-size 0)  # => eax
-    (check-ints-equal %eax 1 "F - test-decimal-size-of-zero")
-$test-decimal-size-of-zero:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-decimal-size-single-digit:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    (decimal-size 4)  # => eax
-    (check-ints-equal %eax 1 "F - test-decimal-size-single-digit")
-$test-decimal-size-single-digit:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-decimal-size-multi-digit:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    (decimal-size 0xa)  # => eax
-    (check-ints-equal %eax 2 "F - test-decimal-size-multi-digit")
-$test-decimal-size-multi-digit:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-decimal-size-single-digit-negative:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    (decimal-size -4)  # => eax
-    (check-ints-equal %eax 2 "F - test-decimal-size-single-digit-negative")
-$test-decimal-size-single-digit-negative:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-decimal-size-multi-digit-negative:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    (decimal-size -0xa)  # => eax
-    (check-ints-equal %eax 3 "F - test-decimal-size-multi-digit-negative")
-$test-decimal-size-multi-digit-negative:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-_parse-array-of-decimal-ints:  # ad: (addr allocation-descriptor), s: (addr array byte), out: (addr handle array int)
-    # pseudocode
-    #   end = &s->data[s->size]
-    #   curr = s->data
-    #   size = 0
-    #   while true
-    #     if (curr >= end) break
-    #     curr = skip-chars-matching-in-slice(curr, end, ' ')
-    #     if (curr >= end) break
-    #     curr = skip-chars-not-matching-in-slice(curr, end, ' ')
-    #     ++size
-    #   allocate-array(ad, size*4, out)
-    #   var slice: slice = {s->data, 0}
-    #   curr = lookup(out)->data
-    #   while true
-    #     if (slice->start >= end) break
-    #     slice->start = skip-chars-matching-in-slice(slice->start, end, ' ')
-    #     if (slice->start >= end) break
-    #     slice->end = skip-chars-not-matching-in-slice(slice->start, end, ' ')
-    #     *curr = parse-hex-int-from-slice(slice)
-    #     curr += 4
-    #     slice->start = slice->end
-    #   return result
-    #
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    53/push-ebx
-    56/push-esi
-    57/push-edi
-    # esi = s
-    8b/-> *(ebp+0xc) 6/r32/esi
-    # var curr/ecx: (addr byte) = s->data
-    8d/copy-address *(esi+4) 1/r32/ecx
-    # var end/edx: (addr byte) = &s->data[s->size]
-    # . edx = s->size
-    8b/-> *esi 2/r32/edx
-    # . edx += curr
-    01/add-to %edx 1/r32/ecx
-    # var size/ebx: int = 0
-    31/xor-with %ebx 3/r32/ebx
-$_parse-array-of-decimal-ints:loop1:
-    # if (curr >= end) break
-    39/compare %ecx 2/r32/edx
-    73/jump-if-addr>= $_parse-array-of-decimal-ints:break1/disp8
-    # curr = skip-chars-matching-in-slice(curr, end, ' ')
-    (skip-chars-matching-in-slice %ecx %edx 0x20)  # => eax
-    89/<- %ecx 0/r32/eax
-    # if (curr >= end) break
-    39/compare %ecx 2/r32/edx
-    73/jump-if-addr>= $_parse-array-of-decimal-ints:break1/disp8
-    # curr = skip-chars-not-matching-in-slice(curr, end, ' ')
-    (skip-chars-not-matching-in-slice %ecx %edx 0x20)  # => eax
-    89/<- %ecx 0/r32/eax
-    # size += 4
-    81 0/subop/add %ebx 4/imm32
-    eb/jump $_parse-array-of-decimal-ints:loop1/disp8
-$_parse-array-of-decimal-ints:break1:
-    (allocate-array *(ebp+8) %ebx *(ebp+0x10))
-$_parse-array-of-decimal-ints:pass2:
-    # var slice/edi: slice = {s->data, 0}
-    68/push 0/imm32/end
-    8d/copy-address *(esi+4) 7/r32/edi
-    57/push-edi
-    89/<- %edi 4/r32/esp
-    # curr = lookup(out)->data
-    8b/-> *(ebp+0x10) 0/r32/eax
-    (lookup *eax *(eax+4))  # => eax
-    8d/copy-address *(eax+4) 1/r32/ecx
-$_parse-array-of-decimal-ints:loop2:
-    # if (slice->start >= end) break
-    39/compare *edi 2/r32/edx
-    73/jump-if-addr>= $_parse-array-of-decimal-ints:end/disp8
-    # slice->start = skip-chars-matching-in-slice(slice->start, end, ' ')
-    (skip-chars-matching-in-slice *edi %edx 0x20)  # => eax
-    89/<- *edi 0/r32/eax
-    # if (slice->start >= end) break
-    39/compare *edi 2/r32/edx
-    73/jump-if-addr>= $_parse-array-of-decimal-ints:end/disp8
-    # slice->end = skip-chars-not-matching-in-slice(slice->start, end, ' ')
-    (skip-chars-not-matching-in-slice *edi %edx 0x20)  # => eax
-    89/<- *(edi+4) 0/r32/eax
-    # *curr = parse-hex-int-from-slice(slice)
-    (parse-decimal-int-from-slice %edi)
-    89/<- *ecx 0/r32/eax
-    # curr += 4
-    81 0/subop/add %ecx 4/imm32
-    # slice->start = slice->end
-    8b/-> *(edi+4) 0/r32/eax
-    89/<- *edi 0/r32/eax
-    eb/jump $_parse-array-of-decimal-ints:loop2/disp8
-$_parse-array-of-decimal-ints:end:
-    # . reclaim locals
-    81 0/subop/add %esp 8/imm32
-    # . 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/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-array-of-decimal-ints:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var h/esi: (handle array int)
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %esi 4/r32/esp
-    # var ecx: (array int) = [1, 2, 3]
-    68/push 3/imm32
-    68/push 2/imm32
-    68/push 1/imm32
-    68/push 0xc/imm32/size
-    89/<- %ecx 4/r32/esp
-    #
-    (_parse-array-of-decimal-ints Heap "1 2 3" %esi)
-    (lookup *esi *(esi+4))  # => eax
-    (array-equal? %ecx %eax)  # => eax
-    (check-ints-equal %eax 1 "F - test-parse-array-of-decimal-ints")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-array-of-decimal-ints-empty:
-    # - empty string = empty array
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var h/esi: handle
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %esi 4/r32/esp
-    #
-    (_parse-array-of-decimal-ints Heap "" %esi)
-    (lookup *esi *(esi+4))  # => eax
-    (check-ints-equal *eax 0 "F - test-parse-array-of-decimal-ints-empty")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-array-of-decimal-ints-just-whitespace:
-    # - just whitespace = empty array
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var h/esi: handle
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %esi 4/r32/esp
-    #
-    (_parse-array-of-decimal-ints Heap Space %esi)
-    (lookup *esi *(esi+4))  # => eax
-    (check-ints-equal *eax 0 "F - test-parse-array-of-decimal-ints-just-whitespace")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-parse-array-of-decimal-ints-extra-whitespace:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # var h/esi: handle
-    68/push 0/imm32
-    68/push 0/imm32
-    89/<- %esi 4/r32/esp
-    # var ecx: (array int) = [1, 2, 3]
-    68/push 3/imm32
-    68/push 2/imm32
-    68/push 1/imm32
-    68/push 0xc/imm32/size
-    89/<- %ecx 4/r32/esp
-    #
-    (_parse-array-of-decimal-ints Heap " 1 2  3  " %esi)
-    (lookup *esi *(esi+4))  # => eax
-    (array-equal? %ecx %eax)  # => eax
-    (check-ints-equal %eax 1 "F - test-parse-array-of-decimal-ints-extra-whitespace")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-parse-array-of-decimal-ints:  # s: (addr array byte), out: (addr handle array int)
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    (_parse-array-of-decimal-ints Heap *(ebp+8) *(ebp+0xc))
-$parse-array-of-decimal-ints:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
diff --git a/baremetal/312copy.subx b/baremetal/312copy.subx
deleted file mode 100644
index d2a6f661..00000000
--- a/baremetal/312copy.subx
+++ /dev/null
@@ -1,13 +0,0 @@
-== code
-
-copy-array-object:  # src: (addr array T), dest-ah: (addr handle array T)
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    #
-    (copy-array Heap *(ebp+8) *(ebp+0xc))
-$copy-array-object:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
diff --git a/baremetal/313index-bounds-check.subx b/baremetal/313index-bounds-check.subx
deleted file mode 100644
index c9d01ee2..00000000
--- a/baremetal/313index-bounds-check.subx
+++ /dev/null
@@ -1,60 +0,0 @@
-# Helper to check an array's bounds, and to abort if they're violated.
-# Really only intended to be called from code generated by mu.subx.
-
-== code
-
-__check-mu-array-bounds:  # index: int, elem-size: int, arr-size: int, function-name: (addr array byte), array-name: (addr array byte)
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # . save registers
-    50/push-eax
-    51/push-ecx
-    52/push-edx
-    # . not bothering saving ebx; it's only clobbered if we're going to abort
-    # ecx = arr-size
-    8b/-> *(ebp+0x10) 1/r32/ecx
-    # var overflow/edx: int = 0
-    ba/copy-to-edx 0/imm32
-    # var offset/eax: int = index * elem-size
-    8b/-> *(ebp+8) 0/r32/eax
-    f7 4/subop/multiply-eax-with *(ebp+0xc)
-    # check for overflow
-    81 7/subop/compare %edx 0/imm32
-    0f 85/jump-if-!= __check-mu-array-bounds:overflow/disp32
-    # check bounds
-    39/compare %eax 1/r32/ecx
-    0f 82/jump-if-unsigned< $__check-mu-array-bounds:end/disp32  # negative index should always abort
-    # abort if necessary
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "fn " 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebp+0x14) 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 ": offset " 3 0)  # 3=cyan
-    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " is too large for array '" 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebp+0x18) 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "'" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
-$__check-mu-array-bounds:end:
-    # . restore registers
-    5a/pop-to-edx
-    59/pop-to-ecx
-    58/pop-to-eax
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-__check-mu-array-bounds:overflow:
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "fn " 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebp+0x14) 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 ": offset to array '" 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "offset to array overflowed 32 bits" 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebp+0x18) 3 0)  # 3=cyan
-    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "' overflowed 32 bits" 3 0)  # 3=cyan
-    {
-      eb/jump loop/disp8
-    }
-    # never gets here
diff --git a/baremetal/314divide.subx b/baremetal/314divide.subx
deleted file mode 100644
index c0b85526..00000000
--- a/baremetal/314divide.subx
+++ /dev/null
@@ -1,17 +0,0 @@
-== code
-
-integer-divide:  # a: int, b: int -> quotient/eax: int, remainder/edx: int
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # eax = a
-    8b/-> *(ebp+8) 0/r32/eax
-    # edx = all 0s or all 1s
-    99/sign-extend-eax-into-edx
-    # quotient, remainder = divide eax by b
-    f7 7/subop/divide-eax-edx-by *(ebp+0xc)
-$integer-divide:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
diff --git a/baremetal/400.mu b/baremetal/400.mu
deleted file mode 100644
index 9d0c095f..00000000
--- a/baremetal/400.mu
+++ /dev/null
@@ -1,82 +0,0 @@
-# screen
-sig pixel-on-real-screen x: int, y: int, color: int
-sig draw-grapheme-on-real-screen g: grapheme, x: int, y: int, color: int, background-color: int
-sig cursor-position-on-real-screen -> _/eax: int, _/ecx: int
-sig set-cursor-position-on-real-screen x: int, y: int
-sig show-cursor-on-real-screen g: grapheme
-
-# keyboard
-sig read-key kbd: (addr keyboard) -> _/eax: byte
-
-# tests
-sig count-test-failure
-sig num-test-failures -> _/eax: int
-
-sig string-equal? s: (addr array byte), benchmark: (addr array byte) -> _/eax: boolean
-sig string-starts-with? s: (addr array byte), benchmark: (addr array byte) -> _/eax: boolean
-sig check-strings-equal s: (addr array byte), expected: (addr array byte), msg: (addr array byte)
-
-# streams
-sig clear-stream f: (addr stream _)
-sig rewind-stream f: (addr stream _)
-sig stream-data-equal? f: (addr stream byte), s: (addr array byte) -> _/eax: boolean
-sig check-stream-equal f: (addr stream byte), s: (addr array byte), msg: (addr array byte)
-sig next-stream-line-equal? f: (addr stream byte), s: (addr array byte) -> _/eax: boolean
-sig check-next-stream-line-equal f: (addr stream byte), s: (addr array byte), msg: (addr array byte)
-sig write f: (addr stream byte), s: (addr array byte)
-sig write-stream f: (addr stream byte), s: (addr stream byte)
-sig read-byte s: (addr stream byte) -> _/eax: byte
-sig append-byte f: (addr stream byte), n: int
-#sig to-hex-char in/eax: int -> out/eax: int
-sig append-byte-hex f: (addr stream byte), n: int
-sig write-int32-hex f: (addr stream byte), n: int
-sig write-int32-hex-bits f: (addr stream byte), n: int, bits: int
-sig is-hex-int? in: (addr slice) -> _/eax: boolean
-sig parse-hex-int in: (addr array byte) -> _/eax: int
-sig parse-hex-int-from-slice in: (addr slice) -> _/eax: int
-#sig parse-hex-int-helper start: (addr byte), end: (addr byte) -> _/eax: int
-sig is-hex-digit? c: byte -> _/eax: boolean
-#sig from-hex-char in/eax: byte -> out/eax: nibble
-sig parse-decimal-int in: (addr array byte) -> _/eax: int
-sig parse-decimal-int-from-slice in: (addr slice) -> _/eax: int
-sig parse-decimal-int-from-stream in: (addr stream byte) -> _/eax: int
-#sig parse-decimal-int-helper start: (addr byte), end: (addr byte) -> _/eax: int
-sig decimal-size n: int -> _/eax: int
-#sig allocate ad: (addr allocation-descriptor), n: int, out: (addr handle _)
-#sig allocate-raw ad: (addr allocation-descriptor), n: int, out: (addr handle _)
-sig lookup h: (handle _T) -> _/eax: (addr _T)
-sig handle-equal? a: (handle _T), b: (handle _T) -> _/eax: boolean
-sig copy-handle src: (handle _T), dest: (addr handle _T)
-#sig allocate-region ad: (addr allocation-descriptor), n: int, out: (addr handle allocation-descriptor)
-#sig allocate-array ad: (addr allocation-descriptor), n: int, out: (addr handle _)
-sig copy-array ad: (addr allocation-descriptor), src: (addr array _T), out: (addr handle array _T)
-#sig zero-out start: (addr byte), size: int
-sig slice-empty? s: (addr slice) -> _/eax: boolean
-sig slice-equal? s: (addr slice), p: (addr array byte) -> _/eax: boolean
-sig slice-starts-with? s: (addr slice), head: (addr array byte) -> _/eax: boolean
-sig write-slice out: (addr stream byte), s: (addr slice)
-# bad name alert
-sig slice-to-string ad: (addr allocation-descriptor), in: (addr slice), out: (addr handle array byte)
-sig write-int32-decimal out: (addr stream byte), n: int
-sig is-decimal-digit? c: grapheme -> _/eax: boolean
-sig to-decimal-digit in: grapheme -> _/eax: int
-# bad name alert
-# next-word really tokenizes
-# next-raw-word really reads whitespace-separated words
-sig next-word line: (addr stream byte), out: (addr slice)  # skips '#' comments
-sig next-raw-word line: (addr stream byte), out: (addr slice)  # does not skip '#' comments
-sig stream-empty? s: (addr stream _) -> _/eax: boolean
-sig stream-full? s: (addr stream _) -> _/eax: boolean
-sig stream-to-array in: (addr stream _), out: (addr handle array _)
-sig unquote-stream-to-array in: (addr stream _), out: (addr handle array _)
-sig stream-first s: (addr stream byte) -> _/eax: byte
-sig stream-final s: (addr stream byte) -> _/eax: byte
-
-#sig copy-bytes src: (addr byte), dest: (addr byte), n: int
-sig copy-array-object src: (addr array _), dest-ah: (addr handle array _)
-sig array-equal? a: (addr array int), b: (addr array int) -> _/eax: boolean
-sig parse-array-of-ints s: (addr array byte), out: (addr handle array int)
-sig parse-array-of-decimal-ints s: (addr array byte), out: (addr handle array int)
-sig check-array-equal a: (addr array int), expected: (addr string), msg: (addr string)
-
-sig integer-divide a: int, b: int -> _/eax: int, _/edx: int
diff --git a/baremetal/403unicode.mu b/baremetal/403unicode.mu
deleted file mode 100644
index 6ec30c3d..00000000
--- a/baremetal/403unicode.mu
+++ /dev/null
@@ -1,193 +0,0 @@
-# Helpers for Unicode.
-#
-# Mu has no characters, only code points and graphemes.
-# Code points are the indivisible atoms of text streams.
-#   https://en.wikipedia.org/wiki/Code_point
-# Graphemes are the smallest self-contained unit of text.
-# Graphemes may consist of multiple code points.
-#
-# Mu graphemes are always represented in utf-8, and they are required to fit
-# in 4 bytes.
-#
-# Mu doesn't currently support combining code points, or graphemes made of
-# multiple code points. One day we will.
-# We also don't currently support code points that translate into multiple
-# or wide graphemes. (In particular, Tab will never be supported.)
-
-# transliterated from tb_utf8_unicode_to_char in https://github.com/nsf/termbox
-# https://wiki.tcl-lang.org/page/UTF%2D8+bit+by+bit explains the algorithm
-#
-# The day we want to support combining characters, this function will need to
-# take multiple code points. Or something.
-fn to-grapheme in: code-point -> _/eax: grapheme {
-  var c/eax: int <- copy in
-  var num-trailers/ecx: int <- copy 0
-  var first/edx: int <- copy 0
-  $to-grapheme:compute-length: {
-    # single byte: just return it
-    compare c, 0x7f
-    {
-      break-if->
-      var g/eax: grapheme <- copy c
-      return g
-    }
-    # 2 bytes
-    compare c, 0x7ff
-    {
-      break-if->
-      num-trailers <- copy 1
-      first <- copy 0xc0
-      break $to-grapheme:compute-length
-    }
-    # 3 bytes
-    compare c, 0xffff
-    {
-      break-if->
-      num-trailers <- copy 2
-      first <- copy 0xe0
-      break $to-grapheme:compute-length
-    }
-    # 4 bytes
-    compare c, 0x1fffff
-    {
-      break-if->
-      num-trailers <- copy 3
-      first <- copy 0xf0
-      break $to-grapheme:compute-length
-    }
-    # more than 4 bytes: unsupported
-    # TODO: print error message to stderr
-    compare c, 0x1fffff
-    {
-      break-if->
-      return 0
-    }
-  }
-  # emit trailer bytes, 6 bits from 'in', first two bits '10'
-  var result/edi: grapheme <- copy 0
-  {
-    compare num-trailers, 0
-    break-if-<=
-    var tmp/esi: int <- copy c
-    tmp <- and 0x3f
-    tmp <- or 0x80
-    result <- shift-left 8
-    result <- or tmp
-    # update loop state
-    c <- shift-right 6
-    num-trailers <- decrement
-    loop
-  }
-  # emit engine
-  result <- shift-left 8
-  result <- or c
-  result <- or first
-  #
-  return result
-}
-
-# TODO: bring in tests once we have check-ints-equal
-
-# read the next grapheme from a stream of bytes
-fn read-grapheme in: (addr stream byte) -> _/eax: grapheme {
-  # if at eof, return EOF
-  {
-    var eof?/eax: boolean <- stream-empty? in
-    compare eof?, 0/false
-    break-if-=
-    return 0xffffffff
-  }
-  var c/eax: byte <- read-byte in
-  var num-trailers/ecx: int <- copy 0
-  $read-grapheme:compute-length: {
-    # single byte: just return it
-    compare c, 0xc0
-    {
-      break-if->=
-      var g/eax: grapheme <- copy c
-      return g
-    }
-    compare c, 0xfe
-    {
-      break-if-<
-      var g/eax: grapheme <- copy c
-      return g
-    }
-    # 2 bytes
-    compare c, 0xe0
-    {
-      break-if->=
-      num-trailers <- copy 1
-      break $read-grapheme:compute-length
-    }
-    # 3 bytes
-    compare c, 0xf0
-    {
-      break-if->=
-      num-trailers <- copy 2
-      break $read-grapheme:compute-length
-    }
-    # 4 bytes
-    compare c, 0xf8
-    {
-      break-if->=
-      num-trailers <- copy 3
-      break $read-grapheme:compute-length
-    }
-    # TODO: print error message
-    return 0
-  }
-  # prepend trailer bytes
-  var result/edi: grapheme <- copy c
-  var num-byte-shifts/edx: int <- copy 1
-  {
-    compare num-trailers, 0
-    break-if-<=
-    var tmp/eax: byte <- read-byte in
-    var tmp2/eax: int <- copy tmp
-    tmp2 <- shift-left-bytes tmp2, num-byte-shifts
-    result <- or tmp2
-    # update loop state
-    num-byte-shifts <- increment
-    num-trailers <- decrement
-    loop
-  }
-  return result
-}
-
-# needed because available primitives only shift by a literal/constant number of bits
-fn shift-left-bytes n: int, k: int -> _/eax: int {
-  var i/ecx: int <- copy 0
-  var result/eax: int <- copy n
-  {
-    compare i, k
-    break-if->=
-    compare i, 4  # only 4 bytes in 32 bits
-    break-if->=
-    result <- shift-left 8
-    i <- increment
-    loop
-  }
-  return result
-}
-
-# write a grapheme to a stream of bytes
-# this is like write-to-stream, except we skip leading 0 bytes
-fn write-grapheme out: (addr stream byte), g: grapheme {
-$write-grapheme:body: {
-  var c/eax: int <- copy g
-  append-byte out, c  # first byte is always written
-  c <- shift-right 8
-  compare c, 0
-  break-if-= $write-grapheme:body
-  append-byte out, c
-  c <- shift-right 8
-  compare c, 0
-  break-if-= $write-grapheme:body
-  append-byte out, c
-  c <- shift-right 8
-  compare c, 0
-  break-if-= $write-grapheme:body
-  append-byte out, c
-}
-}
diff --git a/baremetal/408float.mu b/baremetal/408float.mu
deleted file mode 100644
index d1c04836..00000000
--- a/baremetal/408float.mu
+++ /dev/null
@@ -1,23 +0,0 @@
-# Some quick-n-dirty ways to create floats.
-
-fn fill-in-rational _out: (addr float), nr: int, dr: int {
-  var out/edi: (addr float) <- copy _out
-  var result/xmm0: float <- convert nr
-  var divisor/xmm1: float <- convert dr
-  result <- divide divisor
-  copy-to *out, result
-}
-
-fn fill-in-sqrt _out: (addr float), n: int {
-  var out/edi: (addr float) <- copy _out
-  var result/xmm0: float <- convert n
-  result <- square-root result
-  copy-to *out, result
-}
-
-fn rational nr: int, dr: int -> _/xmm0: float {
-  var result/xmm0: float <- convert nr
-  var divisor/xmm1: float <- convert dr
-  result <- divide divisor
-  return result
-}
diff --git a/baremetal/411string.mu b/baremetal/411string.mu
deleted file mode 100644
index cf0471ac..00000000
--- a/baremetal/411string.mu
+++ /dev/null
@@ -1,125 +0,0 @@
-# read up to 'len' graphemes after skipping the first 'start' ones
-fn substring in: (addr array byte), start: int, len: int, out-ah: (addr handle array byte) {
-  var in-stream: (stream byte 0x100)
-  var in-stream-addr/esi: (addr stream byte) <- address in-stream
-  write in-stream-addr, in
-  var out-stream: (stream byte 0x100)
-  var out-stream-addr/edi: (addr stream byte) <- address out-stream
-  $substring:core: {
-    # skip 'start' graphemes
-    var i/eax: int <- copy 0
-    {
-      compare i, start
-      break-if->=
-      {
-        var dummy/eax: grapheme <- read-grapheme in-stream-addr
-        compare dummy, 0xffffffff/end-of-file
-        break-if-= $substring:core
-      }
-      i <- increment
-      loop
-    }
-    # copy 'len' graphemes
-    i <- copy 0
-    {
-      compare i, len
-      break-if->=
-      {
-        var g/eax: grapheme <- read-grapheme in-stream-addr
-        compare g, 0xffffffff/end-of-file
-        break-if-= $substring:core
-        write-grapheme out-stream-addr, g
-      }
-      i <- increment
-      loop
-    }
-  }
-  stream-to-array out-stream-addr, out-ah
-}
-
-fn test-substring {
-  var out-h: (handle array byte)
-  var out-ah/edi: (addr handle array byte) <- address out-h
-  # prefix substrings
-  substring 0, 0, 3, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-  check-strings-equal out, "", "F - test-substring/null"
-  substring "", 0, 3, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-#?   print-string-to-real-screen out
-#?   print-string-to-real-screen "\n"
-  check-strings-equal out, "", "F - test-substring/empty"
-  #
-  substring "abcde", 0, 3, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-#?   print-string-to-real-screen out
-#?   print-string-to-real-screen "\n"
-  check-strings-equal out, "abc", "F - test-substring/truncate"
-  #
-  substring "abcde", 0, 5, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-  check-strings-equal out, "abcde", "F - test-substring/all"
-  #
-  substring "abcde", 0, 7, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-  check-strings-equal out, "abcde", "F - test-substring/too-small"
-  # substrings outside string
-  substring "abcde", 6, 1, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-  check-strings-equal out, "", "F - test-substring/start-too-large"
-  # trim prefix
-  substring "", 2, 3, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-  check-strings-equal out, "", "F - test-substring/middle-empty"
-  #
-  substring "abcde", 1, 2, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-  check-strings-equal out, "bc", "F - test-substring/middle-truncate"
-  #
-  substring "abcde", 1, 4, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-  check-strings-equal out, "bcde", "F - test-substring/middle-all"
-  #
-  substring "abcde", 1, 5, out-ah
-  var out/eax: (addr array byte) <- lookup *out-ah
-  check-strings-equal out, "bcde", "F - test-substring/middle-too-small"
-}
-
-fn split-string in: (addr array byte), delim: grapheme, out: (addr handle array (handle array byte)) {
-  var in-stream: (stream byte 0x100)
-  var in-stream-addr/esi: (addr stream byte) <- address in-stream
-  write in-stream-addr, in
-  var tokens-stream: (stream (handle array byte) 0x100)
-  var tokens-stream-addr/edi: (addr stream (handle array byte)) <- address tokens-stream
-  var curr-stream: (stream byte 0x100)
-  var curr-stream-addr/ecx: (addr stream byte) <- address curr-stream
-  $split-string:core: {
-    var g/eax: grapheme <- read-grapheme in-stream-addr
-    compare g, 0xffffffff
-    break-if-=
-#?     print-grapheme-to-real-screen g
-#?     print-string-to-real-screen "\n"
-    compare g, delim
-    {
-      break-if-!=
-      # token complete; flush
-      var token: (handle array byte)
-      var token-ah/eax: (addr handle array byte) <- address token
-      stream-to-array curr-stream-addr, token-ah
-      write-to-stream tokens-stream-addr, token-ah
-      clear-stream curr-stream-addr
-      loop $split-string:core
-    }
-    write-grapheme curr-stream-addr, g
-    loop
-  }
-  stream-to-array tokens-stream-addr, out
-}
-
-fn test-split-string {
-  var out-h: (handle array (handle array byte))
-  var out-ah/edi: (addr handle array (handle array byte)) <- address out-h
-  # prefix substrings
-  split-string "bab", 0x61, out-ah
-  # no crash
-}
diff --git a/baremetal/412render-float-decimal.mu b/baremetal/412render-float-decimal.mu
deleted file mode 100644
index 7aaf8b3a..00000000
--- a/baremetal/412render-float-decimal.mu
+++ /dev/null
@@ -1,597 +0,0 @@
-# print out floats in decimal
-# https://research.swtch.com/ftoa
-#
-# Basic idea:
-#   Ignoring sign, floating point numbers are represented as 1.mantissa * 2^exponent
-#   Therefore, to print a float in decimal, we need to:
-#     - compute its value without decimal point
-#     - convert to an array of decimal digits
-#     - print out the array while inserting the decimal point appropriately
-#
-# Basic complication: the computation generates numbers larger than an int can
-# hold. We need a way to represent big ints.
-#
-# Key insight: use a representation for big ints that's close to what we need
-# anyway, an array of decimal digits.
-#
-# Style note: we aren't creating a big int library here. The only operations
-# we need are halving and doubling. Following the link above, it seems more
-# comprehensible to keep these operations inlined so that we can track the
-# position of the decimal point with dispatch.
-#
-# This approach turns out to be fast enough for most purposes.
-# Optimizations, however, get wildly more complex.
-
-fn test-write-float-decimal-approximate-normal {
-  var s-storage: (stream byte 0x10)
-  var s/ecx: (addr stream byte) <- address s-storage
-  # 0.5
-  var half/xmm0: float <- rational 1, 2
-  write-float-decimal-approximate s, half, 3
-  check-stream-equal s, "0.5", "F - test-write-float-decimal-approximate-normal 0.5"
-  # 0.25
-  clear-stream s
-  var quarter/xmm0: float <- rational 1, 4
-  write-float-decimal-approximate s, quarter, 3
-  check-stream-equal s, "0.25", "F - test-write-float-decimal-approximate-normal 0.25"
-  # 0.75
-  clear-stream s
-  var three-quarters/xmm0: float <- rational 3, 4
-  write-float-decimal-approximate s, three-quarters, 3
-  check-stream-equal s, "0.75", "F - test-write-float-decimal-approximate-normal 0.75"
-  # 0.125
-  clear-stream s
-  var eighth/xmm0: float <- rational 1, 8
-  write-float-decimal-approximate s, eighth, 3
-  check-stream-equal s, "0.125", "F - test-write-float-decimal-approximate-normal 0.125"
-  # 0.0625; start using scientific notation
-  clear-stream s
-  var sixteenth/xmm0: float <- rational 1, 0x10
-  write-float-decimal-approximate s, sixteenth, 3
-  check-stream-equal s, "6.25e-2", "F - test-write-float-decimal-approximate-normal 0.0625"
-  # sqrt(2); truncate floats with lots of digits after the decimal but not too many before
-  clear-stream s
-  var two-f/xmm0: float <- rational 2, 1
-  var sqrt-2/xmm0: float <- square-root two-f
-  write-float-decimal-approximate s, sqrt-2, 3
-  check-stream-equal s, "1.414", "F - test-write-float-decimal-approximate-normal √2"
-}
-
-# print whole integers without decimals
-fn test-write-float-decimal-approximate-integer {
-  var s-storage: (stream byte 0x10)
-  var s/ecx: (addr stream byte) <- address s-storage
-  # 1
-  var one-f/xmm0: float <- rational 1, 1
-  write-float-decimal-approximate s, one-f, 3
-  check-stream-equal s, "1", "F - test-write-float-decimal-approximate-integer 1"
-  # 2
-  clear-stream s
-  var two-f/xmm0: float <- rational 2, 1
-  write-float-decimal-approximate s, two-f, 3
-  check-stream-equal s, "2", "F - test-write-float-decimal-approximate-integer 2"
-  # 10
-  clear-stream s
-  var ten-f/xmm0: float <- rational 0xa, 1
-  write-float-decimal-approximate s, ten-f, 3
-  check-stream-equal s, "10", "F - test-write-float-decimal-approximate-integer 10"
-  # -10
-  clear-stream s
-  var minus-ten-f/xmm0: float <- rational -0xa, 1
-  write-float-decimal-approximate s, minus-ten-f, 3
-  check-stream-equal s, "-10", "F - test-write-float-decimal-approximate-integer -10"
-  # 999
-  clear-stream s
-  var minus-ten-f/xmm0: float <- rational 0x3e7, 1
-  write-float-decimal-approximate s, minus-ten-f, 3
-  check-stream-equal s, "999", "F - test-write-float-decimal-approximate-integer 1000"
-  # 1000 - start using scientific notation
-  clear-stream s
-  var minus-ten-f/xmm0: float <- rational 0x3e8, 1
-  write-float-decimal-approximate s, minus-ten-f, 3
-  check-stream-equal s, "1.00e3", "F - test-write-float-decimal-approximate-integer 1000"
-  # 100,000
-  clear-stream s
-  var hundred-thousand/eax: int <- copy 0x186a0
-  var hundred-thousand-f/xmm0: float <- convert hundred-thousand
-  write-float-decimal-approximate s, hundred-thousand-f, 3
-  check-stream-equal s, "1.00e5", "F - test-write-float-decimal-approximate-integer 100,000"
-}
-
-fn test-write-float-decimal-approximate-zero {
-  var s-storage: (stream byte 0x10)
-  var s/ecx: (addr stream byte) <- address s-storage
-  var zero: float
-  write-float-decimal-approximate s, zero, 3
-  check-stream-equal s, "0", "F - test-write-float-decimal-approximate-zero"
-}
-
-fn test-write-float-decimal-approximate-negative-zero {
-  var s-storage: (stream byte 0x10)
-  var s/ecx: (addr stream byte) <- address s-storage
-  var n: int
-  copy-to n, 0x80000000
-  var negative-zero/xmm0: float <- reinterpret n
-  write-float-decimal-approximate s, negative-zero, 3
-  check-stream-equal s, "-0", "F - test-write-float-decimal-approximate-negative-zero"
-}
-
-fn test-write-float-decimal-approximate-infinity {
-  var s-storage: (stream byte 0x10)
-  var s/ecx: (addr stream byte) <- address s-storage
-  var n: int
-  #          0|11111111|00000000000000000000000
-  #          0111|1111|1000|0000|0000|0000|0000|0000
-  copy-to n, 0x7f800000
-  var infinity/xmm0: float <- reinterpret n
-  write-float-decimal-approximate s, infinity, 3
-  check-stream-equal s, "Inf", "F - test-write-float-decimal-approximate-infinity"
-}
-
-fn test-write-float-decimal-approximate-negative-infinity {
-  var s-storage: (stream byte 0x10)
-  var s/ecx: (addr stream byte) <- address s-storage
-  var n: int
-  copy-to n, 0xff800000
-  var negative-infinity/xmm0: float <- reinterpret n
-  write-float-decimal-approximate s, negative-infinity, 3
-  check-stream-equal s, "-Inf", "F - test-write-float-decimal-approximate-negative-infinity"
-}
-
-fn test-write-float-decimal-approximate-not-a-number {
-  var s-storage: (stream byte 0x10)
-  var s/ecx: (addr stream byte) <- address s-storage
-  var n: int
-  copy-to n, 0xffffffff  # exponent must be all 1's, and mantissa must be non-zero
-  var nan/xmm0: float <- reinterpret n
-  write-float-decimal-approximate s, nan, 3
-  check-stream-equal s, "NaN", "F - test-write-float-decimal-approximate-not-a-number"
-}
-
-fn render-float-decimal screen: (addr screen), in: float, precision: int, x: int, y: int, color: int, background-color: int -> _/eax: int {
-  var s-storage: (stream byte 0x10)
-  var s/esi: (addr stream byte) <- address s-storage
-  write-float-decimal-approximate s, in, precision
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  width, height <- screen-size screen
-  var result/eax: int <- draw-stream-rightward screen, s, x, width, y, color, background-color
-  return result
-}
-
-# 'precision' controls the maximum width past which we resort to scientific notation
-fn write-float-decimal-approximate out: (addr stream byte), in: float, precision: int {
-  # - special names
-  var bits/eax: int <- reinterpret in
-  compare bits, 0
-  {
-    break-if-!=
-    write out, "0"
-    return
-  }
-  compare bits, 0x80000000
-  {
-    break-if-!=
-    write out, "-0"
-    return
-  }
-  compare bits, 0x7f800000
-  {
-    break-if-!=
-    write out, "Inf"
-    return
-  }
-  compare bits, 0xff800000
-  {
-    break-if-!=
-    write out, "-Inf"
-    return
-  }
-  var exponent/ecx: int <- copy bits
-  exponent <- shift-right 0x17  # 23 bits of mantissa
-  exponent <- and 0xff
-  exponent <- subtract 0x7f
-  compare exponent, 0x80
-  {
-    break-if-!=
-    write out, "NaN"
-    return
-  }
-  # - regular numbers
-  var sign/edx: int <- copy bits
-  sign <- shift-right 0x1f
-  {
-    compare sign, 1
-    break-if-!=
-    append-byte out, 0x2d/minus
-  }
-
-  # v = 1.mantissa (in base 2) << 0x17
-  var v/ebx: int <- copy bits
-  v <- and 0x7fffff
-  v <- or 0x00800000  # insert implicit 1
-  # e = exponent - 0x17
-  var e/ecx: int <- copy exponent
-  e <- subtract 0x17  # move decimal place from before mantissa to after
-
-  # initialize buffer with decimal representation of v
-  # unlike https://research.swtch.com/ftoa, no ascii here
-  var buf-storage: (array byte 0x7f)
-  var buf/edi: (addr array byte) <- address buf-storage
-  var n/eax: int <- decimal-digits v, buf
-  # I suspect we can do without reversing, but we'll follow https://research.swtch.com/ftoa
-  # closely for now.
-  reverse-digits buf, n
-
-  # loop if e > 0
-  {
-    compare e, 0
-    break-if-<=
-    n <- double-array-of-decimal-digits buf, n
-    e <- decrement
-    loop
-  }
-
-  var dp/edx: int <- copy n
-
-  # loop if e < 0
-  {
-    compare e, 0
-    break-if->=
-    n, dp <- halve-array-of-decimal-digits buf, n, dp
-    e <- increment
-    loop
-  }
-
-  _write-float-array-of-decimal-digits out, buf, n, dp, precision
-}
-
-# store the decimal digits of 'n' into 'buf', units first
-# n must be positive
-fn decimal-digits n: int, _buf: (addr array byte) -> _/eax: int {
-  var buf/edi: (addr array byte) <- copy _buf
-  var i/ecx: int <- copy 0
-  var curr/eax: int <- copy n
-  var curr-byte/edx: int <- copy 0
-  {
-    compare curr, 0
-    break-if-=
-    curr, curr-byte <- integer-divide curr, 0xa
-    var dest/ebx: (addr byte) <- index buf, i
-    copy-byte-to *dest, curr-byte
-    i <- increment
-    loop
-  }
-  return i
-}
-
-fn reverse-digits _buf: (addr array byte), n: int {
-  var buf/esi: (addr array byte) <- copy _buf
-  var left/ecx: int <- copy 0
-  var right/edx: int <- copy n
-  right <- decrement
-  {
-    compare left, right
-    break-if->=
-    {
-      var l-a/ecx: (addr byte) <- index buf, left
-      var r-a/edx: (addr byte) <- index buf, right
-      var l/ebx: byte <- copy-byte *l-a
-      var r/eax: byte <- copy-byte *r-a
-      copy-byte-to *l-a, r
-      copy-byte-to *r-a, l
-    }
-    left <- increment
-    right <- decrement
-    loop
-  }
-}
-
-fn double-array-of-decimal-digits _buf: (addr array byte), _n: int -> _/eax: int {
-  var buf/edi: (addr array byte) <- copy _buf
-  # initialize delta
-  var delta/edx: int <- copy 0
-  {
-    var curr/ebx: (addr byte) <- index buf, 0
-    var tmp/eax: byte <- copy-byte *curr
-    compare tmp, 5
-    break-if-<
-    delta <- copy 1
-  }
-  # loop
-  var x/eax: int <- copy 0
-  var i/ecx: int <- copy _n
-  i <- decrement
-  {
-    compare i, 0
-    break-if-<=
-    # x += 2*buf[i]
-    {
-      var tmp/ecx: (addr byte) <- index buf, i
-      var tmp2/ecx: byte <- copy-byte *tmp
-      x <- add tmp2
-      x <- add tmp2
-    }
-    # x, buf[i+delta] = x/10, x%10
-    {
-      var dest-index/ecx: int <- copy i
-      dest-index <- add delta
-      var dest/edi: (addr byte) <- index buf, dest-index
-      var next-digit/edx: int <- copy 0
-      x, next-digit <- integer-divide x, 0xa
-      copy-byte-to *dest, next-digit
-    }
-    #
-    i <- decrement
-    loop
-  }
-  # final patch-up
-  var n/eax: int <- copy _n
-  compare delta, 1
-  {
-    break-if-!=
-    var curr/ebx: (addr byte) <- index buf, 0
-    var one/edx: int <- copy 1
-    copy-byte-to *curr, one
-    n <- increment
-  }
-  return n
-}
-
-fn halve-array-of-decimal-digits _buf: (addr array byte), _n: int, _dp: int -> _/eax: int, _/edx: int {
-  var buf/edi: (addr array byte) <- copy _buf
-  var n/eax: int <- copy _n
-  var dp/edx: int <- copy _dp
-  # initialize one side
-  {
-    # if buf[n-1]%2 == 0, break
-    var right-index/ecx: int <- copy n
-    right-index <- decrement
-    var right-a/ecx: (addr byte) <- index buf, right-index
-    var right/ecx: byte <- copy-byte *right-a
-    var right-int/ecx: int <- copy right
-    var remainder/edx: int <- copy 0
-    {
-      var dummy/eax: int <- copy 0
-      dummy, remainder <- integer-divide right-int, 2
-    }
-    compare remainder, 0
-    break-if-=
-    # buf[n] = 0
-    var next-a/ecx: (addr byte) <- index buf, n
-    var zero/edx: byte <- copy 0
-    copy-byte-to *next-a, zero
-    # n++
-    n <- increment
-  }
-  # initialize the other
-  var delta/ebx: int <- copy 0
-  var x/esi: int <- copy 0
-  {
-    # if buf[0] >= 2, break
-    var left/ecx: (addr byte) <- index buf, 0
-    var src/ecx: byte <- copy-byte *left
-    compare src, 2
-    break-if->=
-    # delta, x = 1, buf[0]
-    delta <- copy 1
-    x <- copy src
-    # n--
-    n <- decrement
-    # dp--
-    dp <- decrement
-  }
-  # loop
-  var i/ecx: int <- copy 0
-  {
-    compare i, n
-    break-if->=
-    # x = x*10 + buf[i+delta]
-    {
-      var ten/edx: int <- copy 0xa
-      x <- multiply ten
-      var src-index/edx: int <- copy i
-      src-index <- add delta
-      var src-a/edx: (addr byte) <- index buf, src-index
-      var src/edx: byte <- copy-byte *src-a
-      x <- add src
-    }
-    # buf[i], x = x/2, x%2
-    {
-      var quotient/eax: int <- copy 0
-      var remainder/edx: int <- copy 0
-      quotient, remainder <- integer-divide x, 2
-      x <- copy remainder
-      var dest/edx: (addr byte) <- index buf, i
-      copy-byte-to *dest, quotient
-    }
-    #
-    i <- increment
-    loop
-  }
-  return n, dp
-}
-
-fn _write-float-array-of-decimal-digits out: (addr stream byte), _buf: (addr array byte), n: int, dp: int, precision: int {
-  var buf/edi: (addr array byte) <- copy _buf
-  {
-    compare dp, 0
-    break-if->=
-    _write-float-array-of-decimal-digits-in-scientific-notation out, buf, n, dp, precision
-    return
-  }
-  {
-    var dp2/eax: int <- copy dp
-    compare dp2, precision
-    break-if-<=
-    _write-float-array-of-decimal-digits-in-scientific-notation out, buf, n, dp, precision
-    return
-  }
-  {
-    compare dp, 0
-    break-if-!=
-    append-byte out, 0x30/0
-  }
-  var i/eax: int <- copy 0
-  # bounds = min(n, dp+3)
-  var limit/edx: int <- copy dp
-  limit <- add 3
-  {
-    compare limit, n
-    break-if-<=
-    limit <- copy n
-  }
-  {
-    compare i, limit
-    break-if->=
-    # print '.' if necessary
-    compare i, dp
-    {
-      break-if-!=
-      append-byte out, 0x2e/decimal-point
-    }
-    var curr-a/ecx: (addr byte) <- index buf, i
-    var curr/ecx: byte <- copy-byte *curr-a
-    var curr-int/ecx: int <- copy curr
-    curr-int <- add 0x30/0
-    append-byte out, curr-int
-    #
-    i <- increment
-    loop
-  }
-}
-
-fn _write-float-array-of-decimal-digits-in-scientific-notation out: (addr stream byte), _buf: (addr array byte), n: int, dp: int, precision: int {
-  var buf/edi: (addr array byte) <- copy _buf
-  var i/eax: int <- copy 0
-  {
-    compare i, n
-    break-if->=
-    compare i, precision
-    break-if->=
-    compare i, 1
-    {
-      break-if-!=
-      append-byte out, 0x2e/decimal-point
-    }
-    var curr-a/ecx: (addr byte) <- index buf, i
-    var curr/ecx: byte <- copy-byte *curr-a
-    var curr-int/ecx: int <- copy curr
-    curr-int <- add 0x30/0
-    append-byte out, curr-int
-    #
-    i <- increment
-    loop
-  }
-  append-byte out, 0x65/e
-  decrement dp
-  write-int32-decimal out, dp
-}
-
-# follows the structure of write-float-decimal-approximate
-# 'precision' controls the maximum width past which we resort to scientific notation
-fn float-size in: float, precision: int -> _/eax: int {
-  # - special names
-  var bits/eax: int <- reinterpret in
-  compare bits, 0
-  {
-    break-if-!=
-    return 1  # for "0"
-  }
-  compare bits, 0x80000000
-  {
-    break-if-!=
-    return 2  # for "-0"
-  }
-  compare bits, 0x7f800000
-  {
-    break-if-!=
-    return 3  # for "Inf"
-  }
-  compare bits, 0xff800000
-  {
-    break-if-!=
-    return 4  # for "-Inf"
-  }
-  var exponent/ecx: int <- copy bits
-  exponent <- shift-right 0x17  # 23 bits of mantissa
-  exponent <- and 0xff
-  exponent <- subtract 0x7f
-  compare exponent, 0x80
-  {
-    break-if-!=
-    return 3  # for "NaN"
-  }
-  # - regular numbers
-  # v = 1.mantissa (in base 2) << 0x17
-  var v/ebx: int <- copy bits
-  v <- and 0x7fffff
-  v <- or 0x00800000  # insert implicit 1
-  # e = exponent - 0x17
-  var e/ecx: int <- copy exponent
-  e <- subtract 0x17  # move decimal place from before mantissa to after
-
-  # initialize buffer with decimal representation of v
-  var buf-storage: (array byte 0x7f)
-  var buf/edi: (addr array byte) <- address buf-storage
-  var n/eax: int <- decimal-digits v, buf
-  reverse-digits buf, n
-
-  # loop if e > 0
-  {
-    compare e, 0
-    break-if-<=
-    n <- double-array-of-decimal-digits buf, n
-    e <- decrement
-    loop
-  }
-
-  var dp/edx: int <- copy n
-
-  # loop if e < 0
-  {
-    compare e, 0
-    break-if->=
-    n, dp <- halve-array-of-decimal-digits buf, n, dp
-    e <- increment
-    loop
-  }
-
-  compare dp, 0
-  {
-    break-if->=
-    return 8  # hacky for scientific notation
-  }
-  {
-    var dp2/eax: int <- copy dp
-    compare dp2, precision
-    break-if-<=
-    return 8  # hacky for scientific notation
-  }
-
-  # result = min(n, dp+3)
-  var result/ecx: int <- copy dp
-  result <- add 3
-  {
-    compare result, n
-    break-if-<=
-    result <- copy n
-  }
-
-  # account for decimal point
-  compare dp, n
-  {
-    break-if->=
-    result <- increment
-  }
-
-  # account for sign
-  var sign/edx: int <- reinterpret in
-  sign <- shift-right 0x1f
-  {
-    compare sign, 1
-    break-if-!=
-    result <- increment
-  }
-  return result
-}
diff --git a/baremetal/500text-screen.mu b/baremetal/500text-screen.mu
deleted file mode 100644
index a764aa06..00000000
--- a/baremetal/500text-screen.mu
+++ /dev/null
@@ -1,298 +0,0 @@
-# Testable primitives for writing text to screen.
-# (Mu doesn't yet have testable primitives for graphics.)
-#
-# Unlike the top-level, this text mode has no scrolling.
-
-# coordinates here don't match top-level
-# Here we're consistent with graphics mode. Top-level is consistent with
-# terminal emulators.
-type screen {
-  width: int
-  height: int
-  data: (handle array screen-cell)
-  cursor-x: int
-  cursor-y: int
-}
-
-type screen-cell {
-  data: grapheme
-  color: int
-  background-color: int
-}
-
-fn initialize-screen screen: (addr screen), width: int, height: int {
-  var screen-addr/esi: (addr screen) <- copy screen
-  var tmp/eax: int <- copy 0
-  var dest/edi: (addr int) <- copy 0
-  # screen->width = width
-  dest <- get screen-addr, width
-  tmp <- copy width
-  copy-to *dest, tmp
-  # screen->height = height
-  dest <- get screen-addr, height
-  tmp <- copy height
-  copy-to *dest, tmp
-  # screen->data = new screen-cell[width*height]
-  {
-    var data-addr/edi: (addr handle array screen-cell) <- get screen-addr, data
-    tmp <- multiply width
-    populate data-addr, tmp
-  }
-  # screen->cursor-x = 0
-  dest <- get screen-addr, cursor-x
-  copy-to *dest, 0
-  # screen->cursor-y = 0
-  dest <- get screen-addr, cursor-y
-  copy-to *dest, 0
-}
-
-# in graphemes
-fn screen-size screen: (addr screen) -> _/eax: int, _/ecx: int {
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  compare screen, 0
-  {
-    break-if-!=
-    return 0x80/128, 0x30/48
-  }
-  # fake screen
-  var screen-addr/esi: (addr screen) <- copy screen
-  var tmp/edx: (addr int) <- get screen-addr, width
-  width <- copy *tmp
-  tmp <- get screen-addr, height
-  height <- copy *tmp
-  return width, height
-}
-
-# testable screen primitive
-fn draw-grapheme screen: (addr screen), g: grapheme, x: int, y: int, color: int, background-color: int {
-  {
-    compare screen, 0
-    break-if-!=
-    draw-grapheme-on-real-screen g, x, y, color, background-color
-    return
-  }
-  # fake screen
-  var screen-addr/esi: (addr screen) <- copy screen
-  var idx/ecx: int <- screen-cell-index screen-addr, x, y
-  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
-  var data/eax: (addr array screen-cell) <- lookup *data-ah
-  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
-  var dest-cell/ecx: (addr screen-cell) <- index data, offset
-  var dest-grapheme/eax: (addr grapheme) <- get dest-cell, data
-  var g2/edx: grapheme <- copy g
-  copy-to *dest-grapheme, g2
-  var dest-color/eax: (addr int) <- get dest-cell, color
-  var src-color/edx: int <- copy color
-  copy-to *dest-color, src-color
-  dest-color <- get dest-cell, background-color
-  src-color <- copy background-color
-  copy-to *dest-color, src-color
-}
-
-# we can't really render non-ASCII yet, but when we do we'll be ready
-fn draw-code-point screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int {
-  var g/eax: grapheme <- copy c
-  draw-grapheme screen, g, x, y, color, background-color
-}
-
-# not really needed for a real screen, though it shouldn't do any harm
-fn screen-cell-index screen-on-stack: (addr screen), x: int, y: int -> _/ecx: int {
-  var screen/esi: (addr screen) <- copy screen-on-stack
-  # only one bounds check isn't automatically handled
-  {
-    var xmax/eax: (addr int) <- get screen, width
-    var xcurr/ecx: int <- copy x
-    compare xcurr, *xmax
-    break-if-<
-    abort "tried to print out of screen bounds"
-  }
-  var width-addr/eax: (addr int) <- get screen, width
-  var result/ecx: int <- copy y
-  result <- multiply *width-addr
-  result <- add x
-  return result
-}
-
-fn cursor-position screen: (addr screen) -> _/eax: int, _/ecx: int {
-  {
-    compare screen, 0
-    break-if-!=
-    var x/eax: int <- copy 0
-    var y/ecx: int <- copy 0
-    x, y <- cursor-position-on-real-screen
-    return x, y
-  }
-  # fake screen
-  var screen-addr/esi: (addr screen) <- copy screen
-  var cursor-x-addr/eax: (addr int) <- get screen-addr, cursor-x
-  var cursor-y-addr/ecx: (addr int) <- get screen-addr, cursor-y
-  return *cursor-x-addr, *cursor-y-addr
-}
-
-fn set-cursor-position screen: (addr screen), x: int, y: int {
-  {
-    compare screen, 0
-    break-if-!=
-    set-cursor-position-on-real-screen x, y
-    return
-  }
-  # fake screen
-  var screen-addr/esi: (addr screen) <- copy screen
-  # ignore x < 0
-  {
-    compare x, 0
-    break-if->=
-    return
-  }
-  # ignore x >= width
-  {
-    var width-addr/eax: (addr int) <- get screen-addr, width
-    var width/eax: int <- copy *width-addr
-    compare x, width
-    break-if-<=
-    return
-  }
-  # ignore y < 0
-  {
-    compare y, 0
-    break-if->=
-    return
-  }
-  # ignore y >= height
-  {
-    var height-addr/eax: (addr int) <- get screen-addr, height
-    var height/eax: int <- copy *height-addr
-    compare y, height
-    break-if-<
-    return
-  }
-  # screen->cursor-x = x
-  var dest/edi: (addr int) <- get screen-addr, cursor-x
-  var src/eax: int <- copy x
-  copy-to *dest, src
-  # screen->cursor-y = y
-  dest <- get screen-addr, cursor-y
-  src <- copy y
-  copy-to *dest, src
-}
-
-fn show-cursor screen: (addr screen), g: grapheme {
-  {
-    compare screen, 0
-    break-if-!=
-    show-cursor-on-real-screen g
-    return
-  }
-  # fake screen
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  draw-grapheme screen, g, cursor-x, cursor-y, 0/fg, 7/bg
-}
-
-fn clear-screen screen: (addr screen) {
-  {
-    compare screen, 0
-    break-if-!=
-    clear-real-screen
-    return
-  }
-  # fake screen
-  set-cursor-position screen, 0, 0
-  var screen-addr/esi: (addr screen) <- copy screen
-  var y/eax: int <- copy 0
-  var height/ecx: (addr int) <- get screen-addr, height
-  {
-    compare y, *height
-    break-if->=
-    var x/edx: int <- copy 0
-    var width/ebx: (addr int) <- get screen-addr, width
-    {
-      compare x, *width
-      break-if->=
-      draw-code-point screen, 0x20/space, x, y, 0/fg=black, 0/bg=black
-      x <- increment
-      loop
-    }
-    y <- increment
-    loop
-  }
-  set-cursor-position screen, 0, 0
-}
-
-# there's no grapheme that guarantees to cover every pixel, so we'll bump down
-# to pixels for a real screen
-fn clear-real-screen {
-  var y/eax: int <- copy 0
-  {
-    compare y, 0x300/screen-height=768
-    break-if->=
-    var x/edx: int <- copy 0
-    {
-      compare x, 0x400/screen-width=1024
-      break-if->=
-      pixel-on-real-screen x, y, 0/color=black
-      x <- increment
-      loop
-    }
-    y <- increment
-    loop
-  }
-}
-
-fn screen-grapheme-at screen-on-stack: (addr screen), x: int, y: int -> _/eax: grapheme {
-  var screen-addr/esi: (addr screen) <- copy screen-on-stack
-  var idx/ecx: int <- screen-cell-index screen-addr, x, y
-  var result/eax: grapheme <- screen-grapheme-at-idx screen-addr, idx
-  return result
-}
-
-fn screen-grapheme-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> _/eax: grapheme {
-  var screen-addr/esi: (addr screen) <- copy screen-on-stack
-  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
-  var data/eax: (addr array screen-cell) <- lookup *data-ah
-  var idx/ecx: int <- copy idx-on-stack
-  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
-  var cell/eax: (addr screen-cell) <- index data, offset
-  var src/eax: (addr grapheme) <- get cell, data
-  return *src
-}
-
-fn screen-color-at screen-on-stack: (addr screen), x: int, y: int -> _/eax: int {
-  var screen-addr/esi: (addr screen) <- copy screen-on-stack
-  var idx/ecx: int <- screen-cell-index screen-addr, x, y
-  var result/eax: int <- screen-color-at-idx screen-addr, idx
-  return result
-}
-
-fn screen-color-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> _/eax: int {
-  var screen-addr/esi: (addr screen) <- copy screen-on-stack
-  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
-  var data/eax: (addr array screen-cell) <- lookup *data-ah
-  var idx/ecx: int <- copy idx-on-stack
-  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
-  var cell/eax: (addr screen-cell) <- index data, offset
-  var src/eax: (addr int) <- get cell, color
-  var result/eax: int <- copy *src
-  return result
-}
-
-fn screen-background-color-at screen-on-stack: (addr screen), x: int, y: int -> _/eax: int {
-  var screen-addr/esi: (addr screen) <- copy screen-on-stack
-  var idx/ecx: int <- screen-cell-index screen-addr, x, y
-  var result/eax: int <- screen-background-color-at-idx screen-addr, idx
-  return result
-}
-
-fn screen-background-color-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> _/eax: int {
-  var screen-addr/esi: (addr screen) <- copy screen-on-stack
-  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
-  var data/eax: (addr array screen-cell) <- lookup *data-ah
-  var idx/ecx: int <- copy idx-on-stack
-  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
-  var cell/eax: (addr screen-cell) <- index data, offset
-  var src/eax: (addr int) <- get cell, background-color
-  var result/eax: int <- copy *src
-  return result
-}
diff --git a/baremetal/501draw-text.mu b/baremetal/501draw-text.mu
deleted file mode 100644
index 9b207361..00000000
--- a/baremetal/501draw-text.mu
+++ /dev/null
@@ -1,466 +0,0 @@
-# some primitives for moving the cursor without making assumptions about
-# raster order
-fn move-cursor-left screen: (addr screen) {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  compare cursor-x, 0
-  {
-    break-if->
-    return
-  }
-  cursor-x <- decrement
-  set-cursor-position screen, cursor-x, cursor-y
-}
-
-fn move-cursor-right screen: (addr screen) {
-  var _width/eax: int <- copy 0
-  var dummy/ecx: int <- copy 0
-  _width, dummy <- screen-size screen
-  var limit/edx: int <- copy _width
-  limit <- decrement
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  compare cursor-x, limit
-  {
-    break-if-<
-    return
-  }
-  cursor-x <- increment
-  set-cursor-position screen, cursor-x, cursor-y
-}
-
-fn move-cursor-up screen: (addr screen) {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  compare cursor-y, 0
-  {
-    break-if->
-    return
-  }
-  cursor-y <- decrement
-  set-cursor-position screen, cursor-x, cursor-y
-}
-
-fn move-cursor-down screen: (addr screen) {
-  var dummy/eax: int <- copy 0
-  var _height/ecx: int <- copy 0
-  dummy, _height <- screen-size screen
-  var limit/edx: int <- copy _height
-  limit <- decrement
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  compare cursor-y, limit
-  {
-    break-if-<
-    return
-  }
-  cursor-y <- increment
-  set-cursor-position screen, cursor-x, cursor-y
-}
-
-fn move-cursor-to-start-of-next-line screen: (addr screen) {
-  var dummy/eax: int <- copy 0
-  var _height/ecx: int <- copy 0
-  dummy, _height <- screen-size screen
-  var limit/edx: int <- copy _height
-  limit <- decrement
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  compare cursor-y, limit
-  {
-    break-if-<
-    return
-  }
-  cursor-y <- increment
-  cursor-x <- copy 0
-  set-cursor-position screen, cursor-x, cursor-y
-}
-
-fn draw-grapheme-at-cursor screen: (addr screen), g: grapheme, color: int, background-color: int {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  draw-grapheme screen, g, cursor-x, cursor-y, color, background-color
-}
-
-# we can't really render non-ASCII yet, but when we do we'll be ready
-fn draw-code-point-at-cursor screen: (addr screen), c: code-point, color: int, background-color: int {
-  var g/eax: grapheme <- copy c
-  draw-grapheme-at-cursor screen, g, color, background-color
-}
-
-# draw a single line of text from x, y to xmax
-# return the next 'x' coordinate
-# if there isn't enough space, truncate
-fn draw-text-rightward screen: (addr screen), text: (addr array byte), x: int, xmax: int, y: int, color: int, background-color: int -> _/eax: int {
-  var stream-storage: (stream byte 0x100)
-  var stream/esi: (addr stream byte) <- address stream-storage
-  write stream, text
-  var xcurr/eax: int <- draw-stream-rightward screen, stream, x, xmax, y, color, background-color
-  return xcurr
-}
-
-# draw a single-line stream from x, y to xmax
-# return the next 'x' coordinate
-# if there isn't enough space, truncate
-fn draw-stream-rightward screen: (addr screen), stream: (addr stream byte), x: int, xmax: int, y: int, color: int, background-color: int -> _/eax: int {
-  var xcurr/ecx: int <- copy x
-  {
-    var g/eax: grapheme <- read-grapheme stream
-    compare g, 0xffffffff/end-of-file
-    break-if-=
-    draw-grapheme screen, g, xcurr, y, color, background-color
-    xcurr <- increment
-    loop
-  }
-  set-cursor-position screen, xcurr, y
-  return xcurr
-}
-
-fn draw-text-rightward-over-full-screen screen: (addr screen), text: (addr array byte), x: int, y: int, color: int, background-color: int -> _/eax: int {
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  width, height <- screen-size screen
-  var result/eax: int <- draw-text-rightward screen, text, x, width, y, color, background-color
-  return result
-}
-
-fn draw-text-rightward-from-cursor screen: (addr screen), text: (addr array byte), xmax: int, color: int, background-color: int {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  cursor-x <- draw-text-rightward screen, text, cursor-x, xmax, cursor-y, color, background-color
-  set-cursor-position screen, cursor-x, cursor-y
-}
-
-fn render-grapheme screen: (addr screen), g: grapheme, xmin: int, ymin: int, xmax: int, ymax: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  compare g, 0xa/newline
-  var x/eax: int <- copy x
-  {
-    break-if-!=
-    # minimum effort to clear cursor
-    draw-code-point screen, 0x20/space, x, y, color, background-color
-    x <- copy xmin
-    increment y
-    return x, y
-  }
-  draw-grapheme screen, g, x, y, color, background-color
-  x <- increment
-  compare x, xmax
-  {
-    break-if-<
-    x <- copy xmin
-    increment y
-  }
-  return x, y
-}
-
-# draw text in the rectangle from (xmin, ymin) to (xmax, ymax), starting from (x, y), wrapping as necessary
-# return the next (x, y) coordinate in raster order where drawing stopped
-# that way the caller can draw more if given the same min and max bounding-box.
-# if there isn't enough space, truncate
-fn draw-text-wrapping-right-then-down screen: (addr screen), text: (addr array byte), xmin: int, ymin: int, xmax: int, ymax: int, _x: int, _y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var stream-storage: (stream byte 0x100)
-  var stream/esi: (addr stream byte) <- address stream-storage
-  write stream, text
-  var x/eax: int <- copy _x
-  var y/ecx: int <- copy _y
-  x, y <- draw-stream-wrapping-right-then-down screen, stream, xmin, ymin, xmax, ymax, x, y, color, background-color
-  return x, y
-}
-
-# draw a stream in the rectangle from (xmin, ymin) to (xmax, ymax), starting from (x, y), wrapping as necessary
-# return the next (x, y) coordinate in raster order where drawing stopped
-# that way the caller can draw more if given the same min and max bounding-box.
-# if there isn't enough space, truncate
-fn draw-stream-wrapping-right-then-down screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var xcurr/eax: int <- copy x
-  var ycurr/ecx: int <- copy y
-  var g/ebx: grapheme <- copy 0
-  {
-    {
-      var _g/eax: grapheme <- read-grapheme stream
-      g <- copy _g
-    }
-    compare g, 0xffffffff/end-of-file
-    break-if-=
-    xcurr, ycurr <- render-grapheme screen, g, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
-    loop
-  }
-  set-cursor-position screen, xcurr, ycurr
-  return xcurr, ycurr
-}
-
-fn move-cursor-rightward-and-downward screen: (addr screen), xmin: int, xmax: int {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  cursor-x <- increment
-  compare cursor-x, xmax
-  {
-    break-if-<
-    cursor-x <- copy xmin
-    cursor-y <- increment
-  }
-  set-cursor-position screen, cursor-x, cursor-y
-}
-
-fn draw-text-wrapping-right-then-down-over-full-screen screen: (addr screen), text: (addr array byte), x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var x2/eax: int <- copy 0
-  var y2/ecx: int <- copy 0
-  x2, y2 <- screen-size screen  # width, height
-  x2, y2 <- draw-text-wrapping-right-then-down screen, text, 0/xmin, 0/ymin, x2, y2, x, y, color, background-color
-  return x2, y2  # cursor-x, cursor-y
-}
-
-fn draw-text-wrapping-right-then-down-from-cursor screen: (addr screen), text: (addr array byte), xmin: int, ymin: int, xmax: int, ymax: int, color: int, background-color: int {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  var end-x/edx: int <- copy cursor-x
-  end-x <- increment
-  compare end-x, xmax
-  {
-    break-if-<
-    cursor-x <- copy xmin
-    cursor-y <- increment
-  }
-  cursor-x, cursor-y <- draw-text-wrapping-right-then-down screen, text, xmin, ymin, xmax, ymax, cursor-x, cursor-y, color, background-color
-}
-
-fn draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen: (addr screen), text: (addr array byte), color: int, background-color: int {
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  width, height <- screen-size screen
-  draw-text-wrapping-right-then-down-from-cursor screen, text, 0/xmin, 0/ymin, width, height, color, background-color
-}
-
-fn draw-int32-hex-wrapping-right-then-down screen: (addr screen), n: int, xmin: int, ymin: int, xmax: int, ymax: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var stream-storage: (stream byte 0x100)
-  var stream/esi: (addr stream byte) <- address stream-storage
-  write-int32-hex stream, n
-  var xcurr/edx: int <- copy x
-  var ycurr/ecx: int <- copy y
-  {
-    var g/eax: grapheme <- read-grapheme stream
-    compare g, 0xffffffff/end-of-file
-    break-if-=
-    draw-grapheme screen, g, xcurr, ycurr, color, background-color
-    xcurr <- increment
-    compare xcurr, xmax
-    {
-      break-if-<
-      xcurr <- copy xmin
-      ycurr <- increment
-    }
-    loop
-  }
-  set-cursor-position screen, xcurr, ycurr
-  return xcurr, ycurr
-}
-
-fn draw-int32-hex-wrapping-right-then-down-over-full-screen screen: (addr screen), n: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var x2/eax: int <- copy 0
-  var y2/ecx: int <- copy 0
-  x2, y2 <- screen-size screen  # width, height
-  x2, y2 <- draw-int32-hex-wrapping-right-then-down screen, n, 0/xmin, 0/ymin, x2, y2, x, y, color, background-color
-  return x2, y2  # cursor-x, cursor-y
-}
-
-fn draw-int32-hex-wrapping-right-then-down-from-cursor screen: (addr screen), n: int, xmin: int, ymin: int, xmax: int, ymax: int, color: int, background-color: int {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  var end-x/edx: int <- copy cursor-x
-  end-x <- increment
-  compare end-x, xmax
-  {
-    break-if-<
-    cursor-x <- copy xmin
-    cursor-y <- increment
-  }
-  cursor-x, cursor-y <- draw-int32-hex-wrapping-right-then-down screen, n, xmin, ymin, xmax, ymax, cursor-x, cursor-y, color, background-color
-}
-
-fn draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen: (addr screen), n: int, color: int, background-color: int {
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  width, height <- screen-size screen
-  draw-int32-hex-wrapping-right-then-down-from-cursor screen, n, 0/xmin, 0/ymin, width, height, color, background-color
-}
-
-fn draw-int32-decimal-wrapping-right-then-down screen: (addr screen), n: int, xmin: int, ymin: int, xmax: int, ymax: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var stream-storage: (stream byte 0x100)
-  var stream/esi: (addr stream byte) <- address stream-storage
-  write-int32-decimal stream, n
-  var xcurr/edx: int <- copy x
-  var ycurr/ecx: int <- copy y
-  {
-    var g/eax: grapheme <- read-grapheme stream
-    compare g, 0xffffffff/end-of-file
-    break-if-=
-    draw-grapheme screen, g, xcurr, ycurr, color, background-color
-    xcurr <- increment
-    compare xcurr, xmax
-    {
-      break-if-<
-      xcurr <- copy xmin
-      ycurr <- increment
-    }
-    loop
-  }
-  set-cursor-position screen, xcurr, ycurr
-  return xcurr, ycurr
-}
-
-fn draw-int32-decimal-wrapping-right-then-down-over-full-screen screen: (addr screen), n: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var x2/eax: int <- copy 0
-  var y2/ecx: int <- copy 0
-  x2, y2 <- screen-size screen  # width, height
-  x2, y2 <- draw-int32-decimal-wrapping-right-then-down screen, n, 0/xmin, 0/ymin, x2, y2, x, y, color, background-color
-  return x2, y2  # cursor-x, cursor-y
-}
-
-fn draw-int32-decimal-wrapping-right-then-down-from-cursor screen: (addr screen), n: int, xmin: int, ymin: int, xmax: int, ymax: int, color: int, background-color: int {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  var end-x/edx: int <- copy cursor-x
-  end-x <- increment
-  compare end-x, xmax
-  {
-    break-if-<
-    cursor-x <- copy xmin
-    cursor-y <- increment
-  }
-  cursor-x, cursor-y <- draw-int32-decimal-wrapping-right-then-down screen, n, xmin, ymin, xmax, ymax, cursor-x, cursor-y, color, background-color
-}
-
-fn draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen: (addr screen), n: int, color: int, background-color: int {
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  width, height <- screen-size screen
-  draw-int32-decimal-wrapping-right-then-down-from-cursor screen, n, 0/xmin, 0/ymin, width, height, color, background-color
-}
-
-## Text direction: down then right
-
-# draw a single line of text vertically from x, y to ymax
-# return the next 'y' coordinate
-# if there isn't enough space, truncate
-fn draw-text-downward screen: (addr screen), text: (addr array byte), x: int, y: int, ymax: int, color: int, background-color: int -> _/eax: int {
-  var stream-storage: (stream byte 0x100)
-  var stream/esi: (addr stream byte) <- address stream-storage
-  write stream, text
-  var ycurr/eax: int <- draw-stream-downward screen, stream, x, y, ymax, color, background-color
-  return ycurr
-}
-
-# draw a single-line stream vertically from x, y to ymax
-# return the next 'y' coordinate
-# if there isn't enough space, truncate
-fn draw-stream-downward screen: (addr screen), stream: (addr stream byte), x: int, y: int, ymax: int, color: int, background-color: int -> _/eax: int {
-  var ycurr/ecx: int <- copy y
-  {
-    var g/eax: grapheme <- read-grapheme stream
-    compare g, 0xffffffff/end-of-file
-    break-if-=
-    draw-grapheme screen, g, x, ycurr, color, background-color
-    ycurr <- increment
-    loop
-  }
-  set-cursor-position screen, x, ycurr
-  return ycurr
-}
-
-fn draw-text-downward-from-cursor screen: (addr screen), text: (addr array byte), ymax: int, color: int, background-color: int {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  var result/eax: int <- draw-text-downward screen, text, cursor-x, cursor-y, ymax, color, background-color
-}
-
-# draw text down and right in the rectangle from (xmin, ymin) to (xmax, ymax), starting from (x, y), wrapping as necessary
-# return the next (x, y) coordinate in raster order where drawing stopped
-# that way the caller can draw more if given the same min and max bounding-box.
-# if there isn't enough space, truncate
-fn draw-text-wrapping-down-then-right screen: (addr screen), text: (addr array byte), xmin: int, ymin: int, xmax: int, ymax: int, _x: int, _y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var stream-storage: (stream byte 0x100)
-  var stream/esi: (addr stream byte) <- address stream-storage
-  write stream, text
-  var x/eax: int <- copy _x
-  var y/ecx: int <- copy _y
-  x, y <- draw-stream-wrapping-down-then-right screen, stream, xmin, ymin, xmax, ymax, x, y, color, background-color
-  return x, y
-}
-
-# draw a stream down and right in the rectangle from (xmin, ymin) to (xmax, ymax), starting from (x, y), wrapping as necessary
-# return the next (x, y) coordinate in raster order where drawing stopped
-# that way the caller can draw more if given the same min and max bounding-box.
-# if there isn't enough space, truncate
-fn draw-stream-wrapping-down-then-right screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var xcurr/edx: int <- copy x
-  var ycurr/ecx: int <- copy y
-  {
-    var g/eax: grapheme <- read-grapheme stream
-    compare g, 0xffffffff/end-of-file
-    break-if-=
-    draw-grapheme screen, g, xcurr, ycurr, color, background-color
-    ycurr <- increment
-    compare ycurr, ymax
-    {
-      break-if-<
-      xcurr <- increment
-      ycurr <- copy ymin
-    }
-    loop
-  }
-  set-cursor-position screen, xcurr, ycurr
-  return xcurr, ycurr
-}
-
-fn draw-text-wrapping-down-then-right-over-full-screen screen: (addr screen), text: (addr array byte), x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var x2/eax: int <- copy 0
-  var y2/ecx: int <- copy 0
-  x2, y2 <- screen-size screen  # width, height
-  x2, y2 <- draw-text-wrapping-down-then-right screen, text, 0/xmin, 0/ymin, x2, y2, x, y, color, background-color
-  return x2, y2  # cursor-x, cursor-y
-}
-
-fn draw-text-wrapping-down-then-right-from-cursor screen: (addr screen), text: (addr array byte), xmin: int, ymin: int, xmax: int, ymax: int, color: int, background-color: int {
-  var cursor-x/eax: int <- copy 0
-  var cursor-y/ecx: int <- copy 0
-  cursor-x, cursor-y <- cursor-position screen
-  var end-y/edx: int <- copy cursor-y
-  end-y <- increment
-  compare end-y, ymax
-  {
-    break-if-<
-    cursor-x <- increment
-    cursor-y <- copy ymin
-  }
-  cursor-x, cursor-y <- draw-text-wrapping-down-then-right screen, text, xmin, ymin, xmax, ymax, cursor-x, cursor-y, color, background-color
-}
-
-fn draw-text-wrapping-down-then-right-from-cursor-over-full-screen screen: (addr screen), text: (addr array byte), color: int, background-color: int {
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  width, height <- screen-size screen
-  draw-text-wrapping-down-then-right-from-cursor screen, text, 0/xmin, 0/ymin, width, height, color, background-color
-}
-
-# hacky error-handling
-# just go into an infinite loop
-fn abort e: (addr array byte) {
-  var dummy1/eax: int <- copy 0
-  var dummy2/ecx: int <- copy 0
-  dummy1, dummy2 <- draw-text-wrapping-right-then-down-over-full-screen 0/screen, e, 0/x, 0x2f/y, 0xf/fg=white, 0xc/bg=red
-  {
-    loop
-  }
-}
diff --git a/baremetal/502test.mu b/baremetal/502test.mu
deleted file mode 100644
index 00d55d34..00000000
--- a/baremetal/502test.mu
+++ /dev/null
@@ -1,43 +0,0 @@
-# print msg to screen if a != b, otherwise print "."
-fn check-ints-equal _a: int, b: int, msg: (addr array byte) {
-  var a/eax: int <- copy _a
-  compare a, b
-  {
-    break-if-!=
-    draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
-    return
-  }
-  draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-  move-cursor-to-start-of-next-line 0/screen
-  count-test-failure
-}
-
-fn test-check-ints-equal {
-  check-ints-equal 0, 0, "abc"
-}
-
-fn check _a: boolean, msg: (addr array byte) {
-  var a/eax: int <- copy _a
-  compare a, 0/false
-  {
-    break-if-=
-    draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
-    return
-  }
-  draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-  move-cursor-to-start-of-next-line 0/screen
-  count-test-failure
-}
-
-fn check-not _a: boolean, msg: (addr array byte) {
-  var a/eax: int <- copy _a
-  compare a, 0/false
-  {
-    break-if-!=
-    draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
-    return
-  }
-  draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-  move-cursor-to-start-of-next-line 0/screen
-  count-test-failure
-}
diff --git a/baremetal/503manhattan-line.mu b/baremetal/503manhattan-line.mu
deleted file mode 100644
index 5c51473d..00000000
--- a/baremetal/503manhattan-line.mu
+++ /dev/null
@@ -1,28 +0,0 @@
-fn draw-box-on-real-screen x1: int, y1: int, x2: int, y2: int, color: int {
-  draw-horizontal-line-on-real-screen x1, x2, y1, color
-  draw-vertical-line-on-real-screen x1, y1, y2, color
-  draw-horizontal-line-on-real-screen x1, x2, y2, color
-  draw-vertical-line-on-real-screen x2, y1, y2, color
-}
-
-fn draw-horizontal-line-on-real-screen x1: int, x2: int, y: int, color: int {
-  var x/eax: int <- copy x1
-  {
-    compare x, x2
-    break-if->=
-    pixel-on-real-screen x, y, color
-    x <- increment
-    loop
-  }
-}
-
-fn draw-vertical-line-on-real-screen x: int, y1: int, y2: int, color: int {
-  var y/eax: int <- copy y1
-  {
-    compare y, y2
-    break-if->=
-    pixel-on-real-screen x, y, color
-    y <- increment
-    loop
-  }
-}
diff --git a/baremetal/504test-screen.mu b/baremetal/504test-screen.mu
deleted file mode 100644
index a409443b..00000000
--- a/baremetal/504test-screen.mu
+++ /dev/null
@@ -1,327 +0,0 @@
-# Some primitives for checking the state of fake screen objects.
-
-# validate data on screen regardless of attributes (color, bold, etc.)
-# Mu doesn't have multi-line strings, so we provide functions for rows or portions of rows.
-# Tab characters (that translate into multiple screen cells) not supported.
-
-fn check-screen-row screen: (addr screen), y: int, expected: (addr array byte), msg: (addr array byte) {
-  check-screen-row-from screen, 0/x, y, expected, msg
-}
-
-fn check-screen-row-from screen-on-stack: (addr screen), x: int, y: int, expected: (addr array byte), msg: (addr array byte) {
-  var screen/esi: (addr screen) <- copy screen-on-stack
-  var idx/ecx: int <- screen-cell-index screen, x, y
-  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
-  var e: (stream byte 0x100)
-  var e-addr/edx: (addr stream byte) <- address e
-  write e-addr, expected
-  {
-    var done?/eax: boolean <- stream-empty? e-addr
-    compare done?, 0
-    break-if-!=
-    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
-    var g/ebx: grapheme <- copy _g
-    var expected-grapheme/eax: grapheme <- read-grapheme e-addr
-    # compare graphemes
-    $check-screen-row-from:compare-graphemes: {
-      # if expected-grapheme is space, null grapheme is also ok
-      {
-        compare expected-grapheme, 0x20
-        break-if-!=
-        compare g, 0
-        break-if-= $check-screen-row-from:compare-graphemes
-      }
-      # if (g == expected-grapheme) print "."
-      compare g, expected-grapheme
-      {
-        break-if-!=
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
-        break $check-screen-row-from:compare-graphemes
-      }
-      # otherwise print an error
-      count-test-failure
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-      draw-grapheme-at-cursor 0/screen, expected-grapheme, 3/cyan, 0/bg
-      move-cursor-rightward-and-downward 0/screen, 0/xmin, 0x80/xmax=screen-width
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
-      draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
-      draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") but observed '", 3/fg/cyan, 0/bg
-      draw-grapheme-at-cursor 0/screen, g, 3/cyan, 0/bg
-      move-cursor-rightward-and-downward 0/screen, 0/xmin, 0x80/xmax=screen-width
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "'", 3/fg/cyan, 0/bg
-      move-cursor-to-start-of-next-line 0/screen
-    }
-    idx <- increment
-    increment x
-    loop
-  }
-}
-
-# various variants by screen-cell attribute; spaces in the 'expected' data should not match the attribute
-
-fn check-screen-row-in-color screen: (addr screen), fg: int, y: int, expected: (addr array byte), msg: (addr array byte) {
-  check-screen-row-in-color-from screen, fg, y, 0/x, expected, msg
-}
-
-fn check-screen-row-in-color-from screen-on-stack: (addr screen), fg: int, y: int, x: int, expected: (addr array byte), msg: (addr array byte) {
-  var screen/esi: (addr screen) <- copy screen-on-stack
-  var idx/ecx: int <- screen-cell-index screen, x, y
-  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
-  var e: (stream byte 0x100)
-  var e-addr/edx: (addr stream byte) <- address e
-  write e-addr, expected
-  {
-    var done?/eax: boolean <- stream-empty? e-addr
-    compare done?, 0
-    break-if-!=
-    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
-    var g/ebx: grapheme <- copy _g
-    var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
-    var expected-grapheme/edi: grapheme <- copy _expected-grapheme
-    $check-screen-row-in-color-from:compare-cells: {
-      # if expected-grapheme is space, null grapheme is also ok
-      {
-        compare expected-grapheme, 0x20
-        break-if-!=
-        compare g, 0
-        break-if-= $check-screen-row-in-color-from:compare-cells
-      }
-      # if expected-grapheme is space, a different color is ok
-      {
-        compare expected-grapheme, 0x20
-        break-if-!=
-        var color/eax: int <- screen-color-at-idx screen, idx
-        compare color, fg
-        break-if-!= $check-screen-row-in-color-from:compare-cells
-      }
-      # compare graphemes
-      $check-screen-row-in-color-from:compare-graphemes: {
-        # if (g == expected-grapheme) print "."
-        compare g, expected-grapheme
-        {
-          break-if-!=
-          draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
-          break $check-screen-row-in-color-from:compare-graphemes
-        }
-        # otherwise print an error
-        count-test-failure
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-        draw-grapheme-at-cursor 0/screen, expected-grapheme, 3/cyan, 0/bg
-        move-cursor-rightward-and-downward 0/screen, 0/xmin, 0x80/xmax=screen-width
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") but observed '", 3/fg/cyan, 0/bg
-        draw-grapheme-at-cursor 0/screen, g, 3/cyan, 0/bg
-        move-cursor-rightward-and-downward 0/screen, 0/xmin, 0x80/xmax=screen-width
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "'", 3/fg/cyan, 0/bg
-        move-cursor-to-start-of-next-line 0/screen
-      }
-      $check-screen-row-in-color-from:compare-colors: {
-        var color/eax: int <- screen-color-at-idx screen, idx
-        compare fg, color
-        {
-          break-if-!=
-          draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
-          break $check-screen-row-in-color-from:compare-colors
-        }
-        # otherwise print an error
-        count-test-failure
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-        draw-grapheme-at-cursor 0/screen, expected-grapheme, 3/cyan, 0/bg
-        move-cursor-rightward-and-downward 0/screen, 0/xmin, 0x80/xmax=screen-width
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") in color ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, fg, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, " but observed color ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, color, 3/fg/cyan, 0/bg
-        move-cursor-to-start-of-next-line 0/screen
-      }
-    }
-    idx <- increment
-    increment x
-    loop
-  }
-}
-
-fn check-screen-row-in-background-color screen: (addr screen), bg: int, y: int, expected: (addr array byte), msg: (addr array byte) {
-  check-screen-row-in-background-color-from screen, bg, y, 0/x, expected, msg
-}
-
-fn check-screen-row-in-background-color-from screen-on-stack: (addr screen), bg: int, y: int, x: int, expected: (addr array byte), msg: (addr array byte) {
-  var screen/esi: (addr screen) <- copy screen-on-stack
-  var idx/ecx: int <- screen-cell-index screen, x, y
-  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
-  var e: (stream byte 0x100)
-  var e-addr/edx: (addr stream byte) <- address e
-  write e-addr, expected
-  {
-    var done?/eax: boolean <- stream-empty? e-addr
-    compare done?, 0
-    break-if-!=
-    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
-    var g/ebx: grapheme <- copy _g
-    var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
-    var expected-grapheme/edi: grapheme <- copy _expected-grapheme
-    $check-screen-row-in-background-color-from:compare-cells: {
-      # if expected-grapheme is space, null grapheme is also ok
-      {
-        compare expected-grapheme, 0x20
-        break-if-!=
-        compare g, 0
-        break-if-= $check-screen-row-in-background-color-from:compare-cells
-      }
-      # if expected-grapheme is space, a different background-color is ok
-      {
-        compare expected-grapheme, 0x20
-        break-if-!=
-        var background-color/eax: int <- screen-background-color-at-idx screen, idx
-        compare background-color, bg
-        break-if-!= $check-screen-row-in-background-color-from:compare-cells
-      }
-      # compare graphemes
-      $check-screen-row-in-background-color-from:compare-graphemes: {
-        # if (g == expected-grapheme) print "."
-        compare g, expected-grapheme
-        {
-          break-if-!=
-          draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
-          break $check-screen-row-in-background-color-from:compare-graphemes
-        }
-        # otherwise print an error
-        count-test-failure
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-        draw-grapheme-at-cursor 0/screen, expected-grapheme, 3/cyan, 0/bg
-        move-cursor-rightward-and-downward 0/screen, 0/xmin, 0x80/xmax=screen-width
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") but observed '", 3/fg/cyan, 0/bg
-        draw-grapheme-at-cursor 0/screen, g, 3/cyan, 0/bg
-        move-cursor-rightward-and-downward 0/screen, 0/xmin, 0x80/xmax=screen-width
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "'", 3/fg/cyan, 0/bg
-        move-cursor-to-start-of-next-line 0/screen
-        break $check-screen-row-in-background-color-from:compare-graphemes
-      }
-      $check-screen-row-in-background-color-from:compare-background-colors: {
-        var background-color/eax: int <- screen-background-color-at-idx screen, idx
-        compare bg, background-color
-        {
-          break-if-!=
-          draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
-          break $check-screen-row-in-background-color-from:compare-background-colors
-        }
-        # otherwise print an error
-        count-test-failure
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-        draw-grapheme-at-cursor 0/screen, expected-grapheme, 3/cyan, 0/bg
-        move-cursor-rightward-and-downward 0/screen, 0/xmin, 0x80/xmax=screen-width
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") in background-color ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, bg, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, " but observed background-color ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, background-color, 3/fg/cyan, 0/bg
-        move-cursor-to-start-of-next-line 0/screen
-      }
-    }
-    idx <- increment
-    increment x
-    loop
-  }
-}
-
-# helpers for checking just background color, not screen contents
-# these can validate bg for spaces
-
-fn check-background-color-in-screen-row screen: (addr screen), bg: int, y: int, expected-bitmap: (addr array byte), msg: (addr array byte) {
-  check-background-color-in-screen-row-from screen, bg, y, 0/x, expected-bitmap, msg
-}
-
-fn check-background-color-in-screen-row-from screen-on-stack: (addr screen), bg: int, y: int, x: int, expected-bitmap: (addr array byte), msg: (addr array byte) {
-  var screen/esi: (addr screen) <- copy screen-on-stack
-  var idx/ecx: int <- screen-cell-index screen, x, y
-  # compare background color where 'expected-bitmap' is a non-space
-  var e: (stream byte 0x100)
-  var e-addr/edx: (addr stream byte) <- address e
-  write e-addr, expected-bitmap
-  {
-    var done?/eax: boolean <- stream-empty? e-addr
-    compare done?, 0
-    break-if-!=
-    var _expected-bit/eax: grapheme <- read-grapheme e-addr
-    var expected-bit/edi: grapheme <- copy _expected-bit
-    $check-background-color-in-screen-row-from:compare-cells: {
-      var background-color/eax: int <- screen-background-color-at-idx screen, idx
-      # if expected-bit is space, assert that background is NOT bg
-      compare expected-bit, 0x20
-      {
-        break-if-!=
-        compare background-color, bg
-        break-if-!= $check-background-color-in-screen-row-from:compare-cells
-        count-test-failure
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected (", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
-        draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") to not be in background-color ", 3/fg/cyan, 0/bg
-        draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, bg, 3/fg/cyan, 0/bg
-        move-cursor-to-start-of-next-line 0/screen
-        break $check-background-color-in-screen-row-from:compare-cells
-      }
-      # otherwise assert that background IS bg
-      compare background-color, bg
-      break-if-= $check-background-color-in-screen-row-from:compare-cells
-      count-test-failure
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected (", 3/fg/cyan, 0/bg
-      draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
-      draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") in background-color ", 3/fg/cyan, 0/bg
-      draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, bg, 3/fg/cyan, 0/bg
-      draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, " but observed background-color ", 3/fg/cyan, 0/bg
-      draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, background-color, 3/fg/cyan, 0/bg
-      move-cursor-to-start-of-next-line 0/screen
-    }
-    idx <- increment
-    increment x
-    loop
-  }
-}
-
-fn test-draw-single-grapheme {
-  var screen-on-stack: screen
-  var screen/esi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5, 4
-  draw-code-point screen, 0x61/a, 0/x, 0/y, 1/fg, 2/bg
-  check-screen-row screen, 0/y, "a", "F - test-draw-single-grapheme"  # top-left corner of the screen
-  check-screen-row-in-color screen, 1/fg, 0/y, "a", "F - test-draw-single-grapheme-fg"
-  check-screen-row-in-background-color screen, 2/bg, 0/y, "a", "F - test-draw-single-grapheme-bg"
-  check-background-color-in-screen-row screen, 2/bg, 0/y, "x ", "F - test-draw-single-grapheme-bg2"
-}
-
-fn test-draw-multiple-graphemes {
-  var screen-on-stack: screen
-  var screen/esi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/rows, 4/cols
-  draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "Hello, 世界", 1/fg, 2/bg
-  check-screen-row screen, 0/y, "Hello, 世界", "F - test-draw-multiple-graphemes"
-  check-screen-row-in-color screen, 1/fg, 0/y, "Hello, 世界", "F - test-draw-multiple-graphemes-fg"
-  check-background-color-in-screen-row screen, 2/bg, 0/y, "xxxxxxxxx ", "F - test-draw-multiple-graphemes-bg2"
-}
diff --git a/baremetal/README.md b/baremetal/README.md
deleted file mode 100644
index 5cdb75ed..00000000
--- a/baremetal/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-Some apps written in SubX and Mu. Where the rest of this repo relies on a few
-Linux syscalls, the apps in this subdirectory interface directly with hardware.
-We still need the top-level and apps to build them.
-
-I'd like to eventually test these programs on real hardware, and to that end
-they are extremely parsimonious in the hardware they assume:
-
-  0. Lots (more than 640KB/1MB[1]) of RAM
-  1. Pure-graphics video mode (1024x768 pixels) in 256-color mode. At 8x8
-     pixels per grapheme, this will give us 160x128 graphemes. But it's still
-     an open question if it's reasonably widely supported by modern hardware.
-     If it isn't, I'll downsize.
-  2. Keyboard. Just a partial US keyboard for now. Main qwerty zone only. No
-     number pad, no function keys, no ctrl/alt/meta/fn/super/capslck/numlck.
-
-That's it:
-  * No wifi, no networking
-  * No multitouch, no touchscreen, no mouse
-  * No graphics acceleration
-  * No virtual memory, no memory reclamation
-
-Just your processor, gigabytes of RAM[1], a moderately-sized monitor and a
-keyboard. (The mouse should also be easy to provide.)
-
-We can't yet read from or write to disk, except for the initial load of the
-program. Enabling access to lots of RAM gives up access to BIOS helpers for
-the disk.
-
-These programs don't convert to formats like ELF that can load on other
-operating systems. There's also currently no code/data segment separation,
-just labels and bytes. I promise not to write self-modifying code. Security
-and sandboxing is still an open question.
-
-Programs start executing at address 0x9400. See baremetal/boot.hex for
-details.
-
-Mu programs always run all their automated tests first. `main` only runs if
-there are no failing tests. See baremetal/mu-init.subx for details.
-
-So far the programs have only been tested in Qemu and Bochs emulators.
-
-[1] Though we might need to start thinking of [the PC memory map](https://wiki.osdev.org/Memory_Map_(x86))
-as our programs grow past the first 32MB of memory. Mu doesn't yet make any
-attempt to understand how much RAM the underlying computer has. Also, writing
-to random locations can damage hardware or corrupt storage devices.
diff --git a/baremetal/boot.bochsrc b/baremetal/boot.bochsrc
deleted file mode 100644
index 9a02d67c..00000000
--- a/baremetal/boot.bochsrc
+++ /dev/null
@@ -1,15 +0,0 @@
-# Configuration for the Bochs x86 CPU emulator to run baremetal Mu programs
-# See baremetal/boot.hex for more details.
-#
-# Installing Bochs:
-#   On Mac OS:
-#     brew install bochs
-#   On Ubuntu Linux 20.04:
-#     sudo apt install bochs bochs-sdl bochsbios vgabios
-
-display_library: sdl2
-
-ata0-master: type=disk, path="disk.img", mode=flat, cylinders=20, heads=16, spt=63  # 10MB, 512 bytes per sector
-boot: disk
-# PS/2 mouse requires black magic that I don't know how to explain.
-log: -
diff --git a/baremetal/boot.hex b/baremetal/boot.hex
deleted file mode 100644
index 6fa874c2..00000000
--- a/baremetal/boot.hex
+++ /dev/null
@@ -1,1181 +0,0 @@
-# Code for the first few disk sectors that all programs in this directory need:
-#   - load sectors past the first (using BIOS primitives) since only the first is available by default
-#     - if this fails, print 'D' at top-left of screen and halt
-#   - initialize a minimal graphics mode
-#   - switch to 32-bit mode (giving up access to BIOS primitives)
-#   - set up a handler for keyboard events
-#   - jump to start of program
-#
-# To convert to a disk image, first prepare a realistically sized disk image:
-#   dd if=/dev/zero of=disk.img count=20160  # 512-byte sectors, so 10MB
-# Create initial sectors from this file:
-#   ./bootstrap run apps/hex < baremetal/boot.hex > boot.bin
-# Translate other sectors into a file called a.img
-# Load all sectors into the disk image:
-#   cat boot.bin a.img > disk.bin
-#   dd if=disk.bin of=disk.img conv=notrunc
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc  # boot.bochsrc loads disk.img
-#
-# Since we start out in 16-bit mode, we need instructions SubX doesn't
-# support.
-# This file contains just lowercase hex bytes and comments. Programming it
-# requires liberal use of:
-#   - comments documenting expected offsets
-#   - size checks on the emitted file (currently: 6144 bytes)
-#   - xxd to spot-check contents of specific offsets in the generated output
-#
-# Programs using this initialization:
-#   - can't use any syscalls
-#   - can't print text to video memory (past these boot sectors)
-#   - must only print raw pixels (256 colors) to video memory (resolution 1024x768)
-#   - must start executing immediately after this file (see outline below)
-#
-# Don't panic! This file doesn't contain any loops or function calls. 80% of
-# it is data. One pass through less than 1KB of code (there's lots of
-# padding), and then we jump into a better notation. The rest of the stack
-# (really only in a couple of slightly higher-level places) needs to know just
-# a few magic constants:
-#   Video memory: start is stored at 0x8128
-#   Keyboard buffer: starts at 0x8028
-#
-# No mouse support. _That_ would require panicking.
-
-# Outline of this file with offsets and the addresses they map to at run-time:
-# -- 16-bit mode code
-#   offset    0 (address 7c00): boot code
-# -- 16-bit mode data
-#            e0 (address 7c80) global descriptor table
-#            f8 (address 7ca0) <== gdt_descriptor
-# -- 32-bit mode code
-#   offset  100 (address 7d00): boot code
-#           1fe (address 7dfe) boot sector marker (2 bytes)
-#   offset  200 (address 7e00): interrupt handler code
-# -- 32-bit mode data
-#   offset  400 (address 8000): handler data
-#           410 (address 8010): keyboard handler data
-#           428 (address 8028) <== keyboard buffer
-#   offset  500 (address 8100): video mode data (256 bytes)
-#           528 (address 8128) <== start of video RAM stored here
-#   offset  600 (address 8200): interrupt descriptor table (1KB)
-#   offset  a00 (address 8600): keyboard mappings (1.5KB)
-#   offset 1000 (address 8c00): bitmap font (2KB)
-#   offset 1800 (address 9400): entrypoint for applications (don't forget to adjust survey_baremetal if this changes)
-
-# Other details of the current memory map:
-#   code: 4 tracks of disk to [0x00007c00, 0x00027400)
-#   stack grows down from 0x00070000
-#     see below
-#   heap: [0x01000000, 0x02000000)
-#     see baremetal/120allocate.subx
-# Consult https://wiki.osdev.org/Memory_Map_(x86) before modifying any of this.
-
-## 16-bit entry point
-
-# Upon reset, the IBM PC:
-#   - loads the first sector (512 bytes)
-#     from some bootable image (see the boot sector marker at the end of this file)
-#     to the address range [0x7c00, 0x7e00)
-#   - starts executing code at address 0x7c00
-
-# offset 00 (address 0x7c00):
-  # disable interrupts for this initialization
-  fa  # cli
-
-  # initialize segment registers
-  # this isn't always needed, but the recommendation is to not make assumptions
-  b8 00 00  # ax <- 0
-  8e d8  # ds <- ax
-  8e c0  # es <- ax
-  8e e0  # fs <- ax
-  8e e8  # gs <- ax
-
-  # initialize stack to 0x00070000
-  # We don't read or write the stack before we get to 32-bit mode, but BIOS
-  # calls do. We need to move the stack in case BIOS initializes it to some
-  # low address that we want to write code into.
-  b8 00 70  # ax <- 0x7000
-  8e d0  # ss <- ax
-  bc 00 00  # sp <- 0x0000
-
-  # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line
-  # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S
-  # seta20.1:
-  e4 64  # al <- port 0x64
-  a8 02  # set zf if bit 1 (second-least significant) is not set
-  75 fa  # if zf not set, goto seta20.1 (-6)
-  b0 d1  # al <- 0xd1
-  e6 64  # port 0x64 <- al
-  # seta20.2:
-  e4 64  # al <- port 0x64
-  a8 02  # set zf if bit 1 (second-least significant) is not set
-  75 fa  # if zf not set, goto seta20.2 (-6)
-  b0 df  # al <- 0xdf
-  e6 64  # port 0x64 <- al
-
-  # load remaining sectors from first two tracks of disk into addresses [0x7e00, 0x17800)
-  b4 02  # ah <- 2  # read sectors from disk
-  # dl comes conveniently initialized at boot time with the index of the device being booted
-  b5 00  # ch <- 0  # cylinder 0
-  b6 00  # dh <- 0  # track 0
-  b1 02  # cl <- 2  # second sector, 1-based
-  b0 7d  # al <- 125  # number of sectors to read = 2*63 - 1
-  # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment
-  bb 00 00  # bx <- 0
-  8e c3  # es <- bx
-  bb 00 7e  # bx <- 0x7e00 [label]
-  cd 13  # int 13h, BIOS disk service
-  0f 82 a3 00  # jump-if-carry disk_error [label]
-
-  # load two more tracks of disk into addresses [0x17800, 0x27400)
-  b4 02  # ah <- 2  # read sectors from disk
-  # dl comes conveniently initialized at boot time with the index of the device being booted
-  b5 00  # ch <- 0  # cylinder 0
-  b6 02  # dh <- 2  # track 0
-  b1 01  # cl <- 1  # first sector, 1-based
-  b0 7e  # al <- 126  # number of sectors to read = 2*63
-  # address to write sectors to = es:bx = 0x17800
-  bb 80 17  # bx <- 0x1780 [label]
-  8e c3  # es <- bx
-  bb 00 00  # bx <- 0
-  cd 13  # int 13h, BIOS disk service
-  0f 82 9b 00  # jump-if-carry disk_error [label]
-
-  # load two more tracks of disk into addresses [0x27400, 0x37000)
-  b4 02  # ah <- 2  # read sectors from disk
-  # dl comes conveniently initialized at boot time with the index of the device being booted
-  b5 00  # ch <- 0  # cylinder 0
-  b6 02  # dh <- 2  # track 0
-  b1 01  # cl <- 1  # first sector, 1-based
-  b0 7e  # al <- 126  # number of sectors to read = 2*63
-  # address to write sectors to = es:bx = 0x17800
-  bb 80 17  # bx <- 0x1780 [label]
-  8e c3  # es <- bx
-  bb 00 00  # bx <- 0
-  cd 13  # int 13h, BIOS disk service
-  0f 82 9b 00  # jump-if-carry disk_error [label]
-
-  # reset es
-  bb 00 00  # bx <- 0
-  8e c3  # es <- bx
-
-  # adjust video mode
-  b4 4f  # ah <- 4f (VBE)
-  b0 02  # al <- 02 (set video mode)
-  bb 05 41  # bx <- 0x0105 (graphics 1024x768x256
-            #               0x4000 bit = configure linear frame buffer in Bochs emulator; hopefully this doesn't hurt anything when running natively)
-            # fallback mode: 0x0101 (640x480x256)
-  cd 10  # int 10h, Vesa BIOS extensions
-
-  # load information for the (hopefully) current video mode
-  # mostly just for the address to the linear frame buffer
-  b4 4f  # ah <- 4f (VBE)
-  b0 01  # al <- 01 (get video mode info)
-  b9 07 01  # cx <- 0x0107 (mode we requested)
-  bf 00 81  # di <- 0x8100 (video mode info) [label]
-  cd 10
-
-  # switch to 32-bit mode
-  0f 01 16  # lgdt 00/mod/indirect 010/subop 110/rm/use-disp16
-    f8 7c  # *gdt_descriptor [label]
-  0f 20 c0  # eax <- cr0
-  66 83 c8 01  # eax <- or 0x1
-  0f 22 c0  # cr0 <- eax
-  ea 00 7d 08 00  # far jump to initialize_32bit_mode after setting cs to the record at offset 8 in the gdt (gdt_code) [label]
-
-# padding
-# 8e:
-                                          00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# cf:
-# disk_error:
-  # print 'D' to top-left of screen to indicate disk error
-  # *0xb8000 <- 0x0f44
-  # bx <- 0xb800
-  bb 00 b8
-  # ds <- bx
-  8e db  # 11b/mod 011b/reg/ds 011b/rm/bx
-  # al <- 'D'
-  b0 44
-  # ah <- 0x0f  # white on black
-  b4 0f
-  # bx <- 0
-  bb 00 00
-  # *ds:bx <- ax
-  89 07  # 00b/mod/indirect 000b/reg/ax 111b/rm/bx
-
-e9 fd ff  # loop forever
-
-## GDT: 3 records of 8 bytes each
-
-# e0:
-# gdt_start:
-# gdt_null:  mandatory null descriptor
-  00 00 00 00 00 00 00 00
-# gdt_code:  (offset 8 from gdt_start)
-  ff ff  # limit[0:16]
-  00 00 00  # base[0:24]
-  9a  # 1/present 00/privilege 1/descriptor type = 1001b
-      # 1/code 0/conforming 1/readable 0/accessed = 1010b
-  cf  # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b
-      # limit[16:20] = 1111b
-  00  # base[24:32]
-# gdt_data:  (offset 16 from gdt_start)
-  ff ff  # limit[0:16]
-  00 00 00  # base[0:24]
-  92  # 1/present 00/privilege 1/descriptor type = 1001b
-      # 0/data 0/conforming 1/readable 0/accessed = 0010b
-  cf  # same as gdt_code
-  00  # base[24:32]
-# gdt_end:
-
-# f8:
-# gdt_descriptor:
-  17 00  # final index of gdt = gdt_end - gdt_start - 1
-  e0 7c 00 00  # start = gdt_start [label]
-
-# padding
-# fe:
-                                          00 00
-
-## 32-bit code from this point (still some instructions not in SubX)
-
-# offset 100 (address 0x7d00):
-# initialize_32bit_mode:
-  66 b8 10 00  # ax <- offset 16 from gdt_start
-  8e d8  # ds <- ax
-  8e d0  # ss <- ax
-  8e c0  # es <- ax
-  8e e0  # fs <- ax
-  8e e8  # gs <- ax
-
-# 10e:
-  bc 00 00 07 00  # esp <- 0x00070000
-
-# 113:
-  # load interrupt handlers
-  0f 01 1d  # lidt 00/mod/indirect 011/subop 101/rm32/use-disp32
-    00 80 00 00  # *idt_descriptor [label]
-
-  # For now, not bothering reprogramming the IRQ to not conflict with software
-  # exceptions.
-  #   https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode
-  #
-  # Interrupt 1 (keyboard) conflicts with debugger faults. We don't use a
-  # debugger.
-  # Reference:
-  #   https://wiki.osdev.org/Exceptions
-
-# 11a:
-  # enable keyboard IRQ (1)
-  b0 fd  # al <- 0xfd  # disable mask for IRQ1
-  e6 21  # port 0x21 <- al
-
-# 11e:
-  fb  # enable interrupts
-  db e3  # initialize FPU
-  # eax <- cr4
-  0f 20  # copy cr4 to rm32
-    e0  # 11/mod/direct 100/r32/CR4 000/rm32/eax
-  # eax <- or bit 9
-  0f ba
-    e8  # 11/mod/direct 101/subop/bit-test-and-set 000/rm32/eax
-    09  # imm8
-  # cr4 <- eax
-  0f 22  # copy rm32 to cr4
-    e0  # 11/mod/direct 100/r32/CR4 000/rm32/eax
-  e9 d0 16 00 00  # jump to 0x9400 [label]
-
-# padding
-# 130:
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# 1fe:
-# final 2 bytes of boot sector
-55 aa
-
-## sector 2 onwards loaded by load_disk, not automatically on boot
-
-# offset 200 (address 0x7e00):
-# null interrupt handler:
-  cf  # iret
-
-# padding
-# 201:
-   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# 210:
-# keyboard interrupt handler:
-  # prologue
-  fa  # disable interrupts
-  60  # push all registers to stack
-  # acknowledge interrupt
-  b0 20  # al <- 0x20
-  e6 20  # port 0x20 <- al
-  31 c0  # eax <- xor eax;  11/direct 000/r32/eax 000/rm32/eax
-  # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html)
-  e4 64  # al <- port 0x64
-  a8 01  # set zf if bit 0 (least significant) is not set
-  74 bb  # jump to epilogue if 0 bit is not set [label]
-# 21e:
-  # - if keyboard buffer is full, return
-  31 c9  # ecx <- xor ecx;  11/direct 001/r32/ecx 001/rm32/ecx
-  # var index/ecx: byte
-  8a  # copy m8 at r32 to r8
-    0d  # 00/mod/indirect 001/r8/cl 101/rm32/use-disp32
-    28 80 00 00  # disp32 [label]
-  # al = *(keyboard buffer + index)
-  8a  # copy m8 at r32 to r8
-    81  # 10/mod/*+disp32 000/r8/al 001/rm32/ecx
-    30 80 00 00  # disp32 [label]
-  # if (al != 0) return
-  3c 00  # compare al, 0
-  75 a9  # jump to epilogue if != [label]
-# 230:
-  # - read keycode
-  e4 60  # al <- port 0x60
-  # - key released
-  # if (al == 0xaa) shift = false  # left shift is being lifted
-  3c aa  # compare al, 0xaa
-  75 0a  # jump to $1 if != [label]
-  # *shift = 0
-  c7  # copy imm32 to rm32
-    05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
-    10 80 00 00  # disp32 [label]
-    00 00 00 00  # imm32
-# 240:
-# $1:
-  # if (al == 0xb6) shift = false  # right shift is being lifted
-  3c b6  # compare al, 0xb6
-  75 0a  # jump to $2 if != [label]
-  # *shift = 0
-  c7  # copy imm32 to rm32
-    05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
-    10 80 00 00  # disp32 [label]
-    00 00 00 00  # imm32
-# 24e:
-# $2:
-  # if (al == 0x9d) ctrl = false  # ctrl is being lifted
-  3c 9d  # compare al, 0x9d
-  75 0a  # jump to $3 if != [label]
-  # *ctrl = 0
-  c7  # copy imm32 to rm32
-    05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
-    14 80 00 00  # disp32 [label]
-    00 00 00 00  # imm32
-# 25c:
-# $3:
-  # if (al & 0x80) a key is being lifted; return
-  50  # push eax
-  24 80  # al <- and 0x80
-  3c 00  # compare al, 0
-  58  # pop to eax (without touching flags)
-  75 75  # jump to epilogue if != [label]
-# 264:
-  # - key pressed
-  # if (al == 0x2a) shift = true, return  # left shift pressed
-  3c 2a  # compare al, 0x2a
-  75 0c  # jump to $4 if != [label]
-  # *shift = 1
-  c7  # copy imm32 to rm32
-    05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
-    10 80 00 00  # disp32 [label]
-    01 00 00 00  # imm32
-  eb 65 # jump to epilogue [label]
-# 274:
-# $4:
-  # if (al == 0x36) shift = true, return  # right shift pressed
-  3c 36  # compare al, 0x36
-  75 0c  # jump to $5 if != [label]
-  # *shift = 1
-  c7  # copy imm32 to rm32
-    05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
-    10 80 00 00  # disp32 [label]
-    01 00 00 00  # imm32
-  eb 55 # jump to epilogue [label]
-# 284:
-# $5:
-  # if (al == 0x1d) ctrl = true, return
-  3c 1d  # compare al, 0x36
-  75 0c  # jump to $6 if != [label]
-  # *shift = 1
-  c7  # copy imm32 to rm32
-    05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
-    14 80 00 00  # disp32 [label]
-    01 00 00 00  # imm32
-  eb 45 # jump to epilogue [label]
-# 294:
-# $6:
-  # - convert key to character
-  # if (shift) use keyboard shift map
-  81  # operate on rm32 and imm32
-    3d  # 00/mod/indirect 111/subop/compare 101/rm32/use-disp32
-    10 80 00 00  # disp32 = shift [label]
-    00 00 00 00  # imm32
-  74 08  # jump to $7 if = [label]
-  # al <- *(keyboard shift map + eax)
-  8a  # copy m8 at rm32 to r8
-    80  # 10/mod/*+disp32 000/r8/al 000/rm32/eax
-    00 87 00 00  # disp32 [label]
-  eb 1a  # jump to $8 [label]
-# 2a8:
-# $7:
-  # if (ctrl) use keyboard ctrl map
-  81  # operate on rm32 and imm32
-    3d  # 00/mod/indirect 111/subop/compare 101/rm32/use-disp32
-    14 80 00 00  # disp32 = ctrl [label]
-    00 00 00 00  # imm32
-  74 08  # jump to $8 if = [label]
-  # al <- *(keyboard ctrl map + eax)
-  8a  # copy m8 at rm32 to r8
-    80  # 10/mod/*+disp32 000/r8/al 000/rm32/eax
-    00 88 00 00  # disp32 [label]
-  eb 06  # jump to $9 [label]
-# 2bc:
-# $8:
-  # otherwise use keyboard normal map
-  # al <- *(keyboard normal map + eax)
-  8a  # copy m8 at rm32 to r8
-    80  # 10/mod/*+disp32 000/r8/al 000/rm32/eax
-    00 86 00 00  # disp32 [label]
-# 2c2:
-# $9:
-  # - if there's no character mapping, return
-  3c 00  # compare al, 0
-  74 13  # jump to epilogue if = [label]
-# 2c6:
-  # - store al in keyboard buffer
-  88  # copy r8 to m8 at r32
-    81  # 10/mod/*+disp32 000/r8/al 001/rm32/ecx
-    30 80 00 00  # disp32 [label]
-  # increment index
-  fe  # increment byte
-    05  # 00/mod/indirect 000/subop/increment 101/rm32/use-disp32
-    28 80 00 00  # disp32 [label]
-  # clear top nibble of index (keyboard buffer is circular)
-  80  # and byte
-    25  # 00/mod/indirect 100/subop/and 101/rm32/use-disp32
-    28 80 00 00  # disp32 [label]
-    0f  # imm8
-# 2d9:
-  # epilogue
-  61  # pop all registers
-  fb  # enable interrupts
-  cf  # iret
-
-# padding
-# 2dc:
-                                    00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# 300:
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# offset 400 (address 0x8000): interrupt handler data
-# idt_descriptor:
-  ff 03  # idt_end - idt_start - 1
-  00 82 00 00  # start = idt_start [label]
-
-# padding
-# 406:
-                  00 00 00 00 00 00 00 00 00 00
-
-# 410:
-# var shift: boolean
-  00 00 00 00
-
-# 414:
-# var ctrl: boolean
-  00 00 00 00
-
-# padding
-# 418:
-                        00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-
-# 428:
-# var keyboard circular buffer
-# write index: nibble
-# still take up 4 bytes so SubX can handle it
-  00 00 00 00
-# 42c:
-# read index: nibble
-# still take up 4 bytes so SubX can handle it
-  00 00 00 00
-# 430:
-# circular buffer: byte[16]
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# padding
-# 440:
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# offset 500 (address 0x8100):
-# video mode info {{{
-  00 00  # attributes
-  00  # winA
-  00  # winB
-# 04
-  00 00  # granularity
-  00 00  # winsize
-# 08
-  00 00  # segmentA
-  00 00  # segmentB
-# 0c
-  00 00 00 00  # realFctPtr (who knows)
-# 10
-  00 00  # pitch
-  00 00  # Xres
-# 14
-  00 00  # Yres
-  00 00  # Wchar Ychar
-# 18
-  00  # planes
-  00  # bpp
-  00  # banks
-  00  # memory_model
-# 1c
-  00  # bank_size
-  00  # image_pages
-  00  # reserved
-# 1f
-  00 00  # red_mask red_position
-  00 00  # green_mask green_position
-  00 00  # blue_mask blue_position
-  00 00  # rsv_mask rsv_position
-  00  # directcolor_attributes
-# 28
-  00 00 00 00  # physbase <== linear frame buffer
-
-# 2c
-# reserved for video mode info
-                                    00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# }}}
-
-# offset 600 (address 0x8200):
-# interrupt descriptor table {{{
-# 128 entries * 8 bytes each = 1024 bytes (0x400)
-# idt_start:
-
-# entry 0
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-
-# By default, BIOS maps IRQ0-7 to interrupt vectors 8-15.
-# https://wiki.osdev.org/index.php?title=Interrupts&oldid=25102#Default_PC_Interrupt_Vector_Assignment
-
-# entry 8: clock
-  00 7e  # target[0:16] = null interrupt handler [label]
-  08 00  # segment selector (gdt_code)
-  00  # unused
-  8e  # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate
-  00 00  # target[16:32]
-
-# entry 9: keyboard
-  10 7e  # target[0:16] = keyboard interrupt handler [label]
-  08 00  # segment selector (gdt_code)
-  00  # unused
-  8e  # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate
-  00 00  # target[16:32]
-
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-
-# 500:
-# entry 0x20
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-
-# 600:
-# entry 0x40
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-
-# 700:
-# entry 0x60
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-# entry 0x70
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-# idt_end:
-# }}}
-
-## the rest of this file has data
-
-# offset a00 (address 0x8600):
-# translating keys to ASCII {{{
-# keyboard normal map:
-00
-#  es
-   1b
-#     |<--- digits -------------->| -  =  backspace
-      31 32 33 34 35 36 37 38 39 30 2d 3d 08
-# 0f
-# tab q  w  e  r  t  y  u  i  o  p  [  ]
-   09 71 77 65 72 74 79 75 69 6f 70 5b 5d
-# 1c
-#                                         enter (newline)
-                                          0a 00
-# 1e
-#     a  s  d  f  g  h  j  k  l  ;  '  `     \
-      61 73 64 66 67 68 6a 6b 6c 3b 27 60 00 5c
-                                        # ^ left shift
-# 2c
-#     z  x  c  v  b  n  m  ,  .  /     *
-      7a 78 63 76 62 6e 6d 2c 2e 2f 00 2a
-                                  # ^ right shift
-# 38
-#                          space
-                        00 20
-# 3a
-                              00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# numeric keypad would start here, but isn't implemented
-                                             00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# offset b00:
-# keyboard shift map:
-00
-#  es
-   1b
-#     !  @  #  $  %  ^  &  *  (  )  _  +  backspace
-      21 40 23 24 25 53 26 2a 28 29 5f 2b 08
-# 0f
-# tab Q  W  E  R  T  Y  U  I  O  P  {  }
-   09 51 57 45 52 54 59 55 49 5f 50 7b 7d
-# 1c
-#                                         enter (newline)
-                                          0a 00
-# 1e
-#     A  S  D  F  G  H  J  K  L  :  "  ~     |
-      41 53 44 46 47 48 4a 4b 4c 3a 22 7e 00 7c
-# 2c
-#     Z  X  C  V  B  N  M  <  >  ?     *
-      5a 58 43 56 42 4e 4d 3c 3e 3f 00 2a
-# 38
-#                          space
-                        00 20
-# 3a
-                              00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# numeric keypad would start here, but isn't implemented
-                                             00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# c00:
-# keyboard ctrl map:
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# 10
-#     ^q ^w ^e ^r ^t ^y ^u tb ^o ^p
-      11 17 05 12 14 19 15 09 1f 10 00 00
-# 1c
-#                                         carriage-return
-                                          0d 00
-# 1e
-#     ^a ^s ^d ^f ^g ^h ^j ^j ^l             ^\
-      01 13 04 06 07 08 0a 0b 0c 00 00 00 00 1c
-# 2c
-#     ^z ^x ^c ^v ^b ^n ^m       ^/
-      1a 18 03 16 02 0e 0d 00 00 1f 00 00
-# 38
-                        00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# padding (there might be more keyboard tables)
-# d00:
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# e00:
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# f00:
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# }}}
-
-# offset 1000 (address 0x8c00)
-# Bitmaps for some ASCII characters (soon Unicode) {{{
-# Part of GNU Unifont
-# 8px wide, 16px tall
-# Based on http://unifoundry.com/pub/unifont/unifont-13.0.05/font-builds/unifont-13.0.05.hex.gz
-# See https://en.wikipedia.org/wiki/GNU_Unifont#The_.hex_font_format
-# Website: http://unifoundry.com/unifont/index.html
-# License: http://unifoundry.com/LICENSE.txt (GPL v2)
-# Each line below is a bitmap for a single character.
-#   Each byte is a bitmap for a single row of 8 pixels.
-
-# some unprintable ASCII chars
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# 0x20 = space
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# !
-  00 00 00 00 08 08 08 08 08 08 08 00 08 08 00 00
-# "
-  00 00 22 22 22 22 00 00 00 00 00 00 00 00 00 00
-# 0x23 = '#'
-  00 00 00 00 12 12 12 7e 24 24 7e 48 48 48 00 00
-# $
-  00 00 00 00 08 3e 49 48 38 0e 09 49 3e 08 00 00
-# %
-  00 00 00 00 31 4a 4a 34 08 08 16 29 29 46 00 00
-# &
-  00 00 00 00 1c 22 22 14 18 29 45 42 46 39 00 00
-# '
-  00 00 08 08 08 08 00 00 00 00 00 00 00 00 00 00
-# (
-  00 00 00 04 08 08 10 10 10 10 10 10 08 08 04 00
-# )
-  00 00 00 20 10 10 08 08 08 08 08 08 10 10 20 00
-# *
-  00 00 00 00 00 00 08 49 2a 1c 2a 49 08 00 00 00
-# +
-  00 00 00 00 00 00 08 08 08 7f 08 08 08 00 00 00
-# ,
-  00 00 00 00 00 00 00 00 00 00 00 00 18 08 08 10
-# -
-  00 00 00 00 00 00 00 00 00 3c 00 00 00 00 00 00
-# .
-  00 00 00 00 00 00 00 00 00 00 00 00 18 18 00 00
-# /
-  00 00 00 00 02 02 04 08 08 10 10 20 40 40 00 00
-# 0x30 = '0'
-  00 00 00 00 18 24 42 46 4a 52 62 42 24 18 00 00
-# 1
-  00 00 00 00 08 18 28 08 08 08 08 08 08 3e 00 00
-# 2
-  00 00 00 00 3c 42 42 02 0c 10 20 40 40 7e 00 00
-# 3
-  00 00 00 00 3c 42 42 02 1c 02 02 42 42 3c 00 00
-# 4
-  00 00 00 00 04 0c 14 24 44 44 7e 04 04 04 00 00
-# 5
-  00 00 00 00 7e 40 40 40 7c 02 02 02 42 3c 00 00
-# 6
-  00 00 00 00 1c 20 40 40 7c 42 42 42 42 3c 00 00
-# 7
-  00 00 00 00 7e 02 02 04 04 04 08 08 08 08 00 00
-# 8
-  00 00 00 00 3c 42 42 42 3c 42 42 42 42 3c 00 00
-# 9
-  00 00 00 00 3c 42 42 42 3e 02 02 02 04 38 00 00
-# :
-  00 00 00 00 00 00 18 18 00 00 00 18 18 00 00 00
-# ;
-  00 00 00 00 00 00 18 18 00 00 00 18 08 08 10 00
-# <
-  00 00 00 00 00 02 04 08 10 20 10 08 04 02 00 00
-# =
-  00 00 00 00 00 00 00 7e 00 00 00 7e 00 00 00 00
-# >
-  00 00 00 00 00 40 20 10 08 04 08 10 20 40 00 00
-# ?
-  00 00 00 00 3c 42 42 02 04 08 08 00 08 08 00 00
-# 0x40 = @
-  00 00 00 00 1c 22 4a 56 52 52 52 4e 20 1e 00 00
-# A
-  00 00 00 00 18 24 24 42 42 7e 42 42 42 42 00 00
-# B
-  00 00 00 00 7c 42 42 42 7c 42 42 42 42 7c 00 00
-# C
-  00 00 00 00 3c 42 42 40 40 40 40 42 42 3c 00 00
-# D
-  00 00 00 00 78 44 42 42 42 42 42 42 44 78 00 00
-# E
-  00 00 00 00 7e 40 40 40 7c 40 40 40 40 7e 00 00
-# F
-  00 00 00 00 7e 40 40 40 7c 40 40 40 40 40 00 00
-# G
-  00 00 00 00 3c 42 42 40 40 4e 42 42 46 3a 00 00
-# H
-  00 00 00 00 42 42 42 42 7e 42 42 42 42 42 00 00
-# I
-  00 00 00 00 3e 08 08 08 08 08 08 08 08 3e 00 00
-# J
-  00 00 00 00 1f 04 04 04 04 04 04 44 44 38 00 00
-# K
-  00 00 00 00 42 44 48 50 60 60 50 48 44 42 00 00
-# L
-  00 00 00 00 40 40 40 40 40 40 40 40 40 7e 00 00
-# M
-  00 00 00 00 42 42 66 66 5a 5a 42 42 42 42 00 00
-# N
-  00 00 00 00 42 62 62 52 52 4a 4a 46 46 42 00 00
-# O
-  00 00 00 00 3c 42 42 42 42 42 42 42 42 3c 00 00
-# 0x50 = P
-  00 00 00 00 7c 42 42 42 7c 40 40 40 40 40 00 00
-# Q
-  00 00 00 00 3c 42 42 42 42 42 42 5a 66 3c 03 00
-# R
-  00 00 00 00 7c 42 42 42 7c 48 44 44 42 42 00 00
-# S
-  00 00 00 00 3c 42 42 40 30 0c 02 42 42 3c 00 00
-# T
-  00 00 00 00 7f 08 08 08 08 08 08 08 08 08 00 00
-# U
-  00 00 00 00 42 42 42 42 42 42 42 42 42 3c 00 00
-# V
-  00 00 00 00 41 41 41 22 22 22 14 14 08 08 00 00
-# W
-  00 00 00 00 42 42 42 42 5a 5a 66 66 42 42 00 00
-# X
-  00 00 00 00 42 42 24 24 18 18 24 24 42 42 00 00
-# Y
-  00 00 00 00 41 41 22 22 14 08 08 08 08 08 00 00
-# Z
-  00 00 00 00 7e 02 02 04 08 10 20 40 40 7e 00 00
-# [
-  00 00 00 0e 08 08 08 08 08 08 08 08 08 08 0e 00
-# \
-  00 00 00 00 40 40 20 10 10 08 08 04 02 02 00 00
-# ]
-  00 00 00 70 10 10 10 10 10 10 10 10 10 10 70 00
-# ^
-  00 00 18 24 42 00 00 00 00 00 00 00 00 00 00 00
-# _
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 7f 00
-# 0x60 = backtick
-  00 20 10 08 00 00 00 00 00 00 00 00 00 00 00 00
-# a
-  00 00 00 00 00 00 3c 42 02 3e 42 42 46 3a 00 00
-# b
-  00 00 00 40 40 40 5c 62 42 42 42 42 62 5c 00 00
-# c
-  00 00 00 00 00 00 3c 42 40 40 40 40 42 3c 00 00
-# d
-  00 00 00 02 02 02 3a 46 42 42 42 42 46 3a 00 00
-# e
-  00 00 00 00 00 00 3c 42 42 7e 40 40 42 3c 00 00
-# f
-  00 00 00 0c 10 10 10 7c 10 10 10 10 10 10 00 00
-# g
-  00 00 00 00 00 02 3a 44 44 44 38 20 3c 42 42 3c
-# h
-  00 00 00 40 40 40 5c 62 42 42 42 42 42 42 00 00
-# i
-  00 00 00 08 08 00 18 08 08 08 08 08 08 3e 00 00
-# j
-  00 00 00 04 04 00 0c 04 04 04 04 04 04 04 48 30
-# k
-  00 00 00 40 40 40 44 48 50 60 50 48 44 42 00 00
-# l
-  00 00 00 18 08 08 08 08 08 08 08 08 08 3e 00 00
-# m
-  00 00 00 00 00 00 76 49 49 49 49 49 49 49 00 00
-# n
-  00 00 00 00 00 00 5c 62 42 42 42 42 42 42 00 00
-# o
-  00 00 00 00 00 00 3c 42 42 42 42 42 42 3c 00 00
-# 0x70 = p
-  00 00 00 00 00 00 5c 62 42 42 42 42 62 5c 40 40
-# q
-  00 00 00 00 00 00 3a 46 42 42 42 42 46 3a 02 02
-# r
-  00 00 00 00 00 00 5c 62 42 40 40 40 40 40 00 00
-# s
-  00 00 00 00 00 00 3c 42 40 30 0c 02 42 3c 00 00
-# t
-  00 00 00 00 10 10 10 7c 10 10 10 10 10 0c 00 00
-# u
-  00 00 00 00 00 00 42 42 42 42 42 42 46 3a 00 00
-# v
-  00 00 00 00 00 00 42 42 42 24 24 24 18 18 00 00
-# w
-  00 00 00 00 00 00 41 49 49 49 49 49 49 36 00 00
-# x
-  00 00 00 00 00 00 42 42 24 18 18 24 42 42 00 00
-# y
-  00 00 00 00 00 00 42 42 42 42 42 26 1a 02 02 3c
-# z
-  00 00 00 00 00 00 7e 02 04 08 10 20 40 7e 00 00
-# {
-  00 00 00 0c 10 10 08 08 10 20 10 08 08 10 10 0c
-# |
-  00 00 08 08 08 08 08 08 08 08 08 08 08 08 08 08
-# }
-  00 00 00 30 08 08 10 10 08 04 08 10 10 08 08 30
-# ~
-  00 00 00 31 49 46 00 00 00 00 00 00 00 00 00 00
-# 0x7f = del (unused)
-  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-# }}}
-
-# offset 1800 (address 0x9400)
-
-# vim:ft=subx
diff --git a/baremetal/boot0.hex b/baremetal/boot0.hex
deleted file mode 100644
index adb320d1..00000000
--- a/baremetal/boot0.hex
+++ /dev/null
@@ -1,344 +0,0 @@
-# A minimal bootable image that:
-#   - loads more sectors past the first boot sector (using BIOS primitives)
-#   - switches to 32-bit mode (giving up access to BIOS primitives)
-#   - sets up a keyboard handler to print '1' at the top-left of screen when '1' is typed
-#
-# When it's ready to accept keys, it prints 'H' to the top-left of the screen.
-#
-# If the initial load fails, it prints 'D' to the top-left of the screen and
-# halts.
-#
-# To convert to a disk image, first prepare a realistically sized disk image:
-#   dd if=/dev/zero of=disk.img count=20160  # 512-byte sectors, so 10MB
-# Now fill in sectors:
-#   ./bootstrap run apps/hex < baremetal/boot0.hex > boot.bin
-#   dd if=boot.bin of=disk.img conv=notrunc
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc  # boot.bochsrc loads disk.img
-#
-# Since we start out in 16-bit mode, we need instructions SubX doesn't
-# support.
-# This file contains just lowercase hex bytes and comments. Zero
-# error-checking. Make liberal use of:
-#   - comments documenting expected offsets
-#   - size checks on the emitted file (currently: 512 bytes)
-#   - xxd to eyeball that offsets contain expected bytes
-
-## 16-bit entry point
-
-# Upon reset, the IBM PC
-#   loads the first sector (512 bytes)
-#   from some bootable image (see the boot sector marker at the end of this file)
-#   to the address range [0x7c00, 0x7e00)
-
-# offset 00 (address 0x7c00):
-  # disable interrupts for this initialization
-  fa  # cli
-
-  # initialize segment registers
-  # this isn't always needed, but the recommendation is to not make assumptions
-  b8 00 00  # ax <- 0
-  8e d8  # ds <- ax
-  8e d0  # ss <- ax
-  8e c0  # es <- ax
-  8e e0  # fs <- ax
-  8e e8  # gs <- ax
-
-  # We don't read or write the stack before we get to 32-bit mode. No function
-  # calls, so we don't need to initialize the stack.
-
-# 0e:
-  # load more sectors from disk
-  b4 02  # ah <- 2  # read sectors from disk
-  # dl comes conveniently initialized at boot time with the index of the device being booted
-  b5 00  # ch <- 0  # cylinder 0
-  b6 00  # dh <- 0  # track 0
-  b1 02  # cl <- 2  # second sector, 1-based
-  b0 01  # al <- 1  # number of sectors to read
-  # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment
-  bb 00 00  # bx <- 0
-  8e c3  # es <- bx
-  bb 00 7e  # bx <- 0x7e00
-  cd 13  # int 13h, BIOS disk service
-  0f 82 76 00  # jump-if-carry disk-error
-
-# 26:
-  # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line
-  # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S
-  # seta20.1:
-  e4 64  # al <- port 0x64
-  a8 02  # set zf if bit 1 (second-least significant) is not set
-  75 fa  # if zf not set, goto seta20.1 (-6)
-
-  b0 d1  # al <- 0xd1
-  e6 64  # port 0x64 <- al
-
-# 30:
-  # seta20.2:
-  e4 64  # al <- port 0x64
-  a8 02  # set zf if bit 1 (second-least significant) is not set
-  75 fa  # if zf not set, goto seta20.2 (-6)
-
-  b0 df  # al <- 0xdf
-  e6 64  # port 0x64 <- al
-
-# 3a:
-  # switch to 32-bit mode
-  0f 01 16  # lgdt 00/mod/indirect 010/subop 110/rm/use-disp16
-    80 7c  # *gdt_descriptor
-# 3f:
-  0f 20 c0  # eax <- cr0
-  66 83 c8 01  # eax <- or 0x1
-  0f 22 c0  # cr0 <- eax
-  ea c0 7c 08 00  # far jump to initialize_32bit_mode after setting cs to the record at offset 8 in the gdt (gdt_code)
-
-# padding
-# 4e:
-                                          00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-## GDT: 3 records of 8 bytes each
-
-# 60:
-# gdt_start:
-# gdt_null:  mandatory null descriptor
-  00 00 00 00 00 00 00 00
-# gdt_code:  (offset 8 from gdt_start)
-  ff ff  # limit[0:16]
-  00 00 00  # base[0:24]
-  9a  # 1/present 00/privilege 1/descriptor type = 1001b
-      # 1/code 0/conforming 1/readable 0/accessed = 1010b
-  cf  # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b
-      # limit[16:20] = 1111b
-  00  # base[24:32]
-# gdt_data:  (offset 16 from gdt_start)
-  ff ff  # limit[0:16]
-  00 00 00  # base[0:24]
-  92  # 1/present 00/privilege 1/descriptor type = 1001b
-      # 0/data 0/conforming 1/readable 0/accessed = 0010b
-  cf  # same as gdt_code
-  00  # base[24:32]
-# gdt_end:
-
-# padding
-# 78:
-                        00 00 00 00 00 00 00 00
-
-# 80:
-# gdt_descriptor:
-  17 00  # final index of gdt = gdt_end - gdt_start - 1
-  60 7c 00 00  # start = gdt_start
-
-# padding
-# 85:
-                  00 00 00 00 00 00 00 00 00 00
-
-# 90:
-# disk_error:
-  # print 'D' to top-left of screen to indicate disk error
-  # *0xb8000 <- 0x0f44
-  # bx <- 0xb800
-  bb 00 b8
-  # ds <- bx
-  8e db  # 11b/mod 011b/reg/ds 011b/rm/bx
-  # al <- 'D'
-  b0 44
-  # ah <- 0x0f  # white on black
-  b4 0f
-  # bx <- 0
-  bb 00 00
-  # *ds:bx <- ax
-  89 07  # 00b/mod/indirect 000b/reg/ax 111b/rm/bx
-
-e9 fb ff  # loop forever
-
-# padding
-# a1:
-   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-## 32-bit code from this point (still some instructions not in SubX)
-
-# c0:
-# initialize_32bit_mode:
-  66 b8 10 00  # ax <- offset 16 from gdt_start
-  8e d8  # ds <- ax
-  8e d0  # ss <- ax
-  8e c0  # es <- ax
-  8e e0  # fs <- ax
-  8e e8  # gs <- ax
-
-  # load interrupt handlers
-  0f 01 1d  # lidt 00/mod/indirect 011/subop 101/rm32/use-disp32
-    00 7f 00 00  # *idt_descriptor
-
-  # enable keyboard IRQ
-  b0 fd  # al <- 0xfd  # enable just IRQ1
-  e6 21  # port 0x21 <- al
-
-  # initialization is done; enable interrupts
-  fb
-  e9 21 00 00 00  # jump to 0x7d00
-
-# padding
-# df:
-                                             00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-## 'application' SubX code: print one character to top-left of screen
-
-# offset 100 (address 0x7d00):
-# Entry:
-  # eax <- *0x7ff4  # random address in second segment containing 'H'
-  8b  # copy rm32 to r32
-    05  # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32
-    # disp32
-    f4 7f 00 00
-  # *0xb8000 <- eax
-  89  # copy r32 to rm32
-    05  # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32
-    # disp32
-    00 80 0b 00
-
-e9 fb ff ff ff  # loop forever
-
-# padding
-# 111:
-   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# 120:
-# null interrupt handler:
-  cf  # iret
-
-# padding
-# 121:
-   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# 130:
-# keyboard interrupt handler:
-  # prologue
-  fa  # disable interrupts
-  60  # push all registers to stack
-  # acknowledge interrupt
-  b0 20  # al <- 0x20
-  e6 20  # port 0x20 <- al
-  # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html)
-  e4 64  # al <- port 0x64
-  a8 01  # set zf if bit 0 (least significant) is not set
-  74 11  # if bit 0 is not set, skip to epilogue
-  # read keycode into eax
-  31 c0  # eax <- xor eax;  11/direct 000/r32/eax 000/rm32/eax
-  e4 60  # al <- port 0x60
-  # map key '1' to ascii; if eax == 2, eax = 0x31
-  3d 02 00 00 00  # compare eax with 0x02
-  75 0b  # if not equal, goto epilogue
-  b8 31 0f 00 00  # eax <- 0x0f31
-  # print eax to top-left of screen (*0xb8000)
-  89  # copy r32 to rm32
-    05  # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32
-    # disp32
-    00 80 0b 00
-  # epilogue
-  61  # pop all registers
-  fb  # enable interrupts
-  cf  # iret
-
-# padding
-# 155
-               00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
-# final 2 bytes of boot sector
-55 aa
-
-## sector 2
-# loaded by load_disk, not automatically on boot
-
-# offset 200 (address 0x7e00): interrupt descriptor table
-# 32 entries * 8 bytes each = 256 bytes (0x100)
-# idt_start:
-
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-
-# entry 8: clock
-  20 7d  # target[0:16] = null interrupt handler
-  08 00  # segment selector (gdt_code)
-  00  # unused
-  8e  # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate
-  00 00  # target[16:32]
-
-# entry 9: keyboard
-  30 7d  # target[0:16] = keyboard interrupt handler
-  08 00  # segment selector (gdt_code)
-  00  # unused
-  8e  # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate
-  00 00  # target[16:32]
-
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00
-# idt_end:
-
-# offset 300 (address 0x7f00):
-# idt_descriptor:
-  ff 00  # idt_end - idt_start - 1
-  00 7e 00 00  # start = idt_start
-
-# padding
-                  00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-00 00 00 00 48 0f 00 00 00 00 00 00 00 00 00 00  # spot the 'H' with attributes
-# offset 400 (address 0x8000)
-
-# vim:ft=conf
diff --git a/baremetal/ex1.hex b/baremetal/ex1.hex
deleted file mode 100644
index 6f969f90..00000000
--- a/baremetal/ex1.hex
+++ /dev/null
@@ -1,19 +0,0 @@
-# The simplest possible program: just an infinite loop.
-# All is well if your computer clears screen and hangs without restarting.
-# On an emulator the window may get bigger to accomodate the higher-resolution
-# graphics mode.
-#
-# To convert to a disk image, first prepare a realistically sized disk image:
-#   dd if=/dev/zero of=disk.img count=20160  # 512-byte sectors, so 10MB
-# Load the program on the disk image:
-#   cat baremetal/boot.hex baremetal/ex1.hex  |./bootstrap run apps/hex  > a.bin
-#   dd if=a.bin of=disk.img conv=notrunc
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc  # boot.bochsrc loads disk.img
-
-# main:  (address 0x9400)
-e9 fb ff ff ff  # jump to main, hanging indefinitely
-
-# vim:ft=subx
diff --git a/baremetal/ex1.subx b/baremetal/ex1.subx
deleted file mode 100644
index 8a5dfdb7..00000000
--- a/baremetal/ex1.subx
+++ /dev/null
@@ -1,18 +0,0 @@
-# The simplest possible program: just an infinite loop.
-# All is well if your computer clears screen and hangs without restarting.
-# On an emulator the window may get bigger to accomodate the higher-resolution
-# graphics mode.
-#
-# To build a disk image:
-#   ./translate_subx_baremetal baremetal/ex2.subx    # emits disk.img
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc  # boot.bochsrc loads disk.img
-
-== code
-
-main:
-  e9/jump main/disp32
-
-# vim:ft=subx
diff --git a/baremetal/ex2.hex b/baremetal/ex2.hex
deleted file mode 100644
index 6c06565f..00000000
--- a/baremetal/ex2.hex
+++ /dev/null
@@ -1,42 +0,0 @@
-# Test out the video mode by filling in the screen with pixels.
-#
-# To run, first prepare a realistically sized disk image:
-#   dd if=/dev/zero of=disk.img count=20160  # 512-byte sectors, so 10MB
-# Load the program on the disk image:
-#   cat baremetal/boot.hex baremetal/ex2.hex  |./bootstrap run apps/hex  > a.bin
-#   dd if=a.bin of=disk.img conv=notrunc
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc  # boot.bochsrc loads disk.img
-#
-# Expected output:
-#   html/baremetal.png
-
-# main:  (address 0x9400)
-
-# ecx <- LFB
-8b  # copy *rm32 to r32
-  0d  # 00/mod/indirect 001/r32/ecx 101/rm32/use-disp32
-  28 81 00 00 # disp32 [label]
-
-# eax <- LFB + 0xbffff (1024*768 - 1)
-8d  # copy-address rm32 to r32
-  81  # 10/mod/*+disp32 000/r32/eax 001/rm32/ecx
-  ff ff 0b 00  # disp32
-
-# $loop:
-# if (eax < ecx) break
-39  # compare rm32 with r32
-  c8  # 11/mod/direct 001/r32/ecx 000/rm32/eax
-7c 05  # break if < [label]
-# *eax <- al
-88  # copy r8 to m8 at r32
-  00  # 00/mod/indirect 000/r8/AL 000/rm32/eax
-48  # decrement eax
-eb f7  # loop to -9 bytes [label]
-
-# $break:
-e9 fb ff ff ff  # hang indefinitely
-
-# vim:ft=subx
diff --git a/baremetal/ex2.mu b/baremetal/ex2.mu
deleted file mode 100644
index adc905e9..00000000
--- a/baremetal/ex2.mu
+++ /dev/null
@@ -1,31 +0,0 @@
-# Test out the video mode by filling in the screen with pixels.
-#
-# To build a disk image:
-#   ./translate_mu_baremetal baremetal/ex2.mu     # emits disk.img
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc               # boot.bochsrc loads disk.img
-#
-# Expected output:
-#   html/baremetal.png
-
-fn main {
-  var y/eax: int <- copy 0
-  {
-    compare y, 0x300/screen-height=768
-    break-if->=
-    var x/edx: int <- copy 0
-    {
-      compare x, 0x400/screen-width=1024
-      break-if->=
-      var color/ecx: int <- copy x
-      color <- and 0xff
-      pixel-on-real-screen x, y, color
-      x <- increment
-      loop
-    }
-    y <- increment
-    loop
-  }
-}
diff --git a/baremetal/ex2.subx b/baremetal/ex2.subx
deleted file mode 100644
index 074d641e..00000000
--- a/baremetal/ex2.subx
+++ /dev/null
@@ -1,35 +0,0 @@
-# Test out the video mode by filling in the screen with pixels.
-#
-# To build a disk image:
-#   ./translate_subx_baremetal baremetal/ex2.subx    # emits disk.img
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc  # boot.bochsrc loads disk.img
-#
-# Expected output:
-#   html/baremetal.png
-
-== code
-
-main:
-  # ecx <- start of video memory
-  8b/-> *0x8128 1/r32/ecx
-
-  # eax <- final pixel of video memory
-  8d/copy-address *(ecx + 0x0bffff) 0/r32/eax  # 0xbffff = 1024*768 - 1
-
-  # for each pixel in video memory
-  {
-    39/compare %eax 1/r32/ecx
-    7c/jump-if-< break/disp8
-    # write its column number to it
-    88/byte<- *eax 0/r32/AL
-    48/decrement-eax
-    eb/jump loop/disp8
-  }
-
-  # hang indefinitely
-  {
-    eb/jump loop/disp8
-  }
diff --git a/baremetal/ex3.hex b/baremetal/ex3.hex
deleted file mode 100644
index d3639948..00000000
--- a/baremetal/ex3.hex
+++ /dev/null
@@ -1,58 +0,0 @@
-# Draw pixels in response to keyboard events, starting from the top-left
-# and in raster order.
-#
-# To run, first prepare a realistically sized disk image:
-#   dd if=/dev/zero of=disk.img count=20160  # 512-byte sectors, so 10MB
-# Load the program on the disk image:
-#   cat baremetal/boot.hex baremetal/ex3.hex  |./bootstrap run apps/hex  > a.bin
-#   dd if=a.bin of=disk.img conv=notrunc
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc  # boot.bochsrc loads disk.img
-
-# main:  (address 0x9000)
-
-# eax <- LFB
-8b  # copy *rm32 to r32
-  05  # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32
-  28 81 00 00 # disp32 [label]
-
-# var read index/ecx: byte = 0
-31 c9  # ecx <- xor ecx;  11/direct 001/r32/ecx 001/rm32/ecx
-
-# $loop:
-  # CL = *read index
-  8a  # copy m8 at r32 to r8
-    0d  # 00/mod/indirect 001/r8/cl 101/rm32/use-disp32
-    cc 7d 00 00  # disp32 [label]
-  # CL = *(keyboard buffer + ecx)
-  8a  # copy m8 at r32 to r8
-    89  # 10/mod/*+disp32 001/r8/cl 001/rm32/ecx
-    d0 7d 00 00  # disp32 [label]
-  # if (CL == 0) loop (spin loop)
-  80
-    f9  # 11/mod/direct 111/subop/compare 001/rm8/CL
-    00  # imm8
-  74 ef  # loop -17 [label]
-# offset 0x19:
-  # otherwise increment read index
-  fe  # increment byte
-    05  # 00/mod/indirect 000/subop/increment 101/rm32/use-disp32
-    cc 7d 00 00  # disp32 [label]
-  # clear top nibble of index (keyboard buffer is circular)
-  80  # and byte
-    25  # 00/mod/indirect 100/subop/and 101/rm32/use-disp32
-    cc 7d 00 00  # disp32 [label]
-    0f  # imm8
-  # print a pixel in fluorescent green
-  c6  # copy imm8 to m8 at rm32
-    00  # 00/mod/indirect 000/subop 000/rm32/eax
-    31  # imm32
-  40  # increment eax
-  eb dc # loop -36 [label]
-
-# $break:
-e9 fb ff ff ff  # hang indefinitely
-
-# vim:ft=subx
diff --git a/baremetal/ex3.mu b/baremetal/ex3.mu
deleted file mode 100644
index e174ca22..00000000
--- a/baremetal/ex3.mu
+++ /dev/null
@@ -1,31 +0,0 @@
-# Draw pixels in response to keyboard events, starting from the top-left
-# and in raster order.
-#
-# To build a disk image:
-#   ./translate_mu_baremetal baremetal/ex3.mu     # emits disk.img
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc               # boot.bochsrc loads disk.img
-#
-# Expected output: a new green pixel starting from the top left corner of the
-# screen every time you press a key (letter or digit)
-
-fn main {
-  var x/ecx: int <- copy 0
-  var y/edx: int <- copy 0
-  {
-    var key/eax: byte <- read-key 0/keyboard
-    compare key, 0
-    loop-if-=  # busy wait
-    pixel-on-real-screen x, y, 0x31/green
-    x <- increment
-    compare x, 0x400/screen-width=1024
-    {
-      break-if-<
-      y <- increment
-      x <- copy 0
-    }
-    loop
-  }
-}
diff --git a/baremetal/ex4.mu b/baremetal/ex4.mu
deleted file mode 100644
index 5b883131..00000000
--- a/baremetal/ex4.mu
+++ /dev/null
@@ -1,14 +0,0 @@
-# Draw a character using the built-in font (GNU unifont)
-#
-# To build a disk image:
-#   ./translate_mu_baremetal baremetal/ex4.mu     # emits disk.img
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc               # boot.bochsrc loads disk.img
-#
-# Expected output: letter 'A' in green near the top-left corner of screen
-
-fn main {
-  draw-codepoint 0/screen, 0x41/A, 2/row, 1/col, 0xa/fg, 0/bg
-}
diff --git a/baremetal/ex5.mu b/baremetal/ex5.mu
deleted file mode 100644
index 1f3bea10..00000000
--- a/baremetal/ex5.mu
+++ /dev/null
@@ -1,16 +0,0 @@
-# Draw a single line of ASCII text using the built-in font (GNU unifont)
-# Also demonstrates bounds-checking _before_ drawing.
-#
-# To build a disk image:
-#   ./translate_mu_baremetal baremetal/ex5.mu     # emits disk.img
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc               # boot.bochsrc loads disk.img
-#
-# Expected output: text in green near the top-left corner of screen
-
-fn main {
-  var dummy/eax: int <- draw-text-rightward 0/screen, "hello from baremetal Mu!", 0x10/x, 0x400/xmax, 0x10/y, 0xa/color
-  dummy <- draw-text-rightward 0/screen, "you shouldn't see this", 0x10/x, 0xa0/xmax, 0x30/y, 0x3/color  # xmax is too narrow
-}
diff --git a/baremetal/ex6.mu b/baremetal/ex6.mu
deleted file mode 100644
index d209e3f6..00000000
--- a/baremetal/ex6.mu
+++ /dev/null
@@ -1,32 +0,0 @@
-# Drawing ASCII text incrementally.
-#
-# To build a disk image:
-#   ./translate_mu_baremetal baremetal/ex6.mu     # emits disk.img
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc               # boot.bochsrc loads disk.img
-#
-# Expected output: a box and text that doesn't overflow it
-
-fn main {
-  # drawing text within a bounding box
-  draw-box-on-real-screen 0xf, 0x1f, 0x79, 0x51, 0x4
-  var x/eax: int <- copy 0x20
-  var y/ecx: int <- copy 0x20
-  x, y <- draw-text-wrapping-right-then-down 0/screen, "hello ",     0x10/xmin, 0x20/ymin, 0x78/xmax, 0x50/ymax, x, y, 0xa/color
-  x, y <- draw-text-wrapping-right-then-down 0/screen, "from ",      0x10/xmin, 0x20/ymin, 0x78/xmax, 0x50/ymax, x, y, 0xa/color
-  x, y <- draw-text-wrapping-right-then-down 0/screen, "baremetal ", 0x10/xmin, 0x20/ymin, 0x78/xmax, 0x50/ymax, x, y, 0xa/color
-  x, y <- draw-text-wrapping-right-then-down 0/screen, "Mu!",        0x10/xmin, 0x20/ymin, 0x78/xmax, 0x50/ymax, x, y, 0xa/color
-
-  # drawing at the cursor in multiple directions
-  draw-text-wrapping-down-then-right-from-cursor-over-full-screen 0/screen, "abc", 0xa
-  draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "def", 0xa
-
-  # test drawing near the edge
-  x <- draw-text-rightward 0/screen, "R", 0x3f8/x, 0x400/xmax=screen-width, 0x100/y, 0xa/color
-  draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "wrapped from R", 0xa
-
-  x <- draw-text-downward 0/screen, "D", 0x100/x, 0x2f0/y, 0x300/ymax=screen-height, 0xa/color
-  draw-text-wrapping-down-then-right-from-cursor-over-full-screen 0/screen, "wrapped from D", 0xa
-}
diff --git a/baremetal/ex7.mu b/baremetal/ex7.mu
deleted file mode 100644
index 30e3c1bc..00000000
--- a/baremetal/ex7.mu
+++ /dev/null
@@ -1,46 +0,0 @@
-# Cursor-based motions.
-#
-# To build a disk image:
-#   ./translate_mu_baremetal baremetal/ex7.mu     # emits disk.img
-# To run:
-#   qemu-system-i386 disk.img
-# Or:
-#   bochs -f baremetal/boot.bochsrc               # boot.bochsrc loads disk.img
-#
-# Expected output: an interactive game a bit like "snakes". Try pressing h, j,
-# k, l.
-
-fn main {
-  var space/eax: grapheme <- copy 0x20
-  set-cursor-position 0/screen, 0, 0
-  {
-    show-cursor 0/screen, space
-    var key/eax: byte <- read-key 0/keyboard
-    {
-      compare key, 0x68/h
-      break-if-!=
-      draw-code-point-at-cursor 0/screen, 0x2d/dash, 0x31/fg, 0/bg
-      move-cursor-left 0
-    }
-    {
-      compare key, 0x6a/j
-      break-if-!=
-      draw-code-point-at-cursor 0/screen, 0x7c/vertical-bar, 0x31/fg, 0/bg
-      move-cursor-down 0
-    }
-    {
-      compare key, 0x6b/k
-      break-if-!=
-      draw-code-point-at-cursor 0/screen, 0x7c/vertical-bar, 0x31/fg, 0/bg
-      move-cursor-up 0
-    }
-    {
-      compare key, 0x6c/l
-      break-if-!=
-      var g/eax: code-point <- copy 0x2d/dash
-      draw-code-point-at-cursor 0/screen, 0x2d/dash, 0x31/fg, 0/bg
-      move-cursor-right 0
-    }
-    loop
-  }
-}
diff --git a/baremetal/ex8.mu b/baremetal/ex8.mu
deleted file mode 100644
index 367c665f..00000000
--- a/baremetal/ex8.mu
+++ /dev/null
@@ -1,6 +0,0 @@
-# Demo of floating-point
-
-fn main {
-  var n/eax: int <- copy 0
-  var result/xmm0: float <- convert n
-}
diff --git a/baremetal/life.mu b/baremetal/life.mu
deleted file mode 100644
index c8643ea9..00000000
--- a/baremetal/life.mu
+++ /dev/null
@@ -1,252 +0,0 @@
-# Conway's Game of Life
-#
-# To build:
-#   $ ./translate_mu_baremetal baremetal/life.mu
-# To run:
-#   $ qemu-system-i386 disk.img
-
-fn state _grid: (addr array boolean), x: int, y: int -> _/eax: boolean {
-  # clip at the edge
-  compare x, 0
-  {
-    break-if->=
-    return 0/false
-  }
-  compare y, 0
-  {
-    break-if->=
-    return 0/false
-  }
-  compare x, 0x100/width
-  {
-    break-if-<
-    return 0/false
-  }
-  compare y, 0xc0/height
-  {
-    break-if-<
-    return 0/false
-  }
-  var idx/eax: int <- copy y
-  idx <- shift-left 8/log2width
-  idx <- add x
-  var grid/esi: (addr array boolean) <- copy _grid
-  var result/eax: (addr boolean) <- index grid, idx
-  return *result
-}
-
-fn set-state _grid: (addr array boolean), x: int, y: int, val: boolean {
-  # don't bother checking bounds
-  var idx/eax: int <- copy y
-  idx <- shift-left 8/log2width
-  idx <- add x
-  var grid/esi: (addr array boolean) <- copy _grid
-  var result/eax: (addr boolean) <- index grid, idx
-  var src/ecx: boolean <- copy val
-  copy-to *result, src
-}
-
-fn num-live-neighbors grid: (addr array boolean), x: int, y: int -> _/eax: int {
-  var result/edi: int <- copy 0
-  # row above: zig
-  decrement y
-  decrement x
-  var s/eax: boolean <- state grid, x, y
-  {
-    compare s, 0/false
-    break-if-=
-    result <- increment
-  }
-  increment x
-  s <- state grid, x, y
-  {
-    compare s, 0/false
-    break-if-=
-    result <- increment
-  }
-  increment x
-  s <- state grid, x, y
-  {
-    compare s, 0/false
-    break-if-=
-    result <- increment
-  }
-  # curr row: zag
-  increment y
-  s <- state grid, x, y
-  {
-    compare s, 0/false
-    break-if-=
-    result <- increment
-  }
-  subtract-from x, 2
-  s <- state grid, x, y
-  {
-    compare s, 0/false
-    break-if-=
-    result <- increment
-  }
-  # row below: zig
-  increment y
-  s <- state grid, x, y
-  {
-    compare s, 0/false
-    break-if-=
-    result <- increment
-  }
-  increment x
-  s <- state grid, x, y
-  {
-    compare s, 0/false
-    break-if-=
-    result <- increment
-  }
-  increment x
-  s <- state grid, x, y
-  {
-    compare s, 0/false
-    break-if-=
-    result <- increment
-  }
-  return result
-}
-
-fn step old-grid: (addr array boolean), new-grid: (addr array boolean) {
-  var y/ecx: int <- copy 0
-  {
-    compare y, 0xc0/height
-    break-if->=
-    var x/edx: int <- copy 0
-    {
-      compare x, 0x100/width
-      break-if->=
-      var n/eax: int <- num-live-neighbors old-grid, x, y
-      # if neighbors < 2, die of loneliness
-      {
-        compare n, 2
-        break-if->=
-        set-state new-grid, x, y, 0/dead
-      }
-      # if neighbors > 3, die of overcrowding
-      {
-        compare n, 3
-        break-if-<=
-        set-state new-grid, x, y, 0/dead
-      }
-      # if neighbors = 2, preserve state
-      {
-        compare n, 2
-        break-if-!=
-        var old-state/eax: boolean <- state old-grid, x, y
-        set-state new-grid, x, y, old-state
-      }
-      # if neighbors = 3, cell quickens to life
-      {
-        compare n, 3
-        break-if-!=
-        set-state new-grid, x, y, 1/live
-      }
-      x <- increment
-      loop
-    }
-    y <- increment
-    loop
-  }
-}
-
-# color a square of size 'side' starting at x*side, y*side
-fn render-square _x: int, _y: int, color: int {
-  var y/edx: int <- copy _y
-  y <- shift-left 2/log2side
-  var side/ebx: int <- copy 1
-  side <- shift-left 2/log2side
-  var ymax/ecx: int <- copy y
-  ymax <- add side
-  {
-    compare y, ymax
-    break-if->=
-    {
-      var x/eax: int <- copy _x
-      x <- shift-left 2/log2side
-      var xmax/ecx: int <- copy x
-      xmax <- add side
-      {
-        compare x, xmax
-        break-if->=
-        pixel-on-real-screen x, y, color
-        x <- increment
-        loop
-      }
-    }
-    y <- increment
-    loop
-  }
-}
-
-fn render grid: (addr array boolean) {
-  var y/ecx: int <- copy 0
-  {
-    compare y, 0xc0/height
-    break-if->=
-    var x/edx: int <- copy 0
-    {
-      compare x, 0x100/width
-      break-if->=
-      var state/eax: boolean <- state grid, x, y
-      compare state, 0/false
-      {
-        break-if-=
-        render-square x, y, 3/cyan
-      }
-      compare state, 0/false
-      {
-        break-if-!=
-        render-square x, y, 0/black
-      }
-      x <- increment
-      loop
-    }
-    y <- increment
-    loop
-  }
-}
-
-fn main {
-#?   # allocate on the stack
-#?   var grid1-storage: (array boolean 0xc000)  # width * height
-#?   var grid1/esi: (addr array boolean) <- address grid1-storage
-#?   var grid2-storage: (array boolean 0xc000)  # width * height
-#?   var grid2/edi: (addr array boolean) <- address grid2-storage
-  # allocate on the heap
-  var grid1-storage: (handle array boolean)
-  var grid1-ah/eax: (addr handle array boolean) <- address grid1-storage
-  populate grid1-ah, 0xc000  # width * height
-  var _grid1/eax: (addr array boolean) <- lookup *grid1-ah
-  var grid1/esi: (addr array boolean) <- copy _grid1
-  var grid2-storage: (handle array boolean)
-  var grid2-ah/eax: (addr handle array boolean) <- address grid2-storage
-  populate grid2-ah, 0xc000  # width * height
-  var _grid2/eax: (addr array boolean) <- lookup *grid2-ah
-  var grid2/edi: (addr array boolean) <- copy _grid2
-  # initialize grid1
-  set-state grid1, 0x80, 0x5f, 1/live
-  set-state grid1, 0x81, 0x5f, 1/live
-  set-state grid1, 0x7f, 0x60, 1/live
-  set-state grid1, 0x80, 0x60, 1/live
-  set-state grid1, 0x80, 0x61, 1/live
-  # render grid1
-  render grid1
-  {
-    var key/eax: byte <- read-key 0/keyboard
-    compare key, 0
-#?     loop-if-=  # press key to step
-    break-if-!=  # press key to quit
-    # iter: grid1 -> grid2
-    step grid1, grid2
-    render grid2
-    # iter: grid2 -> grid1
-    step grid2, grid1
-    render grid1
-    loop
-  }
-}
diff --git a/baremetal/mu-init.subx b/baremetal/mu-init.subx
deleted file mode 100644
index 26b83451..00000000
--- a/baremetal/mu-init.subx
+++ /dev/null
@@ -1,27 +0,0 @@
-# Initialize the minimal runtime for Mu programs.
-#
-# See translate_mu_baremetal for how this file is used.
-#
-# Mu baremetal programs start at a function called 'main' without inouts or outputs.
-
-== code
-
-# initialize stack
-bd/copy-to-ebp 0/imm32
-# always first run tests
-(run-tests)
-(num-test-failures)  # => eax
-# call main if tests all passed
-{
-  3d/compare-eax-and 0/imm32
-  75/jump-if-!= break/disp8
-  (clear-real-screen)
-  c7 0/subop/copy *Real-screen-cursor-x 0/imm32
-  c7 0/subop/copy *Real-screen-cursor-y 0/imm32
-  (main)
-}
-
-# hang indefinitely
-{
-  eb/jump loop/disp8
-}
diff --git a/baremetal/rpn.mu b/baremetal/rpn.mu
deleted file mode 100644
index eaccccdf..00000000
--- a/baremetal/rpn.mu
+++ /dev/null
@@ -1,152 +0,0 @@
-# Integer arithmetic using postfix notation
-#
-# Limitations:
-#   Division not implemented yet.
-#
-# To build:
-#   $ ./translate_mu_baremetal baremetal/rpn.mu
-#
-# Example session:
-#   $ qemu-system-i386 disk.img
-#   > 4
-#   4
-#   > 5 3 -
-#   2
-#
-# Error handling is non-existent. This is just a prototype.
-
-fn main {
-  var in-storage: (stream byte 0x80)
-  var in/esi: (addr stream byte) <- address in-storage
-  var y/ecx: int <- copy 0
-  var space/edx: grapheme <- copy 0x20
-  # read-eval-print loop
-  {
-    # print prompt
-    var x/eax: int <- draw-text-rightward 0/screen, "> ", 0/x, 0x80/xmax, y, 3/fg/cyan, 0/bg
-    set-cursor-position 0/screen, x, y
-    # read line from keyboard
-    clear-stream in
-    {
-      show-cursor 0/screen, space
-      var key/eax: byte <- read-key 0/keyboard
-      compare key, 0xa/newline
-      break-if-=
-      compare key, 0
-      loop-if-=
-      var key2/eax: int <- copy key
-      append-byte in, key2
-      var g/eax: grapheme <- copy key2
-      draw-grapheme-at-cursor 0/screen, g, 0xf/fg, 0/bg
-      move-cursor-right 0
-      loop
-    }
-    # clear cursor
-    draw-grapheme-at-cursor 0/screen, space, 3/fg/never-used, 0/bg
-    # parse and eval
-    var out/eax: int <- simplify in
-    # print
-    y <- increment
-    out, y <- draw-int32-decimal-wrapping-right-then-down 0/screen, out, 0/xmin, y, 0x80/xmax, 0x30/ymax, 0/x, y, 7/fg, 0/bg
-    # newline
-    y <- increment
-    #
-    loop
-  }
-}
-
-type int-stack {
-  data: (handle array int)
-  top: int
-}
-
-fn simplify in: (addr stream byte) -> _/eax: int {
-  var word-storage: slice
-  var word/ecx: (addr slice) <- address word-storage
-  var stack-storage: int-stack
-  var stack/esi: (addr int-stack) <- address stack-storage
-  initialize-int-stack stack, 0x10
-  $simplify:word-loop: {
-    next-word in, word
-    var done?/eax: boolean <- slice-empty? word
-    compare done?, 0
-    break-if-!=
-    # if word is an operator, perform it
-    {
-      var is-add?/eax: boolean <- slice-equal? word, "+"
-      compare is-add?, 0
-      break-if-=
-      var _b/eax: int <- pop-int-stack stack
-      var b/edx: int <- copy _b
-      var a/eax: int <- pop-int-stack stack
-      a <- add b
-      push-int-stack stack, a
-      loop $simplify:word-loop
-    }
-    {
-      var is-sub?/eax: boolean <- slice-equal? word, "-"
-      compare is-sub?, 0
-      break-if-=
-      var _b/eax: int <- pop-int-stack stack
-      var b/edx: int <- copy _b
-      var a/eax: int <- pop-int-stack stack
-      a <- subtract b
-      push-int-stack stack, a
-      loop $simplify:word-loop
-    }
-    {
-      var is-mul?/eax: boolean <- slice-equal? word, "*"
-      compare is-mul?, 0
-      break-if-=
-      var _b/eax: int <- pop-int-stack stack
-      var b/edx: int <- copy _b
-      var a/eax: int <- pop-int-stack stack
-      a <- multiply b
-      push-int-stack stack, a
-      loop $simplify:word-loop
-    }
-    # otherwise it's an int
-    var n/eax: int <- parse-decimal-int-from-slice word
-    push-int-stack stack, n
-    loop
-  }
-  var result/eax: int <- pop-int-stack stack
-  return result
-}
-
-fn initialize-int-stack _self: (addr int-stack), n: int {
-  var self/esi: (addr int-stack) <- copy _self
-  var d/edi: (addr handle array int) <- get self, data
-  populate d, n
-  var top/eax: (addr int) <- get self, top
-  copy-to *top, 0
-}
-
-fn push-int-stack _self: (addr int-stack), _val: int {
-  var self/esi: (addr int-stack) <- copy _self
-  var top-addr/ecx: (addr int) <- get self, top
-  var data-ah/edx: (addr handle array int) <- get self, data
-  var data/eax: (addr array int) <- lookup *data-ah
-  var top/edx: int <- copy *top-addr
-  var dest-addr/edx: (addr int) <- index data, top
-  var val/eax: int <- copy _val
-  copy-to *dest-addr, val
-  add-to *top-addr, 1
-}
-
-fn pop-int-stack _self: (addr int-stack) -> _/eax: int {
-  var self/esi: (addr int-stack) <- copy _self
-  var top-addr/ecx: (addr int) <- get self, top
-  {
-    compare *top-addr, 0
-    break-if->
-    return 0
-  }
-  subtract-from *top-addr, 1
-  var data-ah/edx: (addr handle array int) <- get self, data
-  var data/eax: (addr array int) <- lookup *data-ah
-  var top/edx: int <- copy *top-addr
-  var result-addr/eax: (addr int) <- index data, top
-  var val/eax: int <- copy *result-addr
-  return val
-}
diff --git a/baremetal/shell/cell.mu b/baremetal/shell/cell.mu
deleted file mode 100644
index 59558fb9..00000000
--- a/baremetal/shell/cell.mu
+++ /dev/null
@@ -1,89 +0,0 @@
-type cell {
-  type: int
-  # type 0: pair
-  left: (handle cell)
-  right: (handle cell)
-  # type 1: number
-  number-data: float
-  # type 2: symbol
-  # type 3: string
-  text-data: (handle stream byte)
-  # TODO: array, (associative) table, stream
-}
-
-fn allocate-symbol _out: (addr handle cell) {
-  var out/eax: (addr handle cell) <- copy _out
-  allocate out
-  var out-addr/eax: (addr cell) <- lookup *out
-  var type/ecx: (addr int) <- get out-addr, type
-  copy-to *type, 2/symbol
-  var dest-ah/eax: (addr handle stream byte) <- get out-addr, text-data
-  populate-stream dest-ah, 0x40/max-symbol-size
-}
-
-fn initialize-symbol _out: (addr handle cell), val: (addr array byte) {
-  var out/eax: (addr handle cell) <- copy _out
-  var out-addr/eax: (addr cell) <- lookup *out
-  var dest-ah/eax: (addr handle stream byte) <- get out-addr, text-data
-  var dest/eax: (addr stream byte) <- lookup *dest-ah
-  write dest, val
-}
-
-fn new-symbol out: (addr handle cell), val: (addr array byte) {
-  allocate-symbol out
-  initialize-symbol out, val
-}
-
-fn allocate-number _out: (addr handle cell) {
-  var out/eax: (addr handle cell) <- copy _out
-  allocate out
-  var out-addr/eax: (addr cell) <- lookup *out
-  var type/ecx: (addr int) <- get out-addr, type
-  copy-to *type, 1/number
-}
-
-fn initialize-integer _out: (addr handle cell), n: int {
-  var out/eax: (addr handle cell) <- copy _out
-  var out-addr/eax: (addr cell) <- lookup *out
-  var dest-ah/eax: (addr float) <- get out-addr, number-data
-  var src/xmm0: float <- convert n
-  copy-to *dest-ah, src
-}
-
-fn new-integer out: (addr handle cell), n: int {
-  allocate-number out
-  initialize-integer out, n
-}
-
-fn initialize-float _out: (addr handle cell), n: float {
-  var out/eax: (addr handle cell) <- copy _out
-  var out-addr/eax: (addr cell) <- lookup *out
-  var dest-ah/eax: (addr float) <- get out-addr, number-data
-  var src/xmm0: float <- copy n
-  copy-to *dest-ah, src
-}
-
-fn new-float out: (addr handle cell), n: float {
-  allocate-number out
-  initialize-float out, n
-}
-
-fn allocate-pair _out: (addr handle cell) {
-  var out/eax: (addr handle cell) <- copy _out
-  allocate out
-  # new cells have type pair by default
-}
-
-fn initialize-pair _out: (addr handle cell), left: (handle cell), right: (handle cell) {
-  var out/eax: (addr handle cell) <- copy _out
-  var out-addr/eax: (addr cell) <- lookup *out
-  var dest-ah/ecx: (addr handle cell) <- get out-addr, left
-  copy-handle left, dest-ah
-  dest-ah <- get out-addr, right
-  copy-handle right, dest-ah
-}
-
-fn new-pair out: (addr handle cell), left: (handle cell), right: (handle cell) {
-  allocate-pair out
-  initialize-pair out, left, right
-}
diff --git a/baremetal/shell/eval.mu b/baremetal/shell/eval.mu
deleted file mode 100644
index e69de29b..00000000
--- a/baremetal/shell/eval.mu
+++ /dev/null
diff --git a/baremetal/shell/gap-buffer.mu b/baremetal/shell/gap-buffer.mu
deleted file mode 100644
index 81537e9c..00000000
--- a/baremetal/shell/gap-buffer.mu
+++ /dev/null
@@ -1,781 +0,0 @@
-# primitive for editing a single word
-
-type gap-buffer {
-  left: grapheme-stack
-  right: grapheme-stack
-  # some fields for scanning incrementally through a gap-buffer
-  left-read-index: int
-  right-read-index: int
-}
-
-fn initialize-gap-buffer _self: (addr gap-buffer), max-word-size: int {
-  var self/esi: (addr gap-buffer) <- copy _self
-  var left/eax: (addr grapheme-stack) <- get self, left
-  initialize-grapheme-stack left, max-word-size
-  var right/eax: (addr grapheme-stack) <- get self, right
-  initialize-grapheme-stack right, max-word-size
-}
-
-# just for tests
-fn initialize-gap-buffer-with self: (addr gap-buffer), s: (addr array byte) {
-  initialize-gap-buffer self, 0x10/max-word-size
-  var stream-storage: (stream byte 0x10/max-word-size)
-  var stream/ecx: (addr stream byte) <- address stream-storage
-  write stream, s
-  {
-    var done?/eax: boolean <- stream-empty? stream
-    compare done?, 0/false
-    break-if-!=
-    var g/eax: grapheme <- read-grapheme stream
-    add-grapheme-at-gap self, g
-    loop
-  }
-}
-
-fn emit-gap-buffer _self: (addr gap-buffer), out: (addr stream byte) {
-  var self/esi: (addr gap-buffer) <- copy _self
-  clear-stream out
-  var left/eax: (addr grapheme-stack) <- get self, left
-  emit-stack-from-bottom left, out
-  var right/eax: (addr grapheme-stack) <- get self, right
-  emit-stack-from-top right, out
-}
-
-# dump stack from bottom to top
-fn emit-stack-from-bottom _self: (addr grapheme-stack), out: (addr stream byte) {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var data-ah/edi: (addr handle array grapheme) <- get self, data
-  var _data/eax: (addr array grapheme) <- lookup *data-ah
-  var data/edi: (addr array grapheme) <- copy _data
-  var top-addr/ecx: (addr int) <- get self, top
-  var i/eax: int <- copy 0
-  {
-    compare i, *top-addr
-    break-if->=
-    var g/edx: (addr grapheme) <- index data, i
-    write-grapheme out, *g
-    i <- increment
-    loop
-  }
-}
-
-# dump stack from top to bottom
-fn emit-stack-from-top _self: (addr grapheme-stack), out: (addr stream byte) {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var data-ah/edi: (addr handle array grapheme) <- get self, data
-  var _data/eax: (addr array grapheme) <- lookup *data-ah
-  var data/edi: (addr array grapheme) <- copy _data
-  var top-addr/ecx: (addr int) <- get self, top
-  var i/eax: int <- copy *top-addr
-  i <- decrement
-  {
-    compare i, 0
-    break-if-<
-    var g/edx: (addr grapheme) <- index data, i
-    write-grapheme out, *g
-    i <- decrement
-    loop
-  }
-}
-
-# We implicitly render everything editable in a single color, and assume the
-# cursor is a single other color.
-fn render-gap-buffer-wrapping-right-then-down screen: (addr screen), _gap: (addr gap-buffer), xmin: int, ymin: int, xmax: int, ymax: int, render-cursor?: boolean -> _/eax: int, _/ecx: int {
-  var gap/esi: (addr gap-buffer) <- copy _gap
-  var left/edx: (addr grapheme-stack) <- get gap, left
-  var x2/eax: int <- copy 0
-  var y2/ecx: int <- copy 0
-  x2, y2 <- render-stack-from-bottom-wrapping-right-then-down screen, left, xmin, ymin, xmax, ymax, xmin, ymin
-  var right/edx: (addr grapheme-stack) <- get gap, right
-  x2, y2 <- render-stack-from-top-wrapping-right-then-down screen, right, xmin, ymin, xmax, ymax, x2, y2, render-cursor?
-  # decide whether we still need to print a cursor
-  var bg/ebx: int <- copy 0
-  compare render-cursor?, 0/false
-  {
-    break-if-=
-    # if the right side is empty, grapheme stack didn't print the cursor
-    var empty?/eax: boolean <- grapheme-stack-empty? right
-    compare empty?, 0/false
-    break-if-=
-    bg <- copy 7/cursor
-  }
-  # print a grapheme either way so that cursor position doesn't affect printed width
-  var space/edx: grapheme <- copy 0x20
-  x2, y2 <- render-grapheme screen, space, xmin, ymin, xmax, ymax, x2, y2, 3/fg=cyan, bg
-  return x2, y2
-}
-
-fn render-gap-buffer screen: (addr screen), gap: (addr gap-buffer), x: int, y: int, render-cursor?: boolean -> _/eax: int {
-  var _width/eax: int <- copy 0
-  var _height/ecx: int <- copy 0
-  _width, _height <- screen-size screen
-  var width/edx: int <- copy _width
-  var height/ebx: int <- copy _height
-  var x2/eax: int <- copy 0
-  var y2/ecx: int <- copy 0
-  x2, y2 <- render-gap-buffer-wrapping-right-then-down screen, gap, x, y, width, height, render-cursor?
-  return x2  # y2? yolo
-}
-
-fn gap-buffer-length _gap: (addr gap-buffer) -> _/eax: int {
-  var gap/esi: (addr gap-buffer) <- copy _gap
-  var left/eax: (addr grapheme-stack) <- get gap, left
-  var tmp/eax: (addr int) <- get left, top
-  var left-length/ecx: int <- copy *tmp
-  var right/esi: (addr grapheme-stack) <- get gap, right
-  tmp <- get right, top
-  var result/eax: int <- copy *tmp
-  result <- add left-length
-  return result
-}
-
-fn add-grapheme-at-gap _self: (addr gap-buffer), g: grapheme {
-  var self/esi: (addr gap-buffer) <- copy _self
-  var left/eax: (addr grapheme-stack) <- get self, left
-  push-grapheme-stack left, g
-}
-
-fn gap-to-start self: (addr gap-buffer) {
-  {
-    var curr/eax: grapheme <- gap-left self
-    compare curr, -1
-    loop-if-!=
-  }
-}
-
-fn gap-to-end self: (addr gap-buffer) {
-  {
-    var curr/eax: grapheme <- gap-right self
-    compare curr, -1
-    loop-if-!=
-  }
-}
-
-fn gap-at-start? _self: (addr gap-buffer) -> _/eax: boolean {
-  var self/esi: (addr gap-buffer) <- copy _self
-  var left/eax: (addr grapheme-stack) <- get self, left
-  var result/eax: boolean <- grapheme-stack-empty? left
-  return result
-}
-
-fn gap-at-end? _self: (addr gap-buffer) -> _/eax: boolean {
-  var self/esi: (addr gap-buffer) <- copy _self
-  var right/eax: (addr grapheme-stack) <- get self, right
-  var result/eax: boolean <- grapheme-stack-empty? right
-  return result
-}
-
-fn gap-right _self: (addr gap-buffer) -> _/eax: grapheme {
-  var self/esi: (addr gap-buffer) <- copy _self
-  var g/eax: grapheme <- copy 0
-  var right/ecx: (addr grapheme-stack) <- get self, right
-  g <- pop-grapheme-stack right
-  compare g, -1
-  {
-    break-if-=
-    var left/ecx: (addr grapheme-stack) <- get self, left
-    push-grapheme-stack left, g
-  }
-  return g
-}
-
-fn gap-left _self: (addr gap-buffer) -> _/eax: grapheme {
-  var self/esi: (addr gap-buffer) <- copy _self
-  var g/eax: grapheme <- copy 0
-  {
-    var left/ecx: (addr grapheme-stack) <- get self, left
-    g <- pop-grapheme-stack left
-  }
-  compare g, -1
-  {
-    break-if-=
-    var right/ecx: (addr grapheme-stack) <- get self, right
-    push-grapheme-stack right, g
-  }
-  return g
-}
-
-fn index-of-gap _self: (addr gap-buffer) -> _/eax: int {
-  var self/eax: (addr gap-buffer) <- copy _self
-  var left/eax: (addr grapheme-stack) <- get self, left
-  var top-addr/eax: (addr int) <- get left, top
-  var result/eax: int <- copy *top-addr
-  return result
-}
-
-fn first-grapheme-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
-  var self/esi: (addr gap-buffer) <- copy _self
-  # try to read from left
-  var left/eax: (addr grapheme-stack) <- get self, left
-  var top-addr/ecx: (addr int) <- get left, top
-  compare *top-addr, 0
-  {
-    break-if-<=
-    var data-ah/eax: (addr handle array grapheme) <- get left, data
-    var data/eax: (addr array grapheme) <- lookup *data-ah
-    var result-addr/eax: (addr grapheme) <- index data, 0
-    return *result-addr
-  }
-  # try to read from right
-  var right/eax: (addr grapheme-stack) <- get self, right
-  top-addr <- get right, top
-  compare *top-addr, 0
-  {
-    break-if-<=
-    var data-ah/eax: (addr handle array grapheme) <- get right, data
-    var data/eax: (addr array grapheme) <- lookup *data-ah
-    var top/ecx: int <- copy *top-addr
-    top <- decrement
-    var result-addr/eax: (addr grapheme) <- index data, top
-    return *result-addr
-  }
-  # give up
-  return -1
-}
-
-fn grapheme-before-cursor-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
-  var self/esi: (addr gap-buffer) <- copy _self
-  # try to read from left
-  var left/ecx: (addr grapheme-stack) <- get self, left
-  var top-addr/edx: (addr int) <- get left, top
-  compare *top-addr, 0
-  {
-    break-if-<=
-    var result/eax: grapheme <- pop-grapheme-stack left
-    push-grapheme-stack left, result
-    return result
-  }
-  # give up
-  return -1
-}
-
-fn delete-before-gap _self: (addr gap-buffer) {
-  var self/eax: (addr gap-buffer) <- copy _self
-  var left/eax: (addr grapheme-stack) <- get self, left
-  var dummy/eax: grapheme <- pop-grapheme-stack left
-}
-
-fn pop-after-gap _self: (addr gap-buffer) -> _/eax: grapheme {
-  var self/eax: (addr gap-buffer) <- copy _self
-  var right/eax: (addr grapheme-stack) <- get self, right
-  var result/eax: grapheme <- pop-grapheme-stack right
-  return result
-}
-
-fn gap-buffer-equal? _self: (addr gap-buffer), s: (addr array byte) -> _/eax: boolean {
-  var self/esi: (addr gap-buffer) <- copy _self
-  # complication: graphemes may be multiple bytes
-  # so don't rely on length
-  # instead turn the expected result into a stream and arrange to read from it in order
-  var stream-storage: (stream byte 0x10/max-word-size)
-  var expected-stream/ecx: (addr stream byte) <- address stream-storage
-  write expected-stream, s
-  # compare left
-  var left/edx: (addr grapheme-stack) <- get self, left
-  var result/eax: boolean <- prefix-match? left, expected-stream
-  compare result, 0/false
-  {
-    break-if-!=
-    return result
-  }
-  # compare right
-  var right/edx: (addr grapheme-stack) <- get self, right
-  result <- suffix-match? right, expected-stream
-  compare result, 0/false
-  {
-    break-if-!=
-    return result
-  }
-  # ensure there's nothing left over
-  result <- stream-empty? expected-stream
-  return result
-}
-
-fn test-gap-buffer-equal-from-end {
-  var _g: gap-buffer
-  var g/esi: (addr gap-buffer) <- address _g
-  initialize-gap-buffer g, 0x10
-  #
-  var c/eax: grapheme <- copy 0x61/a
-  add-grapheme-at-gap g, c
-  add-grapheme-at-gap g, c
-  add-grapheme-at-gap g, c
-  # gap is at end (right is empty)
-  var result/eax: boolean <- gap-buffer-equal? g, "aaa"
-  check result, "F - test-gap-buffer-equal-from-end"
-}
-
-fn test-gap-buffer-equal-from-middle {
-  var _g: gap-buffer
-  var g/esi: (addr gap-buffer) <- address _g
-  initialize-gap-buffer g, 0x10
-  #
-  var c/eax: grapheme <- copy 0x61/a
-  add-grapheme-at-gap g, c
-  add-grapheme-at-gap g, c
-  add-grapheme-at-gap g, c
-  var dummy/eax: grapheme <- gap-left g
-  # gap is in the middle
-  var result/eax: boolean <- gap-buffer-equal? g, "aaa"
-  check result, "F - test-gap-buffer-equal-from-middle"
-}
-
-fn test-gap-buffer-equal-from-start {
-  var _g: gap-buffer
-  var g/esi: (addr gap-buffer) <- address _g
-  initialize-gap-buffer g, 0x10
-  #
-  var c/eax: grapheme <- copy 0x61/a
-  add-grapheme-at-gap g, c
-  add-grapheme-at-gap g, c
-  add-grapheme-at-gap g, c
-  var dummy/eax: grapheme <- gap-left g
-  dummy <- gap-left g
-  dummy <- gap-left g
-  # gap is at the start
-  var result/eax: boolean <- gap-buffer-equal? g, "aaa"
-  check result, "F - test-gap-buffer-equal-from-start"
-}
-
-fn test-gap-buffer-equal-fails {
-  # g = "aaa"
-  var _g: gap-buffer
-  var g/esi: (addr gap-buffer) <- address _g
-  initialize-gap-buffer g, 0x10
-  var c/eax: grapheme <- copy 0x61/a
-  add-grapheme-at-gap g, c
-  add-grapheme-at-gap g, c
-  add-grapheme-at-gap g, c
-  #
-  var result/eax: boolean <- gap-buffer-equal? g, "aa"
-  check-not result, "F - test-gap-buffer-equal-fails"
-}
-
-fn gap-buffers-equal? self: (addr gap-buffer), g: (addr gap-buffer) -> _/eax: boolean {
-  var tmp/eax: int <- gap-buffer-length self
-  var len/ecx: int <- copy tmp
-  var leng/eax: int <- gap-buffer-length g
-  compare len, leng
-  {
-    break-if-=
-    return 0/false
-  }
-  var i/edx: int <- copy 0
-  {
-    compare i, len
-    break-if->=
-    {
-      var tmp/eax: grapheme <- gap-index self, i
-      var curr/ecx: grapheme <- copy tmp
-      var currg/eax: grapheme <- gap-index g, i
-      compare curr, currg
-      break-if-=
-      return 0/false
-    }
-    i <- increment
-    loop
-  }
-  return 1/true
-}
-
-fn gap-index _self: (addr gap-buffer), _n: int -> _/eax: grapheme {
-  var self/esi: (addr gap-buffer) <- copy _self
-  var n/ebx: int <- copy _n
-  # if n < left->length, index into left
-  var left/edi: (addr grapheme-stack) <- get self, left
-  var left-len-a/edx: (addr int) <- get left, top
-  compare n, *left-len-a
-  {
-    break-if->=
-    var data-ah/eax: (addr handle array grapheme) <- get left, data
-    var data/eax: (addr array grapheme) <- lookup *data-ah
-    var result/eax: (addr grapheme) <- index data, n
-    return *result
-  }
-  # shrink n
-  n <- subtract *left-len-a
-  # if n < right->length, index into right
-  var right/edi: (addr grapheme-stack) <- get self, right
-  var right-len-a/edx: (addr int) <- get right, top
-  compare n, *right-len-a
-  {
-    break-if->=
-    var data-ah/eax: (addr handle array grapheme) <- get right, data
-    var data/eax: (addr array grapheme) <- lookup *data-ah
-    # idx = right->len - n - 1
-    var idx/ebx: int <- copy n
-    idx <- subtract *right-len-a
-    idx <- negate
-    idx <- subtract 1
-    var result/eax: (addr grapheme) <- index data, idx
-    return *result
-  }
-  # error
-  abort "gap-index: out of bounds"
-  return 0
-}
-
-fn test-gap-buffers-equal? {
-  var _a: gap-buffer
-  var a/esi: (addr gap-buffer) <- address _a
-  initialize-gap-buffer-with a, "abc"
-  var _b: gap-buffer
-  var b/edi: (addr gap-buffer) <- address _b
-  initialize-gap-buffer-with b, "abc"
-  var _c: gap-buffer
-  var c/ebx: (addr gap-buffer) <- address _c
-  initialize-gap-buffer-with c, "ab"
-  var _d: gap-buffer
-  var d/edx: (addr gap-buffer) <- address _d
-  initialize-gap-buffer-with d, "abd"
-  #
-  var result/eax: boolean <- gap-buffers-equal? a, a
-  check result, "F - test-gap-buffers-equal? - reflexive"
-  result <- gap-buffers-equal? a, b
-  check result, "F - test-gap-buffers-equal? - equal"
-  # length not equal
-  result <- gap-buffers-equal? a, c
-  check-not result, "F - test-gap-buffers-equal? - not equal"
-  # contents not equal
-  result <- gap-buffers-equal? a, d
-  check-not result, "F - test-gap-buffers-equal? - not equal 2"
-  result <- gap-buffers-equal? d, a
-  check-not result, "F - test-gap-buffers-equal? - not equal 3"
-}
-
-fn test-gap-buffer-index {
-  var gap-storage: gap-buffer
-  var gap/esi: (addr gap-buffer) <- address gap-storage
-  initialize-gap-buffer-with gap, "abc"
-  # gap is at end, all contents are in left
-  var g/eax: grapheme <- gap-index gap, 0
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x61/a, "F - test-gap-index/left-1"
-  var g/eax: grapheme <- gap-index gap, 1
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x62/b, "F - test-gap-index/left-2"
-  var g/eax: grapheme <- gap-index gap, 2
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x63/c, "F - test-gap-index/left-3"
-  # now check when everything is to the right
-  gap-to-start gap
-  rewind-gap-buffer gap
-  var g/eax: grapheme <- gap-index gap, 0
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x61/a, "F - test-gap-index/right-1"
-  var g/eax: grapheme <- gap-index gap, 1
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x62/b, "F - test-gap-index/right-2"
-  var g/eax: grapheme <- gap-index gap, 2
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x63/c, "F - test-gap-index/right-3"
-}
-
-fn copy-gap-buffer _src-ah: (addr handle gap-buffer), _dest-ah: (addr handle gap-buffer) {
-  # obtain src-a, dest-a
-  var src-ah/eax: (addr handle gap-buffer) <- copy _src-ah
-  var _src-a/eax: (addr gap-buffer) <- lookup *src-ah
-  var src-a/esi: (addr gap-buffer) <- copy _src-a
-  var dest-ah/eax: (addr handle gap-buffer) <- copy _dest-ah
-  var _dest-a/eax: (addr gap-buffer) <- lookup *dest-ah
-  var dest-a/edi: (addr gap-buffer) <- copy _dest-a
-  # copy left grapheme-stack
-  var src/ecx: (addr grapheme-stack) <- get src-a, left
-  var dest/edx: (addr grapheme-stack) <- get dest-a, left
-  copy-grapheme-stack src, dest
-  # copy right grapheme-stack
-  src <- get src-a, right
-  dest <- get dest-a, right
-  copy-grapheme-stack src, dest
-}
-
-fn gap-buffer-is-decimal-integer? _self: (addr gap-buffer) -> _/eax: boolean {
-  var self/esi: (addr gap-buffer) <- copy _self
-  var curr/ecx: (addr grapheme-stack) <- get self, left
-  var result/eax: boolean <- grapheme-stack-is-decimal-integer? curr
-  {
-    compare result, 0/false
-    break-if-=
-    curr <- get self, right
-    result <- grapheme-stack-is-decimal-integer? curr
-  }
-  return result
-}
-
-fn test-render-gap-buffer-without-cursor {
-  # setup
-  var gap-storage: gap-buffer
-  var gap/esi: (addr gap-buffer) <- address gap-storage
-  initialize-gap-buffer-with gap, "abc"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5, 4
-  #
-  var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 0/no-cursor
-  check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-without-cursor"
-  check-ints-equal x, 4, "F - test-render-gap-buffer-without-cursor: result"
-                                                                # abc
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "    ", "F - test-render-gap-buffer-without-cursor: bg"
-}
-
-fn test-render-gap-buffer-with-cursor-at-end {
-  # setup
-  var gap-storage: gap-buffer
-  var gap/esi: (addr gap-buffer) <- address gap-storage
-  initialize-gap-buffer-with gap, "abc"
-  gap-to-end gap
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5, 4
-  #
-  var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
-  check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-end"
-  # we've drawn one extra grapheme for the cursor
-  check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-end: result"
-                                                                # abc
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "   |", "F - test-render-gap-buffer-with-cursor-at-end: bg"
-}
-
-fn test-render-gap-buffer-with-cursor-in-middle {
-  # setup
-  var gap-storage: gap-buffer
-  var gap/esi: (addr gap-buffer) <- address gap-storage
-  initialize-gap-buffer-with gap, "abc"
-  gap-to-end gap
-  var dummy/eax: grapheme <- gap-left gap
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5, 4
-  #
-  var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
-  check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-in-middle"
-  check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-in-middle: result"
-                                                                # abc
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "  | ", "F - test-render-gap-buffer-with-cursor-in-middle: bg"
-}
-
-fn test-render-gap-buffer-with-cursor-at-start {
-  var gap-storage: gap-buffer
-  var gap/esi: (addr gap-buffer) <- address gap-storage
-  initialize-gap-buffer-with gap, "abc"
-  gap-to-start gap
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5, 4
-  #
-  var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
-  check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-start"
-  check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-start: result"
-                                                                # abc
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|   ", "F - test-render-gap-buffer-with-cursor-at-start: bg"
-}
-
-## some primitives for scanning through a gap buffer
-# don't modify the gap buffer while scanning
-# this includes moving the cursor around
-
-# restart scan without affecting gap-buffer contents
-fn rewind-gap-buffer _self: (addr gap-buffer) {
-  var self/esi: (addr gap-buffer) <- copy _self
-  var dest/eax: (addr int) <- get self, left-read-index
-  copy-to *dest, 0
-  dest <- get self, right-read-index
-  copy-to *dest, 0
-}
-
-fn gap-buffer-scan-done? _self: (addr gap-buffer) -> _/eax: boolean {
-  var self/esi: (addr gap-buffer) <- copy _self
-  # more in left?
-  var left/eax: (addr grapheme-stack) <- get self, left
-  var left-size/eax: int <- grapheme-stack-length left
-  var left-read-index/ecx: (addr int) <- get self, left-read-index
-  compare *left-read-index, left-size
-  {
-    break-if->=
-    return 0/false
-  }
-  # more in right?
-  var right/eax: (addr grapheme-stack) <- get self, right
-  var right-size/eax: int <- grapheme-stack-length right
-  var right-read-index/ecx: (addr int) <- get self, right-read-index
-  compare *right-read-index, right-size
-  {
-    break-if->=
-    return 0/false
-  }
-  #
-  return 1/true
-}
-
-fn peek-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
-  var self/esi: (addr gap-buffer) <- copy _self
-  # more in left?
-  var left/ecx: (addr grapheme-stack) <- get self, left
-  var left-size/eax: int <- grapheme-stack-length left
-  var left-read-index-a/edx: (addr int) <- get self, left-read-index
-  compare *left-read-index-a, left-size
-  {
-    break-if->=
-    var left-data-ah/eax: (addr handle array grapheme) <- get left, data
-    var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
-    var left-read-index/ecx: int <- copy *left-read-index-a
-    var result/eax: (addr grapheme) <- index left-data, left-read-index
-    return *result
-  }
-  # more in right?
-  var right/ecx: (addr grapheme-stack) <- get self, right
-  var _right-size/eax: int <- grapheme-stack-length right
-  var right-size/ebx: int <- copy _right-size
-  var right-read-index-a/edx: (addr int) <- get self, right-read-index
-  compare *right-read-index-a, right-size
-  {
-    break-if->=
-    # read the right from reverse
-    var right-data-ah/eax: (addr handle array grapheme) <- get right, data
-    var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
-    var right-read-index/ebx: int <- copy right-size
-    right-read-index <- subtract *right-read-index-a
-    right-read-index <- subtract 1
-    var result/eax: (addr grapheme) <- index right-data, right-read-index
-    return *result
-  }
-  # if we get here there's nothing left
-  return 0/nul
-}
-
-fn read-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
-  var self/esi: (addr gap-buffer) <- copy _self
-  # more in left?
-  var left/ecx: (addr grapheme-stack) <- get self, left
-  var left-size/eax: int <- grapheme-stack-length left
-  var left-read-index-a/edx: (addr int) <- get self, left-read-index
-  compare *left-read-index-a, left-size
-  {
-    break-if->=
-    var left-data-ah/eax: (addr handle array grapheme) <- get left, data
-    var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
-    var left-read-index/ecx: int <- copy *left-read-index-a
-    var result/eax: (addr grapheme) <- index left-data, left-read-index
-    increment *left-read-index-a
-    return *result
-  }
-  # more in right?
-  var right/ecx: (addr grapheme-stack) <- get self, right
-  var _right-size/eax: int <- grapheme-stack-length right
-  var right-size/ebx: int <- copy _right-size
-  var right-read-index-a/edx: (addr int) <- get self, right-read-index
-  compare *right-read-index-a, right-size
-  {
-    break-if->=
-    # read the right from reverse
-    var right-data-ah/eax: (addr handle array grapheme) <- get right, data
-    var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
-    var right-read-index/ebx: int <- copy right-size
-    right-read-index <- subtract *right-read-index-a
-    right-read-index <- subtract 1
-    var result/eax: (addr grapheme) <- index right-data, right-read-index
-    increment *right-read-index-a
-    return *result
-  }
-  # if we get here there's nothing left
-  return 0/nul
-}
-
-fn test-read-from-gap-buffer {
-  var gap-storage: gap-buffer
-  var gap/esi: (addr gap-buffer) <- address gap-storage
-  initialize-gap-buffer-with gap, "abc"
-  # gap is at end, all contents are in left
-  var done?/eax: boolean <- gap-buffer-scan-done? gap
-  check-not done?, "F - test-read-from-gap-buffer/left-1/done"
-  var g/eax: grapheme <- read-from-gap-buffer gap
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/left-1"
-  var done?/eax: boolean <- gap-buffer-scan-done? gap
-  check-not done?, "F - test-read-from-gap-buffer/left-2/done"
-  var g/eax: grapheme <- read-from-gap-buffer gap
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/left-2"
-  var done?/eax: boolean <- gap-buffer-scan-done? gap
-  check-not done?, "F - test-read-from-gap-buffer/left-3/done"
-  var g/eax: grapheme <- read-from-gap-buffer gap
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/left-3"
-  var done?/eax: boolean <- gap-buffer-scan-done? gap
-  check done?, "F - test-read-from-gap-buffer/left-4/done"
-  var g/eax: grapheme <- read-from-gap-buffer gap
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/left-4"
-  # now check when everything is to the right
-  gap-to-start gap
-  rewind-gap-buffer gap
-  var done?/eax: boolean <- gap-buffer-scan-done? gap
-  check-not done?, "F - test-read-from-gap-buffer/right-1/done"
-  var g/eax: grapheme <- read-from-gap-buffer gap
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/right-1"
-  var done?/eax: boolean <- gap-buffer-scan-done? gap
-  check-not done?, "F - test-read-from-gap-buffer/right-2/done"
-  var g/eax: grapheme <- read-from-gap-buffer gap
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/right-2"
-  var done?/eax: boolean <- gap-buffer-scan-done? gap
-  check-not done?, "F - test-read-from-gap-buffer/right-3/done"
-  var g/eax: grapheme <- read-from-gap-buffer gap
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/right-3"
-  var done?/eax: boolean <- gap-buffer-scan-done? gap
-  check done?, "F - test-read-from-gap-buffer/right-4/done"
-  var g/eax: grapheme <- read-from-gap-buffer gap
-  var x/ecx: int <- copy g
-  check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/right-4"
-}
-
-fn skip-whitespace-from-gap-buffer self: (addr gap-buffer) {
-  var done?/eax: boolean <- gap-buffer-scan-done? self
-  compare done?, 0/false
-  break-if-!=
-  var g/eax: grapheme <- peek-from-gap-buffer self
-  {
-    compare g, 0x20/space
-    break-if-=
-    compare g, 0xa/newline
-    break-if-=
-    return
-  }
-  g <- read-from-gap-buffer self
-  loop
-}
-
-fn edit-gap-buffer self: (addr gap-buffer), key: grapheme {
-  var g/edx: grapheme <- copy key
-  {
-    compare g, 8/backspace
-    break-if-!=
-    delete-before-gap self
-    return
-  }
-  # arrow keys
-  {
-    compare g, 4/ctrl-d
-    break-if-!=
-    # ctrl-d: cursor down
-    return
-  }
-  {
-    compare g, 0x15/ctrl-u
-    break-if-!=
-    # ctrl-u: cursor up
-    return
-  }
-  # default: insert character
-  add-grapheme-at-gap self, g
-}
-
-fn cursor-on-final-line? self: (addr gap-buffer) -> _/eax: boolean {
-  return 1/true
-}
diff --git a/baremetal/shell/grapheme-stack.mu b/baremetal/shell/grapheme-stack.mu
deleted file mode 100644
index 456df0cb..00000000
--- a/baremetal/shell/grapheme-stack.mu
+++ /dev/null
@@ -1,280 +0,0 @@
-# grapheme stacks are the smallest unit of editable text
-
-type grapheme-stack {
-  data: (handle array grapheme)
-  top: int
-}
-
-fn initialize-grapheme-stack _self: (addr grapheme-stack), n: int {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var d/edi: (addr handle array grapheme) <- get self, data
-  populate d, n
-  var top/eax: (addr int) <- get self, top
-  copy-to *top, 0
-}
-
-fn clear-grapheme-stack _self: (addr grapheme-stack) {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var top/eax: (addr int) <- get self, top
-  copy-to *top, 0
-}
-
-fn grapheme-stack-empty? _self: (addr grapheme-stack) -> _/eax: boolean {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var top/eax: (addr int) <- get self, top
-  compare *top, 0
-  {
-    break-if-!=
-    return 1/true
-  }
-  return 0/false
-}
-
-fn grapheme-stack-length _self: (addr grapheme-stack) -> _/eax: int {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var top/eax: (addr int) <- get self, top
-  return *top
-}
-
-fn push-grapheme-stack _self: (addr grapheme-stack), _val: grapheme {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var top-addr/ecx: (addr int) <- get self, top
-  var data-ah/edx: (addr handle array grapheme) <- get self, data
-  var data/eax: (addr array grapheme) <- lookup *data-ah
-  var top/edx: int <- copy *top-addr
-  var dest-addr/edx: (addr grapheme) <- index data, top
-  var val/eax: grapheme <- copy _val
-  copy-to *dest-addr, val
-  add-to *top-addr, 1
-}
-
-fn pop-grapheme-stack _self: (addr grapheme-stack) -> _/eax: grapheme {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var top-addr/ecx: (addr int) <- get self, top
-  {
-    compare *top-addr, 0
-    break-if->
-    return -1
-  }
-  subtract-from *top-addr, 1
-  var data-ah/edx: (addr handle array grapheme) <- get self, data
-  var data/eax: (addr array grapheme) <- lookup *data-ah
-  var top/edx: int <- copy *top-addr
-  var result-addr/eax: (addr grapheme) <- index data, top
-  return *result-addr
-}
-
-fn copy-grapheme-stack _src: (addr grapheme-stack), dest: (addr grapheme-stack) {
-  var src/esi: (addr grapheme-stack) <- copy _src
-  var data-ah/edi: (addr handle array grapheme) <- get src, data
-  var _data/eax: (addr array grapheme) <- lookup *data-ah
-  var data/edi: (addr array grapheme) <- copy _data
-  var top-addr/ecx: (addr int) <- get src, top
-  var i/eax: int <- copy 0
-  {
-    compare i, *top-addr
-    break-if->=
-    var g/edx: (addr grapheme) <- index data, i
-    push-grapheme-stack dest, *g
-    i <- increment
-    loop
-  }
-}
-
-# dump stack to screen from bottom to top
-# colors hardcoded
-fn render-stack-from-bottom-wrapping-right-then-down screen: (addr screen), _self: (addr grapheme-stack), xmin: int, ymin: int, xmax: int, ymax: int, _x: int, _y: int -> _/eax: int, _/ecx: int {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var data-ah/edi: (addr handle array grapheme) <- get self, data
-  var _data/eax: (addr array grapheme) <- lookup *data-ah
-  var data/edi: (addr array grapheme) <- copy _data
-  var x/eax: int <- copy _x
-  var y/ecx: int <- copy _y
-  var top-addr/edx: (addr int) <- get self, top
-  var i/ebx: int <- copy 0
-  {
-    compare i, *top-addr
-    break-if->=
-    {
-      var g/edx: (addr grapheme) <- index data, i
-      x, y <- render-grapheme screen, *g, xmin, ymin, xmax, ymax, x, y, 3/fg=cyan, 0/bg
-    }
-    i <- increment
-    loop
-  }
-  return x, y
-}
-
-# helper for small words
-fn render-stack-from-bottom screen: (addr screen), self: (addr grapheme-stack), x: int, y: int -> _/eax: int {
-  var _width/eax: int <- copy 0
-  var _height/ecx: int <- copy 0
-  _width, _height <- screen-size screen
-  var width/edx: int <- copy _width
-  var height/ebx: int <- copy _height
-  var x2/eax: int <- copy 0
-  var y2/ecx: int <- copy 0
-  x2, y2 <- render-stack-from-bottom-wrapping-right-then-down screen, self, x, y, width, height, x, y
-  return x2  # y2? yolo
-}
-
-# dump stack to screen from top to bottom
-# optionally render a 'cursor' with the top grapheme
-fn render-stack-from-top-wrapping-right-then-down screen: (addr screen), _self: (addr grapheme-stack), xmin: int, ymin: int, xmax: int, ymax: int, _x: int, _y: int, render-cursor?: boolean -> _/eax: int, _/ecx: int {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var data-ah/edi: (addr handle array grapheme) <- get self, data
-  var _data/eax: (addr array grapheme) <- lookup *data-ah
-  var data/edi: (addr array grapheme) <- copy _data
-  var x/eax: int <- copy _x
-  var y/ecx: int <- copy _y
-  var top-addr/edx: (addr int) <- get self, top
-  var i/ebx: int <- copy *top-addr
-  i <- decrement
-  # if render-cursor?, peel off first iteration
-  {
-    compare render-cursor?, 0/false
-    break-if-=
-    compare i, 0
-    break-if-<
-    {
-      var g/edx: (addr grapheme) <- index data, i
-      x, y <- render-grapheme screen, *g, xmin, ymin, xmax, ymax, x, y, 3/fg=cyan, 7/bg=cursor
-    }
-    i <- decrement
-  }
-  # remaining iterations
-  {
-    compare i, 0
-    break-if-<
-    {
-      var g/edx: (addr grapheme) <- index data, i
-      x, y <- render-grapheme screen, *g, xmin, ymin, xmax, ymax, x, y, 3/fg=cyan, 0/bg=cursor
-    }
-    i <- decrement
-    loop
-  }
-  return x, y
-}
-
-# helper for small words
-fn render-stack-from-top screen: (addr screen), self: (addr grapheme-stack), x: int, y: int, render-cursor?: boolean -> _/eax: int {
-  var _width/eax: int <- copy 0
-  var _height/ecx: int <- copy 0
-  _width, _height <- screen-size screen
-  var width/edx: int <- copy _width
-  var height/ebx: int <- copy _height
-  var x2/eax: int <- copy 0
-  var y2/ecx: int <- copy 0
-  x2, y2 <- render-stack-from-top-wrapping-right-then-down screen, self, x, y, width, height, x, y, render-cursor?
-  return x2  # y2? yolo
-}
-
-fn test-render-grapheme-stack {
-  # setup: gs = "abc"
-  var gs-storage: grapheme-stack
-  var gs/edi: (addr grapheme-stack) <- address gs-storage
-  initialize-grapheme-stack gs, 5
-  var g/eax: grapheme <- copy 0x61/a
-  push-grapheme-stack gs, g
-  g <- copy 0x62/b
-  push-grapheme-stack gs, g
-  g <- copy 0x63/c
-  push-grapheme-stack gs, g
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/esi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5, 4
-  #
-  var x/eax: int <- render-stack-from-bottom screen, gs, 0/x, 0/y
-  check-screen-row screen, 0/y, "abc ", "F - test-render-grapheme-stack from bottom"
-  check-ints-equal x, 3, "F - test-render-grapheme-stack from bottom: result"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "   ", "F - test-render-grapheme-stack from bottom: bg"
-  #
-  var x/eax: int <- render-stack-from-top screen, gs, 0/x, 1/y, 0/cursor=false
-  check-screen-row screen, 1/y, "cba ", "F - test-render-grapheme-stack from top without cursor"
-  check-ints-equal x, 3, "F - test-render-grapheme-stack from top without cursor: result"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "   ", "F - test-render-grapheme-stack from top without cursor: bg"
-  #
-  var x/eax: int <- render-stack-from-top screen, gs, 0/x, 2/y, 1/cursor=true
-  check-screen-row screen, 2/y, "cba ", "F - test-render-grapheme-stack from top with cursor"
-  check-ints-equal x, 3, "F - test-render-grapheme-stack from top without cursor: result"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "|   ", "F - test-render-grapheme-stack from top with cursor: bg"
-}
-
-# compare from bottom
-# beware: modifies 'stream', which must be disposed of after a false result
-fn prefix-match? _self: (addr grapheme-stack), s: (addr stream byte) -> _/eax: boolean {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var data-ah/edi: (addr handle array grapheme) <- get self, data
-  var _data/eax: (addr array grapheme) <- lookup *data-ah
-  var data/edi: (addr array grapheme) <- copy _data
-  var top-addr/ecx: (addr int) <- get self, top
-  var i/ebx: int <- copy 0
-  {
-    compare i, *top-addr
-    break-if->=
-    # if curr != expected, return false
-    {
-      var curr-a/edx: (addr grapheme) <- index data, i
-      var expected/eax: grapheme <- read-grapheme s
-      {
-        compare expected, *curr-a
-        break-if-=
-        return 0/false
-      }
-    }
-    i <- increment
-    loop
-  }
-  return 1   # true
-}
-
-# compare from bottom
-# beware: modifies 'stream', which must be disposed of after a false result
-fn suffix-match? _self: (addr grapheme-stack), s: (addr stream byte) -> _/eax: boolean {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var data-ah/edi: (addr handle array grapheme) <- get self, data
-  var _data/eax: (addr array grapheme) <- lookup *data-ah
-  var data/edi: (addr array grapheme) <- copy _data
-  var top-addr/eax: (addr int) <- get self, top
-  var i/ebx: int <- copy *top-addr
-  i <- decrement
-  {
-    compare i, 0
-    break-if-<
-    {
-      var curr-a/edx: (addr grapheme) <- index data, i
-      var expected/eax: grapheme <- read-grapheme s
-      # if curr != expected, return false
-      {
-        compare expected, *curr-a
-        break-if-=
-        return 0/false
-      }
-    }
-    i <- decrement
-    loop
-  }
-  return 1   # true
-}
-
-fn grapheme-stack-is-decimal-integer? _self: (addr grapheme-stack) -> _/eax: boolean {
-  var self/esi: (addr grapheme-stack) <- copy _self
-  var data-ah/eax: (addr handle array grapheme) <- get self, data
-  var _data/eax: (addr array grapheme) <- lookup *data-ah
-  var data/edx: (addr array grapheme) <- copy _data
-  var top-addr/ecx: (addr int) <- get self, top
-  var i/ebx: int <- copy 0
-  var result/eax: boolean <- copy 1/true
-  $grapheme-stack-is-integer?:loop: {
-    compare i, *top-addr
-    break-if->=
-    var g/edx: (addr grapheme) <- index data, i
-    result <- is-decimal-digit? *g
-    compare result, 0/false
-    break-if-=
-    i <- increment
-    loop
-  }
-  return result
-}
diff --git a/baremetal/shell/main.mu b/baremetal/shell/main.mu
deleted file mode 100644
index eb437b67..00000000
--- a/baremetal/shell/main.mu
+++ /dev/null
@@ -1,22 +0,0 @@
-# Experimental Mu shell
-# A Lisp with indent-sensitivity and infix, no macros. Commas are ignored.
-
-fn main {
-  var sandbox-storage: sandbox
-  var sandbox/esi: (addr sandbox) <- address sandbox-storage
-  initialize-sandbox sandbox
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  width, height <- screen-size 0/screen
-  {
-    render-sandbox 0/screen, sandbox, 2/x, 2/y, width, height
-    {
-      var key/eax: byte <- read-key 0/keyboard
-      compare key, 0
-      loop-if-=
-      # no way to quit right now; just reboot
-      edit-sandbox sandbox, key
-    }
-    loop
-  }
-}
diff --git a/baremetal/shell/parse.mu b/baremetal/shell/parse.mu
deleted file mode 100644
index a0045eb3..00000000
--- a/baremetal/shell/parse.mu
+++ /dev/null
@@ -1,136 +0,0 @@
-fn parse-input tokens: (addr stream cell), out: (addr handle cell), trace: (addr trace) {
-  rewind-stream tokens
-  var empty?/eax: boolean <- stream-empty? tokens
-  compare empty?, 0/false
-  {
-    break-if-=
-    error trace, "nothing to parse"
-    return
-  }
-  var close-paren?/eax: boolean <- parse-sexpression tokens, out, trace
-  {
-    compare close-paren?, 0/false
-    break-if-=
-    error trace, "')' is not a valid expression"
-    return
-  }
-  {
-    var empty?/eax: boolean <- stream-empty? tokens
-    compare empty?, 0/false
-    break-if-!=
-    error trace, "unexpected tokens at end; only type in a single expression at a time"
-  }
-}
-
-# return value: true if close-paren was encountered
-fn parse-sexpression tokens: (addr stream cell), _out: (addr handle cell), trace: (addr trace) -> _/eax: boolean {
-  trace-text trace, "read", "parse"
-  trace-lower trace
-  var curr-token-storage: cell
-  var curr-token/ecx: (addr cell) <- address curr-token-storage
-  var empty?/eax: boolean <- stream-empty? tokens
-  compare empty?, 0/false
-  {
-    break-if-=
-    error trace, "end of stream; never found a balancing ')'"
-    return 1/true
-  }
-  read-from-stream tokens, curr-token
-  $parse-sexpression:type-check: {
-    # not bracket -> parse atom
-    var is-bracket-token?/eax: boolean <- is-bracket-token? curr-token
-    compare is-bracket-token?, 0/false
-    {
-      break-if-!=
-      parse-atom curr-token, _out, trace
-      break $parse-sexpression:type-check
-    }
-    # open paren -> parse list
-    var is-open-paren?/eax: boolean <- is-open-paren-token? curr-token
-    compare is-open-paren?, 0/false
-    {
-      break-if-=
-      var curr/esi: (addr handle cell) <- copy _out
-      $parse-sexpression:list-loop: {
-        allocate-pair curr
-        var curr-addr/eax: (addr cell) <- lookup *curr
-        var left/ecx: (addr handle cell) <- get curr-addr, left
-        {
-          var is-close-paren?/eax: boolean <- parse-sexpression tokens, left, trace
-          compare is-close-paren?, 0/false
-          break-if-!= $parse-sexpression:list-loop
-        }
-        #
-        curr <- get curr-addr, right
-        loop
-      }
-      break $parse-sexpression:type-check
-    }
-    # close paren -> parse list
-    var is-close-paren?/eax: boolean <- is-close-paren-token? curr-token
-    compare is-close-paren?, 0/false
-    {
-      break-if-=
-      trace-higher trace
-      return 1/true
-    }
-    # otherwise abort
-    var stream-storage: (stream byte 0x40)
-    var stream/edx: (addr stream byte) <- address stream-storage
-    write stream, "unexpected token "
-    var curr-token-data-ah/eax: (addr handle stream byte) <- get curr-token, text-data
-    var curr-token-data/eax: (addr stream byte) <- lookup *curr-token-data-ah
-    rewind-stream curr-token-data
-    write-stream stream, curr-token-data
-    trace trace, "error", stream
-  }
-  trace-higher trace
-  return 0/false
-}
-
-fn parse-atom _curr-token: (addr cell), _out: (addr handle cell), trace: (addr trace) {
-  trace-text trace, "read", "parse atom"
-  var curr-token/ecx: (addr cell) <- copy _curr-token
-  var curr-token-data-ah/eax: (addr handle stream byte) <- get curr-token, text-data
-  var _curr-token-data/eax: (addr stream byte) <- lookup *curr-token-data-ah
-  var curr-token-data/esi: (addr stream byte) <- copy _curr-token-data
-  trace trace, "read", curr-token-data
-  # number
-  var is-number-token?/eax: boolean <- is-number-token? curr-token
-  compare is-number-token?, 0/false
-  {
-    break-if-=
-    rewind-stream curr-token-data
-    var _val/eax: int <- parse-decimal-int-from-stream curr-token-data
-    var val/ecx: int <- copy _val
-    var val-float/xmm0: float <- convert val
-    allocate-number _out
-    var out/eax: (addr handle cell) <- copy _out
-    var out-addr/eax: (addr cell) <- lookup *out
-    var dest/edi: (addr float) <- get out-addr, number-data
-    copy-to *dest, val-float
-    {
-      var stream-storage: (stream byte 0x40)
-      var stream/ecx: (addr stream byte) <- address stream-storage
-      write stream, "=> number "
-      print-number out-addr, stream, 0/no-trace
-      trace trace, "read", stream
-    }
-    return
-  }
-  # default: symbol
-  # just copy token data
-  allocate-symbol _out
-  var out/eax: (addr handle cell) <- copy _out
-  var out-addr/eax: (addr cell) <- lookup *out
-  var curr-token-data-ah/ecx: (addr handle stream byte) <- get curr-token, text-data
-  var dest-ah/edx: (addr handle stream byte) <- get out-addr, text-data
-  copy-object curr-token-data-ah, dest-ah
-  {
-    var stream-storage: (stream byte 0x40)
-    var stream/ecx: (addr stream byte) <- address stream-storage
-    write stream, "=> symbol "
-    print-symbol out-addr, stream, 0/no-trace
-    trace trace, "read", stream
-  }
-}
diff --git a/baremetal/shell/print.mu b/baremetal/shell/print.mu
deleted file mode 100644
index 32f5e725..00000000
--- a/baremetal/shell/print.mu
+++ /dev/null
@@ -1,260 +0,0 @@
-fn print-cell _in: (addr handle cell), out: (addr stream byte), trace: (addr trace) {
-  trace-text trace, "print", "print-cell"
-  trace-lower trace
-  var in/eax: (addr handle cell) <- copy _in
-  var in-addr/eax: (addr cell) <- lookup *in
-  {
-    var is-nil?/eax: boolean <- is-nil? in-addr
-    compare is-nil?, 0/false
-    break-if-=
-    write out, "()"
-    trace-higher trace
-    return
-  }
-  var in-type/ecx: (addr int) <- get in-addr, type
-  compare *in-type, 0/pair
-  {
-    break-if-!=
-    print-list in-addr, out, trace
-    trace-higher trace
-    return
-  }
-  compare *in-type, 1/number
-  {
-    break-if-!=
-    print-number in-addr, out, trace
-    trace-higher trace
-    return
-  }
-  compare *in-type, 2/symbol
-  {
-    break-if-!=
-    print-symbol in-addr, out, trace
-    trace-higher trace
-    return
-  }
-}
-
-fn print-symbol _in: (addr cell), out: (addr stream byte), trace: (addr trace) {
-  trace-text trace, "print", "symbol"
-  var in/esi: (addr cell) <- copy _in
-  var data-ah/eax: (addr handle stream byte) <- get in, text-data
-  var _data/eax: (addr stream byte) <- lookup *data-ah
-  var data/esi: (addr stream byte) <- copy _data
-  rewind-stream data
-  write-stream out, data
-  # trace
-  rewind-stream data
-  var stream-storage: (stream byte 0x40)
-  var stream/ecx: (addr stream byte) <- address stream-storage
-  write stream, "=> symbol "
-  write-stream stream, data
-  trace trace, "print", stream
-}
-
-fn print-number _in: (addr cell), out: (addr stream byte), trace: (addr trace) {
-  var in/esi: (addr cell) <- copy _in
-  var val/eax: (addr float) <- get in, number-data
-  write-float-decimal-approximate out, *val, 3/precision
-  # trace
-  var stream-storage: (stream byte 0x40)
-  var stream/ecx: (addr stream byte) <- address stream-storage
-  write stream, "=> number "
-  write-float-decimal-approximate stream, *val, 3/precision
-  trace trace, "print", stream
-}
-
-fn print-list _in: (addr cell), out: (addr stream byte), trace: (addr trace) {
-  var curr/esi: (addr cell) <- copy _in
-  write out, "("
-  $print-list:loop: {
-    var left/ecx: (addr handle cell) <- get curr, left
-    {
-      var left-addr/eax: (addr cell) <- lookup *left
-      var left-is-nil?/eax: boolean <- is-nil? left-addr
-      compare left-is-nil?, 0/false
-      {
-        break-if-=
-        trace-text trace, "print", "left is null"
-        break $print-list:loop
-      }
-    }
-    print-cell left, out, trace
-    var right/ecx: (addr handle cell) <- get curr, right
-    var right-addr/eax: (addr cell) <- lookup *right
-    {
-      compare right-addr, 0
-      break-if-!=
-      abort "null encountered"
-    }
-    {
-      var right-is-nil?/eax: boolean <- is-nil? right-addr
-      compare right-is-nil?, 0/false
-      {
-        break-if-=
-        trace-text trace, "print", "right is null"
-        break $print-list:loop
-      }
-    }
-    write out, " "
-    var right-type-addr/edx: (addr int) <- get right-addr, type
-    {
-      compare *right-type-addr, 0/pair
-      break-if-=
-      write out, ". "
-      print-cell right, out, trace
-      break $print-list:loop
-    }
-    curr <- copy right-addr
-    loop
-  }
-  write out, ")"
-}
-
-# Most lisps intern nil, but we don't really have globals yet, so we'll be
-# less efficient for now.
-fn is-nil? _in: (addr cell) -> _/eax: boolean {
-  var in/esi: (addr cell) <- copy _in
-  # if type != pair, return false
-  var type/eax: (addr int) <- get in, type
-  compare *type, 0/pair
-  {
-    break-if-=
-    return 0/false
-  }
-  # if left != null, return false
-  var left-ah/eax: (addr handle cell) <- get in, left
-  var left/eax: (addr cell) <- lookup *left-ah
-  compare left, 0
-  {
-    break-if-=
-    return 0/false
-  }
-  # if right != null, return false
-  var right-ah/eax: (addr handle cell) <- get in, right
-  var right/eax: (addr cell) <- lookup *right-ah
-  compare right, 0
-  {
-    break-if-=
-    return 0/false
-  }
-  return 1/true
-}
-
-fn test-print-cell-zero {
-  var num-storage: (handle cell)
-  var num/esi: (addr handle cell) <- address num-storage
-  new-integer num, 0
-  var out-storage: (stream byte 0x40)
-  var out/edi: (addr stream byte) <- address out-storage
-  print-cell num, out, 0/no-trace
-  check-stream-equal out, "0", "F - test-print-cell-zero"
-}
-
-fn test-print-cell-integer {
-  var num-storage: (handle cell)
-  var num/esi: (addr handle cell) <- address num-storage
-  new-integer num, 1
-  var out-storage: (stream byte 0x40)
-  var out/edi: (addr stream byte) <- address out-storage
-  print-cell num, out, 0/no-trace
-  check-stream-equal out, "1", "F - test-print-cell-integer"
-}
-
-fn test-print-cell-integer-2 {
-  var num-storage: (handle cell)
-  var num/esi: (addr handle cell) <- address num-storage
-  new-integer num, 0x30
-  var out-storage: (stream byte 0x40)
-  var out/edi: (addr stream byte) <- address out-storage
-  print-cell num, out, 0/no-trace
-  check-stream-equal out, "48", "F - test-print-cell-integer-2"
-}
-
-fn test-print-cell-fraction {
-  var num-storage: (handle cell)
-  var num/esi: (addr handle cell) <- address num-storage
-  var val/xmm0: float <- rational 1, 2
-  new-float num, val
-  var out-storage: (stream byte 0x40)
-  var out/edi: (addr stream byte) <- address out-storage
-  print-cell num, out, 0/no-trace
-  check-stream-equal out, "0.5", "F - test-print-cell-fraction"
-}
-
-fn test-print-cell-symbol {
-  var sym-storage: (handle cell)
-  var sym/esi: (addr handle cell) <- address sym-storage
-  new-symbol sym, "abc"
-  var out-storage: (stream byte 0x40)
-  var out/edi: (addr stream byte) <- address out-storage
-  print-cell sym, out, 0/no-trace
-  check-stream-equal out, "abc", "F - test-print-cell-symbol"
-}
-
-fn test-print-cell-nil-list {
-  var nil-storage: (handle cell)
-  var nil/esi: (addr handle cell) <- address nil-storage
-  allocate-pair nil
-  var out-storage: (stream byte 0x40)
-  var out/edi: (addr stream byte) <- address out-storage
-  print-cell nil, out, 0/no-trace
-  check-stream-equal out, "()", "F - test-print-cell-nil-list"
-}
-
-fn test-print-cell-singleton-list {
-  # list
-  var left-storage: (handle cell)
-  var left/ecx: (addr handle cell) <- address left-storage
-  new-symbol left, "abc"
-  var nil-storage: (handle cell)
-  var nil/edx: (addr handle cell) <- address nil-storage
-  allocate-pair nil
-  var list-storage: (handle cell)
-  var list/esi: (addr handle cell) <- address list-storage
-  new-pair list, *left, *nil
-  #
-  var out-storage: (stream byte 0x40)
-  var out/edi: (addr stream byte) <- address out-storage
-  print-cell list, out, 0/no-trace
-  check-stream-equal out, "(abc)", "F - test-print-cell-singleton-list"
-}
-
-fn test-print-cell-list {
-  # list = cons "abc", nil
-  var left-storage: (handle cell)
-  var left/ecx: (addr handle cell) <- address left-storage
-  new-symbol left, "abc"
-  var nil-storage: (handle cell)
-  var nil/edx: (addr handle cell) <- address nil-storage
-  allocate-pair nil
-  var list-storage: (handle cell)
-  var list/esi: (addr handle cell) <- address list-storage
-  new-pair list, *left, *nil
-  # list = cons 64, list
-  new-integer left, 0x40
-  new-pair list, *left, *list
-  #
-  var out-storage: (stream byte 0x40)
-  var out/edi: (addr stream byte) <- address out-storage
-  print-cell list, out, 0/no-trace
-  check-stream-equal out, "(64 abc)", "F - test-print-cell-list"
-}
-
-fn test-print-dotted-list {
-  # list = cons 64, "abc"
-  var left-storage: (handle cell)
-  var left/ecx: (addr handle cell) <- address left-storage
-  new-symbol left, "abc"
-  var right-storage: (handle cell)
-  var right/edx: (addr handle cell) <- address right-storage
-  new-integer right, 0x40
-  var list-storage: (handle cell)
-  var list/esi: (addr handle cell) <- address list-storage
-  new-pair list, *left, *right
-  #
-  var out-storage: (stream byte 0x40)
-  var out/edi: (addr stream byte) <- address out-storage
-  print-cell list, out, 0/no-trace
-  check-stream-equal out, "(abc . 64)", "F - test-print-dotted-list"
-}
diff --git a/baremetal/shell/read.mu b/baremetal/shell/read.mu
deleted file mode 100644
index d3e1dc86..00000000
--- a/baremetal/shell/read.mu
+++ /dev/null
@@ -1,15 +0,0 @@
-# out is not allocated
-fn read-cell in: (addr gap-buffer), out: (addr handle cell), trace: (addr trace) {
-  var tokens-storage: (stream cell 0x100)
-  var tokens/ecx: (addr stream cell) <- address tokens-storage
-  tokenize in, tokens, trace
-  var error?/eax: boolean <- has-errors? trace
-  compare error?, 0/false
-  {
-    break-if-=
-    return
-  }
-  # TODO: insert parens
-  # TODO: transform infix
-  parse-input tokens, out, trace
-}
diff --git a/baremetal/shell/sandbox.mu b/baremetal/shell/sandbox.mu
deleted file mode 100644
index 49c2a5f9..00000000
--- a/baremetal/shell/sandbox.mu
+++ /dev/null
@@ -1,263 +0,0 @@
-type sandbox {
-  data: (handle gap-buffer)
-  value: (handle stream byte)
-  trace: (handle trace)
-  cursor-in-trace?: boolean
-}
-
-fn initialize-sandbox _self: (addr sandbox) {
-  var self/esi: (addr sandbox) <- copy _self
-  var data-ah/eax: (addr handle gap-buffer) <- get self, data
-  allocate data-ah
-  var data/eax: (addr gap-buffer) <- lookup *data-ah
-  initialize-gap-buffer data, 0x1000/4KB
-  var value-ah/eax: (addr handle stream byte) <- get self, value
-  populate-stream value-ah, 0x1000/4KB
-  var trace-ah/eax: (addr handle trace) <- get self, trace
-  allocate trace-ah
-  var trace/eax: (addr trace) <- lookup *trace-ah
-  initialize-trace trace, 0x1000/lines, 0x80/visible-lines
-}
-
-## some helpers for tests
-
-fn initialize-sandbox-with _self: (addr sandbox), s: (addr array byte) {
-  var self/esi: (addr sandbox) <- copy _self
-  var data-ah/eax: (addr handle gap-buffer) <- get self, data
-  allocate data-ah
-  var data/eax: (addr gap-buffer) <- lookup *data-ah
-  initialize-gap-buffer-with data, s
-}
-
-fn allocate-sandbox-with _out: (addr handle sandbox), s: (addr array byte) {
-  var out/eax: (addr handle sandbox) <- copy _out
-  allocate out
-  var out-addr/eax: (addr sandbox) <- lookup *out
-  initialize-sandbox-with out-addr, s
-}
-
-##
-
-fn render-sandbox screen: (addr screen), _self: (addr sandbox), xmin: int, ymin: int, xmax: int, ymax: int {
-  clear-screen screen
-  var self/esi: (addr sandbox) <- copy _self
-  # data
-  var data-ah/eax: (addr handle gap-buffer) <- get self, data
-  var _data/eax: (addr gap-buffer) <- lookup *data-ah
-  var data/edx: (addr gap-buffer) <- copy _data
-  var x/eax: int <- copy xmin
-  var y/ecx: int <- copy ymin
-  var cursor-in-sandbox?/ebx: boolean <- copy 0/false
-  {
-    var cursor-in-trace?/eax: (addr boolean) <- get self, cursor-in-trace?
-    compare *cursor-in-trace?, 0/false
-    break-if-!=
-    cursor-in-sandbox? <- copy 1/true
-  }
-  x, y <- render-gap-buffer-wrapping-right-then-down screen, data, x, y, xmax, ymax, cursor-in-sandbox?
-  y <- increment
-  # trace
-  var trace-ah/eax: (addr handle trace) <- get self, trace
-  var _trace/eax: (addr trace) <- lookup *trace-ah
-  var trace/edx: (addr trace) <- copy _trace
-  var cursor-in-trace?/eax: (addr boolean) <- get self, cursor-in-trace?
-  y <- render-trace screen, trace, xmin, y, xmax, ymax, *cursor-in-trace?
-  # value
-  $render-sandbox:value: {
-    var value-ah/eax: (addr handle stream byte) <- get self, value
-    var _value/eax: (addr stream byte) <- lookup *value-ah
-    var value/esi: (addr stream byte) <- copy _value
-    rewind-stream value
-    var done?/eax: boolean <- stream-empty? value
-    compare done?, 0/false
-    break-if-!=
-    var x/eax: int <- copy 0
-    x, y <- draw-text-wrapping-right-then-down screen, "=> ", xmin, y, xmax, ymax, xmin, y, 7/fg, 0/bg
-    var x2/edx: int <- copy x
-    var dummy/eax: int <- draw-stream-rightward screen, value, x2, xmax, y, 7/fg=grey, 0/bg
-  }
-  # render menu
-  var cursor-in-trace?/eax: (addr boolean) <- get self, cursor-in-trace?
-  compare *cursor-in-trace?, 0/false
-  {
-    break-if-=
-    render-trace-menu screen
-    return
-  }
-  render-sandbox-menu screen
-}
-
-fn render-sandbox-menu screen: (addr screen) {
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  width, height <- screen-size screen
-  var y/ecx: int <- copy height
-  y <- decrement
-  set-cursor-position screen, 0/x, y
-  draw-text-rightward-from-cursor screen, " ctrl-s ", width, 0/fg, 7/bg=grey
-  draw-text-rightward-from-cursor screen, " run sandbox  ", width, 7/fg, 0/bg
-  draw-text-rightward-from-cursor screen, " ctrl-d ", width, 0/fg, 7/bg=grey
-  draw-text-rightward-from-cursor screen, " cursor down  ", width, 7/fg, 0/bg
-  draw-text-rightward-from-cursor screen, " ctrl-u ", width, 0/fg, 7/bg=grey
-  draw-text-rightward-from-cursor screen, " cursor up  ", width, 7/fg, 0/bg
-  draw-text-rightward-from-cursor screen, " tab ", width, 0/fg, 9/bg=blue
-  draw-text-rightward-from-cursor screen, " move to trace  ", width, 7/fg, 0/bg
-}
-
-fn edit-sandbox _self: (addr sandbox), key: byte {
-  var self/esi: (addr sandbox) <- copy _self
-  var g/edx: grapheme <- copy key
-  # running code
-  {
-    compare g, 0x12/ctrl-r
-    break-if-!=
-    # ctrl-r: run function outside sandbox
-    # required: fn (addr screen), (addr keyboard)
-    # Mu will pass in the real screen and keyboard.
-    return
-  }
-  {
-    compare g, 0x13/ctrl-s
-    break-if-!=
-    # ctrl-s: run sandbox(es)
-    var data-ah/eax: (addr handle gap-buffer) <- get self, data
-    var _data/eax: (addr gap-buffer) <- lookup *data-ah
-    var data/ecx: (addr gap-buffer) <- copy _data
-    var value-ah/eax: (addr handle stream byte) <- get self, value
-    var _value/eax: (addr stream byte) <- lookup *value-ah
-    var value/edx: (addr stream byte) <- copy _value
-    var trace-ah/eax: (addr handle trace) <- get self, trace
-    var trace/eax: (addr trace) <- lookup *trace-ah
-    clear-trace trace
-    run data, value, trace
-    return
-  }
-  # tab
-  var cursor-in-trace?/eax: (addr boolean) <- get self, cursor-in-trace?
-  {
-    compare g, 9/tab
-    break-if-!=
-    # if cursor in input, switch to trace
-    {
-      compare *cursor-in-trace?, 0/false
-      break-if-!=
-      copy-to *cursor-in-trace?, 1/true
-      return
-    }
-    # if cursor in trace, switch to input
-    copy-to *cursor-in-trace?, 0/false
-    return
-  }
-  # if cursor in trace, send cursor to trace
-  {
-    compare *cursor-in-trace?, 0/false
-    break-if-=
-    var trace-ah/eax: (addr handle trace) <- get self, trace
-    var trace/eax: (addr trace) <- lookup *trace-ah
-    edit-trace trace, g
-    return
-  }
-  # otherwise send cursor to input
-  var data-ah/eax: (addr handle gap-buffer) <- get self, data
-  var data/eax: (addr gap-buffer) <- lookup *data-ah
-  edit-gap-buffer data, g
-  return
-}
-
-fn run in: (addr gap-buffer), out: (addr stream byte), trace: (addr trace) {
-  var read-result-storage: (handle cell)
-  var read-result/esi: (addr handle cell) <- address read-result-storage
-  read-cell in, read-result, trace
-  var error?/eax: boolean <- has-errors? trace
-  {
-    compare error?, 0/false
-    break-if-=
-    return
-  }
-  # TODO: eval
-  clear-stream out
-  print-cell read-result, out, trace
-  mark-lines-dirty trace
-}
-
-fn test-run-integer {
-  var sandbox-storage: sandbox
-  var sandbox/esi: (addr sandbox) <- address sandbox-storage
-  initialize-sandbox sandbox
-  # type "1"
-  edit-sandbox sandbox, 0x31/1
-  # eval
-  edit-sandbox sandbox, 0x13/ctrl-s
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x80/width, 0x10/height
-  #
-  render-sandbox screen, sandbox, 0/x, 0/y, 0x80/width, 0x10/height
-  check-screen-row screen, 0/y, "1    ", "F - test-run-integer/0"
-  check-screen-row screen, 1/y, "...  ", "F - test-run-integer/1"
-  check-screen-row screen, 2/y, "=> 1 ", "F - test-run-integer/2"
-}
-
-fn test-run-error-invalid-integer {
-  var sandbox-storage: sandbox
-  var sandbox/esi: (addr sandbox) <- address sandbox-storage
-  initialize-sandbox sandbox
-  # type "1a"
-  edit-sandbox sandbox, 0x31/1
-  edit-sandbox sandbox, 0x61/a
-  # eval
-  edit-sandbox sandbox, 0x13/ctrl-s
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x80/width, 0x10/height
-  #
-  render-sandbox screen, sandbox, 0/x, 0/y, 0x80/width, 0x10/height
-  check-screen-row screen, 0/y, "1a             ", "F - test-run-error-invalid-integer/0"
-  check-screen-row screen, 1/y, "...            ", "F - test-run-error-invalid-integer/0"
-  check-screen-row screen, 2/y, "invalid number ", "F - test-run-error-invalid-integer/2"
-}
-
-fn test-run-move-cursor-into-trace {
-  var sandbox-storage: sandbox
-  var sandbox/esi: (addr sandbox) <- address sandbox-storage
-  initialize-sandbox sandbox
-  # type "12"
-  edit-sandbox sandbox, 0x31/1
-  edit-sandbox sandbox, 0x32/2
-  # eval
-  edit-sandbox sandbox, 0x13/ctrl-s
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x80/width, 0x10/height
-  #
-  render-sandbox screen, sandbox, 0/x, 0/y, 0x80/width, 0x10/height
-  check-screen-row screen,                                  0/y, "12    ", "F - test-run-move-cursor-into-trace/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "  |   ", "F - test-run-move-cursor-into-trace/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "...   ", "F - test-run-move-cursor-into-trace/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-run-move-cursor-into-trace/pre-1/cursor"
-  check-screen-row screen,                                  2/y, "=> 12 ", "F - test-run-move-cursor-into-trace/pre-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-run-move-cursor-into-trace/pre-2/cursor"
-  # move cursor into trace
-  edit-sandbox sandbox, 9/tab
-  #
-  render-sandbox screen, sandbox, 0/x, 0/y, 0x80/width, 0x10/height
-  check-screen-row screen,                                  0/y, "12    ", "F - test-run-move-cursor-into-trace/trace-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "      ", "F - test-run-move-cursor-into-trace/trace-0/cursor"
-  check-screen-row screen,                                  1/y, "...   ", "F - test-run-move-cursor-into-trace/trace-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "|||   ", "F - test-run-move-cursor-into-trace/trace-1/cursor"
-  check-screen-row screen,                                  2/y, "=> 12 ", "F - test-run-move-cursor-into-trace/trace-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-run-move-cursor-into-trace/trace-2/cursor"
-  # move cursor into input
-  edit-sandbox sandbox, 9/tab
-  #
-  render-sandbox screen, sandbox, 0/x, 0/y, 0x80/width, 0x10/height
-  check-screen-row screen,                                  0/y, "12    ", "F - test-run-move-cursor-into-trace/input-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "  |   ", "F - test-run-move-cursor-into-trace/input-0/cursor"
-  check-screen-row screen,                                  1/y, "...   ", "F - test-run-move-cursor-into-trace/input-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-run-move-cursor-into-trace/input-1/cursor"
-  check-screen-row screen,                                  2/y, "=> 12 ", "F - test-run-move-cursor-into-trace/input-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-run-move-cursor-into-trace/input-2/cursor"
-}
diff --git a/baremetal/shell/tokenize.mu b/baremetal/shell/tokenize.mu
deleted file mode 100644
index 7beedf23..00000000
--- a/baremetal/shell/tokenize.mu
+++ /dev/null
@@ -1,422 +0,0 @@
-# We reuse the cell data structure for tokenization
-# Token cells are special, though. They have no type, they're always atoms,
-# they always have text-data.
-
-fn tokenize in: (addr gap-buffer), out: (addr stream cell), trace: (addr trace) {
-  trace-text trace, "read", "tokenize"
-  trace-lower trace
-  rewind-gap-buffer in
-  var token-storage: cell
-  var token/edx: (addr cell) <- address token-storage
-  {
-    var done?/eax: boolean <- gap-buffer-scan-done? in
-    compare done?, 0/false
-    break-if-!=
-    # initialize token data each iteration to avoid aliasing
-    var dest-ah/eax: (addr handle stream byte) <- get token, text-data
-    populate-stream dest-ah, 0x40/max-token-size
-    #
-    next-token in, token, trace
-    var error?/eax: boolean <- has-errors? trace
-    compare error?, 0/false
-    {
-      break-if-=
-      return
-    }
-    write-to-stream out, token  # shallow-copy text-data
-    loop
-  }
-  trace-higher trace
-}
-
-fn next-token in: (addr gap-buffer), _out-cell: (addr cell), trace: (addr trace) {
-  trace-text trace, "read", "next-token"
-  trace-lower trace
-  var out-cell/eax: (addr cell) <- copy _out-cell
-  var out-ah/eax: (addr handle stream byte) <- get out-cell, text-data
-  var _out/eax: (addr stream byte) <- lookup *out-ah
-  var out/edi: (addr stream byte) <- copy _out
-  $next-token:body: {
-    clear-stream out
-    skip-whitespace-from-gap-buffer in
-    var g/eax: grapheme <- peek-from-gap-buffer in
-    {
-      var stream-storage: (stream byte 0x40)
-      var stream/esi: (addr stream byte) <- address stream-storage
-      write stream, "next: "
-      var gval/eax: int <- copy g
-      write-int32-hex stream, gval
-      trace trace, "read", stream
-    }
-    # digit
-    {
-      var digit?/eax: boolean <- is-decimal-digit? g
-      compare digit?, 0/false
-      break-if-=
-      next-number-token in, out, trace
-      break $next-token:body
-    }
-    # other symbol char
-    {
-      var symbol?/eax: boolean <- is-symbol-grapheme? g
-      compare symbol?, 0/false
-      break-if-=
-      next-symbol-token in, out, trace
-      break $next-token:body
-    }
-    # brackets are always single-char tokens
-    {
-      var bracket?/eax: boolean <- is-bracket-grapheme? g
-      compare bracket?, 0/false
-      break-if-=
-      var g/eax: grapheme <- read-from-gap-buffer in
-      next-bracket-token g, out, trace
-      break $next-token:body
-    }
-  }
-  trace-higher trace
-  var stream-storage: (stream byte 0x40)
-  var stream/eax: (addr stream byte) <- address stream-storage
-  write stream, "=> "
-  rewind-stream out
-  write-stream stream, out
-  trace trace, "read", stream
-}
-
-fn next-symbol-token in: (addr gap-buffer), out: (addr stream byte), trace: (addr trace) {
-  trace-text trace, "read", "looking for a symbol"
-  trace-lower trace
-  $next-symbol-token:loop: {
-    var done?/eax: boolean <- gap-buffer-scan-done? in
-    compare done?, 0/false
-    break-if-!=
-    var g/eax: grapheme <- peek-from-gap-buffer in
-    {
-      var stream-storage: (stream byte 0x40)
-      var stream/esi: (addr stream byte) <- address stream-storage
-      write stream, "next: "
-      var gval/eax: int <- copy g
-      write-int32-hex stream, gval
-      trace trace, "read", stream
-    }
-    # if non-symbol, return
-    {
-      var symbol-grapheme?/eax: boolean <- is-symbol-grapheme? g
-      compare symbol-grapheme?, 0/false
-      break-if-!=
-      trace-text trace, "read", "stop"
-      break $next-symbol-token:loop
-    }
-    var g/eax: grapheme <- read-from-gap-buffer in
-    write-grapheme out, g
-    loop
-  }
-  trace-higher trace
-  var stream-storage: (stream byte 0x40)
-  var stream/esi: (addr stream byte) <- address stream-storage
-  write stream, "=> "
-  rewind-stream out
-  write-stream stream, out
-  trace trace, "read", stream
-}
-
-fn next-number-token in: (addr gap-buffer), out: (addr stream byte), trace: (addr trace) {
-  trace-text trace, "read", "looking for a number"
-  trace-lower trace
-  $next-number-token:loop: {
-    var done?/eax: boolean <- gap-buffer-scan-done? in
-    compare done?, 0/false
-    break-if-!=
-    var g/eax: grapheme <- peek-from-gap-buffer in
-    {
-      var stream-storage: (stream byte 0x40)
-      var stream/esi: (addr stream byte) <- address stream-storage
-      write stream, "next: "
-      var gval/eax: int <- copy g
-      write-int32-hex stream, gval
-      trace trace, "read", stream
-    }
-    # if not symbol grapheme, return
-    {
-      var symbol-grapheme?/eax: boolean <- is-symbol-grapheme? g
-      compare symbol-grapheme?, 0/false
-      break-if-!=
-      trace-text trace, "read", "stop"
-      break $next-number-token:loop
-    }
-    # if not digit grapheme, abort
-    {
-      var digit?/eax: boolean <- is-decimal-digit? g
-      compare digit?, 0/false
-      break-if-!=
-      error trace, "invalid number"
-      return
-    }
-    trace-text trace, "read", "append"
-    var g/eax: grapheme <- read-from-gap-buffer in
-    write-grapheme out, g
-    loop
-  }
-  trace-higher trace
-}
-
-fn next-bracket-token g: grapheme, out: (addr stream byte), trace: (addr trace) {
-  trace-text trace, "read", "bracket"
-  write-grapheme out, g
-  var stream-storage: (stream byte 0x40)
-  var stream/esi: (addr stream byte) <- address stream-storage
-  write stream, "=> "
-  rewind-stream out
-  write-stream stream, out
-  trace trace, "read", stream
-}
-
-fn is-symbol-grapheme? g: grapheme -> _/eax: boolean {
-  ## whitespace
-  compare g, 9/tab
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0xa/newline
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x20/space
-  {
-    break-if-!=
-    return 0/false
-  }
-  ## quotes
-  compare g, 0x22/double-quote
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x27/single-quote
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x60/backquote
-  {
-    break-if-!=
-    return 0/false
-  }
-  ## brackets
-  compare g, 0x28/open-paren
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x29/close-paren
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x5b/open-square-bracket
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x5d/close-square-bracket
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x7b/open-curly-bracket
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x7d/close-curly-bracket
-  {
-    break-if-!=
-    return 0/false
-  }
-  # - other punctuation
-  # '!' is a symbol char
-  compare g, 0x23/hash
-  {
-    break-if-!=
-    return 0/false
-  }
-  # '$' is a symbol char
-  compare g, 0x25/percent
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x26/ampersand
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x2a/asterisk
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x2b/plus
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x2c/comma
-  {
-    break-if-!=
-    return 0/false
-  }
-  # '-' is a symbol char
-  compare g, 0x2e/period
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x2f/slash
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x2f/slash
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x3a/colon
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x3b/semi-colon
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x3c/less-than
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x3d/equal
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x3e/greater-than
-  {
-    break-if-!=
-    return 0/false
-  }
-  # '?' is a symbol char
-  compare g, 0x40/at-sign
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x5c/backslash
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x5e/caret
-  {
-    break-if-!=
-    return 0/false
-  }
-  # '_' is a symbol char
-  compare g, 0x7c/vertical-line
-  {
-    break-if-!=
-    return 0/false
-  }
-  compare g, 0x7e/tilde
-  {
-    break-if-!=
-    return 0/false
-  }
-  return 1/true
-}
-
-fn is-bracket-grapheme? g: grapheme -> _/eax: boolean {
-  compare g, 0x28/open-paren
-  {
-    break-if-!=
-    return 1/true
-  }
-  compare g, 0x29/close-paren
-  {
-    break-if-!=
-    return 1/true
-  }
-  compare g, 0x5b/open-square-bracket
-  {
-    break-if-!=
-    return 1/true
-  }
-  compare g, 0x5d/close-square-bracket
-  {
-    break-if-!=
-    return 1/true
-  }
-  compare g, 0x7b/open-curly-bracket
-  {
-    break-if-!=
-    return 1/true
-  }
-  compare g, 0x7d/close-curly-bracket
-  {
-    break-if-!=
-    return 1/true
-  }
-  return 0/false
-}
-
-fn is-number-token? _in: (addr cell) -> _/eax: boolean {
-  var in/eax: (addr cell) <- copy _in
-  var in-data-ah/eax: (addr handle stream byte) <- get in, text-data
-  var in-data/eax: (addr stream byte) <- lookup *in-data-ah
-  rewind-stream in-data
-  var g/eax: grapheme <- read-grapheme in-data
-  var result/eax: boolean <- is-decimal-digit? g
-  return result
-}
-
-fn is-bracket-token? _in: (addr cell) -> _/eax: boolean {
-  var in/eax: (addr cell) <- copy _in
-  var in-data-ah/eax: (addr handle stream byte) <- get in, text-data
-  var in-data/eax: (addr stream byte) <- lookup *in-data-ah
-  rewind-stream in-data
-  var g/eax: grapheme <- read-grapheme in-data
-  var result/eax: boolean <- is-bracket-grapheme? g
-  return result
-}
-
-fn is-open-paren-token? _in: (addr cell) -> _/eax: boolean {
-  var in/eax: (addr cell) <- copy _in
-  var in-data-ah/eax: (addr handle stream byte) <- get in, text-data
-  var in-data/eax: (addr stream byte) <- lookup *in-data-ah
-  rewind-stream in-data
-  var g/eax: grapheme <- read-grapheme in-data
-  compare g, 0x28/open-paren
-  {
-    break-if-!=
-    return 1/true
-  }
-  return 0/false
-}
-
-fn is-close-paren-token? _in: (addr cell) -> _/eax: boolean {
-  var in/eax: (addr cell) <- copy _in
-  var in-data-ah/eax: (addr handle stream byte) <- get in, text-data
-  var in-data/eax: (addr stream byte) <- lookup *in-data-ah
-  rewind-stream in-data
-  var g/eax: grapheme <- read-grapheme in-data
-  compare g, 0x29/open-paren
-  {
-    break-if-!=
-    return 1/true
-  }
-  return 0/false
-}
diff --git a/baremetal/shell/trace.mu b/baremetal/shell/trace.mu
deleted file mode 100644
index 7fd52ceb..00000000
--- a/baremetal/shell/trace.mu
+++ /dev/null
@@ -1,1449 +0,0 @@
-# A trace records the evolution of a computation.
-# An integral part of the Mu Shell is facilities for browsing traces.
-
-type trace {
-  # steady-state life cycle of a trace:
-  #   reload loop:
-  #     there are already some visible lines
-  #     append a bunch of new trace lines to the trace
-  #     render loop:
-  #       rendering displays trace lines that match visible lines
-  #       rendering computes cursor-line based on the cursor-y coordinate
-  #       edit-trace updates cursor-y coordinate
-  #       edit-trace might add/remove lines to visible
-  curr-depth: int  # depth that will be assigned to next line appended
-  data: (handle array trace-line)
-  first-free: int
-  visible: (handle array trace-line)
-  recompute-visible?: boolean
-  top-line-index: int  # index into data
-  cursor-y: int  # row index on screen
-  cursor-line-index: int  # index into data
-}
-
-type trace-line {
-  depth: int
-  label: (handle array byte)
-  data: (handle array byte)
-  visible?: boolean
-}
-
-fn initialize-trace _self: (addr trace), capacity: int, visible-capacity: int {
-  var self/esi: (addr trace) <- copy _self
-  compare self, 0
-  break-if-=
-  var trace-ah/eax: (addr handle array trace-line) <- get self, data
-  populate trace-ah, capacity
-  var visible-ah/eax: (addr handle array trace-line) <- get self, visible
-  populate visible-ah, visible-capacity
-}
-
-fn clear-trace _self: (addr trace) {
-  var self/eax: (addr trace) <- copy _self
-  compare self, 0
-  break-if-=
-  var len/edx: (addr int) <- get self, first-free
-  copy-to *len, 0
-  # might leak memory; existing elements won't be used anymore
-}
-
-fn mark-lines-dirty _self: (addr trace) {
-  var self/eax: (addr trace) <- copy _self
-  var dest/edx: (addr boolean) <- get self, recompute-visible?
-  copy-to *dest, 1/true
-}
-
-fn mark-lines-clean _self: (addr trace) {
-  var self/eax: (addr trace) <- copy _self
-  var dest/edx: (addr boolean) <- get self, recompute-visible?
-  copy-to *dest, 0/false
-}
-
-fn has-errors? _self: (addr trace) -> _/eax: boolean {
-  var self/eax: (addr trace) <- copy _self
-  var max/edx: (addr int) <- get self, first-free
-  var trace-ah/eax: (addr handle array trace-line) <- get self, data
-  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
-  var trace/esi: (addr array trace-line) <- copy _trace
-  var i/ecx: int <- copy 0
-  {
-    compare i, *max
-    break-if->=
-    var offset/eax: (offset trace-line) <- compute-offset trace, i
-    var curr/eax: (addr trace-line) <- index trace, offset
-    var curr-label-ah/eax: (addr handle array byte) <- get curr, label
-    var curr-label/eax: (addr array byte) <- lookup *curr-label-ah
-    var is-error?/eax: boolean <- string-equal? curr-label, "error"
-    compare is-error?, 0/false
-    {
-      break-if-=
-      return 1/true
-    }
-    i <- increment
-    loop
-  }
-  return 0/false
-}
-
-fn trace _self: (addr trace), label: (addr array byte), message: (addr stream byte) {
-  var self/esi: (addr trace) <- copy _self
-  compare self, 0
-  break-if-=
-  var data-ah/eax: (addr handle array trace-line) <- get self, data
-  var data/eax: (addr array trace-line) <- lookup *data-ah
-  var index-addr/edi: (addr int) <- get self, first-free
-  var index/ecx: int <- copy *index-addr
-  var offset/ecx: (offset trace-line) <- compute-offset data, index
-  var dest/eax: (addr trace-line) <- index data, offset
-  var depth/ecx: (addr int) <- get self, curr-depth
-  rewind-stream message
-  initialize-trace-line *depth, label, message, dest
-  increment *index-addr
-}
-
-fn trace-text self: (addr trace), label: (addr array byte), s: (addr array byte) {
-  compare self, 0
-  break-if-=
-  var data-storage: (stream byte 0x100)
-  var data/eax: (addr stream byte) <- address data-storage
-  write data, s
-  trace self, label, data
-}
-
-fn error self: (addr trace), message: (addr array byte) {
-  trace-text self, "error", message
-}
-
-fn initialize-trace-line depth: int, label: (addr array byte), data: (addr stream byte), _out: (addr trace-line) {
-  var out/edi: (addr trace-line) <- copy _out
-  # depth
-  var src/eax: int <- copy depth
-  var dest/ecx: (addr int) <- get out, depth
-  copy-to *dest, src
-  # label
-  var dest/eax: (addr handle array byte) <- get out, label
-  copy-array-object label, dest
-  # data
-  var dest/eax: (addr handle array byte) <- get out, data
-  stream-to-array data, dest
-}
-
-fn trace-lower _self: (addr trace) {
-  var self/esi: (addr trace) <- copy _self
-  compare self, 0
-  break-if-=
-  var depth/eax: (addr int) <- get self, curr-depth
-  increment *depth
-}
-
-fn trace-higher _self: (addr trace) {
-  var self/esi: (addr trace) <- copy _self
-  compare self, 0
-  break-if-=
-  var depth/eax: (addr int) <- get self, curr-depth
-  decrement *depth
-}
-
-fn render-trace screen: (addr screen), _self: (addr trace), xmin: int, ymin: int, xmax: int, ymax: int, show-cursor?: boolean -> _/ecx: int {
-  var already-hiding-lines?: boolean
-  var y/ecx: int <- copy ymin
-  var self/esi: (addr trace) <- copy _self
-  compare self, 0
-  {
-    break-if-!=
-    return ymin
-  }
-  clamp-cursor-to-top self, y
-  var trace-ah/eax: (addr handle array trace-line) <- get self, data
-  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
-  var trace/edi: (addr array trace-line) <- copy _trace
-  var i/edx: int <- copy 0
-  var max-addr/ebx: (addr int) <- get self, first-free
-  var max/ebx: int <- copy *max-addr
-  $render-trace:loop: {
-    compare i, max
-    break-if->=
-    $render-trace:iter: {
-      var offset/ebx: (offset trace-line) <- compute-offset trace, i
-      var curr/ebx: (addr trace-line) <- index trace, offset
-      var curr-label-ah/eax: (addr handle array byte) <- get curr, label
-      var curr-label/eax: (addr array byte) <- lookup *curr-label-ah
-      var bg/edi: int <- copy 0/black
-      compare show-cursor?, 0/false
-      {
-        break-if-=
-        var cursor-y/eax: (addr int) <- get self, cursor-y
-        compare *cursor-y, y
-        break-if-!=
-        bg <- copy 7/cursor-line-bg
-        var cursor-line-index/eax: (addr int) <- get self, cursor-line-index
-        copy-to *cursor-line-index, i
-      }
-      # always display errors
-      var is-error?/eax: boolean <- string-equal? curr-label, "error"
-      {
-        compare is-error?, 0/false
-        break-if-=
-        y <- render-trace-line screen, curr, xmin, y, xmax, ymax, 0xc/fg=trace-error, bg
-        copy-to already-hiding-lines?, 0/false
-        break $render-trace:iter
-      }
-      # display expanded lines
-      var display?/eax: boolean <- should-render? self, curr
-      {
-        compare display?, 0/false
-        break-if-=
-        y <- render-trace-line screen, curr, xmin, y, xmax, ymax, 9/fg=blue, bg
-        copy-to already-hiding-lines?, 0/false
-        break $render-trace:iter
-      }
-      # ignore the rest
-      compare already-hiding-lines?, 0/false
-      {
-        break-if-!=
-        var x/eax: int <- copy xmin
-        x, y <- draw-text-wrapping-right-then-down screen, "...", xmin, ymin, xmax, ymax, x, y, 9/fg=trace, bg
-        y <- increment
-        copy-to already-hiding-lines?, 1/true
-      }
-    }
-    i <- increment
-    loop
-  }
-  # prevent cursor from going too far down
-  clamp-cursor-to-bottom self, y, screen, xmin, ymin, xmax, ymax
-  mark-lines-clean self
-  return y
-}
-
-fn render-trace-line screen: (addr screen), _self: (addr trace-line), xmin: int, ymin: int, xmax: int, ymax: int, fg: int, bg: int -> _/ecx: int {
-  var self/esi: (addr trace-line) <- copy _self
-  var xsave/edx: int <- copy xmin
-  var y/ecx: int <- copy ymin
-  var label-ah/eax: (addr handle array byte) <- get self, label
-  var _label/eax: (addr array byte) <- lookup *label-ah
-  var label/ebx: (addr array byte) <- copy _label
-  var is-error?/eax: boolean <- string-equal? label, "error"
-  compare is-error?, 0/false
-  {
-    break-if-!=
-    var x/eax: int <- copy xsave
-    {
-      var depth/edx: (addr int) <- get self, depth
-      x, y <- draw-int32-decimal-wrapping-right-then-down screen, *depth, xmin, ymin, xmax, ymax, x, y, fg, bg
-      x, y <- draw-text-wrapping-right-then-down screen, " ", xmin, ymin, xmax, ymax, x, y, fg, bg
-      # don't show label in UI; it's just for tests
-    }
-    xsave <- copy x
-  }
-  var data-ah/eax: (addr handle array byte) <- get self, data
-  var _data/eax: (addr array byte) <- lookup *data-ah
-  var data/ebx: (addr array byte) <- copy _data
-  var x/eax: int <- copy xsave
-  x, y <- draw-text-wrapping-right-then-down screen, data, xmin, ymin, xmax, ymax, x, y, fg, bg
-  y <- increment
-  return y
-}
-
-fn should-render? _self: (addr trace), _line: (addr trace-line) -> _/eax: boolean {
-  var self/esi: (addr trace) <- copy _self
-  # if visible? is already cached, just return it
-  var dest/edx: (addr boolean) <- get self, recompute-visible?
-  compare *dest, 0/false
-  {
-    break-if-!=
-    var line/eax: (addr trace-line) <- copy _line
-    var result/eax: (addr boolean) <- get line, visible?
-    return *result
-  }
-  # recompute
-  var candidates-ah/eax: (addr handle array trace-line) <- get self, visible
-  var candidates/eax: (addr array trace-line) <- lookup *candidates-ah
-  var i/ecx: int <- copy 0
-  var len/edx: int <- length candidates
-  {
-    compare i, len
-    break-if->=
-    {
-      var curr-offset/ecx: (offset trace-line) <- compute-offset candidates, i
-      var curr/ecx: (addr trace-line) <- index candidates, curr-offset
-      var match?/eax: boolean <- trace-lines-equal? curr, _line
-      compare match?, 0/false
-      break-if-=
-      var line/eax: (addr trace-line) <- copy _line
-      var dest/eax: (addr boolean) <- get line, visible?
-      copy-to *dest, 1/true
-      return 1/true
-    }
-    i <- increment
-    loop
-  }
-  var line/eax: (addr trace-line) <- copy _line
-  var dest/eax: (addr boolean) <- get line, visible?
-  copy-to *dest, 0/false
-  return 0/false
-}
-
-# this is probably super-inefficient, string comparing every trace line
-# against every visible line on every render
-fn trace-lines-equal? _a: (addr trace-line), _b: (addr trace-line) -> _/eax: boolean {
-  var a/esi: (addr trace-line) <- copy _a
-  var b/edi: (addr trace-line) <- copy _b
-  var a-depth/ecx: (addr int) <- get a, depth
-  var b-depth/edx: (addr int) <- get b, depth
-  var benchmark/eax: int <- copy *b-depth
-  compare *a-depth, benchmark
-  {
-    break-if-=
-    return 0/false
-  }
-  var a-label-ah/eax: (addr handle array byte) <- get a, label
-  var _a-label/eax: (addr array byte) <- lookup *a-label-ah
-  var a-label/ecx: (addr array byte) <- copy _a-label
-  var b-label-ah/ebx: (addr handle array byte) <- get b, label
-  var b-label/eax: (addr array byte) <- lookup *b-label-ah
-  var label-match?/eax: boolean <- string-equal? a-label, b-label
-  {
-    compare label-match?, 0/false
-    break-if-!=
-    return 0/false
-  }
-  var a-data-ah/eax: (addr handle array byte) <- get a, data
-  var _a-data/eax: (addr array byte) <- lookup *a-data-ah
-  var a-data/ecx: (addr array byte) <- copy _a-data
-  var b-data-ah/ebx: (addr handle array byte) <- get b, data
-  var b-data/eax: (addr array byte) <- lookup *b-data-ah
-  var data-match?/eax: boolean <- string-equal? a-data, b-data
-  return data-match?
-}
-
-fn clamp-cursor-to-top _self: (addr trace), _y: int {
-  var y/ecx: int <- copy _y
-  var self/esi: (addr trace) <- copy _self
-  var cursor-y/eax: (addr int) <- get self, cursor-y
-  compare *cursor-y, y
-  break-if->=
-  copy-to *cursor-y, y
-}
-
-# extremely hacky; consider deleting test-render-trace-empty-3 when you clean this up
-fn clamp-cursor-to-bottom _self: (addr trace), _y: int, screen: (addr screen), xmin: int, ymin: int, xmax: int, ymax: int {
-  var y/ebx: int <- copy _y
-  compare y, ymin
-  {
-    break-if->
-    return
-  }
-  y <- decrement
-  var self/esi: (addr trace) <- copy _self
-  var cursor-y/eax: (addr int) <- get self, cursor-y
-  compare *cursor-y, y
-  break-if-<=
-  copy-to *cursor-y, y
-  # redraw cursor-line
-  # TODO: ugly duplication
-  var trace-ah/eax: (addr handle array trace-line) <- get self, data
-  var trace/eax: (addr array trace-line) <- lookup *trace-ah
-  var cursor-line-index-addr/ecx: (addr int) <- get self, cursor-line-index
-  var cursor-line-index/ecx: int <- copy *cursor-line-index-addr
-  var first-free/edx: (addr int) <- get self, first-free
-  compare cursor-line-index, *first-free
-  {
-    break-if-<
-    return
-  }
-  var cursor-offset/ecx: (offset trace-line) <- compute-offset trace, cursor-line-index
-  var cursor-line/ecx: (addr trace-line) <- index trace, cursor-offset
-  var display?/eax: boolean <- should-render? self, cursor-line
-  {
-    compare display?, 0/false
-    break-if-=
-    var dummy/ecx: int <- render-trace-line screen, cursor-line, xmin, y, xmax, ymax, 9/fg=blue, 7/cursor-line-bg
-    return
-  }
-  var dummy1/eax: int <- copy 0
-  var dummy2/ecx: int <- copy 0
-  dummy1, dummy2 <- draw-text-wrapping-right-then-down screen, "...", xmin, ymin, xmax, ymax, xmin, y, 9/fg=trace, 7/cursor-line-bg
-}
-
-fn test-render-trace-empty {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 5/xmax, 4/ymax, 0/no-cursor
-  #
-  check-ints-equal y, 0, "F - test-render-trace-empty/cursor"
-  check-screen-row screen,                                  0/y, "    ", "F - test-render-trace-empty"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "    ", "F - test-render-trace-empty/bg"
-}
-
-fn test-render-trace-empty-2 {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 2/ymin, 5/xmax, 4/ymax, 0/no-cursor  # cursor below top row
-  #
-  check-ints-equal y, 2, "F - test-render-trace-empty-2/cursor"
-  check-screen-row screen,                                  2/y, "    ", "F - test-render-trace-empty-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "    ", "F - test-render-trace-empty-2/bg"
-}
-
-fn test-render-trace-empty-3 {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 2/ymin, 5/xmax, 4/ymax, 1/show-cursor  # try show cursor
-  # still no cursor to show
-  check-ints-equal y, 2, "F - test-render-trace-empty-3/cursor"
-  check-screen-row screen,                                  1/y, "    ", "F - test-render-trace-empty-3/line-above-cursor"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "    ", "F - test-render-trace-empty-3/bg-for-line-above-cursor"
-  check-screen-row screen,                                  2/y, "    ", "F - test-render-trace-empty-3"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "    ", "F - test-render-trace-empty-3/bg"
-}
-
-fn test-render-trace-collapsed-by-default {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  trace-text t, "l", "data"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 5/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 5/xmax, 4/ymax, 0/no-cursor
-  #
-  check-ints-equal y, 1, "F - test-render-trace-collapsed-by-default/cursor"
-  check-screen-row screen, 0/y, "... ", "F - test-render-trace-collapsed-by-default"
-}
-
-fn test-render-trace-error {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  error t, "error"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0xa/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 0/no-cursor
-  #
-  check-ints-equal y, 1, "F - test-render-trace-error/cursor"
-  check-screen-row screen, 0/y, "error", "F - test-render-trace-error"
-}
-
-fn test-render-trace-error-at-start {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  error t, "error"
-  trace-text t, "l", "data"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0xa/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 0/no-cursor
-  #
-  check-ints-equal y, 2, "F - test-render-trace-error-at-start/cursor"
-  check-screen-row screen, 0/y, "error", "F - test-render-trace-error-at-start/0"
-  check-screen-row screen, 1/y, "...  ", "F - test-render-trace-error-at-start/1"
-}
-
-fn test-render-trace-error-at-end {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "data"
-  error t, "error"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0xa/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 0/no-cursor
-  #
-  check-ints-equal y, 2, "F - test-render-trace-error-at-end/cursor"
-  check-screen-row screen, 0/y, "...  ", "F - test-render-trace-error-at-end/0"
-  check-screen-row screen, 1/y, "error", "F - test-render-trace-error-at-end/1"
-}
-
-fn test-render-trace-error-in-the-middle {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  error t, "error"
-  trace-text t, "l", "line 3"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0xa/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 0/no-cursor
-  #
-  check-ints-equal y, 3, "F - test-render-trace-error-in-the-middle/cursor"
-  check-screen-row screen, 0/y, "...  ", "F - test-render-trace-error-in-the-middle/0"
-  check-screen-row screen, 1/y, "error", "F - test-render-trace-error-in-the-middle/1"
-  check-screen-row screen, 2/y, "...  ", "F - test-render-trace-error-in-the-middle/2"
-}
-
-fn test-render-trace-cursor-in-single-line {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  error t, "error"
-  trace-text t, "l", "line 3"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0xa/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...   ", "F - test-render-trace-cursor-in-single-line/0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||   ", "F - test-render-trace-cursor-in-single-line/0/cursor"
-  check-screen-row screen,                                  1/y, "error ", "F - test-render-trace-cursor-in-single-line/1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-render-trace-cursor-in-single-line/1/cursor"
-  check-screen-row screen,                                  2/y, "...   ", "F - test-render-trace-cursor-in-single-line/2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-render-trace-cursor-in-single-line/2/cursor"
-}
-
-fn render-trace-menu screen: (addr screen) {
-  var width/eax: int <- copy 0
-  var height/ecx: int <- copy 0
-  width, height <- screen-size screen
-  var y/ecx: int <- copy height
-  y <- decrement
-  set-cursor-position screen, 0/x, y
-  draw-text-rightward-from-cursor screen, " ctrl-s ", width, 0/fg, 7/bg=grey
-  draw-text-rightward-from-cursor screen, " run sandbox  ", width, 7/fg, 0/bg
-  draw-text-rightward-from-cursor screen, " ctrl-d ", width, 0/fg, 7/bg=grey
-  draw-text-rightward-from-cursor screen, " cursor down  ", width, 7/fg, 0/bg
-  draw-text-rightward-from-cursor screen, " ctrl-u ", width, 0/fg, 7/bg=grey
-  draw-text-rightward-from-cursor screen, " cursor up  ", width, 7/fg, 0/bg
-  draw-text-rightward-from-cursor screen, " tab ", width, 0/fg, 3/bg=cyan
-  draw-text-rightward-from-cursor screen, " move to sandbox  ", width, 7/fg, 0/bg
-  draw-text-rightward-from-cursor screen, " enter ", width, 0/fg, 7/bg=grey
-  draw-text-rightward-from-cursor screen, " expand  ", width, 7/fg, 0/bg
-  draw-text-rightward-from-cursor screen, " backspace ", width, 0/fg, 7/bg=grey
-  draw-text-rightward-from-cursor screen, " collapse  ", width, 7/fg, 0/bg
-}
-
-fn edit-trace _self: (addr trace), key: grapheme {
-  var self/esi: (addr trace) <- copy _self
-  # cursor down
-  {
-    compare key, 4/ctrl-d
-    break-if-!=
-    var cursor-y/eax: (addr int) <- get self, cursor-y
-    increment *cursor-y
-    return
-  }
-  # cursor up
-  {
-    compare key, 0x15/ctrl-u
-    break-if-!=
-    var cursor-y/eax: (addr int) <- get self, cursor-y
-    decrement *cursor-y
-    return
-  }
-  # enter = expand
-  {
-    compare key, 0xa/newline
-    break-if-!=
-    expand self
-    return
-  }
-  # backspace = collapse
-  {
-    compare key, 8/backspace
-    break-if-!=
-    collapse self
-    return
-  }
-}
-
-fn expand _self: (addr trace) {
-  var self/esi: (addr trace) <- copy _self
-  var trace-ah/eax: (addr handle array trace-line) <- get self, data
-  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
-  var trace/edi: (addr array trace-line) <- copy _trace
-  var cursor-line-index-addr/ecx: (addr int) <- get self, cursor-line-index
-  var cursor-line-index/ecx: int <- copy *cursor-line-index-addr
-  var cursor-line-offset/eax: (offset trace-line) <- compute-offset trace, cursor-line-index
-  var cursor-line/edx: (addr trace-line) <- index trace, cursor-line-offset
-  var cursor-line-visible?/eax: (addr boolean) <- get cursor-line, visible?
-  var cursor-line-depth/ebx: (addr int) <- get cursor-line, depth
-  var target-depth/ebx: int <- copy *cursor-line-depth
-  # if cursor-line is already visible, increment target-depth
-  compare *cursor-line-visible?, 0/false
-  {
-    break-if-=
-    target-depth <- increment
-  }
-  # reveal the run of lines starting at cursor-line-index with depth target-depth
-  var i/ecx: int <- copy cursor-line-index
-  var max/edx: (addr int) <- get self, first-free
-  {
-    compare i, *max
-    break-if->=
-    var curr-line-offset/eax: (offset trace-line) <- compute-offset trace, i
-    var curr-line/edx: (addr trace-line) <- index trace, curr-line-offset
-    var curr-line-depth/eax: (addr int) <- get curr-line, depth
-    compare *curr-line-depth, target-depth
-    break-if-<
-    {
-      break-if-!=
-      var curr-line-visible?/eax: (addr boolean) <- get curr-line, visible?
-      copy-to *curr-line-visible?, 1/true
-      reveal-trace-line self, curr-line
-    }
-    i <- increment
-    loop
-  }
-}
-
-fn collapse _self: (addr trace) {
-  var self/esi: (addr trace) <- copy _self
-  var trace-ah/eax: (addr handle array trace-line) <- get self, data
-  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
-  var trace/edi: (addr array trace-line) <- copy _trace
-  var cursor-line-index-addr/ecx: (addr int) <- get self, cursor-line-index
-  var cursor-line-index/ecx: int <- copy *cursor-line-index-addr
-  var cursor-line-offset/eax: (offset trace-line) <- compute-offset trace, cursor-line-index
-  var cursor-line/edx: (addr trace-line) <- index trace, cursor-line-offset
-  var cursor-line-visible?/eax: (addr boolean) <- get cursor-line, visible?
-  # if cursor-line is not visible, do nothing
-  compare *cursor-line-visible?, 0/false
-  {
-    break-if-!=
-    return
-  }
-  # hide all lines between previous and next line with a lower depth
-  var cursor-line-depth/ebx: (addr int) <- get cursor-line, depth
-  var cursor-y/edx: (addr int) <- get self, cursor-y
-  var target-depth/ebx: int <- copy *cursor-line-depth
-  var i/ecx: int <- copy cursor-line-index
-  $collapse:loop1: {
-    compare i, 0
-    break-if-<
-    var curr-line-offset/eax: (offset trace-line) <- compute-offset trace, i
-    var curr-line/eax: (addr trace-line) <- index trace, curr-line-offset
-    {
-      var curr-line-depth/eax: (addr int) <- get curr-line, depth
-      compare *curr-line-depth, target-depth
-      break-if-< $collapse:loop1
-    }
-    # if cursor-line is visible, decrement cursor-y
-    {
-      var curr-line-visible?/eax: (addr boolean) <- get curr-line, visible?
-      compare *curr-line-visible?, 0/false
-      break-if-=
-      decrement *cursor-y
-    }
-    i <- decrement
-    loop
-  }
-  i <- increment
-  var max/edx: (addr int) <- get self, first-free
-  $collapse:loop2: {
-    compare i, *max
-    break-if->=
-    var curr-line-offset/eax: (offset trace-line) <- compute-offset trace, i
-    var curr-line/edx: (addr trace-line) <- index trace, curr-line-offset
-    var curr-line-depth/eax: (addr int) <- get curr-line, depth
-    compare *curr-line-depth, target-depth
-    break-if-<
-    {
-      hide-trace-line self, curr-line
-      var curr-line-visible?/eax: (addr boolean) <- get curr-line, visible?
-      copy-to *curr-line-visible?, 0/false
-    }
-    i <- increment
-    loop
-  }
-}
-
-# the 'visible' array is not required to be in order
-# elements can also be deleted out of order
-# so it can have holes
-# however, lines in it always have visible? set
-# we'll use visible? being unset as a sign of emptiness
-fn reveal-trace-line _self: (addr trace), line: (addr trace-line) {
-  var self/esi: (addr trace) <- copy _self
-  var visible-ah/eax: (addr handle array trace-line) <- get self, visible
-  var visible/eax: (addr array trace-line) <- lookup *visible-ah
-  var i/ecx: int <- copy 0
-  var len/edx: int <- length visible
-  {
-    compare i, len
-    break-if->=
-    var curr-offset/edx: (offset trace-line) <- compute-offset visible, i
-    var curr/edx: (addr trace-line) <- index visible, curr-offset
-    var curr-visible?/eax: (addr boolean) <- get curr, visible?
-    compare *curr-visible?, 0/false
-    {
-      break-if-!=
-      # empty slot found
-      copy-object line, curr
-      return
-    }
-    i <- increment
-    loop
-  }
-  abort "too many visible lines; increase size of array trace.visible"
-}
-
-fn hide-trace-line _self: (addr trace), line: (addr trace-line) {
-  var self/esi: (addr trace) <- copy _self
-  var visible-ah/eax: (addr handle array trace-line) <- get self, visible
-  var visible/eax: (addr array trace-line) <- lookup *visible-ah
-  var i/ecx: int <- copy 0
-  var len/edx: int <- length visible
-  {
-    compare i, len
-    break-if->=
-    var curr-offset/edx: (offset trace-line) <- compute-offset visible, i
-    var curr/edx: (addr trace-line) <- index visible, curr-offset
-    var found?/eax: boolean <- trace-lines-equal? curr, line
-    compare found?, 0/false
-    {
-      break-if-=
-      clear-object curr
-    }
-    i <- increment
-    loop
-  }
-}
-
-fn test-cursor-down-and-up-within-trace {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  error t, "error"
-  trace-text t, "l", "line 3"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0xa/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-and-up-within-trace/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||   ", "F - test-cursor-down-and-up-within-trace/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-and-up-within-trace/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-cursor-down-and-up-within-trace/pre-1/cursor"
-  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-and-up-within-trace/pre-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-cursor-down-and-up-within-trace/pre-2/cursor"
-  # cursor down
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-and-up-within-trace/down-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "      ", "F - test-cursor-down-and-up-within-trace/down-0/cursor"
-  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-and-up-within-trace/down-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "||||| ", "F - test-cursor-down-and-up-within-trace/down-1/cursor"
-  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-and-up-within-trace/down-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-cursor-down-and-up-within-trace/down-2/cursor"
-  # cursor up
-  edit-trace t, 0x15/ctrl-u
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-and-up-within-trace/up-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||   ", "F - test-cursor-down-and-up-within-trace/up-0/cursor"
-  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-and-up-within-trace/up-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-cursor-down-and-up-within-trace/up-1/cursor"
-  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-and-up-within-trace/up-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-cursor-down-and-up-within-trace/up-2/cursor"
-}
-
-fn test-cursor-down-past-bottom-of-trace {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  error t, "error"
-  trace-text t, "l", "line 3"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0xa/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-past-bottom-of-trace/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||   ", "F - test-cursor-down-past-bottom-of-trace/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-past-bottom-of-trace/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-cursor-down-past-bottom-of-trace/pre-1/cursor"
-  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-past-bottom-of-trace/pre-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-cursor-down-past-bottom-of-trace/pre-2/cursor"
-  # cursor down several times
-  edit-trace t, 4/ctrl-d
-  edit-trace t, 4/ctrl-d
-  edit-trace t, 4/ctrl-d
-  edit-trace t, 4/ctrl-d
-  edit-trace t, 4/ctrl-d
-  # hack: we do need to render to make this test pass; we're mixing state management with rendering
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
-  # cursor clamps at bottom
-  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-past-bottom-of-trace/down-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "      ", "F - test-cursor-down-past-bottom-of-trace/down-0/cursor"
-  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-past-bottom-of-trace/down-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-cursor-down-past-bottom-of-trace/down-1/cursor"
-  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-past-bottom-of-trace/down-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "|||   ", "F - test-cursor-down-past-bottom-of-trace/down-2/cursor"
-}
-
-fn test-expand-within-trace {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-text t, "l", "line 2"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...      ", "F - test-expand-within-trace/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||      ", "F - test-expand-within-trace/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "         ", "F - test-expand-within-trace/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-expand-within-trace/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1 ", "F - test-expand-within-trace/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||||||| ", "F - test-expand-within-trace/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 2 ", "F - test-expand-within-trace/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-expand-within-trace/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "         ", "F - test-expand-within-trace/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "         ", "F - test-expand-within-trace/expand-2/cursor"
-}
-
-fn test-trace-expand-skips-lower-depth {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-lower t
-  trace-text t, "l", "line 2"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...      ", "F - test-trace-expand-skips-lower-depth/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||      ", "F - test-trace-expand-skips-lower-depth/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "         ", "F - test-trace-expand-skips-lower-depth/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-trace-expand-skips-lower-depth/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1 ", "F - test-trace-expand-skips-lower-depth/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||||||| ", "F - test-trace-expand-skips-lower-depth/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "...      ", "F - test-trace-expand-skips-lower-depth/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-trace-expand-skips-lower-depth/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "         ", "F - test-trace-expand-skips-lower-depth/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "         ", "F - test-trace-expand-skips-lower-depth/expand-2/cursor"
-}
-
-fn test-trace-expand-continues-past-lower-depth {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-lower t
-  trace-text t, "l", "line 1.1"
-  trace-higher t
-  trace-text t, "l", "line 2"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...      ", "F - test-trace-expand-continues-past-lower-depth/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||      ", "F - test-trace-expand-continues-past-lower-depth/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "         ", "F - test-trace-expand-continues-past-lower-depth/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-trace-expand-continues-past-lower-depth/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1 ", "F - test-trace-expand-continues-past-lower-depth/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||||||| ", "F - test-trace-expand-continues-past-lower-depth/expand-0/cursor"
-  # TODO: might be too wasteful to show every place where lines are hidden
-  check-screen-row screen,                                  1/y, "...      ", "F - test-trace-expand-continues-past-lower-depth/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-trace-expand-continues-past-lower-depth/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2 ", "F - test-trace-expand-continues-past-lower-depth/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "         ", "F - test-trace-expand-continues-past-lower-depth/expand-2/cursor"
-}
-
-fn test-trace-expand-stops-at-higher-depth {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1.1"
-  trace-lower t
-  trace-text t, "l", "line 1.1.1"
-  trace-higher t
-  trace-text t, "l", "line 1.2"
-  trace-higher t
-  trace-text t, "l", "line 2"
-  trace-lower t
-  trace-text t, "l", "line 2.1"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 8/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-expand-stops-at-higher-depth/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-expand-stops-at-higher-depth/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-expand-stops-at-higher-depth/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-expand-stops-at-higher-depth/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1.1 ", "F - test-trace-expand-stops-at-higher-depth/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||||||||| ", "F - test-trace-expand-stops-at-higher-depth/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-expand-stops-at-higher-depth/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 1.2 ", "F - test-trace-expand-stops-at-higher-depth/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-2/cursor"
-  check-screen-row screen,                                  3/y, "...        ", "F - test-trace-expand-stops-at-higher-depth/expand-3"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-3/cursor"
-  check-screen-row screen,                                  4/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-4"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 4/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-4/cursor"
-}
-
-fn test-trace-expand-twice {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-lower t
-  trace-text t, "l", "line 1.1"
-  trace-higher t
-  trace-text t, "l", "line 2"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-expand-twice/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-expand-twice/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-expand-twice/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-expand-twice/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-expand-twice/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-expand-twice/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-expand-twice/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-expand-twice/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-expand-twice/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-expand-twice/expand-2/cursor"
-  # cursor down
-  edit-trace t, 4/ctrl-d
-  # hack: we need to render here to make this test pass; we're mixing state management with rendering
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-expand-twice/down-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-expand-twice/down-0/cursor"
-  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-expand-twice/down-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "|||        ", "F - test-trace-expand-twice/down-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-expand-twice/down-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-expand-twice/down-2/cursor"
-  # expand again
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-expand-twice/expand2-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-expand-twice/expand2-0/cursor"
-  check-screen-row screen,                                  1/y, "1 line 1.1 ", "F - test-trace-expand-twice/expand2-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "|||||||||| ", "F - test-trace-expand-twice/expand2-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-expand-twice/expand2-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-expand-twice/expand2-2/cursor"
-}
-
-fn test-trace-refresh-cursor {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-text t, "l", "line 2"
-  trace-text t, "l", "line 3"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-refresh-cursor/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-refresh-cursor/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-refresh-cursor/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-refresh-cursor/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-refresh-cursor/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-refresh-cursor/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 2   ", "F - test-trace-refresh-cursor/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-refresh-cursor/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 3   ", "F - test-trace-refresh-cursor/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-refresh-cursor/expand-2/cursor"
-  # cursor down
-  edit-trace t, 4/ctrl-d
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-refresh-cursor/down-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-refresh-cursor/down-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 2   ", "F - test-trace-refresh-cursor/down-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-refresh-cursor/down-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 3   ", "F - test-trace-refresh-cursor/down-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-refresh-cursor/down-2/cursor"
-  # recreate trace
-  clear-trace t
-  trace-text t, "l", "line 1"
-  trace-text t, "l", "line 2"
-  trace-text t, "l", "line 3"
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # cursor remains unchanged
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-refresh-cursor/refresh-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-refresh-cursor/refresh-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 2   ", "F - test-trace-refresh-cursor/refresh-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-refresh-cursor/refresh-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 3   ", "F - test-trace-refresh-cursor/refresh-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-refresh-cursor/refresh-2/cursor"
-}
-
-fn test-trace-preserve-cursor-on-refresh {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-text t, "l", "line 2"
-  trace-text t, "l", "line 3"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-preserve-cursor-on-refresh/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-preserve-cursor-on-refresh/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-preserve-cursor-on-refresh/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-preserve-cursor-on-refresh/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 2   ", "F - test-trace-preserve-cursor-on-refresh/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 3   ", "F - test-trace-preserve-cursor-on-refresh/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "              ", "F - test-trace-preserve-cursor-on-refresh/expand-2/cursor"
-  # cursor down
-  edit-trace t, 4/ctrl-d
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-preserve-cursor-on-refresh/down-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-preserve-cursor-on-refresh/down-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 2   ", "F - test-trace-preserve-cursor-on-refresh/down-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/down-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 3   ", "F - test-trace-preserve-cursor-on-refresh/down-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-preserve-cursor-on-refresh/down-2/cursor"
-  # recreate trace with slightly different lines
-  clear-trace t
-  trace-text t, "l", "line 4"
-  trace-text t, "l", "line 5"
-  trace-text t, "l", "line 3"  # cursor line is unchanged
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # cursor remains unchanged
-  check-screen-row screen,                                  0/y, "0 line 4   ", "F - test-trace-preserve-cursor-on-refresh/refresh-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-preserve-cursor-on-refresh/refresh-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 5   ", "F - test-trace-preserve-cursor-on-refresh/refresh-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/refresh-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 3   ", "F - test-trace-preserve-cursor-on-refresh/refresh-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-preserve-cursor-on-refresh/refresh-2/cursor"
-}
-
-fn test-trace-keep-cursor-visible-on-refresh {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-text t, "l", "line 2"
-  trace-text t, "l", "line 3"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-keep-cursor-visible-on-refresh/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-keep-cursor-visible-on-refresh/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-keep-cursor-visible-on-refresh/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-keep-cursor-visible-on-refresh/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 2   ", "F - test-trace-keep-cursor-visible-on-refresh/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 3   ", "F - test-trace-keep-cursor-visible-on-refresh/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "              ", "F - test-trace-keep-cursor-visible-on-refresh/expand-2/cursor"
-  # cursor down
-  edit-trace t, 4/ctrl-d
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-keep-cursor-visible-on-refresh/down-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/down-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 2   ", "F - test-trace-keep-cursor-visible-on-refresh/down-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/down-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 3   ", "F - test-trace-keep-cursor-visible-on-refresh/down-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-keep-cursor-visible-on-refresh/down-2/cursor"
-  # recreate trace with entirely different lines
-  clear-trace t
-  trace-text t, "l", "line 4"
-  trace-text t, "l", "line 5"
-  trace-text t, "l", "line 6"
-  mark-lines-dirty t
-  clear-screen screen
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # trace collapses, and cursor bumps up
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-1/cursor"
-  check-screen-row screen,                                  2/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-2/cursor"
-}
-
-fn test-trace-collapse-at-top {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-lower t
-  trace-text t, "l", "line 1.1"
-  trace-higher t
-  trace-text t, "l", "line 2"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-at-top/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-at-top/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-at-top/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-at-top/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-collapse-at-top/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse-at-top/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-at-top/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-at-top/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-collapse-at-top/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-at-top/expand-2/cursor"
-  # collapse
-  edit-trace t, 8/backspace
-  # hack: we need to render here to make this test pass; we're mixing state management with rendering
-  clear-screen screen
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-ints-equal y, 1, "F - test-trace-collapse-at-top/post-0/y"
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-at-top/post-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-at-top/post-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-at-top/post-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-at-top/post-1/cursor"
-}
-
-fn test-trace-collapse {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-text t, "l", "line 2"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-collapse/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "0 line 2   ", "F - test-trace-collapse/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse/expand-1/cursor"
-  # cursor down
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # collapse
-  edit-trace t, 8/backspace
-  clear-screen screen
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-ints-equal y, 1, "F - test-trace-collapse/post-0/y"
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse/post-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse/post-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse/post-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse/post-1/cursor"
-}
-
-fn test-trace-collapse-skips-invisible-lines {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-lower t
-  trace-text t, "l", "line 1.1"
-  trace-higher t
-  trace-text t, "l", "line 2"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-skips-invisible-lines/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-skips-invisible-lines/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # two visible lines with an invisible line in between
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-collapse-skips-invisible-lines/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse-skips-invisible-lines/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-skips-invisible-lines/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-collapse-skips-invisible-lines/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-skips-invisible-lines/expand-2/cursor"
-  # cursor down to second visible line
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # collapse
-  edit-trace t, 8/backspace
-  clear-screen screen
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-ints-equal y, 1, "F - test-trace-collapse-skips-invisible-lines/post-0/y"
-  var cursor-y/eax: (addr int) <- get t, cursor-y
-  check-ints-equal *cursor-y, 0, "F - test-trace-collapse-skips-invisible-lines/post-0/cursor-y"
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-skips-invisible-lines/post-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-skips-invisible-lines/post-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/post-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/post-1/cursor"
-}
-
-fn test-trace-collapse-two-levels {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-lower t
-  trace-text t, "l", "line 1.1"
-  trace-higher t
-  trace-text t, "l", "line 2"
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 4/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-two-levels/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-two-levels/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-two-levels/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-two-levels/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # two visible lines with an invisible line in between
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-collapse-two-levels/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse-two-levels/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-two-levels/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-two-levels/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-collapse-two-levels/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-two-levels/expand-2/cursor"
-  # cursor down to ellipses
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # two visible lines with an invisible line in between
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-collapse-two-levels/expand2-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-collapse-two-levels/expand2-0/cursor"
-  check-screen-row screen,                                  1/y, "1 line 1.1 ", "F - test-trace-collapse-two-levels/expand2-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "|||||||||| ", "F - test-trace-collapse-two-levels/expand2-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-collapse-two-levels/expand2-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-two-levels/expand2-2/cursor"
-  # cursor down to second visible line
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  # collapse
-  edit-trace t, 8/backspace
-  clear-screen screen
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
-  #
-  check-ints-equal y, 1, "F - test-trace-collapse-two-levels/post-0/y"
-  var cursor-y/eax: (addr int) <- get t, cursor-y
-  check-ints-equal *cursor-y, 0, "F - test-trace-collapse-two-levels/post-0/cursor-y"
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-two-levels/post-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-two-levels/post-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-two-levels/post-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-two-levels/post-1/cursor"
-}
-
-fn test-trace-collapse-nested-level {
-  var t-storage: trace
-  var t/esi: (addr trace) <- address t-storage
-  initialize-trace t, 0x10, 0x10
-  #
-  trace-text t, "l", "line 1"
-  trace-lower t
-  trace-text t, "l", "line 1.1"
-  trace-higher t
-  trace-text t, "l", "line 2"
-  trace-lower t
-  trace-text t, "l", "line 2.1"
-  trace-text t, "l", "line 2.2"
-  trace-higher t
-  # setup: screen
-  var screen-on-stack: screen
-  var screen/edi: (addr screen) <- address screen-on-stack
-  initialize-screen screen, 0x10/width, 8/height
-  #
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
-  #
-  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-nested-level/pre-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-nested-level/pre-0/cursor"
-  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-nested-level/pre-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-nested-level/pre-1/cursor"
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
-  # two visible lines with an invisible line in between
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-collapse-nested-level/expand-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse-nested-level/expand-0/cursor"
-  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-nested-level/expand-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-nested-level/expand-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-collapse-nested-level/expand-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-nested-level/expand-2/cursor"
-  check-screen-row screen,                                  3/y, "...        ", "F - test-trace-collapse-nested-level/expand-3"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-collapse-nested-level/expand-3/cursor"
-  # cursor down to bottom
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
-  edit-trace t, 4/ctrl-d
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
-  # expand
-  edit-trace t, 0xa/enter
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
-  # two visible lines with an invisible line in between
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-collapse-nested-level/expand2-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-collapse-nested-level/expand2-0/cursor"
-  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-nested-level/expand2-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-nested-level/expand2-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-collapse-nested-level/expand2-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-nested-level/expand2-2/cursor"
-  check-screen-row screen,                                  3/y, "1 line 2.1 ", "F - test-trace-collapse-nested-level/expand2-3"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "|||||||||| ", "F - test-trace-collapse-nested-level/expand2-3/cursor"
-  check-screen-row screen,                                  4/y, "1 line 2.2 ", "F - test-trace-collapse-nested-level/expand2-4"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 4/y, "           ", "F - test-trace-collapse-nested-level/expand2-4/cursor"
-  # collapse
-  edit-trace t, 8/backspace
-  clear-screen screen
-  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
-  #
-  check-ints-equal y, 4, "F - test-trace-collapse-nested-level/post-0/y"
-  var cursor-y/eax: (addr int) <- get t, cursor-y
-  check-ints-equal *cursor-y, 2, "F - test-trace-collapse-nested-level/post-0/cursor-y"
-  check-screen-row screen,                                  0/y, "0 line 1   ", "F - test-trace-collapse-nested-level/post-0"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-collapse-nested-level/post-0/cursor"
-  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-nested-level/post-1"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-nested-level/post-1/cursor"
-  check-screen-row screen,                                  2/y, "0 line 2   ", "F - test-trace-collapse-nested-level/post-2"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-collapse-nested-level/post-2/cursor"
-  check-screen-row screen,                                  3/y, "...        ", "F - test-trace-collapse-nested-level/post-3"
-  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-collapse-nested-level/post-3/cursor"
-}
diff --git a/baremetal/shell/vimrc.vim b/baremetal/shell/vimrc.vim
deleted file mode 100644
index 348fe364..00000000
--- a/baremetal/shell/vimrc.vim
+++ /dev/null
@@ -1,2 +0,0 @@
-" when opening files in this directory, load vimrc from cwd (top-level)
-source vimrc.vim
diff --git a/baremetal/vga_palette b/baremetal/vga_palette
deleted file mode 100644
index 4c04d989..00000000
--- a/baremetal/vga_palette
+++ /dev/null
@@ -1,283 +0,0 @@
-# Default VGA 256-color palette
-
-# First 16 colors
-  0:  0  0  0                                                                                                                                                                                                   
-  1:  0  0 42                                                                                                                                   ..........................................                      
-  2:  0 42  0                                                                  ..........................................                                                                                       
-  3:  0 42 42                                                                  ..........................................                       ..........................................                      
-  4: 42  0  0 ..........................................                                                                                                                                                        
-  5: 42  0 42 ..........................................                                                                                        ..........................................                      
-  6: 42 21  0 ..........................................                       .....................                                                                                                            
-  7: 42 42 42 ..........................................                       ..........................................                       ..........................................                      
-  8: 21 21 21 .....................                                            .....................                                            .....................                                           
-  9: 21 21 63 .....................                                            .....................                                            ............................................................... 
- 10: 21 63 21 .....................                                            ...............................................................  .....................                                           
- 11: 21 63 63 .....................                                            ...............................................................  ............................................................... 
- 12: 63 21 21 ...............................................................  .....................                                            .....................                                           
- 13: 63 21 63 ...............................................................  .....................                                            ............................................................... 
- 14: 63 63 21 ...............................................................  ...............................................................  .....................                                           
- 15: 63 63 63 ...............................................................  ...............................................................  ............................................................... 
-
-# Greyscale
- 16:  0  0  0                                                                                                                                                                                                   
- 17:  5  5  5 .....                                                            .....                                                            .....                                                           
- 18:  8  8  8 ........                                                         ........                                                         ........                                                        
- 19: 11 11 11 ...........                                                      ...........                                                      ...........                                                     
- 20: 14 14 14 ..............                                                   ..............                                                   ..............                                                  
- 21: 17 17 17 .................                                                .................                                                .................                                               
- 22: 20 20 20 ....................                                             ....................                                             ....................                                            
- 23: 24 24 24 ........................                                         ........................                                         ........................                                        
- 24: 28 28 28 ............................                                     ............................                                     ............................                                    
- 25: 32 32 32 ................................                                 ................................                                 ................................                                
- 26: 36 36 36 ....................................                             ....................................                             ....................................                            
- 27: 40 40 40 ........................................                         ........................................                         ........................................                        
- 28: 45 45 45 .............................................                    .............................................                    .............................................                   
- 29: 50 50 50 ..................................................               ..................................................               ..................................................              
- 30: 56 56 56 ........................................................         ........................................................         ........................................................        
- 31: 63 63 63 ...............................................................  ...............................................................  ............................................................... 
-
-# Cycle 0 (light)
- 32:  0  0 63                                                                                                                                   ............................................................... 
- 33: 16  0 63 ................                                                                                                                  ............................................................... 
- 34: 31  0 63 ...............................                                                                                                   ............................................................... 
- 35: 47  0 63 ...............................................                                                                                   ............................................................... 
- 36: 63  0 63 ...............................................................                                                                   ............................................................... 
- 37: 63  0 47 ...............................................................                                                                   ...............................................                 
- 38: 63  0 31 ...............................................................                                                                   ...............................                                 
- 39: 63  0 16 ...............................................................                                                                   ................                                                
- 40: 63  0  0 ...............................................................                                                                                                                                   
- 41: 63 16  0 ...............................................................  ................                                                                                                                 
- 42: 63 31  0 ...............................................................  ...............................                                                                                                  
- 43: 63 47  0 ...............................................................  ...............................................                                                                                  
- 44: 63 63  0 ...............................................................  ...............................................................                                                                  
- 45: 47 63  0 ...............................................                  ...............................................................                                                                  
- 46: 31 63  0 ...............................                                  ...............................................................                                                                  
- 47: 16 63  0 ................                                                 ...............................................................                                                                  
- 48:  0 63  0                                                                  ...............................................................                                                                  
- 49:  0 63 16                                                                  ...............................................................  ................                                                
- 50:  0 63 31                                                                  ...............................................................  ...............................                                 
- 51:  0 63 47                                                                  ...............................................................  ...............................................                 
- 52:  0 63 63                                                                  ...............................................................  ............................................................... 
- 53:  0 47 63                                                                  ...............................................                  ............................................................... 
- 54:  0 31 63                                                                  ...............................                                  ............................................................... 
- 55:  0 16 63                                                                  ................                                                 ............................................................... 
-
- # Cycle 1 (lighter)
- 56: 31 31 63 ...............................                                  ...............................                                  ............................................................... 
- 57: 39 31 63 .......................................                          ...............................                                  ............................................................... 
- 58: 47 31 63 ...............................................                  ...............................                                  ............................................................... 
- 59: 55 31 63 .......................................................          ...............................                                  ............................................................... 
- 60: 63 31 63 ...............................................................  ...............................                                  ............................................................... 
- 61: 63 31 55 ...............................................................  ...............................                                  .......................................................         
- 62: 63 31 47 ...............................................................  ...............................                                  ...............................................                 
- 63: 63 31 39 ...............................................................  ...............................                                  .......................................                         
- 64: 63 31 31 ...............................................................  ...............................                                  ...............................                                 
- 65: 63 39 31 ...............................................................  .......................................                          ...............................                                 
- 66: 63 47 31 ...............................................................  ...............................................                  ...............................                                 
- 67: 63 55 31 ...............................................................  .......................................................          ...............................                                 
- 68: 63 63 31 ...............................................................  ...............................................................  ...............................                                 
- 69: 55 63 31 .......................................................          ...............................................................  ...............................                                 
- 70: 47 63 31 ...............................................                  ...............................................................  ...............................                                 
- 71: 39 63 31 .......................................                          ...............................................................  ...............................                                 
- 72: 31 63 31 ...............................                                  ...............................................................  ...............................                                 
- 73: 31 63 39 ...............................                                  ...............................................................  .......................................                         
- 74: 31 63 47 ...............................                                  ...............................................................  ...............................................                 
- 75: 31 63 55 ...............................                                  ...............................................................  .......................................................         
- 76: 31 63 63 ...............................                                  ...............................................................  ............................................................... 
- 77: 31 55 63 ...............................                                  .......................................................          ............................................................... 
- 78: 31 47 63 ...............................                                  ...............................................                  ............................................................... 
- 79: 31 39 63 ...............................                                  .......................................                          ............................................................... 
-
- # Cycle 2 (lightest)
- 80: 45 45 63 .............................................                    .............................................                    ............................................................... 
- 81: 49 45 63 .................................................                .............................................                    ............................................................... 
- 82: 54 45 63 ......................................................           .............................................                    ............................................................... 
- 83: 58 45 63 ..........................................................       .............................................                    ............................................................... 
- 84: 63 45 63 ...............................................................  .............................................                    ............................................................... 
- 85: 63 45 58 ...............................................................  .............................................                    ..........................................................      
- 86: 63 45 54 ...............................................................  .............................................                    ......................................................          
- 87: 63 45 49 ...............................................................  .............................................                    .................................................               
- 88: 63 45 45 ...............................................................  .............................................                    .............................................                   
- 89: 63 49 45 ...............................................................  .................................................                .............................................                   
- 90: 63 54 45 ...............................................................  ......................................................           .............................................                   
- 91: 63 58 45 ...............................................................  ..........................................................       .............................................                   
- 92: 63 63 45 ...............................................................  ...............................................................  .............................................                   
- 93: 58 63 45 ..........................................................       ...............................................................  .............................................                   
- 94: 54 63 45 ......................................................           ...............................................................  .............................................                   
- 95: 49 63 45 .................................................                ...............................................................  .............................................                   
- 96: 45 63 45 .............................................                    ...............................................................  .............................................                   
- 97: 45 63 49 .............................................                    ...............................................................  .................................................               
- 98: 45 63 54 .............................................                    ...............................................................  ......................................................          
- 99: 45 63 58 .............................................                    ...............................................................  ..........................................................      
-100: 45 63 63 .............................................                    ...............................................................  ............................................................... 
-101: 45 58 63 .............................................                    ..........................................................       ............................................................... 
-102: 45 54 63 .............................................                    ......................................................           ............................................................... 
-103: 45 49 63 .............................................                    .................................................                ............................................................... 
-
-# Cycle 3 (sober)
-104:  0  0 28                                                                                                                                   ............................                                    
-105:  7  0 28 .......                                                                                                                           ............................                                    
-106: 14  0 28 ..............                                                                                                                    ............................                                    
-107: 21  0 28 .....................                                                                                                             ............................                                    
-108: 28  0 28 ............................                                                                                                      ............................                                    
-109: 28  0 21 ............................                                                                                                      .....................                                           
-110: 28  0 14 ............................                                                                                                      ..............                                                  
-111: 28  0  7 ............................                                                                                                      .......                                                         
-112: 28  0  0 ............................                                                                                                                                                                      
-113: 28  7  0 ............................                                     .......                                                                                                                          
-114: 28 14  0 ............................                                     ..............                                                                                                                   
-115: 28 21  0 ............................                                     .....................                                                                                                            
-116: 28 28  0 ............................                                     ............................                                                                                                     
-117: 21 28  0 .....................                                            ............................                                                                                                     
-118: 14 28  0 ..............                                                   ............................                                                                                                     
-119:  7 28  0 .......                                                          ............................                                                                                                     
-120:  0 28  0                                                                  ............................                                                                                                     
-121:  0 28  7                                                                  ............................                                     .......                                                         
-122:  0 28 14                                                                  ............................                                     ..............                                                  
-123:  0 28 21                                                                  ............................                                     .....................                                           
-124:  0 28 28                                                                  ............................                                     ............................                                    
-125:  0 21 28                                                                  .....................                                            ............................                                    
-126:  0 14 28                                                                  ..............                                                   ............................                                    
-127:  0  7 28                                                                  .......                                                          ............................                                    
-
-# Cycle 4 (darker sober)
-128: 14 14 28 ..............                                                   ..............                                                   ............................                                    
-129: 17 14 28 .................                                                ..............                                                   ............................                                    
-130: 21 14 28 .....................                                            ..............                                                   ............................                                    
-131: 24 14 28 ........................                                         ..............                                                   ............................                                    
-132: 28 14 28 ............................                                     ..............                                                   ............................                                    
-133: 28 14 24 ............................                                     ..............                                                   ........................                                        
-134: 28 14 21 ............................                                     ..............                                                   .....................                                           
-135: 28 14 17 ............................                                     ..............                                                   .................                                               
-136: 28 14 14 ............................                                     ..............                                                   ..............                                                  
-137: 28 17 14 ............................                                     .................                                                ..............                                                  
-138: 28 21 14 ............................                                     .....................                                            ..............                                                  
-139: 28 24 14 ............................                                     ........................                                         ..............                                                  
-140: 28 28 14 ............................                                     ............................                                     ..............                                                  
-141: 24 28 14 ........................                                         ............................                                     ..............                                                  
-142: 21 28 14 .....................                                            ............................                                     ..............                                                  
-143: 17 28 14 .................                                                ............................                                     ..............                                                  
-144: 14 28 14 ..............                                                   ............................                                     ..............                                                  
-145: 14 28 17 ..............                                                   ............................                                     .................                                               
-146: 14 28 21 ..............                                                   ............................                                     .....................                                           
-147: 14 28 24 ..............                                                   ............................                                     ........................                                        
-148: 14 28 28 ..............                                                   ............................                                     ............................                                    
-149: 14 24 28 ..............                                                   ........................                                         ............................                                    
-150: 14 21 28 ..............                                                   .....................                                            ............................                                    
-151: 14 17 28 ..............                                                   .................                                                ............................                                    
-
-# Cycle 5 (darkest sober)
-152: 20 20 28 ....................                                             ....................                                             ............................                                    
-153: 22 20 28 ......................                                           ....................                                             ............................                                    
-154: 24 20 28 ........................                                         ....................                                             ............................                                    
-155: 26 20 28 ..........................                                       ....................                                             ............................                                    
-156: 28 20 28 ............................                                     ....................                                             ............................                                    
-157: 28 20 26 ............................                                     ....................                                             ..........................                                      
-158: 28 20 24 ............................                                     ....................                                             ........................                                        
-159: 28 20 22 ............................                                     ....................                                             ......................                                          
-160: 28 20 20 ............................                                     ....................                                             ....................                                            
-161: 28 22 20 ............................                                     ......................                                           ....................                                            
-162: 28 24 20 ............................                                     ........................                                         ....................                                            
-163: 28 26 20 ............................                                     ..........................                                       ....................                                            
-164: 28 28 20 ............................                                     ............................                                     ....................                                            
-165: 26 28 20 ..........................                                       ............................                                     ....................                                            
-166: 24 28 20 ........................                                         ............................                                     ....................                                            
-167: 22 28 20 ......................                                           ............................                                     ....................                                            
-168: 20 28 20 ....................                                             ............................                                     ....................                                            
-169: 20 28 22 ....................                                             ............................                                     ......................                                          
-170: 20 28 24 ....................                                             ............................                                     ........................                                        
-171: 20 28 26 ....................                                             ............................                                     ..........................                                      
-172: 20 28 28 ....................                                             ............................                                     ............................                                    
-173: 20 26 28 ....................                                             ..........................                                       ............................                                    
-174: 20 24 28 ....................                                             ........................                                         ............................                                    
-175: 20 22 28 ....................                                             ......................                                           ............................                                    
-
-# Cycle 6 (dark)
-176:  0  0 16                                                                                                                                   ................                                                
-177:  4  0 16 ....                                                                                                                              ................                                                
-178:  8  0 16 ........                                                                                                                          ................                                                
-179: 12  0 16 ............                                                                                                                      ................                                                
-180: 16  0 16 ................                                                                                                                  ................                                                
-181: 16  0 12 ................                                                                                                                  ............                                                    
-182: 16  0  8 ................                                                                                                                  ........                                                        
-183: 16  0  4 ................                                                                                                                  ....                                                            
-184: 16  0  0 ................                                                                                                                                                                                  
-185: 16  4  0 ................                                                 ....                                                                                                                             
-186: 16  8  0 ................                                                 ........                                                                                                                         
-187: 16 12  0 ................                                                 ............                                                                                                                     
-188: 16 16  0 ................                                                 ................                                                                                                                 
-189: 12 16  0 ............                                                     ................                                                                                                                 
-190:  8 16  0 ........                                                         ................                                                                                                                 
-191:  4 16  0 ....                                                             ................                                                                                                                 
-192:  0 16  0                                                                  ................                                                                                                                 
-193:  0 16  4                                                                  ................                                                 ....                                                            
-194:  0 16  8                                                                  ................                                                 ........                                                        
-195:  0 16 12                                                                  ................                                                 ............                                                    
-196:  0 16 16                                                                  ................                                                 ................                                                
-197:  0 12 16                                                                  ............                                                     ................                                                
-198:  0  8 16                                                                  ........                                                         ................                                                
-199:  0  4 16                                                                  ....                                                             ................                                                
-
-# Cycle 7 (darker)
-200:  8  8 16 ........                                                         ........                                                         ................                                                
-201: 10  8 16 ..........                                                       ........                                                         ................                                                
-202: 12  8 16 ............                                                     ........                                                         ................                                                
-203: 14  8 16 ..............                                                   ........                                                         ................                                                
-204: 16  8 16 ................                                                 ........                                                         ................                                                
-205: 16  8 14 ................                                                 ........                                                         ..............                                                  
-206: 16  8 12 ................                                                 ........                                                         ............                                                    
-207: 16  8 10 ................                                                 ........                                                         ..........                                                      
-208: 16  8  8 ................                                                 ........                                                         ........                                                        
-209: 16 10  8 ................                                                 ..........                                                       ........                                                        
-210: 16 12  8 ................                                                 ............                                                     ........                                                        
-211: 16 14  8 ................                                                 ..............                                                   ........                                                        
-212: 16 16  8 ................                                                 ................                                                 ........                                                        
-213: 14 16  8 ..............                                                   ................                                                 ........                                                        
-214: 12 16  8 ............                                                     ................                                                 ........                                                        
-215: 10 16  8 ..........                                                       ................                                                 ........                                                        
-216:  8 16  8 ........                                                         ................                                                 ........                                                        
-217:  8 16 10 ........                                                         ................                                                 ..........                                                      
-218:  8 16 12 ........                                                         ................                                                 ............                                                    
-219:  8 16 14 ........                                                         ................                                                 ..............                                                  
-220:  8 16 16 ........                                                         ................                                                 ................                                                
-221:  8 14 16 ........                                                         ..............                                                   ................                                                
-222:  8 12 16 ........                                                         ............                                                     ................                                                
-223:  8 10 16 ........                                                         ..........                                                       ................                                                
-
-# Cycle 8 (darkest)
-224: 11 11 16 ...........                                                      ...........                                                      ................                                                
-225: 12 11 16 ............                                                     ...........                                                      ................                                                
-226: 13 11 16 .............                                                    ...........                                                      ................                                                
-227: 15 11 16 ...............                                                  ...........                                                      ................                                                
-228: 16 11 16 ................                                                 ...........                                                      ................                                                
-229: 16 11 15 ................                                                 ...........                                                      ...............                                                 
-230: 16 11 13 ................                                                 ...........                                                      .............                                                   
-231: 16 11 12 ................                                                 ...........                                                      ............                                                    
-232: 16 11 11 ................                                                 ...........                                                      ...........                                                     
-233: 16 12 11 ................                                                 ............                                                     ...........                                                     
-234: 16 13 11 ................                                                 .............                                                    ...........                                                     
-235: 16 15 11 ................                                                 ...............                                                  ...........                                                     
-236: 16 16 11 ................                                                 ................                                                 ...........                                                     
-237: 15 16 11 ...............                                                  ................                                                 ...........                                                     
-238: 13 16 11 .............                                                    ................                                                 ...........                                                     
-239: 12 16 11 ............                                                     ................                                                 ...........                                                     
-240: 11 16 11 ...........                                                      ................                                                 ...........                                                     
-241: 11 16 12 ...........                                                      ................                                                 ............                                                    
-242: 11 16 13 ...........                                                      ................                                                 .............                                                   
-243: 11 16 15 ...........                                                      ................                                                 ...............                                                 
-244: 11 16 16 ...........                                                      ................                                                 ................                                                
-245: 11 15 16 ...........                                                      ...............                                                  ................                                                
-246: 11 13 16 ...........                                                      .............                                                    ................                                                
-247: 11 12 16 ...........                                                      ............                                                     ................                                                
-
-# Black
-248:  0  0  0                                                                                                                                                                                                   
-249:  0  0  0                                                                                                                                                                                                   
-250:  0  0  0                                                                                                                                                                                                   
-251:  0  0  0                                                                                                                                                                                                   
-252:  0  0  0                                                                                                                                                                                                   
-253:  0  0  0                                                                                                                                                                                                   
-254:  0  0  0                                                                                                                                                                                                   
-255:  0  0  0                                                                                                                                                                                                   
-
-# vim:listchars=:nowrap
diff --git a/baremetal/vga_palette.c b/baremetal/vga_palette.c
deleted file mode 100644
index 9f4161ce..00000000
--- a/baremetal/vga_palette.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/* Visualize the standard VGA palette.
- * Based on https://github.com/canidlogic/vgapal (MIT License) */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-char* bar(int n) {
-  char* result = calloc(1024, 32);
-  int i;
-  strcat(result, "<div style='float:left; width:40em'>");
-  for (i = 0; i < n; ++i)
-//?     result[i] = '.';
-    strcat(result, "&#9632;");  /* black square */
-//?   for (; i < 64; ++i)
-//?     result[i] = ' ';
-//?   result[64] = '\0';
-  strcat(result, "&nbsp;</div>");  /* make sure the div occupies space */
-  return result;
-}
-
-/* convert 6-bit color to 8-bit color */
-int levelUp(int n) {
-  assert(n < 64);
-  /* duplicate two most significant bits in two least significant bits */
-  return (n<<2) | (n>>4);
-}
-
-void addColor(int r, int g, int b) {
-  static int i = 0;
-//?   printf("%02x %02x %02x\n", r, g, b);
-//?   printf("%3d: %2d %2d %2d %s %s %s\n", i, r, g, b, bar(r), bar(g), bar(b));
-  printf("<div style='clear:both; white-space:pre; color:#%02x%02x%02x'><div style='float:left; margin-right:1em'>%03d: %02d %02d %02d</div> %s %s %s</div>\n", levelUp(r), levelUp(g), levelUp(b), i, r, g, b, bar(r), bar(g), bar(b));
-  ++i;
-}
-
-void addGrayColor(int v) {
-  addColor(v, v, v);
-}
-
-void add16Color(int lo, int melo, int mehi, int hi) {
-  int r, g, b, i, h, l;
-
-  for (i = 0; i < 8; i++) {
-    r = g = b = lo;
-    if (i & 4) r = mehi;
-    if (i & 2) g = (i==6)?melo:mehi;  /* exception: color 6 is brown rather than dark yellow */
-    if (i & 1) b = mehi;
-    addColor(r, g, b);
-  }
-
-  for (i = 0; i < 8; i++) {
-    r = g = b = melo;
-    if (i & 4) r = hi;
-    if (i & 2) g = hi;
-    if (i & 1) b = hi;
-    addColor(r, g, b);
-  }
-}
-
-/* Add four colors to the palette corresponding to a "run" within an RGB cycle.
- *
- * start - high and low starting states for each channel at the start of the run
- * ch - the channel to change from high to low or low to high */
-void addRun(int start, int ch, int lo, int melo, int me, int mehi, int hi) {
-  int r, g, b, i, up;
-
-  /* Check parameters */
-  if (start < 0 || start > 7)
-    abort();
-  if (ch != 1 && ch != 2 && ch != 4)
-    abort();
-
-  /* Get the starting RGB color and add it */
-  r = lo;
-  g = lo;
-  b = lo;
-  if ((start & 4) == 4)
-    r = hi;
-  if ((start & 2) == 2)
-    g = hi;
-  if ((start & 1) == 1)
-    b = hi;
-  addColor(r, g, b);
-
-  /* If selected channel starts high, we're going down; otherwise we're going up */
-  up = (start & ch) != ch;
-
-  /* Add remaining three colors of the run */
-  switch (ch) {
-  case 4:  r = up?melo:mehi; break;
-  case 2:  g = up?melo:mehi; break;
-  case 1:  b = up?melo:mehi; break;
-  }
-  addColor(r, g, b);
-
-  switch (ch) {
-  case 4:  r = me; break;
-  case 2:  g = me; break;
-  case 1:  b = me; break;
-  }
-  addColor(r, g, b);
-
-  switch (ch) {
-  case 4:  r = up?mehi:melo; break;
-  case 2:  g = up?mehi:melo; break;
-  case 1:  b = up?mehi:melo; break;
-  }
-  addColor(r, g, b);
-}
-
-/* A cycle consists of six 4-color runs, each of which transitions from
- * one hue to another until arriving back at starting position. */
-void addCycle(int lo, int melo, int me, int mehi, int hi) {
-  int hue = 1;  /* blue */
-  addRun(hue, 4, lo, melo, me, mehi, hi);
-  hue ^= 4;
-  assert(hue == 5);
-  addRun(hue, 1, lo, melo, me, mehi, hi);
-  hue ^= 1;
-  assert(hue == 4);
-  addRun(hue, 2, lo, melo, me, mehi, hi);
-  hue ^= 2;
-  assert(hue == 6);
-  addRun(hue, 4, lo, melo, me, mehi, hi);
-  hue ^= 4;
-  assert(hue == 2);
-  addRun(hue, 1, lo, melo, me, mehi, hi);
-  hue ^= 1;
-  assert(hue == 3);
-  addRun(hue, 2, lo, melo, me, mehi, hi);
-}
-
-int main(void) {
-  int i;
-
-  /* 16-color palette */
-  add16Color(0, 21, 42, 63);
-
-  /* 16 shades of gray */
-  addGrayColor( 0);
-  addGrayColor( 5);
-  addGrayColor( 8);
-  addGrayColor(11);
-  addGrayColor(14);
-  addGrayColor(17);
-  addGrayColor(20);
-  addGrayColor(24);
-  addGrayColor(28);
-  addGrayColor(32);
-  addGrayColor(36);
-  addGrayColor(40);
-  addGrayColor(45);
-  addGrayColor(50);
-  addGrayColor(56);
-  addGrayColor(63);
-
-  /* Nine RGB cycles organized in three groups of three cycles,
-   * The groups represent high/medium/low value,
-   * and the cycles within the groups represent high/medium/low saturation. */
-  addCycle( 0, 16, 31, 47, 63);
-  addCycle(31, 39, 47, 55, 63);
-  addCycle(45, 49, 54, 58, 63);
-
-  addCycle( 0,  7, 14, 21, 28);
-  addCycle(14, 17, 21, 24, 28);
-  addCycle(20, 22, 24, 26, 28);
-
-  addCycle( 0,  4,  8, 12, 16);
-  addCycle( 8, 10, 12, 14, 16);
-  addCycle(11, 12, 13, 15, 16);
-
-  /* final eight palette entries are full black */
-  for (i = 0; i < 8; ++i)
-    addGrayColor(0);
-
-  return 0;
-}
diff --git a/baremetal/vga_palette.png b/baremetal/vga_palette.png
deleted file mode 100644
index cea46962..00000000
--- a/baremetal/vga_palette.png
+++ /dev/null
Binary files differdiff --git a/baremetal/vimrc.vim b/baremetal/vimrc.vim
deleted file mode 100644
index 348fe364..00000000
--- a/baremetal/vimrc.vim
+++ /dev/null
@@ -1,2 +0,0 @@
-" when opening files in this directory, load vimrc from cwd (top-level)
-source vimrc.vim