https://github.com/akkartik/mu/blob/master/apps/mu.subx
   1 # The Mu computer's level-2 language, also called Mu.
   2 # http://akkartik.name/post/mu-2019-2
   3 #
   4 # To run:
   5 #   $ ./ntranslate init.linux 0*.subx apps/mu.subx
   6 
   7 # == Goals
   8 # 1. Be memory safe. It should be impossible to corrupt the heap, or to create
   9 # a bad pointer. (Requires strong type safety.)
  10 # 2. Do as little as possible to achieve goal 1.
  11 #   - runtime checks to avoid complex static analysis
  12 #   - minimize impedance mismatch between source language and SubX target
  13 
  14 # == Language description
  15 # A program is a sequence of function definitions.
  16 #
  17 # Function example:
  18 #   fn foo n: int -> result/eax: int {
  19 #     ...
  20 #   }
  21 #
  22 # Functions consist of a name, optional inputs, optional outputs and a block.
  23 #
  24 # Function inputs and outputs are variables. All variables have a type and
  25 # storage specifier. They can be placed either in memory (on the stack) or in
  26 # one of 6 named registers.
  27 #   eax ecx edx ebx esi edi
  28 # Variables in registers must be primitive 32-bit types.
  29 # Variables not explicitly placed in a register are on the stack.
  30 # Variables in registers need not have a name; in that case you refer to them
  31 # directly by the register name.
  32 #
  33 # Function inputs are always passed in memory (on the stack), while outputs
  34 # are always returned in registers.
  35 #
  36 # Blocks mostly consist of statements.
  37 #
  38 # Statements mostly consist of a name, optional inputs and optional outputs.
  39 #
  40 # Statement inputs are variables or literals. Variables need to specify type
  41 # (and storage) the first time they're mentioned but not later.
  42 #
  43 # Statement outputs, like function outputs, must be variables in registers.
  44 #
  45 # Statement names must be either primitives or user-defined functions.
  46 #
  47 # Primitives can write to any register.
  48 # User-defined functions only write to hard-coded registers. Outputs of each
  49 # call must have the same registers as in the function definition.
  50 #
  51 # There are some other statement types:
  52 #   - blocks. Multiple statements surrounded by '{...}' and optionally
  53 #     prefixed with a label name and ':'
  54 #       - {
  55 #           ...
  56 #         }
  57 #       - foo: {
  58 #           ...
  59 #         }
  60 #
  61 #   - variable definitions on the stack. E.g.:
  62 #       - var foo: int
  63 #       - var bar: (array int 3)
  64 #     There's no initializer; variables are automatically initialized.
  65 #
  66 #   - variables definitions in a register. E.g.:
  67 #       - var foo/eax : int <- add bar 1
  68 #     The initializer is mandatory and must be a valid instruction that writes
  69 #     a single output to the right register. In practice registers will
  70 #     usually be either initialized by primitives or copied from eax.
  71 #       - var eax : int <- foo bar quux
  72 #         var floo/ecx : int <- copy eax
  73 #
  74 # Still todo:
  75 #   global variables
  76 #   heap allocations (planned name: 'handle')
  77 #   user-defined types: 'type' for structs, 'choice' for unions
  78 #   short-lived 'address' type for efficiently writing inside nested structs
  79 #
  80 # Formal types:
  81 #   A program is a linked list of functions
  82 #   A function contains:
  83 #     name: string
  84 #     inouts: linked list of vars  <-- 'inouts' is more precise than 'inputs'
  85 #       data: (address var)
  86 #       next: (address list)
  87 #     outputs: linked list of vars
  88 #       data: (address var)
  89 #       next: (address list)
  90 #     body: block
  91 #   A var-type contains:
  92 #     name: string
  93 #     type: s-expression of type ids
  94 #   Statements are not yet fully designed.
  95 #   statement = var definition or simple statement or block
  96 #   simple statement:
  97 #     operation: string
  98 #     inouts: linked list of vars
  99 #     outputs: linked list of vars
 100 #   block = linked list of statements
 101 
 102 # == Translation: managing the stack
 103 # Now that we know what the language looks like in the large, let's think
 104 # about how translation happens from the bottom up. One crucial piece of the
 105 # puzzle is how Mu will clean up variables defined on the stack for you.
 106 #
 107 # Assume that we maintain a 'functions' list while parsing source code. And a
 108 # 'primitives' list is a global constant. Both these contain enough information
 109 # to perform type-checking on function calls or primitive statements, respectively.
 110 #
 111 # Defining variables pushes them on a stack with the current block depth and
 112 # enough information about their location (stack offset or register).
 113 # Starting a block increments the current block id.
 114 # Each statement now has enough information to emit code for it.
 115 # Ending a block is where the magic happens:
 116 #   pop all variables at the current block depth
 117 #   emit code to restore all register variables introduced at the current depth
 118 #   emit code to clean up all stack variables at the current depth (just increment esp)
 119 #   decrement the current block depth
 120 #
 121 # Formal types:
 122 #   live-vars: stack of vars
 123 #   var:
 124 #     name: string
 125 #     type: s-expression? Just a type id for now.
 126 #     block: int
 127 #     stack-offset: int  (added to ebp)
 128 #     register: string
 129 #       either usual register names
 130 #       or '*' to indicate any register
 131 #   At most one of stack-offset or register-index must be non-zero.
 132 #   A register of '*' designates a variable _template_. Only legal in formal
 133 #   parameters for primitives.
 134 
 135 # == Translating a single function call
 136 # This one's easy. Assuming we've already checked things, we just drop the
 137 # outputs (which use hard-coded registers) and emit inputs in a standard format.
 138 #
 139 # out1, out2, out3, ... <- name inout1, inout2, inout3, ...
 140 # =>
 141 # (subx-name inout1 inout2 inout3)
 142 #
 143 # Formal types:
 144 #   functions: linked list of info
 145 #     name: string
 146 #     inouts: linked list of vars
 147 #     outputs: linked list of vars
 148 #     body: block (singleton linked list)
 149 #     subx-name: string
 150 
 151 # == Translating a single primitive instruction
 152 # A second crucial piece of the puzzle is how Mu converts fairly regular
 153 # primitives with their uniform syntax to SubX instructions with their gnarly
 154 # x86 details.
 155 #
 156 # Mu instructions have inputs and outputs. Primitives can have up to 2 of
 157 # them.
 158 # SubX instructions have rm32 and r32 operands.
 159 # The translation between them covers almost all the possibilities.
 160 #   Instructions with 1 inout may turn into ones with 1 rm32
 161 #     (e.g. incrementing a var on the stack)
 162 #   Instructions with 1 output may turn into ones with 1 rm32
 163 #     (e.g. incrementing a var in a register)
 164 #   1 inout and 1 output may turn into 1 rm32 and 1 r32
 165 #     (e.g. adding a var to a reg)
 166 #   2 inouts may turn into 1 rm32 and 1 r32
 167 #     (e.g. adding a reg to a var)
 168 #   1 inout and 1 literal may turn into 1 rm32 and 1 imm32
 169 #     (e.g. adding a constant to a var)
 170 #   1 output and 1 literal may turn into 1 rm32 and 1 imm32
 171 #     (e.g. adding a constant to a reg)
 172 #   2 outputs to hardcoded registers and 1 inout may turn into 1 rm32
 173 #     (special-case: divide edx:eax by a var or reg)
 174 # Observations:
 175 #   We always emit rm32. It may be the first inout or the first output.
 176 #   We may emit r32 or imm32 or neither.
 177 #   When we emit r32 it may come from first inout or second inout or first output.
 178 #
 179 # Accordingly, the formal data structure for a primitive looks like this:
 180 #   primitives: linked list of info
 181 #     name: string
 182 #     mu-inouts: linked list of vars to check
 183 #     mu-outputs: linked list of vars to check
 184 #     subx-name: string
 185 #     subx-rm32: enum arg-location
 186 #     subx-r32: enum arg-location
 187 #     subx-imm32: enum arg-location
 188 #   arg-location: enum
 189 #     0 means none
 190 #     1 means first inout
 191 #     2 means second inout
 192 #     3 means first output
 193 
 194 # == Translating a block
 195 # Emit block name if necessary
 196 # Emit '{'
 197 # When you encounter a statement, emit it as above
 198 # When you encounter a variable declaration
 199 #   emit any code needed for it (bzeros)
 200 #   push it on the var stack
 201 #   update register dict if necessary
 202 # When you encounter '}'
 203 #   While popping variables off the var stack until block id changes
 204 #     Emit code needed to clean up the stack
 205 #       either increment esp
 206 #       or pop into appropriate register
 207 
 208 # The rest is straightforward.
 209 
 210 == data
 211 
 212 Program:  # (address function)
 213   0/imm32
 214 
 215 Function-name:
 216   0/imm32
 217 Function-subx-name:
 218   4/imm32
 219 Function-inouts:  # (address list var)
 220   8/imm32
 221 Function-outputs:  # (address list var)
 222   0xc/imm32
 223 Function-body:  # (address block)
 224   0x10/imm32
 225 Function-next:  # (address function)
 226   0x14/imm32
 227 Function-size:
 228   0x18/imm32/24
 229 
 230 Primitive-name:
 231   0/imm32
 232 Primitive-inouts:  # (address list var)
 233   4/imm32
 234 Primitive-outputs:  # (address list var)
 235   8/imm32
 236 Primitive-subx-name:  # (address string)
 237   0xc/imm32
 238 Primitive-subx-rm32:  # enum arg-location
 239   0x10/imm32
 240 Primitive-subx-r32:  # enum arg-location
 241   0x14/imm32
 242 Primitive-subx-imm32:  # enum arg-location
 243   0x18/imm32
 244 Primitive-next:  # (address function)
 245   0x1c/imm32
 246 Primitive-size:
 247   0x20/imm32/24
 248 
 249 Stmt-operation:
 250   0/imm32
 251 Stmt-inouts:
 252   4/imm32
 253 Stmt-outputs:
 254   8/imm32
 255 Stmt-next:
 256   0xc/imm32
 257 Stmt-size:
 258   0x10/imm32
 259 
 260 Var-name:
 261   0/imm32
 262 Var-type:
 263   4/imm32
 264 Var-block:
 265   8/imm32
 266 Var-stack-offset:
 267   0xc/imm32
 268 Var-register:
 269   0x10/imm32
 270 Var-size:
 271   0x14/imm32
 272 
 273 Any-register:  # "*"
 274   # size
 275   1/imm32
 276   # data
 277   2a/asterisk
 278 
 279 == code
 280 
 281 Entry:
 282     # . prologue
 283     89/<- %ebp 4/r32/esp
 284     (new-segment Heap-size Heap)
 285     # if (argv[1] == "test') run-tests()
 286     {
 287       # if (argc <= 1) break
 288       81 7/subop/compare *ebp 1/imm32
 289       7e/jump-if-lesser-or-equal break/disp8
 290       # if (argv[1] != "test") break
 291       (kernel-string-equal? *(ebp+8) "test")  # => eax
 292       3d/compare-eax-and 0/imm32
 293       74/jump-if-equal break/disp8
 294       #
 295       (run-tests)
 296       # syscall(exit, *Num-test-failures)
 297       8b/-> *Num-test-failures 3/r32/ebx
 298       eb/jump $mu-main:end/disp8
 299     }
 300     # otherwise convert Stdin
 301     (convert-mu Stdin Stdout)
 302     (flush Stdout)
 303     # syscall(exit, 0)
 304     bb/copy-to-ebx 0/imm32
 305 $mu-main:end:
 306     b8/copy-to-eax 1/imm32/exit
 307     cd/syscall 0x80/imm8
 308 
 309 convert-mu:  # in : (address buffered-file), out : (address buffered-file)
 310     # . prologue
 311     55/push-ebp
 312     89/<- %ebp 4/r32/esp
 313     #
 314     (parse-mu *(ebp+8))
 315     (check-mu-types)
 316     (emit-subx *(ebp+0xc))
 317 $convert-mu:end:
 318     # . epilogue
 319     89/<- %esp 5/r32/ebp
 320     5d/pop-to-ebp
 321     c3/return
 322 
 323 test-convert-empty-input:
 324     # empty input => empty output
 325     # . prologue
 326     55/push-ebp
 327     89/<- %ebp 4/r32/esp
 328     # setup
 329     (clear-stream _test-input-stream)
 330     (clear-stream _test-input-buffered-file->buffer)
 331     (clear-stream _test-output-stream)
 332     (clear-stream _test-output-buffered-file->buffer)
 333     #
 334     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 335     (flush _test-output-buffered-file)
 336     (check-stream-equal _test-output-stream "" "F - test-convert-empty-input")
 337     # . epilogue
 338     89/<- %esp 5/r32/ebp
 339     5d/pop-to-ebp
 340     c3/return
 341 
 342 test-convert-function-skeleton:
 343     # empty function decl => function prologue and epilogue
 344     #   fn foo {
 345     #   }
 346     # =>
 347     #   foo:
 348     #     # . prologue
 349     #     55/push-ebp
 350     #     89/<- %ebp 4/r32/esp
 351     #     # . epilogue
 352     #     89/<- %esp 5/r32/ebp
 353     #     5d/pop-to-ebp
 354     #     c3/return
 355     # . prologue
 356     55/push-ebp
 357     89/<- %ebp 4/r32/esp
 358     # setup
 359     (clear-stream _test-input-stream)
 360     (clear-stream _test-input-buffered-file->buffer)
 361     (clear-stream _test-output-stream)
 362     (clear-stream _test-output-buffered-file->buffer)
 363     #
 364     (write _test-input-stream "fn foo {\n")
 365     (write _test-input-stream "}\n")
 366     # convert
 367     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 368     (flush _test-output-buffered-file)
 369 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 375     # check output
 376     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-skeleton/0")
 377     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-skeleton/1")
 378     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-skeleton/2")
 379     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-skeleton/3")
 380     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-skeleton/4")
 381     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-skeleton/5")
 382     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-skeleton/6")
 383     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-skeleton/7")
 384     # . epilogue
 385     89/<- %esp 5/r32/ebp
 386     5d/pop-to-ebp
 387     c3/return
 388 
 389 test-convert-multiple-function-skeletons:
 390     # multiple functions correctly organized into a linked list
 391     #   fn foo {
 392     #   }
 393     #   fn bar {
 394     #   }
 395     # =>
 396     #   foo:
 397     #     # . prologue
 398     #     55/push-ebp
 399     #     89/<- %ebp 4/r32/esp
 400     #     # . epilogue
 401     #     89/<- %esp 5/r32/ebp
 402     #     5d/pop-to-ebp
 403     #     c3/return
 404     #   bar:
 405     #     # . prologue
 406     #     55/push-ebp
 407     #     89/<- %ebp 4/r32/esp
 408     #     # . epilogue
 409     #     89/<- %esp 5/r32/ebp
 410     #     5d/pop-to-ebp
 411     #     c3/return
 412     # . prologue
 413     55/push-ebp
 414     89/<- %ebp 4/r32/esp
 415     # setup
 416     (clear-stream _test-input-stream)
 417     (clear-stream _test-input-buffered-file->buffer)
 418     (clear-stream _test-output-stream)
 419     (clear-stream _test-output-buffered-file->buffer)
 420     #
 421     (write _test-input-stream "fn foo {\n")
 422     (write _test-input-stream "}\n")
 423     (write _test-input-stream "fn bar {\n")
 424     (write _test-input-stream "}\n")
 425     # convert
 426     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 427     (flush _test-output-buffered-file)
 428 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 434     # check first function
 435     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-multiple-function-skeletons/0")
 436     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/1")
 437     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/2")
 438     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/3")
 439     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/4")
 440     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/5")
 441     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/6")
 442     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/7")
 443     # check second function
 444     (check-next-stream-line-equal _test-output-stream "bar:"                  "F - test-convert-multiple-function-skeletons/10")
 445     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/11")
 446     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/12")
 447     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/13")
 448     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/14")
 449     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/15")
 450     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/16")
 451     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/17")
 452     # . epilogue
 453     89/<- %esp 5/r32/ebp
 454     5d/pop-to-ebp
 455     c3/return
 456 
 457 test-convert-function-with-arg:
 458     # function with one arg and a copy instruction
 459     #   fn foo n : int -> result/eax : int {
 460     #     result <- copy n
 461     #   }
 462     # =>
 463     #   foo:
 464     #     # . prologue
 465     #     55/push-ebp
 466     #     89/<- %ebp 4/r32/esp
 467     #     {
 468     #     # result <- copy n
 469     #     8b/-> *(ebp+8) 0/r32/eax
 470     #     }
 471     #     # . epilogue
 472     #     89/<- %esp 5/r32/ebp
 473     #     5d/pop-to-ebp
 474     #     c3/return
 475     # . prologue
 476     55/push-ebp
 477     89/<- %ebp 4/r32/esp
 478     # setup
 479     (clear-stream _test-input-stream)
 480     (clear-stream _test-input-buffered-file->buffer)
 481     (clear-stream _test-output-stream)
 482     (clear-stream _test-output-buffered-file->buffer)
 483     #
 484     (write _test-input-stream "fn foo {\n")
 485     (write _test-input-stream "}\n")
 486     # convert
 487     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 488     (flush _test-output-buffered-file)
 489 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 495     # check output
 496     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-skeleton/0")
 497     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-skeleton/1")
 498     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-skeleton/2")
 499     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-skeleton/3")
 500     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-skeleton/4")
 501     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-skeleton/5")
 502     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-skeleton/6")
 503     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-skeleton/7")
 504     # . epilogue
 505     89/<- %esp 5/r32/ebp
 506     5d/pop-to-ebp
 507     c3/return
 508 
 509 parse-mu:  # in : (address buffered-file)
 510     # pseudocode
 511     #   var curr-function = Program
 512     #   var line : (stream byte 512)
 513     #   var word-slice : slice
 514     #   while true                                  # line loop
 515     #     clear-stream(line)
 516     #     read-line-buffered(in, line)
 517     #     if (line->write == 0) break               # end of file
 518     #     while true                                # word loop
 519     #       word-slice = next-word-or-string(line)
 520     #       if slice-empty?(word-slice)             # end of line
 521     #         break
 522     #       else if slice-starts-with?(word-slice, "#")  # comment
 523     #         break                                 # end of line
 524     #       else if slice-equal(word-slice, "fn")
 525     #         var new-function : (address function) = new function
 526     #         populate-mu-function(in, new-function)
 527     #         *curr-function = new-function
 528     #         curr-function = &new-function->next
 529     #       else
 530     #         abort()
 531     #
 532     # . prologue
 533     55/push-ebp
 534     89/<- %ebp 4/r32/esp
 535     # . save registers
 536     50/push-eax
 537     51/push-ecx
 538     52/push-edx
 539     57/push-edi
 540     # var line/ecx : (stream byte 512)
 541     81 5/subop/subtract %esp 0x200/imm32
 542     68/push 0x200/imm32/length
 543     68/push 0/imm32/read
 544     68/push 0/imm32/write
 545     89/<- %ecx 4/r32/esp
 546     # var word-slice/edx : slice
 547     68/push 0/imm32/end
 548     68/push 0/imm32/start
 549     89/<- %edx 4/r32/esp
 550     # var curr-function/edi : (address function) = Program
 551     bf/copy-to-edi Program/imm32
 552     {
 553 $parse-mu:line-loop:
 554       (clear-stream %ecx)
 555       (read-line-buffered *(ebp+8) %ecx)
 556       # if (line->write == 0) break
 557       81 7/subop/compare *ecx 0/imm32
 558       0f 84/jump-if-equal break/disp32
 559 +--  6 lines: #?       # dump line ---------------------------------------------------------------------------------------------------------------------------
 565       { # word loop
 566 $parse-mu:word-loop:
 567         (next-word-or-string %ecx %edx)
 568         # if slice-empty?(word-slice) break
 569         (slice-empty? %edx)
 570         3d/compare-eax-and 0/imm32
 571         0f 85/jump-if-not-equal break/disp32
 572         # if (*word-slice->start == "#") break
 573         # . eax = *word-slice->start
 574         8b/-> *edx 0/r32/eax
 575         8a/copy-byte *eax 0/r32/AL
 576         81 4/subop/and %eax 0xff/imm32
 577         # . if (eax == '#') break
 578         3d/compare-eax-and 0x23/imm32/hash
 579         0f 84/jump-if-equal break/disp32
 580         # if (slice-equal?(word-slice, "fn")) parse a function
 581         {
 582           (slice-equal? %edx "fn")
 583           3d/compare-eax-and 0/imm32
 584           0f 84/jump-if-equal break/disp32
 585           # var new-function/eax : (address function) = populate-mu-function()
 586           (allocate Heap *Function-size)  # => eax
 587           (populate-mu-function-header %ecx %eax)
 588           (populate-mu-function-body *(ebp+8) %eax)
 589           # *curr-function = new-function
 590           89/<- *edi 0/r32/eax
 591           # curr-function = &new-function->next
 592           8d/address-> *(eax+0x10) 7/r32/edi
 593           e9/jump $parse-mu:word-loop/disp32
 594         }
 595         # otherwise abort
 596         e9/jump $parse-mu:abort/disp32
 597       } # end word loop
 598       e9/jump loop/disp32
 599     } # end line loop
 600 $parse-mu:end:
 601     # . reclaim locals
 602     81 0/subop/add %esp 0x214/imm32
 603     # . restore registers
 604     5f/pop-to-edi
 605     5a/pop-to-edx
 606     59/pop-to-ecx
 607     58/pop-to-eax
 608     # . epilogue
 609     89/<- %esp 5/r32/ebp
 610     5d/pop-to-ebp
 611     c3/return
 612 
 613 $parse-mu:abort:
 614     # error("unexpected top-level command: " word-slice "\n")
 615     (write-buffered Stderr "unexpected top-level command: ")
 616     (write-buffered Stderr %edx)
 617     (write-buffered Stderr "\n")
 618     (flush Stderr)
 619     # . syscall(exit, 1)
 620     bb/copy-to-ebx  1/imm32
 621     b8/copy-to-eax  1/imm32/exit
 622     cd/syscall  0x80/imm8
 623     # never gets here
 624 
 625 # errors considered:
 626 #   fn foo { {
 627 #   fn foo { }
 628 #   fn foo { } {
 629 #   fn foo  # no block
 630 populate-mu-function-header:  # first-line : (address stream byte), out : (address function)
 631     # . prologue
 632     55/push-ebp
 633     89/<- %ebp 4/r32/esp
 634     # . save registers
 635     50/push-eax
 636     51/push-ecx
 637     57/push-edi
 638     # edi = out
 639     8b/-> *(ebp+0xc) 7/r32/edi
 640     # var word-slice/ecx : slice
 641     68/push 0/imm32/end
 642     68/push 0/imm32/start
 643     89/<- %ecx 4/r32/esp
 644     # save function name
 645     (next-word *(ebp+8) %ecx)
 646     (slice-to-string Heap %ecx)  # => eax
 647     89/<- *edi 0/r32/eax
 648     # assert that next token is '{'
 649     (next-word *(ebp+8) %ecx)
 650     (slice-equal? %ecx "{")
 651     3d/compare-eax-and 0/imm32
 652     74/jump-if-equal $populate-mu-function-header:abort/disp8
 653     # assert that there's no further token
 654     {
 655       # word-slice = next-word(line)
 656       (next-word *(ebp+8) %ecx)
 657       # if (word-slice == '') break
 658       (slice-empty? %ecx)
 659       3d/compare-eax-and 0/imm32
 660       75/jump-if-not-equal break/disp8
 661       # if (slice-starts-with?(word-slice, "#")) break
 662       # . eax = *word-slice->start
 663       8b/-> *edx 0/r32/eax
 664       8a/copy-byte *eax 0/r32/AL
 665       81 4/subop/and %eax 0xff/imm32
 666       # . if (eax == '#') break
 667       3d/compare-eax-and 0x23/imm32/hash
 668       74/jump-if-equal break/disp8
 669       # otherwise abort
 670       eb/jump $populate-mu-function-header:abort/disp8
 671     }
 672 $populate-mu-function-header:end:
 673     # . reclaim locals
 674     81 0/subop/add %esp 8/imm32
 675     # . restore registers
 676     5f/pop-to-edi
 677     59/pop-to-ecx
 678     58/pop-to-eax
 679     # . epilogue
 680     89/<- %esp 5/r32/ebp
 681     5d/pop-to-ebp
 682     c3/return
 683 
 684 $populate-mu-function-header:abort:
 685     # error("function header not in form 'fn <name> {'")
 686     (write-buffered Stderr "function header not in form 'fn <name> {' -- '")
 687     (rewind-stream *(ebp+8))
 688     (write-stream 2 *(ebp+8))
 689     (write-buffered Stderr "'\n")
 690     (flush Stderr)
 691     # . syscall(exit, 1)
 692     bb/copy-to-ebx  1/imm32
 693     b8/copy-to-eax  1/imm32/exit
 694     cd/syscall  0x80/imm8
 695     # never gets here
 696 
 697 # errors considered:
 698 #   { abc
 699 populate-mu-function-body:  # in : (address buffered-file), out : (address function)
 700     # . prologue
 701     55/push-ebp
 702     89/<- %ebp 4/r32/esp
 703     # . save registers
 704     50/push-eax
 705     51/push-ecx
 706     52/push-edx
 707     53/push-ebx
 708     # var line/ecx : (stream byte 512)
 709     81 5/subop/subtract %esp 0x200/imm32
 710     68/push 0x200/imm32/length
 711     68/push 0/imm32/read
 712     68/push 0/imm32/write
 713     89/<- %ecx 4/r32/esp
 714     # var word-slice/edx : slice
 715     68/push 0/imm32/end
 716     68/push 0/imm32/start
 717     89/<- %edx 4/r32/esp
 718     # var open-curly-count/ebx : int = 1
 719     bb/copy-to-ebx 1/imm32
 720     { # line loop
 721 $populate-mu-function-body:line-loop:
 722       # if (open-curly-count == 0) break
 723       81 7/subop/compare %ebx 0/imm32
 724       0f 84/jump-if-equal break/disp32
 725       # line = read-line-buffered(in)
 726       (clear-stream %ecx)
 727       (read-line-buffered *(ebp+8) %ecx)
 728       # if (line->write == 0) break
 729       81 7/subop/compare *ecx 0/imm32
 730       0f 84/jump-if-equal break/disp32
 731       # word-slice = next-word(line)
 732       (next-word %ecx %edx)
 733       # if slice-empty?(word-slice) continue
 734       (slice-empty? %ecx)
 735       3d/compare-eax-and 0/imm32
 736       75/jump-if-not-equal loop/disp8
 737       # if (slice-starts-with?(word-slice, '#') continue
 738       # . eax = *word-slice->start
 739       8b/-> *edx 0/r32/eax
 740       8a/copy-byte *eax 0/r32/AL
 741       81 4/subop/and %eax 0xff/imm32
 742       # . if (eax == '#') continue
 743       3d/compare-eax-and 0x23/imm32/hash
 744       74/jump-if-equal loop/disp8
 745       {
 746         # if slice-equal?(word-slice, "{") ++open-curly-count
 747         {
 748           (slice-equal? %ecx "{")
 749           3d/compare-eax-and 0/imm32
 750           74/jump-if-equal break/disp8
 751           43/increment-ebx
 752           eb/jump $curly-found:end/disp8
 753         }
 754         # else if slice-equal?(word-slice, "}") --open-curly-count
 755         {
 756           (slice-equal? %ecx "}")
 757           3d/compare-eax-and 0/imm32
 758           74/jump-if-equal break/disp8
 759           4b/decrement-ebx
 760           eb/jump $curly-found:end/disp8
 761         }
 762         # else break
 763         eb/jump $populate-mu-function-body:end/disp8
 764       }
 765       # - check for invalid tokens after curly
 766 $curly-found:end:
 767       # second-word-slice = next-word(line)
 768       (next-word %ecx %edx)
 769       # if slice-empty?(second-word-slice) continue
 770       (slice-empty? %ecx)
 771       3d/compare-eax-and 0/imm32
 772       0f 85/jump-if-not-equal loop/disp32
 773       # if (slice-starts-with?(second-word-slice, '#') continue
 774       # . eax = *second-word-slice->start
 775       8b/-> *edx 0/r32/eax
 776       8a/copy-byte *eax 0/r32/AL
 777       81 4/subop/and %eax 0xff/imm32
 778       # . if (eax == '#') continue
 779       3d/compare-eax-and 0x23/imm32/hash
 780       0f 84/jump-if-equal loop/disp32
 781       # abort
 782       eb/jump $populate-mu-function-body:abort/disp8
 783     } # end line loop
 784 $populate-mu-function-body:end:
 785     # . reclaim locals
 786     81 0/subop/add %esp 0x214/imm32
 787     # . restore registers
 788     5b/pop-to-ebx
 789     5a/pop-to-edx
 790     59/pop-to-ecx
 791     58/pop-to-eax
 792     # . epilogue
 793     89/<- %esp 5/r32/ebp
 794     5d/pop-to-ebp
 795     c3/return
 796 
 797 $populate-mu-function-body:abort:
 798     # error("'{' or '}' should be on its own line, but got '")
 799     (write-buffered Stderr "'{' or '}' should be on its own line, but got '")
 800     (rewind-stream %ecx)
 801     (write-stream 2 %ecx)
 802     (write-buffered Stderr "'\n")
 803     (flush Stderr)
 804     # . syscall(exit, 1)
 805     bb/copy-to-ebx  1/imm32
 806     b8/copy-to-eax  1/imm32/exit
 807     cd/syscall  0x80/imm8
 808     # never gets here
 809 
 810 check-mu-types:
 811     # . prologue
 812     55/push-ebp
 813     89/<- %ebp 4/r32/esp
 814     #
 815 $check-types:end:
 816     # . epilogue
 817     89/<- %esp 5/r32/ebp
 818     5d/pop-to-ebp
 819     c3/return
 820 
 821 emit-subx:  # out : (address buffered-file)
 822     # . prologue
 823     55/push-ebp
 824     89/<- %ebp 4/r32/esp
 825     # . save registers
 826     50/push-eax
 827     51/push-ecx
 828     57/push-edi
 829     # edi = out
 830     8b/-> *(ebp+8) 7/r32/edi
 831     # var curr/ecx : (address function) = Program
 832     8b/-> *Program 1/r32/ecx
 833     {
 834       # if (curr == NULL) break
 835       81 7/subop/compare %ecx 0/imm32
 836       0f 84/jump-if-equal break/disp32
 837       (emit-subx-function %edi %ecx)
 838       # curr = curr->next
 839       8b/-> *(ecx+0x10) 1/r32/ecx
 840       e9/jump loop/disp32
 841     }
 842 $emit-subx:end:
 843     # . restore registers
 844     5f/pop-to-edi
 845     59/pop-to-ecx
 846     58/pop-to-eax
 847     # . epilogue
 848     89/<- %esp 5/r32/ebp
 849     5d/pop-to-ebp
 850     c3/return
 851 
 852 # == Emitting a function
 853 # Emit function header
 854 # Emit function prologue
 855 # Translate function body
 856 # Emit function epilogue
 857 
 858 emit-subx-function:  # out : (address buffered-file), f : (address function)
 859     # . prologue
 860     55/push-ebp
 861     89/<- %ebp 4/r32/esp
 862     # . save registers
 863     50/push-eax
 864     51/push-ecx
 865     57/push-edi
 866     # edi = out
 867     8b/-> *(ebp+8) 7/r32/edi
 868     # ecx = f
 869     8b/-> *(ebp+0xc) 1/r32/ecx
 870     #
 871     (write-buffered %edi *ecx)
 872     (write-buffered %edi ":\n")
 873     (emit-subx-prologue %edi)
 874     (emit-subx-block %edi *(ecx+0x10))  # Function-body
 875     (emit-subx-epilogue %edi)
 876 $emit-subx-function:end:
 877     # . restore registers
 878     5f/pop-to-edi
 879     59/pop-to-ecx
 880     58/pop-to-eax
 881     # . epilogue
 882     89/<- %esp 5/r32/ebp
 883     5d/pop-to-ebp
 884     c3/return
 885 
 886 emit-subx-block:  # out : (address buffered-file), block : (address block)
 887     # . prologue
 888     55/push-ebp
 889     89/<- %ebp 4/r32/esp
 890     #
 891 $emit-subx-block:end:
 892     # . epilogue
 893     89/<- %esp 5/r32/ebp
 894     5d/pop-to-ebp
 895     c3/return
 896 
 897 emit-subx-statement:  # out : (address buffered-file), stmt : (address statement), vars : (stack var), primitives : (address primitive), functions : (address function)
 898     # . prologue
 899     55/push-ebp
 900     89/<- %ebp 4/r32/esp
 901     # . save registers
 902     50/push-eax
 903     51/push-ecx
 904     # if stmt matches a primitive, emit it
 905     {
 906 $emit-subx-statement:primitive:
 907       (find-matching-primitive *(ebp+0x14) *(ebp+0xc))  # primitives, stmt => curr/eax
 908       3d/compare-eax-and 0/imm32
 909       74/jump-if-equal break/disp8
 910       (emit-subx-primitive *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax)  # out, stmt, vars, curr
 911       e9/jump $emit-subx-statement:end/disp32
 912     }
 913     # else if stmt matches a function, emit a call to it
 914     {
 915 $emit-subx-statement:call:
 916       (find-matching-function *(ebp+0x18) *(ebp+0xc))  # functions, stmt => curr/eax
 917       3d/compare-eax-and 0/imm32
 918       74/jump-if-equal break/disp8
 919       (emit-subx-call *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax)  # out, stmt, vars, curr
 920       e9/jump $emit-subx-statement:end/disp32
 921     }
 922     # else abort
 923     e9/jump $emit-subx-statement:abort/disp32
 924 $emit-subx-statement:end:
 925     # . restore registers
 926     59/pop-to-ecx
 927     58/pop-to-eax
 928     # . epilogue
 929     89/<- %esp 5/r32/ebp
 930     5d/pop-to-ebp
 931     c3/return
 932 
 933 $emit-subx-statement:abort:
 934     # error("couldn't translate '" stmt "'\n")
 935     (write-buffered Stderr "couldn't translate '")
 936 #?     (emit-string Stderr *(ebp+0xc))  # TODO
 937     (write-buffered Stderr "'\n")
 938     (flush Stderr)
 939     # . syscall(exit, 1)
 940     bb/copy-to-ebx  1/imm32
 941     b8/copy-to-eax  1/imm32/exit
 942     cd/syscall  0x80/imm8
 943     # never gets here
 944 
 945 emit-subx-primitive:  # out : (address buffered-file), stmt : (address statement), vars : (address variable), primitive : (address function)
 946     # . prologue
 947     55/push-ebp
 948     89/<- %ebp 4/r32/esp
 949     # . save registers
 950     50/push-eax
 951     51/push-ecx
 952     # ecx = primitive
 953     8b/-> *(ebp+0x14) 1/r32/ecx
 954     # emit primitive name
 955     (write-buffered *(ebp+8) *(ecx+0xc))  # Primitive-subx-name
 956     # emit rm32 if necessary
 957     (emit-subx-rm32 *(ebp+8) *(ecx+0x10) *(ebp+0xc))  # out, Primitive-subx-rm32, stmt
 958 #?     # emit r32 if necessary
 959 #?     (emit-subx-r32 *(ebp+8) *(ecx+0x14) *(ebp+0xc))  # out, Primitive-subx-r32, stmt
 960 #?     # emit imm32 if necessary
 961 #?     (emit-subx-imm32 *(ebp+8) *(ecx+0x18) *(ebp+0xc))  # out, Primitive-subx-imm32, stmt
 962 $emit-subx-primitive:end:
 963     # . restore registers
 964     59/pop-to-ecx
 965     58/pop-to-eax
 966     # . epilogue
 967     89/<- %esp 5/r32/ebp
 968     5d/pop-to-ebp
 969     c3/return
 970 
 971 emit-subx-rm32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
 972     # . prologue
 973     55/push-ebp
 974     89/<- %ebp 4/r32/esp
 975     # . save registers
 976     50/push-eax
 977     # if (l == 0) return
 978     81 7/subop/compare *(ebp+0xc) 0/imm32
 979     74/jump-if-equal $emit-subx-rm32:end/disp8
 980     #
 981     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
 982     (emit-subx-call-operand *(ebp+8) %eax)  # out, var
 983 $emit-subx-rm32:end:
 984     # . restore registers
 985     58/pop-to-eax
 986     # . epilogue
 987     89/<- %esp 5/r32/ebp
 988     5d/pop-to-ebp
 989     c3/return
 990 
 991 get-stmt-operand-from-arg-location:  # stmt : (address statement), l : arg-location -> var/eax : (address variable)
 992     # . prologue
 993     55/push-ebp
 994     89/<- %ebp 4/r32/esp
 995     # . save registers
 996     51/push-ecx
 997     # eax = l
 998     8b/-> *(ebp+0xc) 0/r32/eax
 999     # ecx = stmt
1000     8b/-> *(ebp+8) 1/r32/ecx
1001     # if (l == 1) return stmt->inouts->var
1002     {
1003       3d/compare-eax-and 1/imm32
1004       75/jump-if-not-equal break/disp8
1005 $get-stmt-operand-from-arg-location:1:
1006       8b/-> *(ecx+4) 0/r32/eax  # Stmt-inouts
1007       8b/-> *eax 0/r32/eax  # Operand-var
1008       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1009     }
1010     # if (l == 2) return stmt->inouts->next->var
1011     {
1012       3d/compare-eax-and 2/imm32
1013       75/jump-if-not-equal break/disp8
1014 $get-stmt-operand-from-arg-location:2:
1015       8b/-> *(ecx+4) 0/r32/eax  # Stmt-inouts
1016       8b/-> *(eax+4) 0/r32/eax  # Operand-next
1017       8b/-> *eax 0/r32/eax  # Operand-var
1018       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1019     }
1020     # if (l == 3) return stmt->outputs
1021     {
1022       3d/compare-eax-and 3/imm32
1023       75/jump-if-not-equal break/disp8
1024 $get-stmt-operand-from-arg-location:3:
1025       8b/-> *(ecx+8) 0/r32/eax  # Stmt-outputs
1026       8b/-> *eax 0/r32/eax  # Operand-var
1027       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1028     }
1029     # abort
1030     e9/jump $get-stmt-operand-from-arg-location:abort/disp32
1031 $get-stmt-operand-from-arg-location:end:
1032     # . restore registers
1033     59/pop-to-ecx
1034     # . epilogue
1035     89/<- %esp 5/r32/ebp
1036     5d/pop-to-ebp
1037     c3/return
1038 
1039 $get-stmt-operand-from-arg-location:abort:
1040     # error("invalid arg-location " eax)
1041     (write-buffered Stderr "invalid arg-location ")
1042     (print-int32-buffered Stderr %eax)
1043     (write-buffered Stderr "\n")
1044     (flush Stderr)
1045     # . syscall(exit, 1)
1046     bb/copy-to-ebx  1/imm32
1047     b8/copy-to-eax  1/imm32/exit
1048     cd/syscall  0x80/imm8
1049     # never gets here
1050 
1051 emit-subx-r32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1052     # . prologue
1053     55/push-ebp
1054     89/<- %ebp 4/r32/esp
1055     # . save registers
1056     50/push-eax
1057     51/push-ecx
1058     # location/ecx : enum = primitive->subx-r32
1059     # if (location == 0) return
1060     # var/ecx : var = get-operand(stmt, primitive->subx-rm32)
1061     # emit-subx-call-operand(out, var)
1062 $emit-subx-r32:end:
1063     # . restore registers
1064     59/pop-to-ecx
1065     58/pop-to-eax
1066     # . epilogue
1067     89/<- %esp 5/r32/ebp
1068     5d/pop-to-ebp
1069     c3/return
1070 
1071 emit-subx-imm32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1072     # . prologue
1073     55/push-ebp
1074     89/<- %ebp 4/r32/esp
1075     # . save registers
1076     50/push-eax
1077     51/push-ecx
1078     # var/ecx : var = get-operand(stmt, primitive->subx-rm32)
1079     # emit-subx-call-operand(out, var)
1080 $emit-subx-imm32:end:
1081     # . restore registers
1082     59/pop-to-ecx
1083     58/pop-to-eax
1084     # . epilogue
1085     89/<- %esp 5/r32/ebp
1086     5d/pop-to-ebp
1087     c3/return
1088 
1089 #?     # var args/ecx : (list var) = stmt->inouts
1090 #?     8b/-> *(ebp+0xc) 1/r32/ecx
1091 #?     8b/-> *(ecx+4) 1/r32/ecx  # Stmt-inouts
1092 #?     {
1093 #?       # if (curr == null) break
1094 #?       81 7/subop/compare %ecx 0/imm32
1095 #?       74/jump-if-equal break/disp8
1096 #?       #
1097 #?       (emit-subx-call-operand *(ebp+8) *ecx)
1098 #?       # args = args->next
1099 #?       8b/-> *(ecx+4) 1/r32/ecx
1100 #?     }
1101 
1102 emit-subx-call:  # out : (address buffered-file), stmt : (address statement), vars : (address variable), callee : (address function)
1103     # . prologue
1104     55/push-ebp
1105     89/<- %ebp 4/r32/esp
1106     # . save registers
1107     50/push-eax
1108     51/push-ecx
1109     #
1110     (write-buffered *(ebp+8) "(")
1111     # - emit function name
1112     8b/-> *(ebp+0x14) 1/r32/ecx
1113     (write-buffered *(ebp+8) *(ecx+4))  # Function-subx-name
1114     # - emit arguments
1115     # var curr/ecx : (list var) = stmt->inouts
1116     8b/-> *(ebp+0xc) 1/r32/ecx
1117     8b/-> *(ecx+4) 1/r32/ecx  # Stmt-inouts
1118     {
1119       # if (curr == null) break
1120       81 7/subop/compare %ecx 0/imm32
1121       74/jump-if-equal break/disp8
1122       #
1123       (emit-subx-call-operand *(ebp+8) *ecx)
1124       # curr = curr->next
1125       8b/-> *(ecx+4) 1/r32/ecx
1126     }
1127     #
1128     (write-buffered *(ebp+8) ")")
1129 $emit-subx-call:end:
1130     # . restore registers
1131     59/pop-to-ecx
1132     58/pop-to-eax
1133     # . epilogue
1134     89/<- %esp 5/r32/ebp
1135     5d/pop-to-ebp
1136     c3/return
1137 
1138 emit-subx-call-operand:  # out : (address buffered-file), operand : (address variable)
1139     # . prologue
1140     55/push-ebp
1141     89/<- %ebp 4/r32/esp
1142     # . save registers
1143     50/push-eax
1144     # eax = operand
1145     8b/-> *(ebp+0xc) 0/r32/eax
1146     # if (operand->register) emit "%__"
1147     {
1148       81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
1149       74/jump-if-equal break/disp8
1150 $emit-subx-call-operand:register:
1151       (write-buffered *(ebp+8) " %")
1152       (write-buffered *(ebp+8) *(eax+0x10))  # Var-register
1153     }
1154     # else if (operand->stack-offset) emit "*(ebp+__)"
1155     {
1156       81 7/subop/compare *(eax+0xc) 0/imm32  # Var-stack-offset
1157       74/jump-if-equal break/disp8
1158 $emit-subx-call-operand:stack:
1159       (write-buffered *(ebp+8) Space)
1160       (write-buffered *(ebp+8) "*(ebp+")
1161       8b/-> *(ebp+0xc) 0/r32/eax
1162       (print-int32-buffered *(ebp+8) *(eax+0xc))  # Var-stack-offset
1163       (write-buffered *(ebp+8) ")")
1164     }
1165 $emit-subx-call-operand:end:
1166     # . restore registers
1167     58/pop-to-eax
1168     # . epilogue
1169     89/<- %esp 5/r32/ebp
1170     5d/pop-to-ebp
1171     c3/return
1172 
1173 find-matching-function:  # functions : (address function), stmt : (address statement) -> result/eax : (address function)
1174     # . prologue
1175     55/push-ebp
1176     89/<- %ebp 4/r32/esp
1177     # . save registers
1178     51/push-ecx
1179     # var curr/ecx : (address function) = functions
1180     8b/-> *(ebp+8) 1/r32/ecx
1181     {
1182       # if (curr == null) break
1183       81 7/subop/compare %ecx 0/imm32
1184       74/jump-if-equal break/disp8
1185       # if match(curr, stmt) return curr
1186       {
1187         (mu-stmt-matches-function? *(ebp+0xc) %ecx)  # => eax
1188         3d/compare-eax-and 0/imm32
1189         74/jump-if-equal break/disp8
1190         89/<- %eax 1/r32/ecx
1191         eb/jump $find-matching-function:end/disp8
1192       }
1193       # curr = curr->next
1194       8b/-> *(ecx+0x10) 1/r32/ecx  # Function-next
1195       eb/jump loop/disp8
1196     }
1197     # return null
1198     b8/copy-to-eax 0/imm32
1199 $find-matching-function:end:
1200     # . restore registers
1201     59/pop-to-ecx
1202     # . epilogue
1203     89/<- %esp 5/r32/ebp
1204     5d/pop-to-ebp
1205     c3/return
1206 
1207 find-matching-primitive:  # primitives : (address primitive), stmt : (address statement) -> result/eax : (address primitive)
1208     # . prologue
1209     55/push-ebp
1210     89/<- %ebp 4/r32/esp
1211     # . save registers
1212     51/push-ecx
1213     # var curr/ecx : (address primitive) = primitives
1214     8b/-> *(ebp+8) 1/r32/ecx
1215     {
1216       # if (curr == null) break
1217       81 7/subop/compare %ecx 0/imm32
1218       74/jump-if-equal break/disp8
1219       # if match(curr, stmt) return curr
1220       {
1221         (mu-stmt-matches-primitive? *(ebp+0xc) %ecx)  # => eax
1222         3d/compare-eax-and 0/imm32
1223         74/jump-if-equal break/disp8
1224         89/<- %eax 1/r32/ecx
1225         eb/jump $find-matching-function:end/disp8
1226       }
1227       # curr = curr->next
1228       8b/-> *(ecx+0x18) 1/r32/ecx  # Primitive-next
1229       eb/jump loop/disp8
1230     }
1231     # return null
1232     b8/copy-to-eax 0/imm32
1233 $find-matching-primitive:end:
1234     # . restore registers
1235     59/pop-to-ecx
1236     # . epilogue
1237     89/<- %esp 5/r32/ebp
1238     5d/pop-to-ebp
1239     c3/return
1240 
1241 mu-stmt-matches-function?:  # stmt : (address statement), function : (address opcode-info) => result/eax : boolean
1242     # . prologue
1243     55/push-ebp
1244     89/<- %ebp 4/r32/esp
1245     # . save registers
1246     51/push-ecx
1247     # return primitive->name == stmt->operation
1248     8b/-> *(ebp+8) 1/r32/ecx
1249     8b/-> *(ebp+0xc) 0/r32/eax
1250     (string-equal? *ecx *eax)  # => eax
1251 $mu-stmt-matches-function?:end:
1252     # . restore registers
1253     59/pop-to-ecx
1254     # . epilogue
1255     89/<- %esp 5/r32/ebp
1256     5d/pop-to-ebp
1257     c3/return
1258 
1259 mu-stmt-matches-primitive?:  # stmt : (address statement), primitive : (address primitive) => result/eax : boolean
1260     # . prologue
1261     55/push-ebp
1262     89/<- %ebp 4/r32/esp
1263     # . save registers
1264     51/push-ecx
1265     # return primitive->name == stmt->operation
1266     8b/-> *(ebp+8) 1/r32/ecx
1267     8b/-> *(ebp+0xc) 0/r32/eax
1268     (string-equal? *ecx *eax)  # => eax
1269 $mu-stmt-matches-primitive?:end:
1270     # . restore registers
1271     59/pop-to-ecx
1272     # . epilogue
1273     89/<- %esp 5/r32/ebp
1274     5d/pop-to-ebp
1275     c3/return
1276 
1277 test-emit-subx-statement-primitive:
1278     # Primitive operation on a variable on the stack.
1279     #   increment foo
1280     # =>
1281     #   ff 0/subop/increment *(ebp-8)
1282     #
1283     # There's a variable on the var stack as follows:
1284     #   name: 'foo'
1285     #   type: int
1286     #   stack-offset: -8
1287     #
1288     # There's a primitive with this info:
1289     #   name: 'increment'
1290     #   inouts: int/mem
1291     #   value: 'ff 0/subop/increment'
1292     #
1293     # There's nothing in functions.
1294     #
1295     # . prologue
1296     55/push-ebp
1297     89/<- %ebp 4/r32/esp
1298     # setup
1299     (clear-stream _test-output-stream)
1300     (clear-stream _test-output-buffered-file->buffer)
1301     # var-foo/ecx : var
1302     68/push 0/imm32/no-register
1303     68/push -8/imm32/stack-offset
1304     68/push 1/imm32/block-depth
1305     68/push 1/imm32/type-int
1306     68/push "foo"/imm32
1307     89/<- %ecx 4/r32/esp
1308     # vars/edx : (stack 1)
1309     51/push-ecx/var-foo
1310     68/push 1/imm32/data-length
1311     68/push 1/imm32/top
1312     89/<- %edx 4/r32/esp
1313     # operand/esi : (list var)
1314     68/push 0/imm32/next
1315     51/push-ecx/var-foo
1316     89/<- %esi 4/r32/esp
1317     # stmt/esi : statement
1318     68/push 0/imm32/next
1319     68/push 0/imm32/outputs
1320     56/push-esi/operands
1321     68/push "increment"/imm32/operation
1322     89/<- %esi 4/r32/esp
1323     # primitives/ebx : primitive
1324     68/push 0/imm32/next
1325     68/push 0/imm32/no-imm32
1326     68/push 0/imm32/no-r32
1327     68/push 1/imm32/rm32-is-first-inout
1328     68/push "ff 0/subop/increment"/imm32/subx-name
1329     68/push 0/imm32/outputs
1330     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
1331     68/push "increment"/imm32/name
1332     89/<- %ebx 4/r32/esp
1333     # convert
1334     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1335     (flush _test-output-buffered-file)
1336 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1342     # check output
1343     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0xfffffff8)" "F - test-emit-subx-statement-primitive/0")
1344     # . reclaim locals
1345     81 0/subop/add %esp 0x48/imm32
1346     # . epilogue
1347     89/<- %esp 5/r32/ebp
1348     5d/pop-to-ebp
1349     c3/return
1350 
1351 test-emit-subx-statement-primitive-register:
1352     # Primitive operation on a variable in a register.
1353     #   foo <- increment
1354     # =>
1355     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1356     #
1357     # There's a variable on the var stack as follows:
1358     #   name: 'foo'
1359     #   type: int
1360     #   register: 'eax'
1361     #
1362     # There's a primitive with this info:
1363     #   name: 'increment'
1364     #   inout: int/reg
1365     #   value: 'ff 0/subop/increment'
1366     #
1367     # There's nothing in functions.
1368     #
1369     # . prologue
1370     55/push-ebp
1371     89/<- %ebp 4/r32/esp
1372     # setup
1373     (clear-stream _test-output-stream)
1374     (clear-stream _test-output-buffered-file->buffer)
1375     # var-foo/ecx : var in eax
1376     68/push "eax"/imm32/register
1377     68/push 0/imm32/no-stack-offset
1378     68/push 1/imm32/block-depth
1379     68/push 1/imm32/type-int
1380     68/push "foo"/imm32
1381     89/<- %ecx 4/r32/esp
1382     # vars/edx : (stack 1)
1383     51/push-ecx/var-foo
1384     68/push 1/imm32/data-length
1385     68/push 1/imm32/top
1386     89/<- %edx 4/r32/esp
1387     # operand/esi : (list var)
1388     68/push 0/imm32/next
1389     51/push-ecx/var-foo
1390     89/<- %esi 4/r32/esp
1391     # stmt/esi : statement
1392     68/push 0/imm32/next
1393     56/push-esi/outputs
1394     68/push 0/imm32/inouts
1395     68/push "increment"/imm32/operation
1396     89/<- %esi 4/r32/esp
1397     # formal-var/ebx : var in any register
1398     68/push Any-register/imm32
1399     68/push 0/imm32/no-stack-offset
1400     68/push 1/imm32/block-depth
1401     68/push 1/imm32/type-int
1402     68/push "dummy"/imm32
1403     89/<- %ebx 4/r32/esp
1404     # operand/ebx : (list var)
1405     68/push 0/imm32/next
1406     53/push-ebx/formal-var
1407     89/<- %ebx 4/r32/esp
1408     # primitives/ebx : primitive
1409     68/push 0/imm32/next
1410     68/push 0/imm32/no-imm32
1411     68/push 0/imm32/no-r32
1412     68/push 3/imm32/rm32-in-first-output
1413     68/push "ff 0/subop/increment"/imm32/subx-name
1414     53/push-ebx/outputs
1415     68/push 0/imm32/inouts
1416     68/push "increment"/imm32/name
1417     89/<- %ebx 4/r32/esp
1418     # convert
1419     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1420     (flush _test-output-buffered-file)
1421 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1427     # check output
1428     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-primitive-register/0")
1429     # . reclaim locals
1430     81 0/subop/add %esp 0x48/imm32
1431     # . epilogue
1432     89/<- %esp 5/r32/ebp
1433     5d/pop-to-ebp
1434     c3/return
1435 
1436 test-emit-subx-statement-function-call:
1437     # Call a function on a variable on the stack.
1438     #   f foo
1439     # =>
1440     #   (f2 *(ebp-8))
1441     # (Changing the function name supports overloading in general, but here it
1442     # just serves to help disambiguate things.)
1443     #
1444     # There's a variable on the var stack as follows:
1445     #   name: 'foo'
1446     #   type: int
1447     #   stack-offset: -8
1448     #
1449     # There's nothing in primitives.
1450     #
1451     # There's a function with this info:
1452     #   name: 'f'
1453     #   inout: int/mem
1454     #   value: 'f2'
1455     #
1456     # . prologue
1457     55/push-ebp
1458     89/<- %ebp 4/r32/esp
1459     # setup
1460     (clear-stream _test-output-stream)
1461     (clear-stream _test-output-buffered-file->buffer)
1462     # var-foo/ecx : var
1463     68/push 0/imm32/no-register
1464     68/push -8/imm32/stack-offset
1465     68/push 0/imm32/block-depth
1466     68/push 1/imm32/type-int
1467     68/push "foo"/imm32
1468     89/<- %ecx 4/r32/esp
1469     # vars/edx = (stack 1)
1470     51/push-ecx/var-foo
1471     68/push 1/imm32/data-length
1472     68/push 1/imm32/top
1473     89/<- %edx 4/r32/esp
1474     # operands/esi : (list var)
1475     68/push 0/imm32/next
1476     51/push-ecx/var-foo
1477     89/<- %esi 4/r32/esp
1478     # stmt/esi : statement
1479     68/push 0/imm32/next
1480     68/push 0/imm32/outputs
1481     56/push-esi/inouts
1482     68/push "f"/imm32/operation
1483     89/<- %esi 4/r32/esp
1484     # functions/ebx : function
1485     68/push 0/imm32/next
1486     68/push 0/imm32/body
1487     68/push 0/imm32/outputs
1488     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
1489     68/push "f2"/imm32/subx-name
1490     68/push "f"/imm32/name
1491     89/<- %ebx 4/r32/esp
1492     # convert
1493     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
1494     (flush _test-output-buffered-file)
1495 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1501     # check output
1502     (check-next-stream-line-equal _test-output-stream "(f2 *(ebp+0xfffffff8))" "F - test-emit-subx-statement-function-call/0")
1503     # . reclaim locals
1504     81 0/subop/add %esp 0x3c/imm32
1505     # . epilogue
1506     89/<- %esp 5/r32/ebp
1507     5d/pop-to-ebp
1508     c3/return
1509 
1510 emit-subx-prologue:  # out : (address buffered-file)
1511     # . prologue
1512     55/push-ebp
1513     89/<- %ebp 4/r32/esp
1514     #
1515     (write-buffered *(ebp+8) "# . prologue\n")
1516     (write-buffered *(ebp+8) "55/push-ebp\n")
1517     (write-buffered *(ebp+8) "89/<- %ebp 4/r32/esp\n")
1518 $emit-subx-prologue:end:
1519     # . epilogue
1520     89/<- %esp 5/r32/ebp
1521     5d/pop-to-ebp
1522     c3/return
1523 
1524 emit-subx-epilogue:  # out : (address buffered-file)
1525     # . prologue
1526     55/push-ebp
1527     89/<- %ebp 4/r32/esp
1528     #
1529     (write-buffered *(ebp+8) "# . epilogue\n")
1530     (write-buffered *(ebp+8) "89/<- %esp 5/r32/ebp\n")
1531     (write-buffered *(ebp+8) "5d/pop-to-ebp\n")
1532     (write-buffered *(ebp+8) "c3/return\n")
1533 $emit-subx-epilogue:end:
1534     # . epilogue
1535     89/<- %esp 5/r32/ebp
1536     5d/pop-to-ebp
1537     c3/return