about summary refs log tree commit diff stats
path: root/subx
diff options
context:
space:
mode:
Diffstat (limited to 'subx')
-rwxr-xr-xsubx (renamed from subx/subx)0
-rw-r--r--subx/000organization.cc162
-rw-r--r--subx/001help.cc294
-rw-r--r--subx/002test.cc111
-rw-r--r--subx/003trace.cc520
-rw-r--r--subx/003trace.test.cc133
-rw-r--r--subx/010---vm.cc401
-rw-r--r--subx/011run.cc467
-rw-r--r--subx/012elf.cc190
-rw-r--r--subx/013direct_addressing.cc1279
-rw-r--r--subx/014indirect_addressing.cc1000
-rw-r--r--subx/015immediate_addressing.cc1272
-rw-r--r--subx/016index_addressing.cc155
-rw-r--r--subx/017jump_disp8.cc407
-rw-r--r--subx/018jump_disp32.cc407
-rw-r--r--subx/019functions.cc122
-rw-r--r--subx/020syscalls.cc123
-rw-r--r--subx/021byte_addressing.cc176
-rw-r--r--subx/022div.cc38
-rw-r--r--subx/028translate.cc212
-rw-r--r--subx/029transforms.cc65
-rw-r--r--subx/030---operands.cc539
-rw-r--r--subx/031check_operands.cc691
-rw-r--r--subx/032check_operand_bounds.cc143
-rw-r--r--subx/034compute_segment_address.cc86
-rw-r--r--subx/035labels.cc416
-rw-r--r--subx/036global_variables.cc305
-rw-r--r--subx/038---literal_strings.cc324
-rw-r--r--subx/039debug.cc147
-rw-r--r--subx/040---tests.cc97
-rw-r--r--subx/049memory_layout.subx8
-rw-r--r--subx/050_write.subx57
-rw-r--r--subx/051test.subx93
-rw-r--r--subx/052kernel-string-equal.subx267
-rw-r--r--subx/053new-segment.subx90
-rw-r--r--subx/054string-equal.subx230
-rw-r--r--subx/055stream.subx73
-rw-r--r--subx/056trace.subx1009
-rw-r--r--subx/057write.subx159
-rw-r--r--subx/058stream-equal.subx593
-rw-r--r--subx/059stop.subx206
-rw-r--r--subx/060read.subx440
-rw-r--r--subx/061read-byte.subx293
-rw-r--r--subx/062write-stream.subx255
-rw-r--r--subx/063error.subx50
-rw-r--r--subx/064write-byte.subx288
-rw-r--r--subx/065write-buffered.subx228
-rw-r--r--subx/066print-int.subx389
-rw-r--r--subx/067parse-hex.subx874
-rw-r--r--subx/068error-byte.subx91
-rw-r--r--subx/069allocate.subx204
-rw-r--r--subx/070new-stream.subx118
-rw-r--r--subx/071read-line.subx391
-rw-r--r--subx/072slice.subx1173
-rw-r--r--subx/073next-token.subx897
-rw-r--r--subx/074print-int-decimal.subx307
-rw-r--r--subx/075array-equal.subx629
-rw-r--r--subx/076zero-out.subx84
-rw-r--r--subx/077slurp.subx161
-rw-r--r--subx/100index11
-rw-r--r--subx/Readme.md777
-rw-r--r--subx/apps/Readme.md2
-rwxr-xr-xsubx/apps/assortbin34384 -> 0 bytes
-rw-r--r--subx/apps/assort.subx911
-rwxr-xr-xsubx/apps/crenshaw2-1bin24788 -> 0 bytes
-rw-r--r--subx/apps/crenshaw2-1.subx585
-rwxr-xr-xsubx/apps/crenshaw2-1bbin25347 -> 0 bytes
-rw-r--r--subx/apps/crenshaw2-1b.subx785
-rwxr-xr-xsubx/apps/dquotesbin40940 -> 0 bytes
-rw-r--r--subx/apps/dquotes.subx2757
-rwxr-xr-xsubx/apps/factorialbin23704 -> 0 bytes
-rw-r--r--subx/apps/factorial.subx117
-rwxr-xr-xsubx/apps/handlebin24558 -> 0 bytes
-rw-r--r--subx/apps/handle.subx412
-rwxr-xr-xsubx/apps/hexbin36939 -> 0 bytes
-rw-r--r--subx/apps/hex.subx1515
-rwxr-xr-xsubx/apps/packbin47070 -> 0 bytes
-rw-r--r--subx/apps/pack.subx5986
-rw-r--r--subx/apps/subx-common.subx3118
-rwxr-xr-xsubx/apps/surveybin43605 -> 0 bytes
-rw-r--r--subx/apps/survey.subx4787
-rwxr-xr-xsubx/apps/testsbin33196 -> 0 bytes
-rw-r--r--subx/apps/tests.subx279
-rwxr-xr-xsubx/build141
-rwxr-xr-xsubx/build_and_test_until18
-rw-r--r--subx/cheatsheet.pdfbin76298 -> 0 bytes
-rwxr-xr-xsubx/clean8
-rwxr-xr-xsubx/diff_ntranslate7
-rwxr-xr-xsubx/diff_translate7
-rwxr-xr-xsubx/edit23
-rw-r--r--subx/examples/Readme.md6
-rwxr-xr-xsubx/examples/ex1bin128 -> 0 bytes
-rw-r--r--subx/examples/ex1.subx21
-rwxr-xr-xsubx/examples/ex10bin195 -> 0 bytes
-rw-r--r--subx/examples/ex10.subx72
-rwxr-xr-xsubx/examples/ex11bin1111 -> 0 bytes
-rw-r--r--subx/examples/ex11.subx357
-rwxr-xr-xsubx/examples/ex12bin167 -> 0 bytes
-rw-r--r--subx/examples/ex12.subx45
-rwxr-xr-xsubx/examples/ex2bin129 -> 0 bytes
-rw-r--r--subx/examples/ex2.subx23
-rwxr-xr-xsubx/examples/ex3bin146 -> 0 bytes
-rw-r--r--subx/examples/ex3.subx39
-rwxr-xr-xsubx/examples/ex4bin171 -> 0 bytes
-rw-r--r--subx/examples/ex4.subx42
-rwxr-xr-xsubx/examples/ex5bin171 -> 0 bytes
-rw-r--r--subx/examples/ex5.subx45
-rwxr-xr-xsubx/examples/ex6bin165 -> 0 bytes
-rw-r--r--subx/examples/ex6.subx37
-rwxr-xr-xsubx/examples/ex7bin313 -> 0 bytes
-rw-r--r--subx/examples/ex7.subx107
-rwxr-xr-xsubx/examples/ex8bin165 -> 0 bytes
-rw-r--r--subx/examples/ex8.subx61
-rwxr-xr-xsubx/examples/ex9bin159 -> 0 bytes
-rw-r--r--subx/examples/ex9.subx55
-rw-r--r--subx/exuberant_ctags_rc3
-rw-r--r--subx/modrm.pdfbin46205 -> 0 bytes
-rwxr-xr-xsubx/ntranslate33
-rw-r--r--subx/opcodes106
-rwxr-xr-xsubx/run_one_test.sh22
-rw-r--r--subx/run_one_test.subx30
-rw-r--r--subx/sib.pdfbin51968 -> 0 bytes
-rw-r--r--subx/stats.md36
-rw-r--r--subx/subx.vim68
-rwxr-xr-xsubx/test_apps336
-rwxr-xr-xsubx/test_layers33
-rwxr-xr-xsubx/translate33
-rw-r--r--subx/vimrc.vim106
128 files changed, 0 insertions, 43901 deletions
diff --git a/subx/subx b/subx
index 257e2f13..257e2f13 100755
--- a/subx/subx
+++ b/subx
diff --git a/subx/000organization.cc b/subx/000organization.cc
deleted file mode 100644
index 87004af5..00000000
--- a/subx/000organization.cc
+++ /dev/null
@@ -1,162 +0,0 @@
-//: You guessed right: the '000' prefix means you should start reading here.
-//:
-//: This project is set up to load all files with a numeric prefix. Just
-//: create a new file and start hacking.
-//:
-//: The first few files (00*) are independent of what this program does, an
-//: experimental skeleton that will hopefully make it both easier for others to
-//: understand and more malleable, easier to rewrite and remould into radically
-//: different shapes without breaking in subtle corner cases. The premise is
-//: that understandability and rewrite-friendliness are related in a virtuous
-//: cycle. Doing one well makes it easier to do the other.
-//:
-//: Lower down, this file contains a legal, bare-bones C++ program. It doesn't
-//: do anything yet; subsequent files will contain :(...) directives to insert
-//: lines into it. For example:
-//:   :(after "more events")
-//: This directive means: insert the following lines after a line in the
-//: program containing the words "more events".
-//:
-//: A simple tool is included to 'tangle' all the files together in sequence
-//: according to their directives into a single source file containing all the
-//: code for the project, and then feed the source file to the compiler.
-//: (It'll drop these comments starting with a '//:' prefix that only make
-//: sense before tangling.)
-//:
-//: Directives free up the programmer to order code for others to read rather
-//: than as forced by the computer or compiler. Each individual feature can be
-//: organized in a self-contained 'layer' that adds code to many different data
-//: structures and functions all over the program. The right decomposition into
-//: layers will let each layer make sense in isolation.
-//:
-//:   "If I look at any small part of it, I can see what is going on -- I don't
-//:   need to refer to other parts to understand what something is doing.
-//:
-//:   If I look at any large part in overview, I can see what is going on -- I
-//:   don't need to know all the details to get it.
-//:
-//:   Every level of detail is as locally coherent and as well thought-out as
-//:   any other level."
-//:
-//:       -- Richard Gabriel, "The Quality Without A Name"
-//:          (http://dreamsongs.com/Files/PatternsOfSoftware.pdf, page 42)
-//:
-//: Directives are powerful; they permit inserting or modifying any point in
-//: the program. Using them tastefully requires mapping out specific lines as
-//: waypoints for future layers to hook into. Often such waypoints will be in
-//: comments, capitalized to hint that other layers rely on their presence.
-//:
-//: A single waypoint might have many different code fragments hooking into
-//: it from all over the codebase. Use 'before' directives to insert
-//: code at a location in order, top to bottom, and 'after' directives to
-//: insert code in reverse order. By convention waypoints intended for insertion
-//: before begin with 'End'. Notice below how the layers line up above the "End
-//: Foo" waypoint.
-//:
-//:   File 001          File 002                File 003
-//:   ============      ===================     ===================
-//:   // Foo
-//:   ------------
-//:              <----  :(before "End Foo")
-//:                     ....
-//:                     ...
-//:   ------------
-//:              <----------------------------  :(before "End Foo")
-//:                                             ....
-//:                                             ...
-//:   // End Foo
-//:   ============
-//:
-//: Here's part of a layer in color: http://i.imgur.com/0eONnyX.png. Directives
-//: are shaded dark.
-//:
-//: Layers do more than just shuffle code around. In a well-organized codebase
-//: it should be possible to stop loading after any file/layer, build and run
-//: the program, and pass all tests for loaded features. (Relevant is
-//: http://youtube.com/watch?v=c8N72t7aScY, a scene from "2001: A Space
-//: Odyssey".) Get into the habit of running the included script called
-//: 'test_layers' before you commit any changes.
-//:
-//: This 'subsetting guarantee' ensures that this directory contains a
-//: cleaned-up narrative of the evolution of this codebase. Organizing
-//: autobiographically allows newcomers to rapidly orient themselves, reading
-//: the first few files to understand a simple gestalt of a program's core
-//: purpose and features, and later gradually working their way through other
-//: features as the need arises.
-//:
-//: Programmers shouldn't need to understand everything about a program to
-//: hack on it. But they shouldn't be prevented from a thorough understanding
-//: of each aspect either. The goal of layers is to reward curiosity.
-
-// Includes
-// End Includes
-
-// Types
-// End Types
-
-// Function prototypes are auto-generated in the 'build' script; define your
-// functions in any order. Just be sure to declare each function header all on
-// one line, ending with the '{'. Our auto-generation scripts are too minimal
-// and simple-minded to handle anything else.
-#include "function_list"  // by convention, files ending with '_list' are auto-generated
-
-// Globals
-//
-// All statements in this section should always define a single variable on a
-// single line. The 'build' script will simple-mindedly auto-generate extern
-// declarations for them. Remember to define (not just declare) constants with
-// extern linkage in this section, since C++ global constants have internal
-// linkage by default.
-//
-// End Globals
-
-int main(int argc, char* argv[]) {
-  atexit(reset);
-  // we require a 32-bit little-endian system
-  assert(sizeof(int) == 4);
-  assert(sizeof(float) == 4);
-  assert_little_endian();
-
-  // End One-time Setup
-
-  // Commandline Parsing
-  // End Commandline Parsing
-
-  // End Main
-
-  return 0;
-}
-
-// Unit Tests
-// End Unit Tests
-
-//: our first directive; insert the following headers at the start of the program
-:(before "End Includes")
-#include <assert.h>
-#include <stdlib.h>
-
-//: Without directives or with the :(code) directive, lines get added at the
-//: end.
-//:
-//: Regardless of where functions are defined, we can call them anywhere we
-//: like as long as we format the function header in a specific way: put it
-//: all on a single line without indent, end the line with ') {' and no
-//: trailing whitespace. As long as functions uniformly start this way, our
-//: 'build' script contains a little command to automatically generate
-//: declarations for them.
-:(code)
-void reset() {
-  // End Reset
-}
-
-void assert_little_endian() {
-  const int x = 1;
-  const char* y = reinterpret_cast<const char*>(&x);
-  if (*y != 1) {
-    cerr << "SubX requires a little-endian processor. Do you have Intel (or AMD or Atom) inside?\n";
-    exit(1);
-  }
-}
-:(before "End Includes")
-#include<iostream>
-using std::cerr;
diff --git a/subx/001help.cc b/subx/001help.cc
deleted file mode 100644
index 7236ca50..00000000
--- a/subx/001help.cc
+++ /dev/null
@@ -1,294 +0,0 @@
-//: Everything this project/binary supports.
-//: This should give you a sense for what to look forward to in later layers.
-
-:(before "End Commandline Parsing")
-if (argc <= 1 || is_equal(argv[1], "--help")) {
-  //: this is the functionality later layers will provide
-  // currently no automated tests for commandline arg parsing
-  cerr << get(Help, "usage");
-  return 0;
-}
-
-//: Support for option parsing.
-//: Options always begin with '--' and are always the first arguments. An
-//: option will never follow a non-option.
-char** arg = &argv[1];
-while (argc > 1 && starts_with(*arg, "--")) {
-  if (false)
-    ;  // no-op branch just so any further additions can consistently always start with 'else'
-  // End Commandline Options(*arg)
-  else
-    cerr << "skipping unknown option " << *arg << '\n';
-  --argc;  ++argv;  ++arg;
-}
-
-if (is_equal(argv[1], "help")) {
-  if (argc == 2) {
-    cerr << "help on what?\n";
-    help_contents();
-    return 0;
-  }
-  string key(argv[2]);
-  // End Help Special-cases(key)
-  if (contains_key(Help, key)) {
-    cerr << get(Help, key);
-    return 0;
-  }
-  else {
-    cerr << "No help found for '" << key << "'\n";
-    help_contents();
-    cerr << "Please check your command for typos.\n";
-    return 1;
-  }
-}
-
-:(code)
-void help_contents() {
-  cerr << "Available top-level topics:\n";
-  cerr << "  usage\n";
-  // End Help Contents
-}
-
-:(before "End Globals")
-map<string, string> Help;
-:(before "End Includes")
-#include <map>
-using std::map;
-:(before "End One-time Setup")
-init_help();
-:(code)
-void init_help() {
-  put(Help, "usage",
-    "Welcome to SubX, a better way to program in machine code.\n"
-    "SubX uses a subset of the x86 instruction set. SubX programs will run\n"
-    "without modification on Linux computers.\n"
-    "It provides a better experience and better error messages than\n"
-    "programming directly in machine code, but you have to stick to the\n"
-    "instructions it supports.\n"
-    "\n"
-    "== Ways to invoke subx\n"
-    "- Run tests:\n"
-    "    subx test\n"
-    "- See this message:\n"
-    "    subx --help\n"
-    "- Convert a textual SubX program into a standard ELF binary that you can\n"
-    "  run on your computer:\n"
-    "    subx translate input1.subx input2.subx ... -o <output ELF binary>\n"
-    "- Run a SubX binary using SubX itself (for better error messages):\n"
-    "    subx run <ELF binary>\n"
-    "\n"
-    "== Debugging aids\n"
-    "- Add '--trace' to any of these commands to save a trace.\n"
-    "- Add '--debug' to add information to traces. 'subx --debug translate' will\n"
-    "  save metadata to disk that 'subx --debug --trace run' uses to make traces\n"
-    "  more informative.\n"
-    "\n"
-    "Options starting with '--' must always come before any other arguments.\n"
-    "\n"
-    "To start learning how to write SubX programs, see Readme.md (particularly\n"
-    "the section on the x86 instruction set) and then run:\n"
-    "  subx help\n"
-  );
-  // End Help Texts
-}
-
-:(code)
-bool is_equal(char* s, const char* lit) {
-  return strncmp(s, lit, strlen(lit)) == 0;
-}
-
-bool starts_with(const string& s, const string& pat) {
-  string::const_iterator a=s.begin(), b=pat.begin();
-  for (/*nada*/;  a!=s.end() && b!=pat.end();  ++a, ++b)
-    if (*a != *b) return false;
-  return b == pat.end();
-}
-
-//: I'll throw some style conventions here for want of a better place for them.
-//: As a rule I hate style guides. Do what you want, that's my motto. But since
-//: we're dealing with C/C++, the one big thing we want to avoid is undefined
-//: behavior. If a compiler ever encounters undefined behavior it can make
-//: your program do anything it wants.
-//:
-//: For reference, my checklist of undefined behaviors to watch out for:
-//:   out-of-bounds access
-//:   uninitialized variables
-//:   use after free
-//:   dereferencing invalid pointers: null, a new of size 0, others
-//:
-//:   casting a large number to a type too small to hold it
-//:
-//:   integer overflow
-//:   division by zero and other undefined expressions
-//:   left-shift by negative count
-//:   shifting values by more than or equal to the number of bits they contain
-//:   bitwise operations on signed numbers
-//:
-//:   Converting pointers to types of different alignment requirements
-//:     T* -> void* -> T*: defined
-//:     T* -> U* -> T*: defined if non-function pointers and alignment requirements are same
-//:     function pointers may be cast to other function pointers
-//:
-//:       Casting a numeric value into a value that can't be represented by the target type (either directly or via static_cast)
-//:
-//: To guard against these, some conventions:
-//:
-//: 0. Initialize all primitive variables in functions and constructors.
-//:
-//: 1. Minimize use of pointers and pointer arithmetic. Avoid 'new' and
-//: 'delete' as far as possible. Rely on STL to perform memory management to
-//: avoid use-after-free issues (and memory leaks).
-//:
-//: 2. Avoid naked arrays to avoid out-of-bounds access. Never use operator[]
-//: except with map. Use at() with STL vectors and so on.
-//:
-//: 3. Valgrind all the things.
-//:
-//: 4. Avoid unsigned numbers. Not strictly an undefined-behavior issue, but
-//: the extra range doesn't matter, and it's one less confusing category of
-//: interaction gotchas to worry about.
-//:
-//: Corollary: don't use the size() method on containers, since it returns an
-//: unsigned and that'll cause warnings about mixing signed and unsigned,
-//: yadda-yadda. Instead use this macro below to perform an unsafe cast to
-//: signed. We'll just give up immediately if a container's ever too large.
-//: Basically, Mu is not concerned about this being a little slower than it
-//: could be. (https://gist.github.com/rygorous/e0f055bfb74e3d5f0af20690759de5a7)
-//:
-//: Addendum to corollary: We're going to uniformly use int everywhere, to
-//: indicate that we're oblivious to number size, and since Clang on 32-bit
-//: platforms doesn't yet support multiplication over 64-bit integers, and
-//: since multiplying two integers seems like a more common situation to end
-//: up in than integer overflow.
-:(before "End Includes")
-#define SIZE(X) (assert((X).size() < (1LL<<(sizeof(int)*8-2))), static_cast<int>((X).size()))
-
-//: 5. Integer overflow is guarded against at runtime using the -ftrapv flag
-//: to the compiler, supported by Clang (GCC version only works sometimes:
-//: http://stackoverflow.com/questions/20851061/how-to-make-gcc-ftrapv-work).
-:(before "atexit(reset)")
-initialize_signal_handlers();  // not always necessary, but doesn't hurt
-//? cerr << INT_MAX+1 << '\n';  // test overflow
-//? assert(false);  // test SIGABRT
-:(code)
-// based on https://spin.atomicobject.com/2013/01/13/exceptions-stack-traces-c
-void initialize_signal_handlers() {
-  struct sigaction action;
-  bzero(&action, sizeof(action));
-  action.sa_sigaction = dump_and_exit;
-  sigemptyset(&action.sa_mask);
-  sigaction(SIGABRT, &action, NULL);  // assert() failure or integer overflow on linux (with -ftrapv)
-  sigaction(SIGILL,  &action, NULL);  // integer overflow on OS X (with -ftrapv)
-}
-void dump_and_exit(int sig, siginfo_t* /*unused*/, void* /*unused*/) {
-  switch (sig) {
-    case SIGABRT:
-      #ifndef __APPLE__
-        cerr << "SIGABRT: might be an integer overflow if it wasn't an assert() failure\n";
-        _Exit(1);
-      #endif
-      break;
-    case SIGILL:
-      #ifdef __APPLE__
-        cerr << "SIGILL: most likely caused by integer overflow\n";
-        _Exit(1);
-      #endif
-      break;
-    default:
-      break;
-  }
-}
-:(before "End Includes")
-#include <signal.h>
-
-//: For good measure we'll also enable SIGFPE.
-:(before "atexit(reset)")
-feenableexcept(FE_OVERFLOW | FE_UNDERFLOW);
-//? assert(sizeof(int) == 4 && sizeof(float) == 4);
-//? //                          | exp   |  mantissa
-//? int smallest_subnormal = 0b00000000000000000000000000000001;
-//? float smallest_subnormal_f = *reinterpret_cast<float*>(&smallest_subnormal);
-//? cerr << "ε: " << smallest_subnormal_f << '\n';
-//? cerr << "ε/2: " << smallest_subnormal_f/2 << " (underflow)\n";  // test SIGFPE
-:(before "End Includes")
-#include <fenv.h>
-:(code)
-#ifdef __APPLE__
-// Public domain polyfill for feenableexcept on OS X
-// http://www-personal.umich.edu/~williams/archive/computation/fe-handling-example.c
-int feenableexcept(unsigned int excepts) {
-  static fenv_t fenv;
-  unsigned int new_excepts = excepts & FE_ALL_EXCEPT;
-  unsigned int old_excepts;
-  if (fegetenv(&fenv)) return -1;
-  old_excepts = fenv.__control & FE_ALL_EXCEPT;
-  fenv.__control &= ~new_excepts;
-  fenv.__mxcsr &= ~(new_excepts << 7);
-  return fesetenv(&fenv) ? -1 : old_excepts;
-}
-#endif
-
-//: 6. Map's operator[] being non-const is fucking evil.
-:(before "Globals")  // can't generate prototypes for these
-// from http://stackoverflow.com/questions/152643/idiomatic-c-for-reading-from-a-const-map
-template<typename T> typename T::mapped_type& get(T& map, typename T::key_type const& key) {
-  typename T::iterator iter(map.find(key));
-  if (iter == map.end()) {
-    cerr << "get couldn't find key '" << key << "'\n";
-    assert(iter != map.end());
-  }
-  return iter->second;
-}
-template<typename T> typename T::mapped_type const& get(const T& map, typename T::key_type const& key) {
-  typename T::const_iterator iter(map.find(key));
-  if (iter == map.end()) {
-    cerr << "get couldn't find key '" << key << "'\n";
-    assert(iter != map.end());
-  }
-  return iter->second;
-}
-template<typename T> typename T::mapped_type const& put(T& map, typename T::key_type const& key, typename T::mapped_type const& value) {
-  map[key] = value;
-  return map[key];
-}
-template<typename T> bool contains_key(T& map, typename T::key_type const& key) {
-  return map.find(key) != map.end();
-}
-template<typename T> typename T::mapped_type& get_or_insert(T& map, typename T::key_type const& key) {
-  return map[key];
-}
-template<typename T> typename T::mapped_type const& put_new(T& map, typename T::key_type const& key, typename T::mapped_type const& value) {
-  assert(map.find(key) == map.end());
-  map[key] = value;
-  return map[key];
-}
-//: The contract: any container that relies on get_or_insert should never call
-//: contains_key.
-
-//: 7. istreams are a royal pain in the arse. You have to be careful about
-//: what subclass you try to putback into. You have to watch out for the pesky
-//: failbit and badbit. Just avoid eof() and use this helper instead.
-:(code)
-bool has_data(istream& in) {
-  return in && !in.eof();
-}
-
-:(before "End Includes")
-#include <assert.h>
-
-#include <iostream>
-using std::istream;
-using std::ostream;
-using std::iostream;
-using std::cin;
-using std::cout;
-using std::cerr;
-#include <iomanip>
-
-#include <string.h>
-#include <string>
-using std::string;
-
-#include <algorithm>
-using std::min;
-using std::max;
diff --git a/subx/002test.cc b/subx/002test.cc
deleted file mode 100644
index f25e331f..00000000
--- a/subx/002test.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-//: A simple test harness. To create new tests, define functions starting with
-//: 'test_'. To run all tests so defined, run:
-//:   $ ./mu test
-//:
-//: Every layer should include tests, and can reach into previous layers.
-//: However, it seems like a good idea never to reach into tests from previous
-//: layers. Every test should be a contract that always passes as originally
-//: written, regardless of any later layers. Avoid writing 'temporary' tests
-//: that are only meant to work until some layer.
-
-:(before "End Types")
-typedef void (*test_fn)(void);
-:(before "Globals")
-// move a global ahead into types that we can't generate an extern declaration for
-const test_fn Tests[] = {
-  #include "test_list"  // auto-generated; see 'build*' scripts
-};
-
-:(before "End Globals")
-bool Run_tests = false;
-bool Passed = true;  // set this to false inside any test to indicate failure
-
-:(before "End Includes")
-#define CHECK(X) \
-  if (Passed && !(X)) { \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << '\n'; \
-    Passed = false; \
-    return;  /* Currently we stop at the very first failure. */ \
-  }
-
-#define CHECK_EQ(X, Y) \
-  if (Passed && (X) != (Y)) { \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << " == " << #Y << '\n'; \
-    cerr << "  got " << (X) << '\n';  /* BEWARE: multiple eval */ \
-    Passed = false; \
-    return;  /* Currently we stop at the very first failure. */ \
-  }
-
-:(before "End Reset")
-Passed = true;
-
-:(before "End Commandline Parsing")
-if (argc > 1 && is_equal(argv[1], "test")) {
-  Run_tests = true;  --argc;  ++argv;  // shift 'test' out of commandline args
-}
-
-:(before "End Main")
-if (Run_tests) {
-  // Test Runs
-  // we run some tests and then exit; assume no state need be maintained afterward
-
-  long num_failures = 0;
-  // End Test Run Initialization
-  time_t t;  time(&t);
-  cerr << "C tests: " << ctime(&t);
-  for (size_t i=0;  i < sizeof(Tests)/sizeof(Tests[0]);  ++i) {
-//?     cerr << "running " << Test_names[i] << '\n';
-    run_test(i);
-    if (Passed) cerr << '.';
-    else ++num_failures;
-  }
-  cerr << '\n';
-  // End Tests
-  if (num_failures > 0) {
-    cerr << num_failures << " failure"
-         << (num_failures > 1 ? "s" : "")
-         << '\n';
-    return 1;
-  }
-  return 0;
-}
-
-:(after "End Main")
-//: Raise other unrecognized sub-commands as errors.
-//: We couldn't do this until now because we want `./subx test` to always
-//: succeed, no matter how many layers are included in the build.
-cerr << "nothing to do\n";
-return 1;
-
-:(code)
-void run_test(size_t i) {
-  if (i >= sizeof(Tests)/sizeof(Tests[0])) {
-    cerr << "no test " << i << '\n';
-    return;
-  }
-  reset();
-  // End Test Setup
-  (*Tests[i])();
-  // End Test Teardown
-}
-
-//: Convenience: run a single test
-:(before "Globals")
-// Names for each element of the 'Tests' global, respectively.
-const string Test_names[] = {
-  #include "test_name_list"  // auto-generated; see 'build*' scripts
-};
-:(after "Test Runs")
-string maybe_single_test_to_run = argv[argc-1];
-if (!starts_with(maybe_single_test_to_run, "test_"))
-  maybe_single_test_to_run.insert(0, "test_");
-for (size_t i=0;  i < sizeof(Tests)/sizeof(Tests[0]);  ++i) {
-  if (Test_names[i] == maybe_single_test_to_run) {
-    run_test(i);
-    if (Passed) cerr << ".\n";
-    return 0;
-  }
-}
-
-:(before "End Includes")
-#include <stdlib.h>
diff --git a/subx/003trace.cc b/subx/003trace.cc
deleted file mode 100644
index 379077a9..00000000
--- a/subx/003trace.cc
+++ /dev/null
@@ -1,520 +0,0 @@
-//: The goal of layers is to make programs more easy to understand and more
-//: malleable, easy to rewrite in radical ways without accidentally breaking
-//: some corner case. Tests further both goals. They help understandability by
-//: letting one make small changes and get feedback. What if I wrote this line
-//: like so? What if I removed this function call, is it really necessary?
-//: Just try it, see if the tests pass. Want to explore rewriting this bit in
-//: this way? Tests put many refactorings on a firmer footing.
-//:
-//: But the usual way we write tests seems incomplete. Refactorings tend to
-//: work in the small, but don't help with changes to function boundaries. If
-//: you want to extract a new function you have to manually test-drive it to
-//: create tests for it. If you want to inline a function its tests are no
-//: longer valid. In both cases you end up having to reorganize code as well as
-//: tests, an error-prone activity.
-//:
-//: In response, this layer introduces the notion of domain-driven *white-box*
-//: testing. We focus on the domain of inputs the whole program needs to
-//: handle rather than the correctness of individual functions. All white-box
-//: tests invoke the program in a single way: by calling run() with some
-//: input. As the program operates on the input, it traces out a list of
-//: _facts_ deduced about the domain:
-//:   trace("label") << "fact 1: " << val;
-//:
-//: Tests can now check for these facts in the trace:
-//:   CHECK_TRACE_CONTENTS("label", "fact 1: 34\n"
-//:                                 "fact 2: 35\n");
-//:
-//: Since we never call anything but the run() function directly, we never have
-//: to rewrite the tests when we reorganize the internals of the program. We
-//: just have to make sure our rewrite deduces the same facts about the domain,
-//: and that's something we're going to have to do anyway.
-//:
-//: To avoid the combinatorial explosion of integration tests, each layer
-//: mainly logs facts to the trace with a common *label*. All tests in a layer
-//: tend to check facts with this label. Validating the facts logged with a
-//: specific label is like calling functions of that layer directly.
-//:
-//: To build robust tests, trace facts about your domain rather than details of
-//: how you computed them.
-//:
-//: More details: http://akkartik.name/blog/tracing-tests
-//:
-//: ---
-//:
-//: Between layers and domain-driven testing, programming starts to look like a
-//: fundamentally different activity. Instead of focusing on a) superficial,
-//: b) local rules on c) code [like say http://blog.bbv.ch/2013/06/05/clean-code-cheat-sheet],
-//: we allow programmers to engage with the a) deep, b) global structure of
-//: the c) domain. If you can systematically track discontinuities in the
-//: domain, you don't care if the code used gotos as long as it passed all
-//: tests. If tests become more robust to run, it becomes easier to try out
-//: radically different implementations for the same program. If code is
-//: super-easy to rewrite, it becomes less important what indentation style it
-//: uses, or that the objects are appropriately encapsulated, or that the
-//: functions are referentially transparent.
-//:
-//: Instead of plumbing, programming becomes building and gradually refining a
-//: map of the environment the program must operate under. Whether a program
-//: is 'correct' at a given point in time is a red herring; what matters is
-//: avoiding regression by monotonically nailing down the more 'eventful'
-//: parts of the terrain. It helps readers new and old, and rewards curiosity,
-//: to organize large programs in self-similar hierarchies of example tests
-//: colocated with the code that makes them work.
-//:
-//:   "Programming properly should be regarded as an activity by which
-//:   programmers form a mental model, rather than as production of a program."
-//:   -- Peter Naur (http://alistair.cockburn.us/ASD+book+extract%3A+%22Naur,+Ehn,+Musashi%22)
-
-//:: == Core data structures
-
-:(before "End Globals")
-trace_stream* Trace_stream = NULL;
-
-:(before "End Types")
-struct trace_stream {
-  vector<trace_line> past_lines;
-  // End trace_stream Fields
-
-  trace_stream() {
-    // End trace_stream Constructor
-  }
-  ~trace_stream() {
-    // End trace_stream Destructor
-  }
-  // End trace_stream Methods
-};
-
-//:: == Adding to the trace
-
-//: Top-level method is trace() which can be used like an ostream. Usage:
-//:   trace(depth, label) << ... << end();
-//: Don't forget the 'end()' to actually append to the trace.
-:(before "End Includes")
-// No brackets around the expansion so that it prints nothing if Trace_stream
-// isn't initialized.
-#define trace(...)  !Trace_stream ? cerr : Trace_stream->stream(__VA_ARGS__)
-
-:(before "End trace_stream Fields")
-// accumulator for current trace_line
-ostringstream* curr_stream;
-string curr_label;
-int curr_depth;
-// other stuff
-int collect_depth;  // avoid tracing lower levels for speed
-ofstream null_stream;  // never opened, so writes to it silently fail
-
-//: Some constants.
-:(before "struct trace_stream")  // include constants in all cleaved compilation units
-const int Max_depth = 9999;
-:(before "End trace_stream Constructor")
-curr_stream = NULL;
-curr_depth = Max_depth;
-collect_depth = Max_depth;
-
-:(before "struct trace_stream")
-struct trace_line {
-  string contents;
-  string label;
-  int depth;  // 0 is 'sea level'; positive integers are progressively 'deeper' and lower level
-  trace_line(string c, string l) {
-    contents = c;
-    label = l;
-    depth = 0;
-  }
-  trace_line(string c, string l, int d) {
-    contents = c;
-    label = l;
-    depth = d;
-  }
-};
-
-string unescape_newline(string& s) {
-  std::stringstream ss;
-  for (int i = 0;  i < SIZE(s);  ++i) {
-    if (s.at(i) == '\n')
-      ss << "\\n";
-    else
-      ss << s.at(i);
-  }
-  return ss.str();
-}
-
-void dump_trace_line(ostream& s, trace_line& t) {
-  s << std::setw(4) << t.depth << ' ' << t.label << ": " << unescape_newline(t.contents) << '\n';
-}
-
-//: Starting a new trace line.
-:(before "End trace_stream Methods")
-ostream& stream(string label) {
-  return stream(Max_depth, label);
-}
-
-ostream& stream(int depth, string label) {
-  if (depth > collect_depth) return null_stream;
-  curr_stream = new ostringstream;
-  curr_label = label;
-  curr_depth = depth;
-  (*curr_stream) << std::hex;  // printing addresses is the common case
-  return *curr_stream;
-}
-
-//: End of a trace line; append it to the trace.
-:(before "End Types")
-struct end {};
-:(code)
-ostream& operator<<(ostream& os, end /*unused*/) {
-  if (Trace_stream) Trace_stream->newline();
-  return os;
-}
-
-//: Fatal error.
-:(before "End Types")
-struct die {};
-:(code)
-ostream& operator<<(ostream& /*unused*/, die /*unused*/) {
-  if (Trace_stream) Trace_stream->newline();
-  exit(1);
-}
-
-:(before "End trace_stream Methods")
-void newline();
-:(code)
-void trace_stream::newline() {
-  if (!curr_stream) return;
-  string curr_contents = curr_stream->str();
-  if (!curr_contents.empty()) {
-    past_lines.push_back(trace_line(curr_contents, trim(curr_label), curr_depth));  // preserve indent in contents
-    // maybe incrementally dump trace
-    trace_line& t = past_lines.back();
-    if (should_incrementally_print_trace()) {
-      dump_trace_line(cerr, t);
-    }
-    // End trace Commit
-  }
-
-  // clean up
-  delete curr_stream;
-  curr_stream = NULL;
-  curr_label.clear();
-  curr_depth = Max_depth;
-}
-
-//:: == Initializing the trace in tests
-
-:(before "End Includes")
-#define START_TRACING_UNTIL_END_OF_SCOPE  lease_tracer leased_tracer;
-:(before "End Test Setup")
-START_TRACING_UNTIL_END_OF_SCOPE
-
-//: Trace_stream is a resource, lease_tracer uses RAII to manage it.
-:(before "End Types")
-struct lease_tracer {
-  lease_tracer();
-  ~lease_tracer();
-};
-:(code)
-lease_tracer::lease_tracer() { Trace_stream = new trace_stream; }
-lease_tracer::~lease_tracer() {
-  delete Trace_stream;
-  Trace_stream = NULL;
-}
-
-//:: == Errors and warnings using traces
-
-:(before "End Includes")
-#define raise  (!Trace_stream ? (++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error"))
-#define warn (!Trace_stream ? (++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Warn_depth, "warn"))
-
-//: Print errors and warnings to the screen by default.
-:(before "struct trace_stream")  // include constants in all cleaved compilation units
-const int Error_depth = 0;
-const int Warn_depth = 1;
-:(before "End Globals")
-int Hide_errors = false;  // if set, don't print errors or warnings to screen
-int Hide_warnings = false;  // if set, don't print warnings to screen
-:(before "End Reset")
-Hide_errors = false;
-Hide_warnings = false;
-//: Never dump warnings in tests
-:(before "End Test Setup")
-Hide_warnings = true;
-:(code)
-bool trace_stream::should_incrementally_print_trace() {
-  if (!Hide_errors && curr_depth == Error_depth) return true;
-  if (!Hide_warnings && !Hide_errors && curr_depth == Warn_depth) return true;
-  // End Incremental Trace Print Conditions
-  return false;
-}
-:(before "End trace_stream Methods")
-bool should_incrementally_print_trace();
-
-:(before "End Globals")
-int Trace_errors = 0;  // used only when Trace_stream is NULL
-
-// Fail tests that displayed (unexpected) errors.
-// Expected errors should always be hidden and silently checked for.
-:(before "End Test Teardown")
-if (Passed && !Hide_errors && trace_contains_errors()) {
-  Passed = false;
-}
-:(code)
-bool trace_contains_errors() {
-  return Trace_errors > 0 || trace_count("error") > 0;
-}
-
-:(before "End Includes")
-// If we aren't yet sure how to deal with some corner case, use assert_for_now
-// to indicate that it isn't an inviolable invariant.
-#define assert_for_now assert
-#define raise_for_now raise
-
-//:: == Other assertions on traces
-//: Primitives:
-//:   - CHECK_TRACE_CONTENTS(lines)
-//:     Assert that the trace contains the given lines (separated by newlines)
-//:     in order. There can be other intervening lines between them.
-//:   - CHECK_TRACE_DOESNT_CONTAIN(line)
-//:   - CHECK_TRACE_DOESNT_CONTAIN(label, contents)
-//:     Assert that the trace doesn't contain the given (single) line.
-//:   - CHECK_TRACE_COUNT(label, count)
-//:     Assert that the trace contains exactly 'count' lines with the given
-//:     'label'.
-//:   - CHECK_TRACE_CONTAINS_ERRORS()
-//:   - CHECK_TRACE_DOESNT_CONTAIN_ERRORS()
-//:   - trace_count_prefix(label, prefix)
-//:     Count the number of trace lines with the given 'label' that start with
-//:     the given 'prefix'.
-
-:(before "End Includes")
-#define CHECK_TRACE_CONTENTS(...)  check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
-
-#define CHECK_TRACE_DOESNT_CONTAIN(...)  CHECK(trace_doesnt_contain(__VA_ARGS__))
-
-#define CHECK_TRACE_COUNT(label, count) \
-  if (Passed && trace_count(label) != (count)) { \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): trace_count of " << label << " should be " << count << '\n'; \
-    cerr << "  got " << trace_count(label) << '\n';  /* multiple eval */ \
-    DUMP(label); \
-    Passed = false; \
-    return;  /* Currently we stop at the very first failure. */ \
-  }
-
-#define CHECK_TRACE_CONTAINS_ERRORS()  CHECK(trace_contains_errors())
-#define CHECK_TRACE_DOESNT_CONTAIN_ERRORS() \
-  if (Passed && trace_contains_errors()) { \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \
-    DUMP("error"); \
-    Passed = false; \
-    return; \
-  }
-
-// Allow tests to ignore trace lines generated during setup.
-#define CLEAR_TRACE  delete Trace_stream, Trace_stream = new trace_stream
-
-:(code)
-bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) {
-  if (!Passed) return false;
-  if (!Trace_stream) return false;
-  vector<string> expected_lines = split(expected, "\n");
-  int curr_expected_line = 0;
-  while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty())
-    ++curr_expected_line;
-  if (curr_expected_line == SIZE(expected_lines)) return true;
-  string label, contents;
-  split_label_contents(expected_lines.at(curr_expected_line), &label, &contents);
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
-    if (label != p->label) continue;
-    string t = trim(p->contents);
-    if (contents != unescape_newline(t)) continue;
-    ++curr_expected_line;
-    while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty())
-      ++curr_expected_line;
-    if (curr_expected_line == SIZE(expected_lines)) return true;
-    split_label_contents(expected_lines.at(curr_expected_line), &label, &contents);
-  }
-
-  if (line_exists_anywhere(label, contents)) {
-    cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): line [" << label << ": " << contents << "] out of order in trace:\n";
-    DUMP("");
-  }
-  else {
-    cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << contents << "] in trace:\n";
-    DUMP(label);
-  }
-  Passed = false;
-  return false;
-}
-
-bool trace_doesnt_contain(string expected) {
-  vector<string> tmp = split_first(expected, ": ");
-  if (SIZE(tmp) == 1) {
-    raise << expected << ": missing label or contents in trace line\n" << end();
-    assert(false);
-  }
-  return trace_count(tmp.at(0), tmp.at(1)) == 0;
-}
-
-int trace_count(string label) {
-  return trace_count(label, "");
-}
-
-int trace_count(string label, string line) {
-  if (!Trace_stream) return 0;
-  long result = 0;
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
-    if (label == p->label) {
-      if (line == "" || trim(line) == trim(p->contents))
-        ++result;
-    }
-  }
-  return result;
-}
-
-int trace_count_prefix(string label, string prefix) {
-  if (!Trace_stream) return 0;
-  long result = 0;
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
-    if (label == p->label) {
-      if (starts_with(trim(p->contents), trim(prefix)))
-        ++result;
-    }
-  }
-  return result;
-}
-
-void split_label_contents(const string& s, string* label, string* contents) {
-  static const string delim(": ");
-  size_t pos = s.find(delim);
-  if (pos == string::npos) {
-    *label = "";
-    *contents = trim(s);
-  }
-  else {
-    *label = trim(s.substr(0, pos));
-    *contents = trim(s.substr(pos+SIZE(delim)));
-  }
-}
-
-bool line_exists_anywhere(const string& label, const string& contents) {
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
-    if (label != p->label) continue;
-    if (contents == trim(p->contents)) return true;
-  }
-  return false;
-}
-
-vector<string> split(string s, string delim) {
-  vector<string> result;
-  size_t begin=0, end=s.find(delim);
-  while (true) {
-    if (end == string::npos) {
-      result.push_back(string(s, begin, string::npos));
-      break;
-    }
-    result.push_back(string(s, begin, end-begin));
-    begin = end+SIZE(delim);
-    end = s.find(delim, begin);
-  }
-  return result;
-}
-
-vector<string> split_first(string s, string delim) {
-  vector<string> result;
-  size_t end=s.find(delim);
-  result.push_back(string(s, 0, end));
-  if (end != string::npos)
-    result.push_back(string(s, end+SIZE(delim), string::npos));
-  return result;
-}
-
-//:: == Helpers for debugging using traces
-
-:(before "End Includes")
-// To debug why a test is failing, dump its trace using '?'.
-#define DUMP(label)  if (Trace_stream) cerr << Trace_stream->readable_contents(label);
-
-// To add temporary prints to the trace, use 'dbg'.
-// `git log` should never show any calls to 'dbg'.
-#define dbg trace(0, "a")
-
-//: Dump the entire trace to file where it can be browsed offline.
-//: Dump the trace as it happens; that way you get something even if the
-//: program crashes.
-
-:(before "End Globals")
-ofstream Trace_file;
-:(before "End Commandline Options(*arg)")
-else if (is_equal(*arg, "--trace")) {
-  cerr << "saving trace to 'last_run'\n";
-  Trace_file.open("last_run");
-  // Add a dummy line up top; otherwise the `browse_trace` tool currently has
-  // no way to expand any lines above an error.
-  Trace_file << "   0 dummy: start\n";
-}
-:(before "End trace Commit")
-if (Trace_file) {
-  dump_trace_line(Trace_file, t);
-}
-:(before "End One-time Setup")
-atexit(cleanup_main);
-:(code)
-void cleanup_main() {
-  if (Trace_file) Trace_file.close();
-  // End cleanup_main
-}
-
-:(before "End trace_stream Methods")
-string readable_contents(string label) {
-  string trim(const string& s);  // prototype
-  ostringstream output;
-  label = trim(label);
-  for (vector<trace_line>::iterator p = past_lines.begin();  p != past_lines.end();  ++p)
-    if (label.empty() || label == p->label)
-      dump_trace_line(output, *p);
-  return output.str();
-}
-
-//: Print traces to the screen as they happen.
-//: Particularly useful when juggling multiple trace streams, like when
-//: debugging sandboxes.
-:(before "End Globals")
-bool Dump_trace = false;
-:(before "End Commandline Options(*arg)")
-else if (is_equal(*arg, "--dump")) {
-  Dump_trace = true;
-}
-:(before "End Incremental Trace Print Conditions")
-if (Dump_trace) return true;
-
-//: Miscellaneous helpers.
-
-:(code)
-string trim(const string& s) {
-  string::const_iterator first = s.begin();
-  while (first != s.end() && isspace(*first))
-    ++first;
-  if (first == s.end()) return "";
-
-  string::const_iterator last = --s.end();
-  while (last != s.begin() && isspace(*last))
-    --last;
-  ++last;
-  return string(first, last);
-}
-
-:(before "End Includes")
-#include <vector>
-using std::vector;
-#include <list>
-using std::list;
-#include <set>
-using std::set;
-
-#include <sstream>
-using std::istringstream;
-using std::ostringstream;
-
-#include <fstream>
-using std::ifstream;
-using std::ofstream;
diff --git a/subx/003trace.test.cc b/subx/003trace.test.cc
deleted file mode 100644
index bec1b789..00000000
--- a/subx/003trace.test.cc
+++ /dev/null
@@ -1,133 +0,0 @@
-void test_trace_check_compares() {
-  trace("test layer") << "foo" << end();
-  CHECK_TRACE_CONTENTS("test layer: foo");
-}
-
-void test_trace_check_ignores_other_layers() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 2") << "bar" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: foo");
-  CHECK_TRACE_DOESNT_CONTAIN("test layer 2: foo");
-}
-
-void test_trace_check_ignores_leading_whitespace() {
-  trace("test layer 1") << " foo" << end();
-  CHECK_EQ(trace_count("test layer 1", /*too little whitespace*/"foo"), 1);
-  CHECK_EQ(trace_count("test layer 1", /*too much whitespace*/"  foo"), 1);
-}
-
-void test_trace_check_ignores_other_lines() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 1") << "bar" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: foo");
-}
-
-void test_trace_check_ignores_other_lines2() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 1") << "bar" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: bar");
-}
-
-void test_trace_ignores_trailing_whitespace() {
-  trace("test layer 1") << "foo\n" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: foo");
-}
-
-void test_trace_ignores_trailing_whitespace2() {
-  trace("test layer 1") << "foo " << end();
-  CHECK_TRACE_CONTENTS("test layer 1: foo");
-}
-
-void test_trace_orders_across_layers() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 2") << "bar" << end();
-  trace("test layer 1") << "qux" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: foo\n"
-                       "test layer 2: bar\n"
-                       "test layer 1: qux\n");
-}
-
-void test_trace_supports_count() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 1") << "foo" << end();
-  CHECK_EQ(trace_count("test layer 1", "foo"), 2);
-}
-
-void test_trace_supports_count2() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 1") << "bar" << end();
-  CHECK_EQ(trace_count("test layer 1"), 2);
-}
-
-void test_trace_count_ignores_trailing_whitespace() {
-  trace("test layer 1") << "foo\n" << end();
-  CHECK_EQ(trace_count("test layer 1", "foo"), 1);
-}
-
-void test_trace_unescapes_newlines() {
-  trace("test layer 1") << "f\no\no\n" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: f\\no\\no");
-}
-
-// pending: DUMP tests
-// pending: readable_contents() adds newline if necessary.
-// pending: raise also prints to stderr.
-// pending: raise doesn't print to stderr if Hide_errors is set.
-// pending: warn doesn't print to stderr if Hide_errors is set.
-// pending: warn doesn't print to stderr if Hide_warnings is set.
-// pending: raise doesn't have to be saved if Hide_errors is set, just printed.
-// pending: raise prints to stderr if Trace_stream is NULL.
-// pending: raise prints to stderr if Trace_stream is NULL even if Hide_errors is set.
-
-// can't check trace because trace methods call 'split'
-
-void test_split_returns_at_least_one_elem() {
-  vector<string> result = split("", ",");
-  CHECK_EQ(result.size(), 1);
-  CHECK_EQ(result.at(0), "");
-}
-
-void test_split_returns_entire_input_when_no_delim() {
-  vector<string> result = split("abc", ",");
-  CHECK_EQ(result.size(), 1);
-  CHECK_EQ(result.at(0), "abc");
-}
-
-void test_split_works() {
-  vector<string> result = split("abc,def", ",");
-  CHECK_EQ(result.size(), 2);
-  CHECK_EQ(result.at(0), "abc");
-  CHECK_EQ(result.at(1), "def");
-}
-
-void test_split_works2() {
-  vector<string> result = split("abc,def,ghi", ",");
-  CHECK_EQ(result.size(), 3);
-  CHECK_EQ(result.at(0), "abc");
-  CHECK_EQ(result.at(1), "def");
-  CHECK_EQ(result.at(2), "ghi");
-}
-
-void test_split_handles_multichar_delim() {
-  vector<string> result = split("abc,,def,,ghi", ",,");
-  CHECK_EQ(result.size(), 3);
-  CHECK_EQ(result.at(0), "abc");
-  CHECK_EQ(result.at(1), "def");
-  CHECK_EQ(result.at(2), "ghi");
-}
-
-void test_trim() {
-  CHECK_EQ(trim(""), "");
-  CHECK_EQ(trim(" "), "");
-  CHECK_EQ(trim("  "), "");
-  CHECK_EQ(trim("a"), "a");
-  CHECK_EQ(trim(" a"), "a");
-  CHECK_EQ(trim("  a"), "a");
-  CHECK_EQ(trim("  ab"), "ab");
-  CHECK_EQ(trim("a "), "a");
-  CHECK_EQ(trim("a  "), "a");
-  CHECK_EQ(trim("ab  "), "ab");
-  CHECK_EQ(trim(" a "), "a");
-  CHECK_EQ(trim("  a  "), "a");
-  CHECK_EQ(trim("  ab  "), "ab");
-}
diff --git a/subx/010---vm.cc b/subx/010---vm.cc
deleted file mode 100644
index 3d60ab5c..00000000
--- a/subx/010---vm.cc
+++ /dev/null
@@ -1,401 +0,0 @@
-//: Core data structures for simulating the SubX VM (subset of an x86 processor)
-//:
-//: At the lowest level ("level 1") of abstraction, SubX executes x86
-//: instructions provided in the form of an array of bytes, loaded into memory
-//: starting at a specific address.
-
-//:: registers
-//: assume segment registers are hard-coded to 0
-//: no floating-point, MMX, etc. yet
-
-:(before "End Types")
-enum {
-  EAX,
-  ECX,
-  EDX,
-  EBX,
-  ESP,
-  EBP,
-  ESI,
-  EDI,
-  NUM_INT_REGISTERS,
-};
-union reg {
-  int32_t i;
-  uint32_t u;
-};
-:(before "End Globals")
-reg Reg[NUM_INT_REGISTERS] = { {0} };
-uint32_t EIP = 1;  // preserve null pointer
-:(before "End Reset")
-bzero(Reg, sizeof(Reg));
-EIP = 1;  // preserve null pointer
-
-:(before "End Help Contents")
-cerr << "  registers\n";
-:(before "End Help Texts")
-put_new(Help, "registers",
-  "SubX currently supports eight 32-bit integer registers. From 0 to 7, they are:\n"
-  "  EAX ECX EDX EBX ESP EBP ESI EDI\n"
-  "ESP contains the top of the stack.\n"
-  "\n"
-  "-- 8-bit registers\n"
-  "Some instructions operate on eight *overlapping* 8-bit registers.\n"
-  "From 0 to 7, they are:\n"
-  "  AL CL DL BL AH CH DH BH\n"
-  "The 8-bit registers overlap with the 32-bit ones. AL is the lowest signicant byte\n"
-  "of EAX, AH is the second lowest significant byte, and so on.\n"
-  "\n"
-  "For example, if EBX contains 0x11223344, then BL contains 0x44, and BH contains 0x33.\n"
-  "\n"
-  "There is no way to access bytes within ESP, EBP, ESI or EDI.\n"
-  "\n"
-  "For complete details consult the IA-32 software developer's manual, volume 2,\n"
-  "table 2-2, \"32-bit addressing forms with the ModR/M byte\".\n"
-  "It is included in this repository as 'modrm.pdf'.\n"
-  "The register encodings are described in the top row of the table, but you'll need\n"
-  "to spend some time with it.\n"
-  "\n"
-  "-- flag registers\n"
-  "Various instructions (particularly 'compare') modify one or more of four 1-bit\n"
-  "'flag' registers, as a side-effect:\n"
-  "- the sign flag (SF): usually set if an arithmetic result is negative, or\n"
-  "  reset if not.\n"
-  "- the zero flag (ZF): usually set if a result is zero, or reset if not.\n"
-  "- the carry flag (CF): usually set if an arithmetic result overflows by just one bit.\n"
-  "  Useful for operating on unsigned numbers.\n"
-  "- the overflow flag (OF): usually set if an arithmetic result overflows by more\n"
-  "  than one bit. Useful for operating on signed numbers.\n"
-  "The flag bits are read by conditional jumps.\n"
-  "\n"
-  "For complete details on how different instructions update the flags, consult the IA-32\n"
-  "manual (volume 2). There's various versions of it online, such as https://c9x.me/x86,\n"
-  "though of course you'll need to be careful to ignore instructions and flag registers\n"
-  "that SubX doesn't support.\n"
-  "\n"
-  "It isn't simple, but if this is the processor you have running on your computer.\n"
-  "Might as well get good at it.\n"
-);
-
-:(before "End Globals")
-// the subset of x86 flag registers we care about
-bool SF = false;  // sign flag
-bool ZF = false;  // zero flag
-bool CF = false;  // carry flag
-bool OF = false;  // overflow flag
-:(before "End Reset")
-SF = ZF = CF = OF = false;
-
-//:: simulated RAM
-
-:(before "End Types")
-const uint32_t SEGMENT_ALIGNMENT = 0x1000000;  // 16MB
-inline uint32_t align_upwards(uint32_t x, uint32_t align) {
-  return (x+align-1) & -(align);
-}
-
-// Like in real-world Linux, we'll allocate RAM for our programs in disjoint
-// slabs called VMAs or Virtual Memory Areas.
-struct vma {
-  uint32_t start;  // inclusive
-  uint32_t end;  // exclusive
-  vector<uint8_t> _data;
-  vma(uint32_t s, uint32_t e) :start(s), end(e) {}
-  vma(uint32_t s) :start(s), end(align_upwards(s+1, SEGMENT_ALIGNMENT)) {}
-  bool match(uint32_t a) {
-    return a >= start && a < end;
-  }
-  bool match32(uint32_t a) {
-    return a >= start && a+4 <= end;
-  }
-  uint8_t& data(uint32_t a) {
-    assert(match(a));
-    uint32_t result_index = a-start;
-    if (_data.size() <= result_index) {
-      const int align = 0x1000;
-      uint32_t result_size = result_index + 1;  // size needed for result_index to be valid
-      uint32_t new_size = align_upwards(result_size, align);
-      // grow at least 2x to maintain some amortized complexity guarantees
-      if (new_size < _data.size() * 2)
-        new_size = _data.size() * 2;
-      // never grow past the stated limit
-      if (new_size > end-start)
-        new_size = end-start;
-      _data.resize(new_size);
-    }
-    return _data.at(result_index);
-  }
-  void grow_until(uint32_t new_end_address) {
-    if (new_end_address < end) return;
-    // Ugly: vma knows about the global Memory list of vmas
-    void sanity_check(uint32_t start, uint32_t end);
-    sanity_check(start, new_end_address);
-    end = new_end_address;
-  }
-  // End vma Methods
-};
-:(code)
-void sanity_check(uint32_t start, uint32_t end) {
-  bool dup_found = false;
-  for (int i = 0;  i < SIZE(Mem);  ++i) {
-    const vma& curr = Mem.at(i);
-    if (curr.start == start) {
-      assert(!dup_found);
-      dup_found = true;
-    }
-    else if (curr.start > start) {
-      assert(curr.start > end);
-    }
-    else if (curr.start < start) {
-      assert(curr.end < start);
-    }
-  }
-}
-
-:(before "End Globals")
-// RAM is made of VMAs.
-vector<vma> Mem;
-:(code)
-:(before "End Globals")
-uint32_t End_of_program = 0;  // when the program executes past this address in tests we'll stop the test
-// The stack grows downward. Can't increase its size for now.
-:(before "End Reset")
-Mem.clear();
-End_of_program = 0;
-:(code)
-// These helpers depend on Mem being laid out contiguously (so you can't use a
-// map, etc.) and on the host also being little-endian.
-inline uint8_t read_mem_u8(uint32_t addr) {
-  uint8_t* handle = mem_addr_u8(addr);  // error messages get printed here
-  return handle ? *handle : 0;
-}
-inline int8_t read_mem_i8(uint32_t addr) {
-  return static_cast<int8_t>(read_mem_u8(addr));
-}
-inline uint32_t read_mem_u32(uint32_t addr) {
-  uint32_t* handle = mem_addr_u32(addr);  // error messages get printed here
-  return handle ? *handle : 0;
-}
-inline int32_t read_mem_i32(uint32_t addr) {
-  return static_cast<int32_t>(read_mem_u32(addr));
-}
-
-inline uint8_t* mem_addr_u8(uint32_t addr) {
-  uint8_t* result = NULL;
-  for (int i = 0;  i < SIZE(Mem);  ++i) {
-    if (Mem.at(i).match(addr)) {
-      if (result)
-        raise << "address 0x" << HEXWORD << addr << " is in two segments\n" << end();
-      result = &Mem.at(i).data(addr);
-    }
-  }
-  if (result == NULL) {
-    if (Trace_file) Trace_file.flush();
-    raise << "Tried to access uninitialized memory at address 0x" << HEXWORD << addr << '\n' << end();
-    exit(1);
-  }
-  return result;
-}
-inline int8_t* mem_addr_i8(uint32_t addr) {
-  return reinterpret_cast<int8_t*>(mem_addr_u8(addr));
-}
-inline uint32_t* mem_addr_u32(uint32_t addr) {
-  uint32_t* result = NULL;
-  for (int i = 0;  i < SIZE(Mem);  ++i) {
-    if (Mem.at(i).match32(addr)) {
-      if (result)
-        raise << "address 0x" << HEXWORD << addr << " is in two segments\n" << end();
-      result = reinterpret_cast<uint32_t*>(&Mem.at(i).data(addr));
-    }
-  }
-  if (result == NULL) {
-    if (Trace_file) Trace_file.flush();
-    raise << "Tried to access uninitialized memory at address 0x" << HEXWORD << addr << '\n' << end();
-    raise << "The entire 4-byte word should be initialized and lie in a single segment.\n" << end();
-    exit(1);
-  }
-  return result;
-}
-inline int32_t* mem_addr_i32(uint32_t addr) {
-  return reinterpret_cast<int32_t*>(mem_addr_u32(addr));
-}
-// helper for some syscalls. But read-only.
-inline const char* mem_addr_kernel_string(uint32_t addr) {
-  return reinterpret_cast<const char*>(mem_addr_u8(addr));
-}
-inline string mem_addr_string(uint32_t addr, uint32_t size) {
-  ostringstream out;
-  for (size_t i = 0;  i < size;  ++i)
-    out << read_mem_u8(addr+i);
-  return out.str();
-}
-
-
-inline void write_mem_u8(uint32_t addr, uint8_t val) {
-  uint8_t* handle = mem_addr_u8(addr);
-  if (handle != NULL) *handle = val;
-}
-inline void write_mem_i8(uint32_t addr, int8_t val) {
-  int8_t* handle = mem_addr_i8(addr);
-  if (handle != NULL) *handle = val;
-}
-inline void write_mem_u32(uint32_t addr, uint32_t val) {
-  uint32_t* handle = mem_addr_u32(addr);
-  if (handle != NULL) *handle = val;
-}
-inline void write_mem_i32(uint32_t addr, int32_t val) {
-  int32_t* handle = mem_addr_i32(addr);
-  if (handle != NULL) *handle = val;
-}
-
-inline bool already_allocated(uint32_t addr) {
-  bool result = false;
-  for (int i = 0;  i < SIZE(Mem);  ++i) {
-    if (Mem.at(i).match(addr)) {
-      if (result)
-        raise << "address 0x" << HEXWORD << addr << " is in two segments\n" << end();
-      result = true;
-    }
-  }
-  return result;
-}
-
-//:: core interpreter loop
-
-:(code)
-// skeleton of how x86 instructions are decoded
-void run_one_instruction() {
-  uint8_t op=0, op2=0, op3=0;
-  // Run One Instruction
-  if (Trace_file) {
-    dump_registers();
-    // End Dump Info for Instruction
-  }
-  uint32_t inst_start_address = EIP;
-  op = next();
-  trace(Callstack_depth+1, "run") << "0x" << HEXWORD << inst_start_address << " opcode: " << HEXBYTE << NUM(op) << end();
-  switch (op) {
-  case 0xf4:  // hlt
-    EIP = End_of_program;
-    break;
-  // End Single-Byte Opcodes
-  case 0x0f:
-    switch(op2 = next()) {
-    // End Two-Byte Opcodes Starting With 0f
-    default:
-      cerr << "unrecognized second opcode after 0f: " << HEXBYTE << NUM(op2) << '\n';
-      exit(1);
-    }
-    break;
-  case 0xf2:
-    switch(op2 = next()) {
-    // End Two-Byte Opcodes Starting With f2
-    case 0x0f:
-      switch(op3 = next()) {
-      // End Three-Byte Opcodes Starting With f2 0f
-      default:
-        cerr << "unrecognized third opcode after f2 0f: " << HEXBYTE << NUM(op3) << '\n';
-        exit(1);
-      }
-      break;
-    default:
-      cerr << "unrecognized second opcode after f2: " << HEXBYTE << NUM(op2) << '\n';
-      exit(1);
-    }
-    break;
-  case 0xf3:
-    switch(op2 = next()) {
-    // End Two-Byte Opcodes Starting With f3
-    case 0x0f:
-      switch(op3 = next()) {
-      // End Three-Byte Opcodes Starting With f3 0f
-      default:
-        cerr << "unrecognized third opcode after f3 0f: " << HEXBYTE << NUM(op3) << '\n';
-        exit(1);
-      }
-      break;
-    default:
-      cerr << "unrecognized second opcode after f3: " << HEXBYTE << NUM(op2) << '\n';
-      exit(1);
-    }
-    break;
-  default:
-    cerr << "unrecognized opcode: " << HEXBYTE << NUM(op) << '\n';
-    exit(1);
-  }
-}
-
-inline uint8_t next() {
-  return read_mem_u8(EIP++);
-}
-
-void dump_registers() {
-  ostringstream out;
-  out << "registers before: ";
-  for (int i = 0;  i < NUM_INT_REGISTERS;  ++i) {
-    if (i > 0) out << "; ";
-    out << "  " << i << ": " << std::hex << std::setw(8) << std::setfill('_') << Reg[i].u;
-  }
-  out << " -- SF: " << SF << "; ZF: " << ZF << "; CF: " << CF << "; OF: " << OF;
-  trace(Callstack_depth+1, "run") << out.str() << end();
-}
-
-//: start tracking supported opcodes
-:(before "End Globals")
-map</*op*/string, string> Name;
-map</*op*/string, string> Name_0f;
-map</*op*/string, string> Name_f3;
-map</*op*/string, string> Name_f3_0f;
-:(before "End One-time Setup")
-init_op_names();
-:(code)
-void init_op_names() {
-  put(Name, "f4", "halt (hlt)");
-  // End Initialize Op Names
-}
-
-:(before "End Help Special-cases(key)")
-if (key == "opcodes") {
-  cerr << "Opcodes currently supported by SubX:\n";
-  for (map<string, string>::iterator p = Name.begin();  p != Name.end();  ++p)
-    cerr << "  " << p->first << ": " << p->second << '\n';
-  for (map<string, string>::iterator p = Name_0f.begin();  p != Name_0f.end();  ++p)
-    cerr << "  0f " << p->first << ": " << p->second << '\n';
-  for (map<string, string>::iterator p = Name_f3.begin();  p != Name_f3.end();  ++p)
-    cerr << "  f3 " << p->first << ": " << p->second << '\n';
-  for (map<string, string>::iterator p = Name_f3_0f.begin();  p != Name_f3_0f.end();  ++p)
-    cerr << "  f3 0f " << p->first << ": " << p->second << '\n';
-  cerr << "Run `subx help instructions` for details on words like 'r32' and 'disp8'.\n"
-          "For complete details on these instructions, consult the IA-32 manual (volume 2).\n"
-          "There's various versions of it online, such as https://c9x.me/x86.\n"
-          "The mnemonics in brackets will help you locate each instruction.\n";
-  return 0;
-}
-:(before "End Help Contents")
-cerr << "  opcodes\n";
-
-//: Helpers for managing trace depths
-//:
-//: We're going to use trace depths primarily to segment code running at
-//: different frames of the call stack. This will make it easy for the trace
-//: browser to collapse over entire calls.
-//:
-//: Errors will be at depth 0.
-//: Warnings will be at depth 1.
-//: SubX instructions will occupy depth 2 and up to Max_depth, organized by
-//: stack frames. Each instruction's internal details will be one level deeper
-//: than its 'main' depth. So 'call' instruction details will be at the same
-//: depth as the instructions of the function it calls.
-:(before "End Globals")
-extern const int Initial_callstack_depth = 2;
-int Callstack_depth = Initial_callstack_depth;
-:(before "End Reset")
-Callstack_depth = Initial_callstack_depth;
-
-:(before "End Includes")
-#include <iomanip>
-#define HEXBYTE  std::hex << std::setw(2) << std::setfill('0')
-#define HEXWORD  std::hex << std::setw(8) << std::setfill('0')
-// ugly that iostream doesn't print uint8_t as an integer
-#define NUM(X) static_cast<int>(X)
-#include <stdint.h>
diff --git a/subx/011run.cc b/subx/011run.cc
deleted file mode 100644
index 194676d8..00000000
--- a/subx/011run.cc
+++ /dev/null
@@ -1,467 +0,0 @@
-//: Running SubX programs on the VM.
-
-//: (Not to be confused with the 'run' subcommand for running ELF binaries on
-//: the VM. That comes later.)
-
-:(before "End Help Texts")
-put_new(Help, "syntax",
-  "SubX programs consist of segments, each segment in turn consisting of lines.\n"
-  "Line-endings are significant; each line should contain a single\n"
-  "instruction, macro or directive.\n"
-  "\n"
-  "Comments start with the '#' character. It should be at the start of a word\n"
-  "(start of line, or following a space).\n"
-  "\n"
-  "Each segment starts with a header line: a '==' delimiter followed by the name of\n"
-  "the segment and a (sometimes approximate) starting address in memory.\n"
-  "The name 'code' is special; instructions to execute should always go here.\n"
-  "\n"
-  "The resulting binary starts running code from a label called 'Entry'\n"
-  "in the code segment.\n"
-  "\n"
-  "Segments with the same name get merged together. This rule helps keep functions and\n"
-  "their data close together in .subx files.\n"
-  "You don't have to specify the starting address after the first time.\n"
-  "\n"
-  "Lines consist of a series of words. Words can contain arbitrary metadata\n"
-  "after a '/', but they can never contain whitespace. Metadata has no effect\n"
-  "at runtime, but can be handy when rewriting macros.\n"
-  "\n"
-  "Check out the examples in the examples/ directory.\n"
-);
-:(before "End Help Contents")
-cerr << "  syntax\n";
-
-:(code)
-void test_copy_imm32_to_EAX() {
-  // At the lowest level, SubX programs are a series of hex bytes, each
-  // (variable-length) instruction on one line.
-  run(
-      // Comments start with '#' and are ignored.
-      "# comment\n"
-      // Segment headers start with '==', a name and a starting hex address.
-      // There's usually one code and one data segment. The code segment
-      // always comes first.
-      "== code 0x1\n"  // code segment
-
-      // After the header, each segment consists of lines, and each line
-      // consists of words separated by whitespace.
-      //
-      // All words can have metadata after a '/'. No spaces allowed in
-      // metadata, of course.
-      // Unrecognized metadata never causes errors, so you can use it for
-      // documentation.
-      //
-      // Within the code segment in particular, x86 instructions consist of
-      // some number of the following parts and sub-parts (see the Readme and
-      // cheatsheet.pdf for details):
-      //   opcodes: 1-3 bytes
-      //   ModR/M byte
-      //   SIB byte
-      //   displacement: 0/1/2/4 bytes
-      //   immediate: 0/1/2/4 bytes
-      // opcode        ModR/M                    SIB                   displacement    immediate
-      // instruction   mod, reg, Reg/Mem bits    scale, index, base
-      // 1-3 bytes     0/1 byte                  0/1 byte              0/1/2/4 bytes   0/1/2/4 bytes
-      "  b8            .                         .                     .               0a 0b 0c 0d\n"  // copy 0x0d0c0b0a to EAX
-      // The periods are just to help the eye track long gaps between columns,
-      // and are otherwise ignored.
-  );
-  // This program, when run, causes the following events in the trace:
-  CHECK_TRACE_CONTENTS(
-      "load: 0x00000001 -> b8\n"
-      "load: 0x00000002 -> 0a\n"
-      "load: 0x00000003 -> 0b\n"
-      "load: 0x00000004 -> 0c\n"
-      "load: 0x00000005 -> 0d\n"
-      "run: copy imm32 0x0d0c0b0a to EAX\n"
-  );
-}
-
-// top-level helper for scenarios: parse the input, transform any macros, load
-// the final hex bytes into memory, run it
-void run(const string& text_bytes) {
-  program p;
-  istringstream in(text_bytes);
-  parse(in, p);
-  if (trace_contains_errors()) return;  // if any stage raises errors, stop immediately
-  transform(p);
-  if (trace_contains_errors()) return;
-  load(p);
-  if (trace_contains_errors()) return;
-  // convenience to keep tests concise: 'Entry' label need not be provided
-  // not allowed in real programs
-  if (p.entry)
-    EIP = p.entry;
-  else
-    EIP = find(p, "code")->start;
-  while (EIP < End_of_program)
-    run_one_instruction();
-}
-
-//:: core data structures
-
-:(before "End Types")
-struct program {
-  uint32_t entry;
-  vector<segment> segments;
-  program() { entry = 0; }
-};
-:(before "struct program")
-struct segment {
-  string name;
-  uint32_t start;
-  vector<line> lines;
-  // End segment Fields
-  segment() {
-    start = 0;
-    // End segment Constructor
-  }
-};
-:(before "struct segment")
-struct line {
-  vector<word> words;
-  vector<string> metadata;
-  string original;
-};
-:(before "struct line")
-struct word {
-  string original;
-  string data;
-  vector<string> metadata;
-};
-
-//:: parse
-
-:(code)
-void parse(istream& fin, program& out) {
-  segment* curr_segment = NULL;
-  vector<line> l;
-  while (has_data(fin)) {
-    string line_data;
-    line curr;
-    getline(fin, line_data);
-    curr.original = line_data;
-    trace(99, "parse") << "line: " << line_data << end();
-    // End Line Parsing Special-cases(line_data -> l)
-    istringstream lin(line_data);
-    while (has_data(lin)) {
-      string word_data;
-      lin >> word_data;
-      if (word_data.empty()) continue;
-      if (word_data[0] == '#') break;  // comment
-      if (word_data == ".") continue;  // comment token
-      if (word_data == "==") {
-        flush(curr_segment, l);
-        string segment_name;
-        lin >> segment_name;
-        curr_segment = find(out, segment_name);
-        if (curr_segment != NULL) {
-          trace(3, "parse") << "appending to segment '" << segment_name << "'" << end();
-        }
-        else {
-          trace(3, "parse") << "new segment '" << segment_name << "'" << end();
-          uint32_t seg_start = 0;
-          lin >> std::hex >> seg_start;
-          sanity_check_program_segment(out, seg_start);
-          out.segments.push_back(segment());
-          curr_segment = &out.segments.back();
-          curr_segment->name = segment_name;
-          curr_segment->start = seg_start;
-          if (trace_contains_errors()) continue;
-          trace(3, "parse") << "starts at address 0x" << HEXWORD << curr_segment->start << end();
-        }
-        break;  // skip rest of line
-      }
-      if (word_data[0] == ':') {
-        // todo: line metadata
-        break;
-      }
-      curr.words.push_back(word());
-      parse_word(word_data, curr.words.back());
-      trace(99, "parse") << "word: " << to_string(curr.words.back());
-    }
-    if (!curr.words.empty())
-      l.push_back(curr);
-  }
-  flush(curr_segment, l);
-  trace(99, "parse") << "done" << end();
-}
-
-segment* find(program& p, const string& segment_name) {
-  for (int i = 0;  i < SIZE(p.segments);  ++i) {
-    if (p.segments.at(i).name == segment_name)
-      return &p.segments.at(i);
-  }
-  return NULL;
-}
-
-void flush(segment* s, vector<line>& lines) {
-  if (lines.empty()) return;
-  if (s == NULL) {
-    raise << "input does not start with a '==' section header\n" << end();
-    return;
-  }
-  trace(3, "parse") << "flushing segment" << end();
-  s->lines.insert(s->lines.end(), lines.begin(), lines.end());
-  lines.clear();
-}
-
-void parse_word(const string& data, word& out) {
-  out.original = data;
-  istringstream win(data);
-  if (getline(win, out.data, '/')) {
-    string m;
-    while (getline(win, m, '/'))
-      out.metadata.push_back(m);
-  }
-}
-
-void sanity_check_program_segment(const program& p, uint32_t addr) {
-  for (int i = 0;  i < SIZE(p.segments);  ++i) {
-    if (p.segments.at(i).start == addr)
-      raise << "can't have multiple segments starting at address 0x" << HEXWORD << addr << '\n' << end();
-  }
-}
-
-// helper for tests
-void parse(const string& text_bytes) {
-  program p;
-  istringstream in(text_bytes);
-  parse(in, p);
-}
-
-void test_detect_duplicate_segments() {
-  Hide_errors = true;
-  parse(
-      "== segment1 0xee\n"
-      "ab\n"
-      "== segment2 0xee\n"
-      "cd\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: can't have multiple segments starting at address 0x000000ee\n"
-  );
-}
-
-//:: transform
-
-:(before "End Types")
-typedef void (*transform_fn)(program&);
-:(before "End Globals")
-vector<transform_fn> Transform;
-
-:(code)
-void transform(program& p) {
-  for (int t = 0;  t < SIZE(Transform);  ++t)
-    (*Transform.at(t))(p);
-}
-
-//:: load
-
-void load(const program& p) {
-  if (find(p, "code") == NULL) {
-    raise << "no code to run\n" << end();
-    return;
-  }
-  // Ensure segments are disjoint.
-  set<uint32_t> overlap;
-  for (int i = 0;   i < SIZE(p.segments);  ++i) {
-    const segment& seg = p.segments.at(i);
-    uint32_t addr = seg.start;
-    if (!already_allocated(addr))
-      Mem.push_back(vma(seg.start));
-    trace(99, "load") << "loading segment " << i << " from " << HEXWORD << addr << end();
-    for (int j = 0;  j < SIZE(seg.lines);  ++j) {
-      const line& l = seg.lines.at(j);
-      for (int k = 0;  k < SIZE(l.words);  ++k) {
-        const word& w = l.words.at(k);
-        uint8_t val = hex_byte(w.data);
-        if (trace_contains_errors()) return;
-        assert(overlap.find(addr) == overlap.end());
-        write_mem_u8(addr, val);
-        overlap.insert(addr);
-        trace(99, "load") << "0x" << HEXWORD << addr << " -> " << HEXBYTE << NUM(read_mem_u8(addr)) << end();
-        ++addr;
-      }
-    }
-    if (seg.name == "code") {
-      End_of_program = addr;
-    }
-  }
-}
-
-const segment* find(const program& p, const string& segment_name) {
-  for (int i = 0;  i < SIZE(p.segments);  ++i) {
-    if (p.segments.at(i).name == segment_name)
-      return &p.segments.at(i);
-  }
-  return NULL;
-}
-
-uint8_t hex_byte(const string& s) {
-  if (contains_uppercase(s)) {
-    raise << "uppercase hex not allowed: " << s << '\n' << end();
-    return 0;
-  }
-  istringstream in(s);
-  int result = 0;
-  in >> std::hex >> result;
-  if (!in || !in.eof()) {
-    raise << "token '" << s << "' is not a hex byte\n" << end();
-    return '\0';
-  }
-  if (result > 0xff || result < -0x8f) {
-    raise << "token '" << s << "' is not a hex byte\n" << end();
-    return '\0';
-  }
-  return static_cast<uint8_t>(result);
-}
-
-void test_number_too_large() {
-  Hide_errors = true;
-  parse_and_load(
-      "== code 0x1\n"
-      "01 cab\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: token 'cab' is not a hex byte\n"
-  );
-}
-
-void test_invalid_hex() {
-  Hide_errors = true;
-  parse_and_load(
-      "== code 0x1\n"
-      "01 cx\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: token 'cx' is not a hex byte\n"
-  );
-}
-
-void test_negative_number() {
-  parse_and_load(
-      "== code 0x1\n"
-      "01 -02\n"
-  );
-  CHECK_TRACE_COUNT("error", 0);
-}
-
-void test_negative_number_too_small() {
-  Hide_errors = true;
-  parse_and_load(
-      "== code 0x1\n"
-      "01 -12345\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: token '-12345' is not a hex byte\n"
-  );
-}
-
-void test_hex_prefix() {
-  parse_and_load(
-      "== code 0x1\n"
-      "0x01 -0x02\n"
-  );
-  CHECK_TRACE_COUNT("error", 0);
-}
-
-void test_repeated_segment_merges_data() {
-  parse_and_load(
-      "== code 0x1\n"
-      "11 22\n"
-      "== code\n"  // again
-      "33 44\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse: new segment 'code'\n"
-      "parse: appending to segment 'code'\n"
-      // first segment
-      "load: 0x00000001 -> 11\n"
-      "load: 0x00000002 -> 22\n"
-      // second segment
-      "load: 0x00000003 -> 33\n"
-      "load: 0x00000004 -> 44\n"
-  );
-}
-
-void test_error_on_missing_segment_header() {
-  Hide_errors = true;
-  parse_and_load(
-      "01 02\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: input does not start with a '==' section header\n"
-  );
-}
-
-void test_error_on_uppercase_hex() {
-  Hide_errors = true;
-  parse_and_load(
-      "== code\n"
-      "01 Ab\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: uppercase hex not allowed: Ab\n"
-  );
-}
-
-//: helper for tests
-void parse_and_load(const string& text_bytes) {
-  program p;
-  istringstream in(text_bytes);
-  parse(in, p);
-  if (trace_contains_errors()) return;  // if any stage raises errors, stop immediately
-  load(p);
-}
-
-//:: run
-
-:(before "End Initialize Op Names")
-put_new(Name, "b8", "copy imm32 to EAX (mov)");
-
-//: our first opcode
-
-:(before "End Single-Byte Opcodes")
-case 0xb8: {  // copy imm32 to EAX
-  const int32_t src = next32();
-  trace(Callstack_depth+1, "run") << "copy imm32 0x" << HEXWORD << src << " to EAX" << end();
-  Reg[EAX].i = src;
-  break;
-}
-
-:(code)
-void test_copy_imm32_to_EAX_again() {
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  b8                                 0a 0b 0c 0d \n"  // copy 0x0d0c0b0a to EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy imm32 0x0d0c0b0a to EAX\n"
-  );
-}
-
-// read a 32-bit int in little-endian order from the instruction stream
-int32_t next32() {
-  int32_t result = read_mem_i32(EIP);
-  EIP+=4;
-  return result;
-}
-
-//:: helpers
-
-string to_string(const word& w) {
-  ostringstream out;
-  out << w.data;
-  for (int i = 0;  i < SIZE(w.metadata);  ++i)
-    out << " /" << w.metadata.at(i);
-  return out.str();
-}
-
-bool contains_uppercase(const string& s) {
-  for (int i = 0;  i < SIZE(s);  ++i)
-    if (isupper(s.at(i))) return true;
-  return false;
-}
diff --git a/subx/012elf.cc b/subx/012elf.cc
deleted file mode 100644
index 9bd6cbf1..00000000
--- a/subx/012elf.cc
+++ /dev/null
@@ -1,190 +0,0 @@
-//: Loading SubX programs from ELF binaries.
-//: This will allow us to run them natively on a Linux kernel.
-//: Based on https://github.com/kragen/stoneknifeforth/blob/702d2ebe1b/386.c
-
-:(before "End Main")
-assert(argc > 1);
-if (is_equal(argv[1], "run")) {
-  START_TRACING_UNTIL_END_OF_SCOPE;
-  trace(2, "run") << "=== Starting to run" << end();
-  assert(argc > 2);
-  reset();
-  cerr << std::hex;
-  load_elf(argv[2], argc, argv);
-  while (EIP < End_of_program)  // weak final-gasp termination check
-    run_one_instruction();
-  raise << "executed past end of the world: " << EIP << " vs " << End_of_program << '\n' << end();
-  return 1;
-}
-
-:(code)
-void load_elf(const string& filename, int argc, char* argv[]) {
-  int fd = open(filename.c_str(), O_RDONLY);
-  if (fd < 0) raise << filename.c_str() << ": open" << perr() << '\n' << die();
-  off_t size = lseek(fd, 0, SEEK_END);
-  lseek(fd, 0, SEEK_SET);
-  uint8_t* elf_contents = static_cast<uint8_t*>(malloc(size));
-  if (elf_contents == NULL) raise << "malloc(" << size << ')' << perr() << '\n' << die();
-  ssize_t read_size = read(fd, elf_contents, size);
-  if (size != read_size) raise << "read → " << size << " (!= " << read_size << ')' << perr() << '\n' << die();
-  load_elf_contents(elf_contents, size, argc, argv);
-  free(elf_contents);
-}
-
-void load_elf_contents(uint8_t* elf_contents, size_t size, int argc, char* argv[]) {
-  uint8_t magic[5] = {0};
-  memcpy(magic, elf_contents, 4);
-  if (memcmp(magic, "\177ELF", 4) != 0)
-    raise << "Invalid ELF file; starts with \"" << magic << '"' << die();
-  if (elf_contents[4] != 1)
-    raise << "Only 32-bit ELF files (4-byte words; virtual addresses up to 4GB) supported.\n" << die();
-  if (elf_contents[5] != 1)
-    raise << "Only little-endian ELF files supported.\n" << die();
-  // unused: remaining 10 bytes of e_ident
-  uint32_t e_machine_type = u32_in(&elf_contents[16]);
-  if (e_machine_type != 0x00030002)
-    raise << "ELF type/machine 0x" << HEXWORD << e_machine_type << " isn't i386 executable\n" << die();
-  // unused: e_version. We only support version 1, and later versions will be backwards compatible.
-  uint32_t e_entry = u32_in(&elf_contents[24]);
-  uint32_t e_phoff = u32_in(&elf_contents[28]);
-  // unused: e_shoff
-  // unused: e_flags
-  uint32_t e_ehsize = u16_in(&elf_contents[40]);
-  if (e_ehsize < 52) raise << "Invalid binary; ELF header too small\n" << die();
-  uint32_t e_phentsize = u16_in(&elf_contents[42]);
-  uint32_t e_phnum = u16_in(&elf_contents[44]);
-  trace(90, "load") << e_phnum << " entries in the program header, each " << e_phentsize << " bytes long" << end();
-  // unused: e_shentsize
-  // unused: e_shnum
-  // unused: e_shstrndx
-
-  set<uint32_t> overlap;  // to detect overlapping segments
-  for (size_t i = 0;  i < e_phnum;  ++i)
-    load_segment_from_program_header(elf_contents, i, size, e_phoff + i*e_phentsize, e_ehsize, overlap);
-
-  // initialize code and stack
-  assert(overlap.find(STACK_SEGMENT) == overlap.end());
-  Mem.push_back(vma(STACK_SEGMENT));
-  assert(overlap.find(AFTER_STACK) == overlap.end());
-  // The stack grows downward.
-  Reg[ESP].u = AFTER_STACK;
-  Reg[EBP].u = 0;
-  EIP = e_entry;
-
-  // initialize args on stack
-  // no envp for now
-  // we wastefully use a separate page of memory for argv
-  Mem.push_back(vma(ARGV_DATA_SEGMENT));
-  uint32_t argv_data = ARGV_DATA_SEGMENT;
-  for (int i = argc-1;  i >= /*skip 'subx_bin' and 'run'*/2;  --i) {
-    push(argv_data);
-    for (size_t j = 0;  j <= strlen(argv[i]);  ++j) {
-      assert(overlap.find(argv_data) == overlap.end());  // don't bother comparing ARGV and STACK
-      write_mem_u8(argv_data, argv[i][j]);
-      argv_data += sizeof(char);
-      assert(argv_data < ARGV_DATA_SEGMENT + SEGMENT_ALIGNMENT);
-    }
-  }
-  push(argc-/*skip 'subx_bin' and 'run'*/2);
-}
-
-void push(uint32_t val) {
-  Reg[ESP].u -= 4;
-  if (Reg[ESP].u < STACK_SEGMENT) {
-    raise << "The stack overflowed its segment. "
-          << "Maybe SPACE_FOR_SEGMENT should be larger? "
-          << "Or you need to carve out an exception for the stack segment "
-          << "to be larger.\n" << die();
-  }
-  trace(Callstack_depth+1, "run") << "decrementing ESP to 0x" << HEXWORD << Reg[ESP].u << end();
-  trace(Callstack_depth+1, "run") << "pushing value 0x" << HEXWORD << val << end();
-  write_mem_u32(Reg[ESP].u, val);
-}
-
-void load_segment_from_program_header(uint8_t* elf_contents, int segment_index, size_t size, uint32_t offset, uint32_t e_ehsize, set<uint32_t>& overlap) {
-  uint32_t p_type = u32_in(&elf_contents[offset]);
-  trace(90, "load") << "program header at offset " << offset << ": type " << p_type << end();
-  if (p_type != 1) {
-    trace(90, "load") << "ignoring segment at offset " << offset << " of non PT_LOAD type " << p_type << " (see http://refspecs.linuxbase.org/elf/elf.pdf)" << end();
-    return;
-  }
-  uint32_t p_offset = u32_in(&elf_contents[offset + 4]);
-  uint32_t p_vaddr = u32_in(&elf_contents[offset + 8]);
-  if (e_ehsize > p_vaddr) raise << "Invalid binary; program header overlaps ELF header\n" << die();
-  // unused: p_paddr
-  uint32_t p_filesz = u32_in(&elf_contents[offset + 16]);
-  uint32_t p_memsz = u32_in(&elf_contents[offset + 20]);
-  if (p_filesz != p_memsz)
-    raise << "Can't yet handle segments where p_filesz != p_memsz (see http://refspecs.linuxbase.org/elf/elf.pdf)\n" << die();
-
-  if (p_offset + p_filesz > size)
-    raise << "Invalid binary; segment at offset " << offset << " is too large: wants to end at " << p_offset+p_filesz << " but the file ends at " << size << '\n' << die();
-  if (p_memsz >= SEGMENT_ALIGNMENT) {
-    raise << "Code segment too small for SubX; for now please manually increase SEGMENT_ALIGNMENT.\n" << end();
-    return;
-  }
-  trace(90, "load") << "blitting file offsets (" << p_offset << ", " << (p_offset+p_filesz) << ") to addresses (" << p_vaddr << ", " << (p_vaddr+p_memsz) << ')' << end();
-  if (size > p_memsz) size = p_memsz;
-  Mem.push_back(vma(p_vaddr));
-  for (size_t i = 0;  i < p_filesz;  ++i) {
-    assert(overlap.find(p_vaddr+i) == overlap.end());
-    write_mem_u8(p_vaddr+i, elf_contents[p_offset+i]);
-    overlap.insert(p_vaddr+i);
-  }
-  if (segment_index == 0 && End_of_program < p_vaddr+p_memsz)
-    End_of_program = p_vaddr+p_memsz;
-}
-
-:(before "End Includes")
-// Very primitive/fixed/insecure ELF segments for now.
-//   --- inaccessible:        0x00000000 -> 0x08047fff
-//   code:                    0x09000000 -> 0x09ffffff (specified in ELF binary)
-//   data:                    0x0a000000 -> 0x0affffff (specified in ELF binary)
-//                      --- heap gets mmap'd somewhere here ---
-//   stack:                   0xbdffffff -> 0xbd000000 (downward; not in ELF binary)
-//   argv hack:               0xbf000000 -> 0xbfffffff (not in ELF binary)
-//   --- reserved for kernel: 0xc0000000 -> ...
-const uint32_t START_HEAP        = 0x0b000000;
-const uint32_t END_HEAP          = 0xbd000000;
-const uint32_t STACK_SEGMENT     = 0xbd000000;
-const uint32_t AFTER_STACK       = 0xbe000000;
-const uint32_t ARGV_DATA_SEGMENT = 0xbf000000;
-// When updating the above memory map, don't forget to update `mmap`'s
-// implementation in the 'syscalls' layer.
-:(before "End Dump Info for Instruction")
-//? dump_stack();  // slow
-:(code)
-void dump_stack() {
-  ostringstream out;
-  trace(Callstack_depth+1, "run") << "stack:" << end();
-  for (uint32_t a = AFTER_STACK-4;  a > Reg[ESP].u;  a -= 4)
-    trace(Callstack_depth+2, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
-  trace(Callstack_depth+2, "run") << "  0x" << HEXWORD << Reg[ESP].u << " => 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << "  <=== ESP" << end();
-  for (uint32_t a = Reg[ESP].u-4;  a > Reg[ESP].u-40;  a -= 4)
-    trace(Callstack_depth+2, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
-}
-
-inline uint32_t u32_in(uint8_t* p) {
-  return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
-}
-
-inline uint16_t u16_in(uint8_t* p) {
-  return p[0] | p[1] << 8;
-}
-
-:(before "End Types")
-struct perr {};
-:(code)
-ostream& operator<<(ostream& os, perr /*unused*/) {
-  if (errno)
-    os << ": " << strerror(errno);
-  return os;
-}
-
-:(before "End Includes")
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <unistd.h>
diff --git a/subx/013direct_addressing.cc b/subx/013direct_addressing.cc
deleted file mode 100644
index 513cb61b..00000000
--- a/subx/013direct_addressing.cc
+++ /dev/null
@@ -1,1279 +0,0 @@
-//: operating directly on a register
-
-:(before "End Initialize Op Names")
-put_new(Name, "01", "add r32 to rm32 (add)");
-
-:(code)
-void test_add_r32_to_r32() {
-  Reg[EAX].i = 0x10;
-  Reg[EBX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     d8                                    \n" // add EBX to EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x01: {  // add r32 to r/m32
-  uint8_t modrm = next();
-  uint8_t arg2 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "add " << rname(arg2) << " to r/m32" << end();
-  int32_t* signed_arg1 = effective_address(modrm);
-  int32_t signed_result = *signed_arg1 + Reg[arg2].i;
-  SF = (signed_result < 0);
-  ZF = (signed_result == 0);
-  int64_t signed_full_result = static_cast<int64_t>(*signed_arg1) + Reg[arg2].i;
-  OF = (signed_result != signed_full_result);
-  // set CF
-  uint32_t unsigned_arg1 = static_cast<uint32_t>(*signed_arg1);
-  uint32_t unsigned_result = unsigned_arg1 + Reg[arg2].u;
-  uint64_t unsigned_full_result = static_cast<uint64_t>(unsigned_arg1) + Reg[arg2].u;
-  CF = (unsigned_result != unsigned_full_result);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  *signed_arg1 = signed_result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-  break;
-}
-
-:(code)
-void test_add_r32_to_r32_signed_overflow() {
-  Reg[EAX].i = 0x7fffffff;  // largest positive signed integer
-  Reg[EBX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     d8                                    \n" // add EBX to EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=1; ZF=0; CF=0; OF=1\n"
-      "run: storing 0x80000000\n"
-  );
-}
-
-void test_add_r32_to_r32_unsigned_overflow() {
-  Reg[EAX].u = 0xffffffff;  // largest unsigned number
-  Reg[EBX].u = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     d8                                    \n" // add EBX to EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=0; ZF=1; CF=1; OF=0\n"
-      "run: storing 0x00000000\n"
-  );
-}
-
-void test_add_r32_to_r32_unsigned_and_signed_overflow() {
-  Reg[EAX].u = Reg[EBX].u = 0x80000000;  // smallest negative signed integer
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     d8                                    \n" // add EBX to EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=0; ZF=1; CF=1; OF=1\n"
-      "run: storing 0x00000000\n"
-  );
-}
-
-:(code)
-// Implement tables 2-2 and 2-3 in the Intel manual, Volume 2.
-// We return a pointer so that instructions can write to multiple bytes in
-// 'Mem' at once.
-// beware: will eventually have side-effects
-int32_t* effective_address(uint8_t modrm) {
-  const uint8_t mod = (modrm>>6);
-  // ignore middle 3 'reg opcode' bits
-  const uint8_t rm = modrm & 0x7;
-  if (mod == 3) {
-    // mod 3 is just register direct addressing
-    trace(Callstack_depth+1, "run") << "r/m32 is " << rname(rm) << end();
-    return &Reg[rm].i;
-  }
-  uint32_t addr = effective_address_number(modrm);
-  trace(Callstack_depth+1, "run") << "effective address contains " << read_mem_i32(addr) << end();
-  return mem_addr_i32(addr);
-}
-
-// beware: will eventually have side-effects
-uint32_t effective_address_number(uint8_t modrm) {
-  const uint8_t mod = (modrm>>6);
-  // ignore middle 3 'reg opcode' bits
-  const uint8_t rm = modrm & 0x7;
-  uint32_t addr = 0;
-  switch (mod) {
-  case 3:
-    // mod 3 is just register direct addressing
-    raise << "unexpected direct addressing mode\n" << end();
-    return 0;
-  // End Mod Special-cases(addr)
-  default:
-    cerr << "unrecognized mod bits: " << NUM(mod) << '\n';
-    exit(1);
-  }
-  //: other mods are indirect, and they'll set addr appropriately
-  // Found effective_address(addr)
-  return addr;
-}
-
-string rname(uint8_t r) {
-  switch (r) {
-  case 0: return "EAX";
-  case 1: return "ECX";
-  case 2: return "EDX";
-  case 3: return "EBX";
-  case 4: return "ESP";
-  case 5: return "EBP";
-  case 6: return "ESI";
-  case 7: return "EDI";
-  default: raise << "invalid register " << r << '\n' << end();  return "";
-  }
-}
-
-//:: subtract
-
-:(before "End Initialize Op Names")
-put_new(Name, "29", "subtract r32 from rm32 (sub)");
-
-:(code)
-void test_subtract_r32_from_r32() {
-  Reg[EAX].i = 10;
-  Reg[EBX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  29     d8                                    \n"  // subtract EBX from EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract EBX from r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing 0x00000009\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x29: {  // subtract r32 from r/m32
-  const uint8_t modrm = next();
-  const uint8_t arg2 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "subtract " << rname(arg2) << " from r/m32" << end();
-  int32_t* signed_arg1 = effective_address(modrm);
-  int32_t signed_result = *signed_arg1 - Reg[arg2].i;
-  SF = (signed_result < 0);
-  ZF = (signed_result == 0);
-  int64_t signed_full_result = static_cast<int64_t>(*signed_arg1) - Reg[arg2].i;
-  OF = (signed_result != signed_full_result);
-  // set CF
-  uint32_t unsigned_arg1 = static_cast<uint32_t>(*signed_arg1);
-  uint32_t unsigned_result = unsigned_arg1 - Reg[arg2].u;
-  uint64_t unsigned_full_result = static_cast<uint64_t>(unsigned_arg1) - Reg[arg2].u;
-  CF = (unsigned_result != unsigned_full_result);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  *signed_arg1 = signed_result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-  break;
-}
-
-:(code)
-void test_subtract_r32_from_r32_signed_overflow() {
-  Reg[EAX].i = 0x80000000;  // smallest negative signed integer
-  Reg[EBX].i = 0x7fffffff;  // largest positive signed integer
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  29     d8                                    \n"  // subtract EBX from EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract EBX from r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=0; ZF=0; CF=0; OF=1\n"
-      "run: storing 0x00000001\n"
-  );
-}
-
-void test_subtract_r32_from_r32_unsigned_overflow() {
-  Reg[EAX].i = 0;
-  Reg[EBX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  29     d8                                    \n"  // subtract EBX from EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract EBX from r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-      "run: storing 0xffffffff\n"
-  );
-}
-
-void test_subtract_r32_from_r32_signed_and_unsigned_overflow() {
-  Reg[EAX].i = 0;
-  Reg[EBX].i = 0x80000000;  // smallest negative signed integer
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  29     d8                                    \n"  // subtract EBX from EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract EBX from r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=1; ZF=0; CF=1; OF=1\n"
-      "run: storing 0x80000000\n"
-  );
-}
-
-//:: multiply
-
-:(before "End Initialize Op Names")
-put_new(Name, "f7", "negate/multiply/divide rm32 (with EAX and EDX if necessary) depending on subop (neg/mul/idiv)");
-
-:(code)
-void test_multiply_EAX_by_r32() {
-  Reg[EAX].i = 4;
-  Reg[ECX].i = 3;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  f7     e1                                    \n"  // multiply EAX by ECX
-      // ModR/M in binary: 11 (direct mode) 100 (subop mul) 001 (src ECX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is ECX\n"
-      "run: subop: multiply EAX by r/m32\n"
-      "run: storing 0x0000000c\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xf7: {
-  const uint8_t modrm = next();
-  trace(Callstack_depth+1, "run") << "operate on r/m32" << end();
-  int32_t* arg1 = effective_address(modrm);
-  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
-  switch (subop) {
-  case 4: {  // mul unsigned EAX by r/m32
-    trace(Callstack_depth+1, "run") << "subop: multiply EAX by r/m32" << end();
-    const uint64_t result = static_cast<uint64_t>(Reg[EAX].u) * static_cast<uint32_t>(*arg1);
-    Reg[EAX].u = result & 0xffffffff;
-    Reg[EDX].u = result >> 32;
-    OF = (Reg[EDX].u != 0);
-    CF = OF;
-    trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-    trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[EAX].u << end();
-    break;
-  }
-  // End Op f7 Subops
-  default:
-    cerr << "unrecognized subop for opcode f7: " << NUM(subop) << '\n';
-    exit(1);
-  }
-  break;
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name_0f, "af", "multiply rm32 into r32 (imul)");
-
-:(code)
-void test_multiply_r32_into_r32() {
-  Reg[EAX].i = 4;
-  Reg[EBX].i = 2;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f af  d8                                    \n"  // subtract EBX into EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: multiply EBX by r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing 0x00000008\n"
-  );
-}
-
-:(before "End Two-Byte Opcodes Starting With 0f")
-case 0xaf: {  // multiply r32 by r/m32
-  const uint8_t modrm = next();
-  const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "multiply " << rname(arg1) << " by r/m32" << end();
-  const int32_t* arg2 = effective_address(modrm);
-  int32_t result = Reg[arg1].i * (*arg2);
-  SF = (Reg[arg1].i < 0);
-  ZF = (Reg[arg1].i == 0);
-  int64_t full_result = static_cast<int64_t>(Reg[arg1].i) * (*arg2);
-  OF = (Reg[arg1].i != full_result);
-  CF = OF;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  Reg[arg1].i = result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[arg1].i << end();
-  break;
-}
-
-//:: negate
-
-:(code)
-void test_negate_r32() {
-  Reg[EBX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  f7     db                                    \n"  // negate EBX
-      // ModR/M in binary: 11 (direct mode) 011 (subop negate) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: negate\n"
-      "run: storing 0xffffffff\n"
-  );
-}
-
-:(before "End Op f7 Subops")
-case 3: {  // negate r/m32
-  trace(Callstack_depth+1, "run") << "subop: negate" << end();
-  // one case that can overflow
-  if (static_cast<uint32_t>(*arg1) == 0x80000000) {
-    trace(Callstack_depth+1, "run") << "overflow" << end();
-    SF = true;
-    ZF = false;
-    OF = true;
-    break;
-  }
-  int32_t result = -(*arg1);
-  SF = (result >> 31);
-  ZF = (result == 0);
-  OF = false;
-  CF = (*arg1 != 0);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  *arg1 = result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
-  break;
-}
-
-:(code)
-// negate can overflow in exactly one situation
-void test_negate_can_overflow() {
-  Reg[EBX].i = 0x80000000;  // INT_MIN
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  f7     db                                    \n"  // negate EBX
-      // ModR/M in binary: 11 (direct mode) 011 (subop negate) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: negate\n"
-      "run: overflow\n"
-  );
-}
-
-//:: divide with remainder
-
-void test_divide_EAX_by_rm32() {
-  Reg[EAX].u = 7;
-  Reg[EDX].u = 0;
-  Reg[ECX].i = 3;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  f7     f9                                    \n"  // multiply EAX by ECX
-      // ModR/M in binary: 11 (direct mode) 111 (subop idiv) 001 (divisor ECX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is ECX\n"
-      "run: subop: divide EDX:EAX by r/m32, storing quotient in EAX and remainder in EDX\n"
-      "run: quotient: 0x00000002\n"
-      "run: remainder: 0x00000001\n"
-  );
-}
-
-:(before "End Op f7 Subops")
-case 7: {  // divide EDX:EAX by r/m32, storing quotient in EAX and remainder in EDX
-  trace(Callstack_depth+1, "run") << "subop: divide EDX:EAX by r/m32, storing quotient in EAX and remainder in EDX" << end();
-  int64_t dividend = static_cast<int64_t>((static_cast<uint64_t>(Reg[EDX].u) << 32) | Reg[EAX].u);
-  int32_t divisor = *arg1;
-  assert(divisor != 0);
-  Reg[EAX].i = dividend/divisor;  // quotient
-  Reg[EDX].i = dividend%divisor;  // remainder
-  // flag state undefined
-  trace(Callstack_depth+1, "run") << "quotient: 0x" << HEXWORD << Reg[EAX].i << end();
-  trace(Callstack_depth+1, "run") << "remainder: 0x" << HEXWORD << Reg[EDX].i << end();
-  break;
-}
-
-:(code)
-void test_divide_EAX_by_negative_rm32() {
-  Reg[EAX].u = 7;
-  Reg[EDX].u = 0;
-  Reg[ECX].i = -3;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  f7     f9                                    \n"  // multiply EAX by ECX
-      // ModR/M in binary: 11 (direct mode) 111 (subop idiv) 001 (divisor ECX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is ECX\n"
-      "run: subop: divide EDX:EAX by r/m32, storing quotient in EAX and remainder in EDX\n"
-      "run: quotient: 0xfffffffe\n"  // -2
-      "run: remainder: 0x00000001\n"
-  );
-}
-
-void test_divide_negative_EAX_by_rm32() {
-  Reg[EAX].i = -7;
-  Reg[EDX].i = -1;  // sign extend
-  Reg[ECX].i = 3;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  f7     f9                                    \n"  // multiply EAX by ECX
-      // ModR/M in binary: 11 (direct mode) 111 (subop idiv) 001 (divisor ECX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is ECX\n"
-      "run: subop: divide EDX:EAX by r/m32, storing quotient in EAX and remainder in EDX\n"
-      "run: quotient: 0xfffffffe\n"  // -2
-      "run: remainder: 0xffffffff\n"  // -1, same sign as divident (EDX:EAX)
-  );
-}
-
-void test_divide_negative_EDX_EAX_by_rm32() {
-  Reg[EAX].i = 0;  // lower 32 bits are clear
-  Reg[EDX].i = -7;
-  Reg[ECX].i = 0x40000000;  // 2^30 (largest positive power of 2)
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  f7     f9                                    \n"  // multiply EAX by ECX
-      // ModR/M in binary: 11 (direct mode) 111 (subop idiv) 001 (divisor ECX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is ECX\n"
-      "run: subop: divide EDX:EAX by r/m32, storing quotient in EAX and remainder in EDX\n"
-      "run: quotient: 0xffffffe4\n"  // (-7 << 32) / (1 << 30) = -7 << 2 = -28
-      "run: remainder: 0x00000000\n"
-  );
-}
-
-//:: shift left
-
-:(before "End Initialize Op Names")
-put_new(Name, "d3", "shift rm32 by CL bits depending on subop (sal/sar/shl/shr)");
-
-:(code)
-void test_shift_left_r32_with_cl() {
-  Reg[EBX].i = 13;
-  Reg[ECX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  d3     e3                                    \n"  // shift EBX left by CL bits
-      // ModR/M in binary: 11 (direct mode) 100 (subop shift left) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift left by CL bits\n"
-      "run: storing 0x0000001a\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xd3: {
-  const uint8_t modrm = next();
-  trace(Callstack_depth+1, "run") << "operate on r/m32" << end();
-  int32_t* arg1 = effective_address(modrm);
-  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
-  switch (subop) {
-  case 4: {  // shift left r/m32 by CL
-    trace(Callstack_depth+1, "run") << "subop: shift left by CL bits" << end();
-    uint8_t count = Reg[ECX].u & 0x1f;
-    // OF is only defined if count is 1
-    if (count == 1) {
-      bool msb = (*arg1 & 0x80000000) >> 1;
-      bool pnsb = (*arg1 & 0x40000000);
-      OF = (msb != pnsb);
-    }
-    int32_t result = (*arg1 << count);
-    ZF = (result == 0);
-    SF = (result < 0);
-    CF = (*arg1 << (count-1)) & 0x80000000;
-    trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-    *arg1 = result;
-    trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
-    break;
-  }
-  // End Op d3 Subops
-  default:
-    cerr << "unrecognized subop for opcode d3: " << NUM(subop) << '\n';
-    exit(1);
-  }
-  break;
-}
-
-//:: shift right arithmetic
-
-:(code)
-void test_shift_right_arithmetic_r32_with_cl() {
-  Reg[EBX].i = 26;
-  Reg[ECX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  d3     fb                                    \n"  // shift EBX right by CL bits, while preserving sign
-      // ModR/M in binary: 11 (direct mode) 111 (subop shift right arithmetic) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while preserving sign\n"
-      "run: storing 0x0000000d\n"
-  );
-}
-
-:(before "End Op d3 Subops")
-case 7: {  // shift right r/m32 by CL, preserving sign
-  trace(Callstack_depth+1, "run") << "subop: shift right by CL bits, while preserving sign" << end();
-  uint8_t count = Reg[ECX].u & 0x1f;
-  *arg1 = (*arg1 >> count);
-  ZF = (*arg1 == 0);
-  SF = (*arg1 < 0);
-  // OF is only defined if count is 1
-  if (count == 1) OF = false;
-  // CF undefined
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
-  break;
-}
-
-:(code)
-void test_shift_right_arithmetic_odd_r32_with_cl() {
-  Reg[EBX].i = 27;
-  Reg[ECX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  d3     fb                                    \n"  // shift EBX right by CL bits, while preserving sign
-      // ModR/M in binary: 11 (direct mode) 111 (subop shift right arithmetic) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while preserving sign\n"
-      // result: 13
-      "run: storing 0x0000000d\n"
-  );
-}
-
-void test_shift_right_arithmetic_negative_r32_with_cl() {
-  Reg[EBX].i = 0xfffffffd;  // -3
-  Reg[ECX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  d3     fb                                    \n"  // shift EBX right by CL bits, while preserving sign
-      // ModR/M in binary: 11 (direct mode) 111 (subop shift right arithmetic) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while preserving sign\n"
-      // result: -2
-      "run: storing 0xfffffffe\n"
-  );
-}
-
-//:: shift right logical
-
-:(code)
-void test_shift_right_logical_r32_with_cl() {
-  Reg[EBX].i = 26;
-  Reg[ECX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  d3     eb                                    \n"  // shift EBX right by CL bits, while padding zeroes
-      // ModR/M in binary: 11 (direct mode) 101 (subop shift right logical) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while padding zeroes\n"
-      // result: 13
-      "run: storing 0x0000000d\n"
-  );
-}
-
-:(before "End Op d3 Subops")
-case 5: {  // shift right r/m32 by CL, padding zeroes
-  trace(Callstack_depth+1, "run") << "subop: shift right by CL bits, while padding zeroes" << end();
-  uint8_t count = Reg[ECX].u & 0x1f;
-  // OF is only defined if count is 1
-  if (count == 1) {
-    bool msb = (*arg1 & 0x80000000) >> 1;
-    bool pnsb = (*arg1 & 0x40000000);
-    OF = (msb != pnsb);
-  }
-  uint32_t* uarg1 = reinterpret_cast<uint32_t*>(arg1);
-  *uarg1 = (*uarg1 >> count);
-  ZF = (*uarg1 == 0);
-  // result is always positive by definition
-  SF = false;
-  // CF undefined
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
-  break;
-}
-
-:(code)
-void test_shift_right_logical_odd_r32_with_cl() {
-  Reg[EBX].i = 27;
-  Reg[ECX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  d3     eb                                    \n"  // shift EBX right by CL bits, while padding zeroes
-      // ModR/M in binary: 11 (direct mode) 101 (subop shift right logical) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while padding zeroes\n"
-      // result: 13
-      "run: storing 0x0000000d\n"
-  );
-}
-
-void test_shift_right_logical_negative_r32_with_cl() {
-  Reg[EBX].i = 0xfffffffd;
-  Reg[ECX].i = 1;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  d3     eb                                    \n"  // shift EBX right by CL bits, while padding zeroes
-      // ModR/M in binary: 11 (direct mode) 101 (subop shift right logical) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while padding zeroes\n"
-      "run: storing 0x7ffffffe\n"
-  );
-}
-
-//:: and
-
-:(before "End Initialize Op Names")
-put_new(Name, "21", "rm32 = bitwise AND of r32 with rm32 (and)");
-
-:(code)
-void test_and_r32_with_r32() {
-  Reg[EAX].i = 0x0a0b0c0d;
-  Reg[EBX].i = 0x000000ff;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  21     d8                                    \n"  // and EBX with destination EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: and EBX with r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing 0x0000000d\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x21: {  // and r32 with r/m32
-  const uint8_t modrm = next();
-  const uint8_t arg2 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "and " << rname(arg2) << " with r/m32" << end();
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  int32_t* signed_arg1 = effective_address(modrm);
-  *signed_arg1 &= Reg[arg2].i;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-  SF = (*signed_arg1 >> 31);
-  ZF = (*signed_arg1 == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:: or
-
-:(before "End Initialize Op Names")
-put_new(Name, "09", "rm32 = bitwise OR of r32 with rm32 (or)");
-
-:(code)
-void test_or_r32_with_r32() {
-  Reg[EAX].i = 0x0a0b0c0d;
-  Reg[EBX].i = 0xa0b0c0d0;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  09     d8                                    \n"  // or EBX with destination EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: or EBX with r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing 0xaabbccdd\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x09: {  // or r32 with r/m32
-  const uint8_t modrm = next();
-  const uint8_t arg2 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "or " << rname(arg2) << " with r/m32" << end();
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  int32_t* signed_arg1 = effective_address(modrm);
-  *signed_arg1 |= Reg[arg2].i;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-  SF = (*signed_arg1 >> 31);
-  ZF = (*signed_arg1 == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:: xor
-
-:(before "End Initialize Op Names")
-put_new(Name, "31", "rm32 = bitwise XOR of r32 with rm32 (xor)");
-
-:(code)
-void test_xor_r32_with_r32() {
-  Reg[EAX].i = 0x0a0b0c0d;
-  Reg[EBX].i = 0xaabbc0d0;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  31     d8                                    \n"  // xor EBX with destination EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: xor EBX with r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing 0xa0b0ccdd\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x31: {  // xor r32 with r/m32
-  const uint8_t modrm = next();
-  const uint8_t arg2 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "xor " << rname(arg2) << " with r/m32" << end();
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  int32_t* signed_arg1 = effective_address(modrm);
-  *signed_arg1 ^= Reg[arg2].i;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-  SF = (*signed_arg1 >> 31);
-  ZF = (*signed_arg1 == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:: not
-
-:(code)
-void test_not_r32() {
-  Reg[EBX].i = 0x0f0f00ff;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  f7     d3                                    \n"  // not EBX
-      // ModR/M in binary: 11 (direct mode) 010 (subop not) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: not\n"
-      "run: storing 0xf0f0ff00\n"
-  );
-}
-
-:(before "End Op f7 Subops")
-case 2: {  // not r/m32
-  trace(Callstack_depth+1, "run") << "subop: not" << end();
-  *arg1 = ~(*arg1);
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
-  // no flags affected
-  break;
-}
-
-//:: compare (cmp)
-
-:(before "End Initialize Op Names")
-put_new(Name, "39", "compare: set SF if rm32 < r32 (cmp)");
-
-:(code)
-void test_compare_r32_with_r32_greater() {
-  Reg[EAX].i = 0x0a0b0c0d;
-  Reg[EBX].i = 0x0a0b0c07;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  39     d8                                    \n"  // compare EAX with EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare r/m32 with EBX\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=0; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x39: {  // set SF if r/m32 < r32
-  const uint8_t modrm = next();
-  const uint8_t reg2 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "compare r/m32 with " << rname(reg2) << end();
-  const int32_t* signed_arg1 = effective_address(modrm);
-  const int32_t signed_difference = *signed_arg1 - Reg[reg2].i;
-  SF = (signed_difference < 0);
-  ZF = (signed_difference == 0);
-  const int64_t signed_full_difference = static_cast<int64_t>(*signed_arg1) - Reg[reg2].i;
-  OF = (signed_difference != signed_full_difference);
-  // set CF
-  const uint32_t unsigned_arg1 = static_cast<uint32_t>(*signed_arg1);
-  const uint32_t unsigned_difference = unsigned_arg1 - Reg[reg2].u;
-  const uint64_t unsigned_full_difference = static_cast<uint64_t>(unsigned_arg1) - Reg[reg2].u;
-  CF = (unsigned_difference != unsigned_full_difference);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-:(code)
-void test_compare_r32_with_r32_lesser_unsigned_and_signed() {
-  Reg[EAX].i = 0x0a0b0c07;
-  Reg[EBX].i = 0x0a0b0c0d;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  39     d8                                    \n"  // compare EAX with EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare r/m32 with EBX\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-void test_compare_r32_with_r32_lesser_unsigned_and_signed_due_to_overflow() {
-  Reg[EAX].i = 0x7fffffff;  // largest positive signed integer
-  Reg[EBX].i = 0x80000000;  // smallest negative signed integer
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  39     d8                                    \n"  // compare EAX with EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare r/m32 with EBX\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=1; ZF=0; CF=1; OF=1\n"
-  );
-}
-
-void test_compare_r32_with_r32_lesser_signed() {
-  Reg[EAX].i = 0xffffffff;  // -1
-  Reg[EBX].i = 0x00000001;  // 1
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  39     d8                                    \n"  // compare EAX with EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare r/m32 with EBX\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=1; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-void test_compare_r32_with_r32_lesser_unsigned() {
-  Reg[EAX].i = 0x00000001;  // 1
-  Reg[EBX].i = 0xffffffff;  // -1
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  39     d8                                    \n"  // compare EAX with EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare r/m32 with EBX\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=0; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-void test_compare_r32_with_r32_equal() {
-  Reg[EAX].i = 0x0a0b0c0d;
-  Reg[EBX].i = 0x0a0b0c0d;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  39     d8                                    \n"  // compare EAX and EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare r/m32 with EBX\n"
-      "run: r/m32 is EAX\n"
-      "run: SF=0; ZF=1; CF=0; OF=0\n"
-  );
-}
-
-//:: copy (mov)
-
-:(before "End Initialize Op Names")
-put_new(Name, "89", "copy r32 to rm32 (mov)");
-
-:(code)
-void test_copy_r32_to_r32() {
-  Reg[EBX].i = 0xaf;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  89     d8                                    \n"  // copy EBX to EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy EBX to r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing 0x000000af\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x89: {  // copy r32 to r/m32
-  const uint8_t modrm = next();
-  const uint8_t rsrc = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "copy " << rname(rsrc) << " to r/m32" << end();
-  int32_t* dest = effective_address(modrm);
-  *dest = Reg[rsrc].i;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *dest << end();
-  break;
-}
-
-//:: xchg
-
-:(before "End Initialize Op Names")
-put_new(Name, "87", "swap the contents of r32 and rm32 (xchg)");
-
-:(code)
-void test_xchg_r32_with_r32() {
-  Reg[EBX].i = 0xaf;
-  Reg[EAX].i = 0x2e;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  87     d8                                    \n"  // exchange EBX with EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: exchange EBX with r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing 0x000000af in r/m32\n"
-      "run: storing 0x0000002e in EBX\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x87: {  // exchange r32 with r/m32
-  const uint8_t modrm = next();
-  const uint8_t reg2 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "exchange " << rname(reg2) << " with r/m32" << end();
-  int32_t* arg1 = effective_address(modrm);
-  const int32_t tmp = *arg1;
-  *arg1 = Reg[reg2].i;
-  Reg[reg2].i = tmp;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << " in r/m32" << end();
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[reg2].i << " in " << rname(reg2) << end();
-  break;
-}
-
-//:: increment
-
-:(before "End Initialize Op Names")
-put_new(Name, "40", "increment EAX (inc)");
-put_new(Name, "41", "increment ECX (inc)");
-put_new(Name, "42", "increment EDX (inc)");
-put_new(Name, "43", "increment EBX (inc)");
-put_new(Name, "44", "increment ESP (inc)");
-put_new(Name, "45", "increment EBP (inc)");
-put_new(Name, "46", "increment ESI (inc)");
-put_new(Name, "47", "increment EDI (inc)");
-
-:(code)
-void test_increment_r32() {
-  Reg[ECX].u = 0x1f;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  41                                           \n"  // increment ECX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: increment ECX\n"
-      "run: storing value 0x00000020\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x40:
-case 0x41:
-case 0x42:
-case 0x43:
-case 0x44:
-case 0x45:
-case 0x46:
-case 0x47: {  // increment r32
-  const uint8_t reg = op & 0x7;
-  trace(Callstack_depth+1, "run") << "increment " << rname(reg) << end();
-  ++Reg[reg].u;
-  trace(Callstack_depth+1, "run") << "storing value 0x" << HEXWORD << Reg[reg].u << end();
-  break;
-}
-
-:(before "End Initialize Op Names")
-put_new(Name, "ff", "increment/decrement/jump/push/call rm32 based on subop (inc/dec/jmp/push/call)");
-
-:(code)
-void test_increment_rm32() {
-  Reg[EAX].u = 0x20;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  ff     c0                                    \n"  // increment EAX
-      // ModR/M in binary: 11 (direct mode) 000 (subop inc) 000 (EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: increment r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing value 0x00000021\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xff: {
-  const uint8_t modrm = next();
-  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
-  switch (subop) {
-    case 0: {  // increment r/m32
-      trace(Callstack_depth+1, "run") << "increment r/m32" << end();
-      int32_t* arg = effective_address(modrm);
-      ++*arg;
-      trace(Callstack_depth+1, "run") << "storing value 0x" << HEXWORD << *arg << end();
-      break;
-    }
-    default:
-      cerr << "unrecognized subop for ff: " << HEXBYTE << NUM(subop) << '\n';
-      exit(1);
-    // End Op ff Subops
-  }
-  break;
-}
-
-//:: decrement
-
-:(before "End Initialize Op Names")
-put_new(Name, "48", "decrement EAX (dec)");
-put_new(Name, "49", "decrement ECX (dec)");
-put_new(Name, "4a", "decrement EDX (dec)");
-put_new(Name, "4b", "decrement EBX (dec)");
-put_new(Name, "4c", "decrement ESP (dec)");
-put_new(Name, "4d", "decrement EBP (dec)");
-put_new(Name, "4e", "decrement ESI (dec)");
-put_new(Name, "4f", "decrement EDI (dec)");
-
-:(code)
-void test_decrement_r32() {
-  Reg[ECX].u = 0x1f;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  49                                           \n"  // decrement ECX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: decrement ECX\n"
-      "run: storing value 0x0000001e\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x48:
-case 0x49:
-case 0x4a:
-case 0x4b:
-case 0x4c:
-case 0x4d:
-case 0x4e:
-case 0x4f: {  // decrement r32
-  const uint8_t reg = op & 0x7;
-  trace(Callstack_depth+1, "run") << "decrement " << rname(reg) << end();
-  --Reg[reg].u;
-  trace(Callstack_depth+1, "run") << "storing value 0x" << HEXWORD << Reg[reg].u << end();
-  break;
-}
-
-:(code)
-void test_decrement_rm32() {
-  Reg[EAX].u = 0x20;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  ff     c8                                    \n"  // decrement EAX
-      // ModR/M in binary: 11 (direct mode) 001 (subop inc) 000 (EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: decrement r/m32\n"
-      "run: r/m32 is EAX\n"
-      "run: storing value 0x0000001f\n"
-  );
-}
-
-:(before "End Op ff Subops")
-case 1: {  // decrement r/m32
-  trace(Callstack_depth+1, "run") << "decrement r/m32" << end();
-  int32_t* arg = effective_address(modrm);
-  --*arg;
-  trace(Callstack_depth+1, "run") << "storing value 0x" << HEXWORD << *arg << end();
-  break;
-}
-
-//:: push
-
-:(before "End Initialize Op Names")
-put_new(Name, "50", "push EAX to stack (push)");
-put_new(Name, "51", "push ECX to stack (push)");
-put_new(Name, "52", "push EDX to stack (push)");
-put_new(Name, "53", "push EBX to stack (push)");
-put_new(Name, "54", "push ESP to stack (push)");
-put_new(Name, "55", "push EBP to stack (push)");
-put_new(Name, "56", "push ESI to stack (push)");
-put_new(Name, "57", "push EDI to stack (push)");
-
-:(code)
-void test_push_r32() {
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000008;
-  Reg[EBX].i = 0x0000000a;
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  53                                           \n"  // push EBX to stack
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: push EBX\n"
-      "run: decrementing ESP to 0xbd000004\n"
-      "run: pushing value 0x0000000a\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x50:
-case 0x51:
-case 0x52:
-case 0x53:
-case 0x54:
-case 0x55:
-case 0x56:
-case 0x57: {  // push r32 to stack
-  uint8_t reg = op & 0x7;
-  trace(Callstack_depth+1, "run") << "push " << rname(reg) << end();
-//?   cerr << "push: " << NUM(reg) << ": " << Reg[reg].u << " => " << Reg[ESP].u << '\n';
-  push(Reg[reg].u);
-  break;
-}
-
-//:: pop
-
-:(before "End Initialize Op Names")
-put_new(Name, "58", "pop top of stack to EAX (pop)");
-put_new(Name, "59", "pop top of stack to ECX (pop)");
-put_new(Name, "5a", "pop top of stack to EDX (pop)");
-put_new(Name, "5b", "pop top of stack to EBX (pop)");
-put_new(Name, "5c", "pop top of stack to ESP (pop)");
-put_new(Name, "5d", "pop top of stack to EBP (pop)");
-put_new(Name, "5e", "pop top of stack to ESI (pop)");
-put_new(Name, "5f", "pop top of stack to EDI (pop)");
-
-:(code)
-void test_pop_r32() {
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000008;
-  write_mem_i32(0xbd000008, 0x0000000a);  // ..before this write
-  run(
-      "== code 0x1\n"  // code segment
-      // op     ModR/M  SIB   displacement  immediate
-      "  5b                                           \n"  // pop stack to EBX
-      "== data 0x2000\n"  // data segment
-      "0a 00 00 00\n"  // 0x0000000a
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: pop into EBX\n"
-      "run: popping value 0x0000000a\n"
-      "run: incrementing ESP to 0xbd00000c\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x58:
-case 0x59:
-case 0x5a:
-case 0x5b:
-case 0x5c:
-case 0x5d:
-case 0x5e:
-case 0x5f: {  // pop stack into r32
-  const uint8_t reg = op & 0x7;
-  trace(Callstack_depth+1, "run") << "pop into " << rname(reg) << end();
-//?   cerr << "pop from " << Reg[ESP].u << '\n';
-  Reg[reg].u = pop();
-//?   cerr << "=> " << NUM(reg) << ": " << Reg[reg].u << '\n';
-  break;
-}
-:(code)
-uint32_t pop() {
-  const uint32_t result = read_mem_u32(Reg[ESP].u);
-  trace(Callstack_depth+1, "run") << "popping value 0x" << HEXWORD << result << end();
-  Reg[ESP].u += 4;
-  trace(Callstack_depth+1, "run") << "incrementing ESP to 0x" << HEXWORD << Reg[ESP].u << end();
-  assert(Reg[ESP].u < AFTER_STACK);
-  return result;
-}
diff --git a/subx/014indirect_addressing.cc b/subx/014indirect_addressing.cc
deleted file mode 100644
index 3767c46d..00000000
--- a/subx/014indirect_addressing.cc
+++ /dev/null
@@ -1,1000 +0,0 @@
-//: operating on memory at the address provided by some register
-//: we'll now start providing data in a separate segment
-
-void test_add_r32_to_mem_at_r32() {
-  Reg[EBX].i = 0x10;
-  Reg[EAX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     18                                    \n"  // add EBX to *EAX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(before "End Mod Special-cases(addr)")
-case 0:  // indirect addressing
-  switch (rm) {
-  default:  // address in register
-    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << Reg[rm].u << " (" << rname(rm) << ")" << end();
-    addr = Reg[rm].u;
-    break;
-  // End Mod 0 Special-cases(addr)
-  }
-  break;
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "03", "add rm32 to r32 (add)");
-
-:(code)
-void test_add_mem_at_r32_to_r32() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x10;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  03     18                                    \n"  // add *EAX to EBX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add r/m32 to EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x03: {  // add r/m32 to r32
-  const uint8_t modrm = next();
-  const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "add r/m32 to " << rname(arg1) << end();
-  const int32_t* signed_arg2 = effective_address(modrm);
-  int32_t signed_result = Reg[arg1].i + *signed_arg2;
-  SF = (signed_result < 0);
-  ZF = (signed_result == 0);
-  int64_t signed_full_result = static_cast<int64_t>(Reg[arg1].i) + *signed_arg2;
-  OF = (signed_result != signed_full_result);
-  // set CF
-  uint32_t unsigned_arg2 = static_cast<uint32_t>(*signed_arg2);
-  uint32_t unsigned_result = Reg[arg1].u + unsigned_arg2;
-  uint64_t unsigned_full_result = static_cast<uint64_t>(Reg[arg1].u) + unsigned_arg2;
-  CF = (unsigned_result != unsigned_full_result);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  Reg[arg1].i = signed_result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[arg1].i << end();
-  break;
-}
-
-:(code)
-void test_add_mem_at_r32_to_r32_signed_overflow() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x7fffffff;  // largest positive signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  03     18                                    \n" // add *EAX to EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 1
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add r/m32 to EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains 1\n"
-      "run: SF=1; ZF=0; CF=0; OF=1\n"
-      "run: storing 0x80000000\n"
-  );
-}
-
-void test_add_mem_at_r32_to_r32_unsigned_overflow() {
-  Reg[EAX].u = 0x2000;
-  Reg[EBX].u = 0xffffffff;  // largest unsigned number
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  03     18                                    \n" // add *EAX to EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add r/m32 to EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains 1\n"
-      "run: SF=0; ZF=1; CF=1; OF=0\n"
-      "run: storing 0x00000000\n"
-  );
-}
-
-void test_add_mem_at_r32_to_r32_unsigned_and_signed_overflow() {
-  Reg[EAX].u = 0x2000;
-  Reg[EBX].u = 0x80000000;  // smallest negative signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  03     18                                    \n" // add *EAX to EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "00 00 00 80\n"  // smallest negative signed integer
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add r/m32 to EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains 80000000\n"
-      "run: SF=0; ZF=1; CF=1; OF=1\n"
-      "run: storing 0x00000000\n"
-  );
-}
-
-//:: subtract
-
-:(code)
-void test_subtract_r32_from_mem_at_r32() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 1;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  29     18                                    \n"  // subtract EBX from *EAX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "0a 00 00 00\n"  // 0x0000000a
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract EBX from r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0x00000009\n"
-  );
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "2b", "subtract rm32 from r32 (sub)");
-
-:(code)
-void test_subtract_mem_at_r32_from_r32() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 10;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  2b     18                                    \n"  // subtract *EAX from EBX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract r/m32 from EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0x00000009\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x2b: {  // subtract r/m32 from r32
-  const uint8_t modrm = next();
-  const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "subtract r/m32 from " << rname(arg1) << end();
-  const int32_t* signed_arg2 = effective_address(modrm);
-  const int32_t signed_result = Reg[arg1].i - *signed_arg2;
-  SF = (signed_result < 0);
-  ZF = (signed_result == 0);
-  int64_t signed_full_result = static_cast<int64_t>(Reg[arg1].i) - *signed_arg2;
-  OF = (signed_result != signed_full_result);
-  // set CF
-  uint32_t unsigned_arg2 = static_cast<uint32_t>(*signed_arg2);
-  uint32_t unsigned_result = Reg[arg1].u - unsigned_arg2;
-  uint64_t unsigned_full_result = static_cast<uint64_t>(Reg[arg1].u) - unsigned_arg2;
-  CF = (unsigned_result != unsigned_full_result);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  Reg[arg1].i = signed_result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[arg1].i << end();
-  break;
-}
-
-:(code)
-void test_subtract_mem_at_r32_from_r32_signed_overflow() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x80000000;  // smallest negative signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  2b     18                                    \n"  // subtract *EAX from EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "ff ff ff 7f\n"  // largest positive signed integer
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract r/m32 from EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains 7fffffff\n"
-      "run: SF=0; ZF=0; CF=0; OF=1\n"
-      "run: storing 0x00000001\n"
-  );
-}
-
-void test_subtract_mem_at_r32_from_r32_unsigned_overflow() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  2b     18                                    \n"  // subtract *EAX from EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 1
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract r/m32 from EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains 1\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-      "run: storing 0xffffffff\n"
-  );
-}
-
-void test_subtract_mem_at_r32_from_r32_signed_and_unsigned_overflow() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  2b     18                                    \n"  // subtract *EAX from EBX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "00 00 00 80\n"  // smallest negative signed integer
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract r/m32 from EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains 80000000\n"
-      "run: SF=1; ZF=0; CF=1; OF=1\n"
-      "run: storing 0x80000000\n"
-  );
-}
-
-//:: and
-:(code)
-void test_and_r32_with_mem_at_r32() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0xff;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  21     18                                    \n"  // and EBX with *EAX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: and EBX with r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0x0000000d\n"
-  );
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "23", "r32 = bitwise AND of r32 with rm32 (and)");
-
-:(code)
-void test_and_mem_at_r32_with_r32() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x0a0b0c0d;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  23     18                                    \n"  // and *EAX with EBX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "ff 00 00 00\n"  // 0x000000ff
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: and r/m32 with EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0x0000000d\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x23: {  // and r/m32 with r32
-  const uint8_t modrm = next();
-  const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "and r/m32 with " << rname(arg1) << end();
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  const int32_t* signed_arg2 = effective_address(modrm);
-  Reg[arg1].i &= *signed_arg2;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[arg1].i << end();
-  SF = (Reg[arg1].i >> 31);
-  ZF = (Reg[arg1].i == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:: or
-
-:(code)
-void test_or_r32_with_mem_at_r32() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0xa0b0c0d0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  09     18                                   #\n"  // EBX with *EAX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: or EBX with r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0xaabbccdd\n"
-  );
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "0b", "r32 = bitwise OR of r32 with rm32 (or)");
-
-:(code)
-void test_or_mem_at_r32_with_r32() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0xa0b0c0d0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0b     18                                    \n"  // or *EAX with EBX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: or r/m32 with EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0xaabbccdd\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x0b: {  // or r/m32 with r32
-  const uint8_t modrm = next();
-  const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "or r/m32 with " << rname(arg1) << end();
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  const int32_t* signed_arg2 = effective_address(modrm);
-  Reg[arg1].i |= *signed_arg2;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[arg1].i << end();
-  SF = (Reg[arg1].i >> 31);
-  ZF = (Reg[arg1].i == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:: xor
-
-:(code)
-void test_xor_r32_with_mem_at_r32() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0xa0b0c0d0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  31     18                                    \n"  // xor EBX with *EAX
-      "== data 0x2000\n"
-      "0d 0c bb aa\n"  // 0xaabb0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: xor EBX with r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0x0a0bccdd\n"
-  );
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "33", "r32 = bitwise XOR of r32 with rm32 (xor)");
-
-:(code)
-void test_xor_mem_at_r32_with_r32() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0xa0b0c0d0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  33     18                                    \n"  // xor *EAX with EBX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: xor r/m32 with EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0xaabbccdd\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x33: {  // xor r/m32 with r32
-  const uint8_t modrm = next();
-  const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "xor r/m32 with " << rname(arg1) << end();
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  const int32_t* signed_arg2 = effective_address(modrm);
-  Reg[arg1].i |= *signed_arg2;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[arg1].i << end();
-  SF = (Reg[arg1].i >> 31);
-  ZF = (Reg[arg1].i == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:: not
-
-:(code)
-void test_not_of_mem_at_r32() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  f7     13                                    \n"  // not *EBX
-      // ModR/M in binary: 00 (indirect mode) 010 (subop not) 011 (dest EBX)
-      "== data 0x2000\n"
-      "ff 00 0f 0f\n"  // 0x0f0f00ff
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: subop: not\n"
-      "run: storing 0xf0f0ff00\n"
-  );
-}
-
-//:: compare (cmp)
-
-:(code)
-void test_compare_mem_at_r32_with_r32_greater() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x0a0b0c07;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  39     18                                    \n"  // compare *EAX with EBX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare r/m32 with EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: SF=0; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-:(code)
-void test_compare_mem_at_r32_with_r32_lesser() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x0a0b0c0d;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  39     18                                    \n"  // compare *EAX with EBX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "07 0c 0b 0a\n"  // 0x0a0b0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare r/m32 with EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-:(code)
-void test_compare_mem_at_r32_with_r32_equal() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x0a0b0c0d;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  39     18                                    \n"  // compare *EAX and EBX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare r/m32 with EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: SF=0; ZF=1; CF=0; OF=0\n"
-  );
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "3b", "compare: set SF if r32 < rm32 (cmp)");
-
-:(code)
-void test_compare_r32_with_mem_at_r32_greater() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x0a0b0c0d;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3b     18                                    \n"  // compare EBX with *EAX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "07 0c 0b 0a\n"  // 0x0a0b0c07
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EBX with r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: SF=0; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x3b: {  // set SF if r32 < r/m32
-  const uint8_t modrm = next();
-  const uint8_t reg1 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "compare " << rname(reg1) << " with r/m32" << end();
-  const int32_t* signed_arg2 = effective_address(modrm);
-  const int32_t signed_difference = Reg[reg1].i - *signed_arg2;
-  SF = (signed_difference < 0);
-  ZF = (signed_difference == 0);
-  int64_t full_signed_difference = static_cast<int64_t>(Reg[reg1].i) - *signed_arg2;
-  OF = (signed_difference != full_signed_difference);
-  const uint32_t unsigned_arg2 = static_cast<uint32_t>(*signed_arg2);
-  const uint32_t unsigned_difference = Reg[reg1].u - unsigned_arg2;
-  const uint64_t full_unsigned_difference = static_cast<uint64_t>(Reg[reg1].u) - unsigned_arg2;
-  CF = (unsigned_difference != full_unsigned_difference);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-:(code)
-void test_compare_r32_with_mem_at_r32_lesser_unsigned_and_signed() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x0a0b0c07;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3b     18                                    \n"  // compare EBX with *EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EBX with r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains a0b0c0d\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-void test_compare_r32_with_mem_at_r32_lesser_unsigned_and_signed_due_to_overflow() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x7fffffff;  // largest positive signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3b     18                                    \n"  // compare EBX with *EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "00 00 00 80\n"  // smallest negative signed integer
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EBX with r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains 80000000\n"
-      "run: SF=1; ZF=0; CF=1; OF=1\n"
-  );
-}
-
-void test_compare_r32_with_mem_at_r32_lesser_signed() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0xffffffff;  // -1
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3b     18                                    \n"  // compare EBX with *EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 1
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EBX with r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains 1\n"
-      "run: SF=1; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-void test_compare_r32_with_mem_at_r32_lesser_unsigned() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x00000001;  // 1
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3b     18                                    \n"  // compare EBX with *EAX
-      // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "ff ff ff ff\n"  // -1
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EBX with r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: effective address contains ffffffff\n"
-      "run: SF=0; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-void test_compare_r32_with_mem_at_r32_equal() {
-  Reg[EAX].i = 0x2000;
-  Reg[EBX].i = 0x0a0b0c0d;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3b     18                                    \n"  // compare EBX with *EAX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EBX with r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: SF=0; ZF=1; CF=0; OF=0\n"
-  );
-}
-
-//:: copy (mov)
-
-void test_copy_r32_to_mem_at_r32() {
-  Reg[EBX].i = 0xaf;
-  Reg[EAX].i = 0x60;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  89     18                                    \n"  // copy EBX to *EAX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy EBX to r/m32\n"
-      "run: effective address is 0x00000060 (EAX)\n"
-      "run: storing 0x000000af\n"
-  );
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "8b", "copy rm32 to r32 (mov)");
-
-:(code)
-void test_copy_mem_at_r32_to_r32() {
-  Reg[EAX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  8b     18                                    \n"  // copy *EAX to EBX
-      "== data 0x2000\n"
-      "af 00 00 00\n"  // 0x000000af
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy r/m32 to EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0x000000af\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x8b: {  // copy r32 to r/m32
-  const uint8_t modrm = next();
-  const uint8_t rdest = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "copy r/m32 to " << rname(rdest) << end();
-  const int32_t* src = effective_address(modrm);
-  Reg[rdest].i = *src;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *src << end();
-  break;
-}
-
-//:: jump
-
-:(code)
-void test_jump_mem_at_r32() {
-  Reg[EAX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  ff     20                                    \n"  // jump to *EAX
-      // ModR/M in binary: 00 (indirect mode) 100 (jump to r/m32) 000 (src EAX)
-      "  b8                                 00 00 00 01\n"
-      "  b8                                 00 00 00 02\n"
-      "== data 0x2000\n"
-      "08 00 00 00\n"  // 0x00000008
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: ff\n"
-      "run: jump to r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: jumping to 0x00000008\n"
-      "run: 0x00000008 opcode: b8\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: b8");
-}
-
-:(before "End Op ff Subops")
-case 4: {  // jump to r/m32
-  trace(Callstack_depth+1, "run") << "jump to r/m32" << end();
-  const int32_t* arg2 = effective_address(modrm);
-  EIP = *arg2;
-  trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
-  break;
-}
-
-//:: push
-
-:(code)
-void test_push_mem_at_r32() {
-  Reg[EAX].i = 0x2000;
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000014;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  ff     30                                    \n"  // push *EAX to stack
-      "== data 0x2000\n"
-      "af 00 00 00\n"  // 0x000000af
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: push r/m32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: decrementing ESP to 0xbd000010\n"
-      "run: pushing value 0x000000af\n"
-  );
-}
-
-:(before "End Op ff Subops")
-case 6: {  // push r/m32 to stack
-  trace(Callstack_depth+1, "run") << "push r/m32" << end();
-  const int32_t* val = effective_address(modrm);
-  push(*val);
-  break;
-}
-
-//:: pop
-
-:(before "End Initialize Op Names")
-put_new(Name, "8f", "pop top of stack to rm32 (pop)");
-
-:(code)
-void test_pop_mem_at_r32() {
-  Reg[EAX].i = 0x60;
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000000;
-  write_mem_i32(0xbd000000, 0x00000030);
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  8f     00                                    \n"  // pop stack into *EAX
-      // ModR/M in binary: 00 (indirect mode) 000 (pop r/m32) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: pop into r/m32\n"
-      "run: effective address is 0x00000060 (EAX)\n"
-      "run: popping value 0x00000030\n"
-      "run: incrementing ESP to 0xbd000004\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x8f: {  // pop stack into r/m32
-  const uint8_t modrm = next();
-  const uint8_t subop = (modrm>>3)&0x7;
-  switch (subop) {
-    case 0: {
-      trace(Callstack_depth+1, "run") << "pop into r/m32" << end();
-      int32_t* dest = effective_address(modrm);
-      *dest = pop();
-      break;
-    }
-  }
-  break;
-}
-
-//:: special-case for loading address from disp32 rather than register
-
-:(code)
-void test_add_r32_to_mem_at_displacement() {
-  Reg[EBX].i = 0x10;  // source
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     1d            00 20 00 00             \n"  // add EBX to *0x2000
-      // ModR/M in binary: 00 (indirect mode) 011 (src EBX) 101 (dest in disp32)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is 0x00002000 (disp32)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(before "End Mod 0 Special-cases(addr)")
-case 5:  // exception: mod 0b00 rm 0b101 => incoming disp32
-  addr = next32();
-  trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << " (disp32)" << end();
-  break;
-
-//:
-
-:(code)
-void test_add_r32_to_mem_at_r32_plus_disp8() {
-  Reg[EBX].i = 0x10;  // source
-  Reg[EAX].i = 0x1ffe;  // dest
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     58            02                      \n"  // add EBX to *(EAX+2)
-      // ModR/M in binary: 01 (indirect+disp8 mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is initially 0x00001ffe (EAX)\n"
-      "run: effective address is 0x00002000 (after adding disp8)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(before "End Mod Special-cases(addr)")
-case 1: {  // indirect + disp8 addressing
-  switch (rm) {
-  default:
-    addr = Reg[rm].u;
-    trace(Callstack_depth+1, "run") << "effective address is initially 0x" << HEXWORD << addr << " (" << rname(rm) << ")" << end();
-    break;
-  // End Mod 1 Special-cases(addr)
-  }
-  int8_t displacement = static_cast<int8_t>(next());
-  if (addr > 0) {
-    addr += displacement;
-    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << " (after adding disp8)" << end();
-  }
-  else {
-    trace(Callstack_depth+1, "run") << "null address; skipping displacement" << end();
-  }
-  break;
-}
-
-:(code)
-void test_add_r32_to_mem_at_r32_plus_negative_disp8() {
-  Reg[EBX].i = 0x10;  // source
-  Reg[EAX].i = 0x2001;  // dest
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     58            ff                      \n"  // add EBX to *(EAX-1)
-      // ModR/M in binary: 01 (indirect+disp8 mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is initially 0x00002001 (EAX)\n"
-      "run: effective address is 0x00002000 (after adding disp8)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-//:
-
-:(code)
-void test_add_r32_to_mem_at_r32_plus_disp32() {
-  Reg[EBX].i = 0x10;  // source
-  Reg[EAX].i = 0x1ffe;  // dest
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     98            02 00 00 00             \n"  // add EBX to *(EAX+2)
-      // ModR/M in binary: 10 (indirect+disp32 mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is initially 0x00001ffe (EAX)\n"
-      "run: effective address is 0x00002000 (after adding disp32)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(before "End Mod Special-cases(addr)")
-case 2:  // indirect + disp32 addressing
-  switch (rm) {
-  default:
-    addr = Reg[rm].u;
-    trace(Callstack_depth+1, "run") << "effective address is initially 0x" << HEXWORD << addr << " (" << rname(rm) << ")" << end();
-    break;
-  // End Mod 2 Special-cases(addr)
-  }
-  if (addr > 0) {
-    addr += next32();
-    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << " (after adding disp32)" << end();
-  }
-  break;
-
-:(code)
-void test_add_r32_to_mem_at_r32_plus_negative_disp32() {
-  Reg[EBX].i = 0x10;  // source
-  Reg[EAX].i = 0x2001;  // dest
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     98            ff ff ff ff             \n"  // add EBX to *(EAX-1)
-      // ModR/M in binary: 10 (indirect+disp32 mode) 011 (src EBX) 000 (dest EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is initially 0x00002001 (EAX)\n"
-      "run: effective address is 0x00002000 (after adding disp32)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-//:: copy address (lea)
-
-:(before "End Initialize Op Names")
-put_new(Name, "8d", "copy address in rm32 into r32 (lea)");
-
-:(code)
-void test_copy_address() {
-  Reg[EAX].u = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  8d     18                                    \n"  // copy address in EAX into EBX
-      // ModR/M in binary: 00 (indirect mode) 011 (dest EBX) 000 (src EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy address into EBX\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x8d: {  // copy address of m32 to r32
-  const uint8_t modrm = next();
-  const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "copy address into " << rname(arg1) << end();
-  Reg[arg1].u = effective_address_number(modrm);
-  break;
-}
diff --git a/subx/015immediate_addressing.cc b/subx/015immediate_addressing.cc
deleted file mode 100644
index 782a16b9..00000000
--- a/subx/015immediate_addressing.cc
+++ /dev/null
@@ -1,1272 +0,0 @@
-//: instructions that (immediately) contain an argument to act with
-
-:(before "End Initialize Op Names")
-put_new(Name, "05", "add imm32 to EAX (add)");
-
-:(before "End Single-Byte Opcodes")
-case 0x05: {  // add imm32 to EAX
-  int32_t signed_arg2 = next32();
-  trace(Callstack_depth+1, "run") << "add imm32 0x" << HEXWORD << signed_arg2 << " to EAX" << end();
-  int32_t signed_result = Reg[EAX].i + signed_arg2;
-  SF = (signed_result < 0);
-  ZF = (signed_result == 0);
-  int64_t signed_full_result = static_cast<int64_t>(Reg[EAX].i) + signed_arg2;
-  OF = (signed_result != signed_full_result);
-  // set CF
-  uint32_t unsigned_arg2 = static_cast<uint32_t>(signed_arg2);
-  uint32_t unsigned_result = Reg[EAX].u + unsigned_arg2;
-  uint64_t unsigned_full_result = static_cast<uint64_t>(Reg[EAX].u) + unsigned_arg2;
-  CF = (unsigned_result != unsigned_full_result);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  Reg[EAX].i = signed_result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[EAX].i << end();
-  break;
-}
-
-:(code)
-void test_add_imm32_to_EAX_signed_overflow() {
-  Reg[EAX].i = 0x7fffffff;  // largest positive signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  05                                 01 00 00 00 \n" // add 1 to EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add imm32 0x00000001 to EAX\n"
-      "run: SF=1; ZF=0; CF=0; OF=1\n"
-      "run: storing 0x80000000\n"
-  );
-}
-
-void test_add_imm32_to_EAX_unsigned_overflow() {
-  Reg[EAX].u = 0xffffffff;  // largest unsigned number
-  Reg[EBX].u = 1;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  05                                 01 00 00 00 \n" // add 1 to EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add imm32 0x00000001 to EAX\n"
-      "run: SF=0; ZF=1; CF=1; OF=0\n"
-      "run: storing 0x00000000\n"
-  );
-}
-
-void test_add_imm32_to_EAX_unsigned_and_signed_overflow() {
-  Reg[EAX].u = 0x80000000;  // smallest negative signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  05                                 00 00 00 80 \n" // add 0x80000000 to EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add imm32 0x80000000 to EAX\n"
-      "run: SF=0; ZF=1; CF=1; OF=1\n"
-      "run: storing 0x00000000\n"
-  );
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "81", "combine rm32 with imm32 based on subop (add/sub/and/or/xor/cmp)");
-
-:(code)
-void test_add_imm32_to_r32() {
-  Reg[EBX].i = 1;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     c3                          0a 0b 0c 0d\n"  // add 0x0d0c0b0a to EBX
-      // ModR/M in binary: 11 (direct mode) 000 (subop add) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: subop add\n"
-      "run: storing 0x0d0c0b0b\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x81: {  // combine r/m32 with imm32
-  trace(Callstack_depth+1, "run") << "combine r/m32 with imm32" << end();
-  const uint8_t modrm = next();
-  int32_t* signed_arg1 = effective_address(modrm);
-  const int32_t signed_arg2 = next32();
-  trace(Callstack_depth+1, "run") << "imm32 is 0x" << HEXWORD << signed_arg2 << end();
-  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
-  switch (subop) {
-  case 0: {
-    trace(Callstack_depth+1, "run") << "subop add" << end();
-    int32_t signed_result = *signed_arg1 + signed_arg2;
-    SF = (signed_result < 0);
-    ZF = (signed_result == 0);
-    int64_t signed_full_result = static_cast<int64_t>(*signed_arg1) + signed_arg2;
-    OF = (signed_result != signed_full_result);
-    // set CF
-    uint32_t unsigned_arg1 = static_cast<uint32_t>(*signed_arg1);
-    uint32_t unsigned_arg2 = static_cast<uint32_t>(signed_arg2);
-    uint32_t unsigned_result = unsigned_arg1 + unsigned_arg2;
-    uint64_t unsigned_full_result = static_cast<uint64_t>(unsigned_arg1) + unsigned_arg2;
-    CF = (unsigned_result != unsigned_full_result);
-    trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-    *signed_arg1 = signed_result;
-    trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-    break;
-  }
-  // End Op 81 Subops
-  default:
-    cerr << "unrecognized subop for opcode 81: " << NUM(subop) << '\n';
-    exit(1);
-  }
-  break;
-}
-
-:(code)
-void test_add_imm32_to_r32_signed_overflow() {
-  Reg[EBX].i = 0x7fffffff;  // largest positive signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     c3                          01 00 00 00\n"  // add 1 to EBX
-      // ModR/M in binary: 11 (direct mode) 000 (subop add) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x00000001\n"
-      "run: subop add\n"
-      "run: SF=1; ZF=0; CF=0; OF=1\n"
-      "run: storing 0x80000000\n"
-  );
-}
-
-void test_add_imm32_to_r32_unsigned_overflow() {
-  Reg[EBX].u = 0xffffffff;  // largest unsigned number
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     c3                          01 00 00 00\n"  // add 1 to EBX
-      // ModR/M in binary: 11 (direct mode) 011 (subop add) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x00000001\n"
-      "run: subop add\n"
-      "run: SF=0; ZF=1; CF=1; OF=0\n"
-      "run: storing 0x00000000\n"
-  );
-}
-
-void test_add_imm32_to_r32_unsigned_and_signed_overflow() {
-  Reg[EBX].u = 0x80000000;  // smallest negative signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     c3                          00 00 00 80\n"  // add 0x80000000 to EBX
-      // ModR/M in binary: 11 (direct mode) 011 (subop add) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x80000000\n"
-      "run: subop add\n"
-      "run: SF=0; ZF=1; CF=1; OF=1\n"
-      "run: storing 0x00000000\n"
-  );
-}
-
-//:
-
-:(code)
-void test_add_imm32_to_mem_at_r32() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     03                          0a 0b 0c 0d \n"  // add 0x0d0c0b0a to *EBX
-      // ModR/M in binary: 00 (indirect mode) 000 (subop add) 011 (dest EBX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: subop add\n"
-      "run: storing 0x0d0c0b0b\n"
-  );
-}
-
-//:: subtract
-
-:(before "End Initialize Op Names")
-put_new(Name, "2d", "subtract imm32 from EAX (sub)");
-
-:(code)
-void test_subtract_imm32_from_EAX() {
-  Reg[EAX].i = 0x0d0c0baa;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  2d                                 0a 0b 0c 0d \n"  // subtract 0x0d0c0b0a from EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract imm32 0x0d0c0b0a from EAX\n"
-      "run: storing 0x000000a0\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x2d: {  // subtract imm32 from EAX
-  const int32_t signed_arg2 = next32();
-  trace(Callstack_depth+1, "run") << "subtract imm32 0x" << HEXWORD << signed_arg2 << " from EAX" << end();
-  int32_t signed_result = Reg[EAX].i - signed_arg2;
-  SF = (signed_result < 0);
-  ZF = (signed_result == 0);
-  int64_t signed_full_result = static_cast<int64_t>(Reg[EAX].i) - signed_arg2;
-  OF = (signed_result != signed_full_result);
-  // set CF
-  uint32_t unsigned_arg2 = static_cast<uint32_t>(signed_arg2);
-  uint32_t unsigned_result = Reg[EAX].u - unsigned_arg2;
-  uint64_t unsigned_full_result = static_cast<uint64_t>(Reg[EAX].u) - unsigned_arg2;
-  CF = (unsigned_result != unsigned_full_result);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  Reg[EAX].i = signed_result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[EAX].i << end();
-  break;
-}
-
-:(code)
-void test_subtract_imm32_from_EAX_signed_overflow() {
-  Reg[EAX].i = 0x80000000;  // smallest negative signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  2d                                 ff ff ff 7f \n"  // subtract largest positive signed integer from EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract imm32 0x7fffffff from EAX\n"
-      "run: SF=0; ZF=0; CF=0; OF=1\n"
-      "run: storing 0x00000001\n"
-  );
-}
-
-void test_subtract_imm32_from_EAX_unsigned_overflow() {
-  Reg[EAX].i = 0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  2d                                 01 00 00 00 \n"  // subtract 1 from EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract imm32 0x00000001 from EAX\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-      "run: storing 0xffffffff\n"
-  );
-}
-
-void test_subtract_imm32_from_EAX_signed_and_unsigned_overflow() {
-  Reg[EAX].i = 0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  2d                                 00 00 00 80 \n"  // subtract smallest negative signed integer from EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: subtract imm32 0x80000000 from EAX\n"
-      "run: SF=1; ZF=0; CF=1; OF=1\n"
-      "run: storing 0x80000000\n"
-  );
-}
-
-//:
-
-void test_subtract_imm32_from_mem_at_r32() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     2b                          01 00 00 00 \n"  // subtract 1 from *EBX
-      // ModR/M in binary: 00 (indirect mode) 101 (subop subtract) 011 (dest EBX)
-      "== data 0x2000\n"
-      "0a 00 00 00\n"  // 0x0000000a
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: imm32 is 0x00000001\n"
-      "run: subop subtract\n"
-      "run: storing 0x00000009\n"
-  );
-}
-
-:(before "End Op 81 Subops")
-case 5: {
-  trace(Callstack_depth+1, "run") << "subop subtract" << end();
-  int32_t signed_result = *signed_arg1 - signed_arg2;
-  SF = (signed_result < 0);
-  ZF = (signed_result == 0);
-  int64_t signed_full_result = static_cast<int64_t>(*signed_arg1) - signed_arg2;
-  OF = (signed_result != signed_full_result);
-  // set CF
-  uint32_t unsigned_arg1 = static_cast<uint32_t>(*signed_arg1);
-  uint32_t unsigned_arg2 = static_cast<uint32_t>(signed_arg2);
-  uint32_t unsigned_result = unsigned_arg1 - unsigned_arg2;
-  uint64_t unsigned_full_result = static_cast<uint64_t>(unsigned_arg1) - unsigned_arg2;
-  CF = (unsigned_result != unsigned_full_result);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  *signed_arg1 = signed_result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-  break;
-}
-
-:(code)
-void test_subtract_imm32_from_mem_at_r32_signed_overflow() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     2b                          ff ff ff 7f \n"  // subtract largest positive signed integer from *EBX
-      // ModR/M in binary: 00 (indirect mode) 101 (subop subtract) 011 (dest EBX)
-      "== data 0x2000\n"
-      "00 00 00 80\n"  // smallest negative signed integer
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: effective address contains 80000000\n"
-      "run: imm32 is 0x7fffffff\n"
-      "run: subop subtract\n"
-      "run: SF=0; ZF=0; CF=0; OF=1\n"
-      "run: storing 0x00000001\n"
-  );
-}
-
-void test_subtract_imm32_from_mem_at_r32_unsigned_overflow() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     2b                          01 00 00 00 \n"  // subtract 1 from *EBX
-      // ModR/M in binary: 00 (indirect mode) 101 (subop subtract) 011 (dest EBX)
-      "== data 0x2000\n"
-      "00 00 00 00\n"  // 0
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: effective address contains 0\n"
-      "run: imm32 is 0x00000001\n"
-      "run: subop subtract\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-      "run: storing 0xffffffff\n"
-  );
-}
-
-void test_subtract_imm32_from_mem_at_r32_signed_and_unsigned_overflow() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     2b                          00 00 00 80 \n"  // subtract smallest negative signed integer from *EBX
-      // ModR/M in binary: 00 (indirect mode) 101 (subop subtract) 011 (dest EBX)
-      "== data 0x2000\n"
-      "00 00 00 00\n"  // 0
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: effective address contains 0\n"
-      "run: imm32 is 0x80000000\n"
-      "run: subop subtract\n"
-      "run: SF=1; ZF=0; CF=1; OF=1\n"
-      "run: storing 0x80000000\n"
-  );
-}
-
-//:
-
-void test_subtract_imm32_from_r32() {
-  Reg[EBX].i = 10;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     eb                          01 00 00 00 \n"  // subtract 1 from EBX
-      // ModR/M in binary: 11 (direct mode) 101 (subop subtract) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x00000001\n"
-      "run: subop subtract\n"
-      "run: storing 0x00000009\n"
-  );
-}
-
-//:: shift left
-
-:(before "End Initialize Op Names")
-put_new(Name, "c1", "shift rm32 by imm8 bits depending on subop (sal/sar/shl/shr)");
-
-:(code)
-void test_shift_left_r32_with_imm8() {
-  Reg[EBX].i = 13;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c1     e3                          01          \n"  // shift EBX left by 1 bit
-      // ModR/M in binary: 11 (direct mode) 100 (subop shift left) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift left by CL bits\n"
-      "run: storing 0x0000001a\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xc1: {
-  const uint8_t modrm = next();
-  trace(Callstack_depth+1, "run") << "operate on r/m32" << end();
-  int32_t* arg1 = effective_address(modrm);
-  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
-  switch (subop) {
-  case 4: {  // shift left r/m32 by CL
-    trace(Callstack_depth+1, "run") << "subop: shift left by CL bits" << end();
-    uint8_t count = next() & 0x1f;
-    // OF is only defined if count is 1
-    if (count == 1) {
-      bool msb = (*arg1 & 0x80000000) >> 1;
-      bool pnsb = (*arg1 & 0x40000000);
-      OF = (msb != pnsb);
-    }
-    *arg1 = (*arg1 << count);
-    ZF = (*arg1 == 0);
-    SF = (*arg1 < 0);
-    // CF undefined
-    trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-    trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
-    break;
-  }
-  // End Op c1 Subops
-  default:
-    cerr << "unrecognized subop for opcode c1: " << NUM(subop) << '\n';
-    exit(1);
-  }
-  break;
-}
-
-//:: shift right arithmetic
-
-:(code)
-void test_shift_right_arithmetic_r32_with_imm8() {
-  Reg[EBX].i = 26;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c1     fb                          01          \n"  // shift EBX right by 1 bit
-      // ModR/M in binary: 11 (direct mode) 111 (subop shift right arithmetic) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while preserving sign\n"
-      "run: storing 0x0000000d\n"
-  );
-}
-
-:(before "End Op c1 Subops")
-case 7: {  // shift right r/m32 by CL, preserving sign
-  trace(Callstack_depth+1, "run") << "subop: shift right by CL bits, while preserving sign" << end();
-  uint8_t count = next() & 0x1f;
-  int32_t result = (*arg1 >> count);
-  ZF = (*arg1 == 0);
-  SF = (*arg1 < 0);
-  // OF is only defined if count is 1
-  if (count == 1) OF = false;
-  // CF
-  CF = ((*arg1 >> (count-1)) & 0x1);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  *arg1 = result;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
-  break;
-}
-
-:(code)
-void test_shift_right_arithmetic_odd_r32_with_imm8() {
-  Reg[EBX].i = 27;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c1     fb                          01          \n"  // shift EBX right by 1 bit
-      // ModR/M in binary: 11 (direct mode) 111 (subop shift right arithmetic) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while preserving sign\n"
-      // result: 13
-      "run: storing 0x0000000d\n"
-  );
-}
-
-:(code)
-void test_shift_right_arithmetic_negative_r32_with_imm8() {
-  Reg[EBX].i = 0xfffffffd;  // -3
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c1     fb                          01          \n"  // shift EBX right by 1 bit, while preserving sign
-      // ModR/M in binary: 11 (direct mode) 111 (subop shift right arithmetic) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while preserving sign\n"
-      // result: -2
-      "run: storing 0xfffffffe\n"
-  );
-}
-
-//:: shift right logical
-
-:(code)
-void test_shift_right_logical_r32_with_imm8() {
-  Reg[EBX].i = 26;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c1     eb                          01          \n"  // shift EBX right by 1 bit, while padding zeroes
-      // ModR/M in binary: 11 (direct mode) 101 (subop shift right logical) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while padding zeroes\n"
-      "run: storing 0x0000000d\n"
-  );
-}
-
-:(before "End Op c1 Subops")
-case 5: {  // shift right r/m32 by CL, preserving sign
-  trace(Callstack_depth+1, "run") << "subop: shift right by CL bits, while padding zeroes" << end();
-  uint8_t count = next() & 0x1f;
-  // OF is only defined if count is 1
-  if (count == 1) {
-    bool msb = (*arg1 & 0x80000000) >> 1;
-    bool pnsb = (*arg1 & 0x40000000);
-    OF = (msb != pnsb);
-  }
-  uint32_t* uarg1 = reinterpret_cast<uint32_t*>(arg1);
-  *uarg1 = (*uarg1 >> count);
-  ZF = (*uarg1 == 0);
-  // result is always positive by definition
-  SF = false;
-  // CF undefined
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
-  break;
-}
-
-:(code)
-void test_shift_right_logical_odd_r32_with_imm8() {
-  Reg[EBX].i = 27;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c1     eb                          01          \n"  // shift EBX right by 1 bit, while padding zeroes
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while padding zeroes\n"
-      // result: 13
-      "run: storing 0x0000000d\n"
-  );
-}
-
-:(code)
-void test_shift_right_logical_negative_r32_with_imm8() {
-  Reg[EBX].i = 0xfffffffd;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c1     eb                          01          \n"  // shift EBX right by 1 bit, while padding zeroes
-      // ModR/M in binary: 11 (direct mode) 101 (subop shift right logical) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: operate on r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: subop: shift right by CL bits, while padding zeroes\n"
-      "run: storing 0x7ffffffe\n"
-  );
-}
-
-//:: and
-
-:(before "End Initialize Op Names")
-put_new(Name, "25", "EAX = bitwise AND of imm32 with EAX (and)");
-
-:(code)
-void test_and_EAX_with_imm32() {
-  Reg[EAX].i = 0xff;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  25                                 0a 0b 0c 0d \n"  // and 0x0d0c0b0a with EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: and imm32 0x0d0c0b0a with EAX\n"
-      "run: storing 0x0000000a\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x25: {  // and imm32 with EAX
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  const int32_t signed_arg2 = next32();
-  trace(Callstack_depth+1, "run") << "and imm32 0x" << HEXWORD << signed_arg2 << " with EAX" << end();
-  Reg[EAX].i &= signed_arg2;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[EAX].i << end();
-  SF = (Reg[EAX].i >> 31);
-  ZF = (Reg[EAX].i == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:
-
-:(code)
-void test_and_imm32_with_mem_at_r32() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     23                          0a 0b 0c 0d \n"  // and 0x0d0c0b0a with *EBX
-      // ModR/M in binary: 00 (indirect mode) 100 (subop and) 011 (dest EBX)
-      "== data 0x2000\n"
-      "ff 00 00 00\n"  // 0x000000ff
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: subop and\n"
-      "run: storing 0x0000000a\n"
-  );
-}
-
-:(before "End Op 81 Subops")
-case 4: {
-  trace(Callstack_depth+1, "run") << "subop and" << end();
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  *signed_arg1 &= signed_arg2;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-  SF = (*signed_arg1 >> 31);
-  ZF = (*signed_arg1 == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:
-
-:(code)
-void test_and_imm32_with_r32() {
-  Reg[EBX].i = 0xff;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     e3                          0a 0b 0c 0d \n"  // and 0x0d0c0b0a with EBX
-      // ModR/M in binary: 11 (direct mode) 100 (subop and) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: subop and\n"
-      "run: storing 0x0000000a\n"
-  );
-}
-
-//:: or
-
-:(before "End Initialize Op Names")
-put_new(Name, "0d", "EAX = bitwise OR of imm32 with EAX (or)");
-
-:(code)
-void test_or_EAX_with_imm32() {
-  Reg[EAX].i = 0xd0c0b0a0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0d                                 0a 0b 0c 0d \n"  // or 0x0d0c0b0a with EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: or imm32 0x0d0c0b0a with EAX\n"
-      "run: storing 0xddccbbaa\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x0d: {  // or imm32 with EAX
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  const int32_t signed_arg2 = next32();
-  trace(Callstack_depth+1, "run") << "or imm32 0x" << HEXWORD << signed_arg2 << " with EAX" << end();
-  Reg[EAX].i |= signed_arg2;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[EAX].i << end();
-  SF = (Reg[EAX].i >> 31);
-  ZF = (Reg[EAX].i == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:
-
-:(code)
-void test_or_imm32_with_mem_at_r32() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     0b                          0a 0b 0c 0d \n"  // or 0x0d0c0b0a with *EBX
-      // ModR/M in binary: 00 (indirect mode) 001 (subop or) 011 (dest EBX)
-      "== data 0x2000\n"
-      "a0 b0 c0 d0\n"  // 0xd0c0b0a0
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: subop or\n"
-      "run: storing 0xddccbbaa\n"
-  );
-}
-
-:(before "End Op 81 Subops")
-case 1: {
-  trace(Callstack_depth+1, "run") << "subop or" << end();
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  *signed_arg1 |= signed_arg2;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-  SF = (*signed_arg1 >> 31);
-  ZF = (*signed_arg1 == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-:(code)
-void test_or_imm32_with_r32() {
-  Reg[EBX].i = 0xd0c0b0a0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     cb                          0a 0b 0c 0d \n"  // or 0x0d0c0b0a with EBX
-      // ModR/M in binary: 11 (direct mode) 001 (subop or) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: subop or\n"
-      "run: storing 0xddccbbaa\n"
-  );
-}
-
-//:: xor
-
-:(before "End Initialize Op Names")
-put_new(Name, "35", "EAX = bitwise XOR of imm32 with EAX (xor)");
-
-:(code)
-void test_xor_EAX_with_imm32() {
-  Reg[EAX].i = 0xddccb0a0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  35                                 0a 0b 0c 0d \n"  // xor 0x0d0c0b0a with EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: xor imm32 0x0d0c0b0a with EAX\n"
-      "run: storing 0xd0c0bbaa\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x35: {  // xor imm32 with EAX
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  const int32_t signed_arg2 = next32();
-  trace(Callstack_depth+1, "run") << "xor imm32 0x" << HEXWORD << signed_arg2 << " with EAX" << end();
-  Reg[EAX].i ^= signed_arg2;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[EAX].i << end();
-  SF = (Reg[EAX].i >> 31);
-  ZF = (Reg[EAX].i == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-//:
-
-:(code)
-void test_xor_imm32_with_mem_at_r32() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     33                          0a 0b 0c 0d \n"  // xor 0x0d0c0b0a with *EBX
-      // ModR/M in binary: 00 (indirect mode) 110 (subop xor) 011 (dest EBX)
-      "== data 0x2000\n"
-      "a0 b0 c0 d0\n"  // 0xd0c0b0a0
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: subop xor\n"
-      "run: storing 0xddccbbaa\n"
-  );
-}
-
-:(before "End Op 81 Subops")
-case 6: {
-  trace(Callstack_depth+1, "run") << "subop xor" << end();
-  // bitwise ops technically operate on unsigned numbers, but it makes no
-  // difference
-  *signed_arg1 ^= signed_arg2;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end();
-  SF = (*signed_arg1 >> 31);
-  ZF = (*signed_arg1 == 0);
-  CF = false;
-  OF = false;
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-:(code)
-void test_xor_imm32_with_r32() {
-  Reg[EBX].i = 0xd0c0b0a0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     f3                          0a 0b 0c 0d \n"  // xor 0x0d0c0b0a with EBX
-      // ModR/M in binary: 11 (direct mode) 110 (subop xor) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: subop xor\n"
-      "run: storing 0xddccbbaa\n"
-  );
-}
-
-//:: compare (cmp)
-
-:(before "End Initialize Op Names")
-put_new(Name, "3d", "compare: set SF if EAX < imm32 (cmp)");
-
-:(code)
-void test_compare_EAX_with_imm32_greater() {
-  Reg[EAX].i = 0x0d0c0b0a;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3d                                 07 0b 0c 0d \n"  // compare EAX with 0x0d0c0b07
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EAX with imm32 0x0d0c0b07\n"
-      "run: SF=0; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x3d: {  // compare EAX with imm32
-  const int32_t signed_arg1 = Reg[EAX].i;
-  const int32_t signed_arg2 = next32();
-  trace(Callstack_depth+1, "run") << "compare EAX with imm32 0x" << HEXWORD << signed_arg2 << end();
-  const int32_t signed_difference = signed_arg1 - signed_arg2;
-  SF = (signed_difference < 0);
-  ZF = (signed_difference == 0);
-  const int64_t full_signed_difference = static_cast<int64_t>(signed_arg1) - signed_arg2;
-  OF = (signed_difference != full_signed_difference);
-  const uint32_t unsigned_arg1 = static_cast<uint32_t>(signed_arg1);
-  const uint32_t unsigned_arg2 = static_cast<uint32_t>(signed_arg2);
-  const uint32_t unsigned_difference = unsigned_arg1 - unsigned_arg2;
-  const uint64_t full_unsigned_difference = static_cast<uint64_t>(unsigned_arg1) - unsigned_arg2;
-  CF = (unsigned_difference != full_unsigned_difference);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-:(code)
-void test_compare_EAX_with_imm32_lesser_unsigned_and_signed() {
-  Reg[EAX].i = 0x0a0b0c07;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3d                                 0d 0c 0b 0a \n"  // compare EAX with imm32
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EAX with imm32 0x0a0b0c0d\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-void test_compare_EAX_with_imm32_lesser_unsigned_and_signed_due_to_overflow() {
-  Reg[EAX].i = 0x7fffffff;  // largest positive signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3d                                 00 00 00 80\n"  // compare EAX with smallest negative signed integer
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EAX with imm32 0x80000000\n"
-      "run: SF=1; ZF=0; CF=1; OF=1\n"
-  );
-}
-
-void test_compare_EAX_with_imm32_lesser_signed() {
-  Reg[EAX].i = 0xffffffff;  // -1
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3d                                 01 00 00 00\n"  // compare EAX with 1
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EAX with imm32 0x00000001\n"
-      "run: SF=1; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-void test_compare_EAX_with_imm32_lesser_unsigned() {
-  Reg[EAX].i = 0x00000001;  // 1
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3d                                 ff ff ff ff\n"  // compare EAX with -1
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EAX with imm32 0xffffffff\n"
-      "run: SF=0; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-void test_compare_EAX_with_imm32_equal() {
-  Reg[EAX].i = 0x0d0c0b0a;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  3d                                 0a 0b 0c 0d \n"  // compare 0x0d0c0b0a with EAX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: compare EAX with imm32 0x0d0c0b0a\n"
-      "run: SF=0; ZF=1; CF=0; OF=0\n"
-  );
-}
-
-//:
-
-void test_compare_imm32_with_r32_greater() {
-  Reg[EBX].i = 0x0d0c0b0a;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     fb                          07 0b 0c 0d \n"  // compare 0x0d0c0b07 with EBX
-      // ModR/M in binary: 11 (direct mode) 111 (subop compare) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x0d0c0b07\n"
-      "run: SF=0; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-:(before "End Op 81 Subops")
-case 7: {
-  trace(Callstack_depth+1, "run") << "subop compare" << end();
-  const int32_t tmp1 = *signed_arg1 - signed_arg2;
-  SF = (tmp1 < 0);
-  ZF = (tmp1 == 0);
-  const int64_t tmp2 = static_cast<int64_t>(*signed_arg1) - signed_arg2;
-  OF = (tmp1 != tmp2);
-  const uint32_t unsigned_arg1 = static_cast<uint32_t>(*signed_arg1);
-  const uint32_t unsigned_arg2 = static_cast<uint32_t>(signed_arg2);
-  const uint32_t tmp3 = unsigned_arg1 - unsigned_arg2;
-  const uint64_t tmp4 = static_cast<uint64_t>(unsigned_arg1) - unsigned_arg2;
-  CF = (tmp3 != tmp4);
-  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
-  break;
-}
-
-:(code)
-void test_compare_rm32_with_imm32_lesser_unsigned_and_signed() {
-  Reg[EAX].i = 0x0a0b0c07;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     f8                          0d 0c 0b 0a \n"  // compare EAX with imm32
-      // ModR/M in binary: 11 (direct mode) 111 (subop compare) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EAX\n"
-      "run: imm32 is 0x0a0b0c0d\n"
-      "run: subop compare\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-void test_compare_rm32_with_imm32_lesser_unsigned_and_signed_due_to_overflow() {
-  Reg[EAX].i = 0x7fffffff;  // largest positive signed integer
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     f8                          00 00 00 80\n"  // compare EAX with smallest negative signed integer
-      // ModR/M in binary: 11 (direct mode) 111 (subop compare) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EAX\n"
-      "run: imm32 is 0x80000000\n"
-      "run: subop compare\n"
-      "run: SF=1; ZF=0; CF=1; OF=1\n"
-  );
-}
-
-void test_compare_rm32_with_imm32_lesser_signed() {
-  Reg[EAX].i = 0xffffffff;  // -1
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     f8                          01 00 00 00\n"  // compare EAX with 1
-      // ModR/M in binary: 11 (direct mode) 111 (subop compare) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EAX\n"
-      "run: imm32 is 0x00000001\n"
-      "run: subop compare\n"
-      "run: SF=1; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-void test_compare_rm32_with_imm32_lesser_unsigned() {
-  Reg[EAX].i = 0x00000001;  // 1
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     f8                          ff ff ff ff\n"  // compare EAX with -1
-      // ModR/M in binary: 11 (direct mode) 111 (subop compare) 000 (dest EAX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EAX\n"
-      "run: imm32 is 0xffffffff\n"
-      "run: subop compare\n"
-      "run: SF=0; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-:(code)
-void test_compare_imm32_with_r32_equal() {
-  Reg[EBX].i = 0x0d0c0b0a;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     fb                          0a 0b 0c 0d \n"  // compare 0x0d0c0b0a with EBX
-      // ModR/M in binary: 11 (direct mode) 111 (subop compare) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: r/m32 is EBX\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: SF=0; ZF=1; CF=0; OF=0\n"
-  );
-}
-
-:(code)
-void test_compare_imm32_with_mem_at_r32_greater() {
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     3b                          07 0b 0c 0d \n"  // compare 0x0d0c0b07 with *EBX
-      // ModR/M in binary: 00 (indirect mode) 111 (subop compare) 011 (dest EBX)
-      "== data 0x2000\n"
-      "0a 0b 0c 0d\n"  // 0x0d0c0b0a
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: imm32 is 0x0d0c0b07\n"
-      "run: SF=0; ZF=0; CF=0; OF=0\n"
-  );
-}
-
-:(code)
-void test_compare_imm32_with_mem_at_r32_lesser() {
-  Reg[EAX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     38                          0a 0b 0c 0d \n"  // compare 0x0d0c0b0a with *EAX
-      // ModR/M in binary: 00 (indirect mode) 111 (subop compare) 000 (dest EAX)
-      "== data 0x2000\n"
-      "07 0b 0c 0d\n"  // 0x0d0c0b07
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: SF=1; ZF=0; CF=1; OF=0\n"
-  );
-}
-
-:(code)
-void test_compare_imm32_with_mem_at_r32_equal() {
-  Reg[EBX].i = 0x0d0c0b0a;
-  Reg[EBX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  81     3b                          0a 0b 0c 0d \n"  // compare 0x0d0c0b0a with *EBX
-      // ModR/M in binary: 00 (indirect mode) 111 (subop compare) 011 (dest EBX)
-      "== data 0x2000\n"
-      "0a 0b 0c 0d\n"  // 0x0d0c0b0a
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: combine r/m32 with imm32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-      "run: SF=0; ZF=1; CF=0; OF=0\n"
-  );
-}
-
-//:: copy (mov)
-
-:(before "End Initialize Op Names")
-// b8 defined earlier to copy imm32 to EAX
-put_new(Name, "b9", "copy imm32 to ECX (mov)");
-put_new(Name, "ba", "copy imm32 to EDX (mov)");
-put_new(Name, "bb", "copy imm32 to EBX (mov)");
-put_new(Name, "bc", "copy imm32 to ESP (mov)");
-put_new(Name, "bd", "copy imm32 to EBP (mov)");
-put_new(Name, "be", "copy imm32 to ESI (mov)");
-put_new(Name, "bf", "copy imm32 to EDI (mov)");
-
-:(code)
-void test_copy_imm32_to_r32() {
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  bb                                 0a 0b 0c 0d \n"  // copy 0x0d0c0b0a to EBX
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy imm32 0x0d0c0b0a to EBX\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xb9:
-case 0xba:
-case 0xbb:
-case 0xbc:
-case 0xbd:
-case 0xbe:
-case 0xbf: {  // copy imm32 to r32
-  const uint8_t rdest = op & 0x7;
-  const int32_t src = next32();
-  trace(Callstack_depth+1, "run") << "copy imm32 0x" << HEXWORD << src << " to " << rname(rdest) << end();
-  Reg[rdest].i = src;
-  break;
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "c7", "copy imm32 to rm32 (mov)");
-
-:(code)
-void test_copy_imm32_to_mem_at_r32() {
-  Reg[EBX].i = 0x60;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c7     03                          0a 0b 0c 0d \n"  // copy 0x0d0c0b0a to *EBX
-      // ModR/M in binary: 00 (indirect mode) 000 (unused) 011 (dest EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy imm32 to r/m32\n"
-      "run: effective address is 0x00000060 (EBX)\n"
-      "run: imm32 is 0x0d0c0b0a\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xc7: {  // copy imm32 to r32
-  const uint8_t modrm = next();
-  trace(Callstack_depth+1, "run") << "copy imm32 to r/m32" << end();
-  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
-  if (subop != 0) {
-    cerr << "unrecognized subop for opcode c7: " << NUM(subop) << " (only 0/copy currently implemented)\n";
-    exit(1);
-  }
-  int32_t* dest = effective_address(modrm);
-  const int32_t src = next32();
-  trace(Callstack_depth+1, "run") << "imm32 is 0x" << HEXWORD << src << end();
-  *dest = src;
-  break;
-}
-
-//:: push
-
-:(before "End Initialize Op Names")
-put_new(Name, "68", "push imm32 to stack (push)");
-
-:(code)
-void test_push_imm32() {
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000014;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  68                                 af 00 00 00 \n"  // push *EAX to stack
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: push imm32 0x000000af\n"
-      "run: ESP is now 0xbd000010\n"
-      "run: contents at ESP: 0x000000af\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x68: {
-  const uint32_t val = static_cast<uint32_t>(next32());
-  trace(Callstack_depth+1, "run") << "push imm32 0x" << HEXWORD << val << end();
-//?   cerr << "push: " << val << " => " << Reg[ESP].u << '\n';
-  push(val);
-  trace(Callstack_depth+1, "run") << "ESP is now 0x" << HEXWORD << Reg[ESP].u << end();
-  trace(Callstack_depth+1, "run") << "contents at ESP: 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << end();
-  break;
-}
diff --git a/subx/016index_addressing.cc b/subx/016index_addressing.cc
deleted file mode 100644
index 6e1f63e6..00000000
--- a/subx/016index_addressing.cc
+++ /dev/null
@@ -1,155 +0,0 @@
-//: operating on memory at the address provided by some register plus optional scale and offset
-
-:(code)
-void test_add_r32_to_mem_at_r32_with_sib() {
-  Reg[EBX].i = 0x10;
-  Reg[EAX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     1c      20                              \n"  // add EBX to *EAX
-      // ModR/M in binary: 00 (indirect mode) 011 (src EBX) 100 (dest in SIB)
-      // SIB in binary: 00 (scale 1) 100 (no index) 000 (base EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is initially 0x00002000 (EAX)\n"
-      "run: effective address is 0x00002000\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(before "End Mod 0 Special-cases(addr)")
-case 4:  // exception: mod 0b00 rm 0b100 => incoming SIB (scale-index-base) byte
-  addr = effective_address_from_sib(mod);
-  break;
-:(code)
-uint32_t effective_address_from_sib(uint8_t mod) {
-  const uint8_t sib = next();
-  const uint8_t base = sib&0x7;
-  uint32_t addr = 0;
-  if (base != EBP || mod != 0) {
-    addr = Reg[base].u;
-    trace(Callstack_depth+1, "run") << "effective address is initially 0x" << HEXWORD << addr << " (" << rname(base) << ")" << end();
-  }
-  else {
-    // base == EBP && mod == 0
-    addr = next32();  // ignore base
-    trace(Callstack_depth+1, "run") << "effective address is initially 0x" << HEXWORD << addr << " (disp32)" << end();
-  }
-  const uint8_t index = (sib>>3)&0x7;
-  if (index == ESP) {
-    // ignore index and scale
-    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << end();
-  }
-  else {
-    const uint8_t scale = (1 << (sib>>6));
-    addr += Reg[index].i*scale;  // treat index register as signed. Maybe base as well? But we'll always ensure it's non-negative.
-    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << " (after adding " << rname(index) << "*" << NUM(scale) << ")" << end();
-  }
-  return addr;
-}
-
-:(code)
-void test_add_r32_to_mem_at_base_r32_index_r32() {
-  Reg[EBX].i = 0x10;  // source
-  Reg[EAX].i = 0x1ffe;  // dest base
-  Reg[ECX].i = 0x2;  // dest index
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     1c      08                              \n"  // add EBX to *(EAX+ECX)
-      // ModR/M in binary: 00 (indirect mode) 011 (src EBX) 100 (dest in SIB)
-      // SIB in binary: 00 (scale 1) 001 (index ECX) 000 (base EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is initially 0x00001ffe (EAX)\n"
-      "run: effective address is 0x00002000 (after adding ECX*1)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(code)
-void test_add_r32_to_mem_at_displacement_using_sib() {
-  Reg[EBX].i = 0x10;  // source
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     1c      25    00 20 00 00               \n"  // add EBX to *0x2000
-      // ModR/M in binary: 00 (indirect mode) 011 (src EBX) 100 (dest in SIB)
-      // SIB in binary: 00 (scale 1) 100 (no index) 101 (not EBP but disp32)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is initially 0x00002000 (disp32)\n"
-      "run: effective address is 0x00002000\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-//:
-
-:(code)
-void test_add_r32_to_mem_at_base_r32_index_r32_plus_disp8() {
-  Reg[EBX].i = 0x10;  // source
-  Reg[EAX].i = 0x1ff9;  // dest base
-  Reg[ECX].i = 0x5;  // dest index
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     5c      08    02                        \n"  // add EBX to *(EAX+ECX+2)
-      // ModR/M in binary: 01 (indirect+disp8 mode) 011 (src EBX) 100 (dest in SIB)
-      // SIB in binary: 00 (scale 1) 001 (index ECX) 000 (base EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is initially 0x00001ff9 (EAX)\n"
-      "run: effective address is 0x00001ffe (after adding ECX*1)\n"
-      "run: effective address is 0x00002000 (after adding disp8)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(before "End Mod 1 Special-cases(addr)")
-case 4:  // exception: mod 0b01 rm 0b100 => incoming SIB (scale-index-base) byte
-  addr = effective_address_from_sib(mod);
-  break;
-
-//:
-
-:(code)
-void test_add_r32_to_mem_at_base_r32_index_r32_plus_disp32() {
-  Reg[EBX].i = 0x10;  // source
-  Reg[EAX].i = 0x1ff9;  // dest base
-  Reg[ECX].i = 0x5;  // dest index
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  01     9c      08    02 00 00 00               \n"  // add EBX to *(EAX+ECX+2)
-      // ModR/M in binary: 10 (indirect+disp32 mode) 011 (src EBX) 100 (dest in SIB)
-      // SIB in binary: 00 (scale 1) 001 (index ECX) 000 (base EAX)
-      "== data 0x2000\n"
-      "01 00 00 00\n"  // 0x00000001
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: add EBX to r/m32\n"
-      "run: effective address is initially 0x00001ff9 (EAX)\n"
-      "run: effective address is 0x00001ffe (after adding ECX*1)\n"
-      "run: effective address is 0x00002000 (after adding disp32)\n"
-      "run: storing 0x00000011\n"
-  );
-}
-
-:(before "End Mod 2 Special-cases(addr)")
-case 4:  // exception: mod 0b10 rm 0b100 => incoming SIB (scale-index-base) byte
-  addr = effective_address_from_sib(mod);
-  break;
diff --git a/subx/017jump_disp8.cc b/subx/017jump_disp8.cc
deleted file mode 100644
index d7558401..00000000
--- a/subx/017jump_disp8.cc
+++ /dev/null
@@ -1,407 +0,0 @@
-//: jump to 8-bit offset
-
-//:: jump
-
-:(before "End Initialize Op Names")
-put_new(Name, "eb", "jump disp8 bytes away (jmp)");
-
-:(code)
-void test_jump_disp8() {
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  eb                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: eb\n"
-      "run: jump 5\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: 05");
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xeb: {  // jump disp8
-  int8_t offset = static_cast<int>(next());
-  trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-  EIP += offset;
-  break;
-}
-
-//:: jump if equal/zero
-
-:(before "End Initialize Op Names")
-put_new(Name, "74", "jump disp8 bytes away if equal, if ZF is set (jcc/jz/je)");
-
-:(code)
-void test_je_disp8_success() {
-  ZF = true;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  74                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 74\n"
-      "run: jump 5\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: 05");
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x74: {  // jump disp8 if ZF
-  const int8_t offset = static_cast<int>(next());
-  if (ZF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_je_disp8_fail() {
-  ZF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  74                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 74\n"
-      "run: 0x00000003 opcode: 05\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if not equal/not zero
-
-:(before "End Initialize Op Names")
-put_new(Name, "75", "jump disp8 bytes away if not equal, if ZF is not set (jcc/jnz/jne)");
-
-:(code)
-void test_jne_disp8_success() {
-  ZF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  75                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 75\n"
-      "run: jump 5\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: 05");
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x75: {  // jump disp8 unless ZF
-  const int8_t offset = static_cast<int>(next());
-  if (!ZF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jne_disp8_fail() {
-  ZF = true;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  75                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 75\n"
-      "run: 0x00000003 opcode: 05\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if greater
-
-:(before "End Initialize Op Names")
-put_new(Name, "7f", "jump disp8 bytes away if greater (signed), if ZF is unset and SF == OF (jcc/jg/jnle)");
-put_new(Name, "77", "jump disp8 bytes away if greater (unsigned), if ZF is unset and CF is unset (jcc/ja/jnbe)");
-
-:(code)
-void test_jg_disp8_success() {
-  ZF = false;
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  7f                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 7f\n"
-      "run: jump 5\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: 05");
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x7f: {  // jump disp8 if SF == OF and !ZF
-  const int8_t offset = static_cast<int>(next());
-  if (SF == OF && !ZF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-case 0x77: {  // jump disp8 if !CF and !ZF
-  const int8_t offset = static_cast<int>(next());
-  if (!CF && !ZF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jg_disp8_fail() {
-  ZF = false;
-  SF = true;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  7f                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 7f\n"
-      "run: 0x00000003 opcode: 05\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if greater or equal
-
-:(before "End Initialize Op Names")
-put_new(Name, "7d", "jump disp8 bytes away if greater or equal (signed), if SF == OF (jcc/jge/jnl)");
-put_new(Name, "73", "jump disp8 bytes away if greater or equal (unsigned), if CF is unset (jcc/jae/jnb)");
-
-:(code)
-void test_jge_disp8_success() {
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  7d                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 7d\n"
-      "run: jump 5\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: 05");
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x7d: {  // jump disp8 if SF == OF
-  const int8_t offset = static_cast<int>(next());
-  if (SF == OF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-case 0x73: {  // jump disp8 if !CF
-  const int8_t offset = static_cast<int>(next());
-  if (!CF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jge_disp8_fail() {
-  SF = true;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  7d                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 7d\n"
-      "run: 0x00000003 opcode: 05\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if lesser
-
-:(before "End Initialize Op Names")
-put_new(Name, "7c", "jump disp8 bytes away if lesser (signed), if SF != OF (jcc/jl/jnge)");
-put_new(Name, "72", "jump disp8 bytes away if lesser (unsigned), if CF is set (jcc/jb/jnae)");
-
-:(code)
-void test_jl_disp8_success() {
-  ZF = false;
-  SF = true;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  7c                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 7c\n"
-      "run: jump 5\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: 05");
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x7c: {  // jump disp8 if SF != OF
-  const int8_t offset = static_cast<int>(next());
-  if (SF != OF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-case 0x72: {  // jump disp8 if CF
-  const int8_t offset = static_cast<int>(next());
-  if (CF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jl_disp8_fail() {
-  ZF = false;
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  7c                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 7c\n"
-      "run: 0x00000003 opcode: 05\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if lesser or equal
-
-:(before "End Initialize Op Names")
-put_new(Name, "7e", "jump disp8 bytes away if lesser or equal (signed), if ZF is set or SF != OF (jcc/jle/jng)");
-put_new(Name, "76", "jump disp8 bytes away if lesser or equal (unsigned), if ZF is set or CF is set (jcc/jbe/jna)");
-
-:(code)
-void test_jle_disp8_equal() {
-  ZF = true;
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  7e                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 7e\n"
-      "run: jump 5\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: 05");
-}
-
-:(code)
-void test_jle_disp8_lesser() {
-  ZF = false;
-  SF = true;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  7e                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 7e\n"
-      "run: jump 5\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: 05");
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x7e: {  // jump disp8 if ZF or SF != OF
-  const int8_t offset = static_cast<int>(next());
-  if (ZF || SF != OF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-case 0x76: {  // jump disp8 if ZF or CF
-  const int8_t offset = static_cast<int>(next());
-  if (ZF || CF) {
-    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jle_disp8_greater() {
-  ZF = false;
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  7e                   05                        \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 7e\n"
-      "run: 0x00000003 opcode: 05\n"
-      "run: 0x00000008 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
diff --git a/subx/018jump_disp32.cc b/subx/018jump_disp32.cc
deleted file mode 100644
index 86e06e9f..00000000
--- a/subx/018jump_disp32.cc
+++ /dev/null
@@ -1,407 +0,0 @@
-//: jump to 32-bit offset
-
-//:: jump
-
-:(before "End Initialize Op Names")
-put_new(Name, "e9", "jump disp32 bytes away (jmp)");
-
-:(code)
-void test_jump_disp32() {
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  e9                   05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: e9\n"
-      "run: jump 5\n"
-      "run: 0x0000000b opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000006 opcode: 05");
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xe9: {  // jump disp32
-  const int32_t offset = next32();
-  trace(Callstack_depth+1, "run") << "jump " << offset << end();
-  EIP += offset;
-  break;
-}
-
-//:: jump if equal/zero
-
-:(before "End Initialize Op Names")
-put_new(Name_0f, "84", "jump disp32 bytes away if equal, if ZF is set (jcc/jz/je)");
-
-:(code)
-void test_je_disp32_success() {
-  ZF = true;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 84                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: jump 5\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000007 opcode: 05");
-}
-
-:(before "End Two-Byte Opcodes Starting With 0f")
-case 0x84: {  // jump disp32 if ZF
-  const int32_t offset = next32();
-  if (ZF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_je_disp32_fail() {
-  ZF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 84                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: 0x00000007 opcode: 05\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if not equal/not zero
-
-:(before "End Initialize Op Names")
-put_new(Name_0f, "85", "jump disp32 bytes away if not equal, if ZF is not set (jcc/jnz/jne)");
-
-:(code)
-void test_jne_disp32_success() {
-  ZF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 85                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: jump 5\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000007 opcode: 05");
-}
-
-:(before "End Two-Byte Opcodes Starting With 0f")
-case 0x85: {  // jump disp32 unless ZF
-  const int32_t offset = next32();
-  if (!ZF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jne_disp32_fail() {
-  ZF = true;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 85                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: 0x00000007 opcode: 05\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if greater
-
-:(before "End Initialize Op Names")
-put_new(Name_0f, "8f", "jump disp32 bytes away if greater (signed), if ZF is unset and SF == OF (jcc/jg/jnle)");
-put_new(Name_0f, "87", "jump disp32 bytes away if greater (unsigned), if ZF is unset and CF is unset (jcc/ja/jnbe)");
-
-:(code)
-void test_jg_disp32_success() {
-  ZF = false;
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 8f                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: jump 5\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000007 opcode: 05");
-}
-
-:(before "End Two-Byte Opcodes Starting With 0f")
-case 0x8f: {  // jump disp32 if !SF and !ZF
-  const int32_t offset = next32();
-  if (!ZF && SF == OF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-case 0x87: {  // jump disp32 if !CF and !ZF
-  const int32_t offset = next();
-  if (!CF && !ZF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jg_disp32_fail() {
-  ZF = false;
-  SF = true;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 8f                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: 0x00000007 opcode: 05\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if greater or equal
-
-:(before "End Initialize Op Names")
-put_new(Name_0f, "8d", "jump disp32 bytes away if greater or equal (signed), if SF == OF (jcc/jge/jnl)");
-put_new(Name_0f, "83", "jump disp32 bytes away if greater or equal (unsigned), if CF is unset (jcc/jae/jnb)");
-
-:(code)
-void test_jge_disp32_success() {
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 8d                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: jump 5\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000007 opcode: 05");
-}
-
-:(before "End Two-Byte Opcodes Starting With 0f")
-case 0x8d: {  // jump disp32 if !SF
-  const int32_t offset = next32();
-  if (SF == OF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-case 0x83: {  // jump disp32 if !CF
-  const int32_t offset = next32();
-  if (!CF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jge_disp32_fail() {
-  SF = true;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 8d                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: 0x00000007 opcode: 05\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if lesser
-
-:(before "End Initialize Op Names")
-put_new(Name_0f, "8c", "jump disp32 bytes away if lesser (signed), if SF != OF (jcc/jl/jnge)");
-put_new(Name_0f, "82", "jump disp32 bytes away if lesser (unsigned), if CF is set (jcc/jb/jnae)");
-
-:(code)
-void test_jl_disp32_success() {
-  ZF = false;
-  SF = true;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 8c                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: jump 5\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000007 opcode: 05");
-}
-
-:(before "End Two-Byte Opcodes Starting With 0f")
-case 0x8c: {  // jump disp32 if SF and !ZF
-  const int32_t offset = next32();
-  if (SF != OF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-case 0x72: {  // jump disp32 if CF
-  const int32_t offset = next32();
-  if (CF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jl_disp32_fail() {
-  ZF = false;
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 8c                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: 0x00000007 opcode: 05\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
-
-//:: jump if lesser or equal
-
-:(before "End Initialize Op Names")
-put_new(Name_0f, "8e", "jump disp32 bytes away if lesser or equal (signed), if ZF is set or SF != OF (jcc/jle/jng)");
-put_new(Name_0f, "86", "jump disp8 bytes away if lesser or equal (unsigned), if ZF is set or CF is set (jcc/jbe/jna)");
-
-:(code)
-void test_jle_disp32_equal() {
-  ZF = true;
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 8e                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: jump 5\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000007 opcode: 05");
-}
-
-:(code)
-void test_jle_disp32_lesser() {
-  ZF = false;
-  SF = true;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 8e                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: jump 5\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000007 opcode: 05");
-}
-
-:(before "End Two-Byte Opcodes Starting With 0f")
-case 0x8e: {  // jump disp32 if SF or ZF
-  const int32_t offset = next32();
-  if (ZF || SF != OF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-case 0x86: {  // jump disp32 if ZF or CF
-  const int32_t offset = next32();
-  if (ZF || CF) {
-    trace(Callstack_depth+1, "run") << "jump " << offset << end();
-    EIP += offset;
-  }
-  break;
-}
-
-:(code)
-void test_jle_disp32_greater() {
-  ZF = false;
-  SF = false;
-  OF = false;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  0f 8e                05 00 00 00               \n"  // skip 1 instruction
-      "  05                                 00 00 00 01 \n"
-      "  05                                 00 00 00 02 \n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000001 opcode: 0f\n"
-      "run: 0x00000007 opcode: 05\n"
-      "run: 0x0000000c opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: jump 5");
-}
diff --git a/subx/019functions.cc b/subx/019functions.cc
deleted file mode 100644
index 81e99e6e..00000000
--- a/subx/019functions.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-//:: call
-
-:(before "End Initialize Op Names")
-put_new(Name, "e8", "call disp32 (call)");
-
-:(code)
-void test_call_disp32() {
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000064;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  e8                                 a0 00 00 00 \n"  // call function offset at 0x000000a0
-      // next EIP is 6
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: call imm32 0x000000a0\n"
-      "run: decrementing ESP to 0xbd000060\n"
-      "run: pushing value 0x00000006\n"
-      "run: jumping to 0x000000a6\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xe8: {  // call disp32 relative to next EIP
-  const int32_t offset = next32();
-  ++Callstack_depth;
-  trace(Callstack_depth+1, "run") << "call imm32 0x" << HEXWORD << offset << end();
-//?   cerr << "push: EIP: " << EIP << " => " << Reg[ESP].u << '\n';
-  push(EIP);
-  EIP += offset;
-  trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
-  break;
-}
-
-//:
-
-:(code)
-void test_call_r32() {
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000064;
-  Reg[EBX].u = 0x000000a0;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  ff     d3                                      \n"  // call function offset at EBX
-      // next EIP is 3
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: call to r/m32\n"
-      "run: r/m32 is EBX\n"
-      "run: decrementing ESP to 0xbd000060\n"
-      "run: pushing value 0x00000003\n"
-      "run: jumping to 0x000000a3\n"
-  );
-}
-
-:(before "End Op ff Subops")
-case 2: {  // call function pointer at r/m32
-  trace(Callstack_depth+1, "run") << "call to r/m32" << end();
-  const int32_t* offset = effective_address(modrm);
-  push(EIP);
-  EIP += *offset;
-  trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
-  ++Callstack_depth;
-  break;
-}
-
-:(code)
-void test_call_mem_at_r32() {
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000064;
-  Reg[EBX].u = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  ff     13                                      \n"  // call function offset at *EBX
-      // next EIP is 3
-      "== data 0x2000\n"
-      "a0 00 00 00\n"  // 0x000000a0
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: call to r/m32\n"
-      "run: effective address is 0x00002000 (EBX)\n"
-      "run: decrementing ESP to 0xbd000060\n"
-      "run: pushing value 0x00000003\n"
-      "run: jumping to 0x000000a3\n"
-  );
-}
-
-//:: ret
-
-:(before "End Initialize Op Names")
-put_new(Name, "c3", "return from most recent unfinished call (ret)");
-
-:(code)
-void test_ret() {
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000064;
-  write_mem_u32(Reg[ESP].u, 0x10);
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c3                                           \n"  // return
-      "== data 0x2000\n"
-      "10 00 00 00\n"  // 0x00000010
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: return\n"
-      "run: popping value 0x00000010\n"
-      "run: jumping to 0x00000010\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xc3: {  // return from a call
-  trace(Callstack_depth+1, "run") << "return" << end();
-  --Callstack_depth;
-  EIP = pop();
-  trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
-  break;
-}
diff --git a/subx/020syscalls.cc b/subx/020syscalls.cc
deleted file mode 100644
index 16495411..00000000
--- a/subx/020syscalls.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-:(before "End Initialize Op Names")
-put_new(Name, "cd", "software interrupt (int)");
-
-:(before "End Single-Byte Opcodes")
-case 0xcd: {  // int imm8 (software interrupt)
-  trace(Callstack_depth+1, "run") << "syscall" << end();
-  uint8_t code = next();
-  if (code != 0x80) {
-    raise << "Unimplemented interrupt code " << HEXBYTE << code << '\n' << end();
-    raise << "  Only `int 80h` supported for now.\n" << end();
-    break;
-  }
-  process_int80();
-  break;
-}
-
-:(code)
-void process_int80() {
-  switch (Reg[EAX].u) {
-  case 1:
-    exit(/*exit code*/Reg[EBX].u);
-    break;
-  case 3:
-    trace(Callstack_depth+1, "run") << "read: " << Reg[EBX].u << ' ' << Reg[ECX].u << ' ' << Reg[EDX].u << end();
-    Reg[EAX].i = read(/*file descriptor*/Reg[EBX].u, /*memory buffer*/mem_addr_u8(Reg[ECX].u), /*size*/Reg[EDX].u);
-    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << "read: " << strerror(errno) << '\n' << end();
-    break;
-  case 4:
-    trace(Callstack_depth+1, "run") << "write: " << Reg[EBX].u << ' ' << Reg[ECX].u << ' ' << Reg[EDX].u << end();
-    trace(Callstack_depth+1, "run") << Reg[ECX].u << " => " << mem_addr_string(Reg[ECX].u, Reg[EDX].u) << end();
-    Reg[EAX].i = write(/*file descriptor*/Reg[EBX].u, /*memory buffer*/mem_addr_u8(Reg[ECX].u), /*size*/Reg[EDX].u);
-    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << "write: " << strerror(errno) << '\n' << end();
-    break;
-  case 5: {
-    check_flags(ECX);
-    check_mode(EDX);
-    trace(Callstack_depth+1, "run") << "open: " << Reg[EBX].u << ' ' << Reg[ECX].u << end();
-    trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
-    Reg[EAX].i = open(/*filename*/mem_addr_kernel_string(Reg[EBX].u), /*flags*/Reg[ECX].u, /*mode*/0640);
-    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << "open: " << strerror(errno) << '\n' << end();
-    break;
-  }
-  case 6:
-    trace(Callstack_depth+1, "run") << "close: " << Reg[EBX].u << end();
-    Reg[EAX].i = close(/*file descriptor*/Reg[EBX].u);
-    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << "close: " << strerror(errno) << '\n' << end();
-    break;
-  case 8:
-    check_mode(ECX);
-    trace(Callstack_depth+1, "run") << "creat: " << Reg[EBX].u << end();
-    trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
-    Reg[EAX].i = creat(/*filename*/mem_addr_kernel_string(Reg[EBX].u), /*mode*/0640);
-    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << "creat: " << strerror(errno) << '\n' << end();
-    break;
-  case 10:
-    trace(Callstack_depth+1, "run") << "unlink: " << Reg[EBX].u << end();
-    trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
-    Reg[EAX].i = unlink(/*filename*/mem_addr_kernel_string(Reg[EBX].u));
-    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << "unlink: " << strerror(errno) << '\n' << end();
-    break;
-  case 38:
-    trace(Callstack_depth+1, "run") << "rename: " << Reg[EBX].u << " -> " << Reg[ECX].u << end();
-    trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
-    trace(Callstack_depth+1, "run") << Reg[ECX].u << " => " << mem_addr_kernel_string(Reg[ECX].u) << end();
-    Reg[EAX].i = rename(/*old filename*/mem_addr_kernel_string(Reg[EBX].u), /*new filename*/mem_addr_kernel_string(Reg[ECX].u));
-    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << "rename: " << strerror(errno) << '\n' << end();
-    break;
-  case 90:  // mmap: allocate memory outside existing segment allocations
-    trace(Callstack_depth+1, "run") << "mmap: allocate new segment" << end();
-    // Ignore most arguments for now: address hint, protection flags, sharing flags, fd, offset.
-    // We only support anonymous maps.
-    Reg[EAX].u = new_segment(/*length*/read_mem_u32(Reg[EBX].u+0x4));
-    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].u << end();
-    break;
-  default:
-    raise << HEXWORD << EIP << ": unimplemented syscall " << Reg[EAX].u << '\n' << end();
-  }
-}
-
-// SubX is oblivious to file permissions, directories, symbolic links, terminals, and much else besides.
-// Also ignoring any concurrency considerations for now.
-void check_flags(int reg) {
-  uint32_t flags = Reg[reg].u;
-  if (flags != ((flags & O_RDONLY) | (flags & O_WRONLY))) {
-    cerr << HEXWORD << EIP << ": most POSIX flags to the open() syscall are not supported. Just O_RDONLY and O_WRONLY for now. Zero concurrent access support.\n";
-    exit(1);
-  }
-  if ((flags & O_RDONLY) && (flags & O_WRONLY)) {
-    cerr << HEXWORD << EIP << ": can't open a file for both reading and writing at once. See http://man7.org/linux/man-pages/man2/open.2.html.\n";
-    exit(1);
-  }
-}
-
-void check_mode(int reg) {
-  if (Reg[reg].u != 0600) {
-    cerr << HEXWORD << EIP << ": SubX is oblivious to file permissions; register " << reg << " must be 0.\n";
-    exit(1);
-  }
-}
-
-:(before "End Globals")
-// Very primitive/fixed/insecure mmap segments for now.
-uint32_t Segments_allocated_above = END_HEAP;
-:(code)
-// always allocate multiples of the segment size
-uint32_t new_segment(uint32_t length) {
-  assert(length > 0);
-  uint32_t result = (Segments_allocated_above - length) & 0xff000000;  // same number of zeroes as SEGMENT_ALIGNMENT
-  if (result <= START_HEAP) {
-    raise << "Allocated too many segments; the VM ran out of memory. "
-          << "Maybe SEGMENT_ALIGNMENT can be smaller?\n" << die();
-  }
-  Mem.push_back(vma(result, result+length));
-  Segments_allocated_above = result;
-  return result;
-}
diff --git a/subx/021byte_addressing.cc b/subx/021byte_addressing.cc
deleted file mode 100644
index a0b36776..00000000
--- a/subx/021byte_addressing.cc
+++ /dev/null
@@ -1,176 +0,0 @@
-//: SubX mostly deals with instructions operating on 32-bit operands, but we
-//: still need to deal with raw bytes for strings and so on.
-
-//: Unfortunately the register encodings when dealing with bytes are a mess.
-//: We need a special case for them.
-:(code)
-string rname_8bit(uint8_t r) {
-  switch (r) {
-  case 0: return "AL";  // lowest byte of EAX
-  case 1: return "CL";  // lowest byte of ECX
-  case 2: return "DL";  // lowest byte of EDX
-  case 3: return "BL";  // lowest byte of EBX
-  case 4: return "AH";  // second lowest byte of EAX
-  case 5: return "CH";  // second lowest byte of ECX
-  case 6: return "DH";  // second lowest byte of EDX
-  case 7: return "BH";  // second lowest byte of EBX
-  default: raise << "invalid 8-bit register " << r << '\n' << end();  return "";
-  }
-}
-
-uint8_t* effective_byte_address(uint8_t modrm) {
-  uint8_t mod = (modrm>>6);
-  uint8_t rm = modrm & 0x7;
-  if (mod == 3) {
-    // select an 8-bit register
-    trace(Callstack_depth+1, "run") << "r/m8 is " << rname_8bit(rm) << end();
-    return reg_8bit(rm);
-  }
-  // the rest is as usual
-  return mem_addr_u8(effective_address_number(modrm));
-}
-
-uint8_t* reg_8bit(uint8_t rm) {
-  uint8_t* result = reinterpret_cast<uint8_t*>(&Reg[rm & 0x3].i);  // _L register
-  if (rm & 0x4)
-    ++result;  // _H register;  assumes host is little-endian
-  return result;
-}
-
-:(before "End Initialize Op Names")
-put_new(Name, "88", "copy r8 to r8/m8-at-r32");
-
-:(code)
-void test_copy_r8_to_mem_at_r32() {
-  Reg[EBX].i = 0x224488ab;
-  Reg[EAX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  88     18                                      \n"  // copy BL to the byte at *EAX
-      // ModR/M in binary: 00 (indirect mode) 011 (src BL) 000 (dest EAX)
-      "== data 0x2000\n"
-      "f0 cc bb aa\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy BL to r8/m8-at-r32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0xab\n"
-  );
-  CHECK_EQ(0xaabbccab, read_mem_u32(0x2000));
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x88: {  // copy r8 to r/m8
-  const uint8_t modrm = next();
-  const uint8_t rsrc = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "copy " << rname_8bit(rsrc) << " to r8/m8-at-r32" << end();
-  // use unsigned to zero-extend 8-bit value to 32 bits
-  uint8_t* dest = reinterpret_cast<uint8_t*>(effective_byte_address(modrm));
-  const uint8_t* src = reg_8bit(rsrc);
-  *dest = *src;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXBYTE << NUM(*dest) << end();
-  break;
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "8a", "copy r8/m8-at-r32 to r8");
-
-:(code)
-void test_copy_mem_at_r32_to_r8() {
-  Reg[EBX].i = 0xaabbcc0f;  // one nibble each of lowest byte set to all 0s and all 1s, to maximize value of this test
-  Reg[EAX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  8a     18                                      \n"  // copy just the byte at *EAX to BL
-      // ModR/M in binary: 00 (indirect mode) 011 (dest EBX) 000 (src EAX)
-      "== data 0x2000\n"
-      "ab ff ff ff\n"  // 0xab with more data in following bytes
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy r8/m8-at-r32 to BL\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0xab\n"
-      // remaining bytes of EBX are *not* cleared
-      "run: EBX now contains 0xaabbccab\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x8a: {  // copy r/m8 to r8
-  const uint8_t modrm = next();
-  const uint8_t rdest = (modrm>>3)&0x7;
-  trace(Callstack_depth+1, "run") << "copy r8/m8-at-r32 to " << rname_8bit(rdest) << end();
-  // use unsigned to zero-extend 8-bit value to 32 bits
-  const uint8_t* src = reinterpret_cast<uint8_t*>(effective_byte_address(modrm));
-  uint8_t* dest = reg_8bit(rdest);
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXBYTE << NUM(*src) << end();
-  *dest = *src;
-  const uint8_t rdest_32bit = rdest & 0x3;
-  trace(Callstack_depth+1, "run") << rname(rdest_32bit) << " now contains 0x" << HEXWORD << Reg[rdest_32bit].u << end();
-  break;
-}
-
-:(code)
-void test_cannot_copy_byte_to_ESP_EBP_ESI_EDI() {
-  Reg[ESI].u = 0xaabbccdd;
-  Reg[EBX].u = 0x11223344;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  8a     f3                                      \n"  // copy just the byte at *EBX to 8-bit register '6'
-      // ModR/M in binary: 11 (direct mode) 110 (dest 8-bit 'register 6') 011 (src EBX)
-  );
-  CHECK_TRACE_CONTENTS(
-      // ensure 8-bit register '6' is DH, not ESI
-      "run: copy r8/m8-at-r32 to DH\n"
-      "run: storing 0x44\n"
-  );
-  // ensure ESI is unchanged
-  CHECK_EQ(Reg[ESI].u, 0xaabbccdd);
-}
-
-//:
-
-:(before "End Initialize Op Names")
-put_new(Name, "c6", "copy imm8 to r8/m8-at-r32 (mov)");
-
-:(code)
-void test_copy_imm8_to_mem_at_r32() {
-  Reg[EAX].i = 0x2000;
-  run(
-      "== code 0x1\n"
-      // op     ModR/M  SIB   displacement  immediate
-      "  c6     00                          dd          \n"  // copy to the byte at *EAX
-      // ModR/M in binary: 00 (indirect mode) 000 (unused) 000 (dest EAX)
-      "== data 0x2000\n"
-      "f0 cc bb aa\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: copy imm8 to r8/m8-at-r32\n"
-      "run: effective address is 0x00002000 (EAX)\n"
-      "run: storing 0xdd\n"
-  );
-  CHECK_EQ(0xaabbccdd, read_mem_u32(0x2000));
-}
-
-:(before "End Single-Byte Opcodes")
-case 0xc6: {  // copy imm8 to r/m8
-  const uint8_t modrm = next();
-  const uint8_t src = next();
-  trace(Callstack_depth+1, "run") << "copy imm8 to r8/m8-at-r32" << end();
-  trace(Callstack_depth+1, "run") << "imm8 is 0x" << HEXWORD << NUM(src) << end();
-  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
-  if (subop != 0) {
-    cerr << "unrecognized subop for opcode c6: " << NUM(subop) << " (only 0/copy currently implemented)\n";
-    exit(1);
-  }
-  // use unsigned to zero-extend 8-bit value to 32 bits
-  uint8_t* dest = reinterpret_cast<uint8_t*>(effective_byte_address(modrm));
-  *dest = src;
-  trace(Callstack_depth+1, "run") << "storing 0x" << HEXBYTE << NUM(*dest) << end();
-  break;
-}
diff --git a/subx/022div.cc b/subx/022div.cc
deleted file mode 100644
index 15ed89d8..00000000
--- a/subx/022div.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-//: helper for division operations: sign-extend EAX into EDX
-
-:(before "End Initialize Op Names")
-put_new(Name, "99", "sign-extend EAX into EDX (cdq)");
-
-:(code)
-void test_cdq() {
-  Reg[EAX].i = 10;
-  run(
-      "== code 0x1\n"
-      "99\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: sign-extend EAX into EDX\n"
-      "run: EDX is now 0x00000000\n"
-  );
-}
-
-:(before "End Single-Byte Opcodes")
-case 0x99: {  // sign-extend EAX into EDX
-  trace(Callstack_depth+1, "run") << "sign-extend EAX into EDX" << end();
-  Reg[EDX].i = (Reg[EAX].i < 0) ? -1 : 0;
-  trace(Callstack_depth+1, "run") << "EDX is now 0x" << HEXWORD << Reg[EDX].u << end();
-  break;
-}
-
-:(code)
-void test_cdq_negative() {
-  Reg[EAX].i = -10;
-  run(
-      "== code 0x1\n"
-      "99\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: sign-extend EAX into EDX\n"
-      "run: EDX is now 0xffffffff\n"
-  );
-}
diff --git a/subx/028translate.cc b/subx/028translate.cc
deleted file mode 100644
index d3a6a8ac..00000000
--- a/subx/028translate.cc
+++ /dev/null
@@ -1,212 +0,0 @@
-//: The bedrock level 1 of abstraction is now done, and we're going to start
-//: building levels above it that make programming in x86 machine code a
-//: little more ergonomic.
-//:
-//: All levels will be "pass through by default". Whatever they don't
-//: understand they will silently pass through to lower levels.
-//:
-//: Since raw hex bytes of machine code are always possible to inject, SubX is
-//: not a language, and we aren't building a compiler. This is something
-//: deliberately leakier. Levels are more for improving auditing, checks and
-//: error messages rather than for hiding low-level details.
-
-//: Translator workflow: read 'source' file. Run a series of transforms on it,
-//: each passing through what it doesn't understand. The final program should
-//: be just machine code, suitable to write to an ELF binary.
-//:
-//: Higher levels usually transform code on the basis of metadata.
-
-:(before "End Main")
-if (is_equal(argv[1], "translate")) {
-  START_TRACING_UNTIL_END_OF_SCOPE;
-  reset();
-  // Begin subx translate
-  program p;
-  string output_filename;
-  for (int i = /*skip 'subx translate'*/2;  i < argc;  ++i) {
-    if (is_equal(argv[i], "-o")) {
-      ++i;
-      if (i >= argc) {
-        print_translate_usage();
-        cerr << "'-o' must be followed by a filename to write results to\n";
-        exit(1);
-      }
-      output_filename = argv[i];
-    }
-    else {
-      trace(2, "parse") << argv[i] << end();
-      ifstream fin(argv[i]);
-      if (!fin) {
-        cerr << "could not open " << argv[i] << '\n';
-        return 1;
-      }
-      parse(fin, p);
-      if (trace_contains_errors()) return 1;
-    }
-  }
-  if (p.segments.empty()) {
-    print_translate_usage();
-    cerr << "nothing to do; must provide at least one file to read\n";
-    exit(1);
-  }
-  if (output_filename.empty()) {
-    print_translate_usage();
-    cerr << "must provide a filename to write to using '-o'\n";
-    exit(1);
-  }
-  trace(2, "transform") << "begin" << end();
-  transform(p);
-  if (trace_contains_errors()) return 1;
-  trace(2, "translate") << "begin" << end();
-  save_elf(p, output_filename);
-  if (trace_contains_errors()) {
-    unlink(output_filename.c_str());
-    return 1;
-  }
-  // End subx translate
-  return 0;
-}
-
-:(code)
-void print_translate_usage() {
-  cerr << "Usage: subx translate file1 file2 ... -o output\n";
-}
-
-// write out a program to a bare-bones ELF file
-void save_elf(const program& p, const string& filename) {
-  ofstream out(filename.c_str(), ios::binary);
-  save_elf(p, out);
-  out.close();
-}
-
-void save_elf(const program& p, ostream& out) {
-  // validation: stay consistent with the self-hosted translator
-  if (p.entry == 0) {
-    raise << "no 'Entry' label found\n" << end();
-    return;
-  }
-  if (find(p, "data") == NULL) {
-    raise << "must include a 'data' segment\n" << end();
-    return;
-  }
-  // processing
-  write_elf_header(out, p);
-  for (size_t i = 0;  i < p.segments.size();  ++i)
-    write_segment(p.segments.at(i), out);
-}
-
-void write_elf_header(ostream& out, const program& p) {
-  char c = '\0';
-#define O(X)  c = (X); out.write(&c, sizeof(c))
-// host is required to be little-endian
-#define emit(X)  out.write(reinterpret_cast<const char*>(&X), sizeof(X))
-  //// ehdr
-  // e_ident
-  O(0x7f); O(/*E*/0x45); O(/*L*/0x4c); O(/*F*/0x46);
-    O(0x1);  // 32-bit format
-    O(0x1);  // little-endian
-    O(0x1); O(0x0);
-  for (size_t i = 0;  i < 8;  ++i) { O(0x0); }
-  // e_type
-  O(0x02); O(0x00);
-  // e_machine
-  O(0x03); O(0x00);
-  // e_version
-  O(0x01); O(0x00); O(0x00); O(0x00);
-  // e_entry
-  uint32_t e_entry = p.entry;
-  // Override e_entry
-  emit(e_entry);
-  // e_phoff -- immediately after ELF header
-  uint32_t e_phoff = 0x34;
-  emit(e_phoff);
-  // e_shoff; unused
-  uint32_t dummy32 = 0;
-  emit(dummy32);
-  // e_flags; unused
-  emit(dummy32);
-  // e_ehsize
-  uint16_t e_ehsize = 0x34;
-  emit(e_ehsize);
-  // e_phentsize
-  uint16_t e_phentsize = 0x20;
-  emit(e_phentsize);
-  // e_phnum
-  uint16_t e_phnum = SIZE(p.segments);
-  emit(e_phnum);
-  // e_shentsize
-  uint16_t dummy16 = 0x0;
-  emit(dummy16);
-  // e_shnum
-  emit(dummy16);
-  // e_shstrndx
-  emit(dummy16);
-
-  uint32_t p_offset = /*size of ehdr*/0x34 + SIZE(p.segments)*0x20/*size of each phdr*/;
-  for (int i = 0;  i < SIZE(p.segments);  ++i) {
-    const segment& curr = p.segments.at(i);
-    //// phdr
-    // p_type
-    uint32_t p_type = 0x1;
-    emit(p_type);
-    // p_offset
-    emit(p_offset);
-    // p_vaddr
-    uint32_t p_start = curr.start;
-    emit(p_start);
-    // p_paddr
-    emit(p_start);
-    // p_filesz
-    uint32_t size = num_words(curr);
-    assert(p_offset + size < SEGMENT_ALIGNMENT);
-    emit(size);
-    // p_memsz
-    emit(size);
-    // p_flags
-    uint32_t p_flags = (curr.name == "code") ? /*r-x*/0x5 : /*rw-*/0x6;
-    emit(p_flags);
-
-    // p_align
-    // "As the system creates or augments a process image, it logically copies
-    // a file's segment to a virtual memory segment.  When—and if— the system
-    // physically reads the file depends on the program's execution behavior,
-    // system load, and so on.  A process does not require a physical page
-    // unless it references the logical page during execution, and processes
-    // commonly leave many pages unreferenced. Therefore delaying physical
-    // reads frequently obviates them, improving system performance. To obtain
-    // this efficiency in practice, executable and shared object files must
-    // have segment images whose file offsets and virtual addresses are
-    // congruent, modulo the page size." -- http://refspecs.linuxbase.org/elf/elf.pdf (page 95)
-    uint32_t p_align = 0x1000;  // default page size on linux
-    emit(p_align);
-    if (p_offset % p_align != p_start % p_align) {
-      raise << "segment starting at 0x" << HEXWORD << p_start << " is improperly aligned; alignment for p_offset " << p_offset << " should be " << (p_offset % p_align) << " but is " << (p_start % p_align) << '\n' << end();
-      return;
-    }
-
-    // prepare for next segment
-    p_offset += size;
-  }
-#undef O
-#undef emit
-}
-
-void write_segment(const segment& s, ostream& out) {
-  for (int i = 0;  i < SIZE(s.lines);  ++i) {
-    const vector<word>& w = s.lines.at(i).words;
-    for (int j = 0;  j < SIZE(w);  ++j) {
-      uint8_t x = hex_byte(w.at(j).data);  // we're done with metadata by this point
-      out.write(reinterpret_cast<const char*>(&x), /*sizeof(byte)*/1);
-    }
-  }
-}
-
-uint32_t num_words(const segment& s) {
-  uint32_t sum = 0;
-  for (int i = 0;  i < SIZE(s.lines);  ++i)
-    sum += SIZE(s.lines.at(i).words);
-  return sum;
-}
-
-:(before "End Includes")
-using std::ios;
diff --git a/subx/029transforms.cc b/subx/029transforms.cc
deleted file mode 100644
index a6e12502..00000000
--- a/subx/029transforms.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-//: Ordering transforms is a well-known hard problem when building compilers.
-//: In our case we also have the additional notion of layers. The ordering of
-//: layers can have nothing in common with the ordering of transforms when
-//: SubX is tangled and run. This can be confusing for readers, particularly
-//: if later layers start inserting transforms at arbitrary points between
-//: transforms introduced earlier. Over time adding transforms can get harder
-//: and harder, having to meet the constraints of everything that's come
-//: before. It's worth thinking about organization up-front so the ordering is
-//: easy to hold in our heads, and it's obvious where to add a new transform.
-//: Some constraints:
-//:
-//:   1. Layers force us to build SubX bottom-up; since we want to be able to
-//:   build and run SubX after stopping loading at any layer, the overall
-//:   organization has to be to introduce primitives before we start using
-//:   them.
-//:
-//:   2. Transforms usually need to be run top-down, converting high-level
-//:   representations to low-level ones so that low-level layers can be
-//:   oblivious to them.
-//:
-//:   3. When running we'd often like new representations to be checked before
-//:   they are transformed away. The whole reason for new representations is
-//:   often to add new kinds of automatic checking for our machine code
-//:   programs.
-//:
-//: Putting these constraints together, we'll use the following broad
-//: organization:
-//:
-//:   a) We'll divide up our transforms into "levels", each level consisting
-//:   of multiple transforms, and dealing in some new set of representational
-//:   ideas. Levels will be added in reverse order to the one their transforms
-//:   will be run in.
-//:
-//:     To run all transforms:
-//:       Load transforms for level n
-//:       Load transforms for level n-1
-//:       ...
-//:       Load transforms for level 2
-//:       Run code at level 1
-//:
-//:   b) *Within* a level we'll usually introduce transforms in the order
-//:   they're run in.
-//:
-//:     To run transforms for level n:
-//:       Perform transform of layer l
-//:       Perform transform of layer l+1
-//:       ...
-//:
-//:   c) Within a level it's often most natural to introduce a new
-//:   representation by showing how it's transformed to the level below. To
-//:   make such exceptions more obvious checks usually won't be first-class
-//:   transforms; instead code that keeps the program unmodified will run
-//:   within transforms before they mutate the program. As an example:
-//:
-//:     Layer l introduces a transform
-//:     Layer l+1 adds precondition checks for the transform
-//:
-//: This may all seem abstract, but will hopefully make sense over time. The
-//: goals are basically to always have a working program after any layer, to
-//: have the order of layers make narrative sense, and to order transforms
-//: correctly at runtime.
-
-:(before "End One-time Setup")
-// Begin Transforms
-// End Transforms
diff --git a/subx/030---operands.cc b/subx/030---operands.cc
deleted file mode 100644
index 5203201e..00000000
--- a/subx/030---operands.cc
+++ /dev/null
@@ -1,539 +0,0 @@
-//: Beginning of "level 2": tagging bytes with metadata around what field of
-//: an x86 instruction they're for.
-//:
-//: The x86 instruction set is variable-length, and how a byte is interpreted
-//: affects later instruction boundaries. A lot of the pain in programming
-//: machine code stems from computer and programmer going out of sync on what
-//: a byte means. The miscommunication is usually not immediately caught, and
-//: metastasizes at runtime into kilobytes of misinterpreted instructions.
-//:
-//: To mitigate these issues, we'll start programming in terms of logical
-//: operands rather than physical bytes. Some operands are smaller than a
-//: byte, and others may consist of multiple bytes. This layer will correctly
-//: pack and order the bytes corresponding to the operands in an instruction.
-
-:(before "End Help Texts")
-put_new(Help, "instructions",
-  "Each x86 instruction consists of an instruction or opcode and some number\n"
-  "of operands.\n"
-  "Each operand has a type. An instruction won't have more than one operand of\n"
-  "any type.\n"
-  "Each instruction has some set of allowed operand types. It'll reject others.\n"
-  "The complete list of operand types: mod, subop, r32 (register), rm32\n"
-  "(register or memory), scale, index, base, disp8, disp16, disp32, imm8,\n"
-  "imm32.\n"
-  "Each of these has its own help page. Try reading 'subx help mod' next.\n"
-);
-:(before "End Help Contents")
-cerr << "  instructions\n";
-
-:(code)
-void test_pack_immediate_constants() {
-  run(
-      "== code 0x1\n"
-      "bb  0x2a/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction 'bb 0x2a/imm32'\n"
-      "transform: instruction after packing: 'bb 2a 00 00 00'\n"
-      "run: copy imm32 0x0000002a to EBX\n"
-  );
-}
-
-//: complete set of valid operand types
-
-:(before "End Globals")
-set<string> Instruction_operands;
-:(before "End One-time Setup")
-Instruction_operands.insert("subop");
-Instruction_operands.insert("mod");
-Instruction_operands.insert("rm32");
-Instruction_operands.insert("base");
-Instruction_operands.insert("index");
-Instruction_operands.insert("scale");
-Instruction_operands.insert("r32");
-Instruction_operands.insert("disp8");
-Instruction_operands.insert("disp16");
-Instruction_operands.insert("disp32");
-Instruction_operands.insert("imm8");
-Instruction_operands.insert("imm32");
-
-:(before "End Help Texts")
-init_operand_type_help();
-:(code)
-void init_operand_type_help() {
-  put(Help, "mod",
-    "2-bit operand controlling the _addressing mode_ of many instructions,\n"
-    "to determine how to compute the _effective address_ to look up memory at\n"
-    "based on the 'rm32' operand and potentially others.\n"
-    "\n"
-    "If mod = 3, just operate on the contents of the register specified by rm32\n"
-    "            (direct mode).\n"
-    "If mod = 2, effective address is usually* rm32 + disp32\n"
-    "            (indirect mode with displacement).\n"
-    "If mod = 1, effective address is usually* rm32 + disp8\n"
-    "            (indirect mode with displacement).\n"
-    "If mod = 0, effective address is usually* rm32 (indirect mode).\n"
-    "(* - The exception is when rm32 is '4'. Register 4 is the stack pointer (ESP).\n"
-    "     Using it as an address gets more involved. For more details,\n"
-    "     try reading the help pages for 'base', 'index' and 'scale'.)\n"
-    "\n"
-    "For complete details, spend some time with two tables in the IA-32 software\n"
-    "developer's manual that are also included in this repo:\n"
-    "  - modrm.pdf: volume 2, table 2-2, \"32-bit addressing with the ModR/M byte.\".\n"
-    "  - sib.pdf: volume 2, table 2-3, \"32-bit addressing with the SIB byte.\".\n"
-  );
-  put(Help, "subop",
-    "Additional 3-bit operand for determining the instruction when the opcode\n"
-    "is 81, 8f, d3, f7 or ff.\n"
-    "Can't coexist with operand of type 'r32' in a single instruction, because\n"
-    "the two use the same bits.\n"
-  );
-  put(Help, "r32",
-    "3-bit operand specifying a register operand used directly, without any further addressing modes.\n"
-  );
-  put(Help, "rm32",
-    "32-bit value in register or memory. The precise details of its construction\n"
-    "depend on the eponymous 3-bit 'rm32' operand, the 'mod' operand, and also\n"
-    "potentially the 'SIB' operands ('scale', 'index' and 'base') and a displacement\n"
-    "('disp8' or 'disp32').\n"
-    "\n"
-    "For complete details, spend some time with two tables in the IA-32 software\n"
-    "developer's manual that are also included in this repo:\n"
-    "  - modrm.pdf: volume 2, table 2-2, \"32-bit addressing with the ModR/M byte.\".\n"
-    "  - sib.pdf: volume 2, table 2-3, \"32-bit addressing with the SIB byte.\".\n"
-  );
-  put(Help, "base",
-    "Additional 3-bit operand (when 'rm32' is 4, unless 'mod' is 3) specifying the\n"
-    "register containing an address to look up.\n"
-    "This address may be further modified by 'index' and 'scale' operands.\n"
-    "  effective address = base + index*scale + displacement (disp8 or disp32)\n"
-    "For complete details, spend some time with the IA-32 software developer's manual,\n"
-    "volume 2, table 2-3, \"32-bit addressing with the SIB byte\".\n"
-    "It is included in this repository as 'sib.pdf'.\n"
-  );
-  put(Help, "index",
-    "Optional 3-bit operand (when 'rm32' is 4 unless 'mod' is 3) that can be added to\n"
-    "the 'base' operand to compute the 'effective address' at which to look up memory.\n"
-    "  effective address = base + index*scale + displacement (disp8 or disp32)\n"
-    "For complete details, spend some time with the IA-32 software developer's manual,\n"
-    "volume 2, table 2-3, \"32-bit addressing with the SIB byte\".\n"
-    "It is included in this repository as 'sib.pdf'.\n"
-  );
-  put(Help, "scale",
-    "Optional 2-bit operand (when 'rm32' is 4 unless 'mod' is 3) that encodes a\n"
-    "power of 2 to be multiplied to the 'index' operand before adding the result to\n"
-    "the 'base' operand to compute the _effective address_ to operate on.\n"
-    "  effective address = base + index * scale + displacement (disp8 or disp32)\n"
-    "\n"
-    "When scale is 0, use index unmodified.\n"
-    "When scale is 1, multiply index by 2.\n"
-    "When scale is 2, multiply index by 4.\n"
-    "When scale is 3, multiply index by 8.\n"
-    "\n"
-    "For complete details, spend some time with the IA-32 software developer's manual,\n"
-    "volume 2, table 2-3, \"32-bit addressing with the SIB byte\".\n"
-    "It is included in this repository as 'sib.pdf'.\n"
-  );
-  put(Help, "disp8",
-    "8-bit value to be added in many instructions.\n"
-  );
-  put(Help, "disp16",
-    "16-bit value to be added in many instructions.\n"
-    "Currently not used in any SubX instructions.\n"
-  );
-  put(Help, "disp32",
-    "32-bit value to be added in many instructions.\n"
-  );
-  put(Help, "imm8",
-    "8-bit value for many instructions.\n"
-  );
-  put(Help, "imm32",
-    "32-bit value for many instructions.\n"
-  );
-}
-
-//:: transform packing operands into bytes in the right order
-
-:(after "Begin Transforms")
-// Begin Level-2 Transforms
-Transform.push_back(pack_operands);
-// End Level-2 Transforms
-
-:(code)
-void pack_operands(program& p) {
-  if (p.segments.empty()) return;
-  segment& code = *find(p, "code");
-  // Pack Operands(segment code)
-  trace(3, "transform") << "-- pack operands" << end();
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    line& inst = code.lines.at(i);
-    if (all_hex_bytes(inst)) continue;
-    trace(99, "transform") << "packing instruction '" << to_string(/*with metadata*/inst) << "'" << end();
-    pack_operands(inst);
-    trace(99, "transform") << "instruction after packing: '" << to_string(/*without metadata*/inst.words) << "'" << end();
-  }
-}
-
-void pack_operands(line& inst) {
-  line new_inst;
-  add_opcodes(inst, new_inst);
-  add_modrm_byte(inst, new_inst);
-  add_sib_byte(inst, new_inst);
-  add_disp_bytes(inst, new_inst);
-  add_imm_bytes(inst, new_inst);
-  inst.words.swap(new_inst.words);
-}
-
-void add_opcodes(const line& in, line& out) {
-  out.words.push_back(in.words.at(0));
-  if (in.words.at(0).data == "0f" || in.words.at(0).data == "f2" || in.words.at(0).data == "f3")
-    out.words.push_back(in.words.at(1));
-  if (in.words.at(0).data == "f3" && in.words.at(1).data == "0f")
-    out.words.push_back(in.words.at(2));
-  if (in.words.at(0).data == "f2" && in.words.at(1).data == "0f")
-    out.words.push_back(in.words.at(2));
-}
-
-void add_modrm_byte(const line& in, line& out) {
-  uint8_t mod=0, reg_subop=0, rm32=0;
-  bool emit = false;
-  for (int i = 0;  i < SIZE(in.words);  ++i) {
-    const word& curr = in.words.at(i);
-    if (has_operand_metadata(curr, "mod")) {
-      mod = hex_byte(curr.data);
-      emit = true;
-    }
-    else if (has_operand_metadata(curr, "rm32")) {
-      rm32 = hex_byte(curr.data);
-      emit = true;
-    }
-    else if (has_operand_metadata(curr, "r32")) {
-      reg_subop = hex_byte(curr.data);
-      emit = true;
-    }
-    else if (has_operand_metadata(curr, "subop")) {
-      reg_subop = hex_byte(curr.data);
-      emit = true;
-    }
-  }
-  if (emit)
-    out.words.push_back(hex_byte_text((mod << 6) | (reg_subop << 3) | rm32));
-}
-
-void add_sib_byte(const line& in, line& out) {
-  uint8_t scale=0, index=0, base=0;
-  bool emit = false;
-  for (int i = 0;  i < SIZE(in.words);  ++i) {
-    const word& curr = in.words.at(i);
-    if (has_operand_metadata(curr, "scale")) {
-      scale = hex_byte(curr.data);
-      emit = true;
-    }
-    else if (has_operand_metadata(curr, "index")) {
-      index = hex_byte(curr.data);
-      emit = true;
-    }
-    else if (has_operand_metadata(curr, "base")) {
-      base = hex_byte(curr.data);
-      emit = true;
-    }
-  }
-  if (emit)
-    out.words.push_back(hex_byte_text((scale << 6) | (index << 3) | base));
-}
-
-void add_disp_bytes(const line& in, line& out) {
-  for (int i = 0;  i < SIZE(in.words);  ++i) {
-    const word& curr = in.words.at(i);
-    if (has_operand_metadata(curr, "disp8"))
-      emit_hex_bytes(out, curr, 1);
-    if (has_operand_metadata(curr, "disp16"))
-      emit_hex_bytes(out, curr, 2);
-    else if (has_operand_metadata(curr, "disp32"))
-      emit_hex_bytes(out, curr, 4);
-  }
-}
-
-void add_imm_bytes(const line& in, line& out) {
-  for (int i = 0;  i < SIZE(in.words);  ++i) {
-    const word& curr = in.words.at(i);
-    if (has_operand_metadata(curr, "imm8"))
-      emit_hex_bytes(out, curr, 1);
-    else if (has_operand_metadata(curr, "imm32"))
-      emit_hex_bytes(out, curr, 4);
-  }
-}
-
-void emit_hex_bytes(line& out, const word& w, int num) {
-  assert(num <= 4);
-  bool is_number = looks_like_hex_int(w.data);
-  if (num == 1 || !is_number) {
-    out.words.push_back(w);  // preserve existing metadata
-    if (is_number)
-      out.words.back().data = hex_byte_to_string(parse_int(w.data));
-    return;
-  }
-  emit_hex_bytes(out, static_cast<uint32_t>(parse_int(w.data)), num);
-}
-
-void emit_hex_bytes(line& out, uint32_t val, int num) {
-  assert(num <= 4);
-  for (int i = 0;  i < num;  ++i) {
-    out.words.push_back(hex_byte_text(val & 0xff));
-    val = val >> 8;
-  }
-}
-
-word hex_byte_text(uint8_t val) {
-  word result;
-  result.data = hex_byte_to_string(val);
-  result.original = result.data+"/auto";
-  return result;
-}
-
-string hex_byte_to_string(uint8_t val) {
-  ostringstream out;
-  // uint8_t prints without padding, but int8_t will expand to 32 bits again
-  out << HEXBYTE << NUM(val);
-  return out.str();
-}
-
-string to_string(const vector<word>& in) {
-  ostringstream out;
-  for (int i = 0;  i < SIZE(in);  ++i) {
-    if (i > 0) out << ' ';
-    out << in.at(i).data;
-  }
-  return out.str();
-}
-
-:(before "End Unit Tests")
-void test_preserve_metadata_when_emitting_single_byte() {
-  word in;
-  in.data = "f0";
-  in.original = "f0/foo";
-  line out;
-  emit_hex_bytes(out, in, 1);
-  CHECK_EQ(out.words.at(0).data, "f0");
-  CHECK_EQ(out.words.at(0).original, "f0/foo");
-}
-
-:(code)
-void test_pack_disp8() {
-  run(
-      "== code 0x1\n"
-      "74 2/disp8\n"  // jump 2 bytes away if ZF is set
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction '74 2/disp8'\n"
-      "transform: instruction after packing: '74 02'\n"
-  );
-}
-
-void test_pack_disp8_negative() {
-  transform(
-      "== code 0x1\n"
-      // running this will cause an infinite loop
-      "74 -1/disp8\n"  // jump 1 byte before if ZF is set
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction '74 -1/disp8'\n"
-      "transform: instruction after packing: '74 ff'\n"
-  );
-}
-
-//: helper for scenario
-void transform(const string& text_bytes) {
-  program p;
-  istringstream in(text_bytes);
-  parse(in, p);
-  if (trace_contains_errors()) return;
-  transform(p);
-}
-
-void test_pack_modrm_imm32() {
-  run(
-      "== code 0x1\n"
-      // instruction                     effective address                                                   operand     displacement    immediate\n"
-      // op          subop               mod             rm32          base        index         scale       r32\n"
-      // 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\n"
-      "  81          0/add/subop         3/mod/direct    3/ebx/rm32                                                                      1/imm32      \n"  // add 1 to EBX
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction '81 0/add/subop 3/mod/direct 3/ebx/rm32 1/imm32'\n"
-      "transform: instruction after packing: '81 c3 01 00 00 00'\n"
-  );
-}
-
-void test_pack_imm32_large() {
-  run(
-      "== code 0x1\n"
-      "b9  0x080490a7/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction 'b9 0x080490a7/imm32'\n"
-      "transform: instruction after packing: 'b9 a7 90 04 08'\n"
-  );
-}
-
-void test_pack_immediate_constants_hex() {
-  run(
-      "== code 0x1\n"
-      "b9  0x2a/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction 'b9 0x2a/imm32'\n"
-      "transform: instruction after packing: 'b9 2a 00 00 00'\n"
-      "run: copy imm32 0x0000002a to ECX\n"
-  );
-}
-
-void test_pack_silently_ignores_non_hex() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "b9  foo/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction 'b9 foo/imm32'\n"
-      // no change (we're just not printing metadata to the trace)
-      "transform: instruction after packing: 'b9 foo'\n"
-  );
-}
-
-void test_pack_flags_bad_hex() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "b9  0xfoo/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: not a number: 0xfoo\n"
-  );
-}
-
-void test_pack_flags_uppercase_hex() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "b9 0xAb/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: uppercase hex not allowed: 0xAb\n"
-  );
-}
-
-//:: helpers
-
-bool all_hex_bytes(const line& inst) {
-  for (int i = 0;  i < SIZE(inst.words);  ++i)
-    if (!is_hex_byte(inst.words.at(i)))
-      return false;
-  return true;
-}
-
-bool is_hex_byte(const word& curr) {
-  if (contains_any_operand_metadata(curr))
-    return false;
-  if (SIZE(curr.data) != 2)
-    return false;
-  if (curr.data.find_first_not_of("0123456789abcdef") != string::npos)
-    return false;
-  return true;
-}
-
-bool contains_any_operand_metadata(const word& word) {
-  for (int i = 0;  i < SIZE(word.metadata);  ++i)
-    if (Instruction_operands.find(word.metadata.at(i)) != Instruction_operands.end())
-      return true;
-  return false;
-}
-
-bool has_operand_metadata(const line& inst, const string& m) {
-  bool result = false;
-  for (int i = 0;  i < SIZE(inst.words);  ++i) {
-    if (!has_operand_metadata(inst.words.at(i), m)) continue;
-    if (result) {
-      raise << "'" << to_string(inst) << "' has conflicting " << m << " operands\n" << end();
-      return false;
-    }
-    result = true;
-  }
-  return result;
-}
-
-bool has_operand_metadata(const word& w, const string& m) {
-  bool result = false;
-  bool metadata_found = false;
-  for (int i = 0;  i < SIZE(w.metadata);  ++i) {
-    const string& curr = w.metadata.at(i);
-    if (Instruction_operands.find(curr) == Instruction_operands.end()) continue;  // ignore unrecognized metadata
-    if (metadata_found) {
-      raise << "'" << w.original << "' has conflicting operand types; it should have only one\n" << end();
-      return false;
-    }
-    metadata_found = true;
-    result = (curr == m);
-  }
-  return result;
-}
-
-word metadata(const line& inst, const string& m) {
-  for (int i = 0;  i < SIZE(inst.words);  ++i)
-    if (has_operand_metadata(inst.words.at(i), m))
-      return inst.words.at(i);
-  assert(false);
-}
-
-bool looks_like_hex_int(const string& s) {
-  if (s.empty()) return false;
-  if (s.at(0) == '-' || s.at(0) == '+') return true;
-  if (isdigit(s.at(0))) return true;  // includes '0x' prefix
-  // End looks_like_hex_int(s) Detectors
-  return false;
-}
-
-string to_string(const line& inst) {
-  ostringstream out;
-  for (int i = 0;  i < SIZE(inst.words);  ++i) {
-    if (i > 0) out << ' ';
-    out << inst.words.at(i).original;
-  }
-  return out.str();
-}
-
-int32_t parse_int(const string& s) {
-  if (s.empty()) return 0;
-  if (contains_uppercase(s)) {
-    raise << "uppercase hex not allowed: " << s << '\n' << end();
-    return 0;
-  }
-  istringstream in(s);
-  in >> std::hex;
-  if (s.at(0) == '-') {
-    int32_t result = 0;
-    in >> result;
-    if (!in || !in.eof()) {
-      raise << "not a number: " << s << '\n' << end();
-      return 0;
-    }
-    return result;
-  }
-  uint32_t uresult = 0;
-  in >> uresult;
-  if (!in || !in.eof()) {
-    raise << "not a number: " << s << '\n' << end();
-    return 0;
-  }
-  return static_cast<int32_t>(uresult);
-}
-:(before "End Unit Tests")
-void test_parse_int() {
-  CHECK_EQ(0, parse_int("0"));
-  CHECK_EQ(0, parse_int("0x0"));
-  CHECK_EQ(0, parse_int("0x0"));
-  CHECK_EQ(16, parse_int("10"));  // hex always
-  CHECK_EQ(-1, parse_int("-1"));
-  CHECK_EQ(-1, parse_int("0xffffffff"));
-}
diff --git a/subx/031check_operands.cc b/subx/031check_operands.cc
deleted file mode 100644
index bf5d3719..00000000
--- a/subx/031check_operands.cc
+++ /dev/null
@@ -1,691 +0,0 @@
-//: Since we're tagging operands with their types, let's start checking these
-//: operand types for each instruction.
-
-void test_check_missing_imm8_operand() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "cd\n"  // interrupt ??
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: 'cd' (software interrupt): missing imm8 operand\n"
-  );
-}
-
-:(before "Pack Operands(segment code)")
-check_operands(code);
-if (trace_contains_errors()) return;
-
-:(code)
-void check_operands(const segment& code) {
-  trace(3, "transform") << "-- check operands" << end();
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    check_operands(code.lines.at(i));
-    if (trace_contains_errors()) return;  // stop at the first mal-formed instruction
-  }
-}
-
-void check_operands(const line& inst) {
-  word op = preprocess_op(inst.words.at(0));
-  if (op.data == "0f") {
-    check_operands_0f(inst);
-    return;
-  }
-  if (op.data == "f3") {
-    check_operands_f3(inst);
-    return;
-  }
-  check_operands(inst, op);
-}
-
-word preprocess_op(word/*copy*/ op) {
-  op.data = tolower(op.data.c_str());
-  // opcodes can't be negative
-  if (starts_with(op.data, "0x"))
-    op.data = op.data.substr(2);
-  if (SIZE(op.data) == 1)
-    op.data = string("0")+op.data;
-  return op;
-}
-
-void test_preprocess_op() {
-  word w1;  w1.data = "0xf";
-  word w2;  w2.data = "0f";
-  CHECK_EQ(preprocess_op(w1).data, preprocess_op(w2).data);
-}
-
-//: To check the operands for an opcode, we'll track the permitted operands
-//: for each supported opcode in a bitvector. That way we can often compute the
-//: 'received' operand bitvector for each instruction's operands and compare
-//: it with the 'expected' bitvector.
-//:
-//: The 'expected' and 'received' bitvectors can be different; the MODRM bit
-//: in the 'expected' bitvector maps to multiple 'received' operand types in
-//: an instruction. We deal in expected bitvectors throughout.
-
-:(before "End Types")
-enum expected_operand_type {
-  // start from the least significant bit
-  MODRM,  // more complex, may also involve disp8 or disp32
-  SUBOP,
-  DISP8,
-  DISP16,
-  DISP32,
-  IMM8,
-  IMM32,
-  NUM_OPERAND_TYPES
-};
-:(before "End Globals")
-vector<string> Operand_type_name;
-map<string, expected_operand_type> Operand_type;
-:(before "End One-time Setup")
-init_op_types();
-:(code)
-void init_op_types() {
-  assert(NUM_OPERAND_TYPES <= /*bits in a uint8_t*/8);
-  Operand_type_name.resize(NUM_OPERAND_TYPES);
-  #define DEF(type) Operand_type_name.at(type) = tolower(#type), put(Operand_type, tolower(#type), type);
-  DEF(MODRM);
-  DEF(SUBOP);
-  DEF(DISP8);
-  DEF(DISP16);
-  DEF(DISP32);
-  DEF(IMM8);
-  DEF(IMM32);
-  #undef DEF
-}
-
-:(before "End Globals")
-map</*op*/string, /*bitvector*/uint8_t> Permitted_operands;
-const uint8_t INVALID_OPERANDS = 0xff;  // no instruction uses all the operand types
-:(before "End One-time Setup")
-init_permitted_operands();
-:(code)
-void init_permitted_operands() {
-  //// Class A: just op, no operands
-  // halt
-  put(Permitted_operands, "f4", 0x00);
-  // inc
-  put(Permitted_operands, "40", 0x00);
-  put(Permitted_operands, "41", 0x00);
-  put(Permitted_operands, "42", 0x00);
-  put(Permitted_operands, "43", 0x00);
-  put(Permitted_operands, "44", 0x00);
-  put(Permitted_operands, "45", 0x00);
-  put(Permitted_operands, "46", 0x00);
-  put(Permitted_operands, "47", 0x00);
-  // dec
-  put(Permitted_operands, "48", 0x00);
-  put(Permitted_operands, "49", 0x00);
-  put(Permitted_operands, "4a", 0x00);
-  put(Permitted_operands, "4b", 0x00);
-  put(Permitted_operands, "4c", 0x00);
-  put(Permitted_operands, "4d", 0x00);
-  put(Permitted_operands, "4e", 0x00);
-  put(Permitted_operands, "4f", 0x00);
-  // push
-  put(Permitted_operands, "50", 0x00);
-  put(Permitted_operands, "51", 0x00);
-  put(Permitted_operands, "52", 0x00);
-  put(Permitted_operands, "53", 0x00);
-  put(Permitted_operands, "54", 0x00);
-  put(Permitted_operands, "55", 0x00);
-  put(Permitted_operands, "56", 0x00);
-  put(Permitted_operands, "57", 0x00);
-  // pop
-  put(Permitted_operands, "58", 0x00);
-  put(Permitted_operands, "59", 0x00);
-  put(Permitted_operands, "5a", 0x00);
-  put(Permitted_operands, "5b", 0x00);
-  put(Permitted_operands, "5c", 0x00);
-  put(Permitted_operands, "5d", 0x00);
-  put(Permitted_operands, "5e", 0x00);
-  put(Permitted_operands, "5f", 0x00);
-  // sign-extend EAX into EDX
-  put(Permitted_operands, "99", 0x00);
-  // return
-  put(Permitted_operands, "c3", 0x00);
-
-  //// Class B: just op and disp8
-  //  imm32 imm8  disp32 |disp16  disp8 subop modrm
-  //  0     0     0      |0       1     0     0
-
-  // jump
-  put(Permitted_operands, "eb", 0x04);
-  put(Permitted_operands, "72", 0x04);
-  put(Permitted_operands, "73", 0x04);
-  put(Permitted_operands, "74", 0x04);
-  put(Permitted_operands, "75", 0x04);
-  put(Permitted_operands, "76", 0x04);
-  put(Permitted_operands, "77", 0x04);
-  put(Permitted_operands, "7c", 0x04);
-  put(Permitted_operands, "7d", 0x04);
-  put(Permitted_operands, "7e", 0x04);
-  put(Permitted_operands, "7f", 0x04);
-
-  //// Class D: just op and disp32
-  //  imm32 imm8  disp32 |disp16  disp8 subop modrm
-  //  0     0     1      |0       0     0     0
-  put(Permitted_operands, "e8", 0x10);  // call
-  put(Permitted_operands, "e9", 0x10);  // jump
-
-  //// Class E: just op and imm8
-  //  imm32 imm8  disp32 |disp16  disp8 subop modrm
-  //  0     1     0      |0       0     0     0
-  put(Permitted_operands, "cd", 0x20);  // software interrupt
-
-  //// Class F: just op and imm32
-  //  imm32 imm8  disp32 |disp16  disp8 subop modrm
-  //  1     0     0      |0       0     0     0
-  put(Permitted_operands, "05", 0x40);  // add
-  put(Permitted_operands, "2d", 0x40);  // subtract
-  put(Permitted_operands, "25", 0x40);  // and
-  put(Permitted_operands, "0d", 0x40);  // or
-  put(Permitted_operands, "35", 0x40);  // xor
-  put(Permitted_operands, "3d", 0x40);  // compare
-  put(Permitted_operands, "68", 0x40);  // push
-  // copy
-  put(Permitted_operands, "b8", 0x40);
-  put(Permitted_operands, "b9", 0x40);
-  put(Permitted_operands, "ba", 0x40);
-  put(Permitted_operands, "bb", 0x40);
-  put(Permitted_operands, "bc", 0x40);
-  put(Permitted_operands, "bd", 0x40);
-  put(Permitted_operands, "be", 0x40);
-  put(Permitted_operands, "bf", 0x40);
-
-  //// Class M: using ModR/M byte
-  //  imm32 imm8  disp32 |disp16  disp8 subop modrm
-  //  0     0     0      |0       0     0     1
-
-  // add
-  put(Permitted_operands, "01", 0x01);
-  put(Permitted_operands, "03", 0x01);
-  // subtract
-  put(Permitted_operands, "29", 0x01);
-  put(Permitted_operands, "2b", 0x01);
-  // and
-  put(Permitted_operands, "21", 0x01);
-  put(Permitted_operands, "23", 0x01);
-  // or
-  put(Permitted_operands, "09", 0x01);
-  put(Permitted_operands, "0b", 0x01);
-  // xor
-  put(Permitted_operands, "31", 0x01);
-  put(Permitted_operands, "33", 0x01);
-  // compare
-  put(Permitted_operands, "39", 0x01);
-  put(Permitted_operands, "3b", 0x01);
-  // copy
-  put(Permitted_operands, "88", 0x01);
-  put(Permitted_operands, "89", 0x01);
-  put(Permitted_operands, "8a", 0x01);
-  put(Permitted_operands, "8b", 0x01);
-  // swap
-  put(Permitted_operands, "87", 0x01);
-  // copy address (lea)
-  put(Permitted_operands, "8d", 0x01);
-
-  //// Class N: op, ModR/M and subop (not r32)
-  //  imm32 imm8  disp32 |disp16  disp8 subop modrm
-  //  0     0     0      |0       0     1     1
-  put(Permitted_operands, "8f", 0x03);  // pop
-  put(Permitted_operands, "d3", 0x03);  // shift
-  put(Permitted_operands, "f7", 0x03);  // test/not/mul/div
-  put(Permitted_operands, "ff", 0x03);  // jump/push/call
-
-  //// Class O: op, ModR/M, subop (not r32) and imm8
-  //  imm32 imm8  disp32 |disp16  disp8 subop modrm
-  //  0     1     0      |0       0     1     1
-  put(Permitted_operands, "c1", 0x23);  // combine
-  put(Permitted_operands, "c6", 0x23);  // copy
-
-  //// Class P: op, ModR/M, subop (not r32) and imm32
-  //  imm32 imm8  disp32 |disp16  disp8 subop modrm
-  //  1     0     0      |0       0     1     1
-  put(Permitted_operands, "81", 0x43);  // combine
-  put(Permitted_operands, "c7", 0x43);  // copy
-
-  // End Init Permitted Operands
-}
-
-#define HAS(bitvector, bit)  ((bitvector) & (1 << (bit)))
-#define SET(bitvector, bit)  ((bitvector) | (1 << (bit)))
-#define CLEAR(bitvector, bit)  ((bitvector) & (~(1 << (bit))))
-
-void check_operands(const line& inst, const word& op) {
-  if (!is_hex_byte(op)) return;
-  uint8_t expected_bitvector = get(Permitted_operands, op.data);
-  if (HAS(expected_bitvector, MODRM)) {
-    check_operands_modrm(inst, op);
-    compare_bitvector_modrm(inst, expected_bitvector, op);
-  }
-  else {
-    compare_bitvector(inst, expected_bitvector, op);
-  }
-}
-
-//: Many instructions can be checked just by comparing bitvectors.
-
-void compare_bitvector(const line& inst, uint8_t expected, const word& op) {
-  if (all_hex_bytes(inst) && has_operands(inst)) return;  // deliberately programming in raw hex; we'll raise a warning elsewhere
-  uint8_t bitvector = compute_expected_operand_bitvector(inst);
-  if (trace_contains_errors()) return;  // duplicate operand type
-  if (bitvector == expected) return;  // all good with this instruction
-  for (int i = 0;  i < NUM_OPERAND_TYPES;  ++i, bitvector >>= 1, expected >>= 1) {
-//?     cerr << "comparing " << HEXBYTE << NUM(bitvector) << " with " << NUM(expected) << '\n';
-    if ((bitvector & 0x1) == (expected & 0x1)) continue;  // all good with this operand
-    const string& optype = Operand_type_name.at(i);
-    if ((bitvector & 0x1) > (expected & 0x1))
-      raise << "'" << to_string(inst) << "'" << maybe_name(op) << ": unexpected " << optype << " operand\n" << end();
-    else
-      raise << "'" << to_string(inst) << "'" << maybe_name(op) << ": missing " << optype << " operand\n" << end();
-    // continue giving all errors for a single instruction
-  }
-  // ignore settings in any unused bits
-}
-
-string maybe_name(const word& op) {
-  if (!is_hex_byte(op)) return "";
-  if (!contains_key(Name, op.data)) return "";
-  // strip stuff in parens from the name
-  const string& s = get(Name, op.data);
-  return " ("+s.substr(0, s.find(" ("))+')';
-}
-
-uint32_t compute_expected_operand_bitvector(const line& inst) {
-  set<string> operands_found;
-  uint32_t bitvector = 0;
-  for (int i = /*skip op*/1;  i < SIZE(inst.words);  ++i) {
-    bitvector = bitvector | expected_bit_for_received_operand(inst.words.at(i), operands_found, inst);
-    if (trace_contains_errors()) return INVALID_OPERANDS;  // duplicate operand type
-  }
-  return bitvector;
-}
-
-bool has_operands(const line& inst) {
-  return SIZE(inst.words) > first_operand(inst);
-}
-
-int first_operand(const line& inst) {
-  if (inst.words.at(0).data == "0f") return 2;
-  if (inst.words.at(0).data == "f2" || inst.words.at(0).data == "f3") {
-    if (inst.words.at(1).data == "0f")
-      return 3;
-    else
-      return 2;
-  }
-  return 1;
-}
-
-// Scan the metadata of 'w' and return the expected bit corresponding to any operand type.
-// Also raise an error if metadata contains multiple operand types.
-uint32_t expected_bit_for_received_operand(const word& w, set<string>& instruction_operands, const line& inst) {
-  uint32_t bv = 0;
-  bool found = false;
-  for (int i = 0;  i < SIZE(w.metadata);  ++i) {
-    string/*copy*/ curr = w.metadata.at(i);
-    string expected_metadata = curr;
-    if (curr == "mod" || curr == "rm32" || curr == "r32" || curr == "scale" || curr == "index" || curr == "base")
-      expected_metadata = "modrm";
-    else if (!contains_key(Operand_type, curr)) continue;  // ignore unrecognized metadata
-    if (found) {
-      raise << "'" << w.original << "' has conflicting operand types; it should have only one\n" << end();
-      return INVALID_OPERANDS;
-    }
-    if (instruction_operands.find(curr) != instruction_operands.end()) {
-      raise << "'" << to_string(inst) << "': duplicate " << curr << " operand\n" << end();
-      return INVALID_OPERANDS;
-    }
-    instruction_operands.insert(curr);
-    bv = (1 << get(Operand_type, expected_metadata));
-    found = true;
-  }
-  return bv;
-}
-
-void test_conflicting_operand_type() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "cd/software-interrupt 80/imm8/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '80/imm8/imm32' has conflicting operand types; it should have only one\n"
-  );
-}
-
-//: Instructions computing effective addresses have more complex rules, so
-//: we'll hard-code a common set of instruction-decoding rules.
-
-void test_check_missing_mod_operand() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "81 0/add/subop       3/rm32/ebx 1/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '81 0/add/subop 3/rm32/ebx 1/imm32' (combine rm32 with imm32 based on subop): missing mod operand\n"
-  );
-}
-
-void check_operands_modrm(const line& inst, const word& op) {
-  if (all_hex_bytes(inst)) return;  // deliberately programming in raw hex; we'll raise a warning elsewhere
-  check_operand_metadata_present(inst, "mod", op);
-  check_operand_metadata_present(inst, "rm32", op);
-  // no check for r32; some instructions don't use it; just assume it's 0 if missing
-  if (op.data == "81" || op.data == "8f" || op.data == "ff") {  // keep sync'd with 'help subop'
-    check_operand_metadata_present(inst, "subop", op);
-    check_operand_metadata_absent(inst, "r32", op, "should be replaced by subop");
-  }
-  if (trace_contains_errors()) return;
-  if (metadata(inst, "rm32").data != "4") return;
-  // SIB byte checks
-  uint8_t mod = hex_byte(metadata(inst, "mod").data);
-  if (mod != /*direct*/3) {
-    check_operand_metadata_present(inst, "base", op);
-    check_operand_metadata_present(inst, "index", op);  // otherwise why go to SIB?
-  }
-  else {
-    check_operand_metadata_absent(inst, "base", op, "direct mode");
-    check_operand_metadata_absent(inst, "index", op, "direct mode");
-  }
-  // no check for scale; 0 (2**0 = 1) by default
-}
-
-// same as compare_bitvector, with one additional exception for modrm-based
-// instructions: they may use an extra displacement on occasion
-void compare_bitvector_modrm(const line& inst, uint8_t expected, const word& op) {
-  if (all_hex_bytes(inst) && has_operands(inst)) return;  // deliberately programming in raw hex; we'll raise a warning elsewhere
-  uint8_t bitvector = compute_expected_operand_bitvector(inst);
-  if (trace_contains_errors()) return;  // duplicate operand type
-  // update 'expected' bitvector for the additional exception
-  if (has_operand_metadata(inst, "mod")) {
-    int32_t mod = parse_int(metadata(inst, "mod").data);
-    switch (mod) {
-    case 0:
-      if (has_operand_metadata(inst, "rm32") && parse_int(metadata(inst, "rm32").data) == 5)
-        expected |= (1<<DISP32);
-      break;
-    case 1:
-      expected |= (1<<DISP8);
-      break;
-    case 2:
-      expected |= (1<<DISP32);
-      break;
-    }
-  }
-  if (bitvector == expected) return;  // all good with this instruction
-  for (int i = 0;  i < NUM_OPERAND_TYPES;  ++i, bitvector >>= 1, expected >>= 1) {
-//?     cerr << "comparing for modrm " << HEXBYTE << NUM(bitvector) << " with " << NUM(expected) << '\n';
-    if ((bitvector & 0x1) == (expected & 0x1)) continue;  // all good with this operand
-    const string& optype = Operand_type_name.at(i);
-    if ((bitvector & 0x1) > (expected & 0x1))
-      raise << "'" << to_string(inst) << "'" << maybe_name(op) << ": unexpected " << optype << " operand\n" << end();
-    else
-      raise << "'" << to_string(inst) << "'" << maybe_name(op) << ": missing " << optype << " operand\n" << end();
-    // continue giving all errors for a single instruction
-  }
-  // ignore settings in any unused bits
-}
-
-void check_operand_metadata_present(const line& inst, const string& type, const word& op) {
-  if (!has_operand_metadata(inst, type))
-    raise << "'" << to_string(inst) << "'" << maybe_name(op) << ": missing " << type << " operand\n" << end();
-}
-
-void check_operand_metadata_absent(const line& inst, const string& type, const word& op, const string& msg) {
-  if (has_operand_metadata(inst, type))
-    raise << "'" << to_string(inst) << "'" << maybe_name(op) << ": unexpected " << type << " operand (" << msg << ")\n" << end();
-}
-
-void test_modrm_with_displacement() {
-  Reg[EAX].u = 0x1;
-  transform(
-      "== code 0x1\n"
-      // just avoid null pointer
-      "8b/copy 1/mod/lookup+disp8 0/rm32/EAX 2/r32/EDX 4/disp8\n"  // copy *(EAX+4) to EDX
-  );
-  CHECK_TRACE_COUNT("error", 0);
-}
-
-void test_check_missing_disp8() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "89/copy 1/mod/lookup+disp8 0/rm32/EAX 1/r32/ECX\n"  // missing disp8
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '89/copy 1/mod/lookup+disp8 0/rm32/EAX 1/r32/ECX' (copy r32 to rm32): missing disp8 operand\n"
-  );
-}
-
-void test_check_missing_disp32() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "8b/copy 0/mod/indirect 5/rm32/.disp32 2/r32/EDX\n"  // missing disp32
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '8b/copy 0/mod/indirect 5/rm32/.disp32 2/r32/EDX' (copy rm32 to r32): missing disp32 operand\n"
-  );
-}
-
-void test_conflicting_operands_in_modrm_instruction() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "01/add 0/mod 3/mod\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '01/add 0/mod 3/mod' has conflicting mod operands\n"
-  );
-}
-
-void test_conflicting_operand_type_modrm() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "01/add 0/mod 3/rm32/r32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '3/rm32/r32' has conflicting operand types; it should have only one\n"
-  );
-}
-
-void test_check_missing_rm32_operand() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "81 0/add/subop 0/mod            1/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '81 0/add/subop 0/mod 1/imm32' (combine rm32 with imm32 based on subop): missing rm32 operand\n"
-  );
-}
-
-void test_check_missing_subop_operand() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "81             0/mod 3/rm32/ebx 1/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '81 0/mod 3/rm32/ebx 1/imm32' (combine rm32 with imm32 based on subop): missing subop operand\n"
-  );
-}
-
-void test_check_missing_base_operand() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "81 0/add/subop 0/mod/indirect 4/rm32/use-sib 1/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '81 0/add/subop 0/mod/indirect 4/rm32/use-sib 1/imm32' (combine rm32 with imm32 based on subop): missing base operand\n"
-  );
-}
-
-void test_check_missing_index_operand() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "81 0/add/subop 0/mod/indirect 4/rm32/use-sib 0/base 1/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '81 0/add/subop 0/mod/indirect 4/rm32/use-sib 0/base 1/imm32' (combine rm32 with imm32 based on subop): missing index operand\n"
-  );
-}
-
-void test_check_missing_base_operand_2() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "81 0/add/subop 0/mod/indirect 4/rm32/use-sib 2/index 3/scale 1/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '81 0/add/subop 0/mod/indirect 4/rm32/use-sib 2/index 3/scale 1/imm32' (combine rm32 with imm32 based on subop): missing base operand\n"
-  );
-}
-
-void test_check_extra_displacement() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "89/copy 0/mod/indirect 0/rm32/EAX 1/r32/ECX 4/disp8\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '89/copy 0/mod/indirect 0/rm32/EAX 1/r32/ECX 4/disp8' (copy r32 to rm32): unexpected disp8 operand\n"
-  );
-}
-
-void test_check_duplicate_operand() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "89/copy 0/mod/indirect 0/rm32/EAX 1/r32/ECX 1/r32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '89/copy 0/mod/indirect 0/rm32/EAX 1/r32/ECX 1/r32': duplicate r32 operand\n"
-  );
-}
-
-void test_check_base_operand_not_needed_in_direct_mode() {
-  run(
-      "== code 0x1\n"
-      "81 0/add/subop 3/mod/indirect 4/rm32/use-sib 1/imm32\n"
-  );
-  CHECK_TRACE_COUNT("error", 0);
-}
-
-void test_extra_modrm() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "59/pop-to-ECX  3/mod/direct 1/rm32/ECX 4/r32/ESP\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '59/pop-to-ECX 3/mod/direct 1/rm32/ECX 4/r32/ESP' (pop top of stack to ECX): unexpected modrm operand\n"
-  );
-}
-
-//:: similarly handle multi-byte opcodes
-
-void check_operands_0f(const line& inst) {
-  assert(inst.words.at(0).data == "0f");
-  if (SIZE(inst.words) == 1) {
-    raise << "opcode '0f' requires a second opcode\n" << end();
-    return;
-  }
-  word op = preprocess_op(inst.words.at(1));
-  if (!contains_key(Name_0f, op.data)) {
-    raise << "unknown 2-byte opcode '0f " << op.data << "'\n" << end();
-    return;
-  }
-  check_operands_0f(inst, op);
-}
-
-void check_operands_f3(const line& /*unused*/) {
-  raise << "no supported opcodes starting with f3\n" << end();
-}
-
-void test_check_missing_disp32_operand() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "  0f 84  # jmp if ZF to ??\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '0f 84' (jump disp32 bytes away if equal, if ZF is set): missing disp32 operand\n"
-  );
-}
-
-:(before "End Globals")
-map</*op*/string, /*bitvector*/uint8_t> Permitted_operands_0f;
-:(before "End Init Permitted Operands")
-//// Class D: just op and disp32
-//  imm32 imm8  disp32 |disp16  disp8 subop modrm
-//  0     0     1      |0       0     0     0
-put_new(Permitted_operands_0f, "82", 0x10);
-put_new(Permitted_operands_0f, "83", 0x10);
-put_new(Permitted_operands_0f, "84", 0x10);
-put_new(Permitted_operands_0f, "85", 0x10);
-put_new(Permitted_operands_0f, "86", 0x10);
-put_new(Permitted_operands_0f, "87", 0x10);
-put_new(Permitted_operands_0f, "8c", 0x10);
-put_new(Permitted_operands_0f, "8d", 0x10);
-put_new(Permitted_operands_0f, "8e", 0x10);
-put_new(Permitted_operands_0f, "8f", 0x10);
-
-//// Class M: using ModR/M byte
-//  imm32 imm8  disp32 |disp16  disp8 subop modrm
-//  0     0     0      |0       0     0     1
-put_new(Permitted_operands_0f, "af", 0x01);
-
-:(code)
-void check_operands_0f(const line& inst, const word& op) {
-  uint8_t expected_bitvector = get(Permitted_operands_0f, op.data);
-  if (HAS(expected_bitvector, MODRM))
-    check_operands_modrm(inst, op);
-  compare_bitvector_0f(inst, CLEAR(expected_bitvector, MODRM), op);
-}
-
-void compare_bitvector_0f(const line& inst, uint8_t expected, const word& op) {
-  if (all_hex_bytes(inst) && has_operands(inst)) return;  // deliberately programming in raw hex; we'll raise a warning elsewhere
-  uint8_t bitvector = compute_expected_operand_bitvector(inst);
-  if (trace_contains_errors()) return;  // duplicate operand type
-  if (bitvector == expected) return;  // all good with this instruction
-  for (int i = 0;  i < NUM_OPERAND_TYPES;  ++i, bitvector >>= 1, expected >>= 1) {
-//?     cerr << "comparing " << HEXBYTE << NUM(bitvector) << " with " << NUM(expected) << '\n';
-    if ((bitvector & 0x1) == (expected & 0x1)) continue;  // all good with this operand
-    const string& optype = Operand_type_name.at(i);
-    if ((bitvector & 0x1) > (expected & 0x1))
-      raise << "'" << to_string(inst) << "'" << maybe_name_0f(op) << ": unexpected " << optype << " operand\n" << end();
-    else
-      raise << "'" << to_string(inst) << "'" << maybe_name_0f(op) << ": missing " << optype << " operand\n" << end();
-    // continue giving all errors for a single instruction
-  }
-  // ignore settings in any unused bits
-}
-
-string maybe_name_0f(const word& op) {
-  if (!is_hex_byte(op)) return "";
-  if (!contains_key(Name_0f, op.data)) return "";
-  // strip stuff in parens from the name
-  const string& s = get(Name_0f, op.data);
-  return " ("+s.substr(0, s.find(" ("))+')';
-}
-
-string tolower(const char* s) {
-  ostringstream out;
-  for (/*nada*/;  *s;  ++s)
-    out << static_cast<char>(tolower(*s));
-  return out.str();
-}
-
-#undef HAS
-#undef SET
-#undef CLEAR
-
-:(before "End Includes")
-#include<cctype>
diff --git a/subx/032check_operand_bounds.cc b/subx/032check_operand_bounds.cc
deleted file mode 100644
index 72a66e3f..00000000
--- a/subx/032check_operand_bounds.cc
+++ /dev/null
@@ -1,143 +0,0 @@
-//:: Check that the different operands of an instruction aren't too large for their bitfields.
-
-void test_check_bitfield_sizes() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "01/add 4/mod 3/rm32 1/r32\n"  // add ECX to EBX
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '4/mod' too large to fit in bitfield mod\n"
-  );
-}
-
-:(before "End Globals")
-map<string, uint32_t> Operand_bound;
-:(before "End One-time Setup")
-put_new(Operand_bound, "subop", 1<<3);
-put_new(Operand_bound, "mod", 1<<2);
-put_new(Operand_bound, "rm32", 1<<3);
-put_new(Operand_bound, "base", 1<<3);
-put_new(Operand_bound, "index", 1<<3);
-put_new(Operand_bound, "scale", 1<<2);
-put_new(Operand_bound, "r32", 1<<3);
-put_new(Operand_bound, "disp8", 1<<8);
-put_new(Operand_bound, "disp16", 1<<16);
-// no bound needed for disp32
-put_new(Operand_bound, "imm8", 1<<8);
-// no bound needed for imm32
-
-:(before "Pack Operands(segment code)")
-check_operand_bounds(code);
-if (trace_contains_errors()) return;
-:(code)
-void check_operand_bounds(const segment& code) {
-  trace(3, "transform") << "-- check operand bounds" << end();
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    const line& inst = code.lines.at(i);
-    for (int j = first_operand(inst);  j < SIZE(inst.words);  ++j)
-      check_operand_bounds(inst.words.at(j));
-    if (trace_contains_errors()) return;  // stop at the first mal-formed instruction
-  }
-}
-
-void check_operand_bounds(const word& w) {
-  for (map<string, uint32_t>::iterator p = Operand_bound.begin();  p != Operand_bound.end();  ++p) {
-    if (!has_operand_metadata(w, p->first)) continue;
-    if (!looks_like_hex_int(w.data)) continue;  // later transforms are on their own to do their own bounds checking
-    int32_t x = parse_int(w.data);
-    if (x >= 0) {
-      if (p->first == "disp8" || p->first == "disp16") {
-        if (static_cast<uint32_t>(x) >= p->second/2)
-          raise << "'" << w.original << "' too large to fit in signed bitfield " << p->first << '\n' << end();
-      }
-      else {
-        if (static_cast<uint32_t>(x) >= p->second)
-          raise << "'" << w.original << "' too large to fit in bitfield " << p->first << '\n' << end();
-      }
-    }
-    else {
-      // hacky? assuming bound is a power of 2
-      if (x < -1*static_cast<int32_t>(p->second/2))
-        raise << "'" << w.original << "' too large to fit in bitfield " << p->first << '\n' << end();
-    }
-  }
-}
-
-void test_check_bitfield_sizes_for_imm8() {
-  run(
-      "== code 0x1\n"
-      "c1/shift 4/subop/left 3/mod/direct 1/rm32/ECX 0xff/imm8"  // shift EBX left
-  );
-  CHECK(!trace_contains_errors());
-}
-
-void test_check_bitfield_sizes_for_imm8_error() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "c1/shift 4/subop/left 3/mod/direct 1/rm32/ECX 0x100/imm8"  // shift EBX left
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '0x100/imm8' too large to fit in bitfield imm8\n"
-  );
-}
-
-void test_check_bitfield_sizes_for_negative_imm8() {
-  run(
-      "== code 0x1\n"
-      "c1/shift 4/subop/left 3/mod/direct 1/rm32/ECX -0x80/imm8"  // shift EBX left
-  );
-  CHECK(!trace_contains_errors());
-}
-
-void test_check_bitfield_sizes_for_negative_imm8_error() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "c1/shift 4/subop/left 3/mod/direct 1/rm32/ECX -0x81/imm8"  // shift EBX left
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '-0x81/imm8' too large to fit in bitfield imm8\n"
-  );
-}
-
-void test_check_bitfield_sizes_for_disp8() {
-  // not bothering to run
-  transform(
-      "== code 0x1\n"
-      "01/add 1/mod/*+disp8 3/rm32 1/r32 0x7f/disp8\n"  // add ECX to *(EBX+0x7f)
-  );
-  CHECK(!trace_contains_errors());
-}
-
-void test_check_bitfield_sizes_for_disp8_error() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "01/add 1/mod/*+disp8 3/rm32 1/r32 0x80/disp8\n"  // add ECX to *(EBX+0x80)
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '0x80/disp8' too large to fit in signed bitfield disp8\n"
-  );
-}
-
-void test_check_bitfield_sizes_for_negative_disp8() {
-  // not bothering to run
-  transform(
-      "== code 0x1\n"
-      "01/add 1/mod/*+disp8 3/rm32 1/r32 -0x80/disp8\n"  // add ECX to *(EBX-0x80)
-  );
-  CHECK(!trace_contains_errors());
-}
-
-void test_check_bitfield_sizes_for_negative_disp8_error() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "01/add 1/mod/*+disp8 3/rm32 1/r32 -0x81/disp8\n"  // add ECX to *(EBX-0x81)
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '-0x81/disp8' too large to fit in bitfield disp8\n"
-  );
-}
diff --git a/subx/034compute_segment_address.cc b/subx/034compute_segment_address.cc
deleted file mode 100644
index 61c3739a..00000000
--- a/subx/034compute_segment_address.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-//: ELF binaries have finicky rules about the precise alignment each segment
-//: should start at. They depend on the amount of code in a program.
-//: We shouldn't expect people to adjust segment addresses everytime they make
-//: a change to their programs.
-//: Let's start taking the given segment addresses as guidelines, and adjust
-//: them as necessary.
-//: This gives up a measure of control in placing code and data.
-
-void test_segment_name() {
-  run(
-      "== code 0x09000000\n"
-      "05/add-to-EAX  0x0d0c0b0a/imm32\n"
-      // code starts at 0x09000000 + p_offset, which is 0x54 for a single-segment binary
-  );
-  CHECK_TRACE_CONTENTS(
-      "load: 0x09000054 -> 05\n"
-      "load: 0x09000055 -> 0a\n"
-      "load: 0x09000056 -> 0b\n"
-      "load: 0x09000057 -> 0c\n"
-      "load: 0x09000058 -> 0d\n"
-      "run: add imm32 0x0d0c0b0a to EAX\n"
-      "run: storing 0x0d0c0b0a\n"
-  );
-}
-
-//: compute segment address
-
-:(before "End Level-2 Transforms")
-Transform.push_back(compute_segment_starts);
-
-:(code)
-void compute_segment_starts(program& p) {
-  trace(3, "transform") << "-- compute segment addresses" << end();
-  uint32_t p_offset = /*size of ehdr*/0x34 + SIZE(p.segments)*0x20/*size of each phdr*/;
-  for (size_t i = 0;  i < p.segments.size();  ++i) {
-    segment& curr = p.segments.at(i);
-    if (curr.start >= 0x08000000) {
-      // valid address for user space, so assume we're creating a real ELF binary, not just running a test
-      curr.start &= 0xfffff000;  // same number of zeros as the p_align used when emitting the ELF binary
-      curr.start |= (p_offset & 0xfff);
-      trace(99, "transform") << "segment " << i << " begins at address 0x" << HEXWORD << curr.start << end();
-    }
-    p_offset += size_of(curr);
-    assert(p_offset < SEGMENT_ALIGNMENT);  // for now we get less and less available space in each successive segment
-  }
-}
-
-uint32_t size_of(const segment& s) {
-  uint32_t sum = 0;
-  for (int i = 0;  i < SIZE(s.lines);  ++i)
-    sum += num_bytes(s.lines.at(i));
-  return sum;
-}
-
-// Assumes all bitfields are packed.
-uint32_t num_bytes(const line& inst) {
-  uint32_t sum = 0;
-  for (int i = 0;  i < SIZE(inst.words);  ++i)
-    sum += size_of(inst.words.at(i));
-  return sum;
-}
-
-int size_of(const word& w) {
-  if (has_operand_metadata(w, "disp32") || has_operand_metadata(w, "imm32"))
-    return 4;
-  else if (has_operand_metadata(w, "disp16"))
-    return 2;
-  // End size_of(word w) Special-cases
-  else
-    return 1;
-}
-
-//: Dependencies:
-//: - We'd like to compute segment addresses before setting up global variables,
-//:   because computing addresses for global variables requires knowing where
-//:   the data segment starts.
-//: - We'd like to finish expanding labels before computing segment addresses,
-//:   because it would make computing the sizes of segments more self-contained
-//:   (num_bytes).
-//:
-//: Decision: compute segment addresses before expanding labels, by being
-//: aware in this layer of certain operand types that will eventually occupy
-//: multiple bytes.
-//:
-//: The layer to expand labels later hooks into num_bytes() to teach this
-//: layer that labels occupy zero space in the binary.
diff --git a/subx/035labels.cc b/subx/035labels.cc
deleted file mode 100644
index 6f7fdbfe..00000000
--- a/subx/035labels.cc
+++ /dev/null
@@ -1,416 +0,0 @@
-//: Labels are defined by ending names with a ':'. This layer will compute
-//: displacements for labels, and compute the offset for instructions using them.
-//:
-//: We won't check this, but our convention will be that jump targets will
-//: start with a '$', while functions will not. Function names will never be
-//: jumped to, and jump targets will never be called.
-
-//: We're introducing non-number names for the first time, so it's worth
-//: laying down some ground rules all transforms will follow, so things don't
-//: get too confusing:
-//:   - if it starts with a digit, it's treated as a number. If it can't be
-//:     parsed as hex it will raise an error.
-//:   - if it starts with '-' it's treated as a number.
-//:   - if it starts with '0x' it's treated as a number.
-//:   - if it's two characters long, it can't be a name. Either it's a hex
-//:     byte, or it raises an error.
-//: That's it. Names can start with any non-digit that isn't a dash. They can
-//: be a single character long. 'a' is not a hex number, it's a variable.
-//: Later layers may add more conventions partitioning the space of names. But
-//: the above rules will remain inviolate.
-
-//: One special label is 'Entry', the address to start running the program at.
-//: It can be non-unique; the last declaration overrides earlier ones.
-//: It must exist in a program. Otherwise we don't know where to start running
-//: programs.
-
-void test_Entry_label() {
-  run(
-      "== code 0x1\n"
-      "05 0x0d0c0b0a/imm32\n"
-      "Entry:\n"
-      "05 0x0d0c0b0a/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000006 opcode: 05\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000001 opcode: 05");
-}
-
-:(before "End looks_like_hex_int(s) Detectors")
-if (SIZE(s) == 2) return true;
-
-:(code)
-void test_pack_immediate_ignores_single_byte_nondigit_operand() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "b9/copy  a/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction 'b9/copy a/imm32'\n"
-      // no change (we're just not printing metadata to the trace)
-      "transform: instruction after packing: 'b9 a'\n"
-  );
-}
-
-void test_pack_immediate_ignores_3_hex_digit_operand() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "b9/copy  aaa/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction 'b9/copy aaa/imm32'\n"
-      // no change (we're just not printing metadata to the trace)
-      "transform: instruction after packing: 'b9 aaa'\n"
-  );
-}
-
-void test_pack_immediate_ignores_non_hex_operand() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "b9/copy xxx/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: packing instruction 'b9/copy xxx/imm32'\n"
-      // no change (we're just not printing metadata to the trace)
-      "transform: instruction after packing: 'b9 xxx'\n"
-  );
-}
-
-//: a helper we'll find handy later
-void check_valid_name(const string& s) {
-  if (s.empty()) {
-    raise << "empty name!\n" << end();
-    return;
-  }
-  if (s.at(0) == '-')
-    raise << "'" << s << "' starts with '-', which can be confused with a negative number; use a different name\n" << end();
-  if (s.substr(0, 2) == "0x") {
-    raise << "'" << s << "' looks like a hex number; use a different name\n" << end();
-    return;
-  }
-  if (isdigit(s.at(0)))
-    raise << "'" << s << "' starts with a digit, and so can be confused with a number; use a different name.\n" << end();
-  if (SIZE(s) == 2)
-    raise << "'" << s << "' is two characters long, which can look like raw hex bytes at a glance; use a different name\n" << end();
-}
-
-//: Now that that's done, let's start using names as labels.
-
-void test_map_label() {
-  transform(
-      "== code 0x1\n"
-      "loop:\n"
-      "  05  0x0d0c0b0a/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: label 'loop' is at address 1\n"
-  );
-}
-
-:(before "End Level-2 Transforms")
-Transform.push_back(rewrite_labels);
-:(code)
-void rewrite_labels(program& p) {
-  trace(3, "transform") << "-- rewrite labels" << end();
-  if (p.segments.empty()) return;
-  segment& code = *find(p, "code");
-  map<string, int32_t> byte_index;  // values are unsigned, but we're going to do subtractions on them so they need to fit in 31 bits
-  compute_byte_indices_for_labels(code, byte_index);
-  if (trace_contains_errors()) return;
-  drop_labels(code);
-  if (trace_contains_errors()) return;
-  replace_labels_with_displacements(code, byte_index);
-  if (contains_key(byte_index, "Entry"))
-    p.entry = code.start + get(byte_index, "Entry");
-}
-
-void compute_byte_indices_for_labels(const segment& code, map<string, int32_t>& byte_index) {
-  int current_byte = 0;
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    const line& inst = code.lines.at(i);
-    if (Source_lines_file.is_open() && !inst.original.empty() && /*not a label*/ *inst.words.at(0).data.rbegin() != ':')
-      Source_lines_file << "0x" << HEXWORD << (code.start + current_byte) << ' ' << inst.original << '\n';
-    for (int j = 0;  j < SIZE(inst.words);  ++j) {
-      const word& curr = inst.words.at(j);
-      // hack: if we have any operand metadata left after previous transforms,
-      // deduce its size
-      // Maybe we should just move this transform to before instruction
-      // packing, and deduce the size of *all* operands. But then we'll also
-      // have to deal with bitfields.
-      if (has_operand_metadata(curr, "disp32") || has_operand_metadata(curr, "imm32")) {
-        if (*curr.data.rbegin() == ':')
-          raise << "'" << to_string(inst) << "': don't use ':' when jumping to labels\n" << end();
-        current_byte += 4;
-      }
-      else if (has_operand_metadata(curr, "disp16")) {
-        if (*curr.data.rbegin() == ':')
-          raise << "'" << to_string(inst) << "': don't use ':' when jumping to labels\n" << end();
-        current_byte += 2;
-      }
-      // automatically handle /disp8 and /imm8 here
-      else if (*curr.data.rbegin() != ':') {
-        ++current_byte;
-      }
-      else {
-        string label = drop_last(curr.data);
-        // ensure labels look sufficiently different from raw hex
-        check_valid_name(label);
-        if (trace_contains_errors()) return;
-        if (contains_any_operand_metadata(curr))
-          raise << "'" << to_string(inst) << "': label definition (':') not allowed in operand\n" << end();
-        if (j > 0)
-          raise << "'" << to_string(inst) << "': labels can only be the first word in a line.\n" << end();
-        if (Labels_file.is_open())
-          Labels_file << "0x" << HEXWORD << (code.start + current_byte) << ' ' << label << '\n';
-        if (contains_key(byte_index, label) && label != "Entry") {
-          raise << "duplicate label '" << label << "'\n" << end();
-          return;
-        }
-        put(byte_index, label, current_byte);
-        trace(99, "transform") << "label '" << label << "' is at address " << (current_byte+code.start) << end();
-        // no modifying current_byte; label definitions won't be in the final binary
-      }
-    }
-  }
-}
-
-:(before "End Globals")
-bool Dump_debug_info = false;  // currently used only by 'subx translate'
-ofstream Labels_file;
-ofstream Source_lines_file;
-:(before "End Commandline Options")
-else if (is_equal(*arg, "--debug")) {
-  Dump_debug_info = true;
-  // End --debug Settings
-}
-//: wait to open "labels" for writing until we're sure we aren't trying to read it
-:(after "Begin subx translate")
-if (Dump_debug_info) {
-  cerr << "saving address->label information to 'labels'\n";
-  Labels_file.open("labels");
-  cerr << "saving address->source information to 'source_lines'\n";
-  Source_lines_file.open("source_lines");
-}
-:(before "End subx translate")
-if (Dump_debug_info) {
-  Labels_file.close();
-  Source_lines_file.close();
-}
-
-:(code)
-void drop_labels(segment& code) {
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    line& inst = code.lines.at(i);
-    vector<word>::iterator new_end = remove_if(inst.words.begin(), inst.words.end(), is_label);
-    inst.words.erase(new_end, inst.words.end());
-  }
-}
-
-bool is_label(const word& w) {
-  return *w.data.rbegin() == ':';
-}
-
-void replace_labels_with_displacements(segment& code, const map<string, int32_t>& byte_index) {
-  int32_t byte_index_next_instruction_starts_at = 0;
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    line& inst = code.lines.at(i);
-    byte_index_next_instruction_starts_at += num_bytes(inst);
-    line new_inst;
-    for (int j = 0;  j < SIZE(inst.words);  ++j) {
-      const word& curr = inst.words.at(j);
-      if (contains_key(byte_index, curr.data)) {
-        int32_t displacement = static_cast<int32_t>(get(byte_index, curr.data)) - byte_index_next_instruction_starts_at;
-        if (has_operand_metadata(curr, "disp8")) {
-          if (displacement > 0x7f || displacement < -0x7f)
-            raise << "'" << to_string(inst) << "': label too far away for displacement " << std::hex << displacement << " to fit in 8 signed bits\n" << end();
-          else
-            emit_hex_bytes(new_inst, displacement, 1);
-        }
-        else if (has_operand_metadata(curr, "disp16")) {
-          if (displacement > 0x7fff || displacement < -0x7fff)
-            raise << "'" << to_string(inst) << "': label too far away for displacement " << std::hex << displacement << " to fit in 16 signed bits\n" << end();
-          else
-            emit_hex_bytes(new_inst, displacement, 2);
-        }
-        else if (has_operand_metadata(curr, "disp32")) {
-          emit_hex_bytes(new_inst, displacement, 4);
-        } else if (has_operand_metadata(curr, "imm32")) {
-          emit_hex_bytes(new_inst, code.start + get(byte_index, curr.data), 4);
-        }
-      }
-      else {
-        new_inst.words.push_back(curr);
-      }
-    }
-    inst.words.swap(new_inst.words);
-    trace(99, "transform") << "instruction after transform: '" << data_to_string(inst) << "'" << end();
-  }
-}
-
-string data_to_string(const line& inst) {
-  ostringstream out;
-  for (int i = 0;  i < SIZE(inst.words);  ++i) {
-    if (i > 0) out << ' ';
-    out << inst.words.at(i).data;
-  }
-  return out.str();
-}
-
-string drop_last(const string& s) {
-  return string(s.begin(), --s.end());
-}
-
-//: Label definitions must be the first word on a line. No jumping inside
-//: instructions.
-//: They should also be the only word on a line.
-//: However, you can absolutely have multiple labels map to the same address,
-//: as long as they're on separate lines.
-
-void test_multiple_labels_at() {
-  transform(
-      "== code 0x1\n"
-      // address 1
-      "loop:\n"
-      " $loop2:\n"
-      // address 1 (labels take up no space)
-      "    05  0x0d0c0b0a/imm32\n"
-      // address 6
-      "    eb  $loop2/disp8\n"
-      // address 8
-      "    eb  $loop3/disp8\n"
-      // address 0xa
-      " $loop3:\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: label 'loop' is at address 1\n"
-      "transform: label '$loop2' is at address 1\n"
-      "transform: label '$loop3' is at address a\n"
-      // first jump is to -7
-      "transform: instruction after transform: 'eb f9'\n"
-      // second jump is to 0 (fall through)
-      "transform: instruction after transform: 'eb 00'\n"
-  );
-}
-
-void test_loading_label_as_imm32() {
-  transform(
-      "== code 0x1\n"
-      "label:\n"
-      "  be/copy-to-ESI  label/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: label 'label' is at address 1\n"
-      "transform: instruction after transform: 'be 01 00 00 00'\n"
-  );
-}
-
-void test_duplicate_label() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "loop:\n"
-      "loop:\n"
-      "    05  0x0d0c0b0a/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: duplicate label 'loop'\n"
-  );
-}
-
-void test_label_too_short() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "xz:\n"
-      "  05  0x0d0c0b0a/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: 'xz' is two characters long, which can look like raw hex bytes at a glance; use a different name\n"
-  );
-}
-
-void test_label_hex() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "0xab:\n"
-      "  05  0x0d0c0b0a/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '0xab' looks like a hex number; use a different name\n"
-  );
-}
-
-void test_label_negative_hex() {
-  Hide_errors = true;
-  transform(
-      "== code 0x1\n"
-      "-a:\n"
-      "    05  0x0d0c0b0a/imm32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: '-a' starts with '-', which can be confused with a negative number; use a different name\n"
-  );
-}
-
-//: As said up top, the 'Entry' label is special.
-//: It can be non-unique; the last declaration overrides earlier ones.
-//: It must exist in a program. Otherwise we don't know where to start running
-//: programs.
-
-void test_duplicate_Entry_label() {
-  transform(
-      "== code 0x1\n"
-      "Entry:\n"
-      "Entry:\n"
-      "    05  0x0d0c0b0a/imm32\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN_ERRORS();
-}
-
-// This test could do with some refactoring.
-// We're duplicating the flow inside `subx translate`, but without
-// reading/writing files.
-// We can't just use run(string) because most of our tests allow programs
-// without 'Entry' labels, as a convenience.
-void test_programs_without_Entry_label() {
-  Hide_errors = true;
-  program p;
-  istringstream in(
-      "== code 0x1\n"
-      "05 0x0d0c0b0a/imm32\n"
-      "05 0x0d0c0b0a/imm32\n"
-  );
-  parse(in, p);
-  transform(p);
-  ostringstream dummy;
-  save_elf(p, dummy);
-  CHECK_TRACE_CONTENTS(
-      "error: no 'Entry' label found\n"
-  );
-}
-
-//: now that we have labels, we need to adjust segment size computation to
-//: ignore them.
-
-void test_segment_size_ignores_labels() {
-  transform(
-      "== code 0x09000074\n"
-      "  05/add  0x0d0c0b0a/imm32\n"  // 5 bytes
-      "foo:\n"                        // 0 bytes
-      "== data 0x0a000000\n"
-      "bar:\n"
-      "  00\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: segment 1 begins at address 0x0a000079\n"
-  );
-}
-
-:(before "End size_of(word w) Special-cases")
-else if (is_label(w))
-  return 0;
diff --git a/subx/036global_variables.cc b/subx/036global_variables.cc
deleted file mode 100644
index c22ac3d3..00000000
--- a/subx/036global_variables.cc
+++ /dev/null
@@ -1,305 +0,0 @@
-//: Global variables.
-//:
-//: Global variables are just labels in the data segment.
-//: However, they can only be used in imm32 and not disp32 operands. And they
-//: can't be used with jump and call instructions.
-//:
-//: This layer has much the same structure as rewriting labels.
-
-:(code)
-void test_global_variable() {
-  run(
-      "== code 0x1\n"
-      "b9  x/imm32\n"
-      "== data 0x2000\n"
-      "x:\n"
-      "  00 00 00 00\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: global variable 'x' is at address 0x00002000\n"
-  );
-}
-
-:(before "End Level-2 Transforms")
-Transform.push_back(rewrite_global_variables);
-:(code)
-void rewrite_global_variables(program& p) {
-  trace(3, "transform") << "-- rewrite global variables" << end();
-  // Begin rewrite_global_variables
-  map<string, uint32_t> address;
-  compute_addresses_for_global_variables(p, address);
-  if (trace_contains_errors()) return;
-  drop_global_variables(p);
-  replace_global_variables_with_addresses(p, address);
-}
-
-void compute_addresses_for_global_variables(const program& p, map<string, uint32_t>& address) {
-  for (int i = 0;  i < SIZE(p.segments);  ++i) {
-    if (p.segments.at(i).name != "code")
-      compute_addresses_for_global_variables(p.segments.at(i), address);
-  }
-}
-
-void compute_addresses_for_global_variables(const segment& s, map<string, uint32_t>& address) {
-  int current_address = s.start;
-  for (int i = 0;  i < SIZE(s.lines);  ++i) {
-    const line& inst = s.lines.at(i);
-    for (int j = 0;  j < SIZE(inst.words);  ++j) {
-      const word& curr = inst.words.at(j);
-      if (*curr.data.rbegin() != ':') {
-        current_address += size_of(curr);
-      }
-      else {
-        string variable = drop_last(curr.data);
-        // ensure variables look sufficiently different from raw hex
-        check_valid_name(variable);
-        if (trace_contains_errors()) return;
-        if (j > 0)
-          raise << "'" << to_string(inst) << "': global variable names can only be the first word in a line.\n" << end();
-        if (Labels_file.is_open())
-          Labels_file << "0x" << HEXWORD << current_address << ' ' << variable << '\n';
-        if (contains_key(address, variable)) {
-          raise << "duplicate global '" << variable << "'\n" << end();
-          return;
-        }
-        put(address, variable, current_address);
-        trace(99, "transform") << "global variable '" << variable << "' is at address 0x" << HEXWORD << current_address << end();
-        // no modifying current_address; global variable definitions won't be in the final binary
-      }
-    }
-  }
-}
-
-void drop_global_variables(program& p) {
-  for (int i = 0;  i < SIZE(p.segments);  ++i) {
-    if (p.segments.at(i).name != "code")
-      drop_labels(p.segments.at(i));
-  }
-}
-
-void replace_global_variables_with_addresses(program& p, const map<string, uint32_t>& address) {
-  if (p.segments.empty()) return;
-  for (int i = 0;  i < SIZE(p.segments);  ++i) {
-    segment& curr = p.segments.at(i);
-    if (curr.name == "code")
-      replace_global_variables_in_code_segment(curr, address);
-    else
-      replace_global_variables_in_data_segment(curr, address);
-  }
-}
-
-void replace_global_variables_in_code_segment(segment& code, const map<string, uint32_t>& address) {
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    line& inst = code.lines.at(i);
-    line new_inst;
-    for (int j = 0;  j < SIZE(inst.words);  ++j) {
-      const word& curr = inst.words.at(j);
-      if (!contains_key(address, curr.data)) {
-        if (!looks_like_hex_int(curr.data))
-          raise << "missing reference to global '" << curr.data << "'\n" << end();
-        new_inst.words.push_back(curr);
-        continue;
-      }
-      if (!valid_use_of_global_variable(curr)) {
-        raise << "'" << to_string(inst) << "': can't refer to global variable '" << curr.data << "'\n" << end();
-        return;
-      }
-      emit_hex_bytes(new_inst, get(address, curr.data), 4);
-    }
-    inst.words.swap(new_inst.words);
-    trace(99, "transform") << "instruction after transform: '" << data_to_string(inst) << "'" << end();
-  }
-}
-
-void replace_global_variables_in_data_segment(segment& data, const map<string, uint32_t>& address) {
-  for (int i = 0;  i < SIZE(data.lines);  ++i) {
-    line& l = data.lines.at(i);
-    line new_l;
-    for (int j = 0;  j < SIZE(l.words);  ++j) {
-      const word& curr = l.words.at(j);
-      if (!contains_key(address, curr.data)) {
-        if (looks_like_hex_int(curr.data)) {
-          if (has_operand_metadata(curr, "imm32"))
-            emit_hex_bytes(new_l, curr, 4);
-          else if (has_operand_metadata(curr, "imm16"))
-            emit_hex_bytes(new_l, curr, 2);
-          else if (has_operand_metadata(curr, "imm8"))
-            emit_hex_bytes(new_l, curr, 1);
-          else if (has_operand_metadata(curr, "disp8"))
-            raise << "can't use /disp8 in a non-code segment\n" << end();
-          else if (has_operand_metadata(curr, "disp16"))
-            raise << "can't use /disp16 in a non-code segment\n" << end();
-          else if (has_operand_metadata(curr, "disp32"))
-            raise << "can't use /disp32 in a non-code segment\n" << end();
-          else
-            new_l.words.push_back(curr);
-        }
-        else {
-          raise << "missing reference to global '" << curr.data << "'\n" << end();
-          new_l.words.push_back(curr);
-        }
-        continue;
-      }
-      trace(99, "transform") << curr.data << " maps to " << HEXWORD << get(address, curr.data) << end();
-      emit_hex_bytes(new_l, get(address, curr.data), 4);
-    }
-    l.words.swap(new_l.words);
-    trace(99, "transform") << "after transform: '" << data_to_string(l) << "'" << end();
-  }
-}
-
-bool valid_use_of_global_variable(const word& curr) {
-  if (has_operand_metadata(curr, "imm32")) return true;
-  // End Valid Uses Of Global Variable(curr)
-  return false;
-}
-
-//:: a more complex sanity check for how we use global variables
-//: requires first saving some data early before we pack operands
-
-:(after "Begin Level-2 Transforms")
-Transform.push_back(correlate_disp32_with_mod);
-:(code)
-void correlate_disp32_with_mod(program& p) {
-  if (p.segments.empty()) return;
-  segment& code = *find(p, "code");
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    line& inst = code.lines.at(i);
-    for (int j = 0;  j < SIZE(inst.words);  ++j) {
-      word& curr = inst.words.at(j);
-      if (has_operand_metadata(curr, "disp32")
-          && has_operand_metadata(inst, "mod"))
-        curr.metadata.push_back("has_mod");
-    }
-  }
-}
-
-:(before "End Valid Uses Of Global Variable(curr)")
-if (has_operand_metadata(curr, "disp32"))
-  return has_metadata(curr, "has_mod");
-// todo: more sophisticated check, to ensure we don't use global variable
-// addresses as a real displacement added to other operands.
-
-:(code)
-bool has_metadata(const word& w, const string& m) {
-  for (int i = 0;  i < SIZE(w.metadata);  ++i)
-    if (w.metadata.at(i) == m) return true;
-  return false;
-}
-
-void test_global_variable_disallowed_in_jump() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "eb/jump  x/disp8\n"
-      "== data 0x2000\n"
-      "x:\n"
-      "  00 00 00 00\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: 'eb/jump x/disp8': can't refer to global variable 'x'\n"
-      // sub-optimal error message; should be
-//?       "error: can't jump to data (variable 'x')\n"
-  );
-}
-
-void test_global_variable_disallowed_in_call() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "e8/call  x/disp32\n"
-      "== data 0x2000\n"
-      "x:\n"
-      "  00 00 00 00\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: 'e8/call x/disp32': can't refer to global variable 'x'\n"
-      // sub-optimal error message; should be
-//?       "error: can't call to the data segment ('x')\n"
-  );
-}
-
-void test_global_variable_in_data_segment() {
-  run(
-      "== code 0x1\n"
-      "b9  x/imm32\n"
-      "== data 0x2000\n"
-      "x:\n"
-      "  y/imm32\n"
-      "y:\n"
-      "  00 00 00 00\n"
-  );
-  // check that we loaded 'x' with the address of 'y'
-  CHECK_TRACE_CONTENTS(
-      "load: 0x00002000 -> 04\n"
-      "load: 0x00002001 -> 20\n"
-      "load: 0x00002002 -> 00\n"
-      "load: 0x00002003 -> 00\n"
-  );
-  CHECK_TRACE_COUNT("error", 0);
-}
-
-void test_raw_number_with_imm32_in_data_segment() {
-  run(
-      "== code 0x1\n"
-      "b9  x/imm32\n"
-      "== data 0x2000\n"
-      "x:\n"
-      "  1/imm32\n"
-  );
-  // check that we loaded 'x' with the address of 1
-  CHECK_TRACE_CONTENTS(
-      "load: 0x00002000 -> 01\n"
-      "load: 0x00002001 -> 00\n"
-      "load: 0x00002002 -> 00\n"
-      "load: 0x00002003 -> 00\n"
-  );
-  CHECK_TRACE_COUNT("error", 0);
-}
-
-void test_duplicate_global_variable() {
-  Hide_errors = true;
-  run(
-      "== code 0x1\n"
-      "40/increment-EAX\n"
-      "== data 0x2000\n"
-      "x:\n"
-      "x:\n"
-      "  00\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "error: duplicate global 'x'\n"
-  );
-}
-
-void test_global_variable_disp32_with_modrm() {
-  run(
-      "== code 0x1\n"
-      "8b/copy 0/mod/indirect 5/rm32/.disp32 2/r32/EDX x/disp32\n"
-      "== data 0x2000\n"
-      "x:\n"
-      "  00 00 00 00\n"
-  );
-  CHECK_TRACE_COUNT("error", 0);
-}
-
-void test_global_variable_disp32_with_call() {
-  transform(
-      "== code 0x1\n"
-      "foo:\n"
-      "  e8/call bar/disp32\n"
-      "bar:\n"
-  );
-  CHECK_TRACE_COUNT("error", 0);
-}
-
-string to_full_string(const line& in) {
-  ostringstream out;
-  for (int i = 0;  i < SIZE(in.words);  ++i) {
-    if (i > 0) out << ' ';
-    out << in.words.at(i).data;
-    for (int j = 0;  j < SIZE(in.words.at(i).metadata);  ++j)
-      out << '/' << in.words.at(i).metadata.at(j);
-  }
-  return out.str();
-}
diff --git a/subx/038---literal_strings.cc b/subx/038---literal_strings.cc
deleted file mode 100644
index ecc80176..00000000
--- a/subx/038---literal_strings.cc
+++ /dev/null
@@ -1,324 +0,0 @@
-//: Allow instructions to mention literals directly.
-//:
-//: This layer will transparently move them to the global segment (assumed to
-//: always be the second segment).
-
-void test_transform_literal_string() {
-  run(
-      "== code 0x1\n"
-      "b8/copy  \"test\"/imm32\n"
-      "== data 0x2000\n"  // need an empty segment
-  );
-  CHECK_TRACE_CONTENTS(
-      "transform: -- move literal strings to data segment\n"
-      "transform: adding global variable '__subx_global_1' containing \"test\"\n"
-      "transform: instruction after transform: 'b8 __subx_global_1'\n"
-  );
-}
-
-//: We don't rely on any transforms running in previous layers, but this layer
-//: knows about labels and global variables and will emit them for previous
-//: layers to transform.
-:(after "Begin Transforms")
-// Begin Level-3 Transforms
-Transform.push_back(transform_literal_strings);
-// End Level-3 Transforms
-
-:(before "End Globals")
-int Next_auto_global = 1;
-:(code)
-void transform_literal_strings(program& p) {
-  trace(3, "transform") << "-- move literal strings to data segment" << end();
-  if (p.segments.empty()) return;
-  segment& code = *find(p, "code");
-  segment& data = *find(p, "data");
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    line& inst = code.lines.at(i);
-    for (int j = 0;  j < SIZE(inst.words);  ++j) {
-      word& curr = inst.words.at(j);
-      if (curr.data.at(0) != '"') continue;
-      ostringstream global_name;
-      global_name << "__subx_global_" << Next_auto_global;
-      ++Next_auto_global;
-      add_global_to_data_segment(global_name.str(), curr, data);
-      curr.data = global_name.str();
-    }
-    trace(99, "transform") << "instruction after transform: '" << data_to_string(inst) << "'" << end();
-  }
-}
-
-void add_global_to_data_segment(const string& name, const word& value, segment& data) {
-  trace(99, "transform") << "adding global variable '" << name << "' containing " << value.data << end();
-  // emit label
-  data.lines.push_back(label(name));
-  // emit size for size-prefixed array
-  data.lines.push_back(line());
-  emit_hex_bytes(data.lines.back(), SIZE(value.data)-/*skip quotes*/2, 4/*bytes*/);
-  // emit data byte by byte
-  data.lines.push_back(line());
-  line& curr = data.lines.back();
-  for (int i = /*skip start quote*/1;  i < SIZE(value.data)-/*skip end quote*/1;  ++i) {
-    char c = value.data.at(i);
-    curr.words.push_back(word());
-    curr.words.back().data = hex_byte_to_string(c);
-    curr.words.back().metadata.push_back(string(1, c));
-  }
-}
-
-//: Within strings, whitespace is significant. So we need to redo our instruction
-//: parsing.
-
-void test_instruction_with_string_literal() {
-  parse_instruction_character_by_character(
-      "a \"abc  def\" z\n"  // two spaces inside string
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: a\n"
-      "parse2: word: \"abc  def\"\n"
-      "parse2: word: z\n"
-  );
-  // no other words
-  CHECK_TRACE_COUNT("parse2", 3);
-}
-
-:(before "End Line Parsing Special-cases(line_data -> l)")
-if (line_data.find('"') != string::npos) {  // can cause false-positives, but we can handle them
-  parse_instruction_character_by_character(line_data, l);
-  continue;
-}
-
-:(code)
-void parse_instruction_character_by_character(const string& line_data, vector<line>& out) {
-  if (line_data.find('\n') != string::npos  && line_data.find('\n') != line_data.size()-1) {
-    raise << "parse_instruction_character_by_character: should receive only a single line\n" << end();
-    return;
-  }
-  // parse literals
-  istringstream in(line_data);
-  in >> std::noskipws;
-  line result;
-  result.original = line_data;
-  // add tokens (words or strings) one by one
-  while (has_data(in)) {
-    skip_whitespace(in);
-    if (!has_data(in)) break;
-    char c = in.get();
-    if (c == '#') break;  // comment; drop rest of line
-    if (c == ':') break;  // line metadata; skip for now
-    if (c == '.') {
-      if (!has_data(in)) break;  // comment token at end of line
-      if (isspace(in.peek()))
-        continue;  // '.' followed by space is comment token; skip
-    }
-    result.words.push_back(word());
-    if (c == '"') {
-      // string literal; slurp everything between quotes into data
-      ostringstream d;
-      d << c;
-      while (has_data(in)) {
-        in >> c;
-        if (c == '\\') {
-          in >> c;
-          if (c == 'n') d << '\n';
-          else if (c == '"') d << '"';
-          else if (c == '\\') d << '\\';
-          else {
-            raise << "parse_instruction_character_by_character: unknown escape sequence '\\" << c << "'\n" << end();
-            return;
-          }
-          continue;
-        } else {
-          d << c;
-        }
-        if (c == '"') break;
-      }
-      result.words.back().data = d.str();
-      // slurp metadata
-      ostringstream m;
-      while (!isspace(in.peek()) && has_data(in)) {  // peek can sometimes trigger eof(), so do it first
-        in >> c;
-        if (c == '/') {
-          if (!m.str().empty()) result.words.back().metadata.push_back(m.str());
-          m.str("");
-        }
-        else {
-          m << c;
-        }
-      }
-      if (!m.str().empty()) result.words.back().metadata.push_back(m.str());
-    }
-    else {
-      // not a string literal; slurp all characters until whitespace
-      ostringstream w;
-      w << c;
-      while (!isspace(in.peek()) && has_data(in)) {  // peek can sometimes trigger eof(), so do it first
-        in >> c;
-        w << c;
-      }
-      parse_word(w.str(), result.words.back());
-    }
-    trace(99, "parse2") << "word: " << to_string(result.words.back()) << end();
-  }
-  if (!result.words.empty())
-    out.push_back(result);
-}
-
-void skip_whitespace(istream& in) {
-  while (true) {
-    if (has_data(in) && isspace(in.peek())) in.get();
-    else break;
-  }
-}
-
-void skip_comment(istream& in) {
-  if (has_data(in) && in.peek() == '#') {
-    in.get();
-    while (has_data(in) && in.peek() != '\n') in.get();
-  }
-}
-
-line label(string s) {
-  line result;
-  result.words.push_back(word());
-  result.words.back().data = (s+":");
-  return result;
-}
-
-// helper for tests
-void parse_instruction_character_by_character(const string& line_data) {
-  vector<line> out;
-  parse_instruction_character_by_character(line_data, out);
-}
-
-void test_parse2_comment_token_in_middle() {
-  parse_instruction_character_by_character(
-      "a . z\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: a\n"
-      "parse2: word: z\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("parse2: word: .");
-  // no other words
-  CHECK_TRACE_COUNT("parse2", 2);
-}
-
-void test_parse2_word_starting_with_dot() {
-  parse_instruction_character_by_character(
-      "a .b c\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: a\n"
-      "parse2: word: .b\n"
-      "parse2: word: c\n"
-  );
-}
-
-void test_parse2_comment_token_at_start() {
-  parse_instruction_character_by_character(
-      ". a b\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: a\n"
-      "parse2: word: b\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("parse2: word: .");
-}
-
-void test_parse2_comment_token_at_end() {
-  parse_instruction_character_by_character(
-      "a b .\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: a\n"
-      "parse2: word: b\n"
-  );
-  CHECK_TRACE_DOESNT_CONTAIN("parse2: word: .");
-}
-
-void test_parse2_word_starting_with_dot_at_start() {
-  parse_instruction_character_by_character(
-      ".a b c\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: .a\n"
-      "parse2: word: b\n"
-      "parse2: word: c\n"
-  );
-}
-
-void test_parse2_metadata() {
-  parse_instruction_character_by_character(
-      ".a b/c d\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: .a\n"
-      "parse2: word: b /c\n"
-      "parse2: word: d\n"
-  );
-}
-
-void test_parse2_string_with_metadata() {
-  parse_instruction_character_by_character(
-      "a \"bc  def\"/disp32 g\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: a\n"
-      "parse2: word: \"bc  def\" /disp32\n"
-      "parse2: word: g\n"
-  );
-}
-
-void test_parse2_string_with_metadata_at_end() {
-  parse_instruction_character_by_character(
-      "a \"bc  def\"/disp32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: a\n"
-      "parse2: word: \"bc  def\" /disp32\n"
-  );
-}
-
-void test_parse2_string_with_metadata_at_end_of_line_without_newline() {
-  parse_instruction_character_by_character(
-      "68/push \"test\"/f"  // no newline, which is how calls from parse() will look
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: 68 /push\n"
-      "parse2: word: \"test\" /f\n"
-  );
-}
-
-//: Make sure slashes inside strings don't trigger adding stuff from inside the
-//: string to metadata.
-
-void test_parse2_string_containing_slashes() {
-  parse_instruction_character_by_character(
-      "a \"bc/def\"/disp32\n"
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: \"bc/def\" /disp32\n"
-  );
-}
-
-void test_instruction_with_string_literal_with_escaped_quote() {
-  parse_instruction_character_by_character(
-      "\"a\\\"b\"\n"  // escaped quote inside string
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: \"a\"b\"\n"
-  );
-  // no other words
-  CHECK_TRACE_COUNT("parse2", 1);
-}
-
-void test_instruction_with_string_literal_with_escaped_backslash() {
-  parse_instruction_character_by_character(
-      "\"a\\\\b\"\n"  // escaped backslash inside string
-  );
-  CHECK_TRACE_CONTENTS(
-      "parse2: word: \"a\\b\"\n"
-  );
-  // no other words
-  CHECK_TRACE_COUNT("parse2", 1);
-}
diff --git a/subx/039debug.cc b/subx/039debug.cc
deleted file mode 100644
index b308a7d4..00000000
--- a/subx/039debug.cc
+++ /dev/null
@@ -1,147 +0,0 @@
-//:: Some helpers for debugging.
-
-//: Load the 'map' file generated during 'subx --debug translate' when running
-//: 'subx --debug --trace run'.
-//: (It'll only affect the trace.)
-
-:(before "End Globals")
-map</*address*/uint32_t, string> Symbol_name;  // used only by 'subx run'
-map</*address*/uint32_t, string> Source_line;  // used only by 'subx run'
-:(before "End --debug Settings")
-load_labels();
-load_source_lines();
-:(code)
-void load_labels() {
-  ifstream fin("labels");
-  fin >> std::hex;
-  while (has_data(fin)) {
-    uint32_t addr = 0;
-    fin >> addr;
-    string name;
-    fin >> name;
-    put(Symbol_name, addr, name);
-  }
-}
-
-void load_source_lines() {
-  ifstream fin("source_lines");
-  fin >> std::hex;
-  while (has_data(fin)) {
-    uint32_t addr = 0;
-    fin >> addr;
-    string line;
-    getline(fin, line);
-    put(Source_line, addr, hacky_squeeze_out_whitespace(line));
-  }
-}
-
-:(after "Run One Instruction")
-if (contains_key(Symbol_name, EIP))
-  trace(Callstack_depth, "run") << "== label " << get(Symbol_name, EIP) << end();
-if (contains_key(Source_line, EIP))
-  trace(Callstack_depth, "run") << "0x" << HEXWORD << EIP << ": " << get(Source_line, EIP) << end();
-else
-  // no source line info; do what you can
-  trace(Callstack_depth, "run") << "0x" << HEXWORD << EIP << ": " << debug_info(EIP) << end();
-
-:(code)
-string debug_info(uint32_t inst_address) {
-  uint8_t op = read_mem_u8(inst_address);
-  if (op != 0xe8) {
-    ostringstream out;
-    out << HEXBYTE << NUM(op);
-    return out.str();
-  }
-  int32_t offset = read_mem_i32(inst_address+/*skip op*/1);
-  uint32_t next_eip = inst_address+/*inst length*/5+offset;
-  if (contains_key(Symbol_name, next_eip))
-    return "e8/call "+get(Symbol_name, next_eip);
-  ostringstream out;
-  out << "e8/call 0x" << HEXWORD << next_eip;
-  return out.str();
-}
-
-//: If a label starts with '$watch-', make a note of the effective address
-//: computed by the next instruction. Start dumping out its contents to the
-//: trace after every subsequent instruction.
-
-:(after "Run One Instruction")
-dump_watch_points();
-:(before "End Globals")
-map<string, uint32_t> Watch_points;
-:(before "End Reset")
-Watch_points.clear();
-:(code)
-void dump_watch_points() {
-  if (Watch_points.empty()) return;
-  trace(Callstack_depth, "dbg") << "watch points:" << end();
-  for (map<string, uint32_t>::iterator p = Watch_points.begin();  p != Watch_points.end();  ++p)
-    trace(Callstack_depth, "dbg") << "  " << p->first << ": " << HEXWORD << p->second << " -> " << HEXWORD << read_mem_u32(p->second) << end();
-}
-
-:(before "End Globals")
-string Watch_this_effective_address;
-:(after "Run One Instruction")
-Watch_this_effective_address = "";
-if (contains_key(Symbol_name, EIP) && starts_with(get(Symbol_name, EIP), "$watch-"))
-  Watch_this_effective_address = get(Symbol_name, EIP);
-:(after "Found effective_address(addr)")
-if (!Watch_this_effective_address.empty()) {
-  dbg << "now watching " << HEXWORD << addr << " for " << Watch_this_effective_address << end();
-  put(Watch_points, Watch_this_effective_address, addr);
-}
-
-//: Special label that dumps regions of memory.
-//: Not a general mechanism; by the time you get here you're willing to hack
-//: on the emulator.
-:(after "Run One Instruction")
-if (contains_key(Symbol_name, EIP) && get(Symbol_name, EIP) == "$dump-stream-at-EAX")
-  dump_stream_at(Reg[EAX].u);
-:(code)
-void dump_stream_at(uint32_t stream_start) {
-  int32_t stream_length = read_mem_i32(stream_start + 8);
-  dbg << "stream length: " << std::dec << stream_length << end();
-  for (int i = 0;  i < stream_length + 12;  ++i)
-    dbg << "0x" << HEXWORD << (stream_start+i) << ": " << HEXBYTE << NUM(read_mem_u8(stream_start+i)) << end();
-}
-
-//: helpers
-
-:(code)
-string hacky_squeeze_out_whitespace(const string& s) {
-  // strip whitespace at start
-  string::const_iterator first = s.begin();
-  while (first != s.end() && isspace(*first))
-    ++first;
-  if (first == s.end()) return "";
-
-  // strip whitespace at end
-  string::const_iterator last = --s.end();
-  while (last != s.begin() && isspace(*last))
-    --last;
-  ++last;
-
-  // replace runs of spaces/dots with single space until comment or string
-  // TODO:
-  //   leave alone dots not surrounded by whitespace
-  //   leave alone '#' within word
-  //   leave alone '"' within word
-  //   squeeze spaces after end of string
-  ostringstream out;
-  bool previous_was_space = false;
-  bool in_comment_or_string = false;
-  for (string::const_iterator curr = first;  curr != last;  ++curr) {
-    if (in_comment_or_string)
-      out << *curr;
-    else if (isspace(*curr) || *curr == '.')
-      previous_was_space = true;
-    else {
-      if (previous_was_space)
-        out << ' ';
-      out << *curr;
-      previous_was_space = false;
-      if (*curr == '#' || *curr == '"') in_comment_or_string = true;
-    }
-  }
-  return out.str();
-}
diff --git a/subx/040---tests.cc b/subx/040---tests.cc
deleted file mode 100644
index af05bff3..00000000
--- a/subx/040---tests.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-//: Automatically aggregate functions starting with 'test-' into a test suite
-//: called 'run-tests'. Running this function will run all tests.
-//:
-//: This is actually SubX's first (trivial) compiler. We generate all the code
-//: needed for the 'run-tests' function.
-//:
-//: By convention, temporary functions needed by tests will start with
-//: '_test-'.
-
-//: We don't rely on any transforms running in previous layers, but this layer
-//: knows about labels and will emit labels for previous layers to transform.
-:(after "Begin Transforms")
-// Begin Level-4 Transforms
-Transform.push_back(create_test_function);
-// End Level-4 Transforms
-
-:(code)
-void test_run_test() {
-  Mem.push_back(vma(0xbd000000));  // manually allocate memory
-  Reg[ESP].u = 0xbd000100;
-  run(
-      "== code 0x1\n"  // code segment
-      "main:\n"
-      "  e8/call run-tests/disp32\n"  // 5 bytes
-      "  f4/halt\n"                   // 1 byte
-      "test-foo:\n"  // offset 7
-      "  01 d8\n"  // just some unique instruction: add EBX to EAX
-      "  c3/return\n"
-  );
-  // check that code in test-foo ran (implicitly called by run-tests)
-  CHECK_TRACE_CONTENTS(
-      "run: 0x00000007 opcode: 01\n"
-  );
-}
-
-void create_test_function(program& p) {
-  if (p.segments.empty()) return;
-  segment& code = *find(p, "code");
-  trace(3, "transform") << "-- create 'run-tests'" << end();
-  vector<line> new_insts;
-  for (int i = 0;  i < SIZE(code.lines);  ++i) {
-    line& inst = code.lines.at(i);
-    for (int j = 0;  j < SIZE(inst.words);  ++j) {
-      const word& curr = inst.words.at(j);
-      if (*curr.data.rbegin() != ':') continue;  // not a label
-      if (!starts_with(curr.data, "test-")) continue;
-      string fn = drop_last(curr.data);
-      new_insts.push_back(call(fn));
-    }
-  }
-  if (new_insts.empty()) return;  // no tests found
-  code.lines.push_back(label("run-tests"));
-  code.lines.insert(code.lines.end(), new_insts.begin(), new_insts.end());
-  code.lines.push_back(ret());
-}
-
-string to_string(const segment& s) {
-  ostringstream out;
-  for (int i = 0;  i < SIZE(s.lines);  ++i) {
-    const line& l = s.lines.at(i);
-    for (int j = 0;  j < SIZE(l.words);  ++j) {
-      if (j > 0) out << ' ';
-      out << to_string(l.words.at(j));
-    }
-    out << '\n';
-  }
-  return out.str();
-}
-
-line call(string s) {
-  line result;
-  result.words.push_back(call());
-  result.words.push_back(disp32(s));
-  return result;
-}
-
-word call() {
-  word result;
-  result.data = "e8";
-  result.metadata.push_back("call");
-  return result;
-}
-
-word disp32(string s) {
-  word result;
-  result.data = s;
-  result.metadata.push_back("disp32");
-  return result;
-}
-
-line ret() {
-  line result;
-  result.words.push_back(word());
-  result.words.back().data = "c3";
-  result.words.back().metadata.push_back("return");
-  return result;
-}
diff --git a/subx/049memory_layout.subx b/subx/049memory_layout.subx
deleted file mode 100644
index 36837ade..00000000
--- a/subx/049memory_layout.subx
+++ /dev/null
@@ -1,8 +0,0 @@
-# Segment addresses aren't really the concern of any other layer, so we'll
-# define them separately. They're approximate due to fidgety ELF alignment
-# requirements, so don't get clever assuming variables are at specific
-# addresses.
-
-== code 0x09000000
-
-== data 0x0a000000
diff --git a/subx/050_write.subx b/subx/050_write.subx
deleted file mode 100644
index 0d6b8152..00000000
--- a/subx/050_write.subx
+++ /dev/null
@@ -1,57 +0,0 @@
-# _write: write to a file descriptor (fd)
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # just exit; can't test _write just yet
-    # . syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-_write:  # fd : int, s : (address array byte) -> <void>
-    # . prolog
-    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
-    # syscall(write, fd, (data) s+4, (size) *s)
-    # . fd : EBX
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   8/disp8         .                 # copy *(EBP+8) to EBX
-    # . data : ECX = s+4
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy *(EBP+12) to ECX
-    81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm32           # add to ECX
-    # . size : EDX = *s
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # copy *EDX to EDX
-    # . syscall
-    b8/copy-to-EAX  4/imm32/write
-    cd/syscall  0x80/imm8
-    # if (EAX < 0) abort
-    3d/compare-EAX-with  0/imm32
-    0f 8c/jump-if-lesser  $_write:abort/disp32
-$_write:end:
-    # . restore registers
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$_write:abort:
-    # can't write a message here for risk of an infinite loop, so we'll use a special exit code instead
-    # . syscall(exit, 255)
-    bb/copy-to-EBX  0xff/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/051test.subx b/subx/051test.subx
deleted file mode 100644
index 6f8cda7a..00000000
--- a/subx/051test.subx
+++ /dev/null
@@ -1,93 +0,0 @@
-# Rudimentary test harness
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # manual test
-    # check-ints-equal(34, 34)
-    # . . push args
-    68/push  "error in check-ints-equal"/imm32
-    68/push  34/imm32
-    68/push  34/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# print msg to stderr if a != b, otherwise print "."
-check-ints-equal:  # (a : int, b : int, msg : (address array byte)) -> <void>
-    # . prolog
-    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
-    # load first 2 args into EAX and EBX
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
-    # if (EAX == EBX) success
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # compare EAX and EBX
-    75/jump-if-unequal  $check-ints-equal:else/disp8
-    # . _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
-    # . return
-    eb/jump  $check-ints-equal:end/disp8
-    # otherwise print error message
-$check-ints-equal:else:
-    # . _write(2/stderr, msg)
-    # . . push args
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # copy *(EBP+16) to ECX
-    51/push-ECX
-    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, Newline)
-    # . . push args
-    68/push  Newline/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
-    # increment Num-test-failures
-    ff          0/subop/increment   0/mod/indirect  5/rm32/.disp32            .             .           .           Num-test-failures/disp32          # increment *Num-test-failures
-$check-ints-equal:end:
-    # . restore registers
-    5b/pop-to-EBX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data
-
-# length-prefixed string containing just a single newline
-# convenient to have when printing messages and so on
-Newline:
-    # size
-    1/imm32
-    # data
-    0a/newline
-
-# every test failure increments this counter
-Num-test-failures:
-    0/imm32
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/052kernel-string-equal.subx b/subx/052kernel-string-equal.subx
deleted file mode 100644
index d2f196ea..00000000
--- a/subx/052kernel-string-equal.subx
+++ /dev/null
@@ -1,267 +0,0 @@
-# Checking null-terminated ascii strings.
-#
-# By default we create strings with a 4-byte length prefix rather than a null suffix.
-# However we still need null-prefixed strings when interacting with the Linux
-# kernel in a few places. This layer implements a function for comparing
-# a null-terminated 'kernel string' with a length-prefixed 'SubX string'.
-#
-# To run (from the subx directory):
-#   $ ./subx translate 05[0-2]*.subx -o /tmp/tmp52
-#   $ ./subx run /tmp/tmp52  # runs a series of tests
-#   ......  # all tests pass
-#
-# (We can't yet run the tests when given a "test" commandline argument,
-# because checking for it would require the function being tested! Breakage
-# would cause tests to not run, rather than to fail as we'd like.)
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # run all tests
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# compare a null-terminated ascii string with a more idiomatic length-prefixed byte array
-# reason for the name: the only place we should have null-terminated ascii strings is from commandline args
-kernel-string-equal?:  # s : null-terminated ascii string, benchmark : length-prefixed ascii string -> EAX : boolean
-    # pseudocode:
-    #   n = benchmark->length
-    #   s1 = s
-    #   s2 = benchmark->data
-    #   i = 0
-    #   while (i < n)
-    #     c1 = *s1
-    #     c2 = *s2
-    #     if (c1 == 0) return false
-    #     if (c1 != c2) return false
-    #     ++s1, ++s2, ++i
-    #   return *s1 == 0
-    #
-    # registers:
-    #   i: ECX
-    #   n: EDX
-    #   s1: EDI
-    #   s2: ESI
-    #   c1: EAX
-    #   c2: EBX
-    #
-    # . prolog
-    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
-    # s1/EDI = s
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-    # n/EDX = benchmark->length
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # copy *EDX to EDX
-    # s2/ESI = benchmark->data
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    81          0/subop/add         3/mod/direct    6/rm32/ESI    .           .             .           .           .               4/imm32           # add to ESI
-    # i/ECX = c1/EAX = c2/EBX = 0
-    b9/copy-to-ECX  0/imm32/exit
-    b8/copy-to-EAX  0/imm32
-    bb/copy-to-EBX  0/imm32
-$kernel-string-equal?:loop:
-    # if (i >= n) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    7d/jump-if-greater-or-equal  $kernel-string-equal?:break/disp8
-    # c1 = *s1
-    8a/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/AL    .               .                 # copy byte at *EDI to AL
-    # c2 = *s2
-    8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           3/r32/BL    .               .                 # copy byte at *ESI to BL
-    # if (c1 == 0) return false
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $kernel-string-equal?:false/disp8
-    # if (c1 != c2) return false
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # compare EAX and EBX
-    75/jump-if-not-equal  $kernel-string-equal?:false/disp8
-    # ++i
-    41/increment-ECX
-    # ++s1
-    47/increment-EDI
-    # ++s2
-    46/increment-ESI
-    eb/jump  $kernel-string-equal?:loop/disp8
-$kernel-string-equal?:break:
-    # return *s1 == 0
-    8a/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/AL    .               .                 # copy byte at *EDI to AL
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $kernel-string-equal?:false/disp8
-$kernel-string-equal?:true:
-    b8/copy-to-EAX  1/imm32
-    eb/jump  $kernel-string-equal?:end/disp8
-$kernel-string-equal?:false:
-    b8/copy-to-EAX  0/imm32
-$kernel-string-equal?:end:
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# - tests
-
-test-compare-null-kernel-string-with-empty-array:
-    # EAX = kernel-string-equal?(Null-kernel-string, "")
-    # . . push args
-    68/push  ""/imm32
-    68/push  Null-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-null-kernel-string-with-empty-array"/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-null-kernel-string-with-non-empty-array:
-    # EAX = kernel-string-equal?(Null-kernel-string, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  Null-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-null-kernel-string-with-non-empty-array"/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-kernel-string-with-equal-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-equal-array"/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-kernel-string-with-inequal-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "Adc")
-    # . . push args
-    68/push  "Adc"/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-equal-array"/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-kernel-string-with-empty-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "")
-    # . . push args
-    68/push  ""/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-equal-array"/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-kernel-string-with-shorter-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-shorter-array"/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-kernel-string-with-longer-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "Abcd")
-    # . . push args
-    68/push  "Abcd"/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-longer-array"/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
-
-== data
-
-Null-kernel-string:
-    00/null
-
-_test-Abc-kernel-string:
-    41/A 62/b 63/c 00/null
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/053new-segment.subx b/subx/053new-segment.subx
deleted file mode 100644
index 83c890ea..00000000
--- a/subx/053new-segment.subx
+++ /dev/null
@@ -1,90 +0,0 @@
-# Create a new segment (pool of memory for allocating chunks from) in the form
-# of an *allocation descriptor* that can be passed to the memory allocator
-# (defined in a later layer).
-#
-# Currently an allocation descriptor consists of just the bounds of the pool of
-# available memory:
-#
-#   curr : address
-#   end : address
-#
-# This isn't enough information to reclaim individual allocations. We can't
-# support arbitrary reclamation yet.
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:   # manual test
-    # var ad/ECX : (address allocation-descriptor) = {0, 0}
-    68/push  0/imm32/limit
-    68/push  0/imm32/curr
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # new-segment(0x1000, ad)
-    # . . push args
-    51/push-ECX
-    68/push  0x1000/imm32
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = ad->curr
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
-    # write to *EAX to check that we have access to the newly-allocated segment
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0x34/imm32        # copy to *EAX
-    # syscall(exit, EAX)
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-new-segment:  # len : int, ad : (address allocation-descriptor)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    53/push-EBX
-    # copy len to _mmap-new-segment->len
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   _mmap-new-segment:len/disp32      # copy EAX to *_mmap-new-segment:len
-    # mmap(_mmap-new-segment)
-    bb/copy-to-EBX  _mmap-new-segment/imm32
-    b8/copy-to-EAX  0x5a/imm32/mmap
-    cd/syscall  0x80/imm8
-    # copy {EAX, EAX+len} to *ad
-    # . EBX = ad
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
-    # . *EBX = EAX
-    89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EBX
-    # . *(EBX+4) = EAX+len
-    03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # add *(EBP+8) to EAX
-    89/copy                         1/mod/*+disp8   3/rm32/EBX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EBX+4)
-$new-segment:end:
-    # . restore registers
-    5b/pop-to-EBX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data
-
-# various constants used here were found in the Linux sources (search for file mman-common.h)
-_mmap-new-segment:  # type mmap_arg_struct
-    # addr
-    0/imm32
-_mmap-new-segment:len:
-    # len
-    0/imm32
-    # protection flags
-    3/imm32  # PROT_READ | PROT_WRITE
-    # sharing flags
-    0x22/imm32  # MAP_PRIVATE | MAP_ANONYMOUS
-    # fd
-    -1/imm32  # since MAP_ANONYMOUS is specified
-    # offset
-    0/imm32  # since MAP_ANONYMOUS is specified
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/054string-equal.subx b/subx/054string-equal.subx
deleted file mode 100644
index 9784e2ad..00000000
--- a/subx/054string-equal.subx
+++ /dev/null
@@ -1,230 +0,0 @@
-# Comparing 'regular' length-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
-
-Entry:  # run all tests
-#?     e8/call test-compare-equal-strings/disp32
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-string-equal?:  # s : (address string), benchmark : (address string) -> EAX : boolean
-    # pseudocode:
-    #   if (s->length != benchmark->length) return false
-    #   currs = s->data
-    #   currb = benchmark->data
-    #   maxs = s->data + s->length
-    #   while currs < maxs
-    #     c1 = *currs
-    #     c2 = *currb
-    #     if (c1 != c2) return false
-    #     ++currs, ++currb
-    #   return true
-    #
-    # registers:
-    #   currs: ESI
-    #   maxs: ECX
-    #   currb: EDI
-    #   c1: EAX
-    #   c2: EBX
-    #
-    # . prolog
-    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
-    # ECX = s->length
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-$string-equal?:lengths:
-    # if (ECX != benchmark->length) return false
-    39/compare                      0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # compare *EDI and ECX
-    75/jump-if-not-equal  $string-equal?:false/disp8
-    # currs/ESI = s->data
-    81          0/subop/add         3/mod/direct    6/rm32/ESI    .           .             .           .           .               4/imm32           # add to ESI
-    # maxs/ECX = s->data + s->length
-    01/add                          3/mod/direct    1/rm32/ECX    .           .             .           6/r32/ESI   .               .                 # add ESI to ECX
-    # currb/EDI = benchmark->data
-    81          0/subop/add         3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm32           # add to EDI
-    # c1/EAX = c2/EDX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    31/xor                          3/mod/direct    2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # clear EDX
-$string-equal?:loop:
-    # if (currs >= maxs) return true
-    39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ESI with ECX
-    73/jump-if-greater-or-equal-unsigned  $string-equal?: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-not-equal  $string-equal?:false/disp8
-    # ++currs
-    46/increment-ESI
-    # ++currb
-    47/increment-EDI
-    eb/jump  $string-equal?:loop/disp8
-$string-equal?:true:
-    b8/copy-to-EAX  1/imm32
-    eb/jump  $string-equal?:end/disp8
-$string-equal?:false:
-    b8/copy-to-EAX  0/imm32
-$string-equal?:end:
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    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 length-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-lengths:
-    # 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-lengths"/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-string-equal:  # s : (address string), expected : (address string), msg : (address string)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    # EAX = 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-string-equal:end:
-    # . restore registers
-    58/pop-to-EAX
-    # . epilog
-    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-string-equal:
-    # check-string-equal?("Abc", "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  "Abc"/imm32
-    # . . call
-    e8/call  check-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-check-string-equal"/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
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/055stream.subx b/subx/055stream.subx
deleted file mode 100644
index f2969861..00000000
--- a/subx/055stream.subx
+++ /dev/null
@@ -1,73 +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 length as usual
-#
-# some primitives for operating on streams:
-#   - clear-stream (clears everything but the data length)
-#   - rewind-stream (resets read pointer)
-
-== 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 : (address stream) -> <void>
-    # . prolog
-    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
-    # ECX = f->length
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EAX+8) to ECX
-    # ECX = &f->data[f->length]
-    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)
-    # EAX = f->data
-    81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xc/imm32         # add to EAX
-    # while (true)
-$clear-stream:loop:
-    # if (EAX >= ECX) break
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
-    73/jump-if-greater-or-equal-unsigned  $clear-stream:end/disp8
-    # *EAX = 0
-    c6          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm8            # copy byte to *EAX
-    # ++EAX
-    40/increment-EAX
-    eb/jump  $clear-stream:loop/disp8
-$clear-stream:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-rewind-stream:  # f : (address stream) -> <void>
-    # . prolog
-    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
-    # . epilog
-    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/subx/056trace.subx b/subx/056trace.subx
deleted file mode 100644
index c1105b15..00000000
--- a/subx/056trace.subx
+++ /dev/null
@@ -1,1009 +0,0 @@
-# primitives for emitting traces to a 'trace' stream, and for tests to make assertions on its contents
-#
-# A trace stream looks like a regular stream:
-#   write : int  # index at which writes go
-#   read : int  # index that we've read until
-#   data : (array byte)  # prefixed by length as usual
-# Usually the trace stream will be in a separate segment set aside for the purpose.
-#
-# primitives for operating on traces (arguments in quotes):
-#   - initialize-trace-stream: populates Trace-stream with a new segment of the given 'size'
-#   - trace: adds a 'line' to Trace-stream
-#   - check-trace-contains: scans from Trace-stream's start for a matching 'line', prints a 'message' to stderr on failure
-#   - check-trace-scans-to: scans from Trace-stream's read pointer for a matching 'line', prints a 'message' to stderr on failure
-
-== data
-
-# We'll save the address of the trace segment here.
-Trace-stream:
-    0/imm32
-
-Trace-segment:
-    0/imm32/curr
-    0/imm32/limit
-
-# Fake trace-stream for tests.
-# Also illustrates the layout of the real trace-stream (segment).
-_test-trace-stream:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    8/imm32
-    # data
-    00 00 00 00 00 00 00 00  # 8 bytes
-
-== 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 a new segment for the trace stream, initialize its length, and save its address to Trace-stream.
-# The Trace-stream segment will consist of variable-length lines separated by newlines (0x0a)
-initialize-trace-stream:  # n : int -> <void>
-    # . prolog
-    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 = n
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
-    # Trace-segment = new-segment(n)
-    # . . push args
-    68/push  Trace-segment/imm32
-    51/push-ECX
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # copy Trace-segment->curr to *Trace-stream
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-segment/disp32              # copy *Trace-segment to EAX
-    # watch point to catch Trace-stream leaks
-#? $watch-1:
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
-    # Trace-stream->length = n - 12
-    # . ECX -= 12
-    81          5/subop/subtract    3/mod/direct    1/rm32/ECX    .           .             .           .           .               0xc/imm32         # subtract from ECX
-    # . Trace-stream->length = ECX
-    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   8/disp8         .                 # copy ECX to *(EAX+8)
-$initialize-trace-stream:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# Append a string to the given trace stream.
-# Silently give up if it's already full. Or truncate the string if there isn't enough room.
-trace:  # line : (address string)
-    # . prolog
-    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
-    # EDI = *Trace-stream
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           7/r32/EDI   Trace-stream/disp32               # copy *Trace-stream to EDI
-    # ESI = line
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # ECX = t->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
-    # EDX = t->length
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
-    # EAX = _append-3(&t->data[t->write], &t->data[t->length], line)
-    # . . push line
-    56/push-ESI
-    # . . push &t->data[t->length]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  2/index/EDX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+EDX+12 to EBX
-    53/push-EBX
-    # . . push &t->data[t->write]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+ECX+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
-    # if (EAX == 0) return
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $trace:end/disp8
-    # t->write += EAX
-    01/add                          0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # add EAX to *EDI
-    # refresh ECX = t->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
-    # EAX = _append-3(&t->data[t->write], &t->data[t->length], line)
-    # . . push line
-    68/push  Newline/imm32
-    # . . push &t->data[t->length]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  2/index/EDX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+EDX+12 to EBX
-    53/push-EBX
-    # . . push &t->data[t->write]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+ECX+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
-    # t->write += EAX
-    01/add                          0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # add EAX to *EDI
-$trace: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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-trace-single:
-    # push *Trace-stream
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # *Trace-stream = _test-trace-stream
-    b8/copy-to-EAX  _test-trace-stream/imm32
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
-    # clear-trace-stream()
-    e8/call  clear-trace-stream/disp32
-    # trace("Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(*_test-trace-stream->data, 41/A 62/b 0a/newline 00, msg)
-    # . . push args
-    68/push  "F - test-trace-single"/imm32
-    68/push  0x0a6241/imm32/Ab-newline
-    # . . push *_test-trace-stream->data
-    b8/copy-to-EAX  _test-trace-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
-    # pop into *Trace-stream
-    8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
-    # end
-    c3/return
-
-test-trace-appends:
-    # push *Trace-stream
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # *Trace-stream = _test-trace-stream
-    b8/copy-to-EAX  _test-trace-stream/imm32
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
-    # clear-trace-stream()
-    e8/call  clear-trace-stream/disp32
-    # trace("C")
-    # . . push args
-    68/push  "C"/imm32
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # trace("D")
-    # . . push args
-    68/push  "D"/imm32
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(*_test-trace-stream->data, 43/C 0a/newline 44/D 0a/newline, msg)
-    # . . push args
-    68/push  "F - test-trace-appends"/imm32
-    68/push  0x0a440a43/imm32/C-newline-D-newline
-    # . . push *_test-trace-stream->data
-    b8/copy-to-EAX  _test-trace-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
-    # pop into *Trace-stream
-    8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
-    # end
-    c3/return
-
-test-trace-empty-line:
-    # push *Trace-stream
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # *Trace-stream = _test-trace-stream
-    b8/copy-to-EAX  _test-trace-stream/imm32
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
-    # clear-trace-stream()
-    e8/call  clear-trace-stream/disp32
-    # trace("")
-    # . . push args
-    68/push  ""/imm32
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(*_test-trace-stream->data, 0, msg)
-    # . . push args
-    68/push  "F - test-trace-empty-line"/imm32
-    68/push  0/imm32
-    # . . push *_test-trace-stream->data
-    b8/copy-to-EAX  _test-trace-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
-    # pop into *Trace-stream
-    8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
-    # end
-    c3/return
-
-check-trace-contains:  # line : (address string), msg : (address string)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # rewind-stream(*Trace-stream)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-trace-scans-to(line, msg)
-    # . . 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  check-trace-scans-to/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$check-trace-contains:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-check-trace-scans-to:  # line : (address string), msg : (address string)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    # EAX = trace-scan(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  trace-scan/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
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    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-trace-scans-to:end:
-    # . restore registers
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# Start scanning from Trace-stream->read for 'line'. If found, update Trace-stream->read and return true.
-trace-scan:  # line : (address string) -> result/EAX : boolean
-    # pseudocode:
-    #   push Trace-stream->read
-    #   while true:
-    #     if Trace-stream->read >= Trace-stream->write
-    #       break
-    #     if next-line-matches?(Trace-stream, line)
-    #       skip-next-line(Trace-stream)
-    #       dump saved copy of Trace-stream->read
-    #       return true
-    #     skip-next-line(Trace-stream)
-    #   pop saved copy of Trace-stream->read
-    #   return false
-    #
-    # . prolog
-    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 = *Trace-stream
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           6/r32/ESI   Trace-stream/disp32               # copy *Trace-stream to ESI
-    # ECX = Trace-stream->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX                   .                 # copy *ESI to ECX
-    # push Trace-stream->read
-    ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # push *(ESI+4)
-$trace-scan:loop:
-    # if (Trace-stream->read >= Trace-stream->write) return false
-    39/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # compare ECX with *(ESI+4)
-    7d/jump-if-greater-or-equal  $trace-scan:false/disp8
-    # EAX = next-line-matches?(Trace-stream, line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    56/push-ESI
-    # . . call
-    e8/call  next-line-matches?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (EAX == 0) continue
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $trace-scan:continue/disp8
-$trace-scan:true:
-    # skip-next-line(Trace-stream)
-    # . . push args
-    56/push-ESI
-    # . . call
-    e8/call  skip-next-line/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # dump saved copy of Trace-stream->read
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # return true
-    b8/copy-to-EAX  1/imm32/true
-    eb/jump  $trace-scan:end/disp8
-$trace-scan:continue:
-    # skip-next-line(Trace-stream)
-    # . . push args
-    56/push-ESI
-    # . . call
-    e8/call  skip-next-line/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    eb/jump  $trace-scan:loop/disp8
-$trace-scan:false:
-    # restore saved copy of Trace-stream->read
-    8f          0/subop/pop         1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # pop to *(ESI+4)
-    # return false
-    b8/copy-to-EAX  0/imm32/false
-$trace-scan:end:
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-trace-scan-first:
-    # push *Trace-stream
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # setup
-    # . *Trace-stream = _test-trace-stream
-    b8/copy-to-EAX  _test-trace-stream/imm32
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
-    # . clear-trace-stream()
-    e8/call  clear-trace-stream/disp32
-    # . trace("Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # EAX = trace-scan("Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    # . . call
-    e8/call  trace-scan/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-trace-scan-first"/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
-    # pop into *Trace-stream
-    8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
-    # . end
-    c3/return
-
-test-trace-scan-skips-lines-until-found:
-    # push *Trace-stream
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # setup
-    # . *Trace-stream = _test-trace-stream
-    b8/copy-to-EAX  _test-trace-stream/imm32
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
-    # . clear-trace-stream()
-    e8/call  clear-trace-stream/disp32
-    # . trace("Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . trace("cd")
-    # . . push args
-    68/push  "cd"/imm32
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # EAX = trace-scan("cd")
-    # . . push args
-    68/push  "cd"/imm32
-    # . . call
-    e8/call  trace-scan/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-trace-scan-skips-lines-until-found"/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
-    # pop into *Trace-stream
-    8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
-    # . end
-    c3/return
-
-test-trace-second-scan-starts-where-first-left-off:
-    # push *Trace-stream
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # setup
-    # . *Trace-stream = _test-trace-stream
-    b8/copy-to-EAX  _test-trace-stream/imm32
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
-    # . clear-trace-stream()
-    e8/call  clear-trace-stream/disp32
-    # . trace("Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . EAX = trace-scan("Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    # . . call
-    e8/call  trace-scan/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # second scan fails
-    # . EAX = trace-scan("Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    # . . call
-    e8/call  trace-scan/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-trace-second-scan-starts-where-first-left-off"/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
-    # pop into *Trace-stream
-    8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
-    # . end
-    c3/return
-
-test-trace-scan-failure-leaves-read-index-untouched:
-    # push *Trace-stream
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # setup
-    # . *Trace-stream = _test-trace-stream
-    b8/copy-to-EAX  _test-trace-stream/imm32
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
-    # . clear-trace-stream()
-    e8/call  clear-trace-stream/disp32
-    # . trace("Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-ints-equal(_test-trace-stream->read, 0, msg)
-    # . . push args
-    68/push  "F - test-trace-second-scan-starts-where-first-left-off/precondition-failure"/imm32
-    68/push  0/imm32
-    b8/copy-to-EAX  _test-trace-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
-    # perform a failing scan
-    # . EAX = trace-scan("Ax")
-    # . . push args
-    68/push  "Ax"/imm32
-    # . . call
-    e8/call  trace-scan/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # no change in read index
-    # . check-ints-equal(_test-trace-stream->read, 0, msg)
-    # . . push args
-    68/push  "F - test-trace-second-scan-starts-where-first-left-off"/imm32
-    68/push  0/imm32
-    b8/copy-to-EAX  _test-trace-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
-    # pop into *Trace-stream
-    8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
-    # . end
-    c3/return
-
-next-line-matches?:  # t : (address stream), line : (address string) -> result/EAX : boolean
-    # pseudocode:
-    #   while true:
-    #     if (currl >= maxl) break
-    #     if (currt >= maxt) return false
-    #     if (*currt != *currl) return false
-    #     ++currt
-    #     ++currl
-    #   return *currt == '\n'
-    #
-    # . prolog
-    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
-    # EDX = line
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
-    # currl/ESI = line->data
-    # . ESI = line/EDX->data
-    8d/copy-address                 1/mod/*+disp8   2/rm32/EDX    .           .             .           6/r32/ESI   4/disp8         .                 # copy EDX+4 to ESI
-    # maxl/ECX = line->data + line->size
-    # . EAX = line/EDX->size
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .                         0/r32/EAX   .               .                 # copy *EDX to EAX
-    # . maxl/ECX = line->data/ESI + line->size/EAX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    6/base/ESI  0/index/EAX   .           1/r32/ECX   .               .                 # copy EDX+EAX to ECX
-    # EDI = t
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-    # EBX = t->data
-    8d/copy-address                 1/mod/*+disp8   7/rm32/EDI    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy EDI+12 to EBX
-    # maxt/EDX = t->data + t->write
-    # . EAX = t->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .                         0/r32/EAX   .               .                 # copy *EDI to EAX
-    # . maxt/EDX = t->data/EBX + t->write/EAX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    3/base/EBX  0/index/EAX   .           2/r32/EDX   .               .                 # copy EBX+EAX to EDX
-    # currt/EDI = t->data + t->read
-    # . EAX = t/EDI->read
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .                         0/r32/EAX   4/disp8         .                 # copy *(EDI+4) to EAX
-    # . currt/EDI = t->data/EBX + t->read/EAX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    3/base/EBX  0/index/EAX   .           7/r32/EDI   .               .                 # copy EBX+EAX to EDI
-$next-line-matches?:loop:
-    # if (currl/ESI >= maxl/ECX) break
-    39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ESI and ECX
-    73/jump-if-greater-or-equal-unsigned  $next-line-matches?:break/disp8
-    # if (currt/EDI >= maxt/EDX) return false
-    # . EAX = false
-    b8/copy-to-EAX  0/imm32/false
-    39/compare                      3/mod/direct    7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # compare EDI and EDX
-    73/jump-if-greater-or-equal-unsigned  $next-line-matches?:end/disp8
-    # if (*currt/EDI != *currl/ESI) return false
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    31/xor                          3/mod/direct    3/rm32/EAX    .           .             .           3/r32/EAX   .               .                 # clear EBX
-    # . EAX = (char) *currt/EDI
-    8a/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .                         0/r32/EAX   .               .                 # copy *EDI to EAX
-    # . EBX = (char) *currl/ESI
-    8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .                         3/r32/EBX   .               .                 # copy *ESI to EBX
-    # . EAX >= EBX
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # compare EAX and EBX
-    # . EAX = false
-    b8/copy-to-EAX  0/imm32/false
-    75/jump-if-not-equal  $next-line-matches?:end/disp8
-    # ++currt/EDI
-    47/increment-EDI
-    # ++currl/ESI
-    46/increment-ESI
-    eb/jump  $next-line-matches?:loop/disp8
-$next-line-matches?:break:
-    # return *currt == '\n'
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    # . EAX = (char) *currt
-    8a/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .                         0/r32/EAX   .               .                 # copy *EDI to EAX
-    3d/compare-EAX-and  0xa/imm32/newline
-    # . EAX = false
-    b8/copy-to-EAX  1/imm32/true
-    74/jump-if-equal  $next-line-matches?:end/disp8
-    b8/copy-to-EAX  0/imm32/true
-$next-line-matches?:end:
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-line-matches?-no-match-1:
-    # next line of "ABABA" does not match "blah blah"
-    # . EAX = next-line-matches?(_test-stream-line-ABABA, "blah blah")
-    # . . push args
-    68/push  "blah blah"/imm32
-    68/push  _test-stream-line-ABABA/imm32
-    # . . call
-    e8/call  next-line-matches?/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-line-matches?-no-match-1"/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
-    c3/return
-
-test-next-line-matches?-no-match-2:
-    # next line of "ABABA" does not match ""
-    # . EAX = next-line-matches?(_test-stream-line-ABABA, "")
-    # . . push args
-    68/push  ""/imm32
-    68/push  _test-stream-line-ABABA/imm32
-    # . . call
-    e8/call  next-line-matches?/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-line-matches?-no-match-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
-    c3/return
-
-test-next-line-matches?-no-match-3:
-    # next line of "ABABA" does not match  "AA"
-    # . EAX = next-line-matches?(_test-stream-line-ABABA, "AA")
-    # . . push args
-    68/push  "AA"/imm32
-    68/push  _test-stream-line-ABABA/imm32
-    # . . call
-    e8/call  next-line-matches?/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-line-matches?-no-match-3"/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
-    c3/return
-
-test-next-line-matches?-match:
-    # next line of "ABABA" matches "ABABA"
-    # . EAX = next-line-matches?(_test-stream-line-ABABA, "ABABA")
-    # . . push args
-    68/push  "ABABA"/imm32
-    68/push  _test-stream-line-ABABA/imm32
-    # . . call
-    e8/call  next-line-matches?/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-line-matches?-match"/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
-    c3/return
-
-# move t->read to _after_ next newline
-skip-next-line:  # t : (address stream)
-    # pseudocode:
-    #   max = t->data + t->write
-    #   i = t->read
-    #   curr = t->data + t->read
-    #   while true
-    #     if (curr >= max) break
-    #     ++i
-    #     if (*curr == '\n') break
-    #     ++curr
-    #   t->read = i
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    53/push-EBX
-    # ECX = t
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
-    # EDX = t/ECX->data
-    8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy ECX+12 to EDX
-    # EAX = t/ECX->write
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
-    # max/EBX = t->data/EDX + t->write/EAX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    2/base/EDX  0/index/EAX   .           3/r32/EBX   .               .                 # copy EDX+EAX to EBX
-    # EAX = t/ECX->read
-    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EDX
-    # curr/ECX = t->data/EDX + t->read/EAX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    2/base/EDX  0/index/EAX   .           1/r32/ECX   .               .                 # copy EDX+EAX to ECX
-    # i/EDX = EAX
-    8b/copy                         3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy EAX to EDX
-$skip-next-line:loop:
-    # if (curr/ECX >= max/EBX) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           3/r32/EBX   .               .                 # compare ECX and EBX
-    73/jump-if-greater-or-equal-unsigned  $skip-next-line:end/disp8
-    # ++i/EDX
-    42/increment-EDX
-    # if (*curr/ECX == '\n') break
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
-    3d/compare-EAX-and  0a/imm32/newline
-    74/jump-if-equal  $skip-next-line:end/disp8
-    # ++curr/ECX
-    41/increment-ECX
-    # loop
-    eb/jump  $skip-next-line:loop/disp8
-$skip-next-line:end:
-    # ECX = t
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
-    # t/ECX->read = i/EDX
-    89/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # copy EDX to *(ECX+4)
-    # . restore registers
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-skip-next-line-empty:
-    # skipping next line in empty stream leaves read pointer at 0
-    # . skip-next-line(_test-stream-empty)
-    # . . push args
-    68/push  _test-stream-empty/imm32
-    # . . call
-    e8/call  skip-next-line/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-ints-equal(_test-stream-empty->read, 0, msg)
-    # . . push args
-    68/push  "F - test-skip-next-line-empty"/imm32
-    68/push  0/imm32
-    b8/copy-to-EAX  _test-stream-empty/imm32
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(EAX+4) to 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
-    c3/return
-
-test-skip-next-line-filled:
-    # skipping next line increments read pointer by length of line + 1 (for newline)
-    # . skip-next-line(_test-stream-filled)
-    # . . push args
-    68/push  _test-stream-filled/imm32
-    # . . call
-    e8/call  skip-next-line/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-ints-equal(_test-stream-filled->read, 5, msg)
-    # . . push args
-    68/push  "F - test-skip-next-line-filled"/imm32
-    68/push  5/imm32
-    b8/copy-to-EAX  _test-stream-filled/imm32
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(EAX+4) to 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
-    c3/return
-
-clear-trace-stream:
-    # . prolog
-    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 = *Trace-stream
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy *Trace-stream to EAX
-    # ECX = t->length
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EAX+8) to ECX
-    # ECX = &t->data[t->length]
-    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
-    # t->write = 0
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
-    # t->read = 0
-    c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         0/imm32           # copy to *(EAX+4)
-    # EAX = t->data
-    81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xc/imm32         # add to EAX
-$clear-trace-stream:loop:
-    # if (EAX >= ECX) break
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
-    73/jump-if-greater-or-equal-unsigned  $clear-trace-stream:end/disp8
-    # *EAX = 0
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
-    # EAX += 4
-    81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               4/imm32           # add to EAX
-    eb/jump  $clear-trace-stream:loop/disp8
-$clear-trace-stream:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# - helpers
-
-# 3-argument variant of _append
-_append-3:  # out : address, outend : address, s : (array byte) -> num_bytes_appended/EAX
-    # . prolog
-    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->length])
-    # . . push &s->data[s->length]
-    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
-    # . epilog
-    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 : address, outend : address, in : address, inend : address -> num_bytes_appended/EAX
-    # . prolog
-    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
-    # EAX/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-greater-or-equal-unsigned  $_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-greater-or-equal-unsigned  $_append-4:abort/disp8
-    # *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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$_append-4:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "stream overflow\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-== data
-
-_test-stream-line-ABABA:
-    # write
-    8/imm32
-    # read
-    0/imm32
-    # length
-    8/imm32
-    # data
-    41 42 41 42 41 0a 00 00  # 8 bytes
-
-_test-stream-empty:
-    # write
-    0/imm32
-    # read
-    0/imm32
-    # length
-    8/imm32
-    # data
-    00 00 00 00 00 00 00 00  # 8 bytes
-
-_test-stream-filled:
-    # write
-    8/imm32
-    # read
-    0/imm32
-    # length
-    8/imm32
-    # data
-    41 41 41 41 0a 41 41 41  # 8 bytes
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/057write.subx b/subx/057write.subx
deleted file mode 100644
index 203cbf76..00000000
--- a/subx/057write.subx
+++ /dev/null
@@ -1,159 +0,0 @@
-# write: like _write, but also support in-memory streams in addition to file
-# descriptors.
-#
-# Our first dependency-injected and testable primitive. We can pass it either
-# a file descriptor or an address to a stream. If a file descriptor is passed
-# in, we _write to it using the right syscall. If a 'fake file descriptor' or
-# stream is passed in, we append to the stream. This lets us redirect output
-# in tests and check it later.
-#
-# We assume our data segment will never begin at an address shorter than
-# 0x08000000, so any smaller arguments are assumed to be real file descriptors.
-#
-# A stream looks like this:
-#   read: int  # index at which to read next
-#   write: int  # index at which writes go
-#   data: (array byte)  # prefixed by length as usual
-
-== 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
-
-# TODO: come up with a way to signal when a write to disk fails
-write:  # f : fd or (address stream), s : (address array byte) -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # if (f < 0x08000000) _write(f, s) and return  # f can't be a user-mode address, so treat it as a kernel file descriptor
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x08000000/imm32  # compare *(EBP+8)
-    73/jump-if-greater-unsigned-or-equal  $write:fake/disp8
-    # . . 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  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    eb/jump  $write:end/disp8
-$write:fake:
-    # otherwise, treat 'f' as a stream to append to
-    # . 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->length
-    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->length], s)
-    # . . push s
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . push &f->data[f->length]
-    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:
-    # . epilog
-    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:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    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
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/058stream-equal.subx b/subx/058stream-equal.subx
deleted file mode 100644
index 68296212..00000000
--- a/subx/058stream-equal.subx
+++ /dev/null
@@ -1,593 +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 : (address stream), s : (address string) -> EAX : boolean
-    # . prolog
-    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
-    # maxf/EDX = 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
-    # currf/ESI = 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
-    # if (f->write != s->length) return false
-$stream-data-equal?:compare-lengths:
-    39/compare                      0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # compare *EDI and EAX
-    75/jump-if-not-equal  $stream-data-equal?:false/disp8
-    # currs/EDI = s->data
-    81          0/subop/add         3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm32           # add to EDI
-    # EAX = ECX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    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-greater-or-equal-unsigned  $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-not-equal  $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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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-length-check:
-    # . prolog
-    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-length-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
-    # . epilog
-    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 : (address stream), s : (address string), msg : (address string)
-    # . prolog
-    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
-    # . epilog
-    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 : (address stream), s : (address string) -> EAX : boolean
-    # pseudocode:
-    #   currf = f->read  # bound: f->write
-    #   currs = 0  # bound : s->length
-    #   while true
-    #     if currf >= f->write
-    #       return currs >= s->length
-    #     if f[currf] == '\n'
-    #       ++currf
-    #       return currs >= s->length
-    #     if (currs >= s->length) 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->length
-    #   while true
-    #     if (currf >= f->write) break
-    #     if (f[currf] == '\n') break
-    #     if (currs >= s->length) 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->length
-    # 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
-    #
-    # . prolog
-    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
-    # currf/ECX = 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
-    # currs/EDX = 0
-    31/xor                          3/mod/direct    2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # clear EDX
-    # EAX = EBX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    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-greater-or-equal  $next-stream-line-equal?:break/disp8
-    # AL = *(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 (EAX == '\n') break
-    3d/compare-EAX-and  0xa/imm32/newline
-    74/jump-if-equal  $next-stream-line-equal?:break/disp8
-    # if (currs >= s->length) return false
-    3b/compare                      0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # compare EDX with *EDI
-    7d/jump-if-greater-or-equal  $next-stream-line-equal?:false/disp8
-    # BL = *(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 (EAX != EBX) return false
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # compare EAX and EBX
-    75/jump-if-not-equal  $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->length) return true
-    3b/compare                      0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # compare EDX with *EDI
-    7c/jump-if-lesser  $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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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/subx/059stop.subx b/subx/059stop.subx
deleted file mode 100644
index dbe8a663..00000000
--- a/subx/059stop.subx
+++ /dev/null
@@ -1,206 +0,0 @@
-# stop: dependency-injected wrapper around the exit() syscall
-#
-# We'd like to be able to write tests for functions that call exit(), and to
-# make assertions about whether they exit() or not in a given situation. To
-# achieve this we'll call exit() via a smarter wrapper called 'stop'.
-#
-# In the context of a test, calling a function X that calls 'stop' (directly
-# or through further intervening calls) will unwind the stack until X returns,
-# so that we can say check any further assertions after the execution of X. To
-# achieve this end, we'll pass the return address of X as a 'target' argument
-# into X, plumbing it through to 'stop'. When 'stop' gets a non-null target it
-# unwinds the stack until the target. If it gets a null target it calls
-# exit().
-#
-# We'd also like to get the exit status out of 'stop', so we'll combine the
-# input target with an output status parameter into a type called 'exit-descriptor'.
-#
-# So the exit-descriptor looks like this:
-#   target : address  # return address for 'stop' to unwind to
-#   value : int  # exit status stop was called with
-#
-# 'stop' thus takes two parameters: an exit-descriptor and the exit status.
-#
-# 'stop' won't bother cleaning up any other processor state besides the stack,
-# such as registers. Only ESP will have a well-defined value after 'stop'
-# returns. (This is a poor man's setjmp/longjmp, if you know what that is.)
-#
-# Before you can call any function that may call 'stop', you need to pass in an
-# exit-descriptor to it. To create an exit-descriptor use 'tailor-exit-descriptor'
-# below. It's not the most pleasant abstraction in the world.
-#
-# An exit-descriptor's target is its input, computed during 'tailor-exit-descriptor'.
-# Its value is its output, computed during stop and available to the test.
-
-== 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
-
-# Configure an exit-descriptor for a call pushing 'nbytes' bytes of args to
-# the stack.
-# Ugly that we need to know the size of args, but so it goes.
-tailor-exit-descriptor:  # ed : (address exit-descriptor), nbytes : int -> <void>
-    # . prolog
-    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 = nbytes
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
-    # Let X be the value of ESP in the caller, before the call to tailor-exit-descriptor.
-    # The return address for a call in the caller's body will be at:
-    #   X-8 if the caller takes 4 bytes of args for the exit-descriptor (add 4 bytes for the return address)
-    #   X-12 if the caller takes 8 bytes of args
-    #   ..and so on
-    # That's the value we need to return: X-nbytes-4
-    #
-    # However, we also need to account for the perturbance to ESP caused by the
-    # call to tailor-exit-descriptor. It pushes 8 bytes of args followed by 4
-    # bytes for the return address and 4 bytes to push EBP above.
-    # So EBP at this point is X-16.
-    #
-    # So the return address for the next call in the caller is:
-    #   EBP+8 if the caller takes 4 bytes of args
-    #   EBP+4 if the caller takes 8 bytes of args
-    #   EBP if the caller takes 12 bytes of args
-    #   EBP-4 if the caller takes 16 bytes of args
-    #   ..and so on
-    # That's EBP+12-nbytes.
-    # option 1: 6 + 3 bytes
-#?     2d/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           .           .               8/imm32           # subtract from EAX
-#?     8d/copy-address                 0/mod/indirect  4/rm32/sib    5/base/EBP  0/index/EAX   .           0/r32/EAX   .               .                 # copy EBP+EAX to EAX
-    # option 2: 2 + 4 bytes
-    f7          3/subop/negate      3/mod/direct    0/rm32/EAX    .           .             .           .           .               .                 # negate EAX
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    5/base/EBP  0/index/EAX   .           0/r32/EAX   0xc/disp8         .               # copy EBP+EAX+12 to EAX
-    # copy EAX to ed->target
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
-    89/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *ECX
-    # initialize ed->value
-    c7          0/subop/copy        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # copy to *(ECX+4)
-$tailor-exit-descriptor:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-stop:  # ed : (address exit-descriptor), value : int
-    # no prolog; one way or another, we're going to clobber registers
-    # EAX = ed
-    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none  .           0/r32/EAX   4/disp8         .                 # copy *(ESP+4) to EAX
-    # if (ed->target == 0) really exit
-    81          7/subop/compare     0/mod/indirect  0/rm32/EAX    .           .             .           .           .               0/imm32           # compare *EAX
-    75/jump-if-not-equal  $stop:fake/disp8
-    # . syscall(exit, value)
-    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none  .           3/r32/EBX   8/disp8         .                 # copy *(ESP+8) to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-$stop:fake:
-    # otherwise:
-    # ed->value = value+1
-    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   8/disp8         .                 # copy *(ESP+8) to ECX
-    41/increment-ECX
-    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy ECX to *(EAX+4)
-    # perform a non-local jump to ed->target
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy *EAX to ESP
-$stop:end:
-    c3/return  # doesn't return to caller
-
-test-stop-skips-returns-on-exit:
-    # This looks like the standard prolog, but is here for different reasons.
-    # A function calling 'stop' can't rely on EBP persisting past the call.
-    #
-    # Use EBP here as a stable base to refer to locals and arguments from in the
-    # presence of push/pop/call instructions.
-    # *Don't* use EBP as a way to restore ESP.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # Make room for an exit descriptor on the stack. That's almost always the
-    # right place for it, available only as long as it's legal to use. Once this
-    # containing function returns we'll need a new exit descriptor.
-    # var ed/EAX : (address exit-descriptor)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # Size the exit-descriptor precisely for the next call below, to _test-stop-1.
-    # tailor-exit-descriptor(ed, 4)
-    # . . push args
-    68/push  4/imm32/nbytes-of-args-for-_test-stop-1
-    50/push-EAX
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . _test-stop-1(ed)
-    # . . push args
-    50/push-EAX
-    # . . call
-    e8/call  _test-stop-1/disp32
-    # registers except ESP may be clobbered at this point
-    # restore args
-    58/pop-to-EAX
-    # check that _test-stop-1 tried to call exit(1)
-    # check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
-    # . . push args
-    68/push  "F - test-stop-skips-returns-on-exit"/imm32
-    68/push  2/imm32
-    # . . push ed->value
-    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
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-_test-stop-1:  # ed : (address exit-descriptor)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # _test-stop-2(ed)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  _test-stop-2/disp32
-    # should never get past this point
-$_test-stop-1:dead-end:
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # signal test failed: check-ints-equal(1, 0, msg)
-    # . . push args
-    68/push  "F - test-stop-skips-returns-on-exit"/imm32
-    68/push  0/imm32
-    68/push  1/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-_test-stop-2:  # ed : (address exit-descriptor)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . stop(ed, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  stop/disp32
-    # should never get past this point
-$_test-stop-2:dead-end:
-    # . epilog
-    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/subx/060read.subx b/subx/060read.subx
deleted file mode 100644
index 61ee75b0..00000000
--- a/subx/060read.subx
+++ /dev/null
@@ -1,440 +0,0 @@
-# read: analogously to write, support reading from in-memory streams in
-# addition to file descriptors.
-#
-# We can pass it either a file descriptor or an address to a stream. If a
-# file descriptor is passed in, we _read from it using the right syscall. If a
-# stream is passed in (a fake file descriptor), we read from it instead. This
-# lets us initialize input for tests.
-#
-# A little counter-intuitively, the output of 'read' ends up in.. a stream. So
-# tests end up doing a redundant copy. Why? Well, consider the alternatives:
-#
-#   a) Reading into a string, and returning a pointer to the end of the read
-#   region, or a count of bytes written. Now this count or end pointer must be
-#   managed separately by the caller, which can be error-prone.
-#
-#   b) Having 'read' return a buffer that it allocates. But there's no way to
-#   know in advance how large to make the buffer. If you read less than the
-#   size of the buffer you again end up needing to manage initialized vs
-#   uninitialized memory.
-#
-#   c) Creating more helpful variants like 'read-byte' or 'read-until' which
-#   also can take a file descriptor or stream, just like 'write'. But such
-#   primitives don't exist in the Linux kernel, so we'd be implementing them
-#   somehow, either with more internal buffering or by making multiple
-#   syscalls.
-#
-# Reading into a stream avoids these problems. The buffer is externally
-# provided and the caller has control over where it's allocated, its lifetime,
-# and so on. The buffer's read and write pointers are internal to it so it's
-# easier to keep in a consistent state. And it can now be passed directly to
-# helpers like 'read-byte' or 'read-until' that only need to support streams,
-# never file descriptors.
-#
-# Like with 'write', we assume our data segment will never begin at an address
-# shorter than 0x08000000, so any smaller arguments are assumed to be real
-# file descriptors.
-#
-# As a reminder, a stream looks like this:
-#   write: int  # index at which to write to next
-#   read: int  # index at which to read next
-#   data: (array byte)  # prefixed by length as usual
-
-== 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
-
-read:  # f : fd or (address stream), s : (address stream) -> num-bytes-read/EAX
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # if (f < 0x08000000) return _read(f, s)  # f can't be a user-mode address, so treat it as a kernel file descriptor
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x08000000/imm32  # compare *(EBP+8)
-    73/jump-if-greater-unsigned-or-equal  $read:fake/disp8
-    # . . 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  _read/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # return
-    eb/jump  $read:end/disp8
-$read:fake:
-    # otherwise, treat 'f' as a stream to scan from
-    # . save registers
-    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
-    # EDI = s
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # EAX = _buffer-4(out = &s->data[s->write], outend = &s->data[s->length],
-    #                 in  = &f->data[f->read],  inend  = &f->data[f->write])
-    # . . push &f->data[f->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 &f->data[f->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 &s->data[s->length]
-    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 &s->data[s->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  _buffer-4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # s->write += EAX
-    01/add                          0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # add EAX to *EDI
-    # f->read += EAX
-    01/add                          1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # add EAX to *(ESI+4)
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-$read:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# - helpers
-
-# '_buffer' is like '_append', but silently stops instead of aborting when it runs out of space
-
-# 3-argument variant of _buffer
-_buffer-3:  # out : address, outend : address, s : (array byte) -> num_bytes_buffered/EAX
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    # EAX = _buffer-4(out, outend, &s->data[0], &s->data[s->length])
-    # . . push &s->data[s->length]
-    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  _buffer-4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-$_buffer-3:end:
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    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 _buffer
-_buffer-4:  # out : address, outend : address, in : address, inend : address -> num_bytes_buffered/EAX
-    # . prolog
-    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
-    # EAX/num_bytes_buffered = 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
-$_buffer-4:loop:
-    # if (in >= inend) break
-    39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ESI with ECX
-    73/jump-if-greater-or-equal-unsigned  $_buffer-4:end/disp8
-    # if (out >= outend) break  # for now silently ignore filled up buffer
-    39/compare                      3/mod/direct    7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # compare EDI with EDX
-    73/jump-if-greater-or-equal-unsigned  $_buffer-4:end/disp8
-    # *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_buffered
-    40/increment-EAX
-    # ++in
-    46/increment-ESI
-    # ++out
-    47/increment-EDI
-    eb/jump  $_buffer-4:loop/disp8
-$_buffer-4:end:
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-
-# idea: a clear-if-empty method on streams that clears only if f->read == f->write
-# Unclear how I'd use it, though. Callers seem to need the check anyway.
-# Maybe a better helper would be 'empty-stream?'
-
-_read:  # fd : int, s : (address stream) -> num-bytes-read/EAX
-    # . prolog
-    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   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # EAX = s->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
-    # EDX = s->length
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(ESI+8) to EDX
-    # syscall(read, fd, &s->data[s->write], s->length - s->write)
-    # . . fd : EBX
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   8/disp8         .                 # copy *(EBP+8) to EBX
-    # . . data : ECX = &s->data[s->write]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  0/index/EAX   .           1/r32/ECX   0xc/disp8       .                 # copy ESI+EAX+12 to ECX
-    # . . size : EDX = s->length - s->write
-    29/subtract                     3/mod/direct    2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # subtract EAX from EDX
-    # . . syscall
-    b8/copy-to-EAX  3/imm32/read
-    cd/syscall  0x80/imm8
-    # add the result EAX to s->write
-    01/add                          0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # add EAX to *ESI
-$_read:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-    # Two options:
-    #   1 (what we have above):
-    #     ECX = s
-    #     EAX = s->write
-    #     EDX = s->length
-    #     # syscall
-    #     ECX = lea ECX+EAX+12
-    #     EDX = sub EDX EAX
-    #
-    #   2:
-    #     ECX = s
-    #     EDX = s->length
-    #     ECX = &s->data
-    #     # syscall
-    #     ECX = add ECX, s->write
-    #     EDX = sub EDX, s->write
-    #
-    # Not much to choose between the two? Option 2 performs a duplicate load to
-    # use one less register, but doesn't increase the amount of spilling (ECX
-    # and EDX must be used, and EAX must be clobbered anyway).
-
-# - tests
-
-test-read-single:
-    # - write a single character into _test-stream, then read from it
-    # 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-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-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
-    # EAX = read(_test-stream, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  read/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(EAX, 2, msg)
-    # . . push args
-    68/push  "F - test-read-single: return EAX"/imm32
-    68/push  2/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(_test-tmp-stream, "Ab", msg)
-    # . . push args
-    68/push  "F - test-read-single"/imm32
-    68/push  "Ab"/imm32
-    68/push  _test-tmp-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-read-is-stateful:
-    # - make two consecutive reads, check that their results are appended
-    # 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-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-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
-    # read(_test-stream, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  read/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
-    # read(_test-stream, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  read/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-stream-equal(_test-tmp-stream, "CD", msg)
-    # . . push args
-    68/push  "F - test-read-is-stateful"/imm32
-    68/push  "CD"/imm32
-    68/push  _test-tmp-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-read-returns-0-on-end-of-file:
-    # - read after hitting end-of-file, check that result is 0
-    # 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-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-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
-    # first read gets to end-of-file
-    # . read(_test-stream, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  read/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # second read
-    # . read(_test-stream, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  read/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-read-returns-0-on-end-of-file"/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
-    # end
-    c3/return
-
-== data
-
-_test-tmp-stream:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    8/imm32
-    # data
-    00 00 00 00 00 00 00 00  # 8 bytes
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/061read-byte.subx b/subx/061read-byte.subx
deleted file mode 100644
index 99e2babe..00000000
--- a/subx/061read-byte.subx
+++ /dev/null
@@ -1,293 +0,0 @@
-# read-byte-buffered: one higher-level abstraction atop 'read'.
-#
-# There are many situations where 'read' is a lot to manage, and we need
-# to abstract some details away. One of them is when we want to read a file
-# character by character. In this situation we follow C's FILE data structure,
-# which manages the underlying file descriptor together with the buffer it
-# reads into. We call our version 'buffered-file'. Should be useful with other
-# primitives as well, in later layers.
-
-== data
-
-# The buffered file for standard input. Also illustrates the layout for
-# buffered-file.
-Stdin:
-    # file descriptor or (address stream)
-    0/imm32  # standard input
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    8/imm32
-    # data
-    00 00 00 00 00 00 00 00  # 8 bytes
-
-# TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
-# I don't want to type in 1024 bytes here.
-
-== 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.
-# On reaching end of file, return 0xffffffff (Eof).
-read-byte-buffered:  # f : (address buffered-file) -> byte-or-Eof/EAX
-    # . prolog
-    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 = f
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # ECX = f->read
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(ESI+8) to ECX
-    # if (f->read >= f->write) populate stream from file
-    3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # compare ECX with *(ESI+4)
-    7c/jump-if-lesser  $read-byte-buffered:from-stream/disp8
-    # . clear-stream(stream = f+4)
-    # . . push args
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy ESI+4 to EAX
-    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
-    # . f->read must now be 0; update its cache at ECX
-    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-    # . EAX = read(f->fd, stream = f+4)
-    # . . push args
-    50/push-EAX
-    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
-    # . . call
-    e8/call  read/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (EAX == 0) return 0xffffffff
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $read-byte-buffered:from-stream/disp8
-    b8/copy-to-EAX  0xffffffff/imm32/Eof
-    eb/jump  $read-byte-buffered:end/disp8
-$read-byte-buffered:from-stream:
-    # read byte from stream
-    # AL = 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    0x10/disp8      .                 # copy byte at *(ESI+ECX+16) to AL
-    # ++f->read
-    ff          0/subop/increment   1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # increment *(ESI+8)
-$read-byte-buffered:end:
-    # . restore registers
-    5e/pop-to-ESI
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# - tests
-
-test-read-byte-buffered-single:
-    # - check that read-byte-buffered returns 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
-    # . clear-stream(_test-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . 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
-    # read-byte-buffered(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  read-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 'A', msg)
-    # . . push args
-    68/push  "F - test-read-byte-buffered-single"/imm32
-    68/push  0x41/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
-    # . end
-    c3/return
-
-test-read-byte-buffered-multiple:
-    # - call read-byte-buffered twice, check that second call returns second byte
-    # 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . 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
-    # read-byte-buffered(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  read-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read-byte-buffered(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  read-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 'b', msg)
-    # . . push args
-    68/push  "F - test-read-byte-buffered-multiple"/imm32
-    68/push  0x62/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
-    # . end
-    c3/return
-
-test-read-byte-buffered-end-of-file:
-    # - call read-byte-buffered on an empty 'file', check that it returns Eof
-    # 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # read-byte-buffered(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  read-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 0xffffffff, msg)
-    # . . push args
-    68/push  "F - test-read-byte-buffered-end-of-file"/imm32
-    68/push  0xffffffff/imm32/Eof
-    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
-    # . end
-    c3/return
-
-test-read-byte-buffered-refills-buffer:
-    # - consume buffered-file's buffer, check that next read-byte-buffered still works
-    # 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . write(_test-stream, "Abcdefgh")
-    # . . push args
-    68/push  "Abcdefgh"/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
-    # pretend buffer is full
-    # . _test-buffered-file->read = 6  # >= _test-buffered-file->length
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         6/imm32           # copy to *(EAX+8)
-    # read-byte-buffered(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  read-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 'A', msg)
-    # . . push args
-    68/push  "F - test-read-byte-buffered-refills-buffer"/imm32
-    68/push  0x41/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
-    # . end
-    c3/return
-
-== data
-
-# a test buffered file for _test-stream
-_test-buffered-file:
-    # file descriptor or (address stream)
-    _test-stream/imm32
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    6/imm32
-    # data
-    00 00 00 00 00 00  # 6 bytes
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/062write-stream.subx b/subx/062write-stream.subx
deleted file mode 100644
index c4ec8db2..00000000
--- a/subx/062write-stream.subx
+++ /dev/null
@@ -1,255 +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
-
-#? Entry:  # manual test
-#?     # write-stream(stdout, _test-stream2)
-#?     68/push  _test-stream2/imm32
-#?     68/push  1/imm32/stdout
-#?     e8/call write-stream/disp32
-#?     # syscall(exit, Num-test-failures)
-#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-#?     b8/copy-to-EAX  1/imm32/exit
-#?     cd/syscall  0x80/imm8
-
-write-stream:  # f : fd or (address stream), s : (address stream) -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # if (f < 0x08000000) _write-stream(f, s), return  # f can't be a user-mode address, so treat it as a kernel file descriptor
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x08000000/imm32  # compare *(EBP+8)
-    73/jump-if-greater-unsigned-or-equal  $write-stream:fake/disp8
-    # . . 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  _write-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    eb/jump  $write-stream:end/disp8
-$write-stream:fake:
-    # otherwise, treat 'f' as a stream to append to
-    # . 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->length], &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->length]
-    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)
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    58/pop-to-EAX
-$write-stream:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-_write-stream:  # fd : int, s : (address stream) -> <void>
-    # . prolog
-    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
-    # EDI = s->read
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           7/r32/EDI   4/disp8         .                 # copy *(ESI+4) to EDI
-    # EDX = s->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    # syscall(write, fd, &s->data[s->read], s->write - s->read)
-    # . . fd : EBX
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   8/disp8         .                 # copy *(EBP+8) to EBX
-    # . . data : ECX = &s->data[s->read]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  7/index/EDI   .           1/r32/ECX   0xc/disp8       .                 # copy ESI+EDI+12 to ECX
-    # . . size : EDX = s->write - s->read
-    29/subtract                     3/mod/direct    2/rm32/EDX    .           .             .           7/r32/EDI   .               .                 # subtract EDI from EDX
-    # . . syscall
-    b8/copy-to-EAX  4/imm32/write
-    cd/syscall  0x80/imm8
-    # if (EAX < 0) abort
-    3d/compare-EAX-with  0/imm32
-    0f 8c/jump-if-lesser  $_write-stream:abort/disp32
-    # s->read += EAX
-    01/add                          1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # add EAX to *(ESI+4)
-    # . 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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$_write-stream:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "_write-stream: failed to write to file\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-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:
-    # current write index
-    4/imm32
-    # current read index
-    1/imm32
-    # length
-    8/imm32
-    # data
-    41/A 42/B 43/C 44/D 00 00 00 00  # 8 bytes
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/063error.subx b/subx/063error.subx
deleted file mode 100644
index db460db1..00000000
--- a/subx/063error.subx
+++ /dev/null
@@ -1,50 +0,0 @@
-# Print an error message and exit.
-
-== 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(out, "Error: "+msg+"\n") then stop(ed, 1)
-error:  # ed : (address exit-descriptor), out : fd or (address stream), msg : (address array byte) -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write(out, "Error: ")
-    # . . push args
-    68/push  "Error: "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(out, msg)
-    # . . 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)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(out, Newline)
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # stop(ed, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  stop/disp32
-    # should never get past this point
-$error:dead-end:
-    # . epilog
-    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/subx/064write-byte.subx b/subx/064write-byte.subx
deleted file mode 100644
index 057b9164..00000000
--- a/subx/064write-byte.subx
+++ /dev/null
@@ -1,288 +0,0 @@
-# write-byte-buffered: add a single byte to a buffered-file.
-# flush: write out any buffered writes to disk.
-#
-# TODO: Come up with a way to signal failure to write to disk. This is hard
-# since the failure may impact previous calls that were buffered.
-
-== data
-
-# The buffered file for standard output.
-Stdout:
-    # file descriptor or (address stream)
-    1/imm32  # standard output
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    8/imm32
-    # data
-    00 00 00 00 00 00 00 00  # 8 bytes
-
-# TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
-# I don't want to type in 1024 bytes here.
-
-== 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'.
-write-byte-buffered:  # f : (address buffered-file), n : int -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    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                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
-    # if (f->write >= f->length) flush and clear f's stream
-    3b/compare                      1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   0xc/disp8       .                 # compare ECX with *(EDI+12)
-    7c/jump-if-lesser  $write-byte-buffered:to-stream/disp8
-    # . flush(f)
-    # . . push args
-    57/push-EDI
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(stream = f+4)
-    # . . push args
-    8d/copy-address                 1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EDI+4 to EAX
-    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
-    # . f->write must now be 0; update its cache at ECX
-    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-$write-byte-buffered: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    0x10/disp8      .                 # copy AL to *(EDI+ECX+16)
-    # ++f->write
-    ff          0/subop/increment   1/mod/*+disp8   7/rm32/EDI    .           .             .           .           4/disp8         .                 # increment *(EDI+4)
-$write-byte-buffered:end:
-    # . restore registers
-    5f/pop-to-EDI
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-flush:  # f : (address buffered-file) -> <void>
-    # . prolog
-    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
-    # write-stream(f->fd, data = f+4)
-      # . . push args
-    8d/copy-address                 1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy EAX+4 to ECX
-    51/push-ECX
-    ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
-      # . . call
-    e8/call  write-stream/disp32
-      # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$flush:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-write-byte-buffered-single:
-    # - check that write-byte-buffered 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
-    # . clear-stream(_test-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # write-byte-buffered(_test-buffered-file, 'A')
-    # . . push args
-    68/push  0x41/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  write-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-stream, "A", msg)
-    # . . push args
-    68/push  "F - test-write-byte-buffered-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
-
-test-write-byte-buffered-multiple-flushes:
-    # - check that write-byte-buffered correctly flushes buffered data
-    # 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # fill up the buffer for _test-buffered-file
-    # . write(_test-buffered-file+4, 'abcdef')
-    # . . push args
-    68/push  "abcdef"/imm32
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    50/push-EAX
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-byte-buffered(_test-buffered-file, 'g')
-    # . . push args
-    68/push  0x67/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  write-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-stream, "abcdef", msg)
-    # . . push args
-    68/push  "F - test-write-byte-buffered-multiple-flushes: 1"/imm32
-    68/push  "abcdefg"/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
-
-# - variant without buffering
-
-# Write lower byte of 'n' to 'f'.
-append-byte:  # f : (address stream), n : int -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    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->length) abort
-    3b/compare                      1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(EDI+8)
-    7d/jump-if-greater-or-equal  $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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$append-byte:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "append-byte: out of space\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # 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/subx/065write-buffered.subx b/subx/065write-buffered.subx
deleted file mode 100644
index 88b14b2d..00000000
--- a/subx/065write-buffered.subx
+++ /dev/null
@@ -1,228 +0,0 @@
-# write-buffered: like 'write', but for a buffered-file
-
-== 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-buffered:  # f : (address buffered-file), msg : (address array byte) -> <void>
-    # pseudocode:
-    #   in = msg->data
-    #   inend = &msg->data[msg->length]
-    #   while (in < inend)
-    #     if f->write >= f->length
-    #       flush(f)
-    #       clear-stream(f)
-    #     c = *in
-    #     f->data[f->write] = c
-    #     ++f->write
-    #     ++in
-    #
-    # registers:
-    #   in: ESI
-    #   inend: ECX
-    #   f: EDI
-    #   f->length: EDX
-    #   f->write: EBX (cached; need to keep in sync)
-    #   c: EAX
-    #
-    # . prolog
-    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
-    # EAX = msg
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
-    # in/ESI = msg->data
-    8d/copy-address                 1/mod/*+disp8   0/rm32/EAX    .           .             .           6/r32/ESI   4/disp8         .                 # copy EAX+4 to ESI
-    # inend/ECX = &msg->data[msg->length]
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    6/base/ESI  1/index/ECX   .           1/r32/ECX   .               .                 # copy ESI+ECX to ECX
-    # EDI = f
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-    # EDX = f->length
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EDI+12) to EDX
-    # EBX = f->write
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           3/r32/EBX   4/disp8         .                 # copy *(EDI+4) to EBX
-$write-buffered:loop:
-    # if (in >= inend) break
-    39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ESI with ECX
-    73/jump-if-greater-or-equal-unsigned  $write-buffered:loop-end/disp8
-    # if (f->write >= f->length) flush and clear f's stream
-    39/compare                      3/mod/direct    3/rm32/EBX    .           .             .           2/r32/EDX   .               .                 # compare EBX with EDX
-    7c/jump-if-lesser  $write-buffered:to-stream/disp8
-    # . persist f->write
-    89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           3/r32/EBX   4/disp8         .                 # copy EBX to *(EDI+4)
-    # . flush(f)
-    # . . push args
-    57/push-EDI
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(stream = f+4)
-    # . . push args
-    8d/copy-address                 1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EDI+4 to EAX
-    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
-    # . f->write must now be 0; update its cache at EBX
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-$write-buffered:to-stream:
-    # write to stream
-    # f->data[f->write] = *in
-    # . AL = *in
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/AL    .               .                 # copy byte at *ESI to AL
-    # . f->data[f->write] = AL
-    88/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/EDI  3/index/EBX   .           0/r32/AL    0x10/disp8      .                 # copy AL to *(EDI+EBX+16)
-    # ++f->write
-    43/increment-EBX
-    # ++in
-    46/increment-ESI
-    eb/jump  $write-buffered:loop/disp8
-$write-buffered:loop-end:
-    # persist necessary variables from registers
-    89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           3/r32/EBX   4/disp8         .                 # copy EBX to *(EDI+4)
-$write-buffered: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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-write-buffered:
-    # - check that write-buffered writes to the 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
-    # . clear-stream(_test-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # write-buffered(_test-buffered-file, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-stream, "Abc", msg)
-    # . . push args
-    68/push  "F - test-write-buffered-single"/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
-    # . end
-    c3/return
-
-test-write-buffered-with-intermediate-flush:
-    # - check that write-buffered flushes in the middle if its buffer fills up
-    # 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # _test-stream can hold 8 bytes, but _test-buffered-file can hold only 6.
-    # Try to write 7 bytes.
-    # . write-buffered(_test-buffered-file, "Abcdefg")
-    # . . push args
-    68/push  "Abcdefg"/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # don't flush
-    # 6 bytes should still have gotten to _test-stream
-    # . check-ints-equal(*_test-stream->write, 6, msg)
-    # . . push args
-    68/push  "F - test-write-buffered-with-intermediate-flush: flushed data"/imm32
-    68/push  6/imm32
-    # . . push *_test-stream->write
-    b8/copy-to-EAX  _test-stream/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
-    # and 1 byte should still be in _test-buffered-file
-    # . check-ints-equal(*_test-buffered-file->write, 1, msg)
-    # . . push args
-    68/push  "F - test-write-buffered-with-intermediate-flush: unflushed bytes"/imm32
-    68/push  1/imm32
-    # . . push *_test-buffered-file->write
-    b8/copy-to-EAX  _test-buffered-file/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
-
-== data
-
-# The buffered file for standard error.
-Stderr:
-    # file descriptor or (address stream)
-    2/imm32  # standard error
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    8/imm32
-    # data
-    00 00 00 00 00 00 00 00  # 8 bytes
-
-# TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
-# I don't want to type in 1024 bytes here.
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/066print-int.subx b/subx/066print-int.subx
deleted file mode 100644
index c5968837..00000000
--- a/subx/066print-int.subx
+++ /dev/null
@@ -1,389 +0,0 @@
-# Print 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
-
-to-hex-char:  # in/EAX : nibble -> out/EAX : byte
-    # no error checking; accepts argument in EAX
-    # if (EAX <= 9) return EAX + '0'
-    3d/compare-EAX-with  0x9/imm32/9
-    7f/jump-if-greater  $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 : (address stream), n : int -> <void>
-    # . prolog
-    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
-    # . epilog
-    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
-
-# print the hex representation for the lowest byte of a number
-print-byte-buffered:  # f : (address buffered-file), n : int -> <void>
-    # . prolog
-    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
-    # write-byte-buffered(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  write-byte-buffered/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
-    # write-byte-buffered(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  write-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$print-byte-buffered:end:
-    # . restore registers
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-print-byte-buffered:
-    # - check that print-byte-buffered 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
-    # . clear-stream(_test-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # print-byte-buffered(_test-buffered-file, 0xa)  # exercises digit, non-digit as well as leading zero
-    # . . push args
-    68/push  0xa/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  print-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-stream, "0a", msg)
-    # . . push args
-    68/push  "F - test-print-byte-buffered"/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
-
-print-int32:  # f : (address stream), n : int -> <void>
-    # pseudocode:
-    #  write(f, "0x")
-    #  ECX = 28
-    #  while true
-    #    if (ECX < 0) break
-    #    EAX = n >> ECX
-    #    EAX = EAX & 0xf
-    #    append-byte(f, AL)
-    #    ECX -= 4
-    #
-    # . prolog
-    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 = 28
-    b9/copy-to-ECX  0x1c/imm32
-$print-int32:print-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
-$print-int32:loop:
-    # if (ECX < 0) break
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0/imm32           # compare ECX
-    7c/jump-if-lesser  $print-int32:end/disp8
-    # EAX = n >> ECX
-    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
-    # ECX -= 4
-    81          5/subop/subtract    3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm32           # subtract from ECX
-    eb/jump  $print-int32:loop/disp8
-$print-int32:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-print-int32:
-    # - check that print-int32 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
-    # print-int32(_test-stream, 0x8899aa)
-    # . . push args
-    68/push  0x8899aa/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  print-int32/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-print-int32"/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
-
-print-int32-buffered:  # f : (address buffered-file), n : int -> <void>
-    # pseudocode:
-    #  write-buffered(f, "0x")
-    #  ECX = 28
-    #  while true
-    #    if (ECX < 0) break
-    #    EAX = n >> ECX
-    #    EAX = EAX & 0xf
-    #    write-byte-buffered(f, AL)
-    #    ECX -= 4
-    #
-    # . prolog
-    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 = 28
-    b9/copy-to-ECX  0x1c/imm32
-$print-int32-buffered:print-hex-prefix:
-    # write-buffered(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-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$print-int32-buffered:loop:
-    # if (ECX < 0) break
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0/imm32           # compare ECX
-    7c/jump-if-lesser  $print-int32-buffered:end/disp8
-    # EAX = n >> ECX
-    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
-    # write-byte-buffered(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  write-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # ECX -= 4
-    81          5/subop/subtract    3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm32           # subtract from ECX
-    eb/jump  $print-int32-buffered:loop/disp8
-$print-int32-buffered:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-print-int32-buffered:
-    # - check that print-int32-buffered 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
-    # . clear-stream(_test-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # print-int32-buffered(_test-buffered-file, 0x8899aa)
-    # . . push args
-    68/push  0x8899aa/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  print-int32-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump line {{{
-#?     # . write-stream(2/stderr, line)
-#?     # . . 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, "0x008899aa", msg)
-    # . . push args
-    68/push  "F - test-print-int32-buffered"/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/subx/067parse-hex.subx b/subx/067parse-hex.subx
deleted file mode 100644
index 45d168d9..00000000
--- a/subx/067parse-hex.subx
+++ /dev/null
@@ -1,874 +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 : (address slice) -> EAX : boolean
-    # . prolog
-    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
-    # curr/ECX = 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-greater-or-equal-unsigned  $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-not-equal  $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-not-equal  $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-greater-or-equal-unsigned  $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-not-equal  $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-greater-or-equal-unsigned  $is-hex-int?:true/disp8
-    # EAX = 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
-    74/jump-if-equal  $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
-    # . epilog
-    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:
-    # . prolog
-    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 = {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
-    # . epilog
-    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:
-    # . prolog
-    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 = {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
-    # . epilog
-    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:
-    # . prolog
-    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 = {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
-    # . epilog
-    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:
-    # . prolog
-    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 = {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
-    # . epilog
-    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:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var slice/ECX = ""
-    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
-    # . epilog
-    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:
-    # . prolog
-    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 = {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
-    # . epilog
-    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:
-    # . prolog
-    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 = {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
-    # . epilog
-    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:
-    # . prolog
-    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 = {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
-    # . epilog
-    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 : (address slice) -> result/EAX
-    # . prolog
-    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
-    # result/EBX = 0
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear 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
-    # curr/ECX = s->start
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # copy *ECX to ECX
-    # negate?/ESI = false
-    31/xor                          3/mod/direct    6/rm32/ESI    .           .             .           6/r32/ESI   .               .                 # clear ESI
-$parse-hex-int:negative:
-    # . if (*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-not-equal  $parse-hex-int:initial-0/disp8
-    # . ++curr
-    41/increment-ECX
-    # . negate = true
-    be/copy-to-ESI  1/imm32/true
-$parse-hex-int: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-not-equal  $parse-hex-int:loop/disp8
-    # . ++curr
-    41/increment-ECX
-$parse-hex-int: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-greater-or-equal-unsigned  $parse-hex-int: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-not-equal  $parse-hex-int:loop/disp8
-    # . ++curr
-    41/increment-ECX
-$parse-hex-int:loop:
-    # if (curr >= in->end) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $parse-hex-int:negate/disp8
-    # EAX = 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:loop/disp8
-$parse-hex-int:negate:
-    81          7/subop/compare     3/mod/direct    6/rm32/ESI    .           .             .           .           .               0/imm32           # compare ESI
-    74/jump-if-equal  $parse-hex-int:end/disp8
-    f7          3/subop/negate      3/mod/direct    3/rm32/EBX    .           .             .           .           .               .                 # negate EBX
-$parse-hex-int:end:
-    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
-    # . epilog
-    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-single-digit:
-    # . prolog
-    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 = {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(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-hex-int/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-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
-    # . epilog
-    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-multi-digit:
-    # . prolog
-    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 = {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(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-hex-int/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-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
-    # . epilog
-    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-0x-prefix:
-    # . prolog
-    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 = {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(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-hex-int/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-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
-    # . epilog
-    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-zero:
-    # . prolog
-    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 = {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(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-hex-int/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-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
-    # . epilog
-    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-0-prefix:
-    # . prolog
-    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 = {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(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-hex-int/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-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
-    # . epilog
-    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-negative:
-    # . prolog
-    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 = {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(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-hex-int/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 0xfffffffd, msg)
-    # . . push args
-    68/push  "F - test-parse-hex-int-negative"/imm32
-    68/push  0xfffffffd/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
-    # . epilog
-    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 -> EAX : boolean
-    # . prolog
-    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'
-    b8/copy-to-EAX  0/imm32/false
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x30/imm32        # compare ECX
-    7c/jump-if-lesser  $is-hex-digit?:end/disp8
-    # return false if c > 'f'
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x66/imm32        # compare ECX
-    7f/jump-if-greater  $is-hex-digit?:end/disp8
-    # return true if c <= '9'
-    b8/copy-to-EAX  1/imm32/true
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x39/imm32        # compare ECX
-    7e/jump-if-lesser-or-equal  $is-hex-digit?:end/disp8
-    # return true if c >= 'a'
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x61/imm32        # compare ECX
-    7d/jump-if-greater-or-equal  $is-hex-digit?:end/disp8
-    # otherwise return false
-    b8/copy-to-EAX  0/imm32/false
-$is-hex-digit?:end:
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    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-lesser  $from-hex-char:abort/disp8
-$from-hex-char:check1:
-    # if (EAX > 'f') goto abort
-    3d/compare-EAX-with  0x66/imm32/f
-    7f/jump-if-greater  $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-greater  $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-lesser  $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:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "invalid hex char: "/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . clear-stream(Stderr+4)
-    # . . save EAX
-    50/push-EAX
-    # . . push args
-    b8/copy-to-EAX  Stderr/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . . restore EAX
-    58/pop-to-EAX
-    # . print-int32-buffered(Stderr, EAX)
-    # . . push args
-    50/push-EAX
-    68/push  Stderr/imm32
-    # . . call
-    e8/call  print-int32-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . flush(Stderr)
-    # . . push args
-    68/push  Stderr/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . _write(2/stderr, "\n")
-    # . . push args
-    68/push  "\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/068error-byte.subx b/subx/068error-byte.subx
deleted file mode 100644
index 1f225d28..00000000
--- a/subx/068error-byte.subx
+++ /dev/null
@@ -1,91 +0,0 @@
-# Print an error message followed by the text representation of a byte. Then exit.
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-#? Entry:  # manual test
-#?     # . var ed/EAX : exit-descriptor
-#?     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-#?     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-#?     # . configure ed to really exit()
-#?     # . . ed->target = 0
-#?     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
-#?     # . error-byte(ed, Stdout, msg, 34)
-#?     68/push  0x34/imm32
-#?     68/push  "abc"/imm32
-#?     68/push  Stderr/imm32
-#?     50/push-EAX
-#?     e8/call  error-byte/disp32
-#?     # . syscall(exit, Num-test-failures)
-#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-#?     b8/copy-to-EAX  1/imm32/exit
-#?     cd/syscall  0x80/imm8
-
-# write(out, "Error: "+msg+": "+byte) then stop(ed, 1)
-error-byte:  # ed : (address exit-descriptor), out : (address buffered-file), msg : (address array byte), n : byte -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write-buffered(out, "Error: ")
-    # . . push args
-    68/push  "Error: "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-buffered(out, msg)
-    # . . 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)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-buffered(out, ": ")
-    # . . push args
-    68/push  ": "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # print-byte-buffered(out, byte)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  print-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-buffered(out, Newline)
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . flush(out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # stop(ed, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  stop/disp32
-    # should never get past this point
-$error-byte:dead-end:
-    # . epilog
-    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/subx/069allocate.subx b/subx/069allocate.subx
deleted file mode 100644
index 6521b43a..00000000
--- a/subx/069allocate.subx
+++ /dev/null
@@ -1,204 +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.
-
-== 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
-
-# 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:  # ad : (address allocation-descriptor), n : int -> address-or-null/EAX
-    # . prolog
-    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 = ad
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
-    # save ad->curr
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
-    # check if there's enough space
-    # . EDX = ad->curr + n
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDX
-    03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # add *(EBP+12) to EDX
-    3b/compare                      1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # compare EDX with *(ECX+4)
-    73/jump-if-greater-or-equal-signed  $allocate:abort/disp8
-$allocate:commit:
-    # update ad->curr
-    89/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy EDX to *ECX
-$allocate:end:
-    # . restore registers
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$allocate:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "allocate: failed to allocate\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-allocate-success:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ad/ECX : (address allocation-descriptor) = {11, 15}
-    68/push  0xf/imm32/limit
-    68/push  0xb/imm32/curr
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # EAX = allocate(ad, 3)
-    # . . push args
-    68/push  3/imm32
-    51/push-ECX
-    # . . call
-    e8/call  allocate/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(EAX, 11, msg)
-    # . . push args
-    68/push  "F - test-allocate-success: returns current pointer of allocation descriptor"/imm32
-    68/push  0xb/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-ints-equal(ad->curr, 14, msg)
-    # . . push args
-    68/push  "F - test-allocate-success: updates allocation descriptor"/imm32
-    68/push  0xe/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-_pending-test-allocate-failure:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ad/ECX : (address allocation-descriptor) = {11, 15}
-    68/push  0xf/imm32/limit
-    68/push  0xb/imm32/curr
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # EAX = allocate(ad, 6)
-    # . . push args
-    68/push  6/imm32
-    51/push-ECX
-    # . . call
-    e8/call  allocate/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-allocate-failure: 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
-    # no change to ad->curr
-    # . check-ints-equal(ad->curr, 11)
-    # . . push args
-    68/push  "F - test-allocate-failure: updates allocation descriptor"/imm32
-    68/push  0xb/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
-    # . epilog
-    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 : (address allocation-descriptor), n : int -> new-ad : (address allocation-descriptor)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    # EAX = allocate(ad, n)
-    # . . 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  allocate/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (EAX == 0) abort
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $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
-    # . epilog
-    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:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "allocate-region: failed to allocate\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/070new-stream.subx b/subx/070new-stream.subx
deleted file mode 100644
index b6934b1e..00000000
--- a/subx/070new-stream.subx
+++ /dev/null
@@ -1,118 +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 : (address allocation-descriptor), length : int, elemsize : int -> address/EAX
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    52/push-EDX
-    # n = elemsize * length + 12 (for read, write and length)
-    # . 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 EAX
-    # . if overflow abort
-    81          7/subop/compare     3/mod/direct    2/rm32/EDX    .           .             .           .           .               0/imm32           # compare EDX
-    75/jump-if-not-equal  $new-stream:abort/disp8
-    # . EDX = elemsize*length
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDX
-    # . EAX += 12
-    05/add-to-EAX  0xc/imm32
-    # allocate(ad, n)
-    # . . push args
-    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    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX->length = elemsize*length
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$new-stream:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "new-stream: size too large\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-new-stream:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var heap/ECX : (address allocation-descriptor) = {0, 0}
-    68/push  0/imm32/limit
-    68/push  0/imm32/curr
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # heap = new-segment(512)
-    # . . push args
-    51/push-ECX
-    68/push  0x200/imm32
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # var start/EDX = ad->curr
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
-    # EAX = new-stream(heap, 3, 2)
-    # . . push args
-    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    .           .             .           .           .               0xc/imm32         # add to ESP
-    # 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
-    # check-ints-equal(EAX->length, 6, msg)
-    # . . push args
-    68/push  "F - test-new-stream: sets length 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
-    # . epilog
-    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/subx/071read-line.subx b/subx/071read-line.subx
deleted file mode 100644
index 0add5568..00000000
--- a/subx/071read-line.subx
+++ /dev/null
@@ -1,391 +0,0 @@
-== 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
-
-# read bytes from 'f' until (and including) a newline and store them into 's'
-# 's' fails to grow if and only if no data found
-# just abort if 's' is too small
-read-line-buffered:  # f : (address buffered-file), s : (address stream byte) -> <void>
-    # pseudocode:
-    #   while true
-    #     if (s->write >= s->length) abort
-    #     if (f->read >= f->write) populate stream from file
-    #     if (f->write == 0) break
-    #     AL = f->data[f->read]
-    #     s->data[s->write] = AL
-    #     ++f->read
-    #     ++s->write
-    #     if (AL == '\n') break
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    56/push-ESI
-    57/push-EDI
-    # ESI = f
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # ECX = f->read
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(ESI+8) to ECX
-    # EDI = s
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
-    # EDX = s->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy *EDI to EDX
-$read-line-buffered:loop:
-    # if (s->write >= s->length) abort
-    3b/compare                      1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # compare EDX with *(EDI+8)
-    7d/jump-if-greater-or-equal  $read-line-buffered:abort/disp8
-    # if (f->read >= f->write) populate stream from file
-    3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # compare ECX with *(ESI+4)
-    7c/jump-if-lesser  $read-line-buffered:from-stream/disp8
-    # . clear-stream(stream = f+4)
-    # . . push args
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy ESI+4 to EAX
-    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
-    # . f->read must now be 0; update its cache at ECX
-    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-    # . EAX = read(f->fd, stream = f+4)
-    # . . push args
-    50/push-EAX
-    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
-    # . . call
-    e8/call  read/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (f->write == 0) break
-    # since f->read was initially 0, EAX is the same as f->write
-    # . if (EAX == 0) return true
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $read-line-buffered:end/disp8
-$read-line-buffered:from-stream:
-    # AL = 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    0x10/disp8      .                 # copy byte at *(ESI+ECX+16) to AL
-    # s->data[s->write] = AL
-    88/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/EDI  2/index/EDX   .           0/r32/AL    0xc/disp8       .                 # copy AL to *(EDI+EDX+12)
-    # ++f->read
-    41/increment-ECX
-    # ++s->write
-    42/increment-EDX
-    # if (AL == '\n') return
-    3d/compare-EAX-and  0xa/imm32
-    75/jump-if-not-equal  $read-line-buffered:loop/disp8
-$read-line-buffered:end:
-    # save f->read
-    89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy ECX to *(ESI+8)
-    # save s->write
-    89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy EDX to *EDI
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$read-line-buffered:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "read-line-buffered: line too long\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-read-line-buffered:
-    # - check that read-line-buffered stops at a newline
-    # 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-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\ncd")
-    # . . push args
-    68/push  "ab\ncd"/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
-    # read a line from _test-stream (buffered by _test-buffered-file) into _test-tmp-stream
-    # . EAX = read-line-buffered(_test-buffered-file, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  read-line-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-next-stream-line-equal(_test-tmp-stream, "ab", msg)
-    # . . push args
-    68/push  "F - test-read-line-buffered"/imm32
-    68/push  "ab"/imm32
-    68/push  _test-tmp-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # end
-    c3/return
-
-test-read-line-buffered-reads-final-line-until-Eof:
-    # 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-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, "cd")
-    # . . push args
-    68/push  "cd"/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
-    # read a line from _test-stream (buffered by _test-buffered-file) into _test-tmp-stream
-    # . EAX = read-line-buffered(_test-buffered-file, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  read-line-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-stream-equal(_test-tmp-stream, "cd", msg)
-    # . . push args
-    68/push  "F - test-read-line-buffered-reads-final-line-until-Eof"/imm32
-    68/push  "cd"/imm32
-    68/push  _test-tmp-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
-
-# read bytes from 'f' until (and including) a newline and store them into 's'
-# 's' fails to grow if and only if no data found
-# just abort if 's' is too small
-read-line:  # f : (address stream), s : (address stream byte) -> <void>
-    # pseudocode:
-    #   while true
-    #     if (s->write >= s->length) abort
-    #     if (f->read >= f->write) break
-    #     AL = f->data[f->read]
-    #     s->data[s->write] = AL
-    #     ++f->read
-    #     ++s->write
-    #     if (AL == '\n') break
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    56/push-ESI
-    57/push-EDI
-    # ESI = f
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # ECX = 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
-    # EDX = s->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy *EDI to EDX
-$read-line:loop:
-    # if (s->write >= s->length) abort
-    3b/compare                      1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # compare EDX with *(EDI+8)
-    0f 8d/jump-if-greater-or-equal  $read-line:abort/disp32
-    # if (f->read >= f->write) break
-    3b/compare                      0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ECX with *ESI
-    7d/jump-if-greater-or-equal  $read-line:end/disp8
-    # AL = 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
-    # s->data[s->write] = AL
-    88/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/EDI  2/index/EDX   .           0/r32/AL    0xc/disp8       .                 # copy AL to *(EDI+EDX+12)
-    # ++f->read
-    41/increment-ECX
-    # ++s->write
-    42/increment-EDX
-    # if (AL == '\n') return
-    3d/compare-EAX-and  0xa/imm32
-    0f 85/jump-if-not-equal  $read-line:loop/disp32
-$read-line:end:
-    # save f->read
-    89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy ECX to *(ESI+4)
-    # save s->write
-    89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy EDX to *EDI
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$read-line:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "read-line: line too long\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-read-line:
-    # - check that read-line stops at a newline
-    # 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-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-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\ncd")
-    # . . push args
-    68/push  "ab\ncd"/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
-    # read a line from _test-stream into _test-tmp-stream
-    # . EAX = read-line(_test-stream, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  read-line/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-next-stream-line-equal(_test-tmp-stream, "ab", msg)
-    # . . push args
-    68/push  "F - test-read-line"/imm32
-    68/push  "ab"/imm32
-    68/push  _test-tmp-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # end
-    c3/return
-
-test-read-line-reads-final-line-until-Eof:
-    # 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-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-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, "cd")
-    # . . push args
-    68/push  "cd"/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
-    # read a line from _test-stream into _test-tmp-stream
-    # . EAX = read-line(_test-stream, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  read-line/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-stream-equal(_test-tmp-stream, "cd", msg)
-    # . . push args
-    68/push  "F - test-read-line-reads-final-line-until-Eof"/imm32
-    68/push  "cd"/imm32
-    68/push  _test-tmp-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/subx/072slice.subx b/subx/072slice.subx
deleted file mode 100644
index e568fbae..00000000
--- a/subx/072slice.subx
+++ /dev/null
@@ -1,1173 +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 : (address slice) -> EAX : boolean
-    # . prolog
-    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
-    # . compare EAX and s->end
-    39/compare                      1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # compare EAX and *(ECX+4)
-    b8/copy-to-EAX  1/imm32/true
-    74/jump-if-equal  $slice-empty?:end/disp8
-    b8/copy-to-EAX  0/imm32/false
-$slice-empty?:end:
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    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:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var slice/ECX = {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
-    # . epilog
-    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:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var slice/ECX = {34, 23}
-    68/push  23/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, 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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-slice-equal?:  # s : (address slice), p : (address string) -> EAX : boolean
-    # pseudocode:
-    #   if (p == 0) return (s == 0)
-    #   currs = s->start
-    #   maxs = s->end
-    #   if (maxs - currs != p->length) 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
-    #
-    # . prolog
-    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
-    # currs/EDX = s->start
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    # maxs/ESI = s->end
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           6/r32/ESI   4/disp8         .                 # copy *(ESI+4) to ESI
-    # EAX = 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-not-equal  $slice-equal?:nonnull-string/disp8
-$slice-equal?:null-string:
-    # return s->start == s->end
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $slice-equal?:true/disp8
-    eb/jump  $slice-equal?:false/disp8
-$slice-equal?:nonnull-string:
-    # if (EAX != p->length) return false
-    39/compare                      0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # compare *EBX and EAX
-    75/jump-if-not-equal  $slice-equal?:false/disp8
-    # currp/EBX = p->data
-    81          0/subop/add         3/mod/direct    3/rm32/EBX    .           .             .           .           .               4/imm32           # add to EBX
-    # EAX = ECX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    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-greater-or-equal-unsigned  $slice-equal?:true/disp8
-    # AL = *currp
-    8a/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at *EBX to AL
-    # CL = *currs
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           1/r32/CL    .               .                 # copy byte at *EDX 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-not-equal  $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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var slice/ECX
-    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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var slice/ECX
-    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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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 : (address slice), head : (address string) -> EAX : boolean
-    # pseudocode
-    #   lenh = head->length
-    #   if (lenh > s->end - s->start) return false
-    #   i = 0
-    #   currs = s->start
-    #   currp = head->data
-    #   while i < lenh
-    #     if (*currs != *currh) return false
-    #     ++i
-    #     ++currs
-    #     ++currh
-    #   return true
-    #
-    # registers:
-    #   currs: ESI
-    #   currh: EDI
-    #   *currs: EAX
-    #   *currh: EBX
-    #   i: ECX
-    #   lenh: EDX
-    #
-    # . prolog
-    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
-    # ECX = 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
-    # lenh/EDX = head->length
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy *EDI to EDX
-    # if (lenh > s->end - s->start) return false
-    39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # compare EDX with ECX
-    7f/jump-if-greater  $slice-starts-with?:false/disp8
-    # currs/ESI = s->start
-    8b/subtract                     0/mod/indirect  6/rm32/ESI    .           .             .           6/r32/ESI   .               .                 # copy *ESI to ESI
-    # currh/EDI = head->data
-    81          0/subop/add         3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm32           # add to EDI
-    # i/ECX = 0
-    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-    # EAX = EBX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-$slice-starts-with?:loop:
-    # if (i >= lenh) return true
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    7d/jump-if-greater-or-equal  $slice-starts-with?:true/disp8
-    # AL = *currs
-    8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/AL    .               .                 # copy byte at *ESI to AL
-    # BL = *currh
-    8a/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           3/r32/BL    .               .                 # copy byte at *EDI to BL
-    # if (*currs != *currh) return false
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # compare EAX and EBX
-    75/jump-if-not-equal  $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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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
-    # . prolog
-    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 = {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
-    # . epilog
-    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 : (address stream), s : (address slice)
-    # . prolog
-    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
-    # curr/ECX = s->start
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    # max/ESI = 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->length
-    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-greater-or-equal-unsigned  $write-slice:loop-end/disp8
-    # if (out->write >= out->length) abort
-    39/compare                      3/mod/direct    3/rm32/EBX    .           .             .           2/r32/EDX   .               .                 # compare EBX with EDX
-    7d/jump-if-greater-or-equal  $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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$write-slice:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "write-slice: out of space"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-write-slice:
-    # . prolog
-    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 = {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
-    # . epilog
-    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 buffered-file
-write-slice-buffered:  # out : (address buffered-file), s : (address slice)
-    # . prolog
-    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
-    # curr/ECX = s->start
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    # max/ESI = 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->length
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EDI+12) to EDX
-    # EBX = out->write
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           3/r32/EBX   4/disp8         .                 # copy *(EDI+4) to EBX
-$write-slice-buffered:loop:
-    # if (curr >= max) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           6/r32/ESI   .               .                 # compare ECX with ESI
-    73/jump-if-greater-or-equal-unsigned  $write-slice-buffered:loop-end/disp8
-    # if (out->write >= out->length) flush and clear out's stream
-    39/compare                      3/mod/direct    3/rm32/EBX    .           .             .           2/r32/EDX   .               .                 # compare EBX with EDX
-    7c/jump-if-lesser  $write-slice-buffered:to-stream/disp8
-    # . persist out->write
-    89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           3/r32/EBX   4/disp8         .                 # copy EBX to *(EDI+4)
-    # . flush(out)
-    # . . push args
-    57/push-EDI
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(stream = out+4)
-    # . . push args
-    8d/copy-address                 1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EDI+4 to EAX
-    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
-    # . out->write must now be 0; update its cache at EBX
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-$write-slice-buffered:to-stream:
-    # 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    0x10/disp8      .                 # copy AL to *(EDI+EBX+16)
-    # ++out->write
-    43/increment-EBX
-    # ++in
-    41/increment-ECX
-    eb/jump  $write-slice-buffered:loop/disp8
-$write-slice-buffered:loop-end:
-    # persist necessary variables from registers
-    89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           3/r32/EBX   4/disp8         .                 # copy EBX to *(EDI+4)
-$write-slice-buffered: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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-write-slice-buffered:
-    # . prolog
-    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
-    # . clear-stream(_test-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (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 = {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-buffered(_test-buffered-file, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-stream, "Abc", msg)
-    # . . push args
-    68/push  "F - test-write-slice-buffered"/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
-    # . epilog
-    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 : (address allocation-descriptor), in : (address slice) -> out/EAX : (address array)
-    # . prolog
-    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 = in
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # curr/EDX = in->start
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    # max/EBX = in->end
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           3/r32/EBX   4/disp8         .                 # copy *(ESI+4) to EBX
-    # size/ECX = max - curr + 4  # total size of output string (including the initial length)
-    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
-    # out/EAX = allocate(ad, size)
-    # . . push args
-    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    .           .             .           .           .               8/imm32           # add to ESP
-    # if (EAX == 0) abort
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $slice-to-string:abort/disp8
-    # *out = 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
-    # 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
-    # . epilog
-    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:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "slice-to-string: out of space\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-slice-to-string:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var heap/EDX : (address allocation-descriptor) = {0, 0}
-    68/push  0/imm32/limit
-    68/push  0/imm32/curr
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # heap = new-segment(512)
-    # . . push args
-    52/push-EDX
-    68/push  0x200/imm32
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/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 = {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-to-string(heap, slice)
-    # . . push args
-    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    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . 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
-    # . epilog
-    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/subx/073next-token.subx b/subx/073next-token.subx
deleted file mode 100644
index 942d9878..00000000
--- a/subx/073next-token.subx
+++ /dev/null
@@ -1,897 +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:  # in : (address stream), delimiter : byte, out : (address slice)
-    # . prolog
-    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 = in
-    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   0x10/disp8      .                 # copy *(EBP+16) to EDI
-    # skip-chars-matching(in, delimiter)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    56/push-ESI
-    # . . call
-    e8/call  skip-chars-matching/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # out->start = &in->data[in->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(in, delimiter)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    56/push-ESI
-    # . . 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
-    # out->end = &in->data[in->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)
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-token:
-    # . prolog
-    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 = {0, 0}
-    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-token(_test-stream, 0x20/space, slice)
-    # . . push args
-    51/push-ECX
-    68/push  0x20/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-token/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/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-token: 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-token: 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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-token-Eof:
-    # . prolog
-    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 = {0, 0}
-    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-token(_test-stream, 0x20/space, slice)
-    # . . push args
-    51/push-ECX
-    68/push  0x20/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-token/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(slice->end, slice->start, msg)
-    # . . push args
-    68/push  "F - test-next-token-Eof"/imm32
-    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  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# 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 : (address byte), end : (address byte), delimiter : byte, out : (address slice) -> <void>
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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 : (address slice) = {0, 0}
-    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
-    # . epilog
-    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:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var out/EDI : (address slice) = {0, 0}
-    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
-    # . epilog
-    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:
-    # . prolog
-    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 : (address slice) = {0, 0}
-    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
-    # . epilog
-    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 : (address stream), delimiter : byte
-    # . prolog
-    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-greater-or-equal  $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-not-equal  $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
-    # . epilog
-    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
-
-# minor fork of 'skip-chars-matching'
-skip-chars-not-matching:  # in : (address stream), delimiter : byte
-    # . prolog
-    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-greater-or-equal  $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-equal  $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
-    # . epilog
-    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 : (address stream)
-    # . prolog
-    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-greater-or-equal  $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-equal  $skip-chars-not-matching-whitespace:end/disp8
-    # if (EAX == '\n') break
-    3d/compare-EAX-and  0x0a/imm32/newline
-    74/jump-if-equal  $skip-chars-not-matching-whitespace:end/disp8
-    # if (EAX == '\t') break
-    3d/compare-EAX-and  0x09/imm32/tab
-    74/jump-if-equal  $skip-chars-not-matching-whitespace:end/disp8
-    # if (EAX == '\r') break
-    3d/compare-EAX-and  0x0d/imm32/cr
-    74/jump-if-equal  $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
-    # . epilog
-    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-slice:  # curr : (address byte), end : (address byte), delimiter : byte -> curr/EAX
-    # . prolog
-    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
-    # EBX = 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-greater-or-equal-unsigned  $skip-chars-matching-in-slice:end/disp8
-    # if (*curr != delimiter) break
-    8a/copy-byte                    0/mod/indirect  0/rm32/EAX    .           .             .           3/r32/BL    .               .                 # copy byte at *EAX to BL
-    39/compare                      3/mod/direct    3/rm32/EBX    .           .             .           2/r32/EDX   .               .                 # compare EBX and EDX
-    75/jump-if-not-equal  $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
-    # . epilog
-    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
-
-# minor fork of 'skip-chars-matching-in-slice'
-skip-chars-not-matching-in-slice:  # curr : (address byte), end : (address byte), delimiter : byte -> curr/EAX
-    # . prolog
-    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
-    # EBX = 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-greater-or-equal-unsigned  $skip-chars-not-matching-in-slice:end/disp8
-    # if (*curr == delimiter) break
-    8a/copy-byte                    0/mod/indirect  0/rm32/EAX    .           .             .           3/r32/BL    .               .                 # copy byte at *EAX to BL
-    39/compare                      3/mod/direct    3/rm32/EBX    .           .             .           2/r32/EDX   .               .                 # compare EBX and EDX
-    74/jump-if-equal  $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
-    # . epilog
-    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
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/074print-int-decimal.subx b/subx/074print-int-decimal.subx
deleted file mode 100644
index f6ea490f..00000000
--- a/subx/074print-int-decimal.subx
+++ /dev/null
@@ -1,307 +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
-
-print-int32-decimal:  # out : (address stream), n : int32
-    # 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->length]
-    #   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)
-    #
-    # . prolog
-    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
-    # ten/ECX = 10
-    b9/copy-to-ECX  0xa/imm32
-    # push sentinel
-    68/push  0/imm32/sentinel
-    # EAX = 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-greater-or-equal  $print-int32-decimal:read-loop/disp8
-$print-int32-decimal:negative:
-    f7          3/subop/negate      3/mod/direct    0/rm32/EAX    .           .             .           .           .               .                 # negate EAX
-$print-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-greater  $print-int32-decimal:read-loop/disp8
-$print-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-greater-or-equal  $print-int32-decimal:write/disp8
-$print-int32-decimal:push-negative:
-    68/push  0x2d/imm32/-
-$print-int32-decimal:write:
-    # EDI = out
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-    # w/EDX = out->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy *EDI to EDX
-    # curr/ECX = &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
-    # max/EBX = &out->data[out->length]
-    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
-$print-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-equal  $print-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-greater-or-equal-unsigned  $print-int32-decimal:abort/disp8
-$print-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  $print-int32-decimal:write-loop/disp8
-$print-int32-decimal:write-break:
-    # out->write = w
-    89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy EDX to *EDI
-$print-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$print-int32-decimal:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "print-int32-decimal: out of space\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-print-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
-    # print-int32-decimal(_test-stream, 9)
-    # . . push args
-    68/push  9/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  print-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-print-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-print-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
-    # print-int32-decimal(_test-stream, 0)
-    # . . push args
-    68/push  0/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  print-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-print-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-print-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
-    # print-int32-decimal(_test-stream, 10)
-    # . . push args
-    68/push  0xa/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  print-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-print-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-print-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
-    # print-int32-decimal(_test-stream, -9)
-    # . . push args
-    68/push  -9/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  print-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-print-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-print-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
-    # print-int32-decimal(_test-stream, -10)
-    # . . push args
-    68/push  -0xa/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  print-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-print-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
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/075array-equal.subx b/subx/075array-equal.subx
deleted file mode 100644
index 60694829..00000000
--- a/subx/075array-equal.subx
+++ /dev/null
@@ -1,629 +0,0 @@
-# Comparing arrays 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
-
-Entry:
-    # initialize heap
-    # . Heap = new-segment(64KB)
-    # . . push args
-    68/push  Heap/imm32
-    68/push  0x10000/imm32/64KB
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-$array-equal-main:end:
-    # syscall(exit, Num-test-failures)
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-array-equal?:  # a : (address array int), b : (address array int) -> EAX : boolean
-    # pseudocode:
-    #   lena = a->length
-    #   if (lena != b->length) return false
-    #   i = 0
-    #   curra = a->data
-    #   currb = b->data
-    #   while i < lena
-    #     i1 = *curra
-    #     i2 = *currb
-    #     if (c1 != c2) return false
-    #     i+=4, curra+=4, currb+=4
-    #   return true
-    #
-    # registers:
-    #   i: ECX
-    #   lena: EDX
-    #   curra: ESI
-    #   currb: EDI
-    #   i1: EAX
-    #   i2: EBX
-    #
-    # . prolog
-    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 = a
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # EDI = b
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
-    # lena/EDX = a->length
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-$array-equal?:lengths:
-    # if (lena != b->length) return false
-    39/compare                      0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # compare *EDI and EDX
-    75/jump-if-not-equal  $array-equal?:false/disp8
-    # curra/ESI = a->data
-    81          0/subop/add         3/mod/direct    6/rm32/ESI    .           .             .           .           .               4/imm32           # add to ESI
-    # currb/EDI = b->data
-    81          0/subop/add         3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm32           # add to EDI
-    # i/ECX = i1/EAX = i2/EBX = 0
-    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-$array-equal?:loop:
-    # if (i >= lena) return true
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    7d/jump-if-greater-or-equal  $array-equal?:true/disp8
-    # i1 = *curra
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
-    # i2 = *currb
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           3/r32/EBX   .               .                 # copy *EDI to EBX
-    # if (i1 != i2) return false
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # compare EAX and EBX
-    75/jump-if-not-equal  $array-equal?:false/disp8
-    # i += 4
-    81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm32           # add to ECX
-    # currs += 4
-    81          0/subop/add         3/mod/direct    6/rm32/ESI    .           .             .           .           .               4/imm32           # add to ESI
-    # currb += 4
-    81          0/subop/add         3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm32           # add to EDI
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-compare-empty-with-empty-array:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ECX = []
-    68/push  0/imm32/size
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var EDX = []
-    68/push  0/imm32/size
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # EAX = array-equal?(ECX, EDX)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  array-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-array"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-compare-empty-with-non-empty-array:  # also checks length-mismatch code path
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ECX = [1]
-    68/push  1/imm32
-    68/push  4/imm32/size
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var EDX = []
-    68/push  0/imm32/size
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # EAX = array-equal?(ECX, EDX)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  array-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-array"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-compare-equal-arrays:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ECX = [1, 2, 3]
-    68/push  3/imm32
-    68/push  2/imm32
-    68/push  1/imm32
-    68/push  0xc/imm32/size
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var EDX = [1, 2, 3]
-    68/push  3/imm32
-    68/push  2/imm32
-    68/push  1/imm32
-    68/push  0xc/imm32/size
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # EAX = array-equal?(ECX, EDX)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  array-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-arrays"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-compare-inequal-arrays-equal-lengths:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ECX = [1, 4, 3]
-    68/push  3/imm32
-    68/push  4/imm32
-    68/push  1/imm32
-    68/push  0xc/imm32/size
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var EDX = [1, 2, 3]
-    68/push  3/imm32
-    68/push  2/imm32
-    68/push  1/imm32
-    68/push  0xc/imm32/size
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # EAX = array-equal?(ECX, EDX)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  array-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-arrays-equal-lengths"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-parse-array-of-ints:  # ad : (address allocation-descriptor), s : (address string) -> result/EAX : (address array int)
-    # pseudocode
-    #   end = s->data + s->length
-    #   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
-    #   result = allocate(ad, (size+1)*4)
-    #   result->size = (size+1)*4
-    #   var slice = {s->data, 0}
-    #   out = result->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, ' ')
-    #     *out = parse-hex-int(slice)
-    #     out += 4
-    #     slice->start = slice->end
-    #   return result
-    #
-    # . prolog
-    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   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # curr/ECX = s->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy ESI+4 to ECX
-    # end/EDX = s->data + s->length
-    # . EDX = s->length
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    # . EDX += curr
-    01/add                          3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # add ECX to EDX
-    # size/EBX = 0
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-$parse-array-of-ints:loop1:
-    # if (curr >= end) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $parse-array-of-ints:break1/disp8
-    # curr = skip-chars-matching-in-slice(curr, end, ' ')
-    # . EAX = skip-chars-matching-in-slice(curr, end, ' ')
-    # . . push args
-    68/push  0x20/imm32/space
-    52/push-EDX
-    51/push-ECX
-    # . . 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
-    # . ECX = EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
-    # if (curr >= end) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $parse-array-of-ints:break1/disp8
-    # curr = skip-chars-not-matching-in-slice(curr, end, ' ')
-    # . EAX = skip-chars-not-matching-in-slice(curr, end, ' ')
-    # . . push args
-    68/push  0x20/imm32/space
-    52/push-EDX
-    51/push-ECX
-    # . . 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
-    # . ECX = EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
-    # size += 4
-    81          0/subop/add         3/mod/direct    3/rm32/EBX    .           .             .           .           .               4/imm32           # add to EBX
-    eb/jump  $parse-array-of-ints:loop1/disp8
-$parse-array-of-ints:break1:
-    # result/EDI = allocate(ad, size+4)
-    # . EAX = allocate(ad, size+4)
-    # . . push args
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # copy EBX to EAX
-    05/add-to-EAX  4/imm32
-    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    .           .             .           .           .               8/imm32           # add to ESP
-    # . EDI = EAX
-    89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDI
-    # result->size = size
-    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # copy EBX to *EAX
-$parse-array-of-ints:pass2:
-    # var slice/ECX = {s->data, 0}
-    # . push 0
-    68/push  0/imm32/end
-    # . push s->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy ESI+4 to ECX
-    51/push-ECX
-    # . bookmark
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # out/EBX = result->data
-    8d/copy-address                 1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   4/disp8         .                 # copy EAX+4 to EBX
-$parse-array-of-ints:loop2:
-    # if (slice->start >= end) break
-    39/compare                      0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare *ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $parse-array-of-ints:end/disp8
-    # slice->start = skip-chars-matching-in-slice(slice->start, end, ' ')
-    # . EAX = skip-chars-matching-in-slice(slice->start, end, ' ')
-    # . . push args
-    68/push  0x20/imm32/space
-    52/push-EDX
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
-    # . . 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
-    # . slice->start = EAX
-    89/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *ECX
-    # if (slice->start >= end) break
-    39/compare                      0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare *ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $parse-array-of-ints:end/disp8
-    # slice->end = skip-chars-not-matching-in-slice(slice->start, end, ' ')
-    # . EAX = skip-chars-not-matching-in-slice(curr, end, ' ')
-    # . . push args
-    68/push  0x20/imm32/space
-    52/push-EDX
-    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
-    # . slice->end = EAX
-    89/copy                         1/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(ECX+4)
-    # *out = parse-hex-int(slice)
-    # . EAX = parse-hex-int(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-hex-int/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # *out = EAX
-    89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EBX
-    # out += 4
-    81          0/subop/add         3/mod/direct    3/rm32/EBX    .           .             .           .           .               4/imm32           # add to EBX
-    # slice->start = slice->end
-    8b/copy                         1/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
-    89/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *ECX
-    81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm32           # add to ECX
-    eb/jump  $parse-array-of-ints:loop2/disp8
-$parse-array-of-ints:end:
-    # return EDI
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           7/r32/EDI   .               .                 # copy EDI to EAX
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-parse-array-of-ints:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ECX = [1, 2, 3]
-    68/push  3/imm32
-    68/push  2/imm32
-    68/push  1/imm32
-    68/push  0xc/imm32/size
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # EAX = parse-array-of-ints(Heap, "1 2 3")
-    # . . push args
-    68/push  "1 2 3"/imm32
-    68/push  Heap/imm32
-    # . . call
-    e8/call  parse-array-of-ints/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = array-equal?(ECX, EAX)
-    # . . push args
-    50/push-EAX
-    51/push-ECX
-    # . . call
-    e8/call  array-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-parse-array-of-ints"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-parse-array-of-ints-empty:
-    # - empty string = empty array
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # EAX = parse-array-of-ints(Heap, "")
-    # . . push args
-    68/push  ""/imm32
-    68/push  Heap/imm32
-    # . . call
-    e8/call  parse-array-of-ints/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-parse-array-of-ints-empty"/imm32
-    68/push  0/imm32/size
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-parse-array-of-ints-just-whitespace:
-    # - just whitespace = empty array
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # EAX = parse-array-of-ints(Heap, " ")
-    # . . push args
-    68/push  " "/imm32
-    68/push  Heap/imm32
-    # . . call
-    e8/call  parse-array-of-ints/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-parse-array-of-ints-empty"/imm32
-    68/push  0/imm32/size
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-parse-array-of-ints-extra-whitespace:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ECX = [1, 2, 3]
-    68/push  3/imm32
-    68/push  2/imm32
-    68/push  1/imm32
-    68/push  0xc/imm32/size
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # EAX = parse-array-of-ints(Heap, " 1 2  3  ")
-    # . . push args
-    68/push  " 1 2  3  "/imm32
-    68/push  Heap/imm32
-    # . . call
-    e8/call  parse-array-of-ints/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = array-equal?(ECX, EAX)
-    # . . push args
-    50/push-EAX
-    51/push-ECX
-    # . . call
-    e8/call  array-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-parse-array-of-ints-extra-whitespace"/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
-    # . epilog
-    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
-# compare an array with a string representation of an array literal
-check-array-equal:  # a : (address array int), expected : (address string), msg : (address string)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    # var b/ECX = parse-array-of-ints(Heap, expected)
-    # . EAX = parse-array-of-ints(Heap, expected)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    68/push  Heap/imm32
-    # . . call
-    e8/call  parse-array-of-ints/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . b = EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
-    # EAX = array-equal?(a, b)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  array-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-array-equal:end:
-    # . restore registers
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-check-array-equal:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ECX = [1, 2, 3]
-    68/push  3/imm32
-    68/push  2/imm32
-    68/push  1/imm32
-    68/push  0xc/imm32/size
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # check-array-equal(ECX, "1 2 3", "msg")
-    # . . push args
-    68/push  "F - test-check-array-equal"/imm32
-    68/push  "1 2 3"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  check-array-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data
-
-Heap:
-  # curr
-  0/imm32
-  # limit
-  0/imm32
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/076zero-out.subx b/subx/076zero-out.subx
deleted file mode 100644
index bc19dc21..00000000
--- a/subx/076zero-out.subx
+++ /dev/null
@@ -1,84 +0,0 @@
-# Fill a region of memory with zeroes.
-
-== 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
-
-zero-out:  # start : address, len : int
-    # pseudocode:
-    #   curr/ESI = start
-    #   i/ECX = 0
-    #   while true
-    #     if (i >= len) break
-    #     *curr = 0
-    #     ++curr
-    #     ++i
-    #
-    # . prolog
-    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
-    # i/ECX = 0
-    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-    # EDX = len
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
-$zero-out:loop:
-    # if (i >= len) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    7d/jump-if-greater-or-equal  $zero-out:end/disp8
-    # *curr = 0
-    c6          0/subop/copy        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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-zero-out:
-    # . prolog
-    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/len
-    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
-    # . epilog
-    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/subx/077slurp.subx b/subx/077slurp.subx
deleted file mode 100644
index 2bf203da..00000000
--- a/subx/077slurp.subx
+++ /dev/null
@@ -1,161 +0,0 @@
-== 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
-
-# read all bytes from 'f' and store them into 's'
-# abort if 's' is too small
-slurp:  # f : (address buffered-file), s : (address stream byte) -> <void>
-    # pseudocode:
-    #   while true
-    #     if (s->write >= s->length) abort
-    #     if (f->read >= f->write) populate stream from file
-    #     if (f->write == 0) break
-    #     AL = f->data[f->read]
-    #     s->data[s->write] = AL
-    #     ++f->read
-    #     ++s->write
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    56/push-ESI
-    57/push-EDI
-    # ESI = f
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # ECX = f->read
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(ESI+8) to ECX
-    # EDI = s
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
-    # EDX = s->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy *EDI to EDX
-$slurp:loop:
-    # if (s->write >= s->length) abort
-    3b/compare                      1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # compare EDX with *(EDI+8)
-    7d/jump-if-greater-or-equal  $slurp:abort/disp8
-    # if (f->read >= f->write) populate stream from file
-    3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # compare ECX with *(ESI+4)
-    7c/jump-if-lesser  $slurp:from-stream/disp8
-    # . clear-stream(stream = f+4)
-    # . . push args
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy ESI+4 to EAX
-    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
-    # . f->read must now be 0; update its cache at ECX
-    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-    # . EAX = read(f->fd, stream = f+4)
-    # . . push args
-    50/push-EAX
-    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
-    # . . call
-    e8/call  read/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (f->write == 0) break
-    # since f->read was initially 0, EAX is the same as f->write
-    # . if (EAX == 0) return true
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $slurp:end/disp8
-$slurp:from-stream:
-    # AL = 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    0x10/disp8      .                 # copy byte at *(ESI+ECX+16) to AL
-    # s->data[s->write] = AL
-    88/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/EDI  2/index/EDX   .           0/r32/AL    0xc/disp8       .                 # copy AL to *(EDI+EDX+12)
-    # ++f->read
-    41/increment-ECX
-    # ++s->write
-    42/increment-EDX
-    eb/jump  $slurp:loop/disp8
-$slurp:end:
-    # save f->read
-    89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy ECX to *(ESI+8)
-    # save s->write
-    89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy EDX to *EDI
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$slurp:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "slurp: destination too small\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-slurp:
-    # 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-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\ncd")
-    # . . push args
-    68/push  "ab\ncd"/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
-    # read a line from _test-stream (buffered by _test-buffered-file) into _test-tmp-stream
-    # . EAX = slurp(_test-buffered-file, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  slurp/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-stream-equal(_test-tmp-stream, "ab\ncd", msg)
-    # . . push args
-    68/push  "F - test-slurp"/imm32
-    68/push  "ab\ncd"/imm32
-    68/push  _test-tmp-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/subx/100index b/subx/100index
deleted file mode 100644
index 355b43d3..00000000
--- a/subx/100index
+++ /dev/null
@@ -1,11 +0,0 @@
--- Overview of layers
-0-9: infrastructure independent of this program
-10-29: level 1, the SubX subset of the 32-bit x86 ISA
-30-39: level 2, moving past counting bytes with operands and labels
-
-See layer 29 for the description of 'level'.
-
---- Overview of tracing depths
-0: errors
-1-98: app level traces for different SubX programs
-99: low-level details of the VM
diff --git a/subx/Readme.md b/subx/Readme.md
deleted file mode 100644
index b6f3e291..00000000
--- a/subx/Readme.md
+++ /dev/null
@@ -1,777 +0,0 @@
-## SubX: A minimalist assembly language for a subset of the x86 ISA
-
-SubX is a simple, minimalist stack for programming your computer.
-
-  ```sh
-  $ git clone https://github.com/akkartik/mu
-  $ cd mu/subx
-  $ ./subx  # print out a help message
-  ```
-
-SubX is designed:
-
-* to enable automation of arbitrary manual tests
-* to be easy to implement in itself, and
-* to help learn and teach the x86 instruction set.
-
-It requires a Unix-like environment with a C++ compiler (Linux or BSD or Mac
-OS). Running `subx` will transparently compile it as necessary.
-
-[![Build Status](https://api.travis-ci.org/akkartik/mu.svg?branch=master)](https://travis-ci.org/akkartik/mu)
-
-You can generate native ELF binaries with it that run on a bare Linux
-kernel. No other dependencies needed.
-
-  ```sh
-  $ ./subx translate examples/ex1.subx -o examples/ex1
-  $ ./examples/ex1  # only on Linux
-  $ echo $?
-  42
- ```
-
-You can run the generated binaries on an interpreter/VM for better error
-messages.
-
-  ```sh
-  $ ./subx run examples/ex1  # on Linux or BSD or OS X
-  $ echo $?
-  42
-  ```
-
-Emulated runs generate a trace that permits [time-travel debugging](https://github.com/akkartik/mu/blob/master/browse_trace/Readme.md).
-
-  ```sh
-  $ ./subx --debug translate examples/factorial.subx -o examples/factorial
-  saving address->label information to 'labels'
-  saving address->source information to 'source_lines'
-
-  $ ./subx --debug --trace run examples/factorial
-  saving trace to 'last_run'
-
-  $ ../browse_trace/browse_trace last_run  # text-mode debugger UI
-  ```
-
-You can write tests for your assembly programs. The entire stack is thoroughly
-covered by automated tests. SubX's tagline: tests before syntax.
-
-  ```sh
-  $ ./subx test
-  $ ./subx run apps/factorial test
-  ```
-
-You can use SubX to translate itself. For example, running natively on Linux:
-
-  ```sh
-  # generate translator phases using the C++ translator
-  $ ./subx translate 0*.subx apps/subx-common.subx apps/hex.subx    -o hex
-  $ ./subx translate 0*.subx apps/subx-common.subx apps/survey.subx -o survey
-  $ ./subx translate 0*.subx apps/subx-common.subx apps/pack.subx   -o pack
-  $ ./subx translate 0*.subx apps/subx-common.subx apps/assort.subx -o assort
-  $ ./subx translate 0*.subx apps/subx-common.subx apps/dquotes.subx -o dquotes
-  $ ./subx translate 0*.subx apps/subx-common.subx apps/tests.subx  -o tests
-  $ chmod +x hex survey pack assort dquotes tests
-
-  # use the generated translator phases to translate SubX programs
-  $ cat examples/ex1.subx |./tests |./dquotes |./assort |./pack |./survey |./hex > a.elf
-  $ chmod +x a.elf
-  $ ./a.elf
-  $ echo $?
-  42
-
-  # or, automating the above steps
-  $ ./ntranslate ex1.subx
-  $ chmod +x a.elf
-  $ ./a.elf
-  $ echo $?
-  42
-  ```
-
-Or, running in a VM on other platforms:
-
-  ```
-  $ ./translate ex1.subx  # generates identical a.elf to above
-  $ ./subx run a.elf
-  $ echo $?
-  42
-  ```
-
-You can use it to learn about the x86 processor that (almost certainly) runs
-your computer. (See below.)
-
-You can read its tiny zero-dependency internals and understand how they work.
-You can hack on it, and its thorough tests will raise the alarm when you break
-something.
-
-Eventually you will be able to program in higher-level notations. But you'll
-always have tests as guardrails and traces for inspecting runs. The entire
-stack will always be designed for others to comprehend. You'll always be
-empowered to understand how things work, and change what doesn't work for you.
-You'll always be expected to make small changes during upgrades.
-
-## What it looks like
-
-Here is the first example we ran above, a program that just returns 42:
-
-  ```sh
-  bb/copy-to-EBX  0x2a/imm32  # 42 in hex
-  b8/copy-to-EAX  1/imm32/exit
-  cd/syscall  0x80/imm8
-  ```
-
-Every line contains at most one instruction. Instructions consist of words
-separated by whitespace. Words may be _opcodes_ (defining the operation being
-performed) or _arguments_ (specifying the data the operation acts on). Any
-word can have extra _metadata_ attached to it after `/`. Some metadata is
-required (like the `/imm32` and `/imm8` above), but unrecognized metadata is
-silently skipped so you can attach comments to words (like the instruction
-name `/copy-to-EAX` above, or the `/exit` operand).
-
-SubX doesn't provide much syntax (there aren't even the usual mnemonics for
-opcodes), but it _does_ provide error-checking. If you miss an operand or
-accidentally add an extra operand you'll get a nice error. SubX won't arbitrarily
-interpret bytes of data as instructions or vice versa.
-
-So much for syntax. What do all these numbers actually _mean_? SubX supports a
-small subset of the 32-bit x86 instruction set that likely runs on your
-computer. (Think of the name as short for "sub-x86".) Instructions operate on
-a few registers:
-
-* Six general-purpose 32-bit registers: EAX, EBX, ECX, EDX, ESI and EDI
-* Two additional 32-bit registers: ESP and EBP (I suggest you only use these to
-  manage the call stack.)
-* Four 1-bit _flag_ registers for conditional branching:
-  - zero/equal flag ZF
-  - sign flag SF
-  - overflow flag OF
-  - carry flag CF
-
-SubX programs consist of instructions like `89/copy`, `01/add`, `3d/compare`
-and `52/push-ECX` which modify these registers as well as a byte-addressable
-memory. For a complete list of supported instructions, run `subx help opcodes`.
-
-(SubX doesn't support floating-point registers yet. Intel processors support
-an 8-bit mode, 16-bit mode and 64-bit mode. SubX will never support them.
-There are other flags. SubX will never support them. There are also _many_
-more instructions that SubX will never support.)
-
-It's worth distinguishing between an instruction's _operands_ and its _arguments_.
-Arguments are provided directly in instructions. Operands are pieces of data
-in register or memory that are operated on by instructions. Intel processors
-determine operands from arguments in fairly complex ways.
-
-## Lengthy interlude: How x86 instructions compute operands
-
-The [Intel processor manual](http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf)
-is the final source of truth on the x86 instruction set, but it can be
-forbidding to make sense of, so here's a quick orientation. You will need
-familiarity with binary numbers, and maybe a few other things. Email [me](mailto:mu@akkartik.com)
-any time if something isn't clear. I love explaining this stuff for as long as
-it takes. The bad news is that it takes some getting used to. The good news is
-that internalizing the next 500 words will give you a significantly deeper
-understanding of your computer.
-
-Most instructions operate on an operand in register or memory ('reg/mem'), and
-a second operand in a register. The register operand is specified fairly
-directly using the 3-bit `/r32` argument:
-
-  - 0 means register `EAX`
-  - 1 means register `ECX`
-  - 2 means register `EDX`
-  - 3 means register `EBX`
-  - 4 means register `ESP`
-  - 5 means register `EBP`
-  - 6 means register `ESI`
-  - 7 means register `EDI`
-
-The reg/mem operand, however, gets complex. It can be specified by 1-7
-arguments, each ranging in size from 2 bits to 4 bytes.
-
-The key argument that's always present for reg/mem operands is `/mod`, the
-_addressing mode_. This is a 2-bit argument that can take 4 possible values,
-and it determines what other arguments are required, and how to interpret
-them.
-
-* If `/mod` is `3`: the operand is in the register described by the 3-bit
-  `/rm32` argument similarly to `/r32` above.
-
-* If `/mod` is `0`: the operand is in the address provided in the register
-  described by `/rm32`. That's `*rm32` in C syntax.
-
-* If `/mod` is `1`: the operand is in the address provided by adding the
-  register in `/rm32` with the (1-byte) displacement. That's `*(rm32 + /disp8)`
-  in C syntax.
-
-* If `/mod` is `2`: the operand is in the address provided by adding the
-  register in `/rm32` with the (4-byte) displacement. That's `*(/rm32 +
-  /disp32)` in C syntax.
-
-In the last three cases, one exception occurs when the `/rm32` argument
-contains `4`. Rather than encoding register `ESP`, it means the address is
-provided by three _whole new_ arguments (`/base`, `/index` and `/scale`) in a
-_totally_ different way:
-
-  ```
-  reg/mem = *(/base + /index * (2 ^ /scale))
-  ```
-
-(There are a couple more exceptions ☹; see [Table 2-2](modrm.pdf) and [Table 2-3](sib.pdf)
-of the Intel manual for the complete story.)
-
-Phew, that was a lot to take in. Some examples to work through as you reread
-and digest it:
-
-1. To read directly from the EAX register, `/mod` must be `3` (direct mode),
-   and `/rm32` must be `0`. There must be no `/base`, `/index` or `/scale`
-   arguments.
-
-1. To read from `*EAX` (in C syntax), `/mod` must be `0` (indirect mode), and
-   the `/rm32` argument must be `0`. There must be no `/base`, `/index` or
-   `/scale` arguments.
-
-1. To read from `*(EAX+4)`, `/mod` must be `1` (indirect + disp8 mode),
-   `/rm32` must be `0`, there must be no SIB byte, and there must be a single
-   displacement byte containing `4`.
-
-1. To read from `*(EAX+ECX+4)`, one approach would be to set `/mod` to `1` as
-   above, `/rm32` to `4` (SIB byte next), `/base` to `0`, `/index` to `1`
-   (ECX) and a single displacement byte to `4`. (What should the `scale` bits
-   be? Can you think of another approach?)
-
-1. To read from `*(EAX+ECX+1000)`, one approach would be:
-   - `/mod`: `2` (indirect + disp32)
-   - `/rm32`: `4` (`/base`, `/index` and `/scale` arguments required)
-   - `/base`: `0` (EAX)
-   - `/index`: `1` (ECX)
-   - `/disp32`: 4 bytes containing `1000`
-
-## Putting it all together
-
-Here's a more meaty example:
-
-<img alt='examples/ex3.subx' src='../html/subx/ex3.png'>
-
-This program sums the first 10 natural numbers. By convention I use horizontal
-tabstops to help read instructions, dots to help follow the long lines,
-comments before groups of instructions to describe their high-level purpose,
-and comments at the end of complex instructions to state the low-level
-operation they perform. Numbers are always in hexadecimal (base 16) and must
-start with a digit ('0'..'9'); use the '0x' prefix when a number starts with a
-letter ('a'..'f'). I tend to also include it as a reminder when numbers look
-like decimal numbers.
-
-Try running this example now:
-
-```sh
-$ ./subx translate examples/ex3.subx -o examples/ex3
-$ ./subx run examples/ex3
-$ echo $?
-55
-```
-
-If you're on Linux you can also run it natively:
-
-```sh
-$ ./examples/ex3
-$ echo $?
-55
-```
-
-Use it now to follow along for a more complete tour of SubX syntax.
-
-## The syntax of SubX programs
-
-SubX programs map to the same ELF binaries that a conventional Linux system
-uses. Linux ELF binaries consist of a series of _segments_. In particular, they
-distinguish between code and data. Correspondingly, SubX programs consist of a
-series of segments, each starting with a header line: `==` followed by a name
-and approximate starting address.
-
-All code must lie in a segment called 'code'. Execution begins at the start of
-the `code` segment by default.
-
-You can reuse segment names:
-
-```
-== code
-...A...
-
-== data
-...B...
-
-== code
-...C...
-```
-
-The `code` segment now contains the instructions of `A` as well as `C`.
-
-Within the `code` segment, each line contains a comment, label or instruction.
-Comments start with a `#` and are ignored. Labels should always be the first
-word on a line, and they end with a `:`.
-
-Instruction arguments must specify their type, from:
-  - `/mod`
-  - `/rm32`
-  - `/r32`
-  - `/subop` (sometimes the `/r32` bits in an instruction are used as an extra opcode)
-  - displacement: `/disp8` or `/disp32`
-  - immediate: `/imm8` or `/imm32`
-
-Different instructions (opcodes) require different arguments. SubX will
-validate each instruction in your programs, and raise an error anytime you
-miss or spuriously add an argument.
-
-I recommend you order arguments consistently in your programs. SubX allows
-arguments in any order, but only because that's simplest to explain/implement.
-Switching order from instruction to instruction is likely to add to the
-reader's burden. Here's the order I've been using after opcodes:
-
-```
-        |<--------- reg/mem --------->|        |<- reg/mem? ->|
-/subop  /mod /rm32  /base /index /scale  /r32   /displacement   /immediate
-```
-
-Instructions can refer to labels in displacement or immediate arguments, and
-they'll obtain a value based on the address of the label: immediate arguments
-will contain the address directly, while displacement arguments will contain
-the difference between the address and the address of the current instruction.
-The latter is mostly useful for `jump` and `call` instructions.
-
-Functions are defined using labels. By convention, labels internal to functions
-(that must only be jumped to) start with a `$`. Any other labels must only be
-called, never jumped to. All labels must be unique.
-
-A special label is `Entry`, which can be used to specify/override the entry
-point of the program. It doesn't have to be unique, and the latest definition
-will override earlier ones.
-
-(The `Entry` label, along with duplicate segment headers, allows programs to
-be built up incrementally out of multiple [_layers_](http://akkartik.name/post/wart-layers).)
-
-The data segment consists of labels as before and byte values. Referring to
-data labels in either `code` segment instructions or `data` segment values
-(using the `imm32` metadata either way) yields their address.
-
-Automatic tests are an important part of SubX, and there's a simple mechanism
-to provide a test harness: all functions that start with `test-` are called in
-turn by a special, auto-generated function called `run-tests`. How you choose
-to call it is up to you.
-
-I try to keep things simple so that there's less work to do when I eventually
-implement SubX in SubX. But there _is_ one convenience: instructions can
-provide a string literal surrounded by quotes (`"`) in an `imm32` argument.
-SubX will transparently copy it to the `data` segment and replace it with its
-address. Strings are the only place where a SubX word is allowed to contain
-spaces.
-
-That should be enough information for writing SubX programs. The `examples/`
-directory provides some fodder for practice, giving a more gradual introduction
-to SubX features. This repo includes the binary for all examples. At any
-commit, an example's binary should be identical bit for bit with the result of
-translating the corresponding `.subx` file. The binary should also be natively
-runnable on a Linux system running on Intel x86 processors, either 32- or
-64-bit. If either of these invariants is broken it's a bug on my part.
-
-## Roadmap and status
-
-* Self-hosting. (✓)
-
-  `tests |dquotes |assort |pack |survey |hex`
-
-* A script to package SubX together with a minimal Linux kernel image
-  (compiled from source, of course).
-
-* Testable, dependency-injected vocabulary of primitives
-  - Streams: `read()`, `write()`. (✓)
-  - `exit()` (✓)
-  - Sockets
-  - Files
-  - Concurrency, and a framework for testing blocking code
-
-* Higher-level notations. Like programming languages, but with thinner
-  implementations that you can -- and are expected to! -- modify.
-  - syntax for addressing modes: `%reg`, `*reg`, `*(reg+disp)`,
-    `*(reg+reg+disp)`, `*(reg+reg<<n + disp)`
-  - function calls in a single line, using addressing modes for arguments
-  - syntax for controlling a type checker, akin to the top-level Mu language
-  - a register allocation _verifier_. Programmer provides registers for
-    variables; verifier checks that register reads are for the same type that
-    was last written -- across all control flow paths.
-
-## Running
-
-`subx` currently has the following sub-commands:
-
-* `subx help`: some helpful documentation to have at your fingertips.
-
-* `subx test`: runs all automated tests.
-
-* `subx translate <input files> -o <output ELF binary>`: translates `.subx`
-  files into an executable ELF binary.
-
-* `subx run <ELF binary>`: simulates running the ELF binaries emitted by `subx
-  translate`. Useful for debugging, and also enables more thorough testing of
-  `translate`.
-
-  Remember, not all 32-bit Linux binaries are guaranteed to run. I'm not
-  building general infrastructure here for all of the x86 instruction set.
-  SubX is about programming with a small, regular subset of 32-bit x86.
-
-## A few hints for debugging
-
-Writing programs in SubX is surprisingly pleasant and addictive. Reading
-programs is a work in progress, and hopefully the extensive unit tests help.
-However, _debugging_ programs is where one really faces up to the low-level
-nature of SubX. Even the smallest modifications need testing to make sure they
-work. In my experience, there is no modification so small that I get it working
-on the first attempt. And when it doesn't work, there are no clear error
-messages. Machine code is too simple-minded for that. You can't use a debugger,
-since SubX's simplistic ELF binaries contain no debugging information. So
-debugging requires returning to basics and practicing with a new, more
-rudimentary but hopefully still workable toolkit:
-
-* Start by nailing down a concrete set of steps for reproducibly obtaining the
-  error or erroneous behavior.
-
-* If possible, turn the steps into a failing test. It's not always possible,
-  but SubX's primary goal is to keep improving the variety of tests one can
-  write.
-
-* Start running the single failing test alone. This involves modifying the top
-  of the program (or the final `.subx` file passed in to `subx translate`) by
-  replacing the call to `run-tests` with a call to the appropriate `test-`
-  function.
-
-* Generate a trace for the failing test while running your program in emulated
-  mode (`subx run`):
-  ```
-  $ ./subx translate input.subx -o binary
-  $ ./subx --trace run binary arg1 arg2  2>trace
-  ```
-  The ability to generate a trace is the essential reason for the existence of
-  `subx run` mode. It gives far better visibility into program internals than
-  running natively.
-
-* As a further refinement, it is possible to render label names in the trace
-  by adding a second flag to both the `translate` and `run` commands:
-  ```
-  $ ./subx --debug translate input.subx -o binary
-  $ ./subx --debug --trace run binary arg1 arg2  2>trace
-  ```
-  `subx --debug translate` emits a mapping from label to address in a file
-  called `labels`. `subx --debug --trace run` reads in the `labels` file at
-  the start and prints out any matching label name as it traces each instruction
-  executed.
-
-  Here's a sample of what a trace looks like, with a few boxes highlighted:
-
-  <img alt='trace example' src='../html/subx/trace.png'>
-
-  Each of the green boxes shows the trace emitted for a single instruction.
-  It starts with a line of the form `run: inst: ___` followed by the opcode
-  for the instruction, the state of registers before the instruction executes,
-  and various other facts deduced during execution. Some instructions first
-  print a matching label. In the above screenshot, the red boxes show that
-  address `0x0900005e` maps to label `$loop` and presumably marks the start of
-  some loop. Function names get similar `run: == label` lines.
-
-* One trick when emitting traces with labels:
-  ```
-  $ grep label trace
-  ```
-  This is useful for quickly showing you the control flow for the run, and the
-  function executing when the error occurred. I find it useful to start with
-  this information, only looking at the complete trace after I've gotten
-  oriented on the control flow. Did it get to the loop I just modified? How
-  many times did it go through the loop?
-
-* Once you have SubX displaying labels in traces, it's a short step to modify
-  the program to insert more labels just to gain more insight. For example,
-  consider the following function:
-
-  <img alt='control example -- before' src='../html/subx/control0.png'>
-
-  This function contains a series of jump instructions. If a trace shows
-  `is-hex-lowercase-byte?` being encountered, and then `$is-hex-lowercase-byte?:end`
-  being encountered, it's still ambiguous what happened. Did we hit an early
-  exit, or did we execute all the way through? To clarify this, add temporary
-  labels after each jump:
-
-  <img alt='control example -- after' src='../html/subx/control1.png'>
-
-  Now the trace should have a lot more detail on which of these labels was
-  reached, and precisely when the exit was taken.
-
-* If you find yourself wondering, "when did the contents of this memory
-  address change?", `subx run` has some rudimentary support for _watch
-  points_. Just insert a label starting with `$watch-` before an instruction
-  that writes to the address, and its value will start getting dumped to the
-  trace after every instruction thereafter.
-
-* Once we have a sense for precisely which instructions we want to look at,
-  it's time to look at the trace as a whole. Key is the state of registers
-  before each instruction. If a function is receiving bad arguments it becomes
-  natural to inspect what values were pushed on the stack before calling it,
-  tracing back further from there, and so on.
-
-  I occasionally want to see the precise state of the stack segment, in which
-  case I uncomment a commented-out call to `dump_stack()` in the `vm.cc`
-  layer. It makes the trace a lot more verbose and a lot less dense, necessitating
-  a lot more scrolling around, so I keep it turned off most of the time.
-
-* If the trace seems overwhelming, try [browsing it](https://github.com/akkartik/mu/blob/master/browse_trace/Readme.md)
-  in the 'time-travel debugger'.
-
-Hopefully these hints are enough to get you started. The main thing to
-remember is to not be afraid of modifying the sources. A good debugging
-session gets into a nice rhythm of generating a trace, staring at it for a
-while, modifying the sources, regenerating the trace, and so on. Email
-[me](mailto:mu@akkartik.com) if you'd like another pair of eyes to stare at a
-trace, or if you have questions or complaints.
-
-## Reference documentation on available primitives
-
-### Data Structures
-
-* Kernel strings: null-terminated arrays of bytes. Unsafe and to be avoided,
-  but needed for interacting with the kernel.
-
-* Strings: length-prefixed arrays of bytes. String contents are preceded by
-  4 bytes (32 bytes) containing the `length` of the array.
-
-* Slices: a pair of 32-bit addresses denoting a [half-open](https://en.wikipedia.org/wiki/Interval_(mathematics))
-  \[`start`, `end`) interval to live memory with a consistent lifetime.
-
-  Invariant: `start` <= `end`
-
-* Streams: strings prefixed by 32-bit `write` and `read` indexes that the next
-  write or read goes to, respectively.
-
-  * offset 0: write index
-  * offset 4: read index
-  * offset 8: length of array (in bytes)
-  * offset 12: start of array data
-
-  Invariant: 0 <= `read` <= `write` <= `length`
-
-* File descriptors (fd): Low-level 32-bit integers that the kernel uses to
-  track files opened by the program.
-
-* File: 32-bit value containing either a fd or an address to a stream (fake
-  file).
-
-* Buffered files (buffered-file): Contain a file descriptor and a stream for
-  buffering reads/writes. Each `buffered-file` must exclusively perform either
-  reads or writes.
-
-### 'system calls'
-
-As I said at the top, a primary design goal of SubX (and Mu more broadly) is
-to explore ways to turn arbitrary manual tests into reproducible automated
-tests. SubX aims for this goal by baking testable interfaces deep into the
-stack, at the OS syscall level. The idea is that every syscall that interacts
-with hardware (and so the environment) should be *dependency injected* so that
-it's possible to insert fake hardware in tests.
-
-But those are big goals. Here are the syscalls I have so far:
-
-* `write`: takes two arguments, a file `f` and an address to array `s`.
-
-  Comparing this interface with the Unix `write()` syscall shows two benefits:
-
-  1. SubX can handle 'fake' file descriptors in tests.
-
-  1. `write()` accepts buffer and its length in separate arguments, which
-     requires callers to manage the two separately and so can be error-prone.
-     SubX's wrapper keeps the two together to increase the chances that we
-     never accidentally go out of array bounds.
-
-* `read`: takes two arguments, a file `f` and an address to stream `s`. Reads
-  as much data from `f` as can fit in (the free space of) `s`.
-
-  Like with `write()`, this wrapper around the Unix `read()` syscall adds the
-  ability to handle 'fake' file descriptors in tests, and reduces the chances
-  of clobbering outside array bounds.
-
-  One bit of weirdness here: in tests we do a redundant copy from one stream
-  to another. See [the comments before the implementation](http://akkartik.github.io/mu/html/subx/058read.subx.html)
-  for a discussion of alternative interfaces.
-
-* `stop`: takes two arguments:
-  - `ed` is an address to an _exit descriptor_. Exit descriptors allow us to
-    `exit()` the program in production, but return to the test harness within
-    tests. That allows tests to make assertions about when `exit()` is called.
-  - `value` is the status code to `exit()` with.
-
-  For more details on exit descriptors and how to create one, see [the
-  comments before the implementation](http://akkartik.github.io/mu/html/subx/057stop.subx.html).
-
-* `new-segment`
-
-  Allocates a whole new segment of memory for the program, discontiguous with
-  both existing code and data (heap) segments. Just a more opinionated form of
-  [`mmap`](http://man7.org/linux/man-pages/man2/mmap.2.html).
-
-* `allocate`: takes two arguments, an address to allocation-descriptor `ad`
-  and an integer `n`
-
-  Allocates a contiguous range of memory that is guaranteed to be exclusively
-  available to the caller. Returns the starting address to the range in `EAX`.
-
-  An allocation descriptor tracks allocated vs available addresses in some
-  contiguous range of memory. The int specifies the number of bytes to allocate.
-
-  Explicitly passing in an allocation descriptor allows for nested memory
-  management, where a sub-system gets a chunk of memory and further parcels it
-  out to individual allocations. Particularly helpful for (surprise) tests.
-
-* ... _(to be continued)_
-
-I will continue to import syscalls over time from [the old Mu VM in the parent
-directory](https://github.com/akkartik/mu), which has experimented with
-interfaces for the screen, keyboard, mouse, disk and network.
-
-### primitives built atop system calls
-
-_(Compound arguments are usually passed in by reference. Where the results are
-compound objects that don't fit in a register, the caller usually passes in
-allocated memory for it.)_
-
-#### assertions for tests
-* `check-ints-equal`: fails current test if given ints aren't equal
-* `check-stream-equal`: fails current test if stream doesn't match string
-* `check-next-stream-line-equal`: fails current test if next line of stream
-  until newline doesn't match string
-
-#### error handling
-* `error`: takes three arguments, an exit-descriptor, a file and a string (message)
-
-  Prints out the message to the file and then exits using the provided
-  exit-descriptor.
-
-* `error-byte`: like `error` but takes an extra byte value that it prints out
-  at the end of the message.
-
-#### predicates
-* `kernel-string-equal?`: compares a kernel string with a string
-* `string-equal?`: compares two strings
-* `stream-data-equal?`: compares a stream with a string
-* `next-stream-line-equal?`: compares with string the next line in a stream, from
-  `read` index to newline
-
-* `slice-empty?`: checks if the `start` and `end` of a slice are equal
-* `slice-equal?`: compares a slice with a string
-* `slice-starts-with?`: compares the start of a slice with a string
-* `slice-ends-with?`: compares the end of a slice with a string
-
-#### writing to disk
-* `write`: string -> file
-  - Can also be used to cat a string into a stream.
-  - Will abort the entire program if destination is a stream and doesn't have
-    enough room.
-* `write-stream`: stream -> file
-  - Can also be used to cat one stream into another.
-  - Will abort the entire program if destination is a stream and doesn't have
-    enough room.
-* `write-slice`: slice -> stream
-  - Will abort the entire program if there isn't enough room in the
-    destination stream.
-* `append-byte`: int -> stream
-  - Will abort the entire program if there isn't enough room in the
-    destination stream.
-* `append-byte-hex`: int -> stream
-  - textual representation in hex, no '0x' prefix
-  - Will abort the entire program if there isn't enough room in the
-    destination stream.
-* `print-int32`: int -> stream
-  - textual representation in hex, including '0x' prefix
-  - Will abort the entire program if there isn't enough room in the
-    destination stream.
-* `write-buffered`: string -> buffered-file
-* `write-slice-buffered`: slice -> buffered-file
-* `flush`: buffered-file
-* `write-byte-buffered`: int -> buffered-file
-* `print-byte-buffered`: int -> buffered-file
-  - textual representation in hex, no '0x' prefix
-* `print-int32-buffered`: int -> buffered-file
-  - textual representation in hex, including '0x' prefix
-
-#### reading from disk
-* `read`: file -> stream
-  - Can also be used to cat one stream into another.
-  - Will silently stop reading when destination runs out of space.
-* `read-byte-buffered`: buffered-file -> byte
-* `read-line-buffered`: buffered-file -> stream
-  - Will abort the entire program if there isn't enough room.
-
-#### non-IO operations on streams
-* `new-stream`: allocates space for a stream of `n` elements, each occupying
-  `b` bytes.
-  - Will abort the entire program if `n*b` requires more than 32 bits.
-* `clear-stream`: resets everything in the stream to `0` (except its `length`).
-* `rewind-stream`: resets the read index of the stream to `0` without modifying
-  its contents.
-
-#### reading/writing hex representations of integers
-* `is-hex-int?`: takes a slice argument, returns boolean result in `EAX`
-* `parse-hex-int`: takes a slice argument, returns int result in `EAX`
-* `is-hex-digit?`: takes a 32-bit word containing a single byte, returns
-  boolean result in `EAX`.
-* `from-hex-char`: takes a hexadecimal digit character in EAX, returns its
-  numeric value in `EAX`
-* `to-hex-char`: takes a single-digit numeric value in EAX, returns its
-  corresponding hexadecimal character in `EAX`
-
-#### tokenization
-
-from a stream:
-* `next-token`: stream, delimiter byte -> slice
-* `skip-chars-matching`: stream, delimiter byte
-* `skip-chars-not-matching`: stream, delimiter byte
-
-from a slice:
-* `next-token-from-slice`: start, end, delimiter byte -> slice
-  - Given a slice and a delimiter byte, returns a new slice inside the input
-    that ends at the delimiter byte.
-
-* `skip-chars-matching-in-slice`: curr, end, delimiter byte -> new-curr (in `EAX`)
-* `skip-chars-not-matching-in-slice`:  curr, end, delimiter byte -> new-curr (in `EAX`)
-
-## Conclusion
-
-The hypothesis of Mu and SubX is that designing the entire system to be
-testable from day 1 and from the ground up would radically impact the culture
-of the eco-system in a way that no bolted-on tool or service at higher levels
-can replicate:
-
-* Tests would make it easier to write programs that can be easily understood
-  by newcomers.
-
-* More broad-based understanding would lead to more forks.
-
-* Tests would make it easy to share code across forks. Copy the tests over,
-  and then copy code over and polish it until the tests pass. Manual work, but
-  tractable and without major risks.
-
-* The community would gain a diversified portfolio of forks for each program,
-  a “wavefront” of possible combinations of features and alternative
-  implementations of features. Application writers who wrote thorough tests
-  for their apps (something they just can’t do today) would be able to bounce
-  around between forks more easily without getting locked in to a single one
-  as currently happens.
-
-* There would be a stronger culture of reviewing the code for programs you use
-  or libraries you depend on. [More eyeballs would make more bugs shallow.](https://en.wikipedia.org/wiki/Linus%27s_Law)
-
-## Resources
-
-* [Single-page cheatsheet for the x86 ISA](https://net.cs.uni-bonn.de/fileadmin/user_upload/plohmann/x86_opcode_structure_and_instruction_overview.pdf)
-  (pdf; [cached local copy](https://github.com/akkartik/mu/blob/master/subx/cheatsheet.pdf))
-* [Concise reference for the x86 ISA](https://c9x.me/x86)
-* [Intel processor manual](http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf) (pdf)
-* [Some details on the unconventional organization of this project.](http://akkartik.name/post/four-repos)
-
-## Inspirations
-
-* [&ldquo;Creating tiny ELF executables&rdquo;](https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html)
-* [&ldquo;Bootstrapping a compiler from nothing&rdquo;](http://web.archive.org/web/20061108010907/http://www.rano.org/bcompiler.html)
-* Forth implementations like [StoneKnifeForth](https://github.com/kragen/stoneknifeforth)
diff --git a/subx/apps/Readme.md b/subx/apps/Readme.md
deleted file mode 100644
index b76c5a8b..00000000
--- a/subx/apps/Readme.md
+++ /dev/null
@@ -1,2 +0,0 @@
-Larger programs than in the subx/examples/ subdirectory, combining the
-techniques demonstrated there.
diff --git a/subx/apps/assort b/subx/apps/assort
deleted file mode 100755
index 70d7aaf3..00000000
--- a/subx/apps/assort
+++ /dev/null
Binary files differdiff --git a/subx/apps/assort.subx b/subx/apps/assort.subx
deleted file mode 100644
index 801e52d0..00000000
--- a/subx/apps/assort.subx
+++ /dev/null
@@ -1,911 +0,0 @@
-# Read a series of segments from stdin and concatenate segments with the same
-# name on stdout.
-#
-# Segments are emitted in order of first encounter.
-#
-# Drop lines that are all comments. They could get misleading after assortment
-# because we don't know if they refer to the line above or the line below.
-#
-# To run (from the subx/ directory):
-#   $ ./subx translate *.subx apps/assort.subx -o apps/assort
-#   $ cat x
-#   == code
-#   abc
-#   == code
-#   def
-#   $ cat x  |./subx run apps/assort
-#   == code
-#   abc
-#   def
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # initialize heap
-    # . Heap = new-segment(Heap-size)
-    # . . push args
-    68/push  Heap/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-
-    # run tests if necessary, convert stdin if not
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # . argc > 1
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # . argv[1] == "test"
-    # . . push args
-    68/push  "test"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  kernel-string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check result
-    3d/compare-EAX-and  1/imm32
-    75/jump-if-not-equal  $run-main/disp8
-    # . run-tests()
-    e8/call  run-tests/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    eb/jump  $main:end/disp8
-$run-main:
-    # - otherwise convert stdin
-    # var ed/EAX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # configure ed to really exit()
-    # . ed->target = 0
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
-    # return convert(Stdin, 1/stdout, 2/stderr, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  Stderr/imm32
-    68/push  Stdout/imm32
-    68/push  Stdin/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # . syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-$main:end:
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# data structure:
-#   table: (address stream {string, (address stream byte)})     (8 bytes per row)
-# inefficient; uses sequential search for looking up segments by name
-
-convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   var table : (address stream) = new-stream(10 rows, 8 bytes each)
-    #   read-segments(in, table)
-    #   write-segments(out, table)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    # var table/ECX : (address stream byte) = stream(10 * 8)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x50/imm32        # subtract from ESP
-    68/push  0x50/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # clear-stream(table)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$convert:read:
-#?     # print("read\n") {{{
-#?     # . . push args
-#?     68/push  "read\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
-#?     # }}}
-    # read-segments(in, table)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-segments/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:write:
-#?     # print("write\n") {{{
-#?     # . . push args
-#?     68/push  "write\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
-#?     # }}}
-    # write-segments(out, table)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-segments/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x5c/imm32        # add to ESP
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-input-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-input-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input (meta comments in parens)
-    #   # comment 1
-    #     # comment 2 indented
-    #   == code 0x09000000  (new segment)
-    #   # comment 3 inside a segment
-    #   1
-    #                         (empty line)
-    #   2 3 # comment 4 inline with other contents
-    #   == data 0x0a000000  (new segment)
-    #   4 5/imm32
-    #   == code  (existing segment but non-contiguous with previous iteration)
-    #   6 7
-    #   8 9  (multiple lines)
-    #   == code  (existing segment contiguous with previous iteration)
-    #   10 11
-    # . write(_test-input-stream, "# comment 1\n")
-    # . . push args
-    68/push  "# comment 1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "  # comment 2 indented\n")
-    # . . push args
-    68/push  "  # comment 2 indented\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== code 0x09000000\n")
-    # . . push args
-    68/push  "== code 0x09000000\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "# comment 3 inside a segment\n")
-    # . . push args
-    68/push  "# comment 3 inside a segment\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "1\n")
-    # . . push args
-    68/push  "1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "\n")  # empty line
-    # . . push args
-    68/push  "\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "2 3 # comment 4 inline with other contents\n")
-    # . . push args
-    68/push  "2 3 # comment 4 inline with other contents\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== data 0x0a000000\n")
-    # . . push args
-    68/push  "== data 0x0a000000\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "4 5/imm32\n")
-    # . . push args
-    68/push  "4 5/imm32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== code\n")
-    # . . push args
-    68/push  "== code\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "6 7\n")
-    # . . push args
-    68/push  "6 7\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "8 9\n")
-    # . . push args
-    68/push  "8 9\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== code\n")
-    # . . push args
-    68/push  "== code\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "10 11\n")
-    # . . push args
-    68/push  "10 11\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert(_test-input-buffered-file, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-buffered-file/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check output
-    #   == code 0x09000000
-    #   1
-    #   2 3 # comment 4 inline with other contents
-    #   6 7
-    #   8 9
-    #   10 11
-    #   == data 0x0a000000
-    #   4 5/imm32
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "== code 0x09000000", msg)
-    # . . push args
-    68/push  "F - test-convert/0"/imm32
-    68/push  "== code 0x09000000"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "1", msg)
-    # . . push args
-    68/push  "F - test-convert/1"/imm32
-    68/push  "1"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "2 3 # comment 4 inline with other contents", msg)
-    # . . push args
-    68/push  "F - test-convert/2"/imm32
-    68/push  "2 3 # comment 4 inline with other contents"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "6 7", msg)
-    # . . push args
-    68/push  "F - test-convert/3"/imm32
-    68/push  "6 7"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "8 9", msg)
-    # . . push args
-    68/push  "F - test-convert/4"/imm32
-    68/push  "8 9"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "10 11", msg)
-    # . . push args
-    68/push  "F - test-convert/5"/imm32
-    68/push  "10 11"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "== data 0x0a000000", msg)
-    # . . push args
-    68/push  "F - test-convert/6"/imm32
-    68/push  "== data 0x0a000000"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "4 5/imm32", msg)
-    # . . push args
-    68/push  "F - test-convert/7"/imm32
-    68/push  "4 5/imm32"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# beware: leaks memory (one name per segment read)
-read-segments:  # in : (address buffered-file), table : (address stream {string, (address stream byte)})
-    # pseudocode:
-    #   var curr-segment = null
-    #   var line = new-stream(512, 1)
-    #   while true
-    #     clear-stream(line)
-    #     read-line-buffered(in, line)
-    #     if (line->write == 0) break             # end of file
-    #     var word-slice = next-word(line)
-    #     if slice-empty?(word-slice)             # whitespace
-    #       continue
-    #     if slice-starts-with?(word-slice, "#")  # comment
-    #       continue
-    #     if slice-equal?(word-slice, "==")
-    #       var segment-name = next-word(line)
-    #       segment-slot = leaky-get-or-insert-slice(table, segment-name, row-size=8)
-    #       curr-segment = *segment-slot
-    #       if curr-segment != 0
-    #         continue
-    #       curr-segment = new-stream(Segment-size)
-    #       *segment-slot = curr-segment
-    #     rewind-stream(line)
-    #     write-stream(curr-segment, line)  # abort if curr-segment overflows
-    #
-    # word-slice and segment-name are both slices with disjoint lifetimes, so
-    # we'll use the same address for them.
-    #
-    # registers:
-    #   line: ECX
-    #   word-slice and segment-name: EDX
-    #   segment-name and curr-segment: EBX
-    #   word-slice->start: ESI
-    #   temporary: EAX
-    #
-    # . prolog
-    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
-    # var line/ECX : (address stream byte) = stream(512)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
-    68/push  0x200/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var word-slice/EDX = {0, 0}
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-$read-segments:loop:
-    # clear-stream(line)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read-line-buffered(in, line)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-line-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$read-segments:check0:
-    # if (line->write == 0) break
-    81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
-    0f 84/jump-if-equal  $read-segments:break/disp32
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, line)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(line)
-#?     # . . push args
-#?     51/push-ECX
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . 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
-#?     # }}}
-    # next-word(line, word-slice)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$read-segments:check1:
-#?     # print("check1\n") {{{
-#?     # . . push args
-#?     68/push  "check1\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
-#?     # }}}
-    # if (slice-empty?(word-slice)) continue
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) continue
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $read-segments:loop/disp32
-$read-segments:check-for-comment:
-#?     # print("check for comment\n") {{{
-#?     # . . push args
-#?     68/push  "check for comment\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
-#?     # }}}
-    # if (slice-starts-with?(word-slice, "#")) continue
-    # . start/ESI = word-slice->start
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           6/r32/ESI   .               .                 # copy *ECX to ESI
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/AL    .               .                 # copy byte at *ESI to AL
-    # . if (EAX == '#') continue
-    3d/compare-EAX-and  0x23/imm32/hash
-    0f 84/jump-if-equal  $read-segments:loop/disp32
-$read-segments:check-for-segment-header:
-#?     # print("check for segment header\n") {{{
-#?     # . . push args
-#?     68/push  "check for segment header\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
-#?     # }}}
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     52/push-EDX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # if !slice-equal?(word-slice, "==") goto next check
-    # . EAX = slice-equal?(word-slice, "==")
-    # . . push args
-    68/push  "=="/imm32
-    52/push-EDX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto check3
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $read-segments:regular-line/disp32
-    # segment-name = next-word(line)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump segment name {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     52/push-EDX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # segment-slot/EAX = leaky-get-or-insert-slice(table, segment-name, row-size=8)
-    # . . push args
-    68/push  8/imm32/row-size
-    52/push-EDX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  leaky-get-or-insert-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # curr-segment = *segment-slot
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # copy *EAX to EBX
-    # if (curr-segment != 0) continue
-    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0/imm32           # compare EBX
-    0f 85/jump-if-not-equal  $read-segments:loop/disp32
-    # curr-segment = new-stream(Heap, Segment-size, 1)
-    # . save segment-slot
-    50/push-EAX
-    # . EAX = new-stream(Heap, Segment-size, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Segment-size/disp32               # push *Segment-size
-    68/push  Heap/imm32
-    # . . call
-    e8/call  new-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . curr-segment = EAX
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    # . restore segment-slot
-    58/pop-to-EAX
-    # *segment-slot = curr-segment
-    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # copy EBX to *EAX
-    # fall through
-$read-segments:regular-line:
-#?     # print("regular line\n") {{{
-#?     # . . push args
-#?     68/push  "regular line\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
-#?     # }}}
-#?     # dump line {{{
-#?     # . write(2/stderr, "regular line: ")
-#?     # . . push args
-#?     68/push  "regular line: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(line)
-#?     # . . push args
-#?     51/push-ECX
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write-stream(2/stderr, line)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # rewind-stream(line)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # print("write stream\n") {{{
-#?     # . . push args
-#?     68/push  "write stream\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
-#?     # }}}
-    # write-stream(curr-segment, line)
-    # . . push args
-    51/push-ECX
-    53/push-EBX
-    # . . call
-    e8/call  write-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # loop
-#?     # print("loop\n") {{{
-#?     # . . push args
-#?     68/push  "loop\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
-#?     # }}}
-    e9/jump  $read-segments:loop/disp32
-$read-segments:break:
-$read-segments:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x214/imm32       # add to ESP
-    # . restore registers
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-write-segments:  # out : (address buffered-file), table : (address stream {string, (address stream byte)})
-    # pseudocode:
-    #   var curr = table->data
-    #   var max = table->data + table->write
-    #   while curr < max
-    #     stream = table[i].stream
-    #     write-stream-data(out, stream)
-    #     curr += 8
-    #   flush(out)
-    #
-    # . prolog
-    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
-    56/push-ESI
-    # ESI = table
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # write/EDX = table->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    # curr/ESI = table->data
-    81          0/subop/add         3/mod/direct    6/rm32/ESI    .           .             .           .           .               0xc/imm32         # add to EAX
-    # max/EDX = curr + write
-    01/add                          3/mod/direct    2/rm32/EDX    .           .             .           6/r32/ESI   .               .                 # add ESI to EDX
-$write-segments:loop:
-    # if (curr >= max) break
-    39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # compare ESI with EDX
-    73/jump-if-greater-or-equal-unsigned  $write-segments:break/disp8
-    # stream/EAX = table[i].stream
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
-    # write-stream-data(out, stream)
-    # . . push args
-    50/push-EAX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$write-segments:continue:
-    # curr += 8
-    81          0/subop/add         3/mod/direct    6/rm32/ESI    .           .             .           .           .               8/imm32           # add to ESI
-    eb/jump  $write-segments:loop/disp8
-$write-segments:break:
-    # flush(out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$write-segments:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    58/pop-to-EAX
-    # . epilog
-    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/subx/apps/crenshaw2-1 b/subx/apps/crenshaw2-1
deleted file mode 100755
index 47ee601e..00000000
--- a/subx/apps/crenshaw2-1
+++ /dev/null
Binary files differdiff --git a/subx/apps/crenshaw2-1.subx b/subx/apps/crenshaw2-1.subx
deleted file mode 100644
index 0c3f180b..00000000
--- a/subx/apps/crenshaw2-1.subx
+++ /dev/null
@@ -1,585 +0,0 @@
-# Port of https://github.com/akkartik/crenshaw/blob/master/tutor2.1.pas
-# which corresponds to the section "single digits" in https://compilers.iecc.com/crenshaw/tutor2.txt
-# except that we support hex digits.
-#
-# To run (from the subx/ directory):
-#   $ ./subx translate *.subx apps/crenshaw2-1.subx -o apps/crenshaw2-1
-#   $ echo '3'  |./subx run apps/crenshaw2-1
-# Expected output:
-#   # syscall(exit, 3)
-#   bb/copy-to-EBX  3/imm32
-#   b8/copy-to-EAX  1/imm32/exit
-#   cd/syscall  0x80/imm8
-#
-# To run the generated output:
-#   $ echo '3'  |./subx run apps/crenshaw2-1 > z1.subx
-#   $ ./subx translate z1.subx -o z1
-#   $ ./subx run z1
-#   $ echo $?
-#   3
-#
-# Stdin must contain just a single hex digit. Other input will print an error:
-#   $ echo 'xyz'  |./subx run apps/crenshaw2-1
-#   Error: integer expected
-#
-# Names in this file sometimes follow Crenshaw's original rather than my usual
-# naming conventions.
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # run tests if necessary, call 'compile' if not
-    # initialize heap
-    # . Heap = new-segment(64KB)
-    # . . push args
-    68/push  Heap/imm32
-    68/push  0x10000/imm32/64KB
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # . argc > 1
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # . argv[1] == "test"
-    # . . push args
-    68/push  "test"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  kernel-string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check result
-    3d/compare-EAX-and  1/imm32
-    75/jump-if-not-equal  $run-main/disp8
-    # . run-tests()
-    e8/call  run-tests/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    eb/jump  $main:end/disp8
-$run-main:
-    # - otherwise read a program from stdin and emit its translation to stdout
-    # var ed/EAX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # configure ed to really exit()
-    # . ed->target = 0
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
-    # return compile(Stdin, 1/stdout, 2/stderr, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  2/imm32/stderr
-    68/push  1/imm32/stdout
-    68/push  Stdin/imm32
-    # . . call
-    e8/call  compile/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # . syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-$main:end:
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# the main entry point
-compile:  # in : (address buffered-file), out : fd or (address stream), err : fd or (address stream), ed : (address exit-descriptor) -> <void>
-    # . prolog
-    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
-    # prime the pump
-    # . Look = get-char(in)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8      .                    # push *(EBP+8)
-    # . . call
-    e8/call  get-char/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var num/ECX : (address stream) on the stack
-    # Numbers can be 32 bits or 8 hex bytes long. One of them will be in 'Look', so we need space for 7 bytes.
-    # Sizing the stream just right buys us overflow-handling for free inside 'get-num'.
-    # Add 12 bytes for 'read', 'write' and 'length' fields, for a total of 19 bytes, or 0x13 in hex.
-    # The stack pointer is no longer aligned, so dump_stack() can be misleading past this point.
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x13/imm32        # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # initialize the stream
-    # . num->length = 7
-    c7          0/subop/copy        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           8/disp8         7/imm32           # copy to *(ECX+8)
-    # . clear-stream(num)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read a digit from 'in' into 'num'
-    # . get-num(in, num, err, ed)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    51/push-ECX/num
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8      .                    # push *(EBP+8)
-    # . . call
-    e8/call  get-num/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # render 'num' into the following template on 'out':
-    #   bb/copy-to-EBX  _num_
-    #   b8/copy-to-EAX  1/imm32/exit
-    #   cd/syscall  0x80/imm8
-    #
-    # . write(out, "bb/copy-to-EBX  ")
-    # . . push args
-    68/push  "bb/copy-to-EBX  "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write-stream(out, num)
-    # . . push args
-    51/push-ECX/num
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(out, Newline)
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(out, "b8/copy-to-EAX  1/imm32/exit\n")
-    # . . push args
-    68/push  "b8/copy-to-EAX  1/imm32/exit\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(out, "cd/syscall  0x80/imm8\n")
-    # . . push args
-    68/push  "cd/syscall  0x80/imm8\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$compile:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# Read a single digit into 'out'. Abort if there are none, or if there is no space in 'out'.
-# Input comes from the global variable 'Look', and we leave the next byte from
-# 'in' into it on exit.
-get-num:  # in : (address buffered-file), out : (address stream), err : fd or (address stream), ed : (address exit-descriptor) -> <void>
-    # pseudocode:
-    #   if (!is-digit?(Look)) expected(ed, err, "integer")
-    #   if out->write >= out->length
-    #     write(err, "Error: too many digits in number\n")
-    #     stop(ed, 1)
-    #   out->data[out->write] = LSB(Look)
-    #   ++out->write
-    #   Look = get-char(in)
-    #
-    # registers:
-    #   in: ESI
-    #   out: EDI
-    #   out->write: ECX (cached copy; need to keep in sync)
-    #   out->length: EDX
-    #   temporaries: EAX, EBX
-    # We can't allocate Look to a register because it gets written implicitly in
-    # get-char in each iteration of the loop. (Thereby demonstrating that it's
-    # not the right interface for us. But we'll keep it just to follow Crenshaw.)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - if (is-digit?(Look)) expected(ed, err, "integer")
-    # . EAX = is-digit?(Look)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
-    # . . call
-    e8/call  is-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX == 0)
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $get-num:main/disp8
-    # . expected(ed, err, "integer")
-    # . . push args
-    68/push  "integer"/imm32
-    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    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    # . . call
-    e8/call  expected/disp32  # never returns
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$get-num:main:
-    # - otherwise read a digit
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    53/push-EBX
-    56/push-ESI
-    57/push-EDI
-    # read necessary variables to registers
-    # ESI = in
-    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
-    # ECX = out->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
-    # EDX = out->length
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
-    # if (out->write >= out->length) error
-    39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # compare EDX with ECX
-    7d/jump-if-lesser  $get-num:stage2/disp8
-    # . error(ed, err, msg)  # TODO: show full number
-    # . . push args
-    68/push  "get-num: too many digits in number"/imm32
-    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    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    # . . call
-    e8/call  error/disp32  # never returns
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$get-num:stage2:
-    # out->data[out->write] = LSB(Look)
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+ECX+12 to EBX
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy *Look to EAX
-    88/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at AL to *EBX
-    # ++out->write
-    41/increment-ECX
-    # Look = get-char(in)
-    # . . push args
-    56/push-ESI
-    # . . call
-    e8/call  get-char/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$get-num:loop-end:
-    # persist necessary variables from registers
-    89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EDI
-$get-num: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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-get-num-reads-single-digit:
-    # - check that get-num returns first character if it's a digit
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize 'in'
-    # . write(_test-stream, "3")
-    # . . push args
-    68/push  "3"/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
-    # initialize exit-descriptor 'ed' for the call to 'get-num' below
-    # . var ed/EAX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # . tailor-exit-descriptor(ed, 16)
-    # . . push args
-    68/push  0x10/imm32/nbytes-of-args-for-get-num
-    50/push-EAX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # prime the pump
-    # . get-char(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-char/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # get-num(in, out, err, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  _test-error-stream/imm32
-    68/push  _test-output-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-num/disp32
-    # registers except ESP may be clobbered at this point
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # check-ints-equal(*_test-output-stream->data, '3', msg)
-    # . . push args
-    68/push  "F - test-get-num-reads-single-digit"/imm32
-    68/push  0x33/imm32
-    b8/copy-to-EAX  _test-output-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
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-get-num-aborts-on-non-digit-in-Look:
-    # - check that get-num returns first character if it's a digit
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize 'in'
-    # . write(_test-stream, "3")
-    # . . push args
-    68/push  "3"/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
-    # initialize exit-descriptor 'ed' for the call to 'get-num' below
-    # . var ed/EAX : (address exit-descriptor)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # . tailor-exit-descriptor(ed, 16)
-    # . . push args
-    68/push  0x10/imm32/nbytes-of-args-for-get-num
-    50/push-EAX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # *don't* prime the pump
-    # get-num(in, out, err, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  _test-error-stream/imm32
-    68/push  _test-output-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-num/disp32
-    # registers except ESP may be clobbered at this point
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # check that get-num tried to call exit(1)
-    # . check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
-    # . . push args
-    68/push  "F - test-get-num-aborts-on-non-digit-in-Look"/imm32
-    68/push  2/imm32
-    # . . push ed->value
-    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
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-## helpers
-
-# write(f, "Error: "+s+" expected\n") then stop(ed, 1)
-expected:  # ed : (address exit-descriptor), f : fd or (address stream), s : (address array byte) -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write(f, "Error: ")
-    # . . push args
-    68/push  "Error: "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(f, s)
-    # . . 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)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(f, " expected")
-    # . . push args
-    68/push  " expected\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # stop(ed, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  stop/disp32
-    # should never get past this point
-$expected:dead-end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# read a byte from 'f', and save it in 'Look'
-get-char:  # f : (address buffered-file) -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    # EAX = read-byte-buffered(f)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # save EAX to Look
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy EAX to *Look
-$get-char:end:
-    # . restore registers
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-is-digit?:  # c : int -> EAX : boolean
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # EAX = false
-    b8/copy-to-EAX  0/imm32
-    # if (c < '0') return false
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x30/imm32        # compare *(EBP+8)
-    7c/jump-if-lesser  $is-digit?:end/disp8
-    # if (c > '9') return false
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x39/imm32        # compare *(EBP+8)
-    7f/jump-if-greater  $is-digit?:end/disp8
-    # otherwise return true
-    b8/copy-to-EAX  1/imm32
-$is-digit?:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data
-
-Look:  # (char with some extra padding)
-    0/imm32
-
-_test-output-stream:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    8/imm32
-    # data
-    00 00 00 00 00 00 00 00  # 8 bytes
-
-_test-error-stream:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    0x40/imm32
-    # data (4 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
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/apps/crenshaw2-1b b/subx/apps/crenshaw2-1b
deleted file mode 100755
index fba81a7d..00000000
--- a/subx/apps/crenshaw2-1b
+++ /dev/null
Binary files differdiff --git a/subx/apps/crenshaw2-1b.subx b/subx/apps/crenshaw2-1b.subx
deleted file mode 100644
index e1bb6448..00000000
--- a/subx/apps/crenshaw2-1b.subx
+++ /dev/null
@@ -1,785 +0,0 @@
-# Port of https://github.com/akkartik/crenshaw/blob/master/tutor2.1.pas
-# which corresponds to the section "single digits" in https://compilers.iecc.com/crenshaw/tutor2.txt
-# except that we support hex numbers of multiple digits.
-#
-# To run (from the subx/ directory):
-#   $ ./subx translate *.subx apps/crenshaw2-1b.subx -o apps/crenshaw2-1b
-#   $ echo '1a'  |./subx run apps/crenshaw2-1b
-# Expected output:
-#   # syscall(exit, 1a)
-#   bb/copy-to-EBX  3/imm32
-#   b8/copy-to-EAX  1/imm32/exit
-#   cd/syscall  0x80/imm8
-#
-# To run the generated output:
-#   $ echo '1a'  |./subx run apps/crenshaw2-1b > z1.subx
-#   $ ./subx translate z1.subx -o z1
-#   $ ./subx run z1
-#   $ echo $?
-#   26  # 0x1a in decimal
-#
-# Stdin must contain just a single hex digit. Other input will print an error:
-#   $ echo 'xyz'  |./subx run apps/crenshaw2-1b
-#   Error: integer expected
-#
-# Names in this file sometimes follow Crenshaw's original rather than my usual
-# naming conventions.
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # run tests if necessary, call 'compile' if not
-    # initialize heap
-    # . Heap = new-segment(64KB)
-    # . . push args
-    68/push  Heap/imm32
-    68/push  0x10000/imm32/64KB
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # . argc > 1
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # . argv[1] == "test"
-    # . . push args
-    68/push  "test"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  kernel-string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check result
-    3d/compare-EAX-and  1/imm32
-    75/jump-if-not-equal  $run-main/disp8
-    # . run-tests()
-    e8/call  run-tests/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    eb/jump  $main:end/disp8
-$run-main:
-    # - otherwise read a program from stdin and emit its translation to stdout
-    # var ed/EAX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # configure ed to really exit()
-    # . ed->target = 0
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
-    # return compile(Stdin, 1/stdout, 2/stderr, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  2/imm32/stderr
-    68/push  1/imm32/stdout
-    68/push  Stdin/imm32
-    # . . call
-    e8/call  compile/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # . syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-$main:end:
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# the main entry point
-compile:  # in : (address buffered-file), out : fd or (address stream), err : fd or (address stream), ed : (address exit-descriptor) -> <void>
-    # . prolog
-    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
-    # prime the pump
-    # . Look = get-char(in)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8      .                    # push *(EBP+8)
-    # . . call
-    e8/call  get-char/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var num/ECX : (address stream) on the stack
-    # Numbers can be 32 bits or 8 hex bytes long. One of them will be in 'Look', so we need space for 7 bytes.
-    # Sizing the stream just right buys us overflow-handling for free inside 'get-num'.
-    # Add 12 bytes for 'read', 'write' and 'length' fields, for a total of 19 bytes, or 0x13 in hex.
-    # The stack pointer is no longer aligned, so dump_stack() can be misleading past this point.
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x13/imm32        # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # initialize the stream
-    # . num->length = 7
-    c7          0/subop/copy        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           8/disp8         7/imm32           # copy to *(ECX+8)
-    # . clear-stream(num)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read a digit from 'in' into 'num'
-    # . get-num(in, num, err, ed)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    51/push-ECX/num
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8      .                    # push *(EBP+8)
-    # . . call
-    e8/call  get-num/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # render 'num' into the following template on 'out':
-    #   bb/copy-to-EBX  _num_
-    #   b8/copy-to-EAX  1/imm32/exit
-    #   cd/syscall  0x80/imm8
-    #
-    # . write(out, "bb/copy-to-EBX  ")
-    # . . push args
-    68/push  "bb/copy-to-EBX  "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write-stream(out, num)
-    # . . push args
-    51/push-ECX/num
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(out, Newline)
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(out, "b8/copy-to-EAX  1/imm32/exit\n")
-    # . . push args
-    68/push  "b8/copy-to-EAX  1/imm32/exit\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(out, "cd/syscall  0x80/imm8\n")
-    # . . push args
-    68/push  "cd/syscall  0x80/imm8\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$compile:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# Read a sequence of digits into 'out'. Abort if there are none, or if there is
-# no space in 'out'.
-# Input comes from the global variable 'Look' (first byte) and the argument
-# 'in' (rest). We leave the next byte from 'in' into 'Look' on exit.
-get-num:  # in : (address buffered-file), out : (address stream), err : fd or (address stream), ed : (address exit-descriptor) -> <void>
-    # pseudocode:
-    #   if (!is-digit?(Look)) expected(ed, err, "integer")
-    #   do
-    #     if out->write >= out->length
-    #       write(err, "Error: too many digits in number\n")
-    #       stop(ed, 1)
-    #     out->data[out->write] = LSB(Look)
-    #     ++out->write
-    #     Look = get-char(in)
-    #   while is-digit?(Look)
-    # This is complicated because I don't want to hard-code the error strategy in
-    # a general helper like write-byte-buffered. Maybe I should just create a
-    # local helper.
-    #
-    # within the loop we'll try to keep things in registers:
-    #   in: ESI
-    #   out: EDI
-    #   out->write: ECX (cached copy; need to keep in sync)
-    #   out->length: EDX
-    #   temporaries: EAX, EBX
-    # We can't allocate Look to a register because it gets written implicitly in
-    # get-char in each iteration of the loop. (Thereby demonstrating that it's
-    # not the right interface for us. But we'll keep it just to follow Crenshaw.)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - if (is-digit?(Look)) expected(ed, err, "integer")
-    # . EAX = is-digit?(Look)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
-    # . . call
-    e8/call  is-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX == 0)
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $get-num:main/disp8
-    # . expected(ed, err, "integer")
-    # . . push args
-    68/push  "integer"/imm32
-    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    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    # . . call
-    e8/call  expected/disp32  # never returns
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$get-num:main:
-    # - otherwise read a digit
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    53/push-EBX
-    56/push-ESI
-    57/push-EDI
-    # read necessary variables to registers
-    # ESI = in
-    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
-    # ECX = out->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
-    # EDX = out->length
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
-$get-num:loop:
-    # if (out->write >= out->length) error
-    39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # compare EDX with ECX
-    7d/jump-if-lesser  $get-num:loop-stage2/disp8
-    # . error(ed, err, msg)  # TODO: show full number
-    # . . push args
-    68/push  "get-num: too many digits in number"/imm32
-    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    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    # . . call
-    e8/call  error/disp32  # never returns
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$get-num:loop-stage2:
-    # out->data[out->write] = LSB(Look)
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+ECX+12 to EBX
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy *Look to EAX
-    88/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at AL to *EBX
-    # ++out->write
-    41/increment-ECX
-    # Look = get-char(in)
-    # . . push args
-    56/push-ESI
-    # . . call
-    e8/call  get-char/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # if (is-digit?(Look)) loop
-    # . EAX = is-digit?(Look)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
-    # . . call
-    e8/call  is-digit?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) loop
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $get-num:loop/disp32
-$get-num:loop-end:
-    # persist necessary variables from registers
-    89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EDI
-$get-num: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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-get-num-reads-single-digit:
-    # - check that get-num returns first character if it's a digit
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize 'in'
-    # . write(_test-stream, "3")
-    # . . push args
-    68/push  "3"/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
-    # initialize exit-descriptor 'ed' for the call to 'get-num' below
-    # . var ed/EAX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # . tailor-exit-descriptor(ed, 16)
-    # . . push args
-    68/push  0x10/imm32/nbytes-of-args-for-get-num
-    50/push-EAX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # prime the pump
-    # . get-char(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-char/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # get-num(in, out, err, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  _test-error-stream/imm32
-    68/push  _test-output-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-num/disp32
-    # registers except ESP may be clobbered at this point
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # check-ints-equal(*_test-output-stream->data, '3', msg)
-    # . . push args
-    68/push  "F - test-get-num-reads-single-digit"/imm32
-    68/push  0x33/imm32
-    b8/copy-to-EAX  _test-output-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
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-get-num-aborts-on-non-digit-in-Look:
-    # - check that get-num returns first character if it's a digit
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize 'in'
-    # . write(_test-stream, "3")
-    # . . push args
-    68/push  "3"/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
-    # initialize exit-descriptor 'ed' for the call to 'get-num' below
-    # . var ed/EAX : (address exit-descriptor)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # . tailor-exit-descriptor(ed, 16)
-    # . . push args
-    68/push  0x10/imm32/nbytes-of-args-for-get-num
-    50/push-EAX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # *don't* prime the pump
-    # get-num(in, out, err, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  _test-error-stream/imm32
-    68/push  _test-output-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-num/disp32
-    # registers except ESP may be clobbered at this point
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # check that get-num tried to call exit(1)
-    # . check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
-    # . . push args
-    68/push  "F - test-get-num-aborts-on-non-digit-in-Look"/imm32
-    68/push  2/imm32
-    # . . push ed->value
-    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
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-get-num-reads-multiple-digits:
-    # - check that get-num returns all initial digits until it encounters a non-digit
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize 'in'
-    # . write(_test-stream, "3456 x")
-    # . . push args
-    68/push  "3456"/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
-    # initialize exit-descriptor 'ed' for the call to 'get-num' below
-    # . var ed/EAX : (address exit-descriptor)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # . tailor-exit-descriptor(ed, 16)
-    # . . push args
-    68/push  0x10/imm32/nbytes-of-args-for-get-num
-    50/push-EAX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # prime the pump
-    # . get-char(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-char/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # get-num(in, out, err, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  _test-error-stream/imm32
-    68/push  _test-output-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-num/disp32
-    # registers except ESP may be clobbered at this point
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # check-ints-equal(*_test-output-stream->data, '3456', msg)
-    # . . push args
-    68/push  "F - test-get-num-reads-multiple-digits"/imm32
-    68/push  0x36353433/imm32
-    b8/copy-to-EAX  _test-output-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
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-get-num-reads-multiple-digits-followed-by-nondigit:
-    # - check that get-num returns all initial digits until it encounters a non-digit
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize 'in'
-    # . write(_test-stream, "3456 x")
-    # . . push args
-    68/push  "3456 x"/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
-    # initialize exit-descriptor 'ed' for the call to 'get-num' below
-    # . var ed/EAX : (address exit-descriptor)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # . tailor-exit-descriptor(ed, 16)
-    # . . push args
-    68/push  0x10/imm32/nbytes-of-args-for-get-num
-    50/push-EAX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # prime the pump
-    # . get-char(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-char/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # get-num(in, out, err, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  _test-error-stream/imm32
-    68/push  _test-output-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  get-num/disp32
-    # registers except ESP may be clobbered at this point
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # check-ints-equal(*_test-output-stream->data, '3456', msg)
-    # . . push args
-    68/push  "F - test-get-num-reads-multiple-digits-followed-by-nondigit"/imm32
-    68/push  0x36353433/imm32
-    b8/copy-to-EAX  _test-output-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
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-## helpers
-
-# write(f, "Error: "+s+" expected\n") then stop(ed, 1)
-expected:  # ed : (address exit-descriptor), f : fd or (address stream), s : (address array byte) -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write(f, "Error: ")
-    # . . push args
-    68/push  "Error: "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(f, s)
-    # . . 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)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(f, " expected\n")
-    # . . push args
-    68/push  " expected\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # stop(ed, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  stop/disp32
-    # should never get past this point
-$expected:dead-end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# read a byte from 'f', and save it in 'Look'
-get-char:  # f : (address buffered-file) -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    # EAX = read-byte-buffered(f)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # save EAX to Look
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy EAX to *Look
-$get-char:end:
-    # . restore registers
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-is-digit?:  # c : int -> EAX : boolean
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # EAX = false
-    b8/copy-to-EAX  0/imm32
-    # if (c < '0') return false
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x30/imm32        # compare *(EBP+8)
-    7c/jump-if-lesser  $is-digit?:end/disp8
-    # if (c > '9') return false
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x39/imm32        # compare *(EBP+8)
-    7f/jump-if-greater  $is-digit?:end/disp8
-    # otherwise return true
-    b8/copy-to-EAX  1/imm32
-$is-digit?:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data
-
-Look:  # (char with some extra padding)
-    0/imm32
-
-_test-output-stream:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    8/imm32
-    # data
-    00 00 00 00 00 00 00 00  # 8 bytes
-
-_test-error-stream:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    0x40/imm32
-    # data (4 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
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/apps/dquotes b/subx/apps/dquotes
deleted file mode 100755
index b7a4adfe..00000000
--- a/subx/apps/dquotes
+++ /dev/null
Binary files differdiff --git a/subx/apps/dquotes.subx b/subx/apps/dquotes.subx
deleted file mode 100644
index 6848ad19..00000000
--- a/subx/apps/dquotes.subx
+++ /dev/null
@@ -1,2757 +0,0 @@
-# Translate literal strings within double quotes.
-# Replace them with references to new variables in the data segment.
-#
-# To run (from the subx/ directory):
-#   $ ./subx translate *.subx apps/dquotes.subx -o apps/dquotes
-#   $ cat x
-#   == code
-#   ab "cd ef"/imm32
-#   $ cat x  |./subx run apps/dquotes
-#   == code
-#   ab __string1/imm32
-#   == data
-#   __string1:
-#     5/imm32
-#     0x63/c 0x64/d 0x20/  0x65/e 0x66/f
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # initialize heap
-    # . Heap = new-segment(Heap-size)
-    # . . push args
-    68/push  Heap/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-
-    # run tests if necessary, convert stdin if not
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # . argc > 1
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # . argv[1] == "test"
-    # . . push args
-    68/push  "test"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  kernel-string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check result
-    3d/compare-EAX-and  1/imm32
-    75/jump-if-not-equal  $run-main/disp8
-    # . run-tests()
-    e8/call  run-tests/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    eb/jump  $main:end/disp8
-$run-main:
-    # - otherwise convert stdin
-    # var ed/EAX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # configure ed to really exit()
-    # . ed->target = 0
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
-    # return convert(Stdin, 1/stdout, 2/stderr, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  Stderr/imm32
-    68/push  Stdout/imm32
-    68/push  Stdin/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # . syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-$main:end:
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# conceptual hierarchy within a line:
-#   line = words separated by ' ', maybe followed by comment starting with '#'
-#   word = datum until '/', then 0 or more metadata separated by '/'
-
-convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   var line = new-stream(512, 1)
-    #   var new-data-segment = new-stream(Heap, Segment-size, 1)
-    #   write(new-data-segment, "== data\n")
-    #   while true
-    #     clear-stream(line)
-    #     read-line-buffered(in, line)
-    #     if (line->write == 0) break               # end of file
-    #     while true
-    #       var word-slice = next-word-or-string(line)
-    #       if slice-empty?(word-slice)             # end of line
-    #         break
-    #       if slice-starts-with?(word-slice, "#")  # comment
-    #         continue
-    #       if slice-starts-with?(word-slice, '"')  # string literal <== what we're here for
-    #         process-string-literal(word-slice, out, new-data-segment)
-    #       else
-    #         write-slice-buffered(out, word-slice)
-    #       write(out, " ")
-    #     write(out, "\n\n")
-    #   write-stream-data(out, new-data-segment)
-    #   flush(out)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    53/push-EBX
-    56/push-ESI
-    57/push-EDI
-    # var line/ECX : (address stream byte) = stream(512)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
-    68/push  0x200/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var word-slice/EDX = {0, 0}
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # new-data-segment/EDI = new-stream(Heap, Segment-size, 1)
-    # . EAX = new-stream(Heap, Segment-size, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Segment-size/disp32               # push *Segment-size
-    68/push  Heap/imm32
-    # . . call
-    e8/call  new-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . EDI = EAX
-    89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDI
-    # write(new-data-segment, "== data\n")
-    # . . push args
-    68/push  "== data\n"/imm32
-    57/push-EDI
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:line-loop:
-    # clear-stream(line)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read-line-buffered(in, line)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-line-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:check0:
-    # if (line->write == 0) break
-    81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
-    0f 84/jump-if-equal  $convert:break/disp32
-$convert:word-loop:
-    # next-word-or-string(line, word-slice)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  next-word-or-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:check1:
-    # if (slice-empty?(word-slice)) break
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) break
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $convert:next-line/disp32
-$convert:check-for-comment:
-    # if (slice-starts-with?(word-slice, "#")) continue
-    # . start/ESI = word-slice->start
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           6/r32/ESI   .               .                 # copy *EDX to ESI
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/AL    .               .                 # copy byte at *ESI to AL
-    # . if (EAX == '#') continue
-    3d/compare-EAX-and  0x23/imm32/hash
-    74/jump-if-equal  $convert:word-loop/disp8
-$convert:check-for-string-literal:
-    3d/compare-EAX-and  0x22/imm32/dquote
-    75/jump-if-not-equal  $convert:regular-word/disp8
-$convert:string-literal:
-    # process-string-literal(word-slice, out, new-data-segment)
-    # . . push args
-    57/push-EDI
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    52/push-EDX
-    # . . call
-    e8/call  process-string-literal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # continue
-    eb/jump  $convert:next-word/disp8
-$convert:regular-word:
-    # write-slice-buffered(out, word-slice)
-    # . . push args
-    52/push-EDX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # fall through
-$convert:next-word:
-    # write-buffered(out, " ")
-    # . . push args
-    68/push  " "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # loop
-    eb/jump  $convert:word-loop/disp8
-$convert:next-line:
-    # write-buffered(out, "\n")
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # loop
-    e9/jump  $convert:line-loop/disp32
-$convert:break:
-    # write-stream-data(out, new-data-segment)
-    # . . push args
-    57/push-EDI
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$convert:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x214/imm32       # add to ESP
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# Write out 'string-literal' in a new format to 'out-segment', assign it a new
-# label, and write the new label out to 'out'.
-process-string-literal:  # string-literal : (address slice), out : (address buffered-file), out-segment : (address stream)
-    # pseudocode:
-    #   print(out-segment, "_string#{Next-string-literal}:\n")
-    #   emit-string-literal-data(out-segment, string-literal)
-    #   print(out, "_string#{Next-string-literal}")
-    #   emit-metadata(out, string-literal)
-    #   ++ *Next-string-literal
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    # var int32-stream/ECX = stream(10)  # number of decimal digits a 32-bit number can have
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa/imm32         # subtract from ESP
-    68/push  0xa/imm32/decimal-digits-in-32bit-number
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # print(out-segment, "_string#{Next-string-literal}:\n")
-    # . write(out-segment, "_string")
-    # . . push args
-    68/push  "_string"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . print-int32-decimal(out-segment, *Next-string-literal)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-string-literal/disp32        # push *Next-string-literal
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    # . . call
-    e8/call  print-int32-decimal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(out-segment, ":\n")
-    # . . push args
-    68/push  ":\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # emit-string-literal-data(out-segment, string-literal)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x8/disp8       .                 # push *(EBP+8)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    # . . call
-    e8/call  emit-string-literal-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(out-segment, "\n")
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # print(out, "_string#{Next-string-literal}")
-    # . write-buffered(out, "_string")
-    # . . push args
-    68/push  "_string"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . print-int32-decimal(int32-stream, *Next-string-literal)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-string-literal/disp32        # push *Next-string-literal
-    51/push-ECX
-    # . . call
-    e8/call  print-int32-decimal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write-stream-data(out, int32-stream)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # emit-metadata(out, string-literal)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit-metadata/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # ++ *Next-string-literal
-    ff          0/subop/increment   0/mod/indirect  5/rm32/.disp32            .             .           .           Next-string-literal/disp32        # increment *Num-test-failures
-$process-string-literal:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x16/imm32        # add to ESP
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-is-idempotent-by-default:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-input-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-input-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input (meta comments in parens)
-    #   # comment 1
-    #     # comment 2 indented
-    #   == code 0x1  (new segment)
-    #   # comment 3 inside a segment
-    #   1
-    #                         (empty line)
-    #   2 3 # comment 4 inline with other contents
-    #   == data 0x2  (new segment)
-    #   4 5/imm32
-    # . write(_test-input-stream, "# comment 1\n")
-    # . . push args
-    68/push  "# comment 1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "  # comment 2 indented\n")
-    # . . push args
-    68/push  "  # comment 2 indented\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== code 0x1\n")
-    # . . push args
-    68/push  "== code 0x1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "# comment 3 inside a segment\n")
-    # . . push args
-    68/push  "# comment 3 inside a segment\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "1\n")
-    # . . push args
-    68/push  "1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "\n")  # empty line
-    # . . push args
-    68/push  "\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "2 3 # comment 4 inline with other contents\n")
-    # . . push args
-    68/push  "2 3 # comment 4 inline with other contents\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== data 0x2\n")
-    # . . push args
-    68/push  "== data 0x2\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "4 5/imm32\n")
-    # . . push args
-    68/push  "4 5/imm32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert(_test-input-buffered-file, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-buffered-file/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check output
-    #     (comment dropped for now)
-    #     (comment dropped for now)
-    #   == code 0x1
-    #     (comment dropped for now)
-    #   1
-    #     (comment dropped for now)
-    #   2 3
-    #   == data 0x2
-    #   4 5/imm32
-    # We don't care right now what exactly happens to comments. Trailing spaces are also minor details.
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "", msg)
-    # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/0"/imm32
-    68/push  ""/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "", msg)
-    # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/1"/imm32
-    68/push  ""/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "== code 0x1 ", msg)
-    # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/2"/imm32
-    68/push  "== code 0x1 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "", msg)
-    # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/3"/imm32
-    68/push  ""/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "1 ", msg)
-    # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/4"/imm32
-    68/push  "1 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "", msg)
-    # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/5"/imm32
-    68/push  ""/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "2 3 ", msg)
-    # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/6"/imm32
-    68/push  "2 3 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "== data 0x2 ", msg)
-    # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/7"/imm32
-    68/push  "== data 0x2 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "4 5/imm32 ", msg)
-    # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/8"/imm32
-    68/push  "4 5/imm32 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-processes-string-literals:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-input-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-input-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input (meta comments in parens)
-    #   == code  (new segment)
-    #   1 "a"/x
-    #   2 "bc"/y
-    68/push  "== code 0x1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "1 \"a\"/x\n")
-    # . . push args
-    68/push  "1 \"a\"/x\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "2 \"bc\"/y\n")
-    # . . push args
-    68/push  "2 \"bc\"/y\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert(_test-input-buffered-file, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-buffered-file/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check output
-    #   == code 0x1
-    #   1 _string1/x
-    #   2 _string2/y
-    #   == data
-    #   _string1:
-    #   1/imm32 61/a
-    #   _string2:
-    #   2/imm32 62/b 63/c
-    # We don't care right now what exactly happens to comments. Trailing spaces are also minor details.
-    #
-    # Open question: how to make this check more robust.
-    # We don't actually care what the auto-generated string variables are
-    # called. We just want to make sure instructions using string literals
-    # switch to a string variable with the right value.
-    # (Modifying string literals completely off the radar for now.)
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "== code 0x1 ", msg)
-    # . . push args
-    68/push  "F - test-convert-processes-string-literals/0"/imm32
-    68/push  "== code 0x1 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "1 _string1/x ", msg)
-    # . . push args
-    68/push  "F - test-convert-processes-string-literals/1"/imm32
-    68/push  "1 _string1/x "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "2 _string2/y ", msg)
-    # . . push args
-    68/push  "F - test-convert-processes-string-literals/2"/imm32
-    68/push  "2 _string2/y "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "== data", msg)
-    # . . push args
-    68/push  "F - test-convert-processes-string-literals/3"/imm32
-    68/push  "== data"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "_string1: ", msg)
-    # . . push args
-    68/push  "F - test-convert-processes-string-literals/4"/imm32
-    68/push  "_string1:"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "1/imm32 61/a ", msg)
-    # . . push args
-    68/push  "F - test-convert-processes-string-literals/5"/imm32
-    68/push  "0x00000001/imm32 61/a "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "_string2: ", msg)
-    # . . push args
-    68/push  "F - test-convert-processes-string-literals/6"/imm32
-    68/push  "_string2:"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "2/imm32 62/b 63/c ", msg)
-    # . . push args
-    68/push  "F - test-convert-processes-string-literals/7"/imm32
-    68/push  "0x00000002/imm32 62/b 63/c "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# generate the data segment contents byte by byte for a given slice
-emit-string-literal-data:  # out : (address stream), word : (address slice)
-    # pseudocode
-    #   len = string-length-at-start-of-slice(word->start, word->end)
-    #   print(out, "#{len}/imm32 ")
-    #   curr = word->start
-    #   ++curr  # skip '"'
-    #   while true
-    #     if (curr >= word->end) break
-    #     c = *curr
-    #     if (c == '"') break
-    #     if (c == '\') {
-    #       ++curr
-    #       c = *curr
-    #       if (c == 'n')
-    #         c = newline
-    #     }
-    #     append-byte-hex(out, c)
-    #     if c is alphanumeric:
-    #       write(out, "/")
-    #       append-byte(out, c)
-    #     write(out, " ")
-    #     ++curr
-    #
-    # . prolog
-    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 = word
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # curr/EDX = word->start
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    # max/ESI = word->end
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           6/r32/ESI   4/disp8         .                 # copy *(ESI+4) to ESI
-$emit-string-literal-data:emit-length:
-    # len/EAX = string-length-at-start-of-slice(word->start, word->end)
-    # . . push args
-    56/push-ESI
-    52/push-EDX
-    # . . call
-    e8/call  string-length-at-start-of-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # print(out, "#{len}/imm32 ")
-    # . print-int32(out, len)
-    # . . push args
-    50/push-EAX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  print-int32/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(out, "/imm32 ")
-    # . . push args
-    68/push  "/imm32 "/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
-$emit-string-literal-data:loop-init:
-    # ++curr  # skip initial '"'
-    42/increment-EDX
-    # c/ECX = 0
-    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-$emit-string-literal-data:loop:
-    # if (curr >= max) break
-    39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           6/r32/ESI   .               .                 # compare EDX with ESI
-    0f 83/jump-if-greater-or-equal-unsigned  $emit-string-literal-data:end/disp32
-    # CL = *curr
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           1/r32/CL    .               .                 # copy byte at *EDX to CL
-    # if (c == '"') break
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x22/imm32/dquote # compare ECX
-    74/jump-if-equal  $emit-string-literal-data:end/disp8
-    # if (c != '\') goto emit
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x5c/imm32/backslash  # compare ECX
-    75/jump-if-not-equal  $emit-string-literal-data:emit/disp8
-    # ++curr
-    42/increment-EDX
-    # if (curr >= max) break
-    39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           6/r32/ESI   .               .                 # compare EDX with ESI
-    73/jump-if-greater-or-equal-unsigned  $emit-string-literal-data:end/disp8
-    # c = *curr
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           1/r32/CL    .               .                 # copy byte at *EDX to CL
-    # if (c == 'n') c = newline
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x6e/imm32/n      # compare ECX
-    75/jump-if-not-equal  $emit-string-literal-data:emit/disp8
-    b9/copy-to-ECX  0x0a/imm32/newline
-$emit-string-literal-data:emit:
-    # append-byte-hex(out, CL)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  append-byte-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (is-alphanumeric?(*curr)) print(out, "/#{*curr}")
-    # . EAX = is-alphanumeric?(CL)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  is-alphanumeric?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX == 0) goto char-done
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-string-literal-data:char-done/disp8
-    # . write(out, "/")
-    # . . push args
-    68/push  Slash/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
-    # . append-byte(out, *curr)
-    # . . push args
-    51/push-ECX
-    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
-$emit-string-literal-data:char-done:
-    # write(out, " ")
-    # . . push args
-    68/push  Space/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
-    # ++curr
-    42/increment-EDX
-    e9/jump $emit-string-literal-data:loop/disp32
-$emit-string-literal-data:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-is-alphanumeric?:  # c : int -> EAX : boolean
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # EAX = c
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
-    # if (EAX < '0') return false
-    3d/compare-EAX-with  0x30/imm32/0
-    7c/jump-if-lesser  $is-alphanumeric?:false/disp8
-    # if (EAX <= '9') return true
-    3d/compare-EAX-with  0x39/imm32/9
-    7e/jump-if-lesser-or-equal  $is-alphanumeric?:true/disp8
-    # if (EAX < 'A') return false
-    3d/compare-EAX-with  0x41/imm32/A
-    7c/jump-if-lesser  $is-alphanumeric?:false/disp8
-    # if (EAX <= 'Z') return true
-    3d/compare-EAX-with  0x5a/imm32/Z
-    7e/jump-if-lesser-or-equal  $is-alphanumeric?:true/disp8
-    # if (EAX < 'a') return false
-    3d/compare-EAX-with  0x61/imm32/a
-    7c/jump-if-lesser  $is-alphanumeric?:false/disp8
-    # if (EAX <= 'z') return true
-    3d/compare-EAX-with  0x7a/imm32/z
-    7e/jump-if-lesser-or-equal  $is-alphanumeric?:true/disp8
-    # return false
-$is-alphanumeric?:false:
-    b8/copy-to-EAX  0/imm32/false
-    eb/jump  $is-alphanumeric?:end/disp8
-$is-alphanumeric?:true:
-    b8/copy-to-EAX  1/imm32/true
-$is-alphanumeric?:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-string-literal-data:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = '"abc"/d'
-    68/push  _test-slice-abc-limit/imm32
-    68/push  _test-slice-abc/imm32
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-string-literal-data(_test-output-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  emit-string-literal-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "3/imm32 61/a 62/b 63/c ", msg)
-    # . . push args
-    68/push  "F - test-emit-string-literal-data"/imm32
-    68/push  "0x00000003/imm32 61/a 62/b 63/c "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-string-literal-data-empty:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = '""'
-    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
-    # emit-string-literal-data(_test-output-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  emit-string-literal-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "0/imm32 ", msg)
-    # . . push args
-    68/push  "F - test-emit-string-literal-data-empty"/imm32
-    68/push  "0x00000000/imm32 "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# just to keep things simple
-test-emit-string-literal-data-no-metadata-for-non-alphanumerics:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = '"a b"'
-    68/push  _test-slice-a-space-b-limit/imm32
-    68/push  _test-slice-a-space-b/imm32
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-string-literal-data(_test-output-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  emit-string-literal-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "3/imm32 61/a 20 62/b ", msg)  # ideally we'd like to say '20/space' but that requires managing names for codepoints
-    # . . push args
-    68/push  "F - test-emit-string-literal-data-no-metadata-for-non-alphanumerics"/imm32
-    68/push  "0x00000003/imm32 61/a 20 62/b "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-string-literal-data-handles-escape-sequences:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = '"a\"b"'
-    68/push  _test-slice-a-dquote-b-limit/imm32
-    68/push  _test-slice-a-dquote-b/imm32
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-string-literal-data(_test-output-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  emit-string-literal-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "3/imm32 61/a 22 62/b ", msg)
-    # . . push args
-    68/push  "F - test-emit-string-literal-data-handles-escape-sequences"/imm32
-    68/push  "0x00000003/imm32 61/a 22 62/b "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-string-literal-data-handles-newline-escape:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = '"a\nb"'
-    68/push  _test-slice-a-newline-b-limit/imm32
-    68/push  _test-slice-a-newline-b/imm32
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-string-literal-data(_test-output-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  emit-string-literal-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "3/imm32 61/a 0a 62/b ", msg)
-    # . . push args
-    68/push  "F - test-emit-string-literal-data-handles-newline-escape"/imm32
-    68/push  "0x00000003/imm32 61/a 0a 62/b "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# emit everything from a word except the initial datum
-emit-metadata:  # out : (address buffered-file), word : (address slice)
-    # pseudocode
-    #   var slice = {0, word->end}
-    #   curr = word->start
-    #   if *curr == '"'
-    #     curr = skip-string-in-slice(curr, word->end)
-    #   else
-    #     while true
-    #       if curr == word->end
-    #         return
-    #       if *curr == '/'
-    #         break
-    #       ++curr
-    #   slice->start = curr
-    #   write-slice-buffered(out, slice)
-    #
-    # . prolog
-    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 = word
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # curr/ECX = word->start
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    # end/EDX = word->end
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ESI+4) to EDX
-    # var slice/EBX = {0, end}
-    52/push-EDX
-    68/push  0/imm32
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
-    # EAX = 0
-    b8/copy-to-EAX  0/imm32
-$emit-metadata:check-for-string-literal:
-    # -  if (*curr == '"') curr = skip-string-in-slice(curr, end)
-    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
-    3d/compare-EAX-and  0x22/imm32/dquote
-    75/jump-if-not-equal  $emit-metadata:skip-datum-loop/disp8
-$emit-metadata:skip-string-literal:
-    # . EAX = skip-string-in-slice(curr, end)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . 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
-    # . curr = EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
-    eb/jump  $emit-metadata:emit/disp8
-$emit-metadata:skip-datum-loop:
-    # - otherwise scan for '/'
-    # if (curr == end) return
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX and EDX
-    74/jump-if-equal  $emit-metadata:end/disp8
-    # if (*curr == '/') break
-    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
-    3d/compare-EAX-and  0x2f/imm32/slash
-    74/jump-if-equal  $emit-metadata:emit/disp8
-    # ++curr
-    41/increment-ECX
-    eb/jump  $emit-metadata:skip-datum-loop/disp8
-$emit-metadata:emit:
-    # slice->start = ECX
-    89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EBX
-    # write-slice-buffered(out, slice)
-    # . . push args
-    53/push-EBX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           8/imm32      .                    # add to ESP
-$emit-metadata:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           8/imm32      .                    # add to ESP
-    # . restore registers
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-metadata:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (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
-    # var slice/ECX = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-metadata(_test-output-buffered-file, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-metadata/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream, "/def", msg)  # important that there's no leading space
-    # . . push args
-    68/push  "F - test-emit-metadata"/imm32
-    68/push  "/def"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-metadata-none:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-metadata(_test-output-buffered-file, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-metadata/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream, "", msg)
-    # . . push args
-    68/push  "F - test-emit-metadata-none"/imm32
-    68/push  ""/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-metadata-multiple:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (EAX..ECX) = "abc/def/ghi"
-    b8/copy-to-EAX  "abc/def/ghi"/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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-metadata(_test-output-buffered-file, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-metadata/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream, "/def/ghi", msg)  # important that there's no leading space
-    # . . push args
-    68/push  "F - test-emit-metadata-multiple"/imm32
-    68/push  "/def/ghi"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-metadata-when-no-datum:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # var slice/ECX = "/abc"
-    b8/copy-to-EAX  "/abc"/imm32
-    # . push end/ECX
-    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 curr/EAX
-    05/add-to-EAX  4/imm32
-    50/push-EAX
-    # . save stack pointer
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-metadata(_test-output-buffered-file, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-metadata/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream, "/abc", msg)  # nothing skipped
-    # . . push args
-    68/push  "F - test-emit-metadata-when-no-datum"/imm32
-    68/push  "/abc"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-metadata-in-string-literal:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # var slice/ECX = "\"abc/def\"/ghi"
-    68/push  _test-slice-literal-string-with-limit/imm32
-    68/push  _test-slice-literal-string/imm32/start
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-metadata(_test-output-buffered-file, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-metadata/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # check-stream-equal(_test-output-stream, "/ghi", msg)  # important that there's no leading space
-    # . . push args
-    68/push  "F - test-emit-metadata-in-string-literal"/imm32
-    68/push  "/ghi"/imm32
-    68/push  _test-output-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
-    # . epilog
-    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
-# return empty string on reaching end of file
-next-word-or-string:  # line : (address stream byte), out : (address slice)
-    # . prolog
-    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(line, ' ')
-    # . . push args
-    68/push  0x20/imm32/space
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  skip-chars-matching/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$next-word-or-string: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-lesser  $next-word-or-string:check-for-comment/disp8
-    # . return out = {0, 0}
-    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-or-string:end/disp8
-$next-word-or-string: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] == '#'
-    # . 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-not-equal  $next-word-or-string:check-for-string-literal/disp8
-$next-word-or-string: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  # skip rest of line
-    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-or-string:end/disp8
-$next-word-or-string:check-for-string-literal:
-    # if line->data[line->read] == '"'
-    # . 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  0x22/imm32/dquote
-    75/jump-if-not-equal  $next-word-or-string:regular-word/disp8
-$next-word-or-string:string-literal:
-    # skip-string(line)
-    # . . push args
-    56/push-ESI
-    # . . call
-    e8/call  skip-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # fall through
-$next-word-or-string:regular-word:
-    # 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-or-string:end:
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-word-or-string:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = {0, 0}
-    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-input-stream, "  ab")
-    # . . push args
-    68/push  "  ab"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # next-word-or-string(_test-input-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  next-word-or-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(_test-input-stream->read, 4, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string/updates-stream-read-correctly"/imm32
-    68/push  4/imm32
-    b8/copy-to-EAX  _test-input-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
-    # check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
-    # . check-ints-equal(slice->start - _test-input-stream, 14, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string: start"/imm32
-    68/push  0xe/imm32
-    # . . push slice->start - _test-input-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-input-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-input-stream->data, 4, msg)
-    # . check-ints-equal(slice->end - _test-input-stream, 16, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string: end"/imm32
-    68/push  0x10/imm32
-    # . . push slice->end - _test-input-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-input-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-word-or-string-returns-whole-comment:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = {0, 0}
-    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-input-stream, "  # a")
-    # . . push args
-    68/push  "  # a"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # next-word-or-string(_test-input-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  next-word-or-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(_test-input-stream->read, 5, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string-returns-whole-comment/updates-stream-read-correctly"/imm32
-    68/push  5/imm32
-    b8/copy-to-EAX  _test-input-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
-    # check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
-    # . check-ints-equal(slice->start - _test-input-stream, 14, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string-returns-whole-comment: start"/imm32
-    68/push  0xe/imm32
-    # . . push slice->start - _test-input-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-input-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-input-stream->data, 5, msg)
-    # . check-ints-equal(slice->end - _test-input-stream, 17, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string-returns-whole-comment: end"/imm32
-    68/push  0x11/imm32
-    # . . push slice->end - _test-input-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-input-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-word-or-string-returns-empty-string-on-eof:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = {0, 0}
-    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-input-stream
-    # next-word-or-string(_test-input-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  next-word-or-string/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-or-string-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-word-or-string-returns-whole-string:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = {0, 0}
-    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-input-stream, " \"a b\"/imm32 ")
-    # . . push args
-    68/push  " \"a b\"/imm32 "/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # next-word-or-string(_test-input-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  next-word-or-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
-    # . check-ints-equal(slice->start - _test-input-stream, 13, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string-returns-whole-string: start"/imm32
-    68/push  0xd/imm32
-    # . . push slice->start - _test-input-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-input-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-input-stream->data, 12, msg)
-    # . check-ints-equal(slice->end - _test-input-stream, 24, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string-returns-whole-string: end"/imm32
-    68/push  0x18/imm32
-    # . . push slice->end - _test-input-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-input-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-word-or-string-returns-string-with-escapes:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var slice/ECX = {0, 0}
-    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-input-stream, " \"a\\\"b\"/x")
-    # . . push args
-    68/push  " \"a\\\"b\"/x"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # next-word-or-string(_test-input-stream, slice)
-    # . . push args
-    51/push-ECX
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  next-word-or-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
-    # . check-ints-equal(slice->start - _test-input-stream, 13, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string-returns-string-with-escapes: start"/imm32
-    68/push  0xd/imm32
-    # . . push slice->start - _test-input-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-input-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-input-stream->data, 9, msg)
-    # . check-ints-equal(slice->end - _test-input-stream, 21, msg)
-    # . . push args
-    68/push  "F - test-next-word-or-string-returns-string-with-escapes: end"/imm32
-    68/push  0x15/imm32
-    # . . push slice->end - _test-input-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-input-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
-    # . epilog
-    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 end of string literal surrounded by double quotes
-# line->read must start out at a double-quote
-skip-string:  # line : (address stream)
-    # . prolog
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-skip-string:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . write(_test-input-stream, "\"abc\" def")
-    # .                   indices:  0123 45
-    # . . push args
-    68/push  "\"abc\" def"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # precondition: line->read == 0
-    # . . push args
-    68/push  "F - test-skip-string/precondition"/imm32
-    68/push  0/imm32
-    b8/copy-to-EAX  _test-input-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-input-stream)
-    # . . push args
-    68/push  _test-input-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-input-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
-    # . epilog
-    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:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . write(_test-input-stream, "\"a b\"/yz")
-    # .                   indices:  0123 45
-    # . . push args
-    68/push  "\"a b\"/yz"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # 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-input-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-input-stream)
-    # . . push args
-    68/push  _test-input-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-input-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
-    # . epilog
-    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:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . write(_test-input-stream, "\"a\\\"b\"/yz")
-    # .                   indices:  01 2 34 56
-    # . . push args
-    68/push  "\"a\\\"b\"/yz"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # 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-input-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-input-stream)
-    # . . push args
-    68/push  _test-input-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-input-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
-    # . epilog
-    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:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . write(_test-input-stream, "0 \"a\\\"b\"/yz")
-    # .                   indices:  01 2 34 56
-    # . . push args
-    68/push  "0 \"a\\\"b\"/yz"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # precondition: line->read == 2
-    c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         2/imm32           # copy to *(EAX+4)
-    # skip-string(_test-input-stream)
-    # . . push args
-    68/push  _test-input-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-input-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
-    # . epilog
-    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 : (address byte), end : (address byte) -> new_curr/EAX
-    # . prolog
-    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
-    # EAX = 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-greater-unsigned-or-equal  $skip-string-in-slice:return-curr/disp8
-    # AL = *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 (EAX == '"') break
-    3d/compare-EAX-and  0x22/imm32/double-quote
-    74/jump-if-equal  $skip-string-in-slice:break/disp8
-$skip-string-in-slice:check-for-escape:
-    # if (EAX == '\') escape next char
-    3d/compare-EAX-and  0x5c/imm32/backslash
-    75/jump-if-not-equal  $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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    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:
-    # . prolog
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-string-length-at-start-of-slice:  # curr : (address byte), end : (address byte) -> length/EAX
-    # . prolog
-    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
-    # length/EAX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    # EBX = 0
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-    # skip initial dquote
-    41/increment-ECX
-$string-length-at-start-of-slice:loop:
-    # if (curr >= end) return length
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    73/jump-if-greater-unsigned-or-equal  $string-length-at-start-of-slice:end/disp8
-    # BL = *curr
-    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           3/r32/BL    .               .                 # copy byte at *ECX to BL
-$string-length-at-start-of-slice:dquote:
-    # if (EBX == '"') break
-    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0x22/imm32/dquote # compare EBX
-    74/jump-if-equal  $string-length-at-start-of-slice:end/disp8
-$string-length-at-start-of-slice:check-for-escape:
-    # if (EBX == '\') escape next char
-    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0x5c/imm32/backslash # compare EBX
-    75/jump-if-not-equal  $string-length-at-start-of-slice:continue/disp8
-$string-length-at-start-of-slice:escape:
-    # increment curr but not result
-    41/increment-ECX
-$string-length-at-start-of-slice:continue:
-    # ++result
-    40/increment-EAX
-    # ++curr
-    41/increment-ECX
-    eb/jump  $string-length-at-start-of-slice:loop/disp8
-$string-length-at-start-of-slice:end:
-    # . restore registers
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-string-length-at-start-of-slice:
-    # . prolog
-    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 = string-length-at-start-of-slice(EAX, ECX)
-    # . . push args
-    51/push-ECX
-    50/push-EAX
-    # . . call
-    e8/call  string-length-at-start-of-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(EAX, 3, msg)
-    # . . push args
-    68/push  "F - test-string-length-at-start-of-slice"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-string-length-at-start-of-slice-escaped:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup: (EAX..ECX) = "\"ab\\c\" def"
-    b8/copy-to-EAX  "\"ab\\c\" 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 = string-length-at-start-of-slice(EAX, ECX)
-    # . . push args
-    51/push-ECX
-    50/push-EAX
-    # . . call
-    e8/call  string-length-at-start-of-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(EAX, 3, msg)
-    # . . push args
-    68/push  "F - test-string-length-at-start-of-slice-escaped"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data
-
-Next-string-literal:  # tracks the next auto-generated variable name
-  1/imm32
-
-# length-prefixed string containing just a single space
-Space:
-    # size
-    1/imm32
-    # data
-    20/space
-
-# length-prefixed string containing just a single slash
-Slash:
-    # size
-    1/imm32
-    # data
-    2f/slash
-
-_test-slice-abc:
-  22/dquote 61/a 62/b 63/c 22/dquote  # "abc"
-  2f/slash 64/d
-_test-slice-abc-limit:
-
-_test-slice-a-space-b:
-  22/dquote 61/a 20/space 62/b 22/dquote  # "a b"
-_test-slice-a-space-b-limit:
-
-_test-slice-a-dquote-b:
-  22/dquote 61/a 5c/backslash 22/dquote 62/b 22/dquote  # "a\"b"
-_test-slice-a-dquote-b-limit:
-
-_test-slice-a-newline-b:
-  22/dquote 61/a 5c/backslash 6e/n 62/b 22/dquote  # "a\nb"
-_test-slice-a-newline-b-limit:
-
-# "abc/def"/ghi
-_test-slice-literal-string:
-  22/dquote
-  61/a 62/b 63/c  # abc
-  2f/slash 64/d 65/e 66/f  # /def
-  22/dquote
-  2f/slash 67/g 68/h 69/i  # /ghi
-_test-slice-literal-string-with-limit:
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/apps/factorial b/subx/apps/factorial
deleted file mode 100755
index b82be120..00000000
--- a/subx/apps/factorial
+++ /dev/null
Binary files differdiff --git a/subx/apps/factorial.subx b/subx/apps/factorial.subx
deleted file mode 100644
index e4b7a057..00000000
--- a/subx/apps/factorial.subx
+++ /dev/null
@@ -1,117 +0,0 @@
-## compute the factorial of 5, and return the result in the exit code
-#
-# To run (from the subx directory):
-#   $ ./subx translate apps/factorial.subx -o apps/factorial
-#   $ ./subx run apps/factorial
-# Expected result:
-#   $ echo $?
-#   120
-#
-# You can also run the automated test suite:
-#   $ ./subx run apps/factorial test
-# Expected output:
-#   ........
-# Every '.' indicates a passing test. Failing tests get a 'F'.
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # run tests if necessary, compute `factorial(5)` if not
-    # initialize heap
-    # . Heap = new-segment(64KB)
-    # . . push args
-    68/push  Heap/imm32
-    68/push  0x10000/imm32/64KB
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # . argc > 1
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # . argv[1] == "test"
-    # . . push args
-    68/push  "test"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  kernel-string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check result
-    3d/compare-EAX-and  1/imm32
-    75/jump-if-not-equal  $run-main/disp8
-    # . run-tests()
-    e8/call  run-tests/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Num-test-failures/disp32          # copy *Num-test-failures to EAX
-    eb/jump  $main:end/disp8  # where EAX will get copied to EBX
-$run-main:
-    # - otherwise return factorial(5)
-    # . . push args
-    68/push  5/imm32
-    # . . call
-    e8/call  factorial/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$main:end:
-    # syscall(exit, EAX)
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-factorial:  # n : int -> int/EAX
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    53/push-EBX
-    # EAX = 1 (base case)
-    b8/copy-to-EAX  1/imm32
-    # if (n <= 1) return
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         1/imm32           # compare *(EBP+8)
-    7e/jump-if-<=  $factorial:end/disp8
-    # EBX = n-1
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         3/r32/EBX   8/disp8         .                 # copy *(EBP+8) to EBX
-    81          5/subop/subtract    3/mod/direct    3/rm32/EBX    .           .             .           .           .               1/imm32           # subtract from EBX
-    # EAX = factorial(n-1)
-    # . . push args
-    53/push-EBX
-    # . . call
-    e8/call  factorial/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # return n * factorial(n-1)
-    f7          4/subop/multiply    1/mod/*+disp8   5/rm32/EBP    .           .                                     8/disp8         .                 # multiply *(EBP+8) into EAX
-    # TODO: check for overflow
-$factorial:end:
-    # . epilog
-    5b/pop-to-EBX
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-factorial:
-    # factorial(5)
-    # . . push args
-    68/push  5/imm32
-    # . . call
-    e8/call  factorial/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 120, msg)
-    # . . push args
-    68/push  "F - test-factorial"/imm32
-    68/push  0x78/imm32/expected-120
-    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
-    # end
-    c3/return
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/apps/handle b/subx/apps/handle
deleted file mode 100755
index b6794474..00000000
--- a/subx/apps/handle
+++ /dev/null
Binary files differdiff --git a/subx/apps/handle.subx b/subx/apps/handle.subx
deleted file mode 100644
index ba824f85..00000000
--- a/subx/apps/handle.subx
+++ /dev/null
@@ -1,412 +0,0 @@
-# A sketch of Mu-style handles or kinda-safe pointers, that add a modicum of
-# checking to dynamically allocated memory.
-#
-# This approach avoids using 'allocate' directly in favor of two primitives:
-#   - 'new', which allocates some space (the 'payload'), stores the address
-#     along with an opaque 'alloc id' in a 'handle', and prepends the same
-#     alloc id to the payload.
-#   - 'lookup', which checks that the alloc id at the start of a handle matches
-#     the alloc id at the start of the payload before returning the address.
-#
-# Layout of a handle:
-#   offset 0: alloc id
-#   offset 4: address
-#
-# To run (from the subx directory):
-#   $ ./subx translate *.subx apps/handle.subx -o apps/handle
-#   $ ./subx run apps/handle
-# Expected result is a successful lookup followed by a hard abort:
-#   lookup succeeded
-#   lookup failed
-# (This file is a prototype. The 'tests' in it aren't real; failures are
-# expected.)
-
-== 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
-
-# no Entry; the standard library runs all tests by default
-
-new:  # ad : (address allocation-descriptor), n : int, out : (address handle)
-    # . prolog
-    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+4
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy *(EBP+12) to ECX
-    81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm32           # add to ECX
-    # EAX = allocate(ad, ECX)
-    # . . push args
-    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    .           .             .           .           .               8/imm32           # add to ESP
-    # EDX = out
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0x10/disp8      .                 # copy *(EBP+16) to EDX
-    # out->address = EAX
-    89/copy                         1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDX+4)
-    # if (EAX == 0) out->alloc_id = 0, return
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $new:continue/disp8
-    c7          0/subop/copy        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               0/imm32           # copy to *EDX
-    eb/jump  $new:end/disp8
-$new:continue:
-    # otherwise:
-    # ECX = *Next-alloc-id
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           1/r32/ECX   Next-alloc-id/disp32              # copy *Next-alloc-id to ECX
-    # *EAX = *Next-alloc-id/ECX
-    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EAX
-    # out->alloc_id = *Next-alloc-id
-    89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EDX
-    # increment *Next-alloc-id
-    ff          0/subop/increment   0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # increment *Next-alloc-id
-$new:end:
-    # . restore registers
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-new:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var heap/EDX : (address allocation-descriptor) = {0, 0}
-    68/push  0/imm32/limit
-    68/push  0/imm32/curr
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # heap = new-segment(512)
-    # . . push args
-    52/push-EDX
-    68/push  0x200/imm32
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # *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
-    # var handle/ECX = {0, 0}
-    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
-    # new(heap, 2, handle/ECX)
-    # . . push args
-    51/push-ECX
-    68/push  2/imm32/size
-    52/push-EDX
-    # . . call
-    e8/call  new/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(handle->alloc_id, 0x34, msg)
-    # . . push args
-    68/push  "F - test-new: alloc id of handle"/imm32
-    68/push  0x34/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
-    # check-ints-equal(*handle->address, 0x34, msg)
-    # . . push args
-    68/push  "F - test-new: alloc id of payload"/imm32
-    68/push  0x34/imm32
-    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
-    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(*Next-alloc-id, 0x35)
-    # . . push args
-    68/push  "F - test-new: next alloc id"/imm32
-    68/push  0x35/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # copy to *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
-    # clean up
-    # . *Next-alloc-id = 1
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-_pending-test-new-failure:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . *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
-    # define an allocation-descriptor with no space left
-    # . var ad/EAX : (address allocation-descriptor) = {0x10, 0x10}
-    68/push  0x10/imm32/limit
-    68/push  0x10/imm32/curr
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # . var handle/ECX = {random, random}
-    68/push  1234/imm32/address
-    68/push  5678/imm32/alloc-id
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # try to allocate
-    # . new(ad, 2, handle/ECX)
-    # . . push args
-    51/push-ECX
-    68/push  2/imm32/size
-    50/push-EAX
-    # . . call
-    e8/call  new/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # handle should be cleared
-    # . check-ints-equal(handle->alloc_id, 0, msg)
-    # . . push args
-    68/push  "F - test-new-failure: alloc id of handle"/imm32
-    68/push  0/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
-    # . check-ints-equal(handle->address, 0, msg)
-    # . . push args
-    68/push  "F - test-new-failure: address of handle"/imm32
-    68/push  0/imm32
-    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
-    # Next-alloc-id should be unmodified
-    # . check-ints-equal(*Next-alloc-id, 0x34)
-    # . . push args
-    68/push  "F - test-new-failure: next alloc id"/imm32
-    68/push  0x34/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # copy to *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
-    # clean up
-    # . *Next-alloc-id = 1
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
-    # . epilog
-    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) -> EAX : (address T)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - as a proof of concept for future inlining, uses no general-purpose registers besides the output (EAX)
-    # EAX = handle
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
-    # - inline {
-    # push handle->alloc_id
-    ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
-    # EAX = handle->address (payload)
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # copy *(EAX+4) to EAX
-    # push handle->address
-    50/push-EAX
-    # EAX = payload->alloc_id
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # copy *EAX to EAX
-    # if (EAX != handle->alloc_id) abort
-    39/compare                      1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none  .           0/r32/EAX   4/disp8         .                 # compare *(ESP+4) and EAX
-    75/jump-if-not-equal  $lookup:abort/disp8
-    # EAX = pop handle->address
-    58/pop-to-EAX
-    # discard handle->alloc_id
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # add 4
-    05/add-to-EAX  4/imm32
-    # - }
-    # - alternative consuming a second register {
-#?     # ECX = handle->alloc_id
-#?     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
-#?     # EAX = handle->address (payload)
-#?     8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(EAX+4) to EAX
-#?     # if (ECX != *EAX) abort
-#?     39/compare                      0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare *EAX and ECX
-#?     75/jump-if-not-equal  $lookup:abort/disp8
-#?     # add 4 to EAX
-#?     05/add-to-EAX  4/imm32
-    # - }
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$lookup:abort:
-    # . _write(2/stderr, msg)
-    # . . push args
-    68/push  "lookup failed\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32/exit-status
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-test-lookup-success:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    # var heap/EBX : (address allocation-descriptor) = {0, 0}
-    68/push  0/imm32/limit
-    68/push  0/imm32/curr
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
-    # heap = new-segment(512)
-    # . . push args
-    53/push-EBX
-    68/push  0x200/imm32
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # var handle/ECX = {0, 0}
-    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 = heap->curr
-    8b/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           2/r32/EDX   .               .                 # copy *EBX to EDX
-    # new(heap, 2, handle)
-    # . . push args
-    51/push-ECX
-    68/push  2/imm32/size
-    53/push-EBX
-    # . . call
-    e8/call  new/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # EAX = lookup(handle)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  lookup/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/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
-    # . *Next-alloc-id = 1
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
-    # write(2/stderr, "lookup succeeded\n")
-    # . . push args
-    68/push  "lookup succeeded\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
-    # . restore registers
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-lookup-failure:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var heap/ESI : (address allocation-descriptor) = {0, 0}
-    68/push  0/imm32/limit
-    68/push  0/imm32/curr
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
-    # heap = new-segment(512)
-    # . . push args
-    56/push-ESI
-    68/push  0x200/imm32
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # var h1/ECX = {0, 0}
-    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 = heap->curr
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           3/r32/EBX   .               .                 # copy *ESI to EBX
-    # first allocation, to h1
-    # . new(heap, 2, h1)
-    # . . push args
-    51/push-ECX
-    68/push  2/imm32/size
-    56/push-ESI
-    # . . call
-    e8/call  new/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # reset heap->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 = {0, 0}
-    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
-    # . new(heap, 2, h2)
-    # . . push args
-    52/push-EDX
-    68/push  2/imm32/size
-    56/push-ESI
-    # . . call
-    e8/call  new/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
-    51/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    .           .             .           .           .               4/imm32           # add to ESP
-    # clean up
-    # . *Next-alloc-id = 1
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data
-
-# Monotonically increasing counter for calls to 'new'
-Next-alloc-id:
-    1/imm32
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/apps/hex b/subx/apps/hex
deleted file mode 100755
index 84575cfd..00000000
--- a/subx/apps/hex
+++ /dev/null
Binary files differdiff --git a/subx/apps/hex.subx b/subx/apps/hex.subx
deleted file mode 100644
index a24c5a2e..00000000
--- a/subx/apps/hex.subx
+++ /dev/null
@@ -1,1515 +0,0 @@
-# Read a text file containing whitespace-separated pairs of ascii hex bytes
-# from stdin, and convert them into binary bytes (octets) on stdout. Ignore
-# comments between '#' and newline.
-#
-# To run (from the subx/ directory):
-#   $ ./subx translate *.subx apps/hex.subx -o apps/hex
-#   $ echo '80 81 82  # comment'  |./subx run apps/hex  |xxd -
-# Expected output:
-#   00000000: 8081 82
-#
-# Only hex bytes and comments are permitted. Outside of comments all words
-# must be exactly 2 characters long and contain only characters [0-9a-f]. No
-# uppercase hex.
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # run tests if necessary, convert stdin if not
-    # initialize heap
-    # . Heap = new-segment(64KB)
-    # . . push args
-    68/push  Heap/imm32
-    68/push  0x10000/imm32/64KB
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # . argc > 1
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # . argv[1] == "test"
-    # . . push args
-    68/push  "test"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  kernel-string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check result
-    3d/compare-EAX-and  1/imm32
-    75/jump-if-not-equal  $run-main/disp8
-    # . run-tests()
-    e8/call  run-tests/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    eb/jump  $main:end/disp8
-$run-main:
-    # - otherwise convert stdin
-    # var ed/EAX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # configure ed to really exit()
-    # . ed->target = 0
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
-    # return convert(Stdin, 1/stdout, 2/stderr, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  Stderr/imm32
-    68/push  Stdout/imm32
-    68/push  Stdin/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # . syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-$main:end:
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# the main entry point
-convert:  # in : (address buffered-file), out : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> <void>
-    # pseudocode:
-    #   while true
-    #     EAX = convert-next-octet(in, err, ed)
-    #     if (EAX == Eof) break
-    #     write-byte-buffered(out, AL)
-    #   flush(out)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-$convert:loop:
-    # EAX = convert-next-octet(in, err, ed)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    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    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  convert-next-octet/disp32
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # if (EAX == Eof) break
-    3d/compare-EAX-and  0xffffffff/imm32/Eof
-    74/jump-if-equal  $convert:loop-end/disp8
-    # write-byte-buffered(out, AL)
-    # . . push args
-    50/push-EAX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # loop
-    eb/jump  $convert:loop/disp8
-$convert:loop-end:
-    # flush(out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$convert:end:
-    # . restore registers
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# read bytes from 'in' until a sequence of two lowercase hex (0-9, a-f) bytes
-# skip spaces and newlines
-# on '#' skip bytes until newline
-# raise an error and abort on all other unexpected bytes
-# return in EAX an _octet_ containing the binary value of the two hex characters
-# return Eof on reaching end of file
-convert-next-octet:  # in : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> byte-or-Eof/EAX
-    # pseudocode:
-    #   EAX = scan-next-byte(in, err, ed)
-    #   if (EAX == Eof) return
-    #   ECX = from-hex-char(EAX)
-    #   EAX = scan-next-byte(in, err, ed)
-    #   if (EAX == Eof) error("partial byte found.")
-    #   EAX = from-hex-char(EAX)
-    #   EAX = (ECX << 4) | EAX
-    #   return
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    # EAX = scan-next-byte(in, err, ed)
-    # . . 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  scan-next-byte/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # if (EAX == Eof) return
-    3d/compare-EAX-and  0xffffffff/imm32/Eof
-    74/jump-if-equal  $convert-next-octet:end/disp8
-    # EAX = from-hex-char(EAX)
-    e8/call from-hex-char/disp32
-    # ECX = EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
-    # EAX = scan-next-byte(in, err, ed)
-    # . . 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  scan-next-byte/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # if (EAX == Eof) error(ed, err, "partial byte found.")
-    3d/compare-EAX-and  0xffffffff/imm32/Eof
-    75/jump-if-not-equal  $convert-next-octet:convert/disp8
-    # . error-byte(ed, err, msg, '.')  # reusing error-byte to avoid creating _yet_ another helper
-    # . . push args
-    68/push  0x2e/imm32/period/dummy
-    68/push  "convert-next-octet: partial byte found"/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    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    # . . call
-    e8/call  error-byte/disp32  # never returns
-$convert-next-octet:convert:
-    # EAX = from-hex-char(EAX)
-    e8/call from-hex-char/disp32
-    # EAX = (ECX << 4) | EAX
-    # . ECX <<= 4
-    c1/shift    4/subop/left        3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm8            # shift ECX left by 4 bits
-    # . EAX |= ECX
-    09/or                           3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # EAX = bitwise OR with ECX
-$convert-next-octet:end:
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-next-octet:
-    # - check that the first two bytes of the input are assembled into the resulting octet
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to "abc"
-    # . 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
-    # initialize exit-descriptor 'ed' for the call to 'convert-next-octet' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-convert-next-octet
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = convert-next-octet(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  convert-next-octet/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to convert-next-octet
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that convert-next-octet didn't abort
-    # . check-ints-equal(ed->value, 0, msg)
-    # . . push args
-    68/push  "F - test-convert-next-octet: unexpected abort"/imm32
-    68/push  0/imm32
-    # . . push ed->value
-    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
-    # return if abort
-    81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
-    75/jump-if-not-equal  $test-convert-next-octet:end/disp8
-    # check-ints-equal(EAX, 0xab, msg)
-    # . . push args
-    68/push  "F - test-convert-next-octet"/imm32
-    68/push  0xab/imm32/ab
-    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
-$test-convert-next-octet:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-next-octet-handles-Eof:
-    # - check that reaching end of file returns Eof
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # don't initialize '_test-stream'
-    # initialize exit-descriptor 'ed' for the call to 'convert-next-octet' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-convert-next-octet
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = convert-next-octet(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  convert-next-octet/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to convert-next-octet
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that convert-next-octet didn't abort
-    # . check-ints-equal(ed->value, 0, msg)
-    # . . push args
-    68/push  "F - test-convert-next-octet: unexpected abort"/imm32
-    68/push  0/imm32
-    # . . push ed->value
-    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
-    # return if abort
-    81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
-    75/jump-if-not-equal  $test-convert-next-octet-handles-Eof:end/disp8
-    # check-ints-equal(EAX, Eof, msg)
-    # . . push args
-    68/push  "F - test-convert-next-octet-handles-Eof"/imm32
-    68/push  0xffffffff/imm32/Eof
-    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
-$test-convert-next-octet-handles-Eof:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-next-octet-aborts-on-single-hex-byte:
-    # - check that a single unaccompanied hex byte aborts
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to "a"
-    # . 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
-    # initialize exit-descriptor 'ed' for the call to 'convert-next-octet' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-convert-next-octet
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = convert-next-octet(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  convert-next-octet/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to convert-next-octet
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that convert-next-octet aborted
-    # . check-ints-equal(ed->value, 2, msg)
-    # . . push args
-    68/push  "F - test-convert-next-octet-aborts-on-single-hex-byte: unexpected abort"/imm32
-    68/push  2/imm32
-    # . . push ed->value
-    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
-$test-convert-next-octet-aborts-on-single-hex-byte:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# read whitespace until a hex byte, and return it
-# return Eof if file ends without finding a hex byte
-# on '#' skip all bytes until newline
-# abort on any other byte
-scan-next-byte:  # in : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> byte-or-Eof/EAX
-    # pseudocode:
-    #   while true
-    #     EAX = read-byte-buffered(in)
-    #     if (EAX == Eof) return EAX
-    #     if (is-hex-digit?(EAX)) return EAX
-    #     if (EAX == ' ' or '\t' or '\n') continue
-    #     if (EAX == '#') skip-until-newline(in)
-    #     else error-byte(ed, err, "invalid byte: " EAX)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-$scan-next-byte:loop:
-    # EAX = read-byte-buffered(in)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # if (EAX == Eof) return EAX
-    3d/compare-with-EAX  0xffffffff/imm32/Eof
-    74/jump-if-equal  $scan-next-byte:end/disp8
-    # if (is-hex-digit?(EAX)) return EAX
-    # . save EAX for now
-    50/push-EAX
-    # . is-hex-digit?(EAX)
-    # . . push args
-    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
-    # . compare with 'false'
-    3d/compare-with-EAX  0/imm32
-    # . restore EAX (does not affect flags)
-    58/pop-to-EAX
-    # . check whether to return
-    75/jump-if-not-equal  $scan-next-byte:end/disp8
-$scan-next-byte:check1:
-    # if (EAX == ' ') continue
-    3d/compare-EAX-and  0x20/imm32/space
-    74/jump-if-equal  $scan-next-byte:loop/disp8
-    # if (EAX == '\t') continue
-    3d/compare-EAX-and  9/imm32/tab
-    74/jump-if-equal  $scan-next-byte:loop/disp8
-    # if (EAX == '\n') continue
-    3d/compare-EAX-and  0xa/imm32/newline
-    74/jump-if-equal  $scan-next-byte:loop/disp8
-$scan-next-byte:check2:
-    # if (EAX == '#') skip-until-newline(in)
-    3d/compare-with-EAX  0x23/imm32
-    75/jump-if-not-equal  $scan-next-byte:check3/disp8
-    # . skip-until-newline(in)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  skip-until-newline/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    eb/jump  $scan-next-byte:loop/disp8
-$scan-next-byte:check3:
-    # otherwise error-byte(ed, err, msg, EAX)
-    # . . push args
-    50/push-EAX
-    68/push  "scan-next-byte: invalid byte"/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    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    # . . call
-    e8/call  error-byte/disp32  # never returns
-$scan-next-byte:end:
-    # . restore registers
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-scan-next-byte:
-    # - check that the first byte of the input is returned
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to "abc"
-    # . 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
-    # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  scan-next-byte/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to scan-next-byte
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that scan-next-byte didn't abort
-    # . check-ints-equal(ed->value, 0, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte: unexpected abort"/imm32
-    68/push  0/imm32
-    # . . push ed->value
-    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
-    # return if abort
-    81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
-    75/jump-if-not-equal  $test-scan-next-byte:end/disp8
-    # check-ints-equal(EAX, 0x61/a, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte"/imm32
-    68/push  0x61/imm32/a
-    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
-$test-scan-next-byte:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-scan-next-byte-skips-whitespace:
-    # - check that the first byte after whitespace is returned
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to input with leading whitespace
-    # . write(_test-stream, text)
-    # . . 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
-    # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  scan-next-byte/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to scan-next-byte
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that scan-next-byte didn't abort
-    # . check-ints-equal(ed->value, 0, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-skips-whitespace: unexpected abort"/imm32
-    68/push  0/imm32
-    # . . push ed->value
-    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
-    # return if abort
-    81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
-    75/jump-if-not-equal  $test-scan-next-byte-skips-whitespace:end/disp8
-    # check-ints-equal(EAX, 0x61/a, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-skips-whitespace"/imm32
-    68/push  0x61/imm32/a
-    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
-$test-scan-next-byte-skips-whitespace:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-scan-next-byte-skips-comment:
-    # - check that the first byte after a comment (and newline) is returned
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to input with leading comment
-    # . write(_test-stream, comment)
-    # . . push args
-    68/push  "#x\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
-    # . write(_test-stream, real text)
-    # . . 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
-    # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  scan-next-byte/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to scan-next-byte
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that scan-next-byte didn't abort
-    # . check-ints-equal(ed->value, 0, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-skips-comment: unexpected abort"/imm32
-    68/push  0/imm32
-    # . . push ed->value
-    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
-    # return if abort
-    81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
-    75/jump-if-not-equal  $test-scan-next-byte-skips-comment:end/disp8
-    # check-ints-equal(EAX, 0x61/a, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-skips-comment"/imm32
-    68/push  0x61/imm32/a
-    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
-$test-scan-next-byte-skips-comment:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-scan-next-byte-skips-comment-and-whitespace:
-    # - check that the first byte after a comment and any further whitespace is returned
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to input with leading comment and more whitespace after newline
-    # . write(_test-stream, comment)
-    # . . push args
-    68/push  "#x\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
-    # . write(_test-stream, real text)
-    # . . 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
-    # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  scan-next-byte/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to scan-next-byte
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that scan-next-byte didn't abort
-    # . check-ints-equal(ed->value, 0, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-skips-comment-and-whitespace: unexpected abort"/imm32
-    68/push  0/imm32
-    # . . push ed->value
-    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
-    # return if abort
-    81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
-    75/jump-if-not-equal  $test-scan-next-byte-skips-comment-and-whitespace:end/disp8
-    # check-ints-equal(EAX, 0x61/a, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-skips-comment-and-whitespace"/imm32
-    68/push  0x61/imm32/a
-    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
-$test-scan-next-byte-skips-comment-and-whitespace:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-scan-next-byte-skips-whitespace-and-comment:
-    # - check that the first byte after any whitespace and comments is returned
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to input with leading whitespace and comment
-    # . write(_test-stream, comment)
-    # . . push args
-    68/push  " #x\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
-    # . write(_test-stream, real text)
-    # . . 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
-    # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  scan-next-byte/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to scan-next-byte
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that scan-next-byte didn't abort
-    # . check-ints-equal(ed->value, 0, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-skips-whitespace-and-comment: unexpected abort"/imm32
-    68/push  0/imm32
-    # . . push ed->value
-    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
-    # return if abort
-    81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
-    75/jump-if-not-equal  $test-scan-next-byte-skips-whitespace-and-comment:end/disp8
-    # check-ints-equal(EAX, 0x61/a, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-skips-whitespace-and-comment"/imm32
-    68/push  0x61/imm32/a
-    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
-$test-scan-next-byte-skips-whitespace-and-comment:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-scan-next-byte-reads-final-byte:
-    # - check that the final byte in input is returned
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to input with single character
-    # . write(_test-stream, character)
-    # . . 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
-    # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  scan-next-byte/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to scan-next-byte
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that scan-next-byte didn't abort
-    # . check-ints-equal(ed->value, 0, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-reads-final-byte: unexpected abort"/imm32
-    68/push  0/imm32
-    # . . push ed->value
-    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
-    # return if abort
-    81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
-    75/jump-if-not-equal  $test-scan-next-byte-reads-final-byte:end/disp8
-    # check-ints-equal(EAX, 0x61/a, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-reads-final-byte"/imm32
-    68/push  0x61/imm32/a
-    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
-$test-scan-next-byte-reads-final-byte:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-scan-next-byte-handles-Eof:
-    # - check that the right sentinel value is returned when there's no data remaining to be read
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # leave '_test-stream' empty
-    # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  scan-next-byte/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to scan-next-byte
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that scan-next-byte didn't abort
-    # . check-ints-equal(ed->value, 0, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-handles-Eof: unexpected abort"/imm32
-    68/push  0/imm32
-    # . . push ed->value
-    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
-    # return if abort
-    81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
-    75/jump-if-not-equal  $test-scan-next-byte-handles-Eof:end/disp8
-    # check-ints-equal(EAX, Eof, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-handles-Eof"/imm32
-    68/push  0xffffffff/imm32/Eof
-    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
-$test-scan-next-byte-handles-Eof:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-scan-next-byte-aborts-on-invalid-byte:
-    # - check that the a bad byte immediately aborts
-    # This test uses exit-descriptors. Use EBP for setting up local variables.
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # clear all streams
-    # . 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-error-stream)
-    # . . push args
-    68/push  _test-error-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-error-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-error-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to "x"
-    # . write(_test-stream, "x")
-    # . . push args
-    68/push  "x"/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
-    # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
-    # . var ed/ECX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . tailor-exit-descriptor(ed, 12)
-    # . . push args
-    68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
-    51/push-ECX/ed
-    # . . call
-    e8/call  tailor-exit-descriptor/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
-    # . . push args
-    51/push-ECX/ed
-    68/push  _test-error-buffered-file/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  scan-next-byte/disp32
-    # registers except ESP may be clobbered at this point
-    # pop args to scan-next-byte
-    # . . discard first 2 args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . restore ed
-    59/pop-to-ECX
-    # check that scan-next-byte aborted
-    # . check-ints-equal(ed->value, 2, msg)
-    # . . push args
-    68/push  "F - test-scan-next-byte-aborts-on-invalid-byte"/imm32
-    68/push  2/imm32
-    # . . push ed->value
-    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
-$test-scan-next-byte-aborts-on-invalid-byte:end:
-    # . epilog
-    # don't restore ESP from EBP; manually reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    5d/pop-to-EBP
-    c3/return
-
-skip-until-newline:  # in : (address buffered-file) -> <void>
-    # pseudocode:
-    #   push EAX
-    #   while true
-    #     EAX = read-byte-buffered(in)
-    #     if (EAX == Eof) break
-    #     if (EAX == 0x0a) break
-    #   pop EAX
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-$skip-until-newline:loop:
-    # . EAX = read-byte-buffered(in)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX == Eof) break
-    3d/compare-EAX-and  0xffffffff/imm32/Eof
-    74/jump-if-equal  $skip-until-newline:end/disp8
-    # . if (EAX != 0xa/newline) loop
-    3d/compare-EAX-and  0xa/imm32/newline
-    75/jump-if-not-equal  $skip-until-newline:loop/disp8
-$skip-until-newline:end:
-    # . restore registers
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-skip-until-newline:
-    # - check that the read pointer points after the newline
-    # 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-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize '_test-stream' to "abc\nde"
-    # . write(_test-stream, "abc")
-    # . . push args
-    68/push  "abc\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
-    # . write(_test-stream, "de")
-    # . . push args
-    68/push  "de"/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-until-newline(_test-buffered-file)
-    # . . push args
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  skip-until-newline/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(_test-buffered-file->read, 4, msg)
-    # . . push args
-    68/push  "F - test-skip-until-newline"/imm32
-    68/push  4/imm32
-    b8/copy-to-EAX  _test-buffered-file/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
-    # . end
-    c3/return
-
-== data
-
-_test-error-stream:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # line
-    0x80/imm32  # 128 bytes
-    # data (8 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
-
-# a test buffered file for _test-error-stream
-_test-error-buffered-file:
-    # file descriptor or (address stream)
-    _test-error-stream/imm32
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    6/imm32
-    # data
-    00 00 00 00 00 00  # 6 bytes
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/apps/pack b/subx/apps/pack
deleted file mode 100755
index 720ba3ea..00000000
--- a/subx/apps/pack
+++ /dev/null
Binary files differdiff --git a/subx/apps/pack.subx b/subx/apps/pack.subx
deleted file mode 100644
index 61a837ba..00000000
--- a/subx/apps/pack.subx
+++ /dev/null
@@ -1,5986 +0,0 @@
-# Read a text file of SubX instructions from stdin, and convert it into a list
-# of whitespace-separated ascii hex bytes on stdout. Label definitions and
-# uses are left untouched.
-#
-# To run (from the subx/ directory):
-#   $ ./subx translate *.subx apps/pack.subx -o apps/pack
-#   $ echo '05/add-to-EAX 0x20/imm32'  |./subx run apps/pack
-# Expected output:
-#   05 20 00 00 00  # 05/add-to-EAX 0x20/imm32
-# The original instruction gets included as a comment at the end of each
-# converted line.
-#
-# There's zero error-checking. For now we assume the input program is valid.
-# We'll continue to rely on the C++ version for error messages.
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # run tests if necessary, convert stdin if not
-    # initialize heap
-    # . Heap = new-segment(64KB)
-    # . . push args
-    68/push  Heap/imm32
-    68/push  0x10000/imm32/64KB
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # . argc > 1
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # . argv[1] == "test"
-    # . . push args
-    68/push  "test"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  kernel-string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check result
-    3d/compare-EAX-and  1/imm32
-    75/jump-if-not-equal  $run-main/disp8
-    # . run-tests()
-    e8/call  run-tests/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    eb/jump  $main:end/disp8
-$run-main:
-    # - otherwise convert stdin
-    # var ed/EAX : exit-descriptor
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
-    # configure ed to really exit()
-    # . ed->target = 0
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
-    # return convert(Stdin, 1/stdout, 2/stderr, ed)
-    # . . push args
-    50/push-EAX/ed
-    68/push  Stderr/imm32
-    68/push  Stdout/imm32
-    68/push  Stdin/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # . syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-$main:end:
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# - big picture
-# We'll operate on each line/instruction in isolation. That way we only need to
-# allocate memory for converting a single instruction.
-#
-# To pack an entire file, convert every segment in it
-# To convert a code segment, convert every instruction (line) until the next segment header
-# To convert a non-data segment, convert every word until the next segment header
-#
-# primary state: line
-#   stream of 512 bytes; abort if it ever overflows
-
-# conceptual hierarchy within a line:
-#   line = words separated by ' ', maybe followed by comment starting with '#'
-#   word = datum until '/', then 0 or more metadata separated by '/'
-#
-# we won't bother saving the internal structure of lines; reparsing should be cheap using three primitives:
-#   next-token(stream, delim char) -> slice (start, end pointers)
-#   next-token-from-slice(start, end, delim char) -> slice
-#   slice-equal?(slice, string)
-
-convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   var line = new-stream(512, 1)
-    #   var in-code? = false
-    #   while true
-    #     clear-stream(line)
-    #     read-line-buffered(in, line)
-    #     if (line->write == 0) break             # end of file
-    #     var word-slice = next-word(line)
-    #     if slice-empty?(word-slice)             # whitespace
-    #       write-stream-data(out, line)
-    #     else if (slice-equal?(word-slice, "=="))
-    #       word-slice = next-word(line)
-    #       in-code? = slice-equal?(word-slice, "code")
-    #       write-stream-data(out, line)
-    #     else if (in-code?)
-    #       rewind-stream(line)
-    #       convert-instruction(line, out)
-    #     else
-    #       rewind-stream(line)
-    #       convert-data(line, out)
-    #   flush(out)
-    #
-    # . prolog
-    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
-    # var line/ECX : (address stream byte) = stream(512)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
-    68/push  0x200/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var word-slice/EDX = {0, 0}
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # var in-code?/EBX = false
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-$convert:loop:
-    # clear-stream(line)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read-line-buffered(in, line)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-line-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:check0:
-    # if (line->write == 0) break
-    81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
-    0f 84/jump-if-equal  $convert:break/disp32
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, line)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # next-word(line, word-slice)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:check1:
-    # if (slice-empty?(word-slice)) write-stream-data(out, line)
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) write-stream-data(out, line)
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $convert:pass-through/disp32
-$convert:check2:
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     52/push-EDX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # if (!slice-equal?(word-slice, "==")) goto next check
-    # . EAX = slice-equal?(word-slice, "==")
-    # . . push args
-    68/push  "=="/imm32
-    52/push-EDX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto check3
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $convert:check3/disp32
-    # word-slice = next-word(line)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump segment name {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     52/push-EDX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # in-code? = slice-equal?(word-slice, "code")
-    # . . push args
-    68/push  "code"/imm32
-    52/push-EDX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . . in-code? = EAX
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    # write-stream-data(out, line)
-    eb/jump  $convert:pass-through/disp8
-$convert:check3:
-    # else rewind-stream(line)
-    # . rewind-stream(line)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # if (in-code? != 0) convert-instruction(line, out)
-    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0/imm32           # compare EBX
-    74/jump-if-equal  $convert:data/disp8
-$convert:code:
-    # . convert-instruction(line, out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    51/push-ECX
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . loop
-    e9/jump  $convert:loop/disp32
-$convert:data:
-    # else convert-data(line, out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    51/push-ECX
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . loop
-    e9/jump  $convert:loop/disp32
-$convert:pass-through:
-    # write-stream-data(out, line)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . loop
-    e9/jump  $convert:loop/disp32
-$convert:break:
-    # flush(out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$convert:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x214/imm32       # add to ESP
-    # . restore registers
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-passes-empty-lines-through:
-    # if a line is empty, pass it along unchanged
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-input-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-input-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # write nothing to input
-    # convert(_test-input-buffered-file, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-buffered-file/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that the line just passed through
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "", msg)
-    # . . push args
-    68/push  "F - test-convert-passes-empty-lines-through"/imm32
-    68/push  ""/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-passes-lines-with-just-whitespace-through:
-    # if a line is empty, pass it along unchanged
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-input-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-input-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "    ")
-    # . . push args
-    68/push  "    "/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert(_test-input-buffered-file, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-buffered-file/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that the line just passed through
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "    ", msg)
-    # . . push args
-    68/push  "F - test-convert-passes-with-just-whitespace-through"/imm32
-    68/push  "    "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-passes-segment-headers-through:
-    # if a line starts with '==', pass it along unchanged
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-input-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-input-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "== abcd 0x1")
-    # . . push args
-    68/push  "== abcd 0x1"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert(_test-input-buffered-file, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-buffered-file/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that the line just passed through
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "== abcd 0x1", msg)
-    # . . push args
-    68/push  "F - test-convert-passes-segment-headers-through"/imm32
-    68/push  "== abcd 0x1"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-in-data-segment:
-    # correctly process lines in the data segment
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-input-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-input-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    #   == code 0x1
-    #   == data 0x2
-    #   3 4/imm32
-    # . write(_test-input-stream, "== code 0x1")
-    # . . push args
-    68/push  "== code 0x1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== data 0x2\n")
-    # . . push args
-    68/push  "== data 0x2\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "3 4/imm32\n")
-    # . . push args
-    68/push  "3 4/imm32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert(_test-input-buffered-file, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-buffered-file/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-#?     # debug print {{{
-#?     # . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "== code 0x1", msg)
-    # . . push args
-    68/push  "F - test-convert-in-data-segment/0"/imm32
-    68/push  "== code 0x1"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "== data 0x2", msg)
-    # . . push args
-    68/push  "F - test-convert-in-data-segment/1"/imm32
-    68/push  "== data 0x2"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "03 04 00 00 00 ", msg)
-    # . . push args
-    68/push  "F - test-convert-in-data-segment/2"/imm32
-    68/push  "03 04 00 00 00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-code-and-data-segments:
-    # correctly process lines in both code and data segments
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-input-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-input-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    #   == code 0x1
-    #   e8/call 20/disp32
-    #   68/push 0x20/imm8
-    #   == data 0x2
-    #   3 4/imm32
-    # . write(_test-input-stream, "== code 0x1\n")
-    # . . push args
-    68/push  "== code 0x1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "e8/call 20/disp32\n")
-    # . . push args
-    68/push  "e8/call 20/disp32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "68/push 0x20/imm8\n")
-    # . . push args
-    68/push  "68/push 0x20/imm8\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== data 0x2\n")
-    # . . push args
-    68/push  "== data 0x2\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "3 4/imm32\n")
-    # . . push args
-    68/push  "3 4/imm32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert(_test-input-buffered-file, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-buffered-file/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    #   == code 0x1
-    #   e8 20 00 00 00  # e8/call 20/disp32
-    #   68 20  # 68/push 0x20/imm8
-    #   == data 0x2
-    #   03 04 00 00 00
-#?     # debug print {{{
-#?     # . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "== code 0x1", msg)
-    # . . push args
-    68/push  "F - test-convert-code-and-data-segments/0"/imm32
-    68/push  "== code 0x1"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "e8 20 00 00 00  # e8/call 20/disp32", msg)
-    # . . push args
-    68/push  "F - test-convert-code-and-data-segments/1"/imm32
-    68/push  "e8 20 00 00 00  # e8/call 20/disp32"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "68 20  # 68/push 0x20/imm8", msg)
-    # . . push args
-    68/push  "F - test-convert-code-and-data-segments/2"/imm32
-    68/push  "68 20  # 68/push 0x20/imm8"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "== data 0x2", msg)
-    # . . push args
-    68/push  "F - test-convert-code-and-data-segments/3"/imm32
-    68/push  "== data 0x2"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "03 04 00 00 00 ", msg)
-    # . . push args
-    68/push  "F - test-convert-code-and-data-segments/4"/imm32
-    68/push  "03 04 00 00 00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-convert-data:  # line : (address stream byte), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   var word-slice = {0, 0}
-    #   while true
-    #     word-slice = next-word(line)
-    #     if slice-empty?(word-slice)                 # end of file (maybe including trailing whitespace)
-    #       break  # skip emitting some whitespace
-    #     if slice-starts-with?(word-slice, "#")      # comment
-    #       write-slice-buffered(out, word-slice)
-    #       return
-    #     if slice-ends-with?(word-slice, ":")        # label
-    #       write-stream-data(out, line)
-    #       return
-    #     if has-metadata?(word-slice, "imm32")
-    #       emit(out, word-slice, 4)
-    #     # disp32 is not permitted in data segments, and anything else is only a byte long
-    #     else
-    #       emit(out, word-slice, 1)
-    #   write-buffered(out, "\n")
-    #
-    # . prolog
-    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
-    # var word-slice/ECX = {0, 0}
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # write-stream(2/stderr, line)
-#?     # . . push args
-#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$convert-data:loop:
-    # next-word(line, word-slice)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$convert-data:check0:
-    # if (slice-empty?(word-slice)) break
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) break
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $convert-data:break/disp32
-$convert-data:check-for-comment:
-    # if (slice-starts-with?(word-slice, "#"))
-    # . start/EDX = word-slice->start
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/AL    .               .                 # copy byte at *EDX to AL
-    # . if (EAX != '#') goto next check
-    3d/compare-EAX-and  0x23/imm32/hash
-    75/jump-if-not-equal  $convert-data:check-for-label/disp8
-$convert-data:comment:
-    # write-slice-buffered(out, word-slice)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # return
-    0f 85/jump-if-not-equal  $convert-data:end/disp32
-$convert-data:check-for-label:
-    # if (slice-ends-with?(word-slice, ":"))
-    # . end/EDX = word-slice->end
-    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
-    # . c/EAX = *(end-1)
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/AL    -1/disp8        .                 # copy byte at *ECX to AL
-    # . if (EAX != ':') goto next check
-    3d/compare-EAX-and  0x3a/imm32/colon
-    75/jump-if-not-equal  $convert-data:check-for-imm32/disp8
-$convert-data:label:
-    # write-stream-data(out, line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # return
-    75/jump-if-not-equal  $convert-data:end/disp8
-$convert-data:check-for-imm32:
-    # if (has-metadata?(word-slice, "imm32"))
-    # . EAX = has-metadata?(ECX, "imm32")
-    # . . push args
-    68/push  "imm32"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) process as a single byte
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $convert-data:single-byte/disp8
-$convert-data:imm32:
-    # emit(out, word-slice, 4)
-    # . . push args
-    68/push  4/imm32
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    e9/jump  $convert-data:loop/disp32
-$convert-data:single-byte:
-    # emit(out, word-slice, 1)
-    # . . push args
-    68/push  1/imm32
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    e9/jump  $convert-data:loop/disp32
-$convert-data:break:
-    # write-buffered(out, "\n")
-    # . . push args
-    68/push  "\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert-data:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-data-passes-comments-through:
-    # if a line starts with '#', pass it along unchanged
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "# abcd")
-    # . . push args
-    68/push  "# abcd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-data(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that the line just passed through
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "# abcd", msg)
-    # . . push args
-    68/push  "F - test-convert-data-passes-comments-through"/imm32
-    68/push  "# abcd"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-data-passes-labels-through:
-    # if the first word ends with ':', pass along the entire line unchanged
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "ab: # cd")
-    # . . push args
-    68/push  "ab: # cd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-data(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that the line just passed through
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "ab: # cd", msg)
-    # . . push args
-    68/push  "F - test-convert-data-passes-labels-through"/imm32
-    68/push  "ab: # cd"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-data-passes-names-through:
-    # If a word is a valid name, just emit it unchanged.
-    # Later phases will deal with it.
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "abcd/imm32")
-    # . . push args
-    68/push  "abcd/imm32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-data(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that the line just passed through
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "abcd/imm32 \n", msg)
-    # . . push args
-    68/push  "F - test-convert-data-passes-names-through"/imm32
-    68/push  "abcd/imm32 \n"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-data-handles-imm32:
-    # If a word has the /imm32 metadata, emit it in 4 bytes.
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "30/imm32")
-    # . . push args
-    68/push  "30/imm32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-data(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that 4 bytes were written
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "30 00 00 00 \n", msg)
-    # . . push args
-    68/push  "F - test-convert-data-handles-imm32"/imm32
-    68/push  "30 00 00 00 \n"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-data-handles-single-byte:
-    # Any metadata but /imm32 will emit a single byte.
-    # Data segments can't have /disp32, and SubX doesn't support 16-bit operands.
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "30/imm16")
-    # . . push args
-    68/push  "30/imm16"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-data(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that a single byte was written (imm16 is not a valid operand type)
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "30 \n", msg)
-    # . . push args
-    68/push  "F - test-convert-data-handles-single-byte"/imm32
-    68/push  "30 \n"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-data-multiple-bytes:
-    # Multiple single-byte words in input stream get processed one by one.
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "1 2")
-    # . . push args
-    68/push  "1 2"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-data(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "01 02 \n", msg)
-    # . . push args
-    68/push  "F - test-convert-data-multiple-bytes"/imm32
-    68/push  "01 02 \n"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-data-byte-then-name:
-    # Single-byte word followed by valid name get processed one by one.
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "30 abcd/o")
-    # . . push args
-    68/push  "30 abcd/o"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-data(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "30 abcd/o \n", msg)
-    # . . push args
-    68/push  "F - test-convert-data-byte-then-name"/imm32
-    68/push  "30 abcd/o \n"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-data-multiple-words:
-    # Multiple words in input stream get processed one by one.
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "30 abcd/o 42e1/imm32")
-    # . . push args
-    68/push  "30 abcd/o 42e1/imm32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-data(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "30 abcd/o 42 e1 00 00 \n", msg)
-    # . . push args
-    68/push  "F - test-convert-data-multiple-words"/imm32
-    68/push  "30 abcd/o e1 42 00 00 \n"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-data-trailing-comment:
-    # Trailing comments in data segment get appropriately ignored.
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "30/imm32 # comment")
-    # . . push args
-    68/push  "30/imm32 # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-data(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "30 00 00 00 # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-data-trailing-comment"/imm32
-    68/push  "30 00 00 00 # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# pack an instruction, following the C++ version
-#
-# zero error handling at the moment (continuing to rely on the C++ version for that):
-#   missing fields are always 0-filled
-#   bytes never mentioned are silently dropped; if you don't provide /mod, /rm32 or /r32 you don't get a 0 ModR/M byte. You get *no* ModR/M byte.
-#   may pick up any of duplicate operands in an instruction
-#   silently drop extraneous operands
-#   unceremoniously abort on non-numeric operands except disp or imm
-#   opcodes must be lowercase and zero padded
-#   opcodes with misleading operand metadata may get duplicated as operands as well. don't rely on this.
-convert-instruction:  # line : (address stream byte), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   # some early exits
-    #   var word-slice = next-word(line)
-    #   if slice-empty?(word-slice)
-    #     write-stream-data(out, line)
-    #     return
-    #   if slice-starts-with?(word-slice, "#")
-    #     write-stream-data(out, line)
-    #     return
-    #   if slice-ends-with?(word-slice, ":")
-    #     write-stream-data(out, line)
-    #     return
-    #   # really convert
-    #   emit-opcodes(line, out)
-    #   emit-modrm(line, out)
-    #   emit-sib(line, out)
-    #   emit-disp(line, out)
-    #   emit-imm(line, out)
-    #   emit-line-in-comment(line, out)
-    #
-    # . prolog
-    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
-    # var word-slice/ECX = {0, 0}
-    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
-    # next-word(line, word-slice)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert-instruction:check0:
-    # if (slice-empty?(word-slice)) break
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) pass through
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $convert-instruction:pass-through/disp8
-$convert-instruction:check1:
-    # if (slice-starts-with?(word-slice, "#")) write-stream-data(out, line)
-    # . start/EDX = word-slice->start
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/AL    .               .                 # copy byte at *EDX to AL
-    # . if (EAX == '#') pass through
-    3d/compare-EAX-and  0x23/imm32/hash
-    74/jump-if-equal  $convert-instruction:pass-through/disp8
-$convert-instruction:check2:
-    # if (slice-ends-with?(word-slice, ":")) write-stream-data(out, line)
-    # . end/EDX = word-slice->end
-    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
-    # . c/EAX = *(end-1)
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/AL    -1/disp8        .                 # copy byte at *ECX to AL
-    # . if (EAX == ':') pass through
-    3d/compare-EAX-and  0x3a/imm32/colon
-    75/jump-if-not-equal  $convert-instruction:really-convert/disp8
-$convert-instruction:pass-through:
-    # write-stream-data(out, line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # return
-    eb/jump  $convert-instruction:end/disp8
-$convert-instruction:really-convert:
-    # emit-opcodes(line, out)
-    # . . 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  emit-opcodes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # emit-modrm(line, out)
-    # . . 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  emit-modrm/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # emit-sib(line, out)
-    # . . 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  emit-sib/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # emit-disp(line, out)
-    # . . 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  emit-disp/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # emit-imm(line, out)
-    # . . 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  emit-imm/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # emit-line-in-comment(line, out)
-    # . . 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  emit-line-in-comment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert-instruction:end:
-    # . restore locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-opcodes:  # line : (address stream byte), out : (address buffered-file) -> <void>
-    # opcodes occupy 1-3 bytes:
-    #   xx
-    #   0f xx
-    #   f2 xx
-    #   f3 xx
-    #   f2 0f xx
-    #   f3 0f xx
-    #
-    # pseudocode:
-    #   rewind-stream(line)
-    #
-    #   var op1 = next-word(line)
-    #   if (slice-empty?(op1) || slice-starts-with?(op1, "#")) return
-    #   op1 = next-token-from-slice(op1->start, op1->end, "/")
-    #   write-slice-buffered(out, op1)
-    #   if !slice-equal?(op1, "0f") && !slice-equal?(op1, "f2") && !slice-equal?(op1, "f3")
-    #     return
-    #
-    #   var op2 = next-word(line)
-    #   if (slice-empty?(op2) || slice-starts-with?(op2, "#")) return
-    #   op2 = next-token-from-slice(op2->start, op2->end, "/")
-    #   write-slice-buffered(out, op2)
-    #   if slice-equal?(op1, "0f")
-    #     return
-    #   if !slice-equal?(op2, "0f")
-    #     return
-    #
-    #   var op3 = next-word(line)
-    #   if (slice-empty?(op3) || slice-starts-with?(op3, "#")) return
-    #   op3 = next-token-from-slice(op3->start, op3->end, "/")
-    #   write-slice-buffered(out, op3)
-    #
-    # . prolog
-    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
-    # var op1/ECX = {0, 0}
-    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
-    # var op2/EDX = {0, 0}
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # rewind-stream(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$emit-opcodes:op1:
-    # next-word(line, op1)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (slice-empty?(op1)) return
-    # . EAX = slice-empty?(op1)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) return
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-opcodes:end/disp32
-    # if (slice-starts-with?(op1, "#")) return
-    # . start/EBX = op1->start
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           3/r32/EBX   .               .                 # copy *ECX to EBX
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at *EBX to AL
-    # . if (EAX == '#') return
-    3d/compare-EAX-and  0x23/imm32/hash
-    0f 84/jump-if-equal  $emit-opcodes:end/disp32
-    # op1 = next-token-from-slice(op1->start, op1->end, '/')
-    # . . push args
-    51/push-ECX
-    68/push  0x2f/imm32/slash
-    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  next-token-from-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # write-slice-buffered(out, op1)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-buffered(out, " ")
-    # . . push args
-    68/push  " "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (slice-equal?(op1, "0f")) goto op2
-    # . EAX = slice-equal?(op1, "0f")
-    # . . push args
-    68/push  "0f"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) goto op2
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $emit-opcodes:op2/disp8
-    # if (slice-equal?(op1, "f2")) goto op2
-    # . EAX = slice-equal?(op1, "f2")
-    # . . push args
-    68/push  "f2"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) goto op2
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $emit-opcodes:op2/disp8
-    # if (slice-equal?(op1, "f3")) goto op2
-    # . EAX = slice-equal?(op1, "f3")
-    # . . push args
-    68/push  "f3"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) goto op2
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $emit-opcodes:op2/disp8
-    # otherwise return
-    e9/jump  $emit-opcodes:end/disp32
-$emit-opcodes:op2:
-    # next-word(line, op2)
-    # . . push args
-    52/push-EDX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (slice-empty?(op2)) return
-    # . EAX = slice-empty?(op2)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) return
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-opcodes:end/disp32
-    # if (slice-starts-with?(op2, "#")) return
-    # . start/EBX = op2->start
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           3/r32/EBX   .               .                 # copy *EDX to EBX
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at *EBX to AL
-    # . if (EAX == '#') return
-    3d/compare-EAX-and  0x23/imm32/hash
-    0f 84/jump-if-equal  $emit-opcodes:end/disp32
-    # op2 = next-token-from-slice(op2->start, op2->end, '/')
-    # . . push args
-    52/push-EDX
-    68/push  0x2f/imm32/slash
-    ff          6/subop/push        1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         .                 # push *(EDX+4)
-    ff          6/subop/push        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               .                 # push *EDX
-    # . . call
-    e8/call  next-token-from-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # write-slice-buffered(out, op2)
-    # . . push args
-    52/push-EDX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-buffered(out, " ")
-    # . . push args
-    68/push  " "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (slice-equal?(op1, "0f")) return
-    # . EAX = slice-equal?(op1, "0f")
-    # . . push args
-    68/push  "0f"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-opcodes:end/disp32
-    # if (!slice-equal?(op2, "0f")) return
-    # . EAX = slice-equal?(op2, "0f")
-    # . . push args
-    68/push  "0f"/imm32
-    52/push-EDX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) return
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $emit-opcodes:end/disp32
-$emit-opcodes:op3:
-    # next-word(line, op3)  # reuse op2/EDX
-    # . . push args
-    52/push-EDX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (slice-empty?(op3)) return
-    # . EAX = slice-empty?(op3)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) return
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-opcodes:end/disp32
-    # if (slice-starts-with?(op3, "#")) return
-    # . start/EBX = op2->start
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           3/r32/EBX   .               .                 # copy *EDX to EBX
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at *EBX to AL
-    # . if (EAX == '#') return
-    3d/compare-EAX-and  0x23/imm32/hash
-    0f 84/jump-if-equal  $emit-opcodes:end/disp32
-    # op3 = next-token-from-slice(op3->start, op3->end, '/')
-    # . . push args
-    52/push-EDX
-    68/push  0x2f/imm32/slash
-    ff          6/subop/push        1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         .                 # push *(EDX+4)
-    ff          6/subop/push        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               .                 # push *EDX
-    # . . call
-    e8/call  next-token-from-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # write-slice-buffered(out, op3)
-    # . . push args
-    52/push-EDX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-buffered(out, " ")
-    # . . push args
-    68/push  " "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$emit-opcodes:end:
-    # . restore locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # . restore registers
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-modrm:  # line : (address stream byte), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   rewind-stream(line)
-    #   var has-modrm? = false, mod = 0, rm32 = 0, r32 = 0
-    #   var word-slice = {0, 0}
-    #   while true
-    #     word-slice = next-word(line)
-    #     if (slice-empty?(word-slice)) break
-    #     if (slice-starts-with?(word-slice, "#")) break
-    #     if (has-metadata?(word-slice, "mod"))
-    #       mod = parse-hex-int(next-token-from-slice(word-slice, "/"))
-    #       has-modrm? = true
-    #     else if (has-metadata?(word-slice, "rm32"))
-    #       rm32 = parse-hex-int(next-token-from-slice(word-slice, "/"))
-    #       has-modrm? = true
-    #     else if (has-metadata?(word-slice, "r32") or has-metadata?(word-slice, "subop"))
-    #       r32 = parse-hex-int(next-token-from-slice(word-slice, "/"))
-    #       has-modrm? = true
-    #   if has-modrm?
-    #     var modrm = mod & 0b11
-    #     modrm <<= 3
-    #     modrm |= r32 & 0b111
-    #     modrm <<= 3
-    #     modrm |= rm32 & 0b111
-    #     emit-hex(out, modrm, 1)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    53/push-EBX
-    56/push-ESI
-    57/push-EDI
-    # var word-slice/ECX = {0, 0}
-    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
-    # var has-modrm?/EDX = false
-    31/xor                          3/mod/direct    2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # clear EDX
-    # var mod/EBX = 0
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-    # var rm32/ESI = 0
-    31/xor                          3/mod/direct    6/rm32/ESI    .           .             .           6/r32/ESI   .               .                 # clear ESI
-    # var r32/EDI = 0
-    31/xor                          3/mod/direct    7/rm32/EDI    .           .             .           7/r32/EDI   .               .                 # clear EDI
-    # rewind-stream(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, line)
-#?     # . . push args
-#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(line)
-#?     # . . push args
-#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-$emit-modrm:loop:
-    # next-word(line, word-slice)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$emit-modrm:check0:
-    # if (slice-empty?(word-slice)) break
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) pass through
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-modrm:break/disp32
-$emit-modrm:check1:
-    # if (slice-starts-with?(word-slice, "#")) break
-    # . spill EDX
-    52/push-EDX
-    # . start/EDX = word-slice->start
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/AL    .               .                 # copy byte at *EDX to AL
-    # . restore EDX
-    5a/pop-to-EDX
-    # . if (EAX == '#') pass through
-    3d/compare-EAX-and  0x23/imm32/hash
-    0f 84/jump-if-equal  $emit-modrm:break/disp32
-$emit-modrm:check-for-mod:
-    # if (has-metadata?(word-slice, "mod"))
-    # . EAX = has-metadata?(ECX, "mod")
-    # . . push args
-    68/push  "mod"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-modrm:check-for-rm32/disp8
-$emit-modrm:mod:
-    # mod = parse-hex-int(next-token-from-slice(word-slice->start, word-slice->end, '/'))
-    # . EAX = parse-datum-of-word(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-datum-of-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . mod = EAX
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    # has-modrm? = true
-    ba/copy-to-EDX  1/imm32/true
-    # continue
-    e9/jump  $emit-modrm:loop/disp32
-$emit-modrm:check-for-rm32:
-    # if (has-metadata?(word-slice, "rm32"))
-    # . EAX = has-metadata?(ECX, "rm32")
-    # . . push args
-    68/push  "rm32"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-modrm:check-for-r32/disp8
-$emit-modrm:rm32:
-    # rm32 = parse-hex-int(next-token-from-slice(word-slice->start, word-slice->end, '/'))
-    # . EAX = parse-datum-of-word(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-datum-of-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . rm32 = EAX
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy EAX to ESI
-    # has-modrm? = true
-    ba/copy-to-EDX  1/imm32/true
-    # continue
-    e9/jump  $emit-modrm:loop/disp32
-$emit-modrm:check-for-r32:
-    # if (has-metadata?(word-slice, "r32"))
-    # . EAX = has-metadata?(ECX, "r32")
-    # . . push args
-    68/push  "r32"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-modrm:check-for-subop/disp8
-$emit-modrm:r32:
-    # r32 = parse-hex-int(next-token-from-slice(word-slice->start, word-slice->end, '/'))
-    # . EAX = parse-datum-of-word(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-datum-of-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . r32 = EAX
-    89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDI
-    # has-modrm? = true
-    ba/copy-to-EDX  1/imm32/true
-    # continue
-    e9/jump  $emit-modrm:loop/disp32
-$emit-modrm:check-for-subop:
-    # if (has-metadata?(word-slice, "subop"))
-    # . EAX = has-metadata?(ECX, "subop")
-    # . . push args
-    68/push  "subop"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) loop
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $emit-modrm:loop/disp32
-$emit-modrm:subop:
-    # r32 = parse-hex-int(next-token-from-slice(word-slice->start, word-slice->end, '/'))
-    # . EAX = parse-datum-of-word(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-datum-of-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . r32 = EAX
-    89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDI
-    # has-modrm? = true
-    ba/copy-to-EDX  1/imm32/true
-    # continue
-    e9/jump  $emit-modrm:loop/disp32
-$emit-modrm:break:
-    # if (!has-modrm?) return
-    81          7/subop/compare     3/mod/direct    2/rm32/EDX    .           .             .           .           .               0/imm32           # compare EDX
-    74/jump-if-equal  $emit-modrm:end/disp8
-$emit-modrm:calculate:
-    # modrm/EBX = mod & 0b11
-    81          4/subop/and         3/mod/direct    3/rm32/EBX    .           .             .           .           .               3/imm32/0b11      # bitwise and of EBX
-    # modrm <<= 3
-    c1/shift    4/subop/left        3/mod/direct    3/rm32/EBX    .           .             .           .           .               3/imm8            # shift EBX left by 3 bits
-    # modrm |= r32 & 0b111
-    81          4/subop/and         3/mod/direct    7/rm32/EDI    .           .             .           .           .               7/imm32/0b111     # bitwise and of EDI
-    09/or                           3/mod/direct    3/rm32/EBX    .           .             .           7/r32/EDI   .               .                 # EBX = bitwise OR with EDI
-    # modrm <<= 3
-    c1/shift    4/subop/left        3/mod/direct    3/rm32/EBX    .           .             .           .           .               3/imm8            # shift EBX left by 3 bits
-    # modrm |= rm32 & 0b111
-    81          4/subop/and         3/mod/direct    6/rm32/ESI    .           .             .           .           .               7/imm32/0b111     # bitwise and of ESI
-    09/or                           3/mod/direct    3/rm32/EBX    .           .             .           6/r32/ESI   .               .                 # EBX = bitwise OR with ESI
-$emit-modrm:emit:
-    # emit-hex(out, modrm, 1)
-    # . . push args
-    68/push  1/imm32
-    53/push-EBX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$emit-modrm:end:
-    # . restore locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-sib:  # line : (address stream byte), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   var has-sib? = false, base = 0, index = 0, scale = 0
-    #   var word-slice = {0, 0}
-    #   while true
-    #     word-slice = next-word(line)
-    #     if (slice-empty?(word-slice)) break
-    #     if (slice-starts-with?(word-slice, "#")) break
-    #     if (has-metadata?(word-slice, "base")
-    #       base = parse-hex-int(next-token-from-slice(word-slice, "/"))
-    #       has-sib? = true
-    #     else if (has-metadata?(word-slice, "index")
-    #       index = parse-hex-int(next-token-from-slice(word-slice, "/"))
-    #       has-sib? = true
-    #     else if (has-metadata?(word-slice, "scale")
-    #       scale = parse-hex-int(next-token-from-slice(word-slice, "/"))
-    #       has-sib? = true
-    #   if has-sib?
-    #     var sib = scale & 0b11
-    #     sib <<= 2
-    #     sib |= index & 0b111
-    #     sib <<= 3
-    #     sib |= base & 0b111
-    #     emit-hex(out, sib, 1)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    53/push-EBX
-    56/push-ESI
-    57/push-EDI
-    # var word-slice/ECX = {0, 0}
-    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
-    # var has-sib?/EDX = false
-    31/xor                          3/mod/direct    2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # clear EDX
-    # var scale/EBX = 0
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-    # var base/ESI = 0
-    31/xor                          3/mod/direct    6/rm32/ESI    .           .             .           6/r32/ESI   .               .                 # clear ESI
-    # var index/EDI = 0
-    31/xor                          3/mod/direct    7/rm32/EDI    .           .             .           7/r32/EDI   .               .                 # clear EDI
-    # rewind-stream(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$emit-sib:loop:
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, line)
-#?     # . . push args
-#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # next-word(line, word-slice)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$emit-sib:check0:
-    # if (slice-empty?(word-slice)) break
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) pass through
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-sib:break/disp32
-$emit-sib:check1:
-    # if (slice-starts-with?(word-slice, "#")) break
-    # . spill EDX
-    52/push-EDX
-    # . start/EDX = word-slice->start
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/AL    .               .                 # copy byte at *EDX to AL
-    # . restore EDX
-    5a/pop-to-EDX
-    # . if (EAX == '#') pass through
-    3d/compare-EAX-and  0x23/imm32/hash
-    0f 84/jump-if-equal  $emit-sib:break/disp32
-$emit-sib:check-for-scale:
-    # if (has-metadata?(word-slice, "scale"))
-    # . EAX = has-metadata?(ECX, "scale")
-    # . . push args
-    68/push  "scale"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-sib:check-for-base/disp8
-$emit-sib:scale:
-    # scale = parse-hex-int(next-token-from-slice(word-slice->start, word-slice->end, '/'))
-    # . EAX = parse-datum-of-word(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-datum-of-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . scale = EAX
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    # has-sib? = true
-    ba/copy-to-EDX  1/imm32/true
-    # continue
-    e9/jump  $emit-sib:loop/disp32
-$emit-sib:check-for-base:
-    # if (has-metadata?(word-slice, "base"))
-    # . EAX = has-metadata?(ECX, "base")
-    # . . push args
-    68/push  "base"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-sib:check-for-index/disp8
-$emit-sib:base:
-    # base = parse-hex-int(next-token-from-slice(word-slice->start, word-slice->end, '/'))
-    # . EAX = parse-datum-of-word(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-datum-of-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . base = EAX
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy EAX to ESI
-    # has-sib? = true
-    ba/copy-to-EDX  1/imm32/true
-    # continue
-    e9/jump  $emit-sib:loop/disp32
-$emit-sib:check-for-index:
-    # if (has-metadata?(word-slice, "index"))
-    # . EAX = has-metadata?(ECX, "index")
-    # . . push args
-    68/push  "index"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) loop
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $emit-sib:loop/disp32
-$emit-sib:index:
-    # index = parse-hex-int(next-token-from-slice(word-slice->start, word-slice->end, '/'))
-    # . EAX = parse-datum-of-word(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-datum-of-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . index = EAX
-    89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDI
-    # has-sib? = true
-    ba/copy-to-EDX  1/imm32/true
-    # continue
-    e9/jump  $emit-sib:loop/disp32
-$emit-sib:break:
-    # if (!has-sib?) return
-    81          7/subop/compare     3/mod/direct    2/rm32/EDX    .           .             .           .           .               0/imm32           # compare EDX
-    74/jump-if-equal  $emit-sib:end/disp8
-$emit-sib:calculate:
-    # sib/EBX = scale & 0b11
-    81          4/subop/and         3/mod/direct    3/rm32/EBX    .           .             .           .           .               3/imm32/0b11      # bitwise and of EBX
-    # sib <<= 2
-    c1/shift    4/subop/left        3/mod/direct    3/rm32/EBX    .           .             .           .           .               2/imm8            # shift EBX left by 2 bits
-    # sib |= index & 0b111
-    81          4/subop/and         3/mod/direct    7/rm32/EDI    .           .             .           .           .               7/imm32/0b111     # bitwise and of EDI
-    09/or                           3/mod/direct    3/rm32/EBX    .           .             .           7/r32/EDI   .               .                 # EBX = bitwise OR with EDI
-    # sib <<= 3
-    c1/shift    4/subop/left        3/mod/direct    3/rm32/EBX    .           .             .           .           .               3/imm8            # shift EBX left by 3 bits
-    # sib |= base & 0b111
-    81          4/subop/and         3/mod/direct    6/rm32/ESI    .           .             .           .           .               7/imm32/0b111     # bitwise and of ESI
-    09/or                           3/mod/direct    3/rm32/EBX    .           .             .           6/r32/ESI   .               .                 # EBX = bitwise OR with ESI
-$emit-sib:emit:
-    # emit-hex(out, sib, 1)
-    # . . push args
-    68/push  1/imm32
-    53/push-EBX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$emit-sib:end:
-    # . restore locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-disp:  # line : (address stream byte), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   rewind-stream(line)
-    #   var word-slice = {0, 0}
-    #   while true
-    #     word-slice = next-word(line)
-    #     if (slice-empty?(word-slice)) break
-    #     if (slice-starts-with?(word-slice, "#")) break
-    #     if has-metadata?(word-slice, "disp32")
-    #       emit(out, word-slice, 4)
-    #       break
-    #     if has-metadata?(word-slice, "disp16")
-    #       emit(out, word-slice, 2)
-    #       break
-    #     if has-metadata?(word-slice, "disp8")
-    #       emit(out, word-slice, 1)
-    #       break
-    #
-    # . prolog
-    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
-    # var word-slice/ECX = {0, 0}
-    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
-    # rewind-stream(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, line)
-#?     # . . push args
-#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$emit-disp:loop:
-    # next-word(line, word-slice)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$emit-disp:check0:
-    # if (slice-empty?(word-slice)) break
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) pass through
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-disp:break/disp32
-$emit-disp:check1:
-    # if (slice-starts-with?(word-slice, "#")) break
-    # . start/EDX = word-slice->start
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/AL    .               .                 # copy byte at *EDX to AL
-    # . if (EAX == '#') break
-    3d/compare-EAX-and  0x23/imm32/hash
-    0f 84/jump-if-equal  $emit-disp:break/disp32
-$emit-disp:check-for-disp32:
-    # if (has-metadata?(word-slice, "disp32"))
-    # . EAX = has-metadata?(ECX, "disp32")
-    # . . push args
-    68/push  "disp32"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-disp:check-for-disp16/disp8
-$emit-disp:disp32:
-    # emit(out, word-slice, 4)
-    # . . push args
-    68/push  4/imm32
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # break
-    e9/jump  $emit-disp:break/disp32
-$emit-disp:check-for-disp16:
-    # else if (has-metadata?(word-slice, "disp16"))
-    # . EAX = has-metadata?(ECX, "disp16")
-    # . . push args
-    68/push  "disp16"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-disp:check-for-disp8/disp8
-$emit-disp:disp16:
-    # emit(out, word-slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # break
-    e9/jump  $emit-disp:break/disp32
-$emit-disp:check-for-disp8:
-    # if (has-metadata?(word-slice, "disp8"))
-    # . EAX = has-metadata?(ECX, "disp8")
-    # . . push args
-    68/push  "disp8"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) loop
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $emit-disp:loop/disp32
-$emit-disp:disp8:
-    # emit(out, word-slice, 1)
-    # . . push args
-    68/push  1/imm32
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # break
-$emit-disp:break:
-    # . restore locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-imm:  # line : (address stream byte), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   rewind-stream(line)
-    #   var word-slice = {0, 0}
-    #   while true
-    #     word-slice = next-word(line)
-    #     if (slice-empty?(word-slice)) break
-    #     if (slice-starts-with?(word-slice, "#")) break
-    #     if has-metadata?(word-slice, "imm32")
-    #       emit(out, word-slice, 4)
-    #       break
-    #     if has-metadata?(word-slice, "imm16")
-    #       emit(out, word-slice, 2)
-    #       break
-    #     if has-metadata?(word-slice, "imm8")
-    #       emit(out, word-slice, 1)
-    #       break
-    #
-    # . prolog
-    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
-    # var word-slice/ECX = {0, 0}
-    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
-    # rewind-stream(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, line)
-#?     # . . push args
-#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$emit-imm:loop:
-    # next-word(line, word-slice)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$emit-imm:check0:
-    # if (slice-empty?(word-slice)) break
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) pass through
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-imm:break/disp32
-$emit-imm:check1:
-    # if (slice-starts-with?(word-slice, "#")) break
-    # . start/EDX = slice->start
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/AL    .               .                 # copy byte at *EDX to AL
-    # . if (EAX == '#') break
-    3d/compare-EAX-and  0x23/imm32/hash
-    0f 84/jump-if-equal  $emit-imm:break/disp32
-$emit-imm:check-for-imm32:
-    # if (has-metadata?(word-slice, "imm32"))
-    # . EAX = has-metadata?(ECX, "imm32")
-    # . . push args
-    68/push  "imm32"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-imm:check-for-imm16/disp8
-$emit-imm:imm32:
-    # emit(out, word-slice, 4)
-    # . . push args
-    68/push  4/imm32
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # break
-    e9/jump  $emit-imm:break/disp32
-$emit-imm:check-for-imm16:
-    # if (has-metadata?(word-slice, "imm16"))
-    # . EAX = has-metadata?(ECX, "imm16")
-    # . . push args
-    68/push  "imm16"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-imm:check-for-imm8/disp8
-$emit-imm:imm16:
-    # emit(out, word-slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # break
-    e9/jump  $emit-imm:break/disp32
-$emit-imm:check-for-imm8:
-    # if (has-metadata?(word-slice, "imm8"))
-    # . EAX = has-metadata?(ECX, "imm8")
-    # . . push args
-    68/push  "imm8"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) loop
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $emit-imm:loop/disp32
-$emit-imm:imm8:
-    # emit(out, word-slice, 1)
-    # . . push args
-    68/push  1/imm32
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # break
-$emit-imm:break:
-    # . restore locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-line-in-comment:  # line : (address stream byte), out : (address buffered-file) -> <void>
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write-buffered(out, " # ")
-    # . . push args
-    68/push  " # "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-stream-data(out, line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$emit-line-in-comment:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-passes-comments-through:
-    # if a line starts with '#', pass it along unchanged
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "# abcd")
-    # . . push args
-    68/push  "# abcd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that the line just passed through
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "# abcd", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-passes-comments-through"/imm32
-    68/push  "# abcd"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-passes-labels-through:
-    # if the first word ends with ':', pass along the entire line unchanged
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "ab: # cd")
-    # . . push args
-    68/push  "ab: # cd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that the line just passed through
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "ab: # cd", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-passes-labels-through"/imm32
-    68/push  "ab: # cd"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-single-opcode:
-    # if the instruction consists of a single opcode, strip its metadata and pass it along
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "ab/cd # comment")
-    # . . push args
-    68/push  "ab/cd # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "ab  # ab/cd # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-single-opcode"/imm32
-    68/push  "ab  # ab/cd # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-0f-opcode:
-    # if the instruction starts with 0f opcode, include a second opcode
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "0f/m1 ab/m2 # comment")
-    # . . push args
-    68/push  "0f/m1 ab/m2 # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "0f ab  # 0f/m1 ab/m2 # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-0f-opcode"/imm32
-    68/push  "0f ab  # 0f/m1 ab/m2 # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-f2-opcode:
-    # if the instruction starts with f2 opcode, include a second opcode
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "f2/m1 ab/m2 # comment")
-    # . . push args
-    68/push  "f2/m1 ab/m2 # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "f2 ab  # f2/m1 ab/m2 # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-f2-opcode"/imm32
-    68/push  "f2 ab  # f2/m1 ab/m2 # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-f3-opcode:
-    # if the instruction starts with f3 opcode, include a second opcode
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "f3/m1 ab/m2 # comment")
-    # . . push args
-    68/push  "f3/m1 ab/m2 # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "f3 ab  # f3/m1 ab/m2 # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-f3-opcode"/imm32
-    68/push  "f3 ab  # f3/m1 ab/m2 # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-f2-0f-opcode:
-    # if the instruction starts with f2 0f opcode, include a second opcode
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "f2/m1 0f/m2 ab/m3 # comment")
-    # . . push args
-    68/push  "f2/m1 0f/m2 ab/m3 # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "f2 0f ab  # f2/m1 0f/m2 ab/m3 # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-f2-0f-opcode"/imm32
-    68/push  "f2 0f ab  # f2/m1 0f/m2 ab/m3 # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-f3-0f-opcode:
-    # if the instruction starts with f3 0f opcode, include a second opcode
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "f3/m1 0f/m2 ab/m3 # comment")
-    # . . push args
-    68/push  "f3/m1 0f/m2 ab/m3 # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "f3 0f ab  # f3/m1 0f/m2 ab/m3 # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-f3-0f-opcode"/imm32
-    68/push  "f3 0f ab  # f3/m1 0f/m2 ab/m3 # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-unused-opcodes:
-    # if the instruction doesn't start with f2, f3 or 0f, don't include other opcodes
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "ab/m1 cd/m2 # comment")
-    # . . push args
-    68/push  "ab/m1 cd/m2 # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "ab  # f3/m1 0f/m2 ab/m3 # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-unused-opcodes"/imm32
-    68/push  "ab  # ab/m1 cd/m2 # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-unused-second-opcodes:
-    # if the second opcode isn't 0f, don't include further opcodes
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "f2/m1 ab/m2 cd/m3 # comment")
-    # . . push args
-    68/push  "f2/m1 ab/m2 cd/m3 # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "f2 ab  # f2/m1 ab/m2 cd/m3 # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-unused-second-opcodes"/imm32
-    68/push  "f2 ab  # f2/m1 ab/m2 cd/m3 # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-unused-second-opcodes-2:
-    # if the second opcode isn't 0f, don't include further opcodes
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "f3/m1 ab/m2 cd/m3 # comment")
-    # . . push args
-    68/push  "f3/m1 ab/m2 cd/m3 # comment"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "f3 ab  # f3/m1 ab/m2 cd/m3 # comment", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-unused-second-opcodes"/imm32
-    68/push  "f3 ab  # f3/m1 ab/m2 cd/m3 # comment"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-modrm-byte:
-    # pack mod, rm32 and r32 operands into ModR/M byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "8b/copy 0/mod 0/rm32 1/r32")
-    # . . push args
-    68/push  "8b/copy 0/mod 0/rm32 1/r32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "8b 08  # 8b/copy 0/mod 0/rm32 1/r32", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-emits-modrm-byte"/imm32
-    68/push  "8b 08  # 8b/copy 0/mod 0/rm32 1/r32"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-modrm-byte-with-non-zero-mod:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "01/add 3/mod/direct 3/rm32/EBX 1/r32/ECX")
-    # . . push args
-    68/push  "01/add 3/mod/direct 3/rm32/EBX 1/r32/ECX"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "out: ")
-#?     # . . push args
-#?     68/push  "out: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # check output
-    # . check-stream-equal(_test-output-stream, "# abcd", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-foo"/imm32
-    68/push  "01 cb  # 01/add 3/mod/direct 3/rm32/EBX 1/r32/ECX"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-modrm-byte-from-subop:
-    # pack mod, rm32 and subop operands into ModR/M byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "ff 6/subop/push 0/mod 0/rm32")
-    # . . push args
-    68/push  "ff 6/subop/push 0/mod 0/rm32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "ff 30  # ff 6/subop/push 0/mod 0/rm32", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-emits-modrm-byte-from-subop"/imm32
-    68/push  "ff 30  # ff 6/subop/push 0/mod 0/rm32"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-modrm-byte-with-missing-mod:
-    # pack rm32 and r32 operands into ModR/M byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "8b/copy 0/rm32 1/r32")
-    # . . push args
-    68/push  "8b/copy 0/rm32 1/r32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "8b 08  # 8b/copy 0/rm32 1/r32", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-emits-modrm-byte-with-missing-mod"/imm32
-    68/push  "8b 08  # 8b/copy 0/rm32 1/r32"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-modrm-byte-with-missing-rm32:
-    # pack mod and r32 operands into ModR/M byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "8b/copy 0/mod 1/r32")
-    # . . push args
-    68/push  "8b/copy 0/mod 1/r32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "8b 08  # 8b/copy 0/mod 1/r32", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-emits-modrm-byte-with-missing-rm32"/imm32
-    68/push  "8b 08  # 8b/copy 0/mod 1/r32"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-modrm-byte-with-missing-r32:
-    # pack mod and rm32 operands into ModR/M byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "8b/copy 0/mod 0/rm32")
-    # . . push args
-    68/push  "8b/copy 0/mod 0/rm32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "8b 00  # 8b/copy 0/mod 0/rm32", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-emits-modrm-byte-with-missing-r32"/imm32
-    68/push  "8b 00  # 8b/copy 0/mod 0/rm32"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-sib-byte:
-    # pack base, index and scale operands into SIB byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "8b/copy 0/mod 4/rm32 1/r32 0/base 1/index 0/scale")
-    # . . push args
-    68/push  "8b/copy 0/mod 4/rm32 1/r32 0/base 1/index 0/scale"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "8b 08  # 8b/copy 0/mod 4/rm32 1/r32 0/base 1/index 0/scale", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-emits-sib-byte"/imm32
-    68/push  "8b 0c 08  # 8b/copy 0/mod 4/rm32 1/r32 0/base 1/index 0/scale"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-sib-byte-with-missing-base:
-    # pack index and scale operands into SIB byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "8b/copy 0/mod 4/rm32 1/r32 1/index 0/scale")
-    # . . push args
-    68/push  "8b/copy 0/mod 4/rm32 1/r32 1/index 0/scale"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "8b 0c 08  # 8b/copy 0/mod 4/rm32 1/r32 1/index 0/scale", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-emits-sib-byte-with-missing-base"/imm32
-    68/push  "8b 0c 08  # 8b/copy 0/mod 4/rm32 1/r32 1/index 0/scale"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-sib-byte-with-missing-index:
-    # pack base and scale operands into SIB byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "8b/copy 0/mod 4/rm32 1/r32 0/base 0/scale")
-    # . . push args
-    68/push  "8b/copy 0/mod 4/rm32 1/r32 0/base 0/scale"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "8b 0c 08  # 8b/copy 0/mod 4/rm32 1/r32 0/base 0/scale", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-emits-sib-byte-with-missing-index"/imm32
-    68/push  "8b 0c 00  # 8b/copy 0/mod 4/rm32 1/r32 0/base 0/scale"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-emits-sib-byte-with-missing-scale:
-    # pack base and index operands into SIB byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "8b/copy 0/mod 4/rm32 1/r32 0/base 1/index")
-    # . . push args
-    68/push  "8b/copy 0/mod 4/rm32 1/r32 0/base 1/index"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "8b 0c 08  # 8b/copy 0/mod 4/rm32 1/r32 0/base 1/index", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-emits-sib-byte-with-missing-scale"/imm32
-    68/push  "8b 0c 08  # 8b/copy 0/mod 4/rm32 1/r32 0/base 1/index"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-disp32-operand:
-    # expand /disp32 operand into 4 bytes
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "e8/call 20/disp32")
-    # . . push args
-    68/push  "e8/call 20/disp32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "e8 20 00 00 00  # e8/call 20/disp32", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-disp32-operand"/imm32
-    68/push  "e8 20 00 00 00  # e8/call 20/disp32"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-disp16-operand:
-    # expand /disp16 operand into 2 bytes
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "e8/call 20/disp16")
-    # . . push args
-    68/push  "e8/call 20/disp16"/imm32  # not a valid instruction
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "e8 20 00  # e8/call 20/disp16", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-disp16-operand"/imm32
-    68/push  "e8 20 00  # e8/call 20/disp16"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-disp8-operand:
-    # expand /disp8 operand into 1 byte
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "eb/jump 20/disp8")
-    # . . push args
-    68/push  "eb/jump 20/disp8"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "eb 20  # eb/jump 20/disp8", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-disp8-operand"/imm32
-    68/push  "eb 20  # eb/jump 20/disp8"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-disp8-name:
-    # pass /disp8 name directly through
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "eb/jump xyz/disp8")
-    # . . push args
-    68/push  "eb/jump xyz/disp8"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "eb xyz/disp8  # eb/jump xyz/disp8", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-disp8-name"/imm32
-    68/push  "eb xyz/disp8  # eb/jump xyz/disp8"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-imm32-operand:
-    # expand /imm32 operand into 4 bytes
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "68/push 0x20/imm32")
-    # . . push args
-    68/push  "68/push 0x20/imm32"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "68 20 00 00 00  # 68/push 0x20/imm32", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-imm32-operand"/imm32
-    68/push  "68 20 00 00 00  # 68/push 0x20/imm32"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-imm16-operand:
-    # expand /imm16 operand into 2 bytes
-    # we don't have one of these at the moment, so this expands to an invalid instruction
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "68/push 0x20/imm16")
-    # . . push args
-    68/push  "68/push 0x20/imm16"/imm32  # not a valid instruction
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "68 20 00  # 68/push 0x20/imm16", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-imm16-operand"/imm32
-    68/push  "68 20 00  # 68/push 0x20/imm16"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-instruction-handles-imm8-operand:
-    # expand /imm8 operand into 1 byte
-    # we don't have one of these at the moment, so this expands to an invalid instruction
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "68/push 0x20/imm8")
-    # . . push args
-    68/push  "68/push 0x20/imm8"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-input-stream, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  convert-instruction/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check output
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-stream-equal(_test-output-stream, "68 20  # 68/push 0x20/imm8", msg)
-    # . . push args
-    68/push  "F - test-convert-instruction-handles-imm8-operand"/imm32
-    68/push  "68 20  # 68/push 0x20/imm8"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# shortcut for parse-hex-int(next-token-from-slice(word->start, word->end, '/'))
-parse-datum-of-word:  # word : (address slice) -> value/EAX
-    # . prolog
-    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 = word
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # var slice/ECX = {0, 0}
-    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
-    # slice = next-token-from-slice(word->start, word->end, '/')
-    # . . push args
-    51/push-ECX
-    68/push  0x2f/imm32/slash
-    ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # push *(ESI+4)
-    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
-    # . . 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
-    # value/EAX = parse-hex-int(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  parse-hex-int/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$parse-datum-of-word:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5e/pop-to-ESI
-    59/pop-to-ECX
-    # . epilog
-    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/subx/apps/subx-common.subx b/subx/apps/subx-common.subx
deleted file mode 100644
index abb223fb..00000000
--- a/subx/apps/subx-common.subx
+++ /dev/null
@@ -1,3118 +0,0 @@
-# common helpers shared by phases of the SubX translator
-
-# - some limits on the programs we can translate
-== data
-
-# maximum memory available for allocation
-Heap-size:
-  0x200000/imm32/2MB
-
-# maximum size of a single segment
-Segment-size:
-  0x80000/imm32/512KB
-
-# maximum size of input textual stream (spanning all segments)
-Input-size:
-  0x100000/imm32/1MB
-
-# maximum size of the 'labels' table in survey.subx
-Max-labels:
-  0x10000/imm32/4K-labels/64KB
-
-== 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
-
-# - managing tables
-# SubX has rudimentary support for tables.
-#
-# Each table is a stream of rows.
-#
-# Each row consists of a 4-byte row (address to a string) and a variable-size
-# value.
-#
-# Accessing the table performs a linear scan for a key string, and always
-# requires passing in the row size.
-#
-# Table primitives:
-#   get(stream, string, row-size)
-#     aborts if not found
-#   get-or-insert(stream, string, row-size)
-#     inserts if not found
-#   get-slice(stream, slice, row-size)
-#     aborts if not found
-#   leaky-get-or-insert-slice(stream, slice, row-size)
-#     inserts if not found
-
-# 'table' is a stream of (key, value) rows
-# keys are always strings (addresses; size 4 bytes)
-# values may be any type, but rows (key+value) always occupy 'row-size' bytes
-# scan 'table' for a row with a key 'key' and return the address of the corresponding value
-# if no row is found, abort
-get:  # table : (address stream {string, _}), key : (address string), row-size : int -> EAX : (address _)
-    # pseudocode:
-    #   curr = table->data
-    #   max = &table->data[table->write]
-    #   while curr < max
-    #     if string-equal?(key, *curr)
-    #       return curr+4
-    #     curr += row-size
-    #   abort
-    #
-    # . prolog
-    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
-    # ESI = table
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # curr/ECX = table->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
-    # max/EDX = table->data + table->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
-$get:search-loop:
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $get:abort/disp8
-    # if (string-equal?(key, *curr)) return curr+4
-    # . EAX = string-equal?(key, *curr)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return EAX = curr+4
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $get:mismatch/disp8
-    8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
-    eb/jump  $get:end/disp8
-$get:mismatch:
-    # curr += row-size
-    03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
-    # loop
-    eb/jump  $get:search-loop/disp8
-$get:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$get:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "get: key not found: "/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, key)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    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
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-get:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - setup: create a table with a couple of keys
-    # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
-    68/push  0x10/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # insert(table, "code", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    68/push  "code"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # insert(table, "data", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    68/push  "data"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-get:check1:
-    # EAX = get(table, "code", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    68/push  "code"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  get/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(EAX - table->data, 4, msg)
-    # . check-ints-equal(EAX - table, 16, msg)
-    # . . push args
-    68/push  "F - test-get/0"/imm32
-    68/push  0x10/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-$test-get:check2:
-    # EAX = get(table, "data", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    68/push  "data"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  get/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(EAX - table->data, 12, msg)
-    # . check-ints-equal(EAX - table, 24, msg)
-    # . . push args
-    68/push  "F - test-get/1"/imm32
-    68/push  0x18/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-$test-get:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# 'table' is a stream of (key, value) rows
-# keys are always strings (addresses; size 4 bytes)
-# values may be any type, but rows (key+value) always occupy 'row-size' bytes
-# scan 'table' for a row with a key 'key' and return the address of the corresponding value
-# if no row is found, abort
-get-slice:  # table : (address stream {string, _}), key : (address slice), row-size : int -> EAX : (address _)
-    # pseudocode:
-    #   curr = table->data
-    #   max = &table->data[table->write]
-    #   while curr < max
-    #     if slice-equal?(key, *curr)
-    #       return curr+4
-    #     curr += row-size
-    #   abort
-    #
-    # . prolog
-    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
-    # ESI = table
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # curr/ECX = table->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
-    # max/EDX = table->data + table->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
-$get-slice:search-loop:
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $get-slice:abort/disp8
-    # if (slice-equal?(key, *curr)) return curr+4
-    # . EAX = slice-equal?(key, *curr)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return EAX = curr+4
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $get-slice:mismatch/disp8
-    8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
-    eb/jump  $get-slice:end/disp8
-$get-slice:mismatch:
-    # curr += row-size
-    03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
-    # loop
-    eb/jump  $get-slice:search-loop/disp8
-$get-slice:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$get-slice:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "get-slice: key not found: "/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write-slice-buffered(Stderr, key)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    68/push  Stderr/imm32
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . flush(Stderr)
-    # . . push args
-    68/push  Stderr/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . _write(2/stderr, "\n")
-    # . . push args
-    68/push  "\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-get-slice:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # - setup: create a table with a couple of keys
-    # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
-    68/push  0x10/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # insert(table, "code", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    68/push  "code"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # insert(table, "data", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    68/push  "data"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-get-slice:check1:
-    # (EAX..EDX) = "code"
-    b8/copy-to-EAX  "code"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
-    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
-    05/add-to-EAX  4/imm32
-    # var slice/EDX = {EAX, EDX}
-    52/push-EDX
-    50/push-EAX
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # EAX = get-slice(table, "code", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  get-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
-    # . check-ints-equal(EAX - table, 16, msg)
-    # . . push args
-    68/push  "F - test-get-slice/0"/imm32
-    68/push  0x10/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-$test-get-slice:check2:
-    # (EAX..EDX) = "data"
-    b8/copy-to-EAX  "data"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
-    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
-    05/add-to-EAX  4/imm32
-    # var slice/EDX = {EAX, EDX}
-    52/push-EDX
-    50/push-EAX
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # EAX = get-slice(table, "data" slice, 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  get-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(EAX - table->data, 12, msg)
-    # . check-ints-equal(EAX - table, 24, msg)
-    # . . push args
-    68/push  "F - test-get-slice/1"/imm32
-    68/push  0x18/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-$test-get-slice:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# 'table' is a stream of (key, value) rows
-# keys are always strings (addresses; size 4 bytes)
-# values may be any type, but rows (key+value) always occupy 'row-size' bytes
-# scan 'table' for a row with a key 'key' and return the address of the corresponding value
-# if no row is found, save 'key' to the next available row
-# if there are no rows free, abort
-# return the address of the value
-# Beware: assume keys are immutable; they're inserted by reference
-# TODO: pass in an allocation descriptor
-get-or-insert:  # table : (address stream {string, _}), key : (address string), row-size : int -> EAX : (address _)
-    # pseudocode:
-    #   curr = table->data
-    #   max = &table->data[table->write]
-    #   while curr < max
-    #     if string-equal?(key, *curr)
-    #       return curr+4
-    #     curr += row-size
-    #   if table->write >= table->length
-    #     abort
-    #   zero-out(max, row-size)
-    #   *max = key
-    #   table->write += row-size
-    #   return max+4
-    #
-    # . prolog
-    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
-    # ESI = table
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # curr/ECX = table->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
-    # max/EDX = table->data + table->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
-$get-or-insert:search-loop:
-    # if (curr >= max) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $get-or-insert:not-found/disp8
-    # if (string-equal?(key, *curr)) return curr+4
-    # . EAX = string-equal?(key, *curr)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return EAX = curr+4
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $get-or-insert:mismatch/disp8
-    8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
-    eb/jump  $get-or-insert:end/disp8
-$get-or-insert:mismatch:
-    # curr += row-size
-    03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
-    # loop
-    eb/jump  $get-or-insert:search-loop/disp8
-$get-or-insert:not-found:
-    # result/EAX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    # if (table->write >= table->length) abort
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(ESI+8)
-    73/jump-if-greater-or-equal-unsigned  $get-or-insert:abort/disp8
-    # zero-out(max, row-size)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    52/push-EDX
-    # . . call
-    e8/call  zero-out/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # *max = key
-    # . EAX = key
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
-    # . *max = EAX
-    89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDX
-    # table->write += row-size
-    # . EAX = row-size
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
-    # . table->write += EAX
-    01/add                          0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # add EAX to *ESI
-    # return max+4
-    # . EAX = max
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy EDX to EAX
-    # . EAX += 4
-    05/add-to-EAX  4/imm32
-$get-or-insert:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$get-or-insert:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "get-or-insert: table is full\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-get-or-insert:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
-    68/push  0x10/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-$test-get-or-insert:first-call:
-    # - start with an empty table, insert one key, verify that it was inserted
-    # EAX = get-or-insert(table, "code", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    68/push  "code"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
-    # . check-ints-equal(EAX - table, 16, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert/0"/imm32
-    68/push  0x10/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-$test-get-or-insert:check2:
-    # check-ints-equal(table->write, row-size = 8, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert/1"/imm32
-    68/push  8/imm32/row-size
-    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
-    # check-string-equal(*table->data, "code", msg)
-    # . . push args
-    68/push  "F - test-get-or-insert/2"/imm32
-    68/push  "code"/imm32
-    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
-    # . . call
-    e8/call  check-string-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-get-or-insert:second-call:
-    # - insert the same key again, verify that it was reused
-    # EAX = get-or-insert(table, "code", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    68/push  "code"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(EAX - table->data, 4, msg)
-    # . check-ints-equal(EAX - table, 16, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert/3"/imm32
-    68/push  0x10/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-    # no new row inserted
-    # . check-ints-equal(table->write, row-size = 8, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert/4"/imm32
-    68/push  8/imm32/row-size
-    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
-    # check-string-equal(*table->data, "code", msg)
-    # . . push args
-    68/push  "F - test-get-or-insert/5"/imm32
-    68/push  "code"/imm32
-    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
-    # . . call
-    e8/call  check-string-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-get-or-insert:third-call:
-    # - insert a new key, verify that it was inserted
-    # EAX = get-or-insert(table, "data", 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    68/push  "data"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # table gets a new row
-    # check-ints-equal(EAX - table->data, 12, msg)  # second row's value slot returned
-    # . check-ints-equal(EAX - table, 24, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert/6"/imm32
-    68/push  0x18/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-    # check-ints-equal(table->write, 2 rows = 16, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert/7"/imm32
-    68/push  0x10/imm32/two-rows
-    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
-    # check-string-equal(*table->data+8, "data", msg)
-    # check-string-equal(*(table+20), "data", msg)
-    # . . push args
-    68/push  "F - test-get-or-insert/8"/imm32
-    68/push  "data"/imm32
-    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0x14/disp8      .                 # push *(ECX+20)
-    # . . call
-    e8/call  check-string-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-get-or-insert:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# 'table' is a stream of (key, value) rows
-# keys are always strings (addresses; size 4 bytes)
-# values may be any type, but rows (key+value) always occupy 'row-size' bytes
-# scan 'table' for a row with a key 'key' and return the address of the corresponding value
-# if no row is found, save 'key' in the next available row
-# if there are no rows free, abort
-# WARNING: leaks memory
-# TODO: pass in an allocation descriptor
-leaky-get-or-insert-slice:  # table : (address stream {string, _}), key : (address slice), row-size : int -> EAX : (address _)
-    # pseudocode:
-    #   curr = table->data
-    #   max = &table->data[table->write]
-    #   while curr < max
-    #     if slice-equal?(key, *curr)
-    #       return curr+4
-    #     curr += row-size
-    #   if table->write >= table->length
-    #     abort
-    #   zero-out(max, row-size)
-    #   *max = slice-to-string(Heap, key)
-    #   table->write += row-size
-    #   return max+4
-    #
-    # . prolog
-    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
-    # ESI = table
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # curr/ECX = table->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
-    # max/EDX = table->data + table->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
-$leaky-get-or-insert-slice:search-loop:
-    # if (curr >= max) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $leaky-get-or-insert-slice:not-found/disp8
-    # if (slice-equal?(key, *curr)) return curr+4
-    # . EAX = slice-equal?(key, *curr)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return EAX = curr+4
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $leaky-get-or-insert-slice:mismatch/disp8
-    8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
-    eb/jump  $leaky-get-or-insert-slice:end/disp8
-$leaky-get-or-insert-slice:mismatch:
-    # curr += row-size
-    03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
-    # loop
-    eb/jump  $leaky-get-or-insert-slice:search-loop/disp8
-$leaky-get-or-insert-slice:not-found:
-    # result/EAX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    # if (table->write >= table->length) abort
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(ESI+8)
-    7d/jump-if-greater-or-equal  $leaky-get-or-insert-slice:abort/disp8
-    # zero-out(max, row-size)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    52/push-EDX
-    # . . call
-    e8/call  zero-out/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # *max = slice-to-string(Heap, key)
-    # . EAX = slice-to-string(Heap, key)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    68/push  Heap/imm32
-    # . . call
-    e8/call  slice-to-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . *max = EAX
-    89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDX
-    # table->write += row-size
-    # . EAX = row-size
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
-    # . table->write += EAX
-    01/add                          0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # add EAX to *ESI
-    # return max+4
-    # . EAX = max
-    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy EDX to EAX
-    # . EAX += 4
-    05/add-to-EAX  4/imm32
-$leaky-get-or-insert-slice:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$leaky-get-or-insert-slice:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "leaky-get-or-insert-slice: table is full\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-leaky-get-or-insert-slice:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
-    68/push  0x10/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # (EAX..EDX) = "code"
-    b8/copy-to-EAX  "code"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
-    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
-    05/add-to-EAX  4/imm32
-    # var slice/EDX = {EAX, EDX}
-    52/push-EDX
-    50/push-EAX
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-$test-leaky-get-or-insert-slice:first-call:
-    # - start with an empty table, insert one key, verify that it was inserted
-    # EAX = leaky-get-or-insert-slice(table, "code" slice, 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  leaky-get-or-insert-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
-    # . check-ints-equal(EAX - table, 16, msg)
-    # . . push args
-    68/push  "F - test-leaky-get-or-insert-slice/0"/imm32
-    68/push  0x10/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-$test-leaky-get-or-insert-slice:check2:
-    # check-ints-equal(table->write, row-size = 8, msg)
-    # . . push args
-    68/push  "F - test-leaky-get-or-insert-slice/1"/imm32
-    68/push  8/imm32/row-size
-    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
-    # check-string-equal(*table->data, "code", msg)
-    # . . push args
-    68/push  "F - test-leaky-get-or-insert-slice/2"/imm32
-    68/push  "code"/imm32
-    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
-    # . . call
-    e8/call  check-string-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-leaky-get-or-insert-slice:second-call:
-    # - insert the same key again, verify that it was reused
-    # EAX = leaky-get-or-insert-slice(table, "code" slice, 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  leaky-get-or-insert-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # check-ints-equal(EAX - table->data, 4, msg)
-    # . check-ints-equal(EAX - table, 16, msg)
-    # . . push args
-    68/push  "F - test-leaky-get-or-insert-slice/3"/imm32
-    68/push  0x10/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-    # no new row inserted
-    # . check-ints-equal(table->write, row-size = 8, msg)
-    # . . push args
-    68/push  "F - test-leaky-get-or-insert-slice/4"/imm32
-    68/push  8/imm32/row-size
-    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
-    # check-string-equal(*table->data, "code", msg)
-    # . . push args
-    68/push  "F - test-leaky-get-or-insert-slice/5"/imm32
-    68/push  "code"/imm32
-    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
-    # . . call
-    e8/call  check-string-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-leaky-get-or-insert-slice:third-call:
-    # - insert a new key, verify that it was inserted
-    # (EAX..EDX) = "data"
-    b8/copy-to-EAX  "data"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
-    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
-    05/add-to-EAX  4/imm32
-    # var slice/EDX = {EAX, EDX}
-    52/push-EDX
-    50/push-EAX
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # EAX = leaky-get-or-insert-slice(table, "data" slice, 8 bytes per row)
-    # . . push args
-    68/push  8/imm32/row-size
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  leaky-get-or-insert-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # table gets a new row
-    # check-ints-equal(EAX - table->data, 12, msg)  # second row's value slot returned
-    # . check-ints-equal(EAX - table, 24, msg)
-    # . . push args
-    68/push  "F - test-leaky-get-or-insert-slice/6"/imm32
-    68/push  0x18/imm32
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # 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
-    # check-ints-equal(table->write, 2 rows = 16, msg)
-    # . . push args
-    68/push  "F - test-leaky-get-or-insert-slice/7"/imm32
-    68/push  0x10/imm32/two-rows
-    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
-    # check-string-equal(*table->data+8, "data", msg)
-    # check-string-equal(*(table+20), "data", msg)
-    # . . push args
-    68/push  "F - test-leaky-get-or-insert-slice/8"/imm32
-    68/push  "data"/imm32
-    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0x14/disp8      .                 # push *(ECX+20)
-    # . . call
-    e8/call  check-string-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-leaky-get-or-insert-slice:end:
-    # . epilog
-    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
-# return empty string on reaching end of file
-next-word:  # line : (address stream byte), out : (address slice)
-    # . prolog
-    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(line, ' ')
-    # . . push args
-    68/push  0x20/imm32/space
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  skip-chars-matching/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/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-lesser  $next-word:check-for-comment/disp8
-    # . return out = {0, 0}
-    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-not-equal  $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
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-word:
-    # . prolog
-    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 = {0, 0}
-    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
-    # . epilog
-    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:
-    # . prolog
-    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 = {0, 0}
-    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
-    # . epilog
-    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:
-    # . prolog
-    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 = {0, 0}
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# write an entire stream's contents to a buffered-file
-# ways to do this:
-#   - construct a 'maximal slice' and pass it to write-slice-buffered
-#   - flush the buffered-file and pass the stream directly to its fd (disabling buffering)
-# we'll go with the first way for now
-write-stream-data:  # f : (address buffered-file), s : (address stream) -> <void>
-    # . prolog
-    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
-    # ESI = s
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # var slice/ECX = {s->data, 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
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy ESI+12 to EAX
-    50/push-EAX
-    # . ECX = ESP
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # write-slice-buffered(f, slice)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$write-stream-data:end:
-    # . restore locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5e/pop-to-ESI
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-write-stream-data:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize input
-    # . write(_test-input-stream, "abcd")
-    # . . push args
-    68/push  "abcd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-stream-data(_test-output-buffered-file, _test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check that the write happened as expected
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . check-stream-equal(_test-output-stream, "abcd", msg)
-    # . . push args
-    68/push  "F - test-write-stream-data"/imm32
-    68/push  "abcd"/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-has-metadata?:  # word : (address slice), s : (address string) -> EAX : boolean
-    # pseudocode:
-    #   var twig : &slice = next-token-from-slice(word->start, word->end, '/')  # skip name
-    #   curr = twig->end
-    #   while true
-    #     twig = next-token-from-slice(curr, word->end, '/')
-    #     if (twig.empty()) break
-    #     if (slice-equal?(twig, s)) return true
-    #     curr = twig->end
-    #   return false
-    # . prolog
-    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 = word
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # EDX = word->end
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ESI+4) to EDX
-    # var twig/EDI : (address slice) = {0, 0}
-    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(word->start, word->end, '/', twig)
-    # . . push args
-    57/push-EDI
-    68/push  0x2f/imm32/slash
-    52/push-EDX
-    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
-    # . . 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
-    # curr/ECX = twig->end
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
-$has-metadata?:loop:
-    # next-token-from-slice(curr, word->end, '/', twig)
-    # . . push args
-    57/push-EDI
-    68/push  0x2f/imm32/slash
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  next-token-from-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # if (slice-empty?(twig)) return false
-    # . EAX = slice-empty?(twig)
-    # . . push args
-    57/push-EDI
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) return false
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $has-metadata?:false/disp8
-    # if (slice-equal?(twig, s)) return true
-    # . EAX = slice-equal?(twig, s)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    57/push-EDI
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return true
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $has-metadata?:true/disp8
-    # curr = twig->end
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
-    eb/jump  $has-metadata?:loop/disp8
-$has-metadata?:true:
-    b8/copy-to-EAX  1/imm32/true
-    eb/jump  $has-metadata?:end/disp8
-$has-metadata?:false:
-    b8/copy-to-EAX  0/imm32/false
-$has-metadata?:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-has-metadata-true:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # (EAX..ECX) = "ab/imm32"
-    b8/copy-to-EAX  "ab/imm32"/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 in/ESI : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
-    # EAX = has-metadata?(ESI, "imm32")
-    # . . push args
-    68/push  "imm32"/imm32
-    56/push-ESI
-    # . . call
-    e8/call  has-metadata?/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-has-metadata-true"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-has-metadata-false:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # (EAX..ECX) = "ab/c"
-    b8/copy-to-EAX  "ab/c"/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 in/ESI : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
-    # EAX = has-metadata?(ESI, "d")
-    # . . push args
-    68/push  "d"/imm32
-    56/push-ESI
-    # . . call
-    e8/call  has-metadata?/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-has-metadata-false"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-has-metadata-ignore-name:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # (EAX..ECX) = "a/b"
-    b8/copy-to-EAX  "a/b"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
-    05/add-to-EAX  4/imm32
-    # var in/ESI : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
-    # EAX = has-metadata?(ESI, "a")
-    # . . push args
-    68/push  "a"/imm32
-    56/push-ESI
-    # . . call
-    e8/call  has-metadata?/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-has-metadata-ignore-name"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-has-metadata-multiple-true:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # (EAX..ECX) = "a/b/c"
-    b8/copy-to-EAX  "a/b/c"/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 in/ESI : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
-    # EAX = has-metadata?(ESI, "c")
-    # . . push args
-    68/push  "c"/imm32
-    56/push-ESI
-    # . . call
-    e8/call  has-metadata?/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-has-metadata-multiple-true"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-has-metadata-multiple-false:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # (EAX..ECX) = "a/b/c"
-    b8/copy-to-EAX  "a/b/c"/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 in/ESI : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
-    # EAX = has-metadata?(ESI, "d")
-    # . . push args
-    68/push  "d"/imm32
-    56/push-ESI
-    # . . call
-    e8/call  has-metadata?/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-has-metadata-multiple-false"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# If datum of 'word' is not a valid name, it must be a hex int. Parse and print
-# it in 'width' bytes of hex, least significant first.
-# Otherwise just print the entire word including metadata.
-# Always print a trailing space.
-emit:  # out : (address buffered-file), word : (address slice), width : int -> <void>
-    # . prolog
-    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
-    # ESI = word
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # var name/EDI : (address slice) = {0, 0}
-    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
-    # datum = next-token-from-slice(word->start, word->end, '/')
-    # . . push args
-    57/push-EDI
-    68/push  0x2f/imm32/slash
-    ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # push *(ESI+4)
-    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
-    # . . call
-    e8/call  next-token-from-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # if (is-valid-name?(datum)) write-slice-buffered(out, word) and return
-    # . EAX = is-valid-name?(name)
-    # . . push args
-    57/push-EDI
-    # . . call
-    e8/call  is-valid-name?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0)
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit:hex-int/disp8
-$emit:name:
-    # . write-slice-buffered(out, word)
-    # . . push args
-    56/push-ESI
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write-buffered(out, " ")
-    # . . push args
-    68/push  " "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . return
-    eb/jump  $emit:end/disp8
-    # otherwise emit-hex(out, parse-hex-int(datum), width)
-    #   (Weird shit can happen here if the datum of 'word' isn't either a valid
-    #   name or a hex number, but we're only going to be passing in real legal
-    #   programs. We just want to make sure that valid names aren't treated as
-    #   (valid) hex numbers.)
-$emit:hex-int:
-    # . value/EAX = parse-hex-int(datum)
-    # . . push args
-    57/push-EDI
-    # . . call
-    e8/call  parse-hex-int/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . emit-hex(out, value, width)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    50/push-EAX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$emit:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-number:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (EAX..ECX) = "30"
-    b8/copy-to-EAX  "30"/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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit(_test-output-buffered-file, slice, 1)
-    # . . push args
-    68/push  1/imm32
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream, "30 ", msg)
-    # . . push args
-    68/push  "F - test-emit-number/1"/imm32
-    68/push  "30 "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-negative-number:
-    # test support for sign-extending negative numbers
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (EAX..ECX) = "-2"
-    b8/copy-to-EAX  "-2"/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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream, "fe ff ", msg)
-    # . . push args
-    68/push  "F - test-emit-number/1"/imm32
-    68/push  "fe ff "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-number-with-metadata:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (EAX..ECX) = "-2/foo"
-    b8/copy-to-EAX  "-2/foo"/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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # the '/foo' will have no impact on the output
-    # check-stream-equal(_test-output-stream, "fe ff ", msg)
-    # . . push args
-    68/push  "F - test-emit-number-with-metadata"/imm32
-    68/push  "fe ff "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-non-number:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (EAX..ECX) = "xyz"
-    b8/copy-to-EAX  "xyz"/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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream, "xyz", msg)
-    # . . push args
-    68/push  "F - test-emit-non-number"/imm32
-    68/push  "xyz "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-non-number-with-metadata:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (EAX..ECX) = "xyz/"
-    b8/copy-to-EAX  "xyz/"/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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream, "xyz/", msg)
-    # . . push args
-    68/push  "F - test-emit-non-number-with-metadata"/imm32
-    68/push  "xyz/ "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-non-number-with-all-hex-digits-and-metadata:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # (EAX..ECX) = "abcd/xyz"
-    b8/copy-to-EAX  "abcd/xyz"/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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . 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-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # check-stream-equal(_test-output-stream, "abcd/xyz")
-    # . . push args
-    68/push  "F - test-emit-non-number-with-all-hex-digits"/imm32
-    68/push  "abcd/xyz "/imm32
-    68/push  _test-output-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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# conditions for 'valid' names that are not at risk of looking like hex numbers
-# keep in sync with the rules in labels.cc
-#: - if it starts with a digit, it's treated as a number. If it can't be
-#:   parsed as hex it will raise an error.
-#: - if it starts with '-' it's treated as a number.
-#: - if it starts with '0x' it's treated as a number. (redundant)
-#: - if it's two characters long, it can't be a name. Either it's a hex
-#:   byte, or it raises an error.
-is-valid-name?:  # in : (address slice) -> EAX : boolean
-    # . prolog
-    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 = in
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # start/ECX = in->start
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    # end/EAX = in->end
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
-$is-valid-name?:check0:
-    # if (start >= end) return false
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # compare ECX with EAX
-    73/jump-if-greater-or-equal-unsigned  $is-valid-name?:false/disp8
-$is-valid-name?:check1:
-    # EAX -= ECX
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
-    # if (EAX == 2) return false
-    3d/compare-EAX-and  2/imm32
-    74/jump-if-equal  $is-valid-name?:false/disp8
-$is-valid-name?:check2:
-    # c/EAX = *ECX
-    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
-    # if (c == "-") return false
-    3d/compare-EAX-and  2d/imm32/-
-    74/jump-if-equal  $is-valid-name?:false/disp8
-$is-valid-name?:check3a:
-    # if (c < "0") return true
-    3d/compare-EAX-with  30/imm32/0
-    7c/jump-if-lesser  $is-valid-name?:true/disp8
-$is-valid-name?:check3b:
-    # if (c > "9") return true
-    3d/compare-EAX-with  39/imm32/9
-    7f/jump-if-greater  $is-valid-name?:true/disp8
-$is-valid-name?:false:
-    # return false
-    b8/copy-to-EAX  0/imm32/false
-    eb/jump  $is-valid-name?:end/disp8
-$is-valid-name?:true:
-    # return true
-    b8/copy-to-EAX  1/imm32/true
-$is-valid-name?:end:
-    # . restore registers
-    5e/pop-to-ESI
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-is-valid-name-digit-prefix:
-    # . prolog
-    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 = {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-valid-name?(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  is-valid-name?/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-valid-name-digit-prefix"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-is-valid-name-negative-prefix:
-    # . prolog
-    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 = {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-valid-name?(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  is-valid-name?/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-valid-name-negative-prefix"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-is-valid-name-0x-prefix:
-    # . prolog
-    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 = {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-valid-name?(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  is-valid-name?/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-valid-name-0x-prefix"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-is-valid-name-starts-with-pre-digit:
-    # . prolog
-    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 = {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-valid-name?(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  is-valid-name?/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-valid-name-starts-with-pre-digit"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-is-valid-name-starts-with-post-digit:
-    # . prolog
-    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 = {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-valid-name?(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  is-valid-name?/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-valid-name-starts-with-post-digit"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-is-valid-name-starts-with-digit:
-    # . prolog
-    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 = {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-valid-name?(slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  is-valid-name?/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-valid-name-starts-with-digit"/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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# print 'n' in hex in 'width' bytes in lower-endian order, with a space after every byte
-emit-hex:  # out : (address buffered-file), n : int, width : int -> <void>
-    # . prolog
-    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
-    # EDI = out
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-    # EBX = n
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
-    # EDX = width
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0x10/disp8      .                 # copy *(EBP+16) to EDX
-    # var curr/ECX = 0
-    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-$emit-hex:loop:
-    # if (curr >= width) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    7d/jump-if-greater-or-equal  $emit-hex:end/disp8
-    # print-byte-buffered(out, EBX)
-    # . . push args
-    53/push-EBX
-    57/push-EDI
-    # . . call
-    e8/call  print-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-byte-buffered(out, ' ')
-    # . . push args
-    68/push  0x20/imm32/space
-    57/push-EDI
-    # . . call
-    e8/call  write-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EBX = EBX >> 8
-    c1/shift    5/subop/logic-right 3/mod/direct    3/rm32/EBX    .           .             .           .           .               8/imm8            # shift EBX right by 8 bits, while padding zeroes
-$emit-hex:continue:
-    # ++curr
-    41/increment-ECX
-    eb/jump  $emit-hex:loop/disp8
-$emit-hex:end:
-    # . restore registers
-    5f/pop-to-EDI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-hex-single-byte:
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # emit-hex(_test-output-buffered-file, 0xab, 1)
-    # . . push args
-    68/push  1/imm32
-    68/push  0xab/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(*_test-output-stream->data, 'ab ', msg)
-    # . . push args
-    68/push  "F - test-emit-hex-single-byte"/imm32
-    68/push  0x206261/imm32
-    # . . push *_test-output-stream->data
-    b8/copy-to-EAX  _test-output-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-emit-hex-multiple-byte:
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # emit-hex(_test-output-buffered-file, 0x1234, 2)
-    # . . push args
-    68/push  2/imm32
-    68/push  0x1234/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream, "34 12 ", msg)
-    # . . push args
-    68/push  "F - test-emit-hex-multiple-byte/1"/imm32
-    68/push  "34 12 "/imm32
-    68/push  _test-output-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-emit-hex-zero-pad:
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # emit-hex(_test-output-buffered-file, 0xab, 2)
-    # . . push args
-    68/push  2/imm32
-    68/push  0xab/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check(_test-output-stream->data == 'ab 00 ')
-    # . . push args
-    68/push  "F - test-emit-hex-zero-pad/1"/imm32
-    68/push  "ab 00 "/imm32
-    68/push  _test-output-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-emit-hex-negative:
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # emit-hex(_test-output-buffered-file, -1, 2)
-    # . . push args
-    68/push  2/imm32
-    68/push  -1/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-stream-equal(_test-output-stream == "ff ff ")
-    # . . push args
-    68/push  "F - test-emit-hex-negative/1"/imm32
-    68/push  "ff ff "/imm32
-    68/push  _test-output-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
-
-# print 'arr' in hex with a space after every byte
-emit-hex-array:  # out : (address buffered-file), arr : (address array byte) -> <void>
-    # . prolog
-    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
-    # EDI = out
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-    # EDX = arr  # <== 0xbdffffe4
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
-    # curr/ECX = arr->data
-    8d/copy-address                 1/mod/*+disp8   2/rm32/EDX    .           .             .           1/r32/ECX   4/disp8         .                 # copy EDX+4 to ECX
-    # max/EDX = arr->data + arr->length
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # copy *EDX to EDX
-    01/add                          3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # add ECX to EDX
-    # EAX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-$emit-hex-array:loop:
-    # if (curr >= width) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    73/jump-if-greater-or-equal-unsigned  $emit-hex-array:end/disp8
-    # emit-hex(out, *curr, width=1)
-    # . . push args
-    68/push  1/imm32/width
-    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
-    50/push-EAX
-    57/push-EDI
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # ++curr
-    41/increment-ECX
-    eb/jump  $emit-hex-array:loop/disp8
-$emit-hex-array:end:
-    # . restore registers
-    5f/pop-to-EDI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-hex-array:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # var arr/ECX (address array byte) = [01, 02, 03]
-    68/push  0x00030201/imm32  # bytes 01 02 03
-    68/push  3/imm32/length
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # emit-hex-array(_test-output-buffered-file, arr)
-    # . . push args
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex-array/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-    # check-next-stream-line-equal(_test-output-stream, "01 02 03 ", msg)
-    # . . push args
-    68/push  "F - test-emit-hex-array"/imm32
-    68/push  "01 02 03 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-compute-width: # word : (address array byte) -> EAX : int
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    # EAX = word
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to ECX
-    # ECX = word + word->length
-    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
-    # EAX = word->data
-    05/add-to-EAX  4/imm32
-    # var in/ECX : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # return compute-width-of-slice(ECX)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  compute-width-of-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$compute-width:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-compute-width-of-slice: # s : (address slice) -> EAX : int
-    # . prolog
-    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 (has-metadata?(word, "imm32")) return 4
-    # . EAX = has-metadata?(word, "imm32")
-    # . . push args
-    68/push  "imm32"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return 4
-    3d/compare-EAX-and  0/imm32
-    b8/copy-to-EAX  4/imm32         # ZF is set, so we can overwrite EAX now
-    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
-    # if (has-metadata?(word, "disp32")) return 4
-    # . EAX = has-metadata?(word, "disp32")
-    # . . push args
-    68/push  "disp32"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return 4
-    3d/compare-EAX-and  0/imm32
-    b8/copy-to-EAX  4/imm32         # ZF is set, so we can overwrite EAX now
-    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
-    # if (has-metadata?(word, "imm16")) return 2
-    # . EAX = has-metadata?(word, "imm16")
-    # . . push args
-    68/push  "imm16"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return 2
-    3d/compare-EAX-and  0/imm32
-    b8/copy-to-EAX  2/imm32         # ZF is set, so we can overwrite EAX now
-    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
-    # if (has-metadata?(word, "disp16")) return 2
-    # . EAX = has-metadata?(word, "disp16")
-    # . . push args
-    68/push  "disp16"/imm32
-    51/push-ECX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return 2
-    3d/compare-EAX-and  0/imm32
-    b8/copy-to-EAX  2/imm32         # ZF is set, so we can overwrite EAX now
-    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
-    # otherwise return 1
-    b8/copy-to-EAX  1/imm32
-$compute-width-of-slice:end:
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-compute-width:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-$test-compute-width:imm8:
-    # EAX = compute-width("0x2/imm8")
-    # . . push args
-    68/push  "0x2/imm8"/imm32
-    # . . call
-    e8/call  compute-width/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-compute-width: 0x2/imm8"/imm32
-    50/push-EAX
-    68/push  1/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-compute-width:imm16:
-    # EAX = compute-width("4/imm16")
-    # . . push args
-    68/push  "4/imm16"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 2, msg)
-    # . . push args
-    68/push  "F - test-compute-width: 4/imm16"/imm32
-    50/push-EAX
-    68/push  2/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-compute-width:imm32:
-    # EAX = compute-width("4/imm32")
-    # . . push args
-    68/push  "4/imm32"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 4, msg)
-    # . . push args
-    68/push  "F - test-compute-width: 4/imm32"/imm32
-    50/push-EAX
-    68/push  4/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-compute-width:disp8:
-    # EAX = compute-width("foo/disp8")
-    # . . push args
-    68/push  "foo/disp8"/imm32
-    # . . call
-    e8/call  compute-width/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-compute-width: foo/disp8"/imm32
-    50/push-EAX
-    68/push  1/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-compute-width:disp16:
-    # EAX = compute-width("foo/disp16")
-    # . . push args
-    68/push  "foo/disp16"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 2, msg)
-    # . . push args
-    68/push  "F - test-compute-width: foo/disp16"/imm32
-    50/push-EAX
-    68/push  2/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-compute-width:disp32:
-    # EAX = compute-width("foo/disp32")
-    # . . push args
-    68/push  "foo/disp32"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 4, msg)
-    # . . push args
-    68/push  "F - test-compute-width: foo/disp32"/imm32
-    50/push-EAX
-    68/push  4/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-compute-width:no-metadata:
-    # EAX = compute-width("45")
-    # . . push args
-    68/push  "45"/imm32
-    # . . call
-    e8/call  compute-width/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-compute-width: 45 (no metadata)"/imm32
-    50/push-EAX
-    68/push  1/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-is-label?: # word : (address slice) -> EAX : boolean
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    # ECX = word
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
-    # ECX = word->end
-    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ECX+4) to ECX
-    # return *(word->end - 1) == ':'
-    # . EAX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    # . EAX = *((char *) word->end - 1)
-    8a/copy-byte                    1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/AL    -1/disp8         .                 # copy byte at *(ECX-1) to AL
-    # . return (EAX == ':')
-    3d/compare-EAX-and  0x3a/imm32/colon
-    b8/copy-to-EAX  1/imm32/true
-    74/jump-if-equal  $is-label?:end/disp8
-    b8/copy-to-EAX  0/imm32/false
-$is-label?:end:
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-is-label?:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-$test-is-label?:true:
-    # (EAX..ECX) = "AAA:"
-    b8/copy-to-EAX  "AAA:"/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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # is-label?(slice/ECX)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  is-label?/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-label?: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
-$test-is-label?:false:
-    # (EAX..ECX) = "AAA"
-    b8/copy-to-EAX  "AAA"/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 = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # is-label?(slice/ECX)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  is-label?/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-label?: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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data
-
-_test-input-stream:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    0x100/imm32  # 256 bytes
-    # data (16 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
-
-# a test buffered file for _test-input-stream
-_test-input-buffered-file:
-    # file descriptor or (address stream)
-    _test-input-stream/imm32
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    6/imm32
-    # data
-    00 00 00 00 00 00  # 6 bytes
-
-_test-output-stream:
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    0x200/imm32  # 512 bytes
-    # data (32 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
-
-# a test buffered file for _test-output-stream
-_test-output-buffered-file:
-    # file descriptor or (address stream)
-    _test-output-stream/imm32
-    # current write index
-    0/imm32
-    # current read index
-    0/imm32
-    # length
-    6/imm32
-    # data
-    00 00 00 00 00 00  # 6 bytes
-
-_test-data-segment:
-  64/d 61/a 74/t 61/a
-_test-data-segment-end:
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/apps/survey b/subx/apps/survey
deleted file mode 100755
index 621efa5c..00000000
--- a/subx/apps/survey
+++ /dev/null
Binary files differdiff --git a/subx/apps/survey.subx b/subx/apps/survey.subx
deleted file mode 100644
index 15957fa2..00000000
--- a/subx/apps/survey.subx
+++ /dev/null
@@ -1,4787 +0,0 @@
-# Assign addresses (co-ordinates) to instructions (landmarks) in a program
-# (landscape).
-# Use the addresses assigned to:
-#   a) replace labels
-#   b) add segment headers with addresses and offsets correctly filled in
-#
-# To build (from the subx/ directory):
-#   $ ./subx translate *.subx apps/survey.subx -o apps/survey
-#
-# The expected input is a stream of bytes with segment headers, comments and
-# some interspersed labels.
-#   $ cat x
-#   == code 0x1
-#   l1:
-#   aa bb l1/imm8
-#   cc dd l2/disp32
-#   l2:
-#   ee foo/imm32
-#   == data 0x10
-#   foo:
-#     00
-#
-# The output is the stream of bytes without segment headers or label definitions,
-# and with label references replaced with numeric values/displacements.
-#
-#   $ cat x  |./subx run apps/assort
-#   ...ELF header bytes...
-#   # ELF header above will specify that code segment begins at this offset
-#   aa bb nn  # some computed address
-#   cc dd nn nn nn nn  # some computed displacement
-#   ee nn nn nn nn  # some computed address
-#   # ELF header above will specify that data segment begins at this offset
-#   00
-#
-# The ELF format has some persnickety constraints on the starting addresses of
-# segments, so input headers are treated as guidelines and adjusted in the
-# output.
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # Heap = new-segment(Heap-size)
-    # . . push args
-    68/push  Heap/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # initialize-trace-stream(256KB)
-    # . . push args
-    68/push  0x40000/imm32/256KB
-    # . . call
-    e8/call  initialize-trace-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-
-    # run tests if necessary, convert stdin if not
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # initialize heap
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # . argc > 1
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # . argv[1] == "test"
-    # . . push args
-    68/push  "test"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  kernel-string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check result
-    3d/compare-EAX-and  1/imm32
-    75/jump-if-not-equal  $run-main/disp8
-    # . run-tests()
-    e8/call  run-tests/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    eb/jump  $main:end/disp8
-$run-main:
-    # - otherwise convert stdin
-    # convert(Stdin, Stdout)
-    # . . push args
-    68/push  Stdout/imm32
-    68/push  Stdin/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-$main:end:
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# data structures:
-#   segment-info: {address, file-offset, size}            (12 bytes)
-#   segments: (address stream {string, segment-info})     (16 bytes per row)
-#   label-info: {segment-name, segment-offset, address}   (12 bytes)
-#   labels: (address stream {string, label-info})         (16 bytes per row)
-# these are all inefficient; use sequential scans for lookups
-
-convert:  # infile : (address buffered-file), out : (address buffered-file) -> <void>
-    # pseudocode
-    #   var in : (address stream byte) = stream(4096)
-    #   slurp(infile, in)
-    #   var segments = new-stream(10 rows, 16 bytes each)
-    #   var labels = new-stream(Max-labels rows, 16 bytes each)
-    #   compute-offsets(in, segments, labels)
-    #   compute-addresses(segments, labels)
-    #   rewind-stream(in)
-    #   emit-output(in, out, segments, labels)
-    #
-    # . prolog
-    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
-    # var segments/ECX = stream(10 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
-    68/push  0xa0/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var labels/EDX = stream(Max-labels * 16)
-    # . data
-    2b/subtract                     0/mod/indirect  5/rm32/.disp32            .             .           4/r32/ESP   Max-labels/disp32                 # subtract *Max-labels from ESP
-    # . length
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Max-labels/disp32                 # push *Max-labels
-    # . read
-    68/push  0/imm32/read
-    # . write
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # var in/ESI = stream(Input-size * 1)
-    # . data
-    2b/subtract                     0/mod/indirect  5/rm32/.disp32            .             .           4/r32/ESP   Input-size/disp32                 # subtract *Input-size from ESP
-    # . length
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Input-size/disp32                 # push *Input-size
-    # . read
-    68/push  0/imm32/read
-    # . write
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
-#?     # dump labels->write {{{
-#?     # . write(2/stderr, "labels->write right after initialization: ")
-#?     # . . push args
-#?     68/push  "labels->write right after initialization: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . save EAX
-#?     50/push-EAX
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . . restore EAX
-#?     58/pop-to-EAX
-#?     # . print-int32-buffered(Stderr, labels->write)
-#?     # . . push args
-#? $watch-1:
-#?     ff          6/subop/push        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               .                 # push *EDX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  print-int32-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "\n")
-#?     # . . push args
-#?     68/push  "\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-#?     # write(2/stderr, "slurp in\n") {{{
-#?     # . . push args
-#?     68/push  "slurp in\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
-#?     # }}}
-    # slurp(infile, in)
-    # . . push args
-    56/push-ESI
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  slurp/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump labels->write {{{
-#?     # . write(2/stderr, "labels->write after slurp: ")
-#?     # . . push args
-#?     68/push  "labels->write after slurp: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . save EAX
-#?     50/push-EAX
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . . restore EAX
-#?     58/pop-to-EAX
-#?     # . print-int32-buffered(Stderr, labels->write)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               .                 # push *EDX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  print-int32-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "\n")
-#?     # . . push args
-#?     68/push  "\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-#?     # dump in {{{
-#?     # . write(2/stderr, "in: ")
-#?     # . . push args
-#?     68/push  "in: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # write-stream(2/stderr, in)
-#?     # . . push args
-#?     56/push-ESI
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(in)
-#?     # . . push args
-#?     56/push-ESI
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-#?     # write(2/stderr, "compute-offsets\n") {{{
-#?     # . . push args
-#?     68/push  "compute-offsets\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
-#?     # }}}
-    # compute-offsets(in, segments, labels)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    56/push-ESI
-    # . . call
-    e8/call  compute-offsets/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-#?     # write(2/stderr, "compute-addresses\n") {{{
-#?     # . . push args
-#?     68/push  "compute-addresses\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
-#?     # }}}
-    # compute-addresses(segments, labels)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  compute-addresses/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
-    # rewind-stream(in)
-    # . . push args
-    56/push-ESI
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # write(2/stderr, "emit-output\n") {{{
-#?     # . . push args
-#?     68/push  "emit-output\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
-#?     # }}}
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-#?     # dump labels->write {{{
-#?     # . write(2/stderr, "labels->write after rewinding input: ")
-#?     # . . push args
-#?     68/push  "labels->write after rewinding input: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . save EAX
-#?     50/push-EAX
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . . restore EAX
-#?     58/pop-to-EAX
-#?     # . print-int32-buffered(Stderr, labels)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               .                 # push *EDX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  print-int32-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "\n")
-#?     # . . push args
-#?     68/push  "\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # emit-output(in, out, segments, labels)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    56/push-ESI
-    # . . call
-    e8/call  emit-output/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # flush(out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$convert:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x30a0/imm32      # add to ESP
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-convert-computes-addresses:
-    # input:
-    #   == code 0x1
-    #   Entry:
-    #   ab x/imm32
-    #   == data 0x1000
-    #   x:
-    #     01
-    #
-    # trace contains (in any order):
-    #   label x is at address 0x1079
-    #   segment code starts at address 0x74
-    #   segment code has size 5
-    #   segment data starts at address 0x1079
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-input-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-input-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # initialize input
-    # . write(_test-input-stream, "== code 0x1\n")
-    # . . push args
-    68/push  "== code 0x1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "Entry:\n")
-    # . . push args
-    68/push  "Entry:\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "ab x/imm32\n")
-    # . . push args
-    68/push  "ab x/imm32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== data 0x1000\n")
-    # . . push args
-    68/push  "== data 0x1000\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "x:\n")
-    # . . push args
-    68/push  "x:\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "01\n")
-    # . . push args
-    68/push  "01\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert(_test-input-buffered-file, _test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-buffered-file/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check trace
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-trace-contains("label 'x' is at address 0x00001079.", msg)
-    # . . push args
-    68/push  "F - test-convert-computes-addresses/0"/imm32
-    68/push  "label 'x' is at address 0x00001079."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("segment 'code' starts at address 0x00000074.", msg)
-    # . . push args
-    68/push  "F - test-convert-computes-addresses/1"/imm32
-    68/push  "segment 'code' starts at address 0x00000074."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("segment 'code' has size 0x00000005.", msg)
-    # . . push args
-    68/push  "F - test-convert-computes-addresses/2"/imm32
-    68/push  "segment 'code' has size 0x00000005."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("segment 'data' starts at address 0x00001079.", msg)
-    # . . push args
-    68/push  "F - test-convert-computes-addresses/3"/imm32
-    68/push  "segment 'data' starts at address 0x00001079."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# global scratch space for compute-offsets in the data segment
-== data
-
-compute-offsets:file-offset:  # int
-  0/imm32
-compute-offsets:segment-offset:  # int
-  0/imm32
-compute-offsets:word-slice:
-  0/imm32/start
-  0/imm32/end
-compute-offsets:segment-tmp:  # slice
-  0/imm32/start
-  0/imm32/end
-
-== code
-
-compute-offsets:  # in : (address stream), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
-    # skeleton:
-    #   for lines in 'in'
-    #     for words in line
-    #       switch word
-    #         case 1
-    #         case 2
-    #         ...
-    #         default
-    #
-    # pseudocode:
-    #   curr-segment-name : (address string) = 0
-    #   var line = new-stream(512, 1)
-    #   while true                                  # line loop
-    #     clear-stream(line)
-    #     read-line(in, line)
-    #     if (line->write == 0) break               # end of file
-    #     while true                                # word loop
-    #       word-slice = next-word(line)
-    #       if slice-empty?(word-slice)             # end of line
-    #         break
-    #       else if slice-starts-with?(word-slice, "#")  # comment
-    #         break                                 # end of line
-    #       else if slice-equal?(word-slice, "==")
-    #         if curr-segment-name != 0
-    #           seg = get-or-insert(segments, curr-segment-name)
-    #           seg->size = *file-offset - seg->file-offset
-    #           trace("segment '", curr-segment-name, "' has size ", seg->size)
-    #         segment-tmp = next-word(line)
-    #         curr-segment-name = slice-to-string(segment-tmp)
-    #         if empty?(curr-segment-name)
-    #           abort
-    #         segment-tmp = next-word(line)
-    #         if slice-empty?(segment-tmp)
-    #           abort
-    #         seg = get-or-insert(segments, curr-segment-name)
-    #         seg->starting-address = parse-hex-int(segment-tmp)
-    #         seg->file-offset = *file-offset
-    #         trace("segment '", curr-segment-name, "' is at file offset ", seg->file-offset)
-    #         segment-offset = 0
-    #         break  (next line)
-    #       else if is-label?(word-slice)
-    #         strip trailing ':' from word-slice
-    #         x : (address label-info) = get-or-insert(labels, name)
-    #         x->segment-name = curr-segment-name
-    #         trace("label '", word-slice, "' is in segment '", curr-segment-name, "'.")
-    #         x->segment-offset = segment-offset
-    #         trace("label '", word-slice, "' is at segment offset ", segment-offset, ".")
-    #         # labels occupy no space, so no need to increment offsets
-    #       else
-    #         width = compute-width-of-slice(word-slice)
-    #         *segment-offset += width
-    #         *file-offset += width
-    #
-    # . prolog
-    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
-    # curr-segment-name/ESI = 0
-    31/xor                          3/mod/direct    6/rm32/ESI    .           .             .           6/r32/ESI   .               .                 # clear ESI
-    # file-offset = 0
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           compute-offsets:file-offset/disp32  0/imm32               # copy to *compute-offsets:word-slice
-    # segment-offset = 0
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           compute-offsets:segment-offset/disp32  0/imm32            # copy to *compute-offsets:word-slice
-    # line/ECX = new-stream(512, 1)
-    # . EAX = new-stream(512, 1)
-    # . . push args
-    68/push  1/imm32
-    68/push  0x200/imm32
-    68/push  Heap/imm32
-    # . . call
-    e8/call  new-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . line/ECX = EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
-$compute-offsets:line-loop:
-    # clear-stream(line/ECX)
-    51/push-ECX
-    e8/call  clear-stream/disp32
-    # . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read-line(in, line/ECX)
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    e8/call  read-line/disp32
-    # . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (line->write == 0) break
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $compute-offsets:break-line-loop/disp32
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # write-stream(2/stderr, line)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(line)
-#?     # . . push args
-#?     51/push-ECX
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-$compute-offsets:word-loop:
-    # EDX = word-slice
-    ba/copy-to-EDX  compute-offsets:word-slice/imm32
-    # next-word(line/ECX, word-slice/EDX)
-    52/push-EDX
-    51/push-ECX
-    e8/call  next-word/disp32
-    # . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump word-slice and maybe curr-segment-name {{{
-#?     # . write(2/stderr, "w: ")
-#?     # . . push args
-#?     68/push  "w: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . save EAX
-#?     50/push-EAX
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . . restore EAX
-#?     58/pop-to-EAX
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     52/push-EDX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . if (curr-segment-name == 0) print curr-segment-name
-#?     81          7/subop/compare     3/mod/direct    6/rm32/ESI    .           .             .           .           .               0/imm32           # compare ESI
-#?     74/jump-if-equal  $compute-offsets:case-empty/disp8
-#?     # . write(2/stderr, "segment at start of word: ")
-#?     # . . push args
-#?     68/push  "segment at start of word: "/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-buffered(Stderr, curr-segment-name)
-#?     # . . push args
-#?     56/push-ESI
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$compute-offsets:case-empty:
-    # if slice-empty?(word/EDX) break
-    # . EAX = slice-empty?(word/EDX)
-    52/push-EDX
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) break
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $compute-offsets:line-loop/disp32
-$compute-offsets:case-comment:
-    # if slice-starts-with?(word-slice, "#") continue
-    68/push  "#"/imm32
-    52/push-EDX
-    e8/call  slice-starts-with?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) break
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $compute-offsets:line-loop/disp32
-$compute-offsets:case-segment-header:
-    # if (!slice-equal?(word-slice/EDX, "==")) goto next case
-    # . EAX = slice-equal?(word-slice/EDX, "==")
-    68/push  "=="/imm32
-    52/push-EDX
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next case
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $compute-offsets:case-label/disp32
-    # if (curr-segment-name == 0) goto construct-next-segment
-    81          7/subop/compare     3/mod/direct    6/rm32/ESI    .           .             .           .           .               0/imm32           # compare ESI
-    74/jump-if-equal  $compute-offsets:construct-next-segment/disp8
-    # seg/EAX = get-or-insert(segments, curr-segment-name, row-size=16)
-    # . . push args
-    68/push  0x10/imm32/row-size
-    56/push-ESI
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # seg->size = file-offset - seg->file-offset
-    # . save ECX
-    51/push-ECX
-    # . EBX = *file-offset
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   compute-offsets:file-offset/disp32 # copy *file-offset to EBX
-    # . ECX = seg->file-offset
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EAX+4) to ECX
-    # . EBX -= ECX
-    29/subtract                     3/mod/direct    3/rm32/EBX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EBX
-    # . seg->size = EBX
-    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   8/disp8         .                 # copy EBX to *(EAX+8)
-    # . restore ECX
-    59/pop-to-ECX
-    # trace-sssns("segment '", curr-segment-name, "' has size ", seg->size, ".")
-    # . . push args
-    68/push  "."/imm32
-    53/push-EBX
-    68/push  "' has size "/imm32
-    56/push-ESI
-    68/push  "segment '"/imm32
-    # . . call
-    e8/call  trace-sssns/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-$compute-offsets:construct-next-segment:
-    # next-word(line/ECX, segment-tmp)
-    68/push  compute-offsets:segment-tmp/imm32
-    51/push-ECX
-    e8/call  next-word/disp32
-    # . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump curr-segment-name if not null (clobbering EAX) {{{
-#?     # . if (curr-segment-name == 0) goto update-curr-segment-name
-#?     81          7/subop/compare     3/mod/direct    6/rm32/ESI    .           .             .           .           .               0/imm32           # compare ESI
-#?     74/jump-if-equal  $compute-offsets:update-curr-segment-name/disp8
-#?     # . write(2/stderr, "setting segment to: ")
-#?     # . . push args
-#?     68/push  "setting segment to: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . . restore EAX
-#?     58/pop-to-EAX
-#?     # . write-buffered(Stderr, curr-segment-name)
-#?     # . . push args
-#?     56/push-ESI
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$compute-offsets:update-curr-segment-name:
-    # curr-segment-name = slice-to-string(segment-tmp)
-    # . EAX = slice-to-string(Heap, segment-tmp)
-    # . . push args
-    68/push  compute-offsets:segment-tmp/imm32
-    68/push  Heap/imm32
-    # . . call
-    e8/call  slice-to-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . curr-segment-name = EAX
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy EAX to ESI
-    # if empty?(curr-segment-name) abort
-    # . if (EAX == 0) abort
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $compute-offsets:abort/disp32
-    # next-word(line/ECX, segment-tmp)
-    68/push  compute-offsets:segment-tmp/imm32
-    51/push-ECX
-    e8/call  next-word/disp32
-    # . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if slice-empty?(segment-tmp) abort
-    # . EAX = slice-empty?(segment-tmp)
-    68/push  compute-offsets:segment-tmp/imm32
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) abort
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $compute-offsets:abort/disp32
-    # seg/EBX = get-or-insert(segments, curr-segment-name, row-size=16)
-    # . . push args
-    68/push  0x10/imm32/row-size
-    56/push-ESI
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . EBX = EAX
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    # seg->address = parse-hex-int(segment-tmp)
-    # . EAX = parse-hex-int(segment-tmp)
-    68/push  compute-offsets:segment-tmp/imm32
-    e8/call  parse-hex-int/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . seg->address = EAX
-    89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EBX
-    # seg->file-offset = *file-offset
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   compute-offsets:file-offset/disp32 # copy *file-offset to EAX
-    89/copy                         1/mod/*+disp8   3/rm32/EBX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EBX+4)
-    # trace-sssns("segment '", curr-segment-name, "' is at file offset ", seg->file-offset, "")
-    # . . push args
-    68/push  "."/imm32
-    50/push-EAX
-    68/push  "' is at file offset "/imm32
-    56/push-ESI
-    68/push  "segment '"/imm32
-    # . . call
-    e8/call  trace-sssns/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # segment-offset = 0
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     compute-offsets:segment-offset/disp32  0/imm32           # copy to *segment-offset
-    # break
-    e9/jump $compute-offsets:line-loop/disp32
-$compute-offsets:case-label:
-    # if (!is-label?(word-slice/EDX)) goto next case
-    # . EAX = is-label?(word-slice/EDX)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call  is-label?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX == 0) goto next case
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $compute-offsets:case-default/disp8
-    # strip trailing ':' from word-slice
-    ff          1/subop/decrement   1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         .                 # decrement *(EDX+4)
-    # x/EAX = leaky-get-or-insert-slice(labels, word-slice, row-size=16)
-    # . . push args
-    68/push  0x10/imm32/row-size
-    52/push-EDX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    # . . call
-    e8/call  leaky-get-or-insert-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$compute-offsets:save-label-offset:
-    # x->segment-name = curr-segment-name
-    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           6/r32/ESI   .               .                 # copy ESI to *EAX
-    # trace-slsss("label '" word-slice/EDX "' is in segment '" current-segment-name "'.")
-    # . . push args
-    68/push  "'."/imm32
-    56/push-ESI
-    68/push  "' is in segment '"/imm32
-    52/push-EDX
-    68/push  "label '"/imm32
-    # . . call
-    e8/call  trace-slsss/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # x->segment-offset = segment-offset
-    # . EBX = segment-offset
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   compute-offsets:segment-offset/disp32  # copy *segment-offset to EBX
-    # . x->segment-offset = EBX
-    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   4/disp8         .                 # copy EBX to *(EAX+4)
-    # trace-slsns("label '" word-slice/EDX "' is at segment offset " *segment-offset/EAX ".")
-    # . . EAX = file-offset
-    b8/copy-to-EAX compute-offsets:segment-offset/imm32
-    # . . EAX = *file-offset/EAX
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # copy *EAX to EAX
-    # . . push args
-    68/push  "."/imm32
-    50/push-EAX
-    68/push  "' is at segment offset "/imm32
-    52/push-EDX
-    68/push  "label '"/imm32
-    # . . call
-    e8/call  trace-slsns/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # continue
-    e9/jump  $compute-offsets:word-loop/disp32
-$compute-offsets:case-default:
-    # width/EAX = compute-width-of-slice(word-slice)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call compute-width-of-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # segment-offset += width
-    01/add                          0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   compute-offsets:segment-offset/disp32 # add EAX to *segment-offset
-    # file-offset += width
-    01/add                          0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   compute-offsets:file-offset/disp32 # add EAX to *file-offset
-#?     # dump segment-offset {{{
-#?     # . write(2/stderr, "segment-offset: ")
-#?     # . . push args
-#?     68/push  "segment-offset: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . save EAX
-#?     50/push-EAX
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . . restore EAX
-#?     58/pop-to-EAX
-#?     # . print-int32-buffered(Stderr, segment-offset)
-#?     # . . push args
-#?     52/push-EDX
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           compute-offsets:segment-offset/disp32  # push *segment-offset
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  print-int32-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "\n")
-#?     # . . push args
-#?     68/push  "\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    e9/jump $compute-offsets:word-loop/disp32
-$compute-offsets:break-line-loop:
-    # seg/EAX = get-or-insert(segments, curr-segment-name, row-size=16)
-    # . . push args
-    68/push  0x10/imm32/row-size
-    56/push-ESI
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  get-or-insert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # seg->size = file-offset - seg->file-offset
-    # . save ECX
-    51/push-ECX
-    # . EBX = *file-offset
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   compute-offsets:file-offset/disp32 # copy *file-offset to EBX
-    # . ECX = seg->file-offset
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EAX+4) to ECX
-    # . EBX -= ECX
-    29/subtract                     3/mod/direct    3/rm32/EBX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EBX
-    # . seg->size = EBX
-    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   8/disp8         .                 # copy EBX to *(EAX+8)
-    # . restore ECX
-    59/pop-to-ECX
-    # trace-sssns("segment '", curr-segment-name, "' has size ", seg->size, ".")
-    # . trace-sssns("segment '", curr-segment-name, "' has size ", EBX, ".")
-    # . . push args
-    68/push  "."/imm32
-    53/push-EBX
-    68/push  "' has size "/imm32
-    56/push-ESI
-    68/push  "segment '"/imm32
-    # . . call
-    e8/call  trace-sssns/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-$compute-offsets:end:
-    # . reclaim locals
-    # . 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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-$compute-offsets:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "'==' must be followed by segment name and segment-start\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-compute-offsets:
-    # input:
-    #   == code 0x1
-    #   ab x/imm32  # skip comment
-    #   == data 0x1000
-    #   00
-    #   x:
-    #     34
-    #
-    # trace contains (in any order):
-    #   segment 'code' is at file offset 0x0.
-    #   segment 'code' has size 0x5.
-    #   segment 'data' is at file offset 0x5.
-    #   segment 'data' has size 0x2.
-    #   label 'x' is in segment 'data'.
-    #   label 'x' is at segment offset 0x1.
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # var segments/ECX = stream(2 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x20/imm32        # subtract from ESP
-    68/push  0x20/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var labels/EDX = stream(2 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x20/imm32        # subtract from ESP
-    68/push  0x20/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # initialize input
-    # . write(_test-input-stream, "== code 0x1\n")
-    # . . push args
-    68/push  "== code 0x1\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "ab x/imm32  # skip comment\n")
-    # . . push args
-    68/push  "ab x/imm32  # skip comment\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== data 0x1000\n")
-    # . . push args
-    68/push  "== data 0x1000\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "00\n")
-    # . . push args
-    68/push  "00\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "x:\n")
-    # . . push args
-    68/push  "x:\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "34\n")
-    # . . push args
-    68/push  "34\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # compute-offsets(_test-input-stream, segments, labels)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  compute-offsets/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32        # add to ESP
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # check trace
-    # . check-trace-contains("segment 'code' is at file offset 0x00000000.", msg)
-    # . . push args
-    68/push  "F - test-compute-offsets/0"/imm32
-    68/push  "segment 'code' is at file offset 0x00000000."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("segment 'code' has size 0x00000005", msg)
-    # . . push args
-    68/push  "F - test-compute-offsets/1"/imm32
-    68/push  "segment 'code' has size 0x00000005."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("segment 'data' is at file offset 0x00000005.", msg)
-    # . . push args
-    68/push  "F - test-compute-offsets/2"/imm32
-    68/push  "segment 'data' is at file offset 0x00000005."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("segment 'data' has size 0x00000002.", msg)
-    # . . push args
-    68/push  "F - test-compute-offsets/3"/imm32
-    68/push  "segment 'data' has size 0x00000002."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("label 'x' is in segment 'data'.", msg)
-    # . . push args
-    68/push  "F - test-compute-offsets/4"/imm32
-    68/push  "label 'x' is in segment 'data'."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("label 'x' is at segment offset 0x00000001.", msg)
-    # . . push args
-    68/push  "F - test-compute-offsets/5"/imm32
-    68/push  "label 'x' is at segment offset 0x00000001."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-ints-equal(labels->write, 0x10, msg)
-    # . . push args
-    68/push  "F - test-compute-offsets-maintains-labels-write-index"/imm32
-    68/push  0x10/imm32/1-entry
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-compute-addresses:  # segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
-    # pseudocode:
-    #   srow : (address segment-info) = segments->data
-    #   max = segments->data + segments->write
-    #   num-segments = segments->write / 16
-    #   starting-offset = 0x34 + (num-segments * 0x20)
-    #   while true
-    #     if (srow >= max) break
-    #     s->file-offset += starting-offset
-    #     s->address &= 0xfffff000  # clear last 12 bits for p_align
-    #     s->address += (s->file-offset & 0x00000fff)
-    #     trace-sssns("segment " s->key " starts at address " s->address)
-    #     srow += 16  # row-size
-    #   lrow : (address label-info) = labels->data
-    #   max = labels->data + labels->write
-    #   while true
-    #     if (lrow >= max) break
-    #     seg-name : (address string) = lrow->segment-name
-    #     label-seg : (address segment-info) = get(segments, seg-name, row-size=16)
-    #     lrow->address = label-seg->address + lrow->segment-offset
-    #     trace-sssns("label " lrow->key " is at address " lrow->address)
-    #     lrow += 16  # row-size
-    #
-    # . prolog
-    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 = segments
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # starting-offset/EDI = 0x34 + (num-segments * 0x20)  # make room for ELF headers
-    # . EDI = segments->write / 16 (row-size)
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           7/r32/EDI   .               .                 # copy *ESI to EDI
-    c1/shift    5/subop/logic-right 3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm8            # shift EDI right by 4 bits, while padding zeroes
-    # . EDI = (EDI * 0x20) + 0x34
-    c1/shift    4/subop/left        3/mod/direct    7/rm32/EDI    .           .             .           .           .               5/imm8            # shift EDI left by 5 bits
-    81          0/subop/add         3/mod/direct    7/rm32/EDI    .           .             .           .           .               0x34/imm32        # add to EDI
-    # srow/EAX = segments->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy ESI+12 to EAX
-    # max/ECX = segments->data + segments->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    01/add                          3/mod/direct    1/rm32/ECX    .           .             .           6/r32/ESI   .               .                 # add ESI to ECX
-$compute-addresses:segment-loop:
-    # if (srow >= max) break
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
-    73/jump-if-greater-or-equal-unsigned  $compute-addresses:segment-break/disp8
-    # srow->file-offset += starting-offset
-    01/add                          1/mod/*+disp8   0/rm32/EAX    .           .             .           7/r32/EDI   8/disp8         .                 # add EDI to *(EAX+8)
-    # clear last 12 bits of srow->address for p_align=0x1000
-    # . EDX = srow->address
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(EAX+4) to EDX
-    # . EDX &= 0xfffff000
-    81          4/subop/and         3/mod/direct    2/rm32/EDX    .           .             .           .           .               0xfffff000/imm32  # bitwise and of EDX
-    # update last 12 bits from srow->file-offset
-    # . EBX = srow->file-offset
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   8/disp8         .                 # copy *(EAX+8) to EBX
-    # . EBX &= 0xfff
-    81          4/subop/and         3/mod/direct    3/rm32/EBX    .           .             .           .           .               0x00000fff/imm32  # bitwise and of EBX
-    # . srow->address = EDX | EBX
-    09/or                           3/mod/direct    2/rm32/EDX    .           .             .           3/r32/EBX   .               .                 # EDX = bitwise OR with EBX
-    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           2/r32/EDX   4/disp8         .                 # copy EDX to *(EAX+4)
-    # trace-sssns("segment " srow " starts at address " srow->address ".")
-    # . . push args
-    68/push  "."/imm32
-    52/push-EDX
-    68/push  "' starts at address "/imm32
-    ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
-    68/push  "segment '"/imm32
-    # . . call
-    e8/call  trace-sssns/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # srow += 16  # size of row
-    05/add-to-EAX  0x10/imm32
-    eb/jump  $compute-addresses:segment-loop/disp8
-$compute-addresses:segment-break:
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # ESI = labels
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # lrow/EAX = labels->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy ESI+12 to EAX
-    # max/ECX = labels->data + labels->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    01/add                          3/mod/direct    1/rm32/ECX    .           .             .           6/r32/ESI   .               .                 # add ESI to ECX
-$compute-addresses:label-loop:
-    # if (lrow >= max) break
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
-    0f 83/jump-if-greater-or-equal-unsigned  $compute-addresses:end/disp32
-#?     # dump lrow->key {{{
-#?     # . write(2/stderr, "label: ")
-#?     # . . push args
-#?     68/push  "label: "/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, lrow->key)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # 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
-#?     # }}}
-    # seg-name/EDX = lrow->segment-name
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *EAX to EDX
-#?     # dump seg-name {{{
-#?     # . write(2/stderr, "compute-addresses: seg-name: ")
-#?     # . . push args
-#?     68/push  "seg-name: "/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, seg-name)
-#?     # . . push args
-#?     52/push-EDX
-#?     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
-#?     # }}}
-    # label-seg/EDX : (address segment-info) = get(segments, seg-name, row-size=16)
-    # . save EAX
-    50/push-EAX
-    # . EAX = get(segments, seg-name, row-size=16)
-    # . . push args
-    68/push  0x10/imm32/row-size
-    52/push-EDX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  get/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . EDX = EAX
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDX
-    # . restore EAX
-    58/pop-to-EAX
-    # EBX = label-seg->address
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           3/r32/EBX   .               .                 # copy *EDX to EBX
-    # EBX += lrow->segment-offset
-    03/add                          1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   8/disp8         .                 # add *(EAX+8) to EBX
-    # lrow->address = EBX
-    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy EBX to *(EAX+12)
-    # trace-sssns("label " lrow->key " is at address " lrow->address ".")
-    # . . push args
-    68/push  "."/imm32
-    53/push-EBX
-    68/push  "' is at address "/imm32
-    ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
-    68/push  "label '"/imm32
-    # . . call
-    e8/call  trace-sssns/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # lrow += 16  # size of row
-    05/add-to-EAX  0x10/imm32
-    e9/jump  $compute-addresses:label-loop/disp32
-$compute-addresses: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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-compute-addresses:
-    # input:
-    #   segments:
-    #     - 'a': {0x1000, 0, 5}
-    #     - 'b': {0x2018, 5, 1}
-    #     - 'c': {0x5444, 6, 12}
-    #   labels:
-    #     - 'l1': {'a', 3, 0}
-    #     - 'l2': {'b', 0, 0}
-    #
-    # trace contains in any order (comments in parens):
-    #   segment 'a' starts at address 0x00001094.  (0x34 + 0x20 for each segment)
-    #   segment 'b' starts at address 0x00002099.  (0x018 discarded)
-    #   segment 'c' starts at address 0x0000509a.  (0x444 discarded)
-    #   label 'l1' is at address 0x00001097.       (0x1094 + segment-offset 3)
-    #   label 'l2' is at address 0x00002099.       (0x2099 + segment-offset 0)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . var segments/ECX = stream(10 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
-    68/push  0xa0/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . var labels/EDX = stream(512 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
-    68/push  0x2000/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # . stream-add4(segments, "a", 0x1000, 0, 5)
-    68/push  5/imm32/segment-size
-    68/push  0/imm32/file-offset
-    68/push  0x1000/imm32/start-address
-    68/push  "a"/imm32/segment-name
-    51/push-ECX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(segments, "b", 0x2018, 5, 1)
-    68/push  1/imm32/segment-size
-    68/push  5/imm32/file-offset
-    68/push  0x2018/imm32/start-address
-    68/push  "b"/imm32/segment-name
-    51/push-ECX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(segments, "c", 0x5444, 6, 12)
-    68/push  0xc/imm32/segment-size
-    68/push  6/imm32/file-offset
-    68/push  0x5444/imm32/start-address
-    68/push  "c"/imm32/segment-name
-    51/push-ECX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(labels, "l1", "a", 3, 0)
-    68/push  0/imm32/label-address
-    68/push  3/imm32/segment-offset
-    68/push  "a"/imm32/segment-name
-    68/push  "l1"/imm32/label-name
-    52/push-EDX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(labels, "l2", "b", 0, 0)
-    68/push  0/imm32/label-address
-    68/push  0/imm32/segment-offset
-    68/push  "b"/imm32/segment-name
-    68/push  "l2"/imm32/label-name
-    52/push-EDX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # component under test
-    # . compute-addresses(segments, labels)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  compute-addresses/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # checks
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . check-trace-contains("segment 'a' starts at address 0x00001094.", msg)
-    # . . push args
-    68/push  "F - test-compute-addresses/0"/imm32
-    68/push  "segment 'a' starts at address 0x00001094."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("segment 'b' starts at address 0x00002099.", msg)
-    # . . push args
-    68/push  "F - test-compute-addresses/1"/imm32
-    68/push  "segment 'b' starts at address 0x00002099."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("segment 'c' starts at address 0x0000509a.", msg)
-    # . . push args
-    68/push  "F - test-compute-addresses/2"/imm32
-    68/push  "segment 'c' starts at address 0x0000509a."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("label 'l1' is at address 0x00001097.", msg)
-    # . . push args
-    68/push  "F - test-compute-addresses/3"/imm32
-    68/push  "label 'l1' is at address 0x00001097."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("label 'l2' is at address 0x00002099.", msg)
-    # . . push args
-    68/push  "F - test-compute-addresses/4"/imm32
-    68/push  "label 'l2' is at address 0x00002099."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-ints-equal(labels->write, 0x20, msg)
-    # . . push args
-    68/push  "F - test-compute-addresses/maintains-labels-write-index"/imm32
-    68/push  0x20/imm32/2-entries
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-compute-addresses-large-segments:
-    # input:
-    #   segments:
-    #     - 'a': {0x1000, 0, 0x5604}
-    #     - 'b': {0x2018, 0x5604, 1}
-    #   labels:
-    #     - 'l1': {'a', 3, 0}
-    #
-    # trace contains in any order (comments in parens):
-    #   segment 'a' starts at address 0x00001074.  (0x34 + 0x20 for each segment)
-    #   segment 'b' starts at address 0x00002678.  (0x018 discarded; last 3 nibbles from 0x1074 + 0x5604)
-    #   label 'l1' is at address 0x00001077.       (0x1074 + segment-offset 3)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . var segments/ECX = stream(10 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
-    68/push  0xa0/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . var labels/EDX = stream(512 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
-    68/push  0x2000/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # . stream-add4(segments, "a", 0x1000, 0, 0x5604)
-    68/push  0x5604/imm32/segment-size
-    68/push  0/imm32/file-offset
-    68/push  0x1000/imm32/start-address
-    68/push  "a"/imm32/segment-name
-    51/push-ECX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(segments, "b", 0x2018, 0x5604, 1)
-    68/push  1/imm32/segment-size
-    68/push  0x5604/imm32/file-offset
-    68/push  0x2018/imm32/start-address
-    68/push  "b"/imm32/segment-name
-    51/push-ECX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(labels, "l1", "a", 3, 0)
-    68/push  0/imm32/label-address
-    68/push  3/imm32/segment-offset
-    68/push  "a"/imm32/segment-name
-    68/push  "l1"/imm32/label-name
-    52/push-EDX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # component under test
-    # . compute-addresses(segments, labels)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  compute-addresses/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # checks
-    # . check-trace-contains("segment 'a' starts at address 0x00001074.", msg)
-    # . . push args
-    68/push  "F - test-compute-addresses-large-segments/0"/imm32
-    68/push  "segment 'a' starts at address 0x00001074."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("segment 'b' starts at address 0x00002678.", msg)
-    # . . push args
-    68/push  "F - test-compute-addresses-large-segments/1"/imm32
-    68/push  "segment 'b' starts at address 0x00002678."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check-trace-contains("label 'l1' is at address 0x00001077.", msg)
-    # . . push args
-    68/push  "F - test-compute-addresses-large-segments/3"/imm32
-    68/push  "label 'l1' is at address 0x00001077."/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-output:  # in : (address stream), out : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
-    # pseudocode:
-    #   emit-headers(out, segments, labels)
-    #   emit-segments(in, out, segments, labels)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-#?     # write(2/stderr, "emit-headers\n") {{{
-#?     # . . push args
-#?     68/push  "emit-headers\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # emit-headers(out, segments, labels)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8       .                # push *(EBP+20)
-    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)
-    # . . call
-    e8/call  emit-headers/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-#?     # write(2/stderr, "emit-segments\n") {{{
-#?     # . . push args
-#?     68/push  "emit-segments\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # emit-segments(in, out, segments, labels)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8       .                # push *(EBP+20)
-    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  emit-segments/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-$emit-output:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-segments:  # in : (address stream), out : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
-    # pseudocode:
-    #   var offset-of-next-instruction = 0
-    #   var line = new-stream(512, 1)
-    #   line-loop:
-    #   while true
-    #     clear-stream(line)
-    #     read-line(in, line)
-    #     if (line->write == 0) break               # end of file
-    #     offset-of-next-instruction += num-bytes(line)
-    #     while true
-    #       var word-slice = next-word(line)
-    #       if slice-empty?(word-slice)             # end of line
-    #         break
-    #       if slice-starts-with?(word-slice, "#")  # comment
-    #         break
-    #       if is-label?(word-slice)                # no need for label declarations anymore
-    #         goto line-loop                        # don't insert empty lines
-    #       if slice-equal?(word-slice, "==")       # no need for segment header lines
-    #         goto line-loop                        # don't insert empty lines
-    #       if length(word-slice) == 2
-    #         write-slice-buffered(out, word-slice)
-    #         write-buffered(out, " ")
-    #         continue
-    #       datum = next-token-from-slice(word-slice->start, word-slice->end, "/")
-    #       info = get-slice(labels, datum)
-    #       if !string-equal?(info->segment-name, "code")
-    #         if has-metadata?(word-slice, "disp8")
-    #           abort
-    #         if has-metadata?(word-slice, "imm8")
-    #           abort
-    #         emit(out, info->address, 4)  # global variables always translate to absolute addresses
-    #       # code segment cases
-    #       else if has-metadata?(word-slice, "imm8")
-    #         abort  # label should never go to imm8
-    #       else if has-metadata?(word-slice, "imm32")
-    #         emit(out, info->address, 4)
-    #       else if has-metadata?(word-slice, "disp8")
-    #         value = info->offset - offset-of-next-instruction
-    #         emit(out, value, 1)
-    #       else if has-metadata?(word-slice, "disp32")
-    #         value = info->offset - offset-of-next-instruction
-    #         emit(out, value, 4)
-    #       else
-    #         abort
-    #     write-buffered(out, "\n")
-    #
-    # registers:
-    #   line: ECX
-    #   word-slice: EDX
-    #   offset-of-next-instruction: EBX
-    #   datum: EDI
-    #   info: ESI (inner loop only)
-    #   temporaries: EAX, ESI (outer loop)
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    53/push-EBX
-    56/push-ESI
-    57/push-EDI
-    # var line/ECX : (address stream byte) = stream(512)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
-    68/push  0x200/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var word-slice/EDX = {0, 0}
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # var datum/EDI = {0, 0}
-    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
-    # offset-of-next-instruction/EBX = 0
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-$emit-segments:line-loop:
-    # clear-stream(line)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read-line(in, line)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-line/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # write-stream(2/stderr, line)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(line)
-#?     # . . push args
-#?     51/push-ECX
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-$emit-segments:check-for-end-of-input:
-    # if (line->write == 0) break
-    81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
-    0f 84/jump-if-equal  $emit-segments:end/disp32
-    # offset-of-next-instruction += num-bytes(line)
-    # . EAX = num-bytes(line)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . EBX += EAX
-    01/add                          3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # add EAX to EBX
-$emit-segments:word-loop:
-    # next-word(line, word-slice)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "w: ")
-#?     # . . push args
-#?     68/push  "w: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     52/push-EDX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$emit-segments:check-for-end-of-line:
-    # if (slice-empty?(word-slice)) break
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) break
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-segments:next-line/disp32
-$emit-segments:check-for-comment:
-    # if (slice-starts-with?(word-slice, "#")) break
-    # . start/ESI = word-slice->start
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           6/r32/ESI   .               .                 # copy *EDX to ESI
-    # . c/EAX = *start
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/AL    .               .                 # copy byte at *ESI to AL
-    # . if (EAX == '#') break
-    3d/compare-EAX-and  0x23/imm32/hash
-    0f 84/jump-if-equal  $emit-segments:next-line/disp32
-$emit-segments:check-for-label:
-    # if is-label?(word-slice) break
-    # . EAX = is-label?(word-slice)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call  is-label?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) break
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-segments:line-loop/disp32
-$emit-segments:check-for-segment-header:
-    # if (slice-equal?(word-slice, "==")) break
-    # . EAX = slice-equal?(word-slice, "==")
-    # . . push args
-    68/push  "=="/imm32
-    52/push-EDX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) break
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-segments:line-loop/disp32
-$emit-segments:2-character:
-    # if (length(word-slice) != 2) goto next check
-    # . EAX = length(word-slice)
-    8b/copy                         1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(EDX+4) to EAX
-    2b/subtract                     0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # subtract *EDX from EAX
-    # . if (EAX != 2) goto next check
-    3d/compare-EAX-and  2/imm32
-    75/jump-if-not-equal  $emit-segments:check-metadata/disp8
-    # write-slice-buffered(out, word-slice)
-    # . . push args
-    52/push-EDX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-buffered(out, " ")
-    # . . push args
-    68/push  " "/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # continue
-    e9/jump  $emit-segments:word-loop/disp32
-$emit-segments:check-metadata:
-    # - if we get here, 'word-slice' must be a label to be looked up
-    # datum/EDI = next-token-from-slice(word-slice->start, word-slice->end, "/")
-    # . . push args
-    57/push-EDI
-    68/push  0x2f/imm32/slash
-    ff          6/subop/push        1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         .                 # push *(EDX+4)
-    ff          6/subop/push        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               .                 # push *EDX
-    # . . call
-    e8/call  next-token-from-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "datum: ")
-#?     # . . push args
-#?     68/push  "datum: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     57/push-EDI
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # info/ESI = get-slice(labels, datum, row-size=16)
-    # . EAX = get-slice(labels, datum, row-size=16)
-    # . . push args
-    68/push  0x10/imm32/row-size
-    57/push-EDI
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    # . . call
-    e8/call  get-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . ESI = EAX
-    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy EAX to ESI
-$emit-segments:check-global-variable:
-#?     # dump info->segment-name {{{
-#?     # . write(2/stderr, "aa: label segment: ")
-#?     # . . push args
-#?     68/push  "aa: label segment: "/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, info->segment-name)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
-#?     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
-#?     # }}}
-    # if string-equal?(info->segment-name, "code") goto code label checks
-    # . EAX = string-equal?(info->segment-name, "code")
-    # . . push args
-    68/push  "code"/imm32
-    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
-    # . . call
-    e8/call  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) goto code label checks
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-segments:check-code-label-for-imm8/disp32
-$emit-segments:check-global-variable-for-disp8:
-    # if has-metadata?(word-slice, "disp8") abort
-    # . EAX = has-metadata?(word-slice, "disp8")
-    # . . push args
-    68/push  "disp8"/imm32
-    52/push-EDX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) abort
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-segments:global-variable-abort/disp32
-$emit-segments:check-global-variable-for-imm8:
-    # if has-metadata?(word-slice, "imm8") abort
-    # . EAX = has-metadata?(word-slice, "imm8")
-    # . . push args
-    68/push  "imm8"/imm32
-    52/push-EDX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) abort
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-segments:global-variable-abort/disp32
-$emit-segments:emit-global-variable:
-    # emit-hex(out, info->address, 4)
-    # . . push args
-    68/push  4/imm32
-    ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # push *(ESI+8)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # continue
-    e9/jump  $emit-segments:word-loop/disp32
-$emit-segments:check-code-label-for-imm8:
-    # if (has-metadata?(word-slice, "imm8")) abort
-    # . EAX = has-metadata?(EDX, "imm8")
-    # . . push args
-    68/push  "imm8"/imm32
-    52/push-EDX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) abort
-    3d/compare-EAX-and  0/imm32
-    0f 85/jump-if-not-equal  $emit-segments:imm8-abort/disp32
-$emit-segments:check-code-label-for-imm32:
-    # if (!has-metadata?(word-slice, "imm32")) goto next check
-    # . EAX = has-metadata?(EDX, "imm32")
-    # . . push args
-    68/push  "imm32"/imm32
-    52/push-EDX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-segments:check-code-label-for-disp8/disp8
-#?     # dump info->address {{{
-#?     # . write(2/stderr, "info->address: ")
-#?     # . . push args
-#?     68/push  "info->address: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . print-int32-buffered(Stderr, info->address)
-#?     # . . push args
-#?     ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # push *(ESI+8)
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  print-int32-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$emit-segments:emit-code-label-imm32:
-    # emit-hex(out, info->address, 4)
-    # . . push args
-    68/push  4/imm32
-    ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # push *(ESI+8)
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # continue
-    e9/jump  $emit-segments:word-loop/disp32
-$emit-segments:check-code-label-for-disp8:
-    # if (!has-metadata?(word-slice, "disp8")) goto next check
-    # . EAX = has-metadata?(EDX, "disp8")
-    # . . push args
-    68/push  "disp8"/imm32
-    52/push-EDX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-segments:check-code-label-for-disp32/disp8
-$emit-segments:emit-code-label-disp8:
-    # emit-hex(out, info->offset - offset-of-next-instruction, 1)
-    # . . push args
-    68/push  1/imm32
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # subtract EBX from EAX
-    50/push-EAX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # continue
-    e9/jump  $emit-segments:word-loop/disp32
-$emit-segments:check-code-label-for-disp32:
-    # if (!has-metadata?(word-slice, "disp32")) abort
-    # . EAX = has-metadata?(EDX, "disp32")
-    # . . push args
-    68/push  "disp32"/imm32
-    52/push-EDX
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) abort
-    3d/compare-EAX-and  0/imm32
-    0f 84/jump-if-equal  $emit-segments:abort/disp32
-$emit-segments:emit-code-label-disp32:
-    # emit-hex(out, info->offset - offset-of-next-instruction, 4)
-    # . . push args
-    68/push  4/imm32
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # subtract EBX from EAX
-    50/push-EAX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # continue
-    e9/jump  $emit-segments:word-loop/disp32
-$emit-segments:next-line:
-    # write-buffered(out, "\n")
-    # . . push args
-    68/push  Newline/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # loop
-    e9/jump  $emit-segments:line-loop/disp32
-$emit-segments:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x21c/imm32       # add to ESP
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$emit-segments:global-variable-abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "emit-segments: must refer to global variables with /disp32 or /imm32"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-$emit-segments:imm8-abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "emit-segments: cannot refer to code labels with /imm8"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-$emit-segments:abort:
-    # print(stderr, "missing metadata in " word-slice)
-    # . _write(2/stderr, "missing metadata in word ")
-    # . . push args
-    68/push  "emit-segments: missing metadata in "/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write-slice-buffered(Stderr, word-slice)
-    # . . push args
-    52/push-EDX
-    68/push  Stderr/imm32
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . flush(Stderr)
-    # . . push args
-    68/push  Stderr/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-emit-segments-global-variable:
-    # global variables always convert to absolute addresses, regardless of metadata
-    #
-    # input:
-    #   in:
-    #     == code 0x1000
-    #     ab cd ef gh
-    #     ij x/disp32
-    #     == data 0x2000
-    #     00
-    #     x:
-    #       34
-    #   segments:
-    #     - 'code': {0x1074, 0, 9}
-    #     - 'data': {0x2079, 5, 2}
-    #   labels:
-    #     - 'x': {'data', 1, 0x207a}
-    #
-    # output:
-    #   ab cd ef gh
-    #   ij 7a 20 00 00
-    #   00
-    #   34
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . var segments/ECX = stream(10 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
-    68/push  0xa0/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . var labels/EDX = stream(512 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
-    68/push  0x2000/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # initialize input
-    # . write(_test-input-stream, "== code 0x1000\n")
-    # . . push args
-    68/push  "== code 0x1000\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "ab cd ef gh\n")
-    # . . push args
-    68/push  "ab cd ef gh\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "ij x/disp32\n")
-    # . . push args
-    68/push  "ij x/disp32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "== data 0x2000\n")
-    # . . push args
-    68/push  "== data 0x2000\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "00\n")
-    # . . push args
-    68/push  "00\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "x:\n")
-    # . . push args
-    68/push  "x:\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "34\n")
-    # . . push args
-    68/push  "34\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . stream-add4(segments, "code", 0x1074, 0, 9)
-    68/push  9/imm32/segment-size
-    68/push  0/imm32/file-offset
-    68/push  0x1074/imm32/start-address
-    68/push  "code"/imm32/segment-name
-    51/push-ECX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(segments, "data", 0x2079, 5, 2)
-    68/push  1/imm32/segment-size
-    68/push  5/imm32/file-offset
-    68/push  0x2079/imm32/start-address
-    68/push  "data"/imm32/segment-name
-    51/push-ECX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(labels, "x", "data", 1, 0x207a)
-    68/push  0x207a/imm32/label-address
-    68/push  1/imm32/segment-offset
-    68/push  "data"/imm32/segment-name
-    68/push  "x"/imm32/label-name
-    52/push-EDX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # component under test
-    # . emit-segments(_test-input-stream, _test-output-buffered-file, segments, labels)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  emit-segments/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # checks
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "ab cd ef gh ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-global-variable/0"/imm32
-    68/push  "ab cd ef gh "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "ij 7a 20 00 00 ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-global-variable/1"/imm32
-    68/push  "ij 7a 20 00 00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "00 ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-global-variable/2"/imm32
-    68/push  "00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "34 ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-global-variable/3"/imm32
-    68/push  "34 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-segments-code-label:
-    # labels usually convert to displacements
-    #
-    # input:
-    #   in:
-    #     == code 0x1000
-    #     ab cd
-    #     l1:
-    #       ef gh
-    #       ij l1/disp32
-    #   segments:
-    #     - 'code': {0x1054, 0, 9}
-    #   labels:
-    #     - 'l1': {'code', 2, 0x1056}
-    #
-    # output:
-    #   ab cd
-    #   ef gh
-    #   ij f9 ff ff ff  # -7
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . var segments/ECX = stream(10 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
-    68/push  0xa0/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . var labels/EDX = stream(512 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
-    68/push  0x2000/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # initialize input
-    # . write(_test-input-stream, "== code 0x1000\n")
-    # . . push args
-    68/push  "== code 0x1000\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "ab cd\n")
-    # . . push args
-    68/push  "ab cd\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "l1:\n")
-    # . . push args
-    68/push  "l1:\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "  ef gh\n")
-    # . . push args
-    68/push  "  ef gh\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "  ij l1/disp32\n")
-    # . . push args
-    68/push  "  ij l1/disp32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . stream-add4(segments, "code", 0x1054, 0, 9)
-    68/push  9/imm32/segment-size
-    68/push  0/imm32/file-offset
-    68/push  0x1054/imm32/start-address
-    68/push  "code"/imm32/segment-name
-    51/push-ECX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(labels, "l1", "code", 2, 0x1056)
-    68/push  0x1056/imm32/label-address
-    68/push  2/imm32/segment-offset
-    68/push  "code"/imm32/segment-name
-    68/push  "l1"/imm32/label-name
-    52/push-EDX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # component under test
-    # . emit-segments(_test-input-stream, _test-output-buffered-file, segments, labels)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  emit-segments/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # checks
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-code-label/0"/imm32
-    68/push  "ab cd "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-code-label/1"/imm32
-    68/push  "ef gh "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "ij f9 ff ff ff ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-code-label/2"/imm32
-    68/push  "ij f9 ff ff ff "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-emit-segments-code-label-absolute:
-    # labels can also convert to absolute addresses
-    #
-    # input:
-    #   in:
-    #     == code 0x1000
-    #     ab cd
-    #     l1:
-    #       ef gh
-    #       ij l1/imm32
-    #   segments:
-    #     - 'code': {0x1054, 0, 9}
-    #   labels:
-    #     - 'l1': {'code', 2, 0x1056}
-    #
-    # output:
-    #   ab cd
-    #   ef gh
-    #   ij 56 10 00 00
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-buffered-file+4)
-    # . . push args
-    b8/copy-to-EAX  _test-output-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    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
-    # . var segments/ECX = stream(10 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
-    68/push  0xa0/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # . var labels/EDX = stream(512 * 16)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
-    68/push  0x2000/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # initialize input
-    # . write(_test-input-stream, "== code 0x1000\n")
-    # . . push args
-    68/push  "== code 0x1000\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "ab cd\n")
-    # . . push args
-    68/push  "ab cd\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "l1:\n")
-    # . . push args
-    68/push  "l1:\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "  ef gh\n")
-    # . . push args
-    68/push  "  ef gh\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "  ij l1/imm32\n")
-    # . . push args
-    68/push  "  ij l1/imm32\n"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . stream-add4(segments, "code", 0x1054, 0, 9)
-    68/push  9/imm32/segment-size
-    68/push  0/imm32/file-offset
-    68/push  0x1054/imm32/start-address
-    68/push  "code"/imm32/segment-name
-    51/push-ECX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(labels, "l1", "code", 2, 0x1056)
-    68/push  0x1056/imm32/label-address
-    68/push  2/imm32/segment-offset
-    68/push  "code"/imm32/segment-name
-    68/push  "l1"/imm32/label-name
-    52/push-EDX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # component under test
-    # . emit-segments(_test-input-stream, _test-output-buffered-file, segments, labels)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    68/push  _test-output-buffered-file/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  emit-segments/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
-    # checks
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # }}}
-    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-code-label-absolute/0"/imm32
-    68/push  "ab cd "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-code-label-absolute/1"/imm32
-    68/push  "ef gh "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . check-next-stream-line-equal(_test-output-stream, "ij f9 ff ff ff ", msg)
-    # . . push args
-    68/push  "F - test-emit-segments-code-label-absolute/2"/imm32
-    68/push  "ij 56 10 00 00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-headers:  # out : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
-    # pseudocode:
-    #   emit-elf-header(out, segments, labels)
-    #   curr-segment = segments->data
-    #   max = segments->data + segments->write
-    #   while true
-    #     if (curr-segment >= max) break
-    #     emit-elf-program-header-entry(out, curr-segment)
-    #     curr-segment += 16                        # size of a row
-    #
-    # . prolog
-    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
-#?     # write(2/stderr, "emit-elf-header\n") {{{
-#?     # . . push args
-#?     68/push  "emit-elf-header\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # emit-elf-header(out, segments, labels)
-    # . . 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  emit-elf-header/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # EAX = segments
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
-    # ECX = segments->write
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
-    # curr-segment/EAX = segments->data
-    8d/copy-address                 1/mod/*+disp8   0/rm32/EAX    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy EAX+12 to EAX
-    # max/ECX = segments->data + segments->write
-    01/add                          3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # add EAX to ECX
-$emit-headers:loop:
-    # if (curr-segment >= max) break
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
-    0f 83/jump-if-greater-or-equal-unsigned  $emit-headers:end/disp32
-#?     # dump curr-segment->name {{{
-#?     # . write(2/stderr, "about to emit ph entry: segment->name: ")
-#?     # . . push args
-#?     68/push  "about to emit ph entry: segment->name: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . save EAX
-#?     50/push-EAX
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . . restore EAX
-#?     58/pop-to-EAX
-#?     # . print-int32-buffered(Stderr, &curr-segment)
-#?     # . . push args
-#?     50/push-EAX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  print-int32-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, " -> ")
-#?     # . . 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
-#?     # . print-int32-buffered(Stderr, curr-segment->name)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  print-int32-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "\n")
-#?     # . . push args
-#?     68/push  "\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-#?     # write(2/stderr, "emit-segment-header\n") {{{
-#?     # . . push args
-#?     68/push  "emit-segment-header\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # emit-elf-program-header-entry(out, curr-segment)
-    # . . push args
-    50/push-EAX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  emit-elf-program-header-entry/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # curr-segment += 16                        # size of a row
-    81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               0x10/imm32        # add to EAX
-    e9/jump  $emit-headers:loop/disp32
-$emit-headers:end:
-    # . restore registers
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-elf-header:  # out : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
-    # pseudocode
-    #   *Elf_e_entry = get(labels, "Entry")->address
-    #   *Elf_e_phnum = segments->write / 16         # size of a row
-    #   emit-hex-array(out, Elf_header)
-    #   write-buffered(out, "\n")
-    #
-    # . prolog
-    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  # just because we need to call idiv
-    # *Elf_e_entry = get(labels, "Entry")->address
-    # . EAX = labels
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
-    # . label-info/EAX = get(labels, "Entry", row-size=16)
-    # . . push args
-    68/push  0x10/imm32/row-size
-    68/push  "Entry"/imm32
-    50/push-EAX
-    # . . call
-    e8/call  get/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . EAX = label-info->address
-    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EAX+8) to EAX
-    # . *Elf_e_entry = EAX
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_e_entry/disp32                # copy EAX to *Elf_e_entry
-    # *Elf_e_phnum = segments->write / 0x10
-    # . EAX = segments
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
-    # . len/EAX = segments->write
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # copy *EAX to EAX
-    # . EAX = len / 0x10  (destroying EDX)
-    b9/copy-to-ECX  0x10/imm32
-    31/xor                          3/mod/direct    2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # clear EDX
-    f7          7/subop/idiv        3/mod/direct    1/rm32/ECX    .           .             .           .           .               .                 # divide EDX:EAX by ECX, storing quotient in EAX and remainder in EDX
-    # . *Elf_e_phnum = EAX
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_e_phnum/disp32                # copy EAX to *Elf_e_phnum
-    # emit-hex-array(out, Elf_header)
-    # . . push args
-    68/push  Elf_header/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  emit-hex-array/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-buffered(out, "\n")
-    # . . push args
-    68/push  "\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$emit-elf-header:end:
-    # . restore registers
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-emit-elf-program-header-entry:  # out : (address buffered-file), curr-segment : (address {string, segment-info})
-    # pseudocode:
-    #   *Elf_p_offset = curr-segment->file-offset
-    #   *Elf_p_vaddr = curr-segment->address
-    #   *Elf_p_paddr = curr-segment->address
-    #   *Elf_p_filesz = curr-segment->size
-    #   *Elf_p_memsz = curr-segment->size
-    #   if curr-segment->name == "code"
-    #     *Elf_p_flags = 5  # r-x
-    #   else
-    #     *Elf_p_flags = 6  # rw-
-    #   emit-hex-array(out, Elf_program_header_entry)
-    #   write-buffered(out, "\n")
-    #
-    # . prolog
-    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
-    # ESI = curr-segment
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    # *Elf_p_offset = curr-segment->file-offset
-    # . EAX = curr-segment->file-offset
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(ESI+8) to EAX
-    # . *Elf_p_offset = EAX
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_offset/disp32               # copy EAX to *Elf_p_offset
-    # *Elf_p_vaddr = curr-segment->address
-    # . EAX = curr-segment->address
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
-    # . *Elf_p_vaddr = EAX
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_vaddr/disp32                # copy EAX to *Elf_p_vaddr
-    # *Elf_p_paddr = curr-segment->address
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_paddr/disp32                # copy EAX to *Elf_p_paddr
-    # *Elf_p_filesz = curr-segment->size
-    # . EAX = curr-segment->size
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(ESI+12) to EAX
-    # . *Elf_p_filesz = EAX
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_filesz/disp32               # copy EAX to *Elf_p_filesz
-    # *Elf_p_memsz = curr-segment->size
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_memsz/disp32                # copy EAX to *Elf_p_memsz
-    # if (!string-equal?(curr-segment->name, "code") goto next check
-    # . EAX = curr-segment->name
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
-    # . EAX = string-equal?(curr-segment->name, "code")
-    # . . push args
-    68/push  "code"/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
-    # . if (EAX == 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-elf-program-header-entry:data/disp8
-    # *Elf_p_flags = r-x
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Elf_p_flags/disp32  5/imm32       # copy to *Elf_p_flags
-    eb/jump  $emit-elf-program-header-entry:really-emit/disp8
-$emit-elf-program-header-entry:data:
-    # otherwise *Elf_p_flags = rw-
-    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Elf_p_flags/disp32  6/imm32       # copy to *Elf_p_flags
-$emit-elf-program-header-entry:really-emit:
-    # emit-hex-array(out, Elf_program_header_entry)
-    # . . push args
-    68/push  Elf_program_header_entry/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  emit-hex-array/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-buffered(out, "\n")
-    # . . push args
-    68/push  "\n"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  write-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$emit-elf-program-header-entry:end:
-    # . restore registers
-    5e/pop-to-ESI
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# - some helpers for tests
-
-stream-add4:  # in : (address stream byte), key : address, val1 : address, val2 : address, val3 : address
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    50/push-EAX
-    51/push-ECX
-    52/push-EDX
-    56/push-ESI
-    # ESI = in
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # curr/EAX = in->data + in->write
-    # . EAX = in->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
-    # . EAX = ESI+EAX+12
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  0/index/EAX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+EAX+12 to EAX
-    # max/EDX = in->data + in->length
-    # . EDX = in->length
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(ESI+8) to EDX
-    # . EDX = ESI+EDX+12
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  2/index/EDX   .           2/r32/EDX   0xc/disp8       .                 # copy ESI+EDX+12 to EDX
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # compare EAX with EDX
-    73/jump-if-greater-or-equal-unsigned  $stream-add4:abort/disp8
-    # *curr = key
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   0xc/disp8       .                 # copy *(EBP+12) to ECX
-    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EAX
-    # curr += 4
-    05/add-to-EAX  4/imm32
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # compare EAX with EDX
-    73/jump-if-greater-or-equal-unsigned  $stream-add4:abort/disp8
-    # *curr = val1
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   0x10/disp8      .                 # copy *(EBP+16) to ECX
-    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EAX
-    # curr += 4
-    05/add-to-EAX  4/imm32
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # compare EAX with EDX
-    73/jump-if-greater-or-equal-unsigned  $stream-add4:abort/disp8
-    # *curr = val2
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   0x14/disp8      .                 # copy *(EBP+20) to ECX
-    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EAX
-    # curr += 4
-    05/add-to-EAX  4/imm32
-    # if (curr >= max) abort
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # compare EAX with EDX
-    73/jump-if-greater-or-equal-unsigned  $stream-add4:abort/disp8
-    # *curr = val3
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   0x18/disp8      .                 # copy *(EBP+24) to ECX
-    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EAX
-    # in->write += 16
-    81          0/subop/add         0/mod/indirect  6/rm32/ESI    .           .             .           .           .               0x10/imm32        # add to *ESI
-$stream-add4:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$stream-add4:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "overflow in stream-add4\n"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-# some variants of 'trace' that take multiple arguments in different combinations of types:
-#   n: int
-#   c: character [4-bytes, will eventually be UTF-8]
-#   s: (address string)
-#   l: (address slice)
-# one gotcha: 's5' must not be empty
-
-trace-sssns:  # s1 : (address string), s2 : (address string), s3 : (address string), n4 : int, s5 : (address string)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write(*Trace-stream, s1)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(*Trace-stream, s2)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(*Trace-stream, s3)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # print-int32(*Trace-stream, n4)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  print-int32/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # trace(s5)  # implicitly adds a newline and finalizes the trace line
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$trace-sssns:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-trace-sssns:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . *Trace-stream->write = 0
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy *Trace-stream to EAX
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # clear *EAX
-    # trace-sssns("A" "b" "c " 3 " e")
-    # . . push args
-    68/push  " e"/imm32
-    68/push  3/imm32
-    68/push  "c "/imm32
-    68/push  "b"/imm32
-    68/push  "A"/imm32
-    # . . call
-    e8/call  trace-sssns/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # check-trace-contains("Abc 0x00000003 e")
-    # . . push args
-    68/push  "F - test-trace-sssns"/imm32
-    68/push  "Abc 0x00000003 e"/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-trace-snsns:  # s1 : (address string), n2 : int, s3 : (address string), n4 : int, s5 : (address string)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write(*Trace-stream, s1)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # print-int32(*Trace-stream, n2)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  print-int32/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(*Trace-stream, s3)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # print-int32(*Trace-stream, n4)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  print-int32/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # trace(s5)  # implicitly adds a newline and finalizes the trace line
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$trace-snsns:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-trace-snsns:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . *Trace-stream->write = 0
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy *Trace-stream to EAX
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # clear *EAX
-    # trace-snsns("A " 2 " c " 3 " e")
-    # . . push args
-    68/push  " e"/imm32
-    68/push  3/imm32
-    68/push  " c "/imm32
-    68/push  2/imm32
-    68/push  "A "/imm32
-    # . . call
-    e8/call  trace-snsns/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # check-trace-contains("Abc 0x00000003 e")
-    # . . push args
-    68/push  "F - test-trace-snsns"/imm32
-    68/push  "A 0x00000002 c 0x00000003 e"/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-trace-slsls:  # s1 : (address string), l2 : (address slice), s3 : (address string), l4 : (address slice), s5 : (address string)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write(*Trace-stream, s1)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-slice(*Trace-stream, l2)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(*Trace-stream, s3)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-slice(*Trace-stream, l4)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # trace(s5)  # implicitly adds a newline and finalizes the trace line
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$trace-slsls:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-trace-slsls:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . *Trace-stream->write = 0
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy *Trace-stream to EAX
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # clear *EAX
-    # (EAX..ECX) = "b"
-    b8/copy-to-EAX  "b"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
-    05/add-to-EAX  4/imm32
-    # var b/EBX : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
-    # (EAX..ECX) = "d"
-    b8/copy-to-EAX  "d"/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 d/EDX : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # trace-slsls("A" b "c" d "e")
-    # . . push args
-    68/push  "e"/imm32
-    52/push-EDX
-    68/push  "c"/imm32
-    53/push-EBX
-    68/push  "A"/imm32
-    # . . call
-    e8/call  trace-slsls/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # check-trace-contains("Abcde")
-    # . . push args
-    68/push  "F - test-trace-slsls"/imm32
-    68/push  "Abcde"/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-trace-slsns:  # s1 : (address string), l2 : (address slice), s3 : (address string), n4 : int, s5 : (address string)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write(*Trace-stream, s1)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-slice(*Trace-stream, l2)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(*Trace-stream, s3)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # print-int32(*Trace-stream, n4)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  print-int32/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # trace(s5)  # implicitly adds a newline and finalizes the trace line
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$trace-slsns:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-trace-slsns:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . *Trace-stream->write = 0
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy *Trace-stream to EAX
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # clear *EAX
-    # (EAX..ECX) = "b"
-    b8/copy-to-EAX  "b"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
-    05/add-to-EAX  4/imm32
-    # var b/EBX : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
-    # trace-slsls("A" b "c " 3 " e")
-    # . . push args
-    68/push  " e"/imm32
-    68/push  3/imm32
-    68/push  "c "/imm32
-    53/push-EBX
-    68/push  "A"/imm32
-    # . . call
-    e8/call  trace-slsns/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # check-trace-contains("Abc 0x00000003 e")
-    # . . push args
-    68/push  "F - test-trace-slsls"/imm32
-    68/push  "Abc 0x00000003 e"/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-trace-slsss:  # s1 : (address string), l2 : (address slice), s3 : (address string), s4 : (address string), s5 : (address string)
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # write(*Trace-stream, s1)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-slice(*Trace-stream, l2)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(*Trace-stream, s3)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(*Trace-stream, s4)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # trace(s5)  # implicitly adds a newline and finalizes the trace line
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
-    # . . call
-    e8/call  trace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$trace-slsss:end:
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-trace-slsss:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . *Trace-stream->write = 0
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy *Trace-stream to EAX
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # clear *EAX
-    # (EAX..ECX) = "b"
-    b8/copy-to-EAX  "b"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
-    05/add-to-EAX  4/imm32
-    # var b/EBX : (address slice) = {EAX, ECX}
-    51/push-ECX
-    50/push-EAX
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
-    # trace-slsss("A" b "c" "d" "e")
-    # . . push args
-    68/push  "e"/imm32
-    68/push  "d"/imm32
-    68/push  "c"/imm32
-    53/push-EBX
-    68/push  "A"/imm32
-    # . . call
-    e8/call  trace-slsss/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-#?     # dump *Trace-stream {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write-stream(2/stderr, *Trace-stream)
-#?     # . . push args
-#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # check-trace-contains("Abcde")
-    # . . push args
-    68/push  "F - test-trace-slsss"/imm32
-    68/push  "Abcde"/imm32
-    # . . call
-    e8/call  check-trace-contains/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-num-bytes:  # line : (address stream) -> EAX : int
-    # pseudocode:
-    #   result = 0
-    #   while true
-    #     var word-slice = next-word(line)
-    #     if slice-empty?(word-slice)             # end of line
-    #       break
-    #     if slice-starts-with?(word-slice, "#")  # comment
-    #       break
-    #     if is-label?(word-slice)                # no need for label declarations anymore
-    #       break
-    #     if slice-equal?(word-slice, "==")
-    #       break                                 # no need for segment header lines
-    #     result += compute-width(word-slice)
-    #   return result
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    52/push-EDX
-    53/push-EBX
-    # var result/EAX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    # var word-slice/ECX = {0, 0}
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-#?     # dump line {{{
-#?     # . write(2/stderr, "LL: ")
-#?     # . . push args
-#?     68/push  "LL: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # write-stream(2/stderr, line)
-#?     # . . push args
-#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-    # . rewind-stream(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$num-bytes:loop:
-    # next-word(line, word-slice)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # dump word-slice {{{
-#?     # . write(2/stderr, "AA: ")
-#?     # . . push args
-#?     68/push  "AA: "/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . clear-stream(Stderr+4)
-#?     # . . save EAX
-#?     50/push-EAX
-#?     # . . push args
-#?     b8/copy-to-EAX  Stderr/imm32
-#?     05/add-to-EAX  4/imm32
-#?     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
-#?     # . . restore EAX
-#?     58/pop-to-EAX
-#?     # . write-slice-buffered(Stderr, word-slice)
-#?     # . . push args
-#?     51/push-ECX
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  write-slice-buffered/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # . flush(Stderr)
-#?     # . . push args
-#?     68/push  Stderr/imm32
-#?     # . . call
-#?     e8/call  flush/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-#?     # }}}
-$num-bytes:check0:
-    # if (slice-empty?(word-slice)) break
-    # . save result
-    50/push-EAX
-    # . EAX = slice-empty?(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX != 0) break
-    3d/compare-EAX-and  0/imm32
-    # . restore result now that ZF is set
-    58/pop-to-EAX
-    75/jump-if-not-equal  $num-bytes:end/disp8
-$num-bytes:check-for-comment:
-    # if (slice-starts-with?(word-slice, "#")) break
-    # . start/EDX = word-slice->start
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
-    # . c/EBX = *start
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           3/r32/BL    .               .                 # copy byte at *EDX to BL
-    # . if (EBX == '#') break
-    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0x23/imm32/hash   # compare EBX
-    74/jump-if-equal  $num-bytes:end/disp8
-$num-bytes:check-for-label:
-    # if (slice-ends-with?(word-slice, ":")) break
-    # . end/EDX = word-slice->end
-    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
-    # . c/EBX = *(end-1)
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-    8a/copy-byte                    1/mod/*+disp8   2/rm32/EDX    .           .             .           3/r32/BL    -1/disp8        .                 # copy byte at *ECX to BL
-    # . if (EBX == ':') break
-    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0x3a/imm32/colon  # compare EBX
-    74/jump-if-equal  $num-bytes:end/disp8
-$num-bytes:check-for-segment-header:
-    # if (slice-equal?(word-slice, "==")) break
-    # . push result
-    50/push-EAX
-    # . EAX = slice-equal?(word-slice, "==")
-    # . . push args
-    68/push  "=="/imm32
-    51/push-ECX
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) break
-    3d/compare-EAX-and  0/imm32
-    # . restore result now that ZF is set
-    58/pop-to-EAX
-    75/jump-if-not-equal  $num-bytes:end/disp8
-$num-bytes:loop-body:
-    # result += compute-width-of-slice(word-slice)
-    # . copy result to EDX
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDX
-    # . EAX = compute-width-of-slice(word-slice)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call compute-width-of-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . EAX += result
-    01/add                          3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # add EDX to EAX
-    e9/jump  $num-bytes:loop/disp32
-$num-bytes:end:
-    # . rewind-stream(line)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . restore registers
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-num-bytes-handles-empty-string:
-    # if a line starts with '#', return 0
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # no contents in input
-    # EAX = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 0, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-handles-empty-string"/imm32
-    68/push  0/imm32/true
-    50/push-EAX
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-num-bytes-ignores-comments:
-    # if a line starts with '#', return 0
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize input
-    # . write(_test-input-stream, "# abcd")
-    # . . push args
-    68/push  "# abcd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 0, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-ignores-comments"/imm32
-    68/push  0/imm32/true
-    50/push-EAX
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-num-bytes-ignores-labels:
-    # if the first word ends with ':', return 0
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize input
-    # . write(_test-input-stream, "ab: # cd")
-    # . . push args
-    68/push  "ab: # cd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 0, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-ignores-labels"/imm32
-    68/push  0/imm32/true
-    50/push-EAX
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-num-bytes-ignores-segment-headers:
-    # if the first word is '==', return 0
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize input
-    # . write(_test-input-stream, "== ab cd")
-    # . . push args
-    68/push  "== ab cd"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 0, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-ignores-segment-headers"/imm32
-    68/push  0/imm32/true
-    50/push-EAX
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-num-bytes-counts-words-by-default:
-    # without metadata, count words
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize input
-    # . write(_test-input-stream, "ab cd ef")
-    # . . push args
-    68/push  "ab cd ef"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 3, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-counts-words-by-default"/imm32
-    68/push  3/imm32/true
-    50/push-EAX
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-num-bytes-ignores-trailing-comment:
-    # trailing comments appropriately ignored
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize input
-    # . write(_test-input-stream, "ab cd # ef")
-    # . . push args
-    68/push  "ab cd # ef"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 2, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-ignores-trailing-comment"/imm32
-    68/push  2/imm32/true
-    50/push-EAX
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-num-bytes-handles-imm32:
-    # if a word has the /imm32 metadata, count it as 4 bytes
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # setup
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # initialize input
-    # . write(_test-input-stream, "ab cd/imm32 ef")
-    # . . push args
-    68/push  "ab cd/imm32 ef"/imm32
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # EAX = num-bytes(_test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    # . . call
-    e8/call  num-bytes/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # check-ints-equal(EAX, 6, msg)
-    # . . push args
-    68/push  "F - test-num-bytes-handles-imm32"/imm32
-    68/push  6/imm32/true
-    50/push-EAX
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data
-
-# This block of bytes gets copied to the start of the output ELF file, with
-# some fields filled in.
-# http://www.sco.com/developers/gabi/latest/ch4.eheader.html
-Elf_header:
-  # - length
-  0x34/imm32
-  # - data
-$e_ident:
-  7f 45/E 4c/L 46/F
-  01/32-bit  01/little-endian  01/file-version  00/no-os-extensions
-  00 00 00 00 00 00 00 00  # 8 bytes of padding
-$e_type:
-  02 00
-$e_machine:
-  03 00
-$e_version:
-  1/imm32
-Elf_e_entry:
-  0x09000000/imm32  # approximate default; must be updated
-$e_phoff:
-  0x34/imm32  # offset for the 'program header table' containing segment headers
-$e_shoff:
-  0/imm32  # no sections
-$e_flags:
-  0/imm32  # unused
-$e_ehsize:
-  0x34 00
-$e_phentsize:
-  0x20 00
-Elf_e_phnum:
-  00 00  # number of segments; must be updated
-$e_shentsize:
-  00 00  # no sections
-$e_shnum:
-  00 00
-$e_shstrndx:
-  00 00
-
-# This block of bytes gets copied after the Elf_header once for each segment.
-# Some fields need filling in each time.
-# https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-83432/index.html
-Elf_program_header_entry:
-  # - length
-  0x20/imm32
-  # - data
-$p_type:
-  1/imm32/PT_LOAD
-Elf_p_offset:
-  0/imm32  # byte offset in the file at which a segment begins; must be updated
-Elf_p_vaddr:
-  0/imm32  # starting address to store the segment at before running the program
-Elf_p_paddr:
-  0/imm32  # should have same value as Elf_p_vaddr
-Elf_p_filesz:
-  0/imm32
-Elf_p_memsz:
-  0/imm32  # should have same value as Elf_p_filesz
-Elf_p_flags:
-  6/imm32/rw-  # read/write/execute permissions for the segment; must be updated for the code segment
-$p_align:
-  # we hold this constant; changing it will require adjusting the way we
-  # compute the starting address for each segment
-  0x1000/imm32
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/apps/tests b/subx/apps/tests
deleted file mode 100755
index 52af8441..00000000
--- a/subx/apps/tests
+++ /dev/null
Binary files differdiff --git a/subx/apps/tests.subx b/subx/apps/tests.subx
deleted file mode 100644
index 12f4902b..00000000
--- a/subx/apps/tests.subx
+++ /dev/null
@@ -1,279 +0,0 @@
-# Generate code for a new function called 'run-tests' which calls in sequence
-# all functions starting with 'test-'.
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # Heap = new-segment(Heap-size)
-    # . . push args
-    68/push  Heap/imm32
-    68/push  Heap-size/imm32
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # initialize-trace-stream(256KB)
-    # . . push args
-    68/push  0x40000/imm32/256KB
-    # . . call
-    e8/call  initialize-trace-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-
-    # run tests if necessary, convert stdin if not
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # initialize heap
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # . argc > 1
-    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # . argv[1] == "test"
-    # . . push args
-    68/push  "test"/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  kernel-string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . check result
-    3d/compare-EAX-and  1/imm32
-    75/jump-if-not-equal  $run-main/disp8
-    # . run-tests()
-    e8/call  run-tests/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    eb/jump  $main:end/disp8
-$run-main:
-    # - otherwise convert stdin
-    # convert(Stdin, Stdout)
-    # . . push args
-    68/push  Stdout/imm32
-    68/push  Stdin/imm32
-    # . . call
-    e8/call  convert/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 0)
-    bb/copy-to-EBX  0/imm32
-$main:end:
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
-    # pseudocode
-    #   bool tests-found = false
-    #   var line = new-stream(512, 1)
-    #   var new-code-segment = new-stream(Segment-size, 1)
-    #   write(new-code-segment, "\n==code\n")
-    #   write(new-code-segment, "run-tests:\n")
-    #   while true
-    #     clear-stream(line)
-    #     read-line-buffered(in, line)
-    #     if (line->write == 0) break               # end of file
-    #     var word-slice = next-word(line)
-    #     if is-label?(word-slice)
-    #       if slice-starts-with?(word-slice, "test-")
-    #         tests-found = true
-    #         write(new-code-segment, "  e8/call  ")
-    #         write-slice(new-code-segment, word-slice)
-    #         write(new-code-segment, "/disp32\n")
-    #     rewind-stream(line)
-    #     write-stream-data(out, line)
-    #   if tests-found
-    #     write(new-code-segment, "  c3/return\n")
-    #     write-stream-data(out, new-code-segment)
-    #   flush(out)
-    #
-    # . prolog
-    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
-    # var line/ECX : (address stream byte) = stream(512)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
-    68/push  0x200/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # var word-slice/EDX = {0, 0}
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-    # tests-found?/EBX = false
-    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
-    # new-code-segment/EDI = new-stream(Heap, Segment-size, 1)
-    # . EAX = new-stream(Heap, Segment-size, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Segment-size/disp32               # push *Segment-size
-    68/push  Heap/imm32
-    # . . call
-    e8/call  new-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # . EDI = EAX
-    89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDI
-    # write(new-code-segment, "\n== code\n")
-    # . . push args
-    68/push  "\n== code\n"/imm32
-    57/push-EDI
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(new-code-segment, "run-tests:\n")
-    # . . push args
-    68/push  "run-tests:\n"/imm32
-    57/push-EDI
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:loop:
-    # clear-stream(line)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  clear-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read-line-buffered(in, line)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  read-line-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:check0:
-    # if (line->write == 0) break
-    81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
-    0f 84/jump-if-equal  $convert:break/disp32
-    # next-word(line, word-slice)
-    # . . push args
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:check-for-label:
-    # if (!is-label?(word-slice)) continue
-    # . EAX = is-label?(word-slice)
-    # . . push args
-    52/push-EDX
-    # . . call
-    e8/call  is-label?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . if (EAX == 0) continue
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $convert:continue/disp8
-$convert:check-label-prefix:
-    # strip trailing ':' from word-slice
-    ff          1/subop/decrement   1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         .                 # decrement *(EDX+4)
-    # if !slice-starts-with?(word-slice, "test-") continue
-    # . . push args
-    68/push  "test-"/imm32
-    52/push-EDX
-    # . . call
-    e8/call  slice-starts-with?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX == 0) break
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $convert:continue/disp8
-$convert:call-test-function:
-    # tests-found? = true
-    bb/copy-to-EBX  1/imm32/true
-    # write(new-code-segment, "  e8/call  ")
-    # . . push args
-    68/push  "  e8/call  "/imm32
-    57/push-EDI
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-slice(new-code-segment, word-slice)
-    # . . push args
-    52/push-EDX
-    57/push-EDI
-    # . . call
-    e8/call  write-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write(new-code-segment, "/disp32\n")
-    # . . push args
-    68/push  "/disp32\n"/imm32
-    57/push-EDI
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:continue:
-    # rewind-stream(line)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  rewind-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # write-stream-data(out, line)
-    # . . push args
-    51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # loop
-    e9/jump  $convert:loop/disp32
-$convert:break:
-    # if (!tests-found?) goto end
-    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0/imm32           # compare EBX
-    74/jump-if-equal  $convert:end/disp8
-    # write(new-code-segment, "  c3/return\n")
-    # . . push args
-    68/push  "  c3/return\n"/imm32
-    57/push-EDI
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # write-stream-data(out, new-code-segment)
-    # . . push args
-    57/push-EDI
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  write-stream-data/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:end:
-    # flush(out)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  flush/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x214/imm32       # add to ESP
-    # . restore registers
-    5f/pop-to-EDI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    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/subx/build b/subx/build
deleted file mode 100755
index 67479b92..00000000
--- a/subx/build
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/bin/sh
-# returns 0 on successful build or nothing to build
-# non-zero exit status only on error during building
-set -e  # stop immediately on error
-
-# [0-9]*.cc -> subx.cc -> subx_bin
-# (layers)   |          |
-#          tangle      $CXX
-
-# can also be called with a layer to only build until
-#   $ ./build --until 050
-UNTIL_LAYER=${2:-zzz}
-
-# we use two mechanisms to speed up rebuilds:
-# - older_than: run a command if the output is older than any of the inputs
-# - update: if a command is quick to run, always run it but update the result only on any change
-#
-# avoid combining both mechanisms to generate a single file
-# otherwise you'll see spurious messages about files being updated
-# risk: a file may unnecessarily update without changes, causing unnecessary work downstream
-
-test "$CXX" || export CXX=c++
-test "$CC" || export CC=cc
-test "$CFLAGS" || export CFLAGS="-g -O3 -std=c++98"  # CI has an ancient version; don't expect recent dialects
-export CFLAGS="$CFLAGS -Wall -Wextra -fno-strict-aliasing"
-
-# return 1 if $1 is older than _any_ of the remaining args
-older_than() {
-  local target=$1
-  shift
-  if [ ! -e $target ]
-  then
-#?     echo "$target doesn't exist"
-    echo "updating $target" >&2
-    return 0  # success
-  fi
-  local f
-  for f in $*
-  do
-    if [ $f -nt $target ]
-    then
-      echo "updating $target" >&2
-      return 0  # success
-    fi
-  done
-  return 1  # failure
-}
-
-# redirect to $1, unless it's already identical
-update() {
-  if [ ! -e $1 ]
-  then
-    cat > $1
-  else
-    cat > $1.tmp
-    diff -q $1 $1.tmp >/dev/null  &&  rm $1.tmp  ||  mv $1.tmp $1
-  fi
-}
-
-update_cp() {
-  if [ ! -e $2/$1 ]
-  then
-    cp $1 $2
-  elif [ $1 -nt $2/$1 ]
-  then
-    cp $1 $2
-  fi
-}
-
-noisy_cd() {
-  cd $1
-  echo "-- `pwd`" >&2
-}
-
-older_than ../enumerate/enumerate ../enumerate/enumerate.cc && {
-  $CXX $CFLAGS ../enumerate/enumerate.cc -o ../enumerate/enumerate
-}
-
-older_than ../tangle/tangle ../tangle/*.cc && {
-  noisy_cd ../tangle
-    {
-      grep -h "^struct .* {" [0-9]*.cc  |sed 's/\(struct *[^ ]*\).*/\1;/'
-      grep -h "^typedef " [0-9]*.cc
-    }  |update type_list
-    grep -h "^[^ #].*) {" [0-9]*.cc  |sed 's/ {.*/;/'  |update function_list
-    ls [0-9]*.cc  |grep -v "\.test\.cc$"  |sed 's/.*/#include "&"/'  |update file_list
-    ls [0-9]*.test.cc  |sed 's/.*/#include "&"/'  |update test_file_list
-    grep -h "^[[:space:]]*void test_" [0-9]*.cc  |sed 's/^\s*void \(.*\)() {$/\1,/'  |update test_list
-    grep -h "^\s*void test_" [0-9]*.cc  |sed 's/^\s*void \(.*\)() {.*/"\1",/'  |update test_name_list
-    $CXX $CFLAGS boot.cc -o tangle
-    ./tangle test
-  noisy_cd ../subx  # no effect; just to show us returning to the parent directory
-}
-
-LAYERS=$(../enumerate/enumerate --until $UNTIL_LAYER  |grep '.cc$')
-older_than subx.cc $LAYERS ../enumerate/enumerate ../tangle/tangle && {
-  # no update here; rely on 'update' calls downstream
-  ../tangle/tangle $LAYERS  > subx.cc
-}
-
-grep -h "^[^[:space:]#].*) {$" subx.cc  |grep -v ":.*("  |sed 's/ {.*/;/'  |update function_list
-grep -h "^\s*void test_" subx.cc  |sed 's/^\s*void \(.*\)() {.*/\1,/'  |update test_list
-grep -h "^\s*void test_" subx.cc  |sed 's/^\s*void \(.*\)() {.*/"\1",/'  |update test_name_list
-
-older_than subx_bin subx.cc *_list && {
-  $CXX $CFLAGS subx.cc -o subx_bin
-}
-
-if [ $# -eq 0 ]
-then
-
-  # Assumption: SubX programs don't need to be retranslated every time we
-  # rebuild the C++ bootstrap.
-
-  # simple example programs
-  for n in `seq 1 12`
-  do
-    older_than examples/ex$n examples/ex$n.subx && {
-      ./subx_bin translate examples/ex$n.subx -o examples/ex$n
-    }
-  done
-
-  # simple apps that use the standard library
-  for app in factorial crenshaw2-1 crenshaw2-1b handle
-  do
-    older_than apps/$app apps/$app.subx [0-9]*.subx && {
-      ./subx_bin translate [0-9]*.subx apps/$app.subx -o apps/$app
-    }
-  done
-
-  # self-hosting translator
-  for phase in hex survey pack assort dquotes tests
-  do
-    older_than apps/$phase apps/$phase.subx apps/subx-common.subx [0-9]*.subx && {
-      ./subx_bin translate [0-9]*.subx apps/subx-common.subx apps/$phase.subx -o apps/$phase
-    }
-  done
-
-fi
-
-exit 0
diff --git a/subx/build_and_test_until b/subx/build_and_test_until
deleted file mode 100755
index 710e1d55..00000000
--- a/subx/build_and_test_until
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/sh
-# Run tests for just a subset of layers.
-#
-# Usage:
-#   build_and_test_until [file prefix] [test name]
-# Provide the second arg to run just a single test.
-set -e
-
-# clean previous builds if they were building until a different layer
-touch .until
-PREV_UNTIL=`cat .until`
-if [ "$PREV_UNTIL" != $1 ]
-then
-  ./clean top-level
-  echo $1 > .until
-fi
-
-./build --until $1  &&  ./subx_bin test $2
diff --git a/subx/cheatsheet.pdf b/subx/cheatsheet.pdf
deleted file mode 100644
index 386738e9..00000000
--- a/subx/cheatsheet.pdf
+++ /dev/null
Binary files differdiff --git a/subx/clean b/subx/clean
deleted file mode 100755
index 2878ea58..00000000
--- a/subx/clean
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-set -e
-
-set -v
-rm -rf subx.cc subx_bin* *_list
-test $# -gt 0 && exit 0  # convenience: 'clean top-level' to leave subsidiary tools alone
-rm -rf ../enumerate/enumerate ../tangle/tangle ../tangle/*_list ../*/*.dSYM
-rm -rf .until
diff --git a/subx/diff_ntranslate b/subx/diff_ntranslate
deleted file mode 100755
index 46e76844..00000000
--- a/subx/diff_ntranslate
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env zsh
-
-set -e
-subx translate $* -o a0.elf
-xxd a0.elf > a0.xxd
-./ntranslate $*  # into a.elf and a.xxd
-diff a0.xxd a.xxd
diff --git a/subx/diff_translate b/subx/diff_translate
deleted file mode 100755
index dbe55a24..00000000
--- a/subx/diff_translate
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env zsh
-
-set -e
-subx translate $* -o a0.elf
-xxd a0.elf > a0.xxd
-./translate $*  # into a.elf and a.xxd
-diff a0.xxd a.xxd
diff --git a/subx/edit b/subx/edit
deleted file mode 100755
index dc434f58..00000000
--- a/subx/edit
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env zsh
-# Helper to more conveniently open commonly-used SubX programs.
-
-if [ $# -eq 0 ]
-then
-  echo "Usage: $0 <file root without subdirectory or .subx extension>"
-  echo
-  echo "Naming convention: Files starting with 'ex' will be assumed to live in examples/"
-  echo "Other files will be assumed to live in apps/"
-  exit 1
-fi
-
-if [[ $EDITOR == *'vim'* ]]
-then
-  LOCAL_SETTINGS='-S vimrc.vim'
-fi
-
-if [[ $1 == 'ex'* ]]
-then
-  eval $EDITOR $LOCAL_SETTINGS examples/$1.subx
-else
-  eval $EDITOR $LOCAL_SETTINGS apps/$1.subx
-fi
diff --git a/subx/examples/Readme.md b/subx/examples/Readme.md
deleted file mode 100644
index d4bb1ff5..00000000
--- a/subx/examples/Readme.md
+++ /dev/null
@@ -1,6 +0,0 @@
-Small example programs, each with a simple pedagogical goal.
-
-They also help to validate SubX instruction semantics against native x86
-hardware. For example, loading a single byte to a register would for some time
-clear the rest of the register. This behavior was internally consistent with
-unit tests. It took running an example binary natively to catch the discrepancy.
diff --git a/subx/examples/ex1 b/subx/examples/ex1
deleted file mode 100755
index aeb62302..00000000
--- a/subx/examples/ex1
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex1.subx b/subx/examples/ex1.subx
deleted file mode 100644
index 0aca40f8..00000000
--- a/subx/examples/ex1.subx
+++ /dev/null
@@ -1,21 +0,0 @@
-# First program: same as https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
-# Just return 42.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex1.2.subx -o examples/ex1
-#   $ ./subx run examples/ex1
-# Expected result:
-#   $ echo $?
-#   42
-
-== code 0x09000000
-
-Entry:
-# syscall(exit, 42)
-bb/copy-to-EBX  2a/imm32  # 42 in hex
-b8/copy-to-EAX  1/imm32/exit
-cd/syscall  0x80/imm8
-
-== data 0x0a000000
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex10 b/subx/examples/ex10
deleted file mode 100755
index d8ad528e..00000000
--- a/subx/examples/ex10
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex10.subx b/subx/examples/ex10.subx
deleted file mode 100644
index 51cc0a8c..00000000
--- a/subx/examples/ex10.subx
+++ /dev/null
@@ -1,72 +0,0 @@
-# String comparison: return 1 iff the two args passed in at the commandline are equal.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex10.subx -o examples/ex10
-#   $ ./subx run examples/ex10 abc abd
-# Expected result:
-#   $ echo $?
-#   0  # false
-
-== code 0x09000000
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # return argv-equal(argv[1], argv[2])
-#       At the start of a SubX program:
-#         argc: *ESP
-#         argv[0]: *(ESP+4)
-#         argv[1]: *(ESP+8)
-#         ...
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # argv-equal(argv[1], argv[2])
-    # . . push argv[2]
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . push argv[1]
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call argv-equal/disp32
-    # syscall(exit, EAX)
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# compare two null-terminated ascii strings
-# reason for the name: the only place we should have null-terminated ascii strings is from commandline args
-argv-equal:  # (s1, s2) : null-terminated ascii strings -> EAX : boolean
-    # initialize s1 (ECX) and s2 (EDX)
-    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   4/disp8         .                 # copy *(ESP+4) to ECX
-    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none  .           2/r32/EDX   8/disp8         .                 # copy *(ESP+8) to EDX
-$argv-equal:loop:
-    # c1/EAX, c2/EBX = *s1, *s2
-    b8/copy-to-EAX  0/imm32
-    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
-    bb/copy-to-EBX  0/imm32
-    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           3/r32/BL    .               .                 # copy byte at *EDX to BL
-    # if (c1 == 0) break
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $argv-equal:break/disp8
-    # if (c1 != c2) return false
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # compare EAX and EBX
-    75/jump-if-not-equal  $argv-equal:false/disp8
-    # ++s1, ++s2
-    41/increment-ECX
-    42/increment-EDX
-    # end while
-    eb/jump  $argv-equal:loop/disp8
-$argv-equal:break:
-    # if (c2 == 0) return true
-    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0/imm32           # compare EBX
-    75/jump-if-not-equal  $argv-equal:false/disp8
-$argv-equal:success:
-    b8/copy-to-EAX  1/imm32
-    c3/return
-    # return false
-$argv-equal:false:
-    b8/copy-to-EAX  0/imm32
-    c3/return
-
-== data 0x0a000000
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex11 b/subx/examples/ex11
deleted file mode 100755
index 0ffafb6f..00000000
--- a/subx/examples/ex11
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex11.subx b/subx/examples/ex11.subx
deleted file mode 100644
index ba75c1d3..00000000
--- a/subx/examples/ex11.subx
+++ /dev/null
@@ -1,357 +0,0 @@
-# Null-terminated vs length-prefixed ascii strings.
-#
-# By default we create strings with a 4-byte length prefix rather than a null suffix.
-# However we still need null-prefixed strings when interacting with the Linux
-# kernel in a few places. This layer implements a function for comparing
-# a null-terminated 'kernel string' with a length-prefixed 'SubX string'.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex11.subx -o examples/ex11
-#   $ ./subx run examples/ex11  # runs a series of tests
-#   ......  # all tests pass
-#
-# (We can't yet run the tests when given a "test" commandline argument,
-# because checking for it would require the function being tested! Breakage
-# would cause tests to not run, rather than to fail as we'd like.)
-
-== code 0x09000000
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:  # run all tests
-    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
-    # syscall(exit, EAX)
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# compare a null-terminated ascii string with a more idiomatic length-prefixed byte array
-# reason for the name: the only place we should have null-terminated ascii strings is from commandline args
-kernel-string-equal?:  # s : null-terminated ascii string, benchmark : length-prefixed ascii string -> EAX : boolean
-    # pseudocode:
-    #   n = benchmark->length
-    #   s1 = s
-    #   s2 = benchmark->data
-    #   i = 0
-    #   while i < n
-    #     c1 = *s1
-    #     c2 = *s2
-    #     if (c1 == 0) return false
-    #     if (c1 != c2) return false
-    #     ++s1, ++s2, ++i
-    #   return *s1 == 0
-    #
-    # registers:
-    #   i: ECX
-    #   n: EDX
-    #   s1: EDI
-    #   s2: ESI
-    #   c1: EAX
-    #   c2: EBX
-    #
-    # . prolog
-    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
-    # s1/EDI = s
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-    # n/EDX = benchmark->length
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # copy *EDX to EDX
-    # s2/ESI = benchmark->data
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
-    81          0/subop/add         3/mod/direct    6/rm32/ESI    .           .             .           .           .               4/imm32           # add to ESI
-    # i/ECX = c1/EAX = c2/EBX = 0
-    b9/copy-to-ECX  0/imm32/exit
-    b8/copy-to-EAX  0/imm32
-    bb/copy-to-EBX  0/imm32
-$kernel-string-equal?:loop:
-    # if (i >= n) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    7d/jump-if-greater-or-equal  $kernel-string-equal?:break/disp8
-    # c1 = *s1
-    8a/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/AL    .               .                 # copy byte at *EDI to AL
-    # c2 = *s2
-    8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           3/r32/BL    .               .                 # copy byte at *ESI to BL
-    # if (c1 == 0) return false
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $kernel-string-equal?:false/disp8
-    # if (c1 != c2) return false
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # compare EAX and EBX
-    75/jump-if-not-equal  $kernel-string-equal?:false/disp8
-    # ++i
-    41/increment-ECX
-    # ++s1
-    47/increment-EDI
-    # ++s2
-    46/increment-ESI
-    eb/jump  $kernel-string-equal?:loop/disp8
-$kernel-string-equal?:break:
-    # return *s1 == 0
-    8a/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/AL    .               .                 # copy byte at *EDI to AL
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $kernel-string-equal?:false/disp8
-$kernel-string-equal?:true:
-    b8/copy-to-EAX  1/imm32
-    eb/jump  $kernel-string-equal?:end/disp8
-$kernel-string-equal?:false:
-    b8/copy-to-EAX  0/imm32
-$kernel-string-equal?:end:
-    # . restore registers
-    5f/pop-to-EDI
-    5e/pop-to-ESI
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-# - tests
-
-test-compare-null-kernel-string-with-empty-array:
-    # EAX = kernel-string-equal?(Null-kernel-string, "")
-    # . . push args
-    68/push  ""/imm32
-    68/push  Null-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-null-kernel-string-with-empty-array"/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-null-kernel-string-with-non-empty-array:
-    # EAX = kernel-string-equal?(Null-kernel-string, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  Null-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-null-kernel-string-with-non-empty-array"/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-kernel-string-with-equal-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "Abc")
-    # . . push args
-    68/push  "Abc"/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-equal-array"/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-kernel-string-with-inequal-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "Adc")
-    # . . push args
-    68/push  "Adc"/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-equal-array"/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-kernel-string-with-empty-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "")
-    # . . push args
-    68/push  ""/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-equal-array"/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-kernel-string-with-shorter-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "Ab")
-    # . . push args
-    68/push  "Ab"/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-shorter-array"/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-kernel-string-with-longer-array:
-    # EAX = kernel-string-equal?(_test-Abc-kernel-string, "Abcd")
-    # . . push args
-    68/push  "Abcd"/imm32
-    68/push  _test-Abc-kernel-string/imm32
-    # . . call
-    e8/call  kernel-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-kernel-string-with-longer-array"/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
-
-# - helpers
-
-# print msg to stderr if a != b, otherwise print "."
-check-ints-equal:  # (a : int, b : int, msg : (address array byte)) -> boolean
-    # . prolog
-    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
-    # load args into EAX, EBX and ECX
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
-    # if (EAX == b/EBX) print('.') and return
-    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # compare EAX and EBX
-    75/jump-if-unequal  $check-ints-equal:else/disp8
-    # . write-stderr('.')
-    # . . push args
-    68/push  "."/imm32
-    # . . call
-    e8/call  write-stderr/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # . return
-    eb/jump  $check-ints-equal:end/disp8
-    # otherwise print(msg)
-$check-ints-equal:else:
-    # copy msg into ECX
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8       .                # copy *(EBP+16) to ECX
-    # print(ECX)
-    # . . push args
-    51/push-ECX
-    # . . call
-    e8/call  write-stderr/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # print newline
-    # . . push args
-    68/push  Newline/imm32
-    # . . call
-    e8/call  write-stderr/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$check-ints-equal:end:
-    # . restore registers
-    5b/pop-to-EBX
-    59/pop-to-ECX
-    # end
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-write-stderr:  # s : (address array byte) -> <void>
-    # . prolog
-    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
-    # syscall(write, 2/stderr, (data) s+4, (size) *s)
-    # . . fd = 2 (stderr)
-    bb/copy-to-EBX  2/imm32
-    # . . x = s+4
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
-    81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm32           # add to ECX
-    # . . size = *s
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EBP+8) to EDX
-    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # copy *EDX to EDX
-    # . . syscall
-    b8/copy-to-EAX  4/imm32/write
-    cd/syscall  0x80/imm8
-    # . restore registers
-    5b/pop-to-EBX
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . end
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-== data 0x0a000000
-
-Newline:
-    # size
-    1/imm32
-    # data
-    0a/newline
-
-# for kernel-string-equal tests
-Null-kernel-string:
-    00/null
-
-_test-Abc-kernel-string:
-    41/A 62/b 63/c 00/null
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex12 b/subx/examples/ex12
deleted file mode 100755
index c13a86ed..00000000
--- a/subx/examples/ex12
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex12.subx b/subx/examples/ex12.subx
deleted file mode 100644
index 358da1d3..00000000
--- a/subx/examples/ex12.subx
+++ /dev/null
@@ -1,45 +0,0 @@
-# Example showing mmap syscall.
-# Create a new segment using mmap, save the address, write to it.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex12.subx -o examples/ex12
-#   $ ./subx run examples/ex12
-# You shouldn't get a segmentation fault.
-
-== code 0x09000000
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # syscall(mmap, 0x1000)
-    bb/copy-to-EBX  Mmap-new-segment/imm32
-    b8/copy-to-EAX  0x5a/imm32/mmap
-    cd/syscall  0x80/imm8
-
-    # write to *EAX to check that we have access to the newly-allocated segment
-    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0x34/imm32        # copy to *EAX
-
-    # syscall(exit, EAX)
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-== data 0x0a000000
-
-# various constants used here were found in the Linux sources (search for file mman-common.h)
-Mmap-new-segment:  # type mmap_arg_struct
-    # addr
-    0/imm32
-    # len
-    0x100/imm32
-    # protection flags
-    3/imm32  # PROT_READ | PROT_WRITE
-    # sharing flags
-    0x22/imm32  # MAP_PRIVATE | MAP_ANONYMOUS
-    # fd
-    -1/imm32  # since MAP_ANONYMOUS is specified
-    # offset
-    0/imm32  # since MAP_ANONYMOUS is specified
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex2 b/subx/examples/ex2
deleted file mode 100755
index 55a87a3f..00000000
--- a/subx/examples/ex2
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex2.subx b/subx/examples/ex2.subx
deleted file mode 100644
index 025eb0fa..00000000
--- a/subx/examples/ex2.subx
+++ /dev/null
@@ -1,23 +0,0 @@
-# Add 1 and 1, and return the result in the exit code.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex2.subx -o examples/ex2
-#   $ ./subx run examples/ex2
-# Expected result:
-#   $ echo $?
-#   2
-
-== code 0x09000000
-
-Entry:
-# EBX = 1
-bb/copy-to-EBX  1/imm32
-# increment EBX
-43/increment-EBX
-# syscall(exit, EBX)
-b8/copy-to-EAX  1/imm32/exit
-cd/syscall  0x80/imm8
-
-== data 0x0a000000
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex3 b/subx/examples/ex3
deleted file mode 100755
index d85aba8e..00000000
--- a/subx/examples/ex3
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex3.subx b/subx/examples/ex3.subx
deleted file mode 100644
index 1d52e87f..00000000
--- a/subx/examples/ex3.subx
+++ /dev/null
@@ -1,39 +0,0 @@
-# Add the first 10 numbers, and return the result in the exit code.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex3.subx -o examples/ex3
-#   $ ./subx run examples/ex3
-# Expected result:
-#   $ echo $?
-#   55
-
-== code 0x09000000
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # result: EBX = 0
-    bb/copy-to-EBX  0/imm32
-    # counter: ECX = 1
-    b9/copy-to-ECX  1/imm32
-
-$loop:
-    # if (counter > 10) break
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0xa/imm32         # compare ECX
-    7f/jump-if-greater  $exit/disp8
-    # result += counter
-    01/add                          3/mod/direct    3/rm32/EBX    .           .             .           1/r32/ECX   .               .                 # add ECX to EBX
-    # ++counter
-    41/increment-ECX
-    # loop
-    eb/jump  $loop/disp8
-
-$exit:
-    # syscall(exit, EBX)
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-== data 0x0a000000
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex4 b/subx/examples/ex4
deleted file mode 100755
index 66fb61e6..00000000
--- a/subx/examples/ex4
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex4.subx b/subx/examples/ex4.subx
deleted file mode 100644
index 31c1c10c..00000000
--- a/subx/examples/ex4.subx
+++ /dev/null
@@ -1,42 +0,0 @@
-# Read a character from stdin, save it to a global, write it to stdout.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex4.subx -o examples/ex4
-#   $ ./subx run examples/ex4
-
-== data 0x0a000000
-
-# the global variable we save to
-X:
-    0/imm32  # space for read() to write to
-
-== code 0x09000000
-
-Entry:
-# syscall(read, stdin, X, 1)
-# . fd = 0 (stdin)
-bb/copy-to-EBX  0/imm32
-# . data = X (location to write result to)
-b9/copy-to-ECX  X/imm32
-# . size = 1 character
-ba/copy-to-EDX  1/imm32
-# . syscall
-b8/copy-to-EAX  3/imm32/read
-cd/syscall  0x80/imm8
-
-# syscall(write, stdout, X, 1)
-# . fd = 1 (stdout)
-bb/copy-to-EBX  1/imm32
-# . initialize X (location to read from)
-b9/copy-to-ECX  X/imm32
-# . size = 1 character
-ba/copy-to-EDX  1/imm32
-# . syscall
-b8/copy-to-EAX  4/imm32/write
-cd/syscall  0x80/imm8
-
-# syscall(exit, EBX)
-b8/copy-to-EAX  1/imm32/exit
-cd/syscall  0x80/imm8
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex5 b/subx/examples/ex5
deleted file mode 100755
index 37689c98..00000000
--- a/subx/examples/ex5
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex5.subx b/subx/examples/ex5.subx
deleted file mode 100644
index 6f5b1d90..00000000
--- a/subx/examples/ex5.subx
+++ /dev/null
@@ -1,45 +0,0 @@
-# Read a character from stdin, save it to a local on the stack, write it to stdout.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex5.subx -o examples/ex5
-#   $ ./subx run examples/ex5
-
-== code 0x09000000
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-
-    # allocate x on the stack
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # subtract from ESP
-
-    # syscall(read, stdin, x, 1)
-    # . fd = 0 (stdin)
-    bb/copy-to-EBX  0/imm32
-    # . data = x (location to write result to)
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none              1/r32/ECX   4/disp8         .                 # copy ESP+4 to ECX
-    # . size = 1 character
-    ba/copy-to-EDX  1/imm32
-    # . syscall
-    b8/copy-to-EAX  3/imm32/read
-    cd/syscall  0x80/imm8
-
-    # syscall(write, stdout, x, 1)
-    # . fd = 1 (stdout)
-    bb/copy-to-EBX  1/imm32
-    # . data = x (location to read from)
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none              1/r32/ECX   4/disp8         .                 # copy ESP+4 to ECX
-    # . size = 1 character
-    ba/copy-to-EDX  1/imm32
-    # . syscall
-    b8/copy-to-EAX  4/imm32/write
-    cd/syscall  0x80/imm8
-
-    # syscall(exit, EBX)
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-== data 0x0a000000
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex6 b/subx/examples/ex6
deleted file mode 100755
index aa84591c..00000000
--- a/subx/examples/ex6
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex6.subx b/subx/examples/ex6.subx
deleted file mode 100644
index a90f11df..00000000
--- a/subx/examples/ex6.subx
+++ /dev/null
@@ -1,37 +0,0 @@
-# Print out a (global variable) string to stdout.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex6.subx -o examples/ex6
-#   $ ./subx run examples/ex6
-#   Hello, world!
-
-== code 0x09000000
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # syscall(write, stdout, X, Size)
-    # . fd = 1 (stdout)
-    bb/copy-to-EBX  1/imm32
-    # . initialize X (location to write result to)
-    b9/copy-to-ECX  X/imm32
-    # . initialize Size
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           2/r32/EDX   Size/disp32     .                 # copy *Size to EDX
-    # . syscall
-    b8/copy-to-EAX  4/imm32/write
-    cd/syscall  0x80/imm8
-
-    # syscall(exit, EBX)
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-== data 0x0a000000
-
-Size:  # size of string
-    0x0e/imm32  # 14
-X:  # string to print
-    48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 0a       00
-#   H  e  l  l  o  ,  ␣  w  o  r  l  d  !  newline  null
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex7 b/subx/examples/ex7
deleted file mode 100755
index 28c5bda9..00000000
--- a/subx/examples/ex7
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex7.subx b/subx/examples/ex7.subx
deleted file mode 100644
index d3b33f23..00000000
--- a/subx/examples/ex7.subx
+++ /dev/null
@@ -1,107 +0,0 @@
-# Example showing file syscalls.
-#
-# Create a file, open it for writing, write a character to it, close it, open
-# it for reading, read a character from it, close it, delete it, and return
-# the character read.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex7.subx -o examples/ex7
-#   $ ./subx run examples/ex7
-# Expected result:
-#   $ echo $?
-#   97
-
-== code 0x09000000
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # syscall(creat, Filename)
-    bb/copy-to-EBX  Filename/imm32
-    b9/copy-to-ECX  0x180/imm32/fixed-perms
-    b8/copy-to-EAX  8/imm32/creat
-    cd/syscall  0x80/imm8
-
-    # stream = syscall(open, Filename, O_WRONLY, 0)  # we can't use 'fd' because it looks like a hex byte
-    bb/copy-to-EBX  Filename/imm32
-    b9/copy-to-ECX  1/imm32/wronly
-    ba/copy-to-EDX  0x180/imm32/fixed-perms
-    b8/copy-to-EAX  5/imm32/open
-    cd/syscall  0x80/imm8
-    # save stream
-    bb/copy-to-EBX  Stream/imm32
-    89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EBX
-
-    # syscall(write, Stream, "a", 1)
-    # . load stream
-    bb/copy-to-EBX  Stream/imm32
-    8b/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # copy *EBX to EBX
-    # .
-    b9/copy-to-ECX  A/imm32
-    ba/copy-to-EDX  1/imm32/size
-    b8/copy-to-EAX  4/imm32/write
-    cd/syscall  0x80/imm8
-
-    # syscall(close, Stream)
-    # . load stream
-    bb/copy-to-EBX  Stream/imm32
-    8b/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # copy *EBX to EBX
-    # .
-    b8/copy-to-EAX  6/imm32/close
-    cd/syscall  0x80/imm8
-
-    # stream = syscall(open, Filename, O_RDONLY, 0)
-    bb/copy-to-EBX  Filename/imm32
-    b9/copy-to-ECX  0/imm32/rdonly
-    ba/copy-to-EDX  0x180/imm32/fixed-perms
-    b8/copy-to-EAX  5/imm32/open
-    cd/syscall  0x80/imm8
-    # . save Stream
-    bb/copy-to-EBX  Stream/imm32
-    89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EBX
-
-    # syscall(read, Stream, B, 1)
-    # . load stream
-    bb/copy-to-EBX  Stream/imm32
-    8b/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # copy *EBX to EBX
-    # .
-    b9/copy-to-ECX  B/imm32
-    ba/copy-to-EDX  1/imm32/size
-    b8/copy-to-EAX  3/imm32/read
-    cd/syscall  0x80/imm8
-
-    # syscall(close, Stream)
-    # . load stream
-    bb/copy-to-EBX  Stream/imm32
-    8b/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # copy *EBX to EBX
-    #
-    b8/copy-to-EAX  6/imm32/close
-    cd/syscall  0x80/imm8
-
-    # syscall(unlink, filename)
-    bb/copy-to-EBX  Filename/imm32
-    b8/copy-to-EAX  0xa/imm32/unlink
-    cd/syscall  0x80/imm8
-
-    # syscall(exit, b)
-    # . load b
-    bb/copy-to-EBX  B/imm32
-    8b/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # copy *EBX to EBX
-    #
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-== data 0x0a000000
-
-Stream:
-    0/imm32
-A:
-    61/imm32/A
-B:
-    0/imm32
-Filename:
-    2e 66 6f 6f 00 00 00 00
-#   .  f  o  o  null
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex8 b/subx/examples/ex8
deleted file mode 100755
index 5be0e1e6..00000000
--- a/subx/examples/ex8
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex8.subx b/subx/examples/ex8.subx
deleted file mode 100644
index 9d7255e1..00000000
--- a/subx/examples/ex8.subx
+++ /dev/null
@@ -1,61 +0,0 @@
-# Example reading commandline arguments: compute length of first arg.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex8.subx -o examples/ex8
-#   $ ./subx run examples/ex8 abc de fghi
-# Expected result:
-#   $ echo $?
-#   3  # length of 'abc'
-#
-# At the start of a SubX program:
-#   argc: *ESP
-#   argv[0]: *(ESP+4)
-#   argv[1]: *(ESP+8)
-#   ...
-# Locals start from ESP-4 downwards.
-
-== code 0x09000000
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # EAX = ascii-length(argv[1])
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  ascii-length/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-
-    # exit(EAX)
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-ascii-length:  # s : (address array byte) -> n/EAX
-    # EDX = s
-    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none  .           2/r32/EDX   4/disp8         .                 # copy *(ESP+4) to EDX
-    # var result/EAX = 0
-    b8/copy-to-EAX  0/imm32
-$ascii-length:loop:
-    # var c/ECX = *s
-    8a/copy-byte                    0/mod/*         2/rm32/EDX    .           .             .           1/r32/CL    .               .                 # copy byte at *EDX to CL
-    # if (c == '\0') break
-    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0/imm32           # compare ECX
-    74/jump-if-equal  $ascii-length:end/disp8
-    # ++s
-    42/increment-EDX
-    # ++result
-    40/increment-EAX
-    # loop
-    eb/jump  $ascii-length:loop/disp8
-$ascii-length:end:
-    # return EAX
-    c3/return
-
-== data 0x0a000000
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/examples/ex9 b/subx/examples/ex9
deleted file mode 100755
index fce7629c..00000000
--- a/subx/examples/ex9
+++ /dev/null
Binary files differdiff --git a/subx/examples/ex9.subx b/subx/examples/ex9.subx
deleted file mode 100644
index e3318b05..00000000
--- a/subx/examples/ex9.subx
+++ /dev/null
@@ -1,55 +0,0 @@
-# Example showing arg order on the stack.
-#
-# Show difference between ascii codes of first letter of first arg and first
-# letter of second arg.
-#
-# To run (from the subx directory):
-#   $ ./subx translate examples/ex9.subx -o examples/ex9
-#   $ ./subx run examples/ex9 z x
-# Expected result:
-#   $ echo $?
-#   2
-#
-# At the start of a SubX program:
-#   argc: *ESP
-#   argv[0]: *(ESP+4)
-#   argv[1]: *(ESP+8)
-#   ...
-# Locals start from ESP-4 downwards.
-
-== code 0x09000000
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # . prolog
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # ascii-difference(argv[1], argv[2])
-    # . . push argv[2]
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . push argv[1]
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-    # . . call
-    e8/call  ascii-difference/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # syscall(exit, EAX)
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-ascii-difference:  # (s1, s2) : null-terminated ascii strings
-    # a = first letter of s1 (ECX)
-    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none              0/r32/EAX   4/disp8         .                 # copy *(ESP+4) to EAX
-    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # copy *EAX to EAX
-    # b = first letter of s2 (EDX)
-    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none              1/r32/ECX   8/disp8                           # copy *(ESP+8) to ECX
-    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # copy *ECX to ECX
-    # a-b
-    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
-    c3/return
-
-== data 0x0a000000
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/exuberant_ctags_rc b/subx/exuberant_ctags_rc
deleted file mode 100644
index 785caaf0..00000000
--- a/subx/exuberant_ctags_rc
+++ /dev/null
@@ -1,3 +0,0 @@
---langdef=subx
---langmap=subx:.subx
---regex-subx=/^([^$ #][^ #]*):/\1/d,definition/
diff --git a/subx/modrm.pdf b/subx/modrm.pdf
deleted file mode 100644
index 552aec53..00000000
--- a/subx/modrm.pdf
+++ /dev/null
Binary files differdiff --git a/subx/ntranslate b/subx/ntranslate
deleted file mode 100755
index 24d87056..00000000
--- a/subx/ntranslate
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-# Translate SubX using the self-hosted translator.
-#
-# Possible knobs:
-#   Whether to run a phase directly or emulated.
-#     This script is for running natively.
-#   Whether to stop after a phase.
-#     Just always run all phases, but print out phases so it's clear where an error happens.
-#   Whether to trace a phase. Whether to always trace or rerun with tracing enabled after an error.
-#     Leave tracing to other scripts. We save intermediate files so it's easy to rerun a single phase afterwards.
-#   Whether to run a phase with debug information. (Need to juggle multiple sets of debug files.)
-#     Again, that's for subsequent scripts.
-
-set -e
-set -v
-
-./build
-
-echo `cat $* |grep -v '^\s*#\|^\s*$' |wc -l` lines
-
-cat $*          |apps/tests    > a.tests
-
-cat a.tests     |apps/dquotes  > a.dquotes
-
-cat a.dquotes   |apps/assort   > a.assort
-
-cat a.assort    |apps/pack     > a.pack
-
-cat a.pack      |apps/survey   > a.survey
-
-cat a.survey    |apps/hex      > a.elf
-
-xxd a.elf                      > a.xxd
diff --git a/subx/opcodes b/subx/opcodes
deleted file mode 100644
index bebf3052..00000000
--- a/subx/opcodes
+++ /dev/null
@@ -1,106 +0,0 @@
-Opcodes currently supported by SubX:
-  01: add r32 to rm32 (add)
-  03: add rm32 to r32 (add)
-  05: add imm32 to EAX (add)
-  09: rm32 = bitwise OR of r32 with rm32 (or)
-  0b: r32 = bitwise OR of r32 with rm32 (or)
-  0d: EAX = bitwise OR of imm32 with EAX (or)
-  21: rm32 = bitwise AND of r32 with rm32 (and)
-  23: r32 = bitwise AND of r32 with rm32 (and)
-  25: EAX = bitwise AND of imm32 with EAX (and)
-  29: subtract r32 from rm32 (sub)
-  2b: subtract rm32 from r32 (sub)
-  2d: subtract imm32 from EAX (sub)
-  31: rm32 = bitwise XOR of r32 with rm32 (xor)
-  33: r32 = bitwise XOR of r32 with rm32 (xor)
-  35: EAX = bitwise XOR of imm32 with EAX (xor)
-  39: compare: set SF if rm32 < r32 (cmp)
-  3b: compare: set SF if r32 < rm32 (cmp)
-  3d: compare: set SF if EAX < imm32 (cmp)
-  40: increment EAX (inc)
-  41: increment ECX (inc)
-  42: increment EDX (inc)
-  43: increment EBX (inc)
-  44: increment ESP (inc)
-  45: increment EBP (inc)
-  46: increment ESI (inc)
-  47: increment EDI (inc)
-  48: decrement EAX (dec)
-  49: decrement ECX (dec)
-  4a: decrement EDX (dec)
-  4b: decrement EBX (dec)
-  4c: decrement ESP (dec)
-  4d: decrement EBP (dec)
-  4e: decrement ESI (dec)
-  4f: decrement EDI (dec)
-  50: push EAX to stack (push)
-  51: push ECX to stack (push)
-  52: push EDX to stack (push)
-  53: push EBX to stack (push)
-  54: push ESP to stack (push)
-  55: push EBP to stack (push)
-  56: push ESI to stack (push)
-  57: push EDI to stack (push)
-  58: pop top of stack to EAX (pop)
-  59: pop top of stack to ECX (pop)
-  5a: pop top of stack to EDX (pop)
-  5b: pop top of stack to EBX (pop)
-  5c: pop top of stack to ESP (pop)
-  5d: pop top of stack to EBP (pop)
-  5e: pop top of stack to ESI (pop)
-  5f: pop top of stack to EDI (pop)
-  68: push imm32 to stack (push)
-  72: jump disp8 bytes away if lesser (unsigned), if CF is set (jcc/jb/jnae)
-  73: jump disp8 bytes away if greater or equal (unsigned), if CF is unset (jcc/jae/jnb)
-  74: jump disp8 bytes away if equal, if ZF is set (jcc/jz/je)
-  75: jump disp8 bytes away if not equal, if ZF is not set (jcc/jnz/jne)
-  76: jump disp8 bytes away if lesser or equal (unsigned), if ZF is set or CF is set (jcc/jbe/jna)
-  77: jump disp8 bytes away if greater (unsigned), if ZF is unset and CF is unset (jcc/ja/jnbe)
-  7c: jump disp8 bytes away if lesser (signed), if SF != OF (jcc/jl/jnge)
-  7d: jump disp8 bytes away if greater or equal (signed), if SF == OF (jcc/jge/jnl)
-  7e: jump disp8 bytes away if lesser or equal (signed), if ZF is set or SF != OF (jcc/jle/jng)
-  7f: jump disp8 bytes away if greater (signed), if ZF is unset and SF == OF (jcc/jg/jnle)
-  81: combine rm32 with imm32 based on subop (add/sub/and/or/xor/cmp)
-  87: swap the contents of r32 and rm32 (xchg)
-  88: copy r8 to r8/m8-at-r32
-  89: copy r32 to rm32 (mov)
-  8a: copy r8/m8-at-r32 to r8
-  8b: copy rm32 to r32 (mov)
-  8d: copy address in rm32 into r32 (lea)
-  8f: pop top of stack to rm32 (pop)
-  99: sign-extend EAX into EDX (cdq)
-  b8: copy imm32 to EAX (mov)
-  b9: copy imm32 to ECX (mov)
-  ba: copy imm32 to EDX (mov)
-  bb: copy imm32 to EBX (mov)
-  bc: copy imm32 to ESP (mov)
-  bd: copy imm32 to EBP (mov)
-  be: copy imm32 to ESI (mov)
-  bf: copy imm32 to EDI (mov)
-  c1: shift rm32 by imm8 bits depending on subop (sal/sar/shl/shr)
-  c3: return from most recent unfinished call (ret)
-  c6: copy imm8 to r8/m8-at-r32 (mov)
-  c7: copy imm32 to rm32 (mov)
-  cd: software interrupt (int)
-  d3: shift rm32 by CL bits depending on subop (sal/sar/shl/shr)
-  e8: call disp32 (call)
-  e9: jump disp32 bytes away (jmp)
-  eb: jump disp8 bytes away (jmp)
-  f4: halt (hlt)
-  f7: negate/multiply/divide rm32 (with EAX and EDX if necessary) depending on subop (neg/mul/idiv)
-  ff: increment/decrement/jump/push/call rm32 based on subop (inc/dec/jmp/push/call)
-  0f 82: jump disp32 bytes away if lesser (unsigned), if CF is set (jcc/jb/jnae)
-  0f 83: jump disp32 bytes away if greater or equal (unsigned), if CF is unset (jcc/jae/jnb)
-  0f 84: jump disp32 bytes away if equal, if ZF is set (jcc/jz/je)
-  0f 85: jump disp32 bytes away if not equal, if ZF is not set (jcc/jnz/jne)
-  0f 86: jump disp8 bytes away if lesser or equal (unsigned), if ZF is set or CF is set (jcc/jbe/jna)
-  0f 87: jump disp32 bytes away if greater (unsigned), if ZF is unset and CF is unset (jcc/ja/jnbe)
-  0f 8c: jump disp32 bytes away if lesser (signed), if SF != OF (jcc/jl/jnge)
-  0f 8d: jump disp32 bytes away if greater or equal (signed), if SF == OF (jcc/jge/jnl)
-  0f 8e: jump disp32 bytes away if lesser or equal (signed), if ZF is set or SF != OF (jcc/jle/jng)
-  0f 8f: jump disp32 bytes away if greater (signed), if ZF is unset and SF == OF (jcc/jg/jnle)
-  0f af: multiply rm32 into r32 (imul)
-Run `subx help instructions` for details on words like 'r32' and 'disp8'.
-For complete details on these instructions, consult the IA-32 manual (volume 2).
-There's various versions of it online, such as https://c9x.me/x86.
-The mnemonics in brackets will help you locate each instruction.
diff --git a/subx/run_one_test.sh b/subx/run_one_test.sh
deleted file mode 100755
index caecd63c..00000000
--- a/subx/run_one_test.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env zsh
-# Either run the test with the given name, or rerun the most recently run test.
-# Intended to be called from within Vim. Check out the vimrc.vim file.
-
-if [[ $2 == 'test-'* ]]
-then
-  TEST_NAME=$2 envsubst '$TEST_NAME' < run_one_test.subx > /tmp/run_one_test.subx
-  FILES=$(ls [0-9]*.subx apps/subx-common.subx $1 |sort |uniq)
-  echo $FILES > /tmp/last_run_files
-elif [[ -e /tmp/last_run_files ]]
-then
-  FILES=`cat /tmp/last_run_files`
-else
-  echo "no test found"
-  exit 0  # don't open trace
-fi
-
-set -e
-                                      # turn newlines into spaces
-CFLAGS=$CFLAGS ./subx --debug translate $(echo $FILES) /tmp/run_one_test.subx -o /tmp/a.elf
-
-./subx --debug --trace run /tmp/a.elf
diff --git a/subx/run_one_test.subx b/subx/run_one_test.subx
deleted file mode 100644
index 43c98fca..00000000
--- a/subx/run_one_test.subx
+++ /dev/null
@@ -1,30 +0,0 @@
-# run a single test
-
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . op          subop               mod             rm32          base        index         scale       r32
-# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
-
-Entry:
-    # Heap = new-segment(64KB)
-    # . . push args
-    68/push  Heap/imm32
-    68/push  0x10000/imm32/64KB
-    # . . call
-    e8/call  new-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # initialize-trace-stream(256KB)
-    # . . push args
-    68/push  0x40000/imm32/256KB
-    # . . call
-    e8/call  initialize-trace-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # for debugging: run a single test
-    e8/call $TEST_NAME/disp32
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-
-# . . vim:nowrap:textwidth=0
diff --git a/subx/sib.pdf b/subx/sib.pdf
deleted file mode 100644
index 735a40ca..00000000
--- a/subx/sib.pdf
+++ /dev/null
Binary files differdiff --git a/subx/stats.md b/subx/stats.md
deleted file mode 100644
index fcf0191f..00000000
--- a/subx/stats.md
+++ /dev/null
@@ -1,36 +0,0 @@
-                          Initial   -tests/whitespace/comments
-## Lines in source
-standard library           9597     2316
-apps/crenshaw2-1b.subx      798      176
-apps/crenshaw2-1.subx       601      180
-apps/factorial.subx         107       28
-apps/handle.subx            361       58
-apps/hex.subx              1511      144
-apps/pack.subx             7348     1054
-apps/assort.subx           1318      284
-apps/dquotes.subx          2694      497
-apps/survey.subx           4573      998
-
-## Bytes in executable
-apps/crenshaw2-1          17612     4112
-apps/crenshaw2-1b         18171     4140
-apps/factorial            16530     3488
-apps/handle               17323     3582
-apps/hex                  22684     4909
-apps/pack                 37316     7825
-apps/assort               22506     5342
-apps/dquotes              27186     5849
-apps/survey               42791    11258
-
-# lines per test (including whitespace/comments)
-                          num tests     num lines     lines/test
-apps/crenshaw2-1.subx        2            585          292.5
-apps/crenshaw2-1b.subx       4            785          196.25
-apps/factorial.subx          1            117          117
-apps/handle.subx             4            412          103
-apps/hex.subx               12           1515          126.25
-apps/pack.subx              42           5977          142.3
-apps/assort.subx             1            839          839
-apps/dquotes.subx           26           2680          103
-apps/survey.subx            18           4570          254
-apps/subx-common.subx       32           3098           96.8125
diff --git a/subx/subx.vim b/subx/subx.vim
deleted file mode 100644
index 13990b31..00000000
--- a/subx/subx.vim
+++ /dev/null
@@ -1,68 +0,0 @@
-" SubX syntax file
-" Language:    SubX
-" Maintainer:  Kartik Agaram <mu@akkartik.com>
-" URL:         https://github.com/akkartik/mu
-" License:     public domain
-"
-" Copy this into your ftplugin directory, and add the following to your vimrc
-" or to .vim/ftdetect/subx.vim:
-"   autocmd BufReadPost,BufNewFile *.subx set filetype=subx
-
-let s:save_cpo = &cpo
-set cpo&vim
-
-" setlocal iskeyword=@,48-57,?,!,_,$,-
-setlocal formatoptions-=t  " allow long lines
-setlocal formatoptions+=c  " but comments should still wrap
-
-setlocal iskeyword+=-,?
-
-" blue tones
-" comment colors for dark terminal: 14, 39, 27, 19
-" comment colors for light terminal: 19, 27, 39, 6
-"? syntax match subxH1Comment /# - .*/ | highlight subxH1Comment cterm=underline ctermfg=27
-"? syntax match subxComment /#[^ ].*\|# [^.-].*\|# \?$/ | highlight subxComment ctermfg=27
-"? syntax match subxS1Comment /# \..*/ | highlight subxS1Comment ctermfg=19
-"? syntax match subxS2Comment /# \. \..*/ | highlight subxS2Comment ctermfg=245
-
-" blue-green tones
-syntax match subxH1Comment /# - .*/ | highlight subxH1Comment cterm=underline ctermfg=25
-syntax match subxComment /#\( \.\| - \|? \)\@!.*/ | highlight subxComment ctermfg=25
-syntax match subxS1Comment /# \..*/ | highlight subxS1Comment ctermfg=19
-syntax match subxS2Comment /# \. \..*/ | highlight subxS2Comment ctermfg=245
-
-" grey tones
-"? syntax match subxH1Comment /# - .*/ | highlight subxH1Comment cterm=bold,underline
-"? syntax match subxComment /#[^ ].*\|# [^.-].*\|# \?$/ | highlight subxComment cterm=bold ctermfg=236
-"? hi Normal ctermfg=236
-"? syntax match subxS1Comment /# \..*/ | highlight subxS1Comment cterm=bold ctermfg=242
-"? syntax match subxS2Comment /# \. \..*/ | highlight subxS2Comment ctermfg=242
-
-set comments-=:#
-set comments+=n:#
-syntax match subxCommentedCode "#? .*"  | highlight link subxCommentedCode CommentedCode
-let b:cmt_head = "#? "
-
-" comment token
-syntax match subxDelimiter / \. /  | highlight link subxDelimiter Normal
-
-syntax match subxString %"[^"]*"% | highlight link subxString Constant
-
-"" definitions
-" match globals but not registers like 'EAX'
-" don't match capitalized words in metadata
-" don't match inside strings
-syntax match subxGlobal %\(/\)\@<!\<[A-Z][a-z0-9_-]*\>% | highlight link subxGlobal SpecialChar
-" tweak the red color from the colorscheme just a tad to improve contrast
-highlight SpecialChar ctermfg=160
-
-" functions but not tests, globals or internal functions
-syntax match subxFunction "^\(test_\)\@<![a-z][^ ]*\(:\)\@=" | highlight subxFunction cterm=underline ctermfg=130
-" tests starting with 'test-'; dark:34 light:64
-syntax match subxTest "^test-[^ ]*\(:\)\@=" | highlight subxTest ctermfg=64
-" internal functions starting with '_'
-syntax match subxMinorFunction "^_[^ ]*\(:\)\@=" | highlight subxMinorFunction ctermfg=95
-" other internal labels starting with '$'
-syntax match subxLabel "^\$[^ ]*\(:\)\@=" | highlight link subxLabel Constant
-
-let &cpo = s:save_cpo
diff --git a/subx/test_apps b/subx/test_apps
deleted file mode 100755
index b8c142be..00000000
--- a/subx/test_apps
+++ /dev/null
@@ -1,336 +0,0 @@
-#!/bin/sh
-# Build and test (in emulated mode) all included SubX programs.
-# Also compare generated binaries.
-# If running on Linux, also test natively.
-
-set -e
-cd `dirname $0`
-
-test `uname` = 'Linux'  &&  echo 'testing native runs as well'
-
-CFLAGS=$CFLAGS ./build
-
-echo "== translating and running using C++"
-
-echo ex1
-./subx translate examples/ex1.subx  -o examples/ex1
-[ "$1" != record ]  &&  git diff --exit-code examples/ex1
-./subx run examples/ex1  ||  ret=$?
-test $ret -eq 42  # life, the universe and everything
-test `uname` = 'Linux'  &&  {
-  examples/ex1  ||  ret=$?
-  test $ret -eq 42  # life, the universe and everything
-}
-
-echo ex2
-./subx translate examples/ex2.subx  -o examples/ex2
-[ "$1" != record ]  &&  git diff --exit-code examples/ex2
-./subx run examples/ex2  ||  ret=$?
-test $ret -eq 2  # 1 + 1
-test `uname` = 'Linux'  &&  {
-  examples/ex2  ||  ret=$?
-  test $ret -eq 2  # 1 + 1
-}
-
-echo ex3
-./subx translate examples/ex3.subx  -o examples/ex3
-[ "$1" != record ]  &&  git diff --exit-code examples/ex3
-./subx run examples/ex3  ||  ret=$?
-test $ret -eq 55  # 1 + 2 + ... + 10
-test `uname` = 'Linux'  &&  {
-  examples/ex3  ||  ret=$?
-  test $ret -eq 55  # 1 + 2 + ... + 10
-}
-
-echo ex4
-./subx translate examples/ex4.subx  -o examples/ex4
-[ "$1" != record ]  &&  git diff --exit-code examples/ex4
-echo a | ./subx run examples/ex4 >ex4.out  ||  true
-test `cat ex4.out` = 'a'
-test `uname` = 'Linux'  &&  {
-  echo a | examples/ex4 >ex4.out  ||  true
-  test `cat ex4.out` = 'a'
-}
-
-echo ex5
-./subx translate examples/ex5.subx  -o examples/ex5
-[ "$1" != record ]  &&  git diff --exit-code examples/ex5
-echo a | ./subx run examples/ex5 >ex5.out  ||  true
-test `cat ex5.out` = 'a'
-test `uname` = 'Linux'  &&  {
-  echo a | examples/ex5 >ex5.out  ||  true
-  test `cat ex5.out` = 'a'
-}
-
-echo ex6
-./subx translate examples/ex6.subx  -o examples/ex6
-[ "$1" != record ]  &&  git diff --exit-code examples/ex6
-./subx run examples/ex6 >ex6.out  ||  true
-test "`cat ex6.out`" = 'Hello, world!'
-test `uname` = 'Linux'  &&  {
-  examples/ex6 >ex6.out  ||  true
-  test "`cat ex6.out`" = 'Hello, world!'
-}
-
-echo ex7
-./subx translate examples/ex7.subx  -o examples/ex7
-[ "$1" != record ]  &&  git diff --exit-code examples/ex7
-./subx run examples/ex7  ||  ret=$?
-test $ret -eq 97  # 'a'
-test `uname` = 'Linux'  &&  {
-  examples/ex7  ||  ret=$?
-  test $ret -eq 97  # 'a'
-}
-
-echo ex8
-./subx translate examples/ex8.subx  -o examples/ex8
-[ "$1" != record ]  && git diff --exit-code examples/ex8
-./subx run examples/ex8 abcd  ||  ret=$?
-test $ret -eq 4  # length('abcd')
-test `uname` = 'Linux'  &&  {
-  examples/ex8 abcd  ||  ret=$?
-  test $ret -eq 4  # length('abcd')
-}
-
-echo ex9
-./subx translate examples/ex9.subx  -o examples/ex9
-[ "$1" != record ]  && git diff --exit-code examples/ex9
-./subx run examples/ex9 z x  ||  ret=$?
-test $ret -eq 2  # 'z' - 'x'
-test `uname` = 'Linux'  &&  {
-  examples/ex9 z x  ||  ret=$?
-  test $ret -eq 2  # 'z' - 'x'
-}
-
-echo ex10
-./subx translate examples/ex10.subx  -o examples/ex10
-[ "$1" != record ]  && git diff --exit-code examples/ex10
-./subx run examples/ex10 abc abc  ||  ret=$?
-test $ret -eq 1  # equal
-./subx run examples/ex10 abc abcd  # 0; not equal
-test `uname` = 'Linux'  &&  {
-  examples/ex10 abc abc  ||  ret=$?
-  test $ret -eq 1  # equal
-  examples/ex10 abc abcd  # 0; not equal
-}
-
-echo ex11
-./subx translate examples/ex11.subx  -o examples/ex11
-[ "$1" != record ]  && git diff --exit-code examples/ex11
-./subx run examples/ex11
-echo
-test `uname` = 'Linux'  &&  {
-  examples/ex11
-  echo
-}
-
-echo ex12
-./subx translate examples/ex12.subx  -o examples/ex12
-[ "$1" != record ]  && git diff --exit-code examples/ex12
-./subx run examples/ex12  # final byte of mmap'd address is well-nigh guaranteed to be 0
-test `uname` = 'Linux'  &&  examples/ex12
-
-echo factorial
-./subx translate 0*.subx apps/factorial.subx  -o apps/factorial
-[ "$1" != record ]  &&  git diff --exit-code apps/factorial
-./subx run apps/factorial  ||  ret=$?
-test $ret -eq 120  # factorial(5)
-./subx run apps/factorial test
-echo
-test `uname` = 'Linux'  &&  {
-  apps/factorial  ||  ret=$?
-  test $ret -eq 120  # factorial(5)
-  apps/factorial test
-  echo
-}
-
-echo crenshaw2-1
-./subx translate 0*.subx apps/crenshaw2-1.subx  -o apps/crenshaw2-1
-[ "$1" != record ]  &&  git diff --exit-code apps/crenshaw2-1
-./subx run apps/crenshaw2-1 test
-echo
-test `uname` = 'Linux'  &&  {
-  apps/crenshaw2-1 test
-  echo
-}
-
-echo crenshaw2-1b
-./subx translate 0*.subx apps/crenshaw2-1b.subx  -o apps/crenshaw2-1b
-[ "$1" != record ]  &&  git diff --exit-code apps/crenshaw2-1b
-./subx run apps/crenshaw2-1b test
-echo
-test `uname` = 'Linux'  &&  {
-  apps/crenshaw2-1b test
-  echo
-}
-
-echo handle
-./subx translate 0*.subx apps/handle.subx  -o apps/handle
-[ "$1" != record ]  &&  git diff --exit-code apps/handle
-./subx run apps/handle > handle.out 2>&1  ||  true
-grep -q 'lookup succeeded' handle.out  ||  { echo "missing success test"; exit 1; }
-grep -q 'lookup failed' handle.out  ||  { echo "missing failure test"; exit 1; }
-test `uname` = 'Linux'  &&  {
-  apps/handle > handle.out 2>&1  ||  true
-  grep -q 'lookup succeeded' handle.out  ||  { echo "missing success test"; exit 1; }
-  grep -q 'lookup failed' handle.out  ||  { echo "missing failure test"; exit 1; }
-}
-
-echo hex
-./subx translate 0*.subx apps/subx-common.subx apps/hex.subx  -o apps/hex
-[ "$1" != record ]  &&  git diff --exit-code apps/hex
-./subx run apps/hex test
-echo
-test `uname` = 'Linux'  &&  {
-  apps/hex test
-  echo
-}
-
-echo survey
-./subx translate 0*.subx apps/subx-common.subx apps/survey.subx  -o apps/survey
-[ "$1" != record ]  &&  git diff --exit-code apps/survey
-./subx run apps/survey test
-echo
-test `uname` = 'Linux'  &&  {
-  apps/survey test
-  echo
-}
-
-echo pack
-./subx translate 0*.subx apps/subx-common.subx apps/pack.subx  -o apps/pack
-[ "$1" != record ]  &&  git diff --exit-code apps/pack
-./subx run apps/pack test
-echo
-test `uname` = 'Linux'  &&  {
-  apps/pack test
-  echo
-}
-
-echo assort
-./subx translate 0*.subx apps/subx-common.subx apps/assort.subx  -o apps/assort
-[ "$1" != record ]  &&  git diff --exit-code apps/assort
-./subx run apps/assort test
-echo
-test `uname` = 'Linux'  &&  {
-  apps/assort test
-  echo
-}
-
-echo dquotes
-./subx translate 0*.subx apps/subx-common.subx apps/dquotes.subx  -o apps/dquotes
-[ "$1" != record ]  &&  git diff --exit-code apps/dquotes
-./subx run apps/dquotes test
-echo
-test `uname` = 'Linux'  &&  {
-  apps/dquotes test
-  echo
-}
-
-echo tests
-./subx translate 0*.subx apps/subx-common.subx apps/tests.subx  -o apps/tests
-[ "$1" != record ]  &&  git diff --exit-code apps/tests
-./subx run apps/tests test
-echo
-test `uname` = 'Linux'  &&  {
-  apps/tests test
-  echo
-}
-
-echo "== translating using SubX"
-
-echo ex1
-cat examples/ex1.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex1 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex1.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex1 -
-}
-
-echo ex2
-cat examples/ex2.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex2 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex2.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex2 -
-}
-
-echo ex3
-cat examples/ex3.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex3 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex3.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex3 -
-}
-
-echo ex4
-cat examples/ex4.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex4 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex4.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex4 -
-}
-
-echo ex5
-cat examples/ex5.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex5 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex5.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex5 -
-}
-
-echo ex6
-cat examples/ex6.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex6 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex6.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex6 -
-}
-
-echo ex7
-cat examples/ex7.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex7 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex7.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex7 -
-}
-
-echo ex8
-cat examples/ex8.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex8 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex8.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex8 -
-}
-
-echo ex9
-cat examples/ex9.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex9 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex9.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex9 -
-}
-
-echo ex10
-cat examples/ex10.subx |./subx_bin run apps/tests |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex10 -
-test `uname` = 'Linux'  &&  {
-  cat examples/ex10.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex10 -
-}
-
-# Only native runs beyond this point. Emulated runs time out on travis-ci.org.
-test `uname` = 'Linux'  ||  exit 0
-
-echo ex11
-cat examples/ex11.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex11 -
-
-echo ex12
-cat examples/ex12.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex12 -
-
-# Larger apps that use the standard library.
-
-echo factorial
-cat 0*.subx apps/factorial.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/factorial -
-echo crenshaw2-1
-cat 0*.subx apps/crenshaw2-1.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/crenshaw2-1 -
-echo crenshaw2-1b
-cat 0*.subx apps/crenshaw2-1b.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/crenshaw2-1b -
-echo handle
-cat 0*.subx apps/handle.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/handle -
-
-# Phases of the self-hosted SubX translator.
-
-echo hex
-cat 0*.subx apps/subx-common.subx apps/hex.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/hex -
-echo survey
-cat 0*.subx apps/subx-common.subx apps/survey.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/survey -
-echo pack
-cat 0*.subx apps/subx-common.subx apps/pack.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/pack -
-echo assort
-cat 0*.subx apps/subx-common.subx apps/assort.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/assort -
-echo dquotes
-cat 0*.subx apps/subx-common.subx apps/dquotes.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/dquotes -
-echo tests
-cat 0*.subx apps/subx-common.subx apps/tests.subx |apps/tests |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff apps/tests -
-
-exit 0
diff --git a/subx/test_layers b/subx/test_layers
deleted file mode 100755
index f1e22faf..00000000
--- a/subx/test_layers
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-# Repeatedly stop building until successive layers, and run all tests built.
-#
-# Assumes .subx files all come after .cc files.
-
-set -e
-
-cd `dirname $0`
-# add C++ files one at a time
-for f in [0-9]*cc
-do
-  echo "=== $f"
-  ./build_and_test_until $f
-done
-
-# build everything one last time
-./clean
-CFLAGS=$CFLAGS ./build  # build optimized by default since we'll be running it repeatedly below
-
-# add SubX files one at a time
-for f in [0-9]*.subx
-do
-  [ $f == *'memory_layout.subx' ]  &&  continue  # the very first .subx layer has no code
-  echo "=== $f"
-  ./subx translate $(../enumerate/enumerate --until $f |grep '\.subx$') -o a.elf
-  ./subx run a.elf
-  echo
-  test `uname` = 'Linux'  &&  {
-    chmod +x a.elf
-    ./a.elf
-    echo
-  } || true
-done
diff --git a/subx/translate b/subx/translate
deleted file mode 100755
index 7acf92c4..00000000
--- a/subx/translate
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-# Translate SubX using the self-hosted translator.
-#
-# Possible knobs:
-#   Whether to run a phase directly or emulated.
-#     Just always emulate for now since we debug on non-Linux.
-#   Whether to stop after a phase.
-#     Just always run all phases, but print out phases so it's clear where an error happens.
-#   Whether to trace a phase. Whether to always trace or rerun with tracing enabled after an error.
-#     Leave tracing to other scripts. We save intermediate files so it's easy to rerun a single phase afterwards.
-#   Whether to run a phase with debug information. (Need to juggle multiple sets of debug files.)
-#     Again, that's for subsequent scripts.
-
-set -e
-set -v
-
-./build
-
-echo `cat $* |grep -v '^\s*#\|^\s*$' |wc -l` lines
-
-cat $*          |./subx_bin run apps/tests    > a.tests
-
-cat a.tests     |./subx_bin run apps/dquotes  > a.dquotes
-
-cat a.dquotes   |./subx_bin run apps/assort   > a.assort
-
-cat a.assort    |./subx_bin run apps/pack     > a.pack
-
-cat a.pack      |./subx_bin run apps/survey   > a.survey
-
-cat a.survey    |./subx_bin run apps/hex      > a.elf
-
-xxd a.elf                                     > a.xxd
diff --git a/subx/vimrc.vim b/subx/vimrc.vim
deleted file mode 100644
index 80ec5a5f..00000000
--- a/subx/vimrc.vim
+++ /dev/null
@@ -1,106 +0,0 @@
-" Highlighting literate directives in C++ sources.
-function! HighlightTangledFile()
-  " Tangled comments only make sense in the sources and are stripped out of
-  " the generated .cc file. They're highlighted same as regular comments.
-  syntax match tangledComment /\/\/:.*/ | highlight link tangledComment Comment
-  syntax match tangledSalientComment /\/\/::.*/ | highlight link tangledSalientComment SalientComment
-  set comments-=://
-  set comments-=n://
-  set comments+=n://:,n://
-
-  " Inside tangle scenarios.
-  syntax region tangleDirective start=+:(+ skip=+".*"+ end=+)+
-  highlight link tangleDirective Delimiter
-  syntax match traceContains /^+.*/
-  highlight traceContains ctermfg=22
-  syntax match traceAbsent /^-.*/
-  highlight traceAbsent ctermfg=darkred
-  syntax match tangleScenarioSetup /^\s*% .*/ | highlight link tangleScenarioSetup SpecialChar
-  highlight Special ctermfg=160
-
-  syntax match subxString %"[^"]*"% | highlight link subxString Constant
-  " match globals but not registers like 'EAX'
-  syntax match subxGlobal %\<[A-Z][a-z0-9_-]*\>% | highlight link subxGlobal SpecialChar
-endfunction
-augroup LocalVimrc
-  autocmd BufRead,BufNewFile *.cc call HighlightTangledFile()
-  autocmd BufRead,BufNewFile *.subx set ft=subx
-augroup END
-
-" Scenarios considered:
-"   opening or starting vim with a new or existing file without an extension (should interpret as C++)
-"   opening or starting vim with a new or existing file with a .mu extension
-"   starting vim or opening a buffer without a file name (ok to do nothing)
-"   opening a second file in a new or existing window (shouldn't mess up existing highlighting)
-"   reloading an existing file (shouldn't mess up existing highlighting)
-
-" assumes CWD is subx/
-command! -nargs=1 E call EditSubx("edit", <f-args>)
-if exists("&splitvertical")
-  command! -nargs=1 S call EditSubx("vert split", <f-args>)
-  command! -nargs=1 H call EditSubx("hor split", <f-args>)
-else
-  command! -nargs=1 S call EditSubx("vert split", <f-args>)
-  command! -nargs=1 H call EditSubx("split", <f-args>)
-endif
-
-function! EditSubx(cmd, arg)
-  exec "silent! " . a:cmd . " " . SubxPath(a:arg)
-endfunction
-
-function! SubxPath(arg)
-  if a:arg =~ "^ex"
-    return "examples/" . a:arg . ".subx"
-  else
-    return "apps/" . a:arg . ".subx"
-  endif
-endfunction
-
-" we often want to crib lines of machine code from other files
-function! GrepSubX(regex)
-  " https://github.com/mtth/scratch.vim
-  Scratch!
-  silent exec "r !grep -h '".a:regex."' *.subx */*.subx"
-endfunction
-command! -nargs=1 G call GrepSubX(<q-args>)
-
-if exists("&splitvertical")
-  command! -nargs=0 P hor split opcodes
-else
-  command! -nargs=0 P split opcodes
-endif
-
-" useful for inspecting just the control flow in a trace
-" see https://github.com/akkartik/mu/blob/master/subx/Readme.md#a-few-hints-for-debugging
-" the '-a' is because traces can sometimes contain unprintable characters that bother grep
-command! -nargs=0 L exec "%!grep -a label |grep -v clear-stream:loop"
-
-" run test cursor around cursor
-"   if test fails, open trace in split window
-"   if test passes, just show output and wait for <CR>
-"   don't move cursor in original window
-" this solution is unfortunate, but seems forced:
-"   can't put initial cursor movement inside function because we rely on <C-r><C-w> to grab word at cursor
-"   can't put final cursor movement out of function because that disables the wait for <CR> prompt; function must be final operation of map
-"   can't avoid the function because that disables the wait for <CR> prompt
-" known issue:
-"   cursor on '#' causes error
-noremap <Leader>t {j0:call RunTestMoveCursor("<C-r><C-w>")<CR>
-function RunTestMoveCursor(arg)
-  exec "!run_one_test.sh ".expand("%")." ".a:arg
-  exec "normal \<C-o>"
-endfunction
-function RunTestMoveCursorAndMaybeOpenTrace(arg)
-  exec "!run_one_test.sh ".expand("%")." ".a:arg
-  exec "normal \<C-o>"
-  if v:shell_error
-    noautocmd vertical split last_run
-  endif
-endfunction
-
-set switchbuf=useopen
-if exists("&splitvertical")
-  command! -nargs=0 T badd last_run | sbuffer last_run
-else
-  command! -nargs=0 T badd last_run | vert sbuffer last_run
-endif