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. The translator should be
  11 # implementable in machine code.
  12 #   - minimize impedance mismatch between source language and SubX target
  13 #     (e.g. programmer manages registers manually)
  14 #   - checks over syntax
  15 #     (e.g. programmer's register allocation is checked)
  16 #   - runtime checks to avoid complex static analysis
  17 #     (e.g. array indexing always checks bounds)
  18 
  19 # == Language description
  20 # A program is a sequence of function definitions.
  21 #
  22 # Function example:
  23 #   fn foo n: int -> result/eax: int {
  24 #     ...
  25 #   }
  26 #
  27 # Functions consist of a name, optional inputs, optional outputs and a block.
  28 #
  29 # Function inputs and outputs are variables. All variables have a type and
  30 # storage specifier. They can be placed either in memory (on the stack) or in
  31 # one of 6 named registers.
  32 #   eax ecx edx ebx esi edi
  33 # Variables in registers must be primitive 32-bit types.
  34 # Variables not explicitly placed in a register are on the stack.
  35 # Variables in registers need not have a name; in that case you refer to them
  36 # directly by the register name.
  37 #
  38 # Function inputs are always passed in memory (on the stack), while outputs
  39 # are always returned in registers.
  40 #
  41 # Blocks mostly consist of statements.
  42 #
  43 # Statements mostly consist of a name, optional inputs and optional outputs.
  44 #
  45 # Statement inputs are variables or literals. Variables need to specify type
  46 # (and storage) the first time they're mentioned but not later.
  47 #
  48 # Statement outputs, like function outputs, must be variables in registers.
  49 #
  50 # Statement names must be either primitives or user-defined functions.
  51 #
  52 # Primitives can write to any register.
  53 # User-defined functions only write to hard-coded registers. Outputs of each
  54 # call must have the same registers as in the function definition.
  55 #
  56 # There are some other statement types:
  57 #   - blocks. Multiple statements surrounded by '{...}' and optionally
  58 #     prefixed with a label name and ':'
  59 #       - {
  60 #           ...
  61 #         }
  62 #       - foo: {
  63 #           ...
  64 #         }
  65 #
  66 #   - variable definitions on the stack. E.g.:
  67 #       - var foo: (ref int)
  68 #       - var bar: (ref array int 3)
  69 #     There's no initializer; variables are automatically initialized.
  70 #     The type of a local variable is either word-length (4 bytes) or starts with 'ref'.
  71 #
  72 #   - variables definitions in a register. E.g.:
  73 #       - var foo/eax : int <- add bar 1
  74 #     The initializer is mandatory and must be a valid instruction that writes
  75 #     a single output to the right register. In practice registers will
  76 #     usually be either initialized by primitives or copied from eax.
  77 #       - var eax : int <- foo bar quux
  78 #         var floo/ecx : int <- copy eax
  79 #
  80 # Still todo:
  81 #   global variables
  82 #   heap allocations (planned name: 'handle')
  83 #   user-defined types: 'type' for structs, 'choice' for unions
  84 #   short-lived 'address' type for efficiently writing inside nested structs
  85 #
  86 # We don't have 'handle' types yet, but we try to distinguish 'ref', 'handle'
  87 # and 'address' in comments. Their definitions are in layer 50, but really you
  88 # can ignore the distinctions on a first reading of this program.
  89 #
  90 # Formal types:
  91 #   A program is a linked list of functions
  92 #   A function contains:
  93 #     name: (handle array byte)
  94 #     inouts: linked list of vars  <-- 'inouts' is more precise than 'inputs'
  95 #       data: (handle var)
  96 #       next: (handle list)
  97 #     outputs: linked list of vars
  98 #       data: (handle var)
  99 #       next: (handle list)
 100 #     body: (handle block)
 101 #   A var-type contains:
 102 #     name: (handle array byte)
 103 #     type: (handle s-expression type-id)
 104 #
 105 #   A statement can be:
 106 #     tag 0: a block
 107 #     tag 1: a simple statement
 108 #     tag 2: a variable defined on the stack
 109 #     tag 3: a variable defined in a register
 110 #     tag 4: a named block
 111 #
 112 #   A block contains:
 113 #     tag: 0
 114 #     statements: (handle list statement)
 115 #
 116 #   A regular statement contains:
 117 #     tag: 1
 118 #     operation: (handle array byte)
 119 #     inouts: (handle list operand)
 120 #     outputs: (handle list var)
 121 #
 122 #   A variable defined on the stack contains:
 123 #     tag: 2
 124 #     name: (handle array byte)
 125 #     type: (handle s-expression type-id)
 126 #
 127 #   A variable defined in a register contains:
 128 #     tag: 3
 129 #     name: (handle array byte)
 130 #     type: (handle s-expression type-id)
 131 #     reg: (handle array byte)
 132 #
 133 #   A named block contains:
 134 #     tag: 4
 135 #     name: (handle array byte)
 136 #     statements: (handle list statement)
 137 
 138 # == Translation: managing the stack
 139 # Now that we know what the language looks like in the large, let's think
 140 # about how translation happens from the bottom up. One crucial piece of the
 141 # puzzle is how Mu will clean up variables defined on the stack for you.
 142 #
 143 # Assume that we maintain a 'functions' list while parsing source code. And a
 144 # 'primitives' list is a global constant. Both these contain enough information
 145 # to perform type-checking on function calls or primitive statements, respectively.
 146 #
 147 # Defining variables pushes them on a stack with the current block depth and
 148 # enough information about their location (stack offset or register).
 149 # Starting a block increments the current block id.
 150 # Each statement now has enough information to emit code for it.
 151 # Ending a block is where the magic happens:
 152 #   pop all variables at the current block depth
 153 #   emit code to restore all register variables introduced at the current depth
 154 #   emit code to clean up all stack variables at the current depth (just increment esp)
 155 #   decrement the current block depth
 156 #
 157 # Formal types:
 158 #   live-vars: stack of vars
 159 #   var:
 160 #     name: (handle array byte)
 161 #     type: s-expression? Just a type id for now.
 162 #     block: int
 163 #     stack-offset: int  (added to ebp)
 164 #     register: (handle array byte)
 165 #       either usual register names
 166 #       or '*' to indicate any register
 167 #   At most one of stack-offset or register-index must be non-zero.
 168 #   A register of '*' designates a variable _template_. Only legal in formal
 169 #   parameters for primitives.
 170 
 171 # == Translating a single function call
 172 # This one's easy. Assuming we've already checked things, we just drop the
 173 # outputs (which use hard-coded registers) and emit inputs in a standard format.
 174 #
 175 # out1, out2, out3, ... <- name inout1, inout2, inout3, ...
 176 # =>
 177 # (subx-name inout1 inout2 inout3)
 178 #
 179 # Formal types:
 180 #   functions: linked list of info
 181 #     name: (handle array byte)
 182 #     inouts: linked list of vars
 183 #     outputs: linked list of vars
 184 #     body: block (singleton linked list)
 185 #     subx-name: (handle array byte)
 186 
 187 # == Translating a single primitive instruction
 188 # A second crucial piece of the puzzle is how Mu converts fairly regular
 189 # primitives with their uniform syntax to SubX instructions with their gnarly
 190 # x86 details.
 191 #
 192 # Mu instructions have inputs and outputs. Primitives can have up to 2 of
 193 # them.
 194 # SubX instructions have rm32 and r32 operands.
 195 # The translation between them covers almost all the possibilities.
 196 #   Instructions with 1 inout may turn into ones with 1 rm32
 197 #     (e.g. incrementing a var on the stack)
 198 #   Instructions with 1 output may turn into ones with 1 rm32
 199 #     (e.g. incrementing a var in a register)
 200 #   1 inout and 1 output may turn into 1 rm32 and 1 r32
 201 #     (e.g. adding a var to a reg)
 202 #   2 inouts may turn into 1 rm32 and 1 r32
 203 #     (e.g. adding a reg to a var)
 204 #   1 inout and 1 literal may turn into 1 rm32 and 1 imm32
 205 #     (e.g. adding a constant to a var)
 206 #   1 output and 1 literal may turn into 1 rm32 and 1 imm32
 207 #     (e.g. adding a constant to a reg)
 208 #   2 outputs to hardcoded registers and 1 inout may turn into 1 rm32
 209 #     (special-case: divide edx:eax by a var or reg)
 210 # Observations:
 211 #   We always emit rm32. It may be the first inout or the first output.
 212 #   We may emit r32 or imm32 or neither.
 213 #   When we emit r32 it may come from first inout or second inout or first output.
 214 #
 215 # Accordingly, the formal data structure for a primitive looks like this:
 216 #   primitives: linked list of info
 217 #     name: (handle array byte)
 218 #     mu-inouts: linked list of vars to check
 219 #     mu-outputs: linked list of vars to check
 220 #     subx-name: (handle array byte)
 221 #     subx-rm32: enum arg-location
 222 #     subx-r32: enum arg-location
 223 #     subx-imm32: enum arg-location
 224 #   arg-location: enum
 225 #     0 means none
 226 #     1 means first inout
 227 #     2 means second inout
 228 #     3 means first output
 229 
 230 # == Translating a block
 231 # Emit block name if necessary
 232 # Emit '{'
 233 # When you encounter a statement, emit it as above
 234 # When you encounter a variable declaration
 235 #   emit any code needed for it (bzeros)
 236 #   push it on the var stack
 237 #   update register dict if necessary
 238 # When you encounter '}'
 239 #   While popping variables off the var stack until block id changes
 240 #     Emit code needed to clean up the stack
 241 #       either increment esp
 242 #       or pop into appropriate register
 243 
 244 # The rest is straightforward.
 245 
 246 == data
 247 
 248 Program:  # (handle function)
 249   0/imm32
 250 
 251 Function-name:
 252   0/imm32
 253 Function-subx-name:
 254   4/imm32
 255 Function-inouts:  # (handle list var)
 256   8/imm32
 257 Function-outputs:  # (handle list var)
 258   0xc/imm32
 259 Function-body:  # (handle block)
 260   0x10/imm32
 261 Function-next:  # (handle function)
 262   0x14/imm32
 263 Function-size:
 264   0x18/imm32/24
 265 
 266 Primitive-name:
 267   0/imm32
 268 Primitive-inouts:  # (handle list var)
 269   4/imm32
 270 Primitive-outputs:  # (handle list var)
 271   8/imm32
 272 Primitive-subx-name:  # (handle array byte)
 273   0xc/imm32
 274 Primitive-subx-rm32:  # enum arg-location
 275   0x10/imm32
 276 Primitive-subx-r32:  # enum arg-location
 277   0x14/imm32
 278 Primitive-subx-imm32:  # enum arg-location
 279   0x18/imm32
 280 Primitive-next:  # (handle function)
 281   0x1c/imm32
 282 Primitive-size:
 283   0x20/imm32/24
 284 
 285 Stmt-tag:
 286   0/imm32
 287 
 288 Block-statements:  # (handle list statement)
 289   4/imm32
 290 
 291 Stmt1-operation:  # (handle array byte)
 292   4/imm32
 293 Stmt1-inouts:  # (handle list var)
 294   8/imm32
 295 Stmt1-outputs:  # (handle list var)
 296   0xc/imm32
 297 
 298 Vardef-name:  # (handle array byte)
 299   4/imm32
 300 Vardef-type:  # (handle tree type-id)
 301   8/imm32
 302 
 303 Regvardef-name:  # (handle array byte)
 304   4/imm32
 305 Regvardef-type:  # (handle tree type-id)
 306   8/imm32
 307 Regvardef-register:  # (handle array byte)
 308   0xc/imm32
 309 
 310 Named-block-name:
 311   4/imm32
 312 Named-block-statements:  # (handle list statement)
 313   8/imm32
 314 
 315 Stmt-size:
 316   0x10/imm32
 317 
 318 Var-name:
 319   0/imm32
 320 Var-type:
 321   4/imm32
 322 Var-block:
 323   8/imm32
 324 Var-stack-offset:
 325   0xc/imm32
 326 Var-register:
 327   0x10/imm32
 328 Var-size:
 329   0x14/imm32
 330 
 331 Any-register:  # "*"
 332   # size
 333   1/imm32
 334   # data
 335   2a/asterisk
 336 
 337 List-value:
 338   0/imm32
 339 List-next:
 340   4/imm32
 341 List-size:
 342   8/imm32
 343 
 344 == code
 345 
 346 Entry:
 347     # . prologue
 348     89/<- %ebp 4/r32/esp
 349     (new-segment *Heap-size Heap)
 350     # if (argv[1] == "test') run-tests()
 351     {
 352       # if (argc <= 1) break
 353       81 7/subop/compare *ebp 1/imm32
 354       7e/jump-if-lesser-or-equal break/disp8
 355       # if (argv[1] != "test") break
 356       (kernel-string-equal? *(ebp+8) "test")  # => eax
 357       3d/compare-eax-and 0/imm32
 358       74/jump-if-equal break/disp8
 359       #
 360       (run-tests)
 361       # syscall(exit, *Num-test-failures)
 362       8b/-> *Num-test-failures 3/r32/ebx
 363       eb/jump $mu-main:end/disp8
 364     }
 365     # otherwise convert Stdin
 366     (convert-mu Stdin Stdout)
 367     (flush Stdout)
 368     # syscall(exit, 0)
 369     bb/copy-to-ebx 0/imm32
 370 $mu-main:end:
 371     b8/copy-to-eax 1/imm32/exit
 372     cd/syscall 0x80/imm8
 373 
 374 convert-mu:  # in : (address buffered-file), out : (address buffered-file)
 375     # . prologue
 376     55/push-ebp
 377     89/<- %ebp 4/r32/esp
 378     #
 379     (parse-mu *(ebp+8))
 380     (check-mu-types)
 381     (emit-subx *(ebp+0xc))
 382 $convert-mu:end:
 383     # . epilogue
 384     89/<- %esp 5/r32/ebp
 385     5d/pop-to-ebp
 386     c3/return
 387 
 388 test-convert-empty-input:
 389     # empty input => empty output
 390     # . prologue
 391     55/push-ebp
 392     89/<- %ebp 4/r32/esp
 393     # setup
 394     (clear-stream _test-input-stream)
 395     (clear-stream $_test-input-buffered-file->buffer)
 396     (clear-stream _test-output-stream)
 397     (clear-stream $_test-output-buffered-file->buffer)
 398     #
 399     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 400     (flush _test-output-buffered-file)
 401     (check-stream-equal _test-output-stream "" "F - test-convert-empty-input")
 402     # . epilogue
 403     89/<- %esp 5/r32/ebp
 404     5d/pop-to-ebp
 405     c3/return
 406 
 407 test-convert-function-skeleton:
 408     # empty function decl => function prologue and epilogue
 409     #   fn foo {
 410     #   }
 411     # =>
 412     #   foo:
 413     #     # . prologue
 414     #     55/push-ebp
 415     #     89/<- %ebp 4/r32/esp
 416     #     # . epilogue
 417     #     89/<- %esp 5/r32/ebp
 418     #     5d/pop-to-ebp
 419     #     c3/return
 420     # . prologue
 421     55/push-ebp
 422     89/<- %ebp 4/r32/esp
 423     # setup
 424     (clear-stream _test-input-stream)
 425     (clear-stream $_test-input-buffered-file->buffer)
 426     (clear-stream _test-output-stream)
 427     (clear-stream $_test-output-buffered-file->buffer)
 428     #
 429     (write _test-input-stream "fn foo {\n")
 430     (write _test-input-stream "}\n")
 431     # convert
 432     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 433     (flush _test-output-buffered-file)
 434 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 440     # check output
 441     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-skeleton/0")
 442     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-skeleton/1")
 443     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-skeleton/2")
 444     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-skeleton/3")
 445     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-skeleton/4")
 446     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-skeleton/5")
 447     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-skeleton/6")
 448     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-skeleton/7")
 449     # . epilogue
 450     89/<- %esp 5/r32/ebp
 451     5d/pop-to-ebp
 452     c3/return
 453 
 454 test-convert-multiple-function-skeletons:
 455     # multiple functions correctly organized into a linked list
 456     #   fn foo {
 457     #   }
 458     #   fn bar {
 459     #   }
 460     # =>
 461     #   foo:
 462     #     # . prologue
 463     #     55/push-ebp
 464     #     89/<- %ebp 4/r32/esp
 465     #     # . epilogue
 466     #     89/<- %esp 5/r32/ebp
 467     #     5d/pop-to-ebp
 468     #     c3/return
 469     #   bar:
 470     #     # . prologue
 471     #     55/push-ebp
 472     #     89/<- %ebp 4/r32/esp
 473     #     # . epilogue
 474     #     89/<- %esp 5/r32/ebp
 475     #     5d/pop-to-ebp
 476     #     c3/return
 477     # . prologue
 478     55/push-ebp
 479     89/<- %ebp 4/r32/esp
 480     # setup
 481     (clear-stream _test-input-stream)
 482     (clear-stream $_test-input-buffered-file->buffer)
 483     (clear-stream _test-output-stream)
 484     (clear-stream $_test-output-buffered-file->buffer)
 485     #
 486     (write _test-input-stream "fn foo {\n")
 487     (write _test-input-stream "}\n")
 488     (write _test-input-stream "fn bar {\n")
 489     (write _test-input-stream "}\n")
 490     # convert
 491     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 492     (flush _test-output-buffered-file)
 493 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 499     # check first function
 500     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-multiple-function-skeletons/0")
 501     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/1")
 502     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/2")
 503     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/3")
 504     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/4")
 505     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/5")
 506     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/6")
 507     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/7")
 508     # check second function
 509     (check-next-stream-line-equal _test-output-stream "bar:"                  "F - test-convert-multiple-function-skeletons/10")
 510     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/11")
 511     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/12")
 512     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/13")
 513     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/14")
 514     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/15")
 515     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/16")
 516     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/17")
 517     # . epilogue
 518     89/<- %esp 5/r32/ebp
 519     5d/pop-to-ebp
 520     c3/return
 521 
 522 test-convert-function-with-arg:
 523     # function with one arg
 524     #   fn foo n : int {
 525     #   }
 526     # =>
 527     #   foo:
 528     #     # . prologue
 529     #     55/push-ebp
 530     #     89/<- %ebp 4/r32/esp
 531     #     # . epilogue
 532     #     89/<- %esp 5/r32/ebp
 533     #     5d/pop-to-ebp
 534     #     c3/return
 535     # . prologue
 536     55/push-ebp
 537     89/<- %ebp 4/r32/esp
 538     # setup
 539     (clear-stream _test-input-stream)
 540     (clear-stream $_test-input-buffered-file->buffer)
 541     (clear-stream _test-output-stream)
 542     (clear-stream $_test-output-buffered-file->buffer)
 543     #
 544     (write _test-input-stream "fn foo n : int {\n")
 545     (write _test-input-stream "}\n")
 546     # convert
 547     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 548     (flush _test-output-buffered-file)
 549 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 555     # check output
 556     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-with-arg/0")
 557     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-with-arg/1")
 558     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-with-arg/2")
 559     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-arg/3")
 560     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-with-arg/4")
 561     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-arg/5")
 562     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-with-arg/6")
 563     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-with-arg/7")
 564     # . epilogue
 565     89/<- %esp 5/r32/ebp
 566     5d/pop-to-ebp
 567     c3/return
 568 
 569 test-convert-function-with-arg-and-body:
 570     # function with one arg and one instruction in the body
 571     #   fn foo n : int {
 572     #     increment n
 573     #   }
 574     # =>
 575     #   foo:
 576     #     # . prologue
 577     #     55/push-ebp
 578     #     89/<- %ebp 4/r32/esp
 579     #     {
 580     #       ff 0/subop/increment *(ebp+8)
 581     #     }
 582     #     # . epilogue
 583     #     89/<- %esp 5/r32/ebp
 584     #     5d/pop-to-ebp
 585     #     c3/return
 586     # . prologue
 587     55/push-ebp
 588     89/<- %ebp 4/r32/esp
 589     # setup
 590     (clear-stream _test-input-stream)
 591     (clear-stream $_test-input-buffered-file->buffer)
 592     (clear-stream _test-output-stream)
 593     (clear-stream $_test-output-buffered-file->buffer)
 594     #
 595     (write _test-input-stream "fn foo n : int {\n")
 596     (write _test-input-stream "  increment n\n")
 597     (write _test-input-stream "}\n")
 598     # convert
 599     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 600     (flush _test-output-buffered-file)
 601 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 607     # check output
 608     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-with-arg-and-body/0")
 609     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-with-arg-and-body/1")
 610     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-with-arg-and-body/2")
 611     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-arg-and-body/3")
 612     (check-next-stream-line-equal _test-output-stream "{"                     "F - test-convert-function-with-arg-and-body/4")
 613     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0x00000008)"  "F - test-convert-function-with-arg-and-body/5")
 614     (check-next-stream-line-equal _test-output-stream "}"                     "F - test-convert-function-with-arg-and-body/6")
 615     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-with-arg-and-body/7")
 616     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-arg-and-body/8")
 617     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-with-arg-and-body/9")
 618     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-with-arg-and-body/10")
 619     # . epilogue
 620     89/<- %esp 5/r32/ebp
 621     5d/pop-to-ebp
 622     c3/return
 623 
 624 #######################################################
 625 # Parsing
 626 #######################################################
 627 
 628 parse-mu:  # in : (address buffered-file)
 629     # pseudocode
 630     #   var curr-function : (handle function) = Program
 631     #   var line : (ref stream byte 512)
 632     #   var word-slice : (ref slice)
 633     #   while true                                  # line loop
 634     #     clear-stream(line)
 635     #     read-line-buffered(in, line)
 636     #     if (line->write == 0) break               # end of file
 637     #     word-slice = next-word-or-string(line)
 638     #     if slice-empty?(word-slice)               # end of line
 639     #       continue
 640     #     else if slice-starts-with?(word-slice, "#")  # comment
 641     #       continue                                # end of line
 642     #     else if slice-equal(word-slice, "fn")
 643     #       var new-function : (handle function) = new function
 644     #       populate-mu-function-header(in, new-function)
 645     #       populate-mu-function-body(in, new-function)
 646     #       *curr-function = new-function
 647     #       curr-function = &new-function->next
 648     #     else
 649     #       abort()
 650     #
 651     # . prologue
 652     55/push-ebp
 653     89/<- %ebp 4/r32/esp
 654     # . save registers
 655     50/push-eax
 656     51/push-ecx
 657     52/push-edx
 658     57/push-edi
 659     # var line/ecx : (ref stream byte 512)
 660     81 5/subop/subtract %esp 0x200/imm32
 661     68/push 0x200/imm32/length
 662     68/push 0/imm32/read
 663     68/push 0/imm32/write
 664     89/<- %ecx 4/r32/esp
 665     # var word-slice/edx : (ref slice)
 666     68/push 0/imm32/end
 667     68/push 0/imm32/start
 668     89/<- %edx 4/r32/esp
 669     # var curr-function/edi : (handle function) = Program
 670     bf/copy-to-edi Program/imm32
 671     {
 672 $parse-mu:line-loop:
 673       (clear-stream %ecx)
 674       (read-line-buffered *(ebp+8) %ecx)
 675       # if (line->write == 0) break
 676       81 7/subop/compare *ecx 0/imm32
 677       0f 84/jump-if-equal break/disp32
 678 +--  6 lines: #?       # dump line ---------------------------------------------------------------------------------------------------------------------------
 684       (next-word-or-string %ecx %edx)
 685       # if slice-empty?(word-slice) continue
 686       (slice-empty? %edx)
 687       3d/compare-eax-and 0/imm32
 688       0f 85/jump-if-not-equal loop/disp32
 689       # if (*word-slice->start == "#") continue
 690       # . eax = *word-slice->start
 691       8b/-> *edx 0/r32/eax
 692       8a/copy-byte *eax 0/r32/AL
 693       81 4/subop/and %eax 0xff/imm32
 694       # . if (eax == '#') continue
 695       3d/compare-eax-and 0x23/imm32/hash
 696       0f 84/jump-if-equal loop/disp32
 697       # if (slice-equal?(word-slice, "fn")) parse a function
 698       {
 699 $parse-mu:fn:
 700         (slice-equal? %edx "fn")
 701         3d/compare-eax-and 0/imm32
 702         0f 84/jump-if-equal break/disp32
 703         # var new-function/eax : (handle function) = populate-mu-function()
 704         (allocate Heap *Function-size)  # => eax
 705         (populate-mu-function-header %ecx %eax)
 706         (populate-mu-function-body *(ebp+8) %eax)
 707         # *curr-function = new-function
 708         89/<- *edi 0/r32/eax
 709         # curr-function = &new-function->next
 710         8d/address-> *(eax+0x14) 7/r32/edi  # Function-next
 711         e9/jump $parse-mu:line-loop/disp32
 712       }
 713       # otherwise abort
 714       e9/jump $parse-mu:abort/disp32
 715     } # end line loop
 716 $parse-mu:end:
 717     # . reclaim locals
 718     81 0/subop/add %esp 0x214/imm32
 719     # . restore registers
 720     5f/pop-to-edi
 721     5a/pop-to-edx
 722     59/pop-to-ecx
 723     58/pop-to-eax
 724     # . epilogue
 725     89/<- %esp 5/r32/ebp
 726     5d/pop-to-ebp
 727     c3/return
 728 
 729 $parse-mu:abort:
 730     # error("unexpected top-level command: " word-slice "\n")
 731     (write-buffered Stderr "unexpected top-level command: ")
 732     (write-slice-buffered Stderr %edx)
 733     (write-buffered Stderr "\n")
 734     (flush Stderr)
 735     # . syscall(exit, 1)
 736     bb/copy-to-ebx  1/imm32
 737     b8/copy-to-eax  1/imm32/exit
 738     cd/syscall  0x80/imm8
 739     # never gets here
 740 
 741 # scenarios considered:
 742 # ✗ fn foo  # no block
 743 # ✓ fn foo {
 744 # ✗ fn foo { {
 745 # ✗ fn foo { }
 746 # ✗ fn foo { } {
 747 # ✗ fn foo x {
 748 # ✗ fn foo x : {
 749 # ✓ fn foo x : int {
 750 # ✓ fn foo x: int {
 751 # ✓ fn foo x: int -> y/eax: int {
 752 populate-mu-function-header:  # first-line : (address stream byte), out : (handle function)
 753     # pseudocode:
 754     #   var name : (ref slice)
 755     #   next-word(first-line, name)
 756     #   assert(name not in '{' '}' '->')
 757     #   out->name = slice-to-string(name)
 758     #   ## inouts
 759     #   while true
 760     #     ## name
 761     #     name = next-word(first-line)
 762     #     if (name == '{') goto done
 763     #     if (name == '->') break
 764     #     assert(name != '}')
 765     #     var v : (handle var) = parse-var-with-type(name, first-line)
 766     #     out->inouts = append(out->inouts, v)
 767     #   ## outputs
 768     #   while true
 769     #     ## name
 770     #     name = next-word(first-line)
 771     #     assert(name not in '{' '}' '->')
 772     #     var v : (handle var) = parse-var-with-type(name, first-line)
 773     #     out->outputs = append(out->outputs, v)
 774     #   done:
 775     #
 776     # . prologue
 777     55/push-ebp
 778     89/<- %ebp 4/r32/esp
 779     # . save registers
 780     50/push-eax
 781     51/push-ecx
 782     57/push-edi
 783     # edi = out
 784     8b/-> *(ebp+0xc) 7/r32/edi
 785     # var word-slice/ecx : (ref slice)
 786     68/push 0/imm32/end
 787     68/push 0/imm32/start
 788     89/<- %ecx 4/r32/esp
 789     # read function name
 790     (next-word *(ebp+8) %ecx)
 791     # error checking
 792     # if (word-slice == '{') abort
 793     (slice-equal? %ecx "{")   # => eax
 794     3d/compare-eax-and 0/imm32
 795     0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32
 796     # if (word-slice == '->') abort
 797     (slice-equal? %ecx "->")   # => eax
 798     3d/compare-eax-and 0/imm32
 799     0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32
 800     # if (word-slice == '}') abort
 801     (slice-equal? %ecx "}")   # => eax
 802     3d/compare-eax-and 0/imm32
 803     0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32
 804     # save function name
 805     (slice-to-string Heap %ecx)  # => eax
 806     89/<- *edi 0/r32/eax  # Function-name
 807     # save function inouts
 808     {
 809       (next-word *(ebp+8) %ecx)
 810       # if (word-slice == '{') goto done
 811       (slice-equal? %ecx "{")   # => eax
 812       3d/compare-eax-and 0/imm32
 813       0f 85/jump-if-not-equal $populate-mu-function-header:done/disp32
 814       # if (word-slice == '->') break
 815       (slice-equal? %ecx "->")   # => eax
 816       3d/compare-eax-and 0/imm32
 817       75/jump-if-not-equal break/disp8
 818       # if (word-slice == '}') abort
 819       (slice-equal? %ecx "}")   # => eax
 820       3d/compare-eax-and 0/imm32
 821       0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32
 822       #
 823       (parse-var-with-type %ecx *(ebp+8))
 824       (append-list Heap %eax *(edi+8))  # Function-inouts => eax
 825       89/<- *(edi+8) 0/r32/eax  # Function-inouts
 826       e9/jump loop/disp32
 827     }
 828     # save function outputs
 829     {
 830       (next-word *(ebp+8) %ecx)
 831       # if (word-slice == '{') break
 832       (slice-equal? %ecx "{")   # => eax
 833       3d/compare-eax-and 0/imm32
 834       75/jump-if-not-equal break/disp8
 835       # if (word-slice == '->') abort
 836       (slice-equal? %ecx "->")   # => eax
 837       3d/compare-eax-and 0/imm32
 838       0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32
 839       # if (word-slice == '}') abort
 840       (slice-equal? %ecx "}")   # => eax
 841       3d/compare-eax-and 0/imm32
 842       0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32
 843       #
 844       (parse-var-with-type %ecx *(ebp+8))
 845       (append-list Heap %eax *(edi+0xc))  # Function-outputs
 846       89/<- *(edi+0xc) 0/r32/eax  # Function-outputs
 847       e9/jump loop/disp32
 848     }
 849 $populate-mu-function-header:done:
 850     (check-no-tokens-left *(ebp+8))
 851 $populate-mu-function-header:end:
 852     # . reclaim locals
 853     81 0/subop/add %esp 8/imm32
 854     # . restore registers
 855     5f/pop-to-edi
 856     59/pop-to-ecx
 857     58/pop-to-eax
 858     # . epilogue
 859     89/<- %esp 5/r32/ebp
 860     5d/pop-to-ebp
 861     c3/return
 862 
 863 $populate-mu-function-header:abort:
 864     # error("function header not in form 'fn <name> {'")
 865     (write-buffered Stderr "function header not in form 'fn <name> [inouts] [-> outputs] {' -- '")
 866     (flush Stderr)
 867     (rewind-stream *(ebp+8))
 868     (write-stream 2 *(ebp+8))
 869     (write-buffered Stderr "'\n")
 870     (flush Stderr)
 871     # . syscall(exit, 1)
 872     bb/copy-to-ebx  1/imm32
 873     b8/copy-to-eax  1/imm32/exit
 874     cd/syscall  0x80/imm8
 875     # never gets here
 876 
 877 test-function-header-with-arg:
 878     # 'foo n : int {'
 879     # . prologue
 880     55/push-ebp
 881     89/<- %ebp 4/r32/esp
 882     # setup
 883     (clear-stream _test-input-stream)
 884     (write _test-input-stream "foo n : int {\n")
 885     # result/ecx : (ref function)
 886     2b/subtract-> *Function-size 4/r32/esp
 887     89/<- %ecx 4/r32/esp
 888     (zero-out %ecx *Function-size)
 889     # convert
 890     (populate-mu-function-header _test-input-stream %ecx)
 891     # check result
 892     (check-strings-equal *ecx "foo" "F - test-function-header-with-arg/name")  # Function-name
 893     # edx : (handle list var) = result->inouts
 894     8b/-> *(ecx+8) 2/r32/edx  # Function-inouts
 895     # ebx : (handle var) = result->inouts->value
 896     8b/-> *edx 3/r32/ebx  # List-value
 897     (check-strings-equal *ebx "n" "F - test-function-header-with-arg/inout:0")  # Var-name
 898     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:0/type")  # Var-type
 899     # . epilogue
 900     89/<- %esp 5/r32/ebp
 901     5d/pop-to-ebp
 902     c3/return
 903 
 904 test-function-header-with-multiple-args:
 905     # 'fn foo a: int, b: int, c: int {'
 906     # . prologue
 907     55/push-ebp
 908     89/<- %ebp 4/r32/esp
 909     # setup
 910     (clear-stream _test-input-stream)
 911     (write _test-input-stream "foo a: int, b: int c: int {\n")
 912     # result/ecx : (handle function)
 913     2b/subtract-> *Function-size 4/r32/esp
 914     89/<- %ecx 4/r32/esp
 915     (zero-out %ecx *Function-size)
 916     # convert
 917     (populate-mu-function-header _test-input-stream %ecx)
 918     # check result
 919     (check-strings-equal *ecx "foo")  # Function-name
 920     # edx : (handle list var) = result->inouts
 921     8b/-> *(ecx+8) 2/r32/edx  # Function-inouts
 922     # ebx : (handle var) = result->inouts->value
 923     8b/-> *edx 3/r32/ebx  # List-value
 924     (check-strings-equal *ebx "a" "F - test-function-header-with-multiple-args/inout:0")  # Var-name
 925     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:0/type")  # Var-type
 926     # edx = result->inouts->next
 927     8b/-> *(edx+4) 2/r32/edx  # List-next
 928     # ebx = result->inouts->next->value
 929     8b/-> *edx 3/r32/ebx  # List-value
 930     (check-strings-equal *ebx "b" "F - test-function-header-with-multiple-args/inout:1")  # Var-name
 931     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:1/type")  # Var-type
 932     # edx = result->inouts->next->next
 933     8b/-> *(edx+4) 2/r32/edx  # List-next
 934     # ebx = result->inouts->next->next->value
 935     8b/-> *edx 3/r32/ebx  # List-value
 936     (check-strings-equal *ebx "c" "F - test-function-header-with-multiple-args/inout:2")  # Var-name
 937     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:2/type")  # Var-type
 938     # . epilogue
 939     89/<- %esp 5/r32/ebp
 940     5d/pop-to-ebp
 941     c3/return
 942 
 943 test-function-with-multiple-args-and-outputs:
 944     # fn foo a: int, b: int, c: int -> x: int, y: int {
 945     # . prologue
 946     55/push-ebp
 947     89/<- %ebp 4/r32/esp
 948     # setup
 949     (clear-stream _test-input-stream)
 950     (write _test-input-stream "foo a: int, b: int, c: int -> x/ecx: int y/edx : int {\n")
 951     # result/ecx : (handle function)
 952     2b/subtract-> *Function-size 4/r32/esp
 953     89/<- %ecx 4/r32/esp
 954     (zero-out %ecx *Function-size)
 955     # convert
 956     (populate-mu-function-header _test-input-stream %ecx)
 957     # check result
 958     (check-strings-equal *ecx "foo")  # Function-name
 959     # edx : (handle list var) = result->inouts
 960     8b/-> *(ecx+8) 2/r32/edx  # Function-inouts
 961     # ebx : (handle var) = result->inouts->value
 962     8b/-> *edx 3/r32/ebx  # List-value
 963     (check-strings-equal *ebx "a" "F - test-function-header-with-multiple-args/inout:0")  # Var-name
 964     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:0/type")  # Var-type
 965     # edx = result->inouts->next
 966     8b/-> *(edx+4) 2/r32/edx  # List-next
 967     # ebx = result->inouts->next->value
 968     8b/-> *edx 3/r32/ebx  # List-value
 969     (check-strings-equal *ebx "b" "F - test-function-header-with-multiple-args/inout:1")  # Var-name
 970     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:1/type")  # Var-type
 971     # edx = result->inouts->next->next
 972     8b/-> *(edx+4) 2/r32/edx  # List-next
 973     # ebx = result->inouts->next->next->value
 974     8b/-> *edx 3/r32/ebx  # List-value
 975     (check-strings-equal *ebx "c" "F - test-function-header-with-multiple-args/inout:2")  # Var-name
 976     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:2/type")  # Var-type
 977     # edx : (handle list var) = result->outputs
 978     8b/-> *(ecx+0xc) 2/r32/edx  # Function-outputs
 979     # ebx : (handle var) = result->outputs->value
 980     8b/-> *edx 3/r32/ebx  # List-value
 981     (check-strings-equal *ebx "x" "F - test-function-header-with-multiple-args/output:0")  # Var-name
 982     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/output:0/type")  # Var-type
 983     (check-strings-equal *(ebx+0x10) "ecx" "F - test-function-header-with-arg/output:0/register")  # Var-register
 984     # edx = result->outputs->next
 985     8b/-> *(edx+4) 2/r32/edx  # List-next
 986     # ebx = result->outputs->next->value
 987     8b/-> *edx 3/r32/ebx  # List-value
 988     (check-strings-equal *ebx "y" "F - test-function-header-with-multiple-args/output:1")  # Var-name
 989     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/output:1/type")  # Var-type
 990     (check-strings-equal *(ebx+0x10) "edx" "F - test-function-header-with-arg/output:0/register")  # Var-register
 991     # . epilogue
 992     89/<- %esp 5/r32/ebp
 993     5d/pop-to-ebp
 994     c3/return
 995 
 996 # format for variables with types
 997 #   x : int
 998 #   x: int
 999 #   x: int,
1000 # ignores at most one trailing colon or comma
1001 parse-var-with-type:  # name: (address slice), first-line: (address stream byte) -> result/eax: (handle var)
1002     # pseudocode:
1003     #   var v : (handle var) = allocate(Heap, Var-size)
1004     #   var s : (ref slice)
1005     #   next-token-from-slice(name->start, name->end, '/', s)
1006     #   var end : (address byte) = s->end
1007     #   if (slice-ends-with(s, ":"))
1008     #     decrement s->end
1009     #   if (slice-ends-with(s, ","))
1010     #     decrement s->end
1011     #   v->name = slice-to-string(s)
1012     #   ## register
1013     #   next-token-from-slice(end, name->end, '/', s)
1014     #   if (slice-ends-with(s, ":"))
1015     #     decrement s->end
1016     #   if (slice-ends-with(s, ","))
1017     #     decrement s->end
1018     #   if (!slice-empty?(s))
1019     #     v->register = slice-to-string(s)
1020     #   ## type
1021     #   s = next-mu-token(first-line)
1022     #   assert(s not in '{' '}' '->')
1023     #   if (slice-empty?(s)) {
1024     #     s = next-mu-token(first-line)
1025     #     assert(type not in '{' '}' '->')
1026     #   }
1027     #   type = type-for(s)
1028     #   v->type = type
1029     #   return v
1030     #
1031     # . prologue
1032     55/push-ebp
1033     89/<- %ebp 4/r32/esp
1034     # . save registers
1035     51/push-ecx
1036     52/push-edx
1037     53/push-ebx
1038     56/push-esi
1039     57/push-edi
1040     # var result/edi : (handle var) = allocate(Heap, Var-size)
1041     (allocate Heap *Var-size)
1042     89/<- %edi 0/r32/eax
1043     # esi = name
1044     8b/-> *(ebp+8) 6/r32/esi
1045     # var s/ecx : (ref slice)
1046     68/push 0/imm32/end
1047     68/push 0/imm32/start
1048     89/<- %ecx 4/r32/esp
1049     # save v->name
1050     (next-token-from-slice *esi *(esi+4) 0x2f %ecx)  # Slice-start, Slice-end, '/'
1051     # . end/edx = s->end
1052     8b/-> *(ecx+4) 2/r32/edx
1053     # . if s ends with ':', decrement s->end
1054     {
1055       8b/-> *(ecx+4) 0/r32/eax
1056       48/decrement-eax
1057       8a/copy-byte *eax 3/r32/BL
1058       81 4/subop/and %ebx 0xff/imm32
1059       81 7/subop/compare %ebx 0x3a/imm32/colon
1060       75/jump-if-not-equal break/disp8
1061       89/<- *(ecx+4) 0/r32/eax
1062     }
1063     # . if s ends with ',', decrement s->end
1064     {
1065       8b/-> *(ecx+4) 0/r32/eax
1066       48/decrement-eax
1067       8a/copy-byte *eax 3/r32/BL
1068       81 4/subop/and %ebx 0xff/imm32
1069       81 7/subop/compare %ebx 0x2c/imm32/comma
1070       75/jump-if-not-equal break/disp8
1071       89/<- *(ecx+4) 0/r32/eax
1072     }
1073     (slice-to-string Heap %ecx)  # => eax
1074     89/<- *edi 0/r32/eax  # Var-name
1075     # save v->register
1076     (next-token-from-slice %edx *(esi+4) 0x2f %ecx)  # end, name->end, '/'
1077     # . if s ends with ':', decrement s->end
1078     {
1079       8b/-> *(ecx+4) 0/r32/eax
1080       48/decrement-eax
1081       8a/copy-byte *eax 3/r32/BL
1082       81 4/subop/and %ebx 0xff/imm32
1083       81 7/subop/compare %ebx 0x3a/imm32/colon
1084       75/jump-if-not-equal break/disp8
1085       89/<- *(ecx+4) 0/r32/eax
1086     }
1087     # . if s ends with ',', decrement s->end
1088     {
1089       8b/-> *(ecx+4) 0/r32/eax
1090       48/decrement-eax
1091       8a/copy-byte *eax 3/r32/BL
1092       81 4/subop/and %ebx 0xff/imm32
1093       81 7/subop/compare %ebx 0x2c/imm32/comma
1094       75/jump-if-not-equal break/disp8
1095       89/<- *(ecx+4) 0/r32/eax
1096     }
1097     # if (!slice-empty?(s)) v->register = slice-to-string(s)
1098     {
1099       (slice-empty? %ecx)
1100       3d/compare-eax-and 0/imm32
1101       75/jump-if-not-equal break/disp8
1102       (slice-to-string Heap %ecx)
1103       89/<- *(edi+0x10) 0/r32/eax  # Var-register
1104     }
1105     # save v->type
1106     (next-mu-token *(ebp+0xc) %ecx)
1107     # if (word-slice == '{') abort
1108     (slice-equal? %ecx "{")   # => eax
1109     3d/compare-eax-and 0/imm32
1110     0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1111     # if (word-slice == '->') abort
1112     (slice-equal? %ecx "->")   # => eax
1113     3d/compare-eax-and 0/imm32
1114     0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1115     # if (word-slice == '}') abort
1116     (slice-equal? %ecx "}")   # => eax
1117     3d/compare-eax-and 0/imm32
1118     0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1119     # if (slice-empty?(type)) skip
1120     (slice-empty? %ecx)
1121     {
1122       3d/compare-eax-and 0/imm32
1123       0f 84/jump-if-equal break/disp32
1124       (next-mu-token *(ebp+0xc) %ecx)
1125       # if (word-slice == '{') abort
1126       (slice-equal? %ecx "{")   # => eax
1127       3d/compare-eax-and 0/imm32
1128       0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1129       # if (word-slice == '->') abort
1130       (slice-equal? %ecx "->")   # => eax
1131       3d/compare-eax-and 0/imm32
1132       0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1133       # if (word-slice == '}') abort
1134       (slice-equal? %ecx "}")   # => eax
1135       3d/compare-eax-and 0/imm32
1136       0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1137     }
1138     (type-for %ecx)
1139     89/<- *(edi+4) 0/r32/eax  # Var-type
1140 $parse-var-with-type:end:
1141     # return result
1142     89/<- %eax 7/r32/edi
1143     # . reclaim locals
1144     81 0/subop/add %esp 8/imm32
1145     # . restore registers
1146     5f/pop-to-edi
1147     5e/pop-to-esi
1148     5b/pop-to-ebx
1149     5a/pop-to-edx
1150     59/pop-to-ecx
1151     # . epilogue
1152     89/<- %esp 5/r32/ebp
1153     5d/pop-to-ebp
1154     c3/return
1155 
1156 $parse-var-with-type:abort:
1157     # error("function header not in form 'fn <name> {'")
1158     (write-buffered Stderr "var should have form 'name: type' in '")
1159     (flush Stderr)
1160     (rewind-stream *(ebp+0xc))
1161     (write-stream 2 *(ebp+0xc))
1162     (write-buffered Stderr "'\n")
1163     (flush Stderr)
1164     # . syscall(exit, 1)
1165     bb/copy-to-ebx  1/imm32
1166     b8/copy-to-eax  1/imm32/exit
1167     cd/syscall  0x80/imm8
1168     # never gets here
1169 
1170 next-mu-token:  # in: (address stream byte), out: (address slice)
1171     # . prologue
1172     55/push-ebp
1173     89/<- %ebp 4/r32/esp
1174     # . save registers
1175     50/push-eax
1176     57/push-edi
1177     # edi = out
1178     8b/-> *(ebp+0xc) 7/r32/edi
1179     #
1180     (next-word *(ebp+8) %edi)  # TODO: support s-expressions
1181     # if out ends with ':', decrement out->end
1182     {
1183       8b/-> *(edi+4) 0/r32/eax
1184       48/decrement-eax
1185       8a/copy-byte *eax 3/r32/BL
1186       81 4/subop/and %ebx 0xff/imm32
1187       81 7/subop/compare %ebx 0x3a/imm32/colon
1188       75/jump-if-not-equal break/disp8
1189       89/<- *(edi+4) 0/r32/eax
1190     }
1191     # if out ends with ',', decrement out->end
1192     {
1193       8b/-> *(edi+4) 0/r32/eax
1194       48/decrement-eax
1195       8a/copy-byte *eax 3/r32/BL
1196       81 4/subop/and %ebx 0xff/imm32
1197       81 7/subop/compare %ebx 0x2c/imm32/comma
1198       75/jump-if-not-equal break/disp8
1199       89/<- *(edi+4) 0/r32/eax
1200     }
1201 $next-mu-token:end:
1202     b8/copy-to-eax 1/imm32/int
1203     # . restore registers
1204     5f/pop-to-edi
1205     58/pop-to-eax
1206     # . epilogue
1207     89/<- %esp 5/r32/ebp
1208     5d/pop-to-ebp
1209     c3/return
1210 
1211 type-for:  # name: (address slice) -> result/eax: (handle s-expression type-id)
1212     # . prologue
1213     55/push-ebp
1214     89/<- %ebp 4/r32/esp
1215     # . save registers
1216 #?     (write-buffered Stderr "type: ")
1217 #?     (write-slice-buffered Stderr *(ebp+8))
1218 #?     (write-buffered Stderr Newline)
1219 #?     (flush Stderr)
1220 $type-for:end:
1221     b8/copy-to-eax 1/imm32/int
1222     # . restore registers
1223     # . epilogue
1224     89/<- %esp 5/r32/ebp
1225     5d/pop-to-ebp
1226     c3/return
1227 
1228 test-parse-var-with-type:
1229     # . prologue
1230     55/push-ebp
1231     89/<- %ebp 4/r32/esp
1232     # (eax..ecx) = "x:"
1233     b8/copy-to-eax "x:"/imm32
1234     8b/-> *eax 1/r32/ecx
1235     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1236     05/add-to-eax 4/imm32
1237     # var slice/ecx : (ref slice) = {eax, ecx}
1238     51/push-ecx
1239     50/push-eax
1240     89/<- %ecx 4/r32/esp
1241     # _test-input-stream contains "int"
1242     (clear-stream _test-input-stream)
1243     (write _test-input-stream "int")
1244     #
1245     (parse-var-with-type %ecx _test-input-stream)
1246     8b/-> *eax 2/r32/edx  # Var-name
1247     (check-strings-equal %edx "x" "F - test-var-with-type/name")
1248     8b/-> *(eax+4) 2/r32/edx  # Var-type
1249     (check-ints-equal %edx 1 "F - test-var-with-type/type")
1250     # . epilogue
1251     89/<- %esp 5/r32/ebp
1252     5d/pop-to-ebp
1253     c3/return
1254 
1255 test-parse-var-with-type-and-register:
1256     # . prologue
1257     55/push-ebp
1258     89/<- %ebp 4/r32/esp
1259     # (eax..ecx) = "x/eax"
1260     b8/copy-to-eax "x/eax"/imm32
1261     8b/-> *eax 1/r32/ecx
1262     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1263     05/add-to-eax 4/imm32
1264     # var slice/ecx : (ref slice) = {eax, ecx}
1265     51/push-ecx
1266     50/push-eax
1267     89/<- %ecx 4/r32/esp
1268     # _test-input-stream contains ": int"
1269     (clear-stream _test-input-stream)
1270     (write _test-input-stream ": int")
1271     #
1272     (parse-var-with-type %ecx _test-input-stream)
1273     8b/-> *eax 2/r32/edx  # Var-name
1274     (check-strings-equal %edx "x" "F - test-var-with-type-and-register/name")
1275     8b/-> *(eax+0x10) 2/r32/edx  # Var-register
1276     (check-strings-equal %edx "eax" "F - test-var-with-type-and-register/register")
1277     8b/-> *(eax+4) 2/r32/edx  # Var-type
1278     (check-ints-equal %edx 1 "F - test-var-with-type-and-register/type")
1279     # . epilogue
1280     89/<- %esp 5/r32/ebp
1281     5d/pop-to-ebp
1282     c3/return
1283 
1284 test-parse-var-with-trailing-characters:
1285     # . prologue
1286     55/push-ebp
1287     89/<- %ebp 4/r32/esp
1288     # (eax..ecx) = "x/eax:"
1289     b8/copy-to-eax "x/eax:"/imm32
1290     8b/-> *eax 1/r32/ecx
1291     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1292     05/add-to-eax 4/imm32
1293     # var slice/ecx : (ref slice) = {eax, ecx}
1294     51/push-ecx
1295     50/push-eax
1296     89/<- %ecx 4/r32/esp
1297     # _test-input-stream contains "int,"
1298     (clear-stream _test-input-stream)
1299     (write _test-input-stream "int,")
1300     #
1301     (parse-var-with-type %ecx _test-input-stream)
1302     8b/-> *eax 2/r32/edx  # Var-name
1303     (check-strings-equal %edx "x" "F - test-var-with-trailing-characters/name")
1304     8b/-> *(eax+0x10) 2/r32/edx  # Var-register
1305     (check-strings-equal %edx "eax" "F - test-var-with-trailing-characters/register")
1306     8b/-> *(eax+4) 2/r32/edx  # Var-type
1307     (check-ints-equal %edx 1 "F - test-var-with-trailing-characters/type")
1308     # . epilogue
1309     89/<- %esp 5/r32/ebp
1310     5d/pop-to-ebp
1311     c3/return
1312 
1313 # identifier starts with a letter or '$' or '_'
1314 # no constraints at the moment on later letters
1315 # all we really want to do so far is exclude '{', '}' and '->'
1316 is-identifier?:  # in : (address slice) -> result/eax : boolean
1317     # . prologue
1318     55/push-ebp
1319     89/<- %ebp 4/r32/esp
1320     # if (slice-empty?(in)) return false
1321     (slice-empty? *(ebp+8))  # => eax
1322     3d/compare-eax-and 0/imm32
1323     75/jump-if-not-equal $is-identifier?:false/disp8
1324     # var c/eax : byte = *in->start
1325     8b/-> *(ebp+8) 0/r32/eax
1326     8b/-> *eax 0/r32/eax
1327     8a/copy-byte *eax 0/r32/AL
1328     81 4/subop/and %eax 0xff/imm32
1329     # if (c == '$') return true
1330     3d/compare-eax-and 0x24/imm32/$
1331     74/jump-if-equal $is-identifier?:true/disp8
1332     # if (c == '_') return true
1333     3d/compare-eax-and 0x5f/imm32/_
1334     74/jump-if-equal $is-identifier?:true/disp8
1335     # drop case
1336     25/and-eax-with 0x5f/imm32
1337     # if (c < 'A') return false
1338     3d/compare-eax-and 0x41/imm32/A
1339     7c/jump-if-lesser $is-identifier?:false/disp8
1340     # if (c > 'Z') return false
1341     3d/compare-eax-and 0x5a/imm32/Z
1342     7f/jump-if-greater $is-identifier?:false/disp8
1343     # otherwise return true
1344 $is-identifier?:true:
1345     b8/copy-to-eax 1/imm32/true
1346     eb/jump $is-identifier?:end/disp8
1347 $is-identifier?:false:
1348     b8/copy-to-eax 0/imm32/false
1349 $is-identifier?:end:
1350     # . epilogue
1351     89/<- %esp 5/r32/ebp
1352     5d/pop-to-ebp
1353     c3/return
1354 
1355 test-is-identifier-dollar:
1356     # . prologue
1357     55/push-ebp
1358     89/<- %ebp 4/r32/esp
1359     # (eax..ecx) = "$a"
1360     b8/copy-to-eax "$a"/imm32
1361     8b/-> *eax 1/r32/ecx
1362     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1363     05/add-to-eax 4/imm32
1364     # var slice/ecx : (ref slice) = {eax, ecx}
1365     51/push-ecx
1366     50/push-eax
1367     89/<- %ecx 4/r32/esp
1368     #
1369     (is-identifier? %ecx)
1370     (check-ints-equal %eax 1 "F - test-is-identifier-dollar")
1371     # . epilogue
1372     89/<- %esp 5/r32/ebp
1373     5d/pop-to-ebp
1374     c3/return
1375 
1376 test-is-identifier-underscore:
1377     # . prologue
1378     55/push-ebp
1379     89/<- %ebp 4/r32/esp
1380     # (eax..ecx) = "_a"
1381     b8/copy-to-eax "_a"/imm32
1382     8b/-> *eax 1/r32/ecx
1383     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1384     05/add-to-eax 4/imm32
1385     # var slice/ecx : (ref slice) = {eax, ecx}
1386     51/push-ecx
1387     50/push-eax
1388     89/<- %ecx 4/r32/esp
1389     #
1390     (is-identifier? %ecx)
1391     (check-ints-equal %eax 1 "F - test-is-identifier-underscore")
1392     # . epilogue
1393     89/<- %esp 5/r32/ebp
1394     5d/pop-to-ebp
1395     c3/return
1396 
1397 test-is-identifier-a:
1398     # . prologue
1399     55/push-ebp
1400     89/<- %ebp 4/r32/esp
1401     # (eax..ecx) = "a$"
1402     b8/copy-to-eax "a$"/imm32
1403     8b/-> *eax 1/r32/ecx
1404     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1405     05/add-to-eax 4/imm32
1406     # var slice/ecx : (ref slice) = {eax, ecx}
1407     51/push-ecx
1408     50/push-eax
1409     89/<- %ecx 4/r32/esp
1410     #
1411     (is-identifier? %ecx)
1412     (check-ints-equal %eax 1 "F - test-is-identifier-a")
1413     # . epilogue
1414     89/<- %esp 5/r32/ebp
1415     5d/pop-to-ebp
1416     c3/return
1417 
1418 test-is-identifier-z:
1419     # . prologue
1420     55/push-ebp
1421     89/<- %ebp 4/r32/esp
1422     # (eax..ecx) = "z$"
1423     b8/copy-to-eax "z$"/imm32
1424     8b/-> *eax 1/r32/ecx
1425     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1426     05/add-to-eax 4/imm32
1427     # var slice/ecx : (ref slice) = {eax, ecx}
1428     51/push-ecx
1429     50/push-eax
1430     89/<- %ecx 4/r32/esp
1431     #
1432     (is-identifier? %ecx)
1433     (check-ints-equal %eax 1 "F - test-is-identifier-z")
1434     # . epilogue
1435     89/<- %esp 5/r32/ebp
1436     5d/pop-to-ebp
1437     c3/return
1438 
1439 test-is-identifier-A:
1440     # . prologue
1441     55/push-ebp
1442     89/<- %ebp 4/r32/esp
1443     # (eax..ecx) = "A$"
1444     b8/copy-to-eax "A$"/imm32
1445     8b/-> *eax 1/r32/ecx
1446     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1447     05/add-to-eax 4/imm32
1448     # var slice/ecx : (ref slice) = {eax, ecx}
1449     51/push-ecx
1450     50/push-eax
1451     89/<- %ecx 4/r32/esp
1452     #
1453     (is-identifier? %ecx)
1454     (check-ints-equal %eax 1 "F - test-is-identifier-A")
1455     # . epilogue
1456     89/<- %esp 5/r32/ebp
1457     5d/pop-to-ebp
1458     c3/return
1459 
1460 test-is-identifier-Z:
1461     # . prologue
1462     55/push-ebp
1463     89/<- %ebp 4/r32/esp
1464     # (eax..ecx) = "Z$"
1465     b8/copy-to-eax "Z$"/imm32
1466     8b/-> *eax 1/r32/ecx
1467     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1468     05/add-to-eax 4/imm32
1469     # var slice/ecx : (ref slice) = {eax, ecx}
1470     51/push-ecx
1471     50/push-eax
1472     89/<- %ecx 4/r32/esp
1473     #
1474     (is-identifier? %ecx)
1475     (check-ints-equal %eax 1 "F - test-is-identifier-Z")
1476     # . epilogue
1477     89/<- %esp 5/r32/ebp
1478     5d/pop-to-ebp
1479     c3/return
1480 
1481 test-is-identifier-@:
1482     # character before 'A' is invalid
1483     # . prologue
1484     55/push-ebp
1485     89/<- %ebp 4/r32/esp
1486     # (eax..ecx) = "@a"
1487     b8/copy-to-eax "@a"/imm32
1488     8b/-> *eax 1/r32/ecx
1489     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1490     05/add-to-eax 4/imm32
1491     # var slice/ecx : (ref slice) = {eax, ecx}
1492     51/push-ecx
1493     50/push-eax
1494     89/<- %ecx 4/r32/esp
1495     #
1496     (is-identifier? %ecx)
1497     (check-ints-equal %eax 0 "F - test-is-identifier-@")
1498     # . epilogue
1499     89/<- %esp 5/r32/ebp
1500     5d/pop-to-ebp
1501     c3/return
1502 
1503 test-is-identifier-square-bracket:
1504     # character after 'Z' is invalid
1505     # . prologue
1506     55/push-ebp
1507     89/<- %ebp 4/r32/esp
1508     # (eax..ecx) = "[a"
1509     b8/copy-to-eax "[a"/imm32
1510     8b/-> *eax 1/r32/ecx
1511     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1512     05/add-to-eax 4/imm32
1513     # var slice/ecx : (ref slice) = {eax, ecx}
1514     51/push-ecx
1515     50/push-eax
1516     89/<- %ecx 4/r32/esp
1517     #
1518     (is-identifier? %ecx)
1519     (check-ints-equal %eax 0 "F - test-is-identifier-@")
1520     # . epilogue
1521     89/<- %esp 5/r32/ebp
1522     5d/pop-to-ebp
1523     c3/return
1524 
1525 test-is-identifier-backtick:
1526     # character before 'a' is invalid
1527     # . prologue
1528     55/push-ebp
1529     89/<- %ebp 4/r32/esp
1530     # (eax..ecx) = "`a"
1531     b8/copy-to-eax "`a"/imm32
1532     8b/-> *eax 1/r32/ecx
1533     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1534     05/add-to-eax 4/imm32
1535     # var slice/ecx : (ref slice) = {eax, ecx}
1536     51/push-ecx
1537     50/push-eax
1538     89/<- %ecx 4/r32/esp
1539     #
1540     (is-identifier? %ecx)
1541     (check-ints-equal %eax 0 "F - test-is-identifier-backtick")
1542     # . epilogue
1543     89/<- %esp 5/r32/ebp
1544     5d/pop-to-ebp
1545     c3/return
1546 
1547 test-is-identifier-curly-brace-open:
1548     # character after 'z' is invalid; also used for blocks
1549     # . prologue
1550     55/push-ebp
1551     89/<- %ebp 4/r32/esp
1552     # (eax..ecx) = "{a"
1553     b8/copy-to-eax "{a"/imm32
1554     8b/-> *eax 1/r32/ecx
1555     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1556     05/add-to-eax 4/imm32
1557     # var slice/ecx : (ref slice) = {eax, ecx}
1558     51/push-ecx
1559     50/push-eax
1560     89/<- %ecx 4/r32/esp
1561     #
1562     (is-identifier? %ecx)
1563     (check-ints-equal %eax 0 "F - test-is-identifier-curly-brace-open")
1564     # . epilogue
1565     89/<- %esp 5/r32/ebp
1566     5d/pop-to-ebp
1567     c3/return
1568 
1569 test-is-identifier-curly-brace-close:
1570     # . prologue
1571     55/push-ebp
1572     89/<- %ebp 4/r32/esp
1573     # (eax..ecx) = "}a"
1574     b8/copy-to-eax "}a"/imm32
1575     8b/-> *eax 1/r32/ecx
1576     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1577     05/add-to-eax 4/imm32
1578     # var slice/ecx : (ref slice) = {eax, ecx}
1579     51/push-ecx
1580     50/push-eax
1581     89/<- %ecx 4/r32/esp
1582     #
1583     (is-identifier? %ecx)
1584     (check-ints-equal %eax 0 "F - test-is-identifier-curly-brace-close")
1585     # . epilogue
1586     89/<- %esp 5/r32/ebp
1587     5d/pop-to-ebp
1588     c3/return
1589 
1590 test-is-identifier-hyphen:
1591     # disallow leading '-' since '->' has special meaning
1592     # . prologue
1593     55/push-ebp
1594     89/<- %ebp 4/r32/esp
1595     # (eax..ecx) = "-a"
1596     b8/copy-to-eax "-a"/imm32
1597     8b/-> *eax 1/r32/ecx
1598     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1599     05/add-to-eax 4/imm32
1600     # var slice/ecx : (ref slice) = {eax, ecx}
1601     51/push-ecx
1602     50/push-eax
1603     89/<- %ecx 4/r32/esp
1604     #
1605     (is-identifier? %ecx)
1606     (check-ints-equal %eax 0 "F - test-is-identifier-hyphen")
1607     # . epilogue
1608     89/<- %esp 5/r32/ebp
1609     5d/pop-to-ebp
1610     c3/return
1611 
1612 populate-mu-function-body:  # in : (address buffered-file), out : (handle function)
1613     # . prologue
1614     55/push-ebp
1615     89/<- %ebp 4/r32/esp
1616     # . save registers
1617     50/push-eax
1618     56/push-esi
1619     57/push-edi
1620     # esi = in
1621     8b/-> *(ebp+8) 6/r32/esi
1622     # edi = out
1623     8b/-> *(ebp+0xc) 7/r32/edi
1624     # var eax : (handle block) = parse-mu-block(in)
1625     (parse-mu-block %esi)  # => eax
1626     # out->body = eax
1627     89/<- *(edi+0x10) 0/r32/eax  # Function-body
1628 $populate-mu-function-body:end:
1629     # . restore registers
1630     5f/pop-to-edi
1631     5e/pop-to-esi
1632     58/pop-to-eax
1633     # . epilogue
1634     89/<- %esp 5/r32/ebp
1635     5d/pop-to-ebp
1636     c3/return
1637 
1638 # parses a block, assuming that the leading '{' has already been read by the caller
1639 parse-mu-block:  # in : (address buffered-file) -> result/eax : (handle block)
1640     # pseudocode:
1641     #   var line : (ref stream byte 512)
1642     #   var word-slice : (ref slice)
1643     #   result/eax = allocate(Heap, Stmt-size)
1644     #   result->tag = 0/Block
1645     #   while true                                  # line loop
1646     #     clear-stream(line)
1647     #     read-line-buffered(in, line)
1648     #     if (line->write == 0) break               # end of file
1649     #     word-slice = next-word(line)
1650     #     if slice-empty?(word-slice)               # end of line
1651     #       continue
1652     #     else if slice-starts-with?(word-slice, "#")
1653     #       continue
1654     #     else if slice-equal?(word-slice, "{")
1655     #       assert(no-tokens-in(line))
1656     #       block = parse-mu-block(in)
1657     #       append-to-block(result, block)
1658     #     else if slice-equal?(word-slice, "}")
1659     #       break
1660     #     else if slice-ends-with?(word-slice, ":")
1661     #       named-block = parse-mu-named-block(word-slice, line, in)
1662     #       append-to-block(result, named-block)
1663     #     else if slice-equal?(word-slice, "var")
1664     #       var-def = parse-mu-var-def(line)
1665     #       append-to-block(result, var-def)
1666     #     else
1667     #       stmt = parse-mu-stmt(line)
1668     #       append-to-block(result, stmt)
1669     #   return result
1670     #
1671     # . prologue
1672     55/push-ebp
1673     89/<- %ebp 4/r32/esp
1674     # . save registers
1675     51/push-ecx
1676     52/push-edx
1677     53/push-ebx
1678     56/push-esi
1679     57/push-edi
1680     # var line/ecx : (ref stream byte 512)
1681     81 5/subop/subtract %esp 0x200/imm32
1682     68/push 0x200/imm32/length
1683     68/push 0/imm32/read
1684     68/push 0/imm32/write
1685     89/<- %ecx 4/r32/esp
1686     # var word-slice/edx : (ref slice)
1687     68/push 0/imm32/end
1688     68/push 0/imm32/start
1689     89/<- %edx 4/r32/esp
1690     # edi = result
1691     (allocate Heap *Stmt-size)  # => eax
1692     89/<- %edi 0/r32/eax
1693     { # line loop
1694 $parse-mu-block:line-loop:
1695       # line = read-line-buffered(in)
1696       (clear-stream %ecx)
1697       (read-line-buffered *(ebp+8) %ecx)
1698 #?       (write-buffered Stderr "line: ")
1699 #?       (write-stream-data Stderr %ecx)
1700 #?       (write-buffered Stderr Newline)
1701 #?       (flush Stderr)
1702       # if (line->write == 0) break
1703       81 7/subop/compare *ecx 0/imm32
1704       0f 84/jump-if-equal break/disp32
1705       # word-slice = next-word(line)
1706       (next-word %ecx %edx)
1707 #?       (write-buffered Stderr "word: ")
1708 #?       (write-slice-buffered Stderr %edx)
1709 #?       (write-buffered Stderr Newline)
1710 #?       (flush Stderr)
1711       # if slice-empty?(word-slice) continue
1712       (slice-empty? %edx)
1713       3d/compare-eax-and 0/imm32
1714       0f 85/jump-if-not-equal loop/disp32
1715       # if (slice-starts-with?(word-slice, '#') continue
1716       # . eax = *word-slice->start
1717       8b/-> *edx 0/r32/eax
1718       8a/copy-byte *eax 0/r32/AL
1719       81 4/subop/and %eax 0xff/imm32
1720       # . if (eax == '#') continue
1721       3d/compare-eax-and 0x23/imm32/hash
1722       0f 84/jump-if-equal loop/disp32
1723       # if slice-equal?(word-slice, "{")
1724       {
1725 $parse-mu-block:check-for-block:
1726         (slice-equal? %edx "{")
1727         3d/compare-eax-and 0/imm32
1728         74/jump-if-equal break/disp8
1729         (check-no-tokens-left %ecx)
1730         # parse new block and append
1731         (parse-mu-block *(ebp+8))  # => eax
1732         (append-to-block %edi %eax)
1733         e9/jump $parse-mu-block:line-loop/disp32
1734       }
1735       # if slice-equal?(word-slice, "}") break
1736 $parse-mu-block:check-for-end:
1737       (slice-equal? %edx "}")
1738       3d/compare-eax-and 0/imm32
1739       0f 85/jump-if-not-equal break/disp32
1740       # if slice-ends-with?(word-slice, ":") parse named block and append
1741       {
1742 $parse-mu-block:check-for-named-block:
1743         # . eax = *word-slice->end
1744         8b/-> *(edx+4) 0/r32/eax
1745         8a/copy-byte *eax 0/r32/AL
1746         81 4/subop/and %eax 0xff/imm32
1747         # . if (eax != ':') break
1748         3d/compare-eax-and 0x23/imm32/hash
1749         0f 85/jump-if-not-equal break/disp32
1750         #
1751         (parse-mu-named-block %edx %ecx *(ebp+8))  # => eax
1752         (append-to-block %edi %eax)
1753         e9/jump $parse-mu-block:line-loop/disp32
1754       }
1755       # if slice-equal?(word-slice, "var")
1756       {
1757 $parse-mu-block:check-for-var:
1758         (slice-equal? %edx "var")
1759         3d/compare-eax-and 0/imm32
1760         74/jump-if-equal break/disp8
1761         #
1762         (parse-mu-var-def %ecx)  # => eax
1763         (append-to-block %edi %eax)
1764         e9/jump $parse-mu-block:line-loop/disp32
1765       }
1766 $parse-mu-block:regular-stmt:
1767       # otherwise
1768       (parse-mu-stmt %ecx)  # => eax
1769       (append-to-block Heap %edi %eax)
1770       e9/jump loop/disp32
1771     } # end line loop
1772     # return result
1773     89/<- %eax 7/r32/edi
1774 $parse-mu-block:end:
1775     # . reclaim locals
1776     81 0/subop/add %esp 0x214/imm32
1777     # . restore registers
1778     5f/pop-to-edi
1779     5e/pop-to-esi
1780     5b/pop-to-ebx
1781     5a/pop-to-edx
1782     59/pop-to-ecx
1783     # . epilogue
1784     89/<- %esp 5/r32/ebp
1785     5d/pop-to-ebp
1786     c3/return
1787 
1788 $parse-mu-block:abort:
1789     # error("'{' or '}' should be on its own line, but got '")
1790     (write-buffered Stderr "'{' or '}' should be on its own line, but got '")
1791     (rewind-stream %ecx)
1792     (write-stream 2 %ecx)
1793     (write-buffered Stderr "'\n")
1794     (flush Stderr)
1795     # . syscall(exit, 1)
1796     bb/copy-to-ebx  1/imm32
1797     b8/copy-to-eax  1/imm32/exit
1798     cd/syscall  0x80/imm8
1799     # never gets here
1800 
1801 check-no-tokens-left:  # line : (address stream byte)
1802     # . prologue
1803     55/push-ebp
1804     89/<- %ebp 4/r32/esp
1805     # . save registers
1806     50/push-eax
1807     51/push-ecx
1808     # var s/ecx : (ref slice)
1809     68/push 0/imm32/end
1810     68/push 0/imm32/start
1811     89/<- %ecx 4/r32/esp
1812     #
1813     (next-word *(ebp+8) %ecx)
1814     # if slice-empty?(s) return
1815     (slice-empty? %ecx)
1816     3d/compare-eax-and 0/imm32
1817     75/jump-if-not-equal $check-no-tokens-left:end/disp8
1818     # if (slice-starts-with?(s, '#') return
1819     # . eax = *s->start
1820     8b/-> *edx 0/r32/eax
1821     8a/copy-byte *eax 0/r32/AL
1822     81 4/subop/and %eax 0xff/imm32
1823     # . if (eax == '#') continue
1824     3d/compare-eax-and 0x23/imm32/hash
1825     74/jump-if-equal $check-no-tokens-left:end/disp8
1826     # abort
1827     (write-buffered Stderr "'{' or '}' should be on its own line, but got '")
1828     (rewind-stream %ecx)
1829     (write-stream 2 %ecx)
1830     (write-buffered Stderr "'\n")
1831     (flush Stderr)
1832     # . syscall(exit, 1)
1833     bb/copy-to-ebx  1/imm32
1834     b8/copy-to-eax  1/imm32/exit
1835     cd/syscall  0x80/imm8
1836     # never gets here
1837 $check-no-tokens-left:end:
1838     # . reclaim locals
1839     81 0/subop/add %esp 8/imm32
1840     # . restore registers
1841     59/pop-to-ecx
1842     58/pop-to-eax
1843     # . epilogue
1844     89/<- %esp 5/r32/ebp
1845     5d/pop-to-ebp
1846     c3/return
1847 
1848 parse-mu-named-block:  # name : (address slice), first-line : (address stream byte), in : (address buffered-file) -> result/eax : (handle stmt)
1849     # pseudocode:
1850     #   var line : (ref stream byte 512)
1851     #   var word-slice : (ref slice)
1852     #   result/eax = allocate(Heap, Stmt-size)
1853     #   result->tag = 4/Named-block
1854     #   result->name = name
1855     #   assert(next-word(first-line) == "{")
1856     #   assert(no-tokens-in(first-line))
1857     #   while true                                  # line loop
1858     #     clear-stream(line)
1859     #     read-line-buffered(in, line)
1860     #     if (line->write == 0) break               # end of file
1861     #     word-slice = next-word(line)
1862     #     if slice-empty?(word-slice)               # end of line
1863     #       break
1864     #     else if slice-equal?(word-slice, "{")
1865     #       block = parse-mu-block(in)
1866     #       append-to-block(result, block)
1867     #     else if slice-equal?(word-slice, "}")
1868     #       break
1869     #     else if slice-ends-with?(word-slice, ":")
1870     #       named-block = parse-mu-named-block(word-slice, in)
1871     #       append-to-block(result, named-block)
1872     #     else if slice-equal?(word-slice, "var")
1873     #       var-def = parse-mu-var-def(line)
1874     #       append-to-block(result, var-def)
1875     #     else
1876     #       stmt = parse-mu-stmt(line)
1877     #       append-to-block(result, stmt)
1878     #   return result
1879     #
1880     # . prologue
1881     55/push-ebp
1882     89/<- %ebp 4/r32/esp
1883     # . save registers
1884 $parse-mu-named-block:end:
1885     # . reclaim locals
1886     # . restore registers
1887     # . epilogue
1888     89/<- %esp 5/r32/ebp
1889     5d/pop-to-ebp
1890     c3/return
1891 
1892 parse-mu-var-def:  # line : (address stream byte) -> result/eax : (handle stmt)
1893     # pseudocode:
1894     #
1895     # . prologue
1896     55/push-ebp
1897     89/<- %ebp 4/r32/esp
1898     # . save registers
1899 $parse-mu-var-def:end:
1900     # . reclaim locals
1901     # . restore registers
1902     # . epilogue
1903     89/<- %esp 5/r32/ebp
1904     5d/pop-to-ebp
1905     c3/return
1906 
1907 parse-mu-stmt:  # line : (address stream byte) -> result/eax : (handle stmt)
1908     # pseudocode:
1909     #   var name : (ref slice)
1910     #   var v : (ref var)
1911     #   result = allocate(Heap, Stmt-size)
1912     #   if stmt-has-outputs?(line)
1913     #     while true
1914     #       name = next-word(line)
1915     #       if (name == '<-') break
1916     #       assert(is-identifier?(name))
1917     #       v = parse-var(name)
1918     #       result->outputs = append(result->outputs, v)
1919     #   result->name = slice-to-string(next-word(line))
1920     #   while true
1921     #     name = next-word-or-string(line)
1922     #     v = parse-var-or-literal(name)
1923     #     result->inouts = append(result->inouts, v)
1924     #
1925     # . prologue
1926     55/push-ebp
1927     89/<- %ebp 4/r32/esp
1928     # . save registers
1929     51/push-ecx
1930     57/push-edi
1931     # var name/ecx : (ref slice)
1932     68/push 0/imm32/end
1933     68/push 0/imm32/start
1934     89/<- %ecx 4/r32/esp
1935     # result/edi : (handle stmt)
1936     (allocate Heap *Stmt-size)
1937     89/<- %edi 0/r32/eax
1938     # result->tag = 1/stmt
1939     c7 0/subop/copy *edi 1/imm32/stmt1  # Stmt-tag
1940     {
1941       (stmt-has-outputs? *(ebp+8))
1942       3d/compare-eax-and 0/imm32
1943       0f 84/jump-if-equal break/disp32
1944       {
1945 $parse-mu-stmt:read-outputs:
1946         # name = next-word(line)
1947         (next-word *(ebp+8) %ecx)
1948         # if slice-empty?(word-slice) break
1949         (slice-empty? %ecx)
1950         3d/compare-eax-and 0/imm32
1951         0f 85/jump-if-not-equal break/disp32
1952         # if (name == "<-") break
1953         (slice-equal? %ecx "<-")
1954         3d/compare-eax-and 0/imm32
1955         75/jump-if-not-equal break/disp8
1956         # assert(is-identifier?(name))
1957         (is-identifier? %ecx)
1958         3d/compare-eax-and 0/imm32
1959         0f 84/jump-if-equal $parse-mu-stmt:abort/disp32
1960         #
1961         (parse-var Heap %ecx)  # => eax
1962         (append-list Heap %eax *(edi+0xc))  # Stmt1-outputs => eax
1963         89/<- *(edi+0xc) 0/r32/eax  # Stmt1-outputs
1964         e9/jump loop/disp32
1965       }
1966     }
1967 $parse-mu-stmt:read-operation:
1968     (next-word *(ebp+8) %ecx)
1969     (slice-to-string Heap %ecx)
1970     89/<- *(edi+4) 0/r32/eax  # Stmt1-operation
1971     {
1972 $parse-mu-stmt:read-inouts:
1973       # name = next-word-or-string(line)
1974       (next-word-or-string *(ebp+8) %ecx)
1975       # if slice-empty?(word-slice) break
1976       (slice-empty? %ecx)
1977       3d/compare-eax-and 0/imm32
1978       0f 85/jump-if-not-equal break/disp32
1979       # if (name == "<-") abort
1980       (slice-equal? %ecx "<-")
1981       3d/compare-eax-and 0/imm32
1982       0f 85/jump-if-not-equal $parse-mu-stmt:abort2/disp32
1983       #
1984       (parse-var Heap %ecx)  # => eax  # TODO: parse-var-or-literal
1985       (append-list Heap %eax *(edi+8))  # Stmt1-inouts => eax
1986       89/<- *(edi+8) 0/r32/eax  # Stmt1-inouts
1987       e9/jump loop/disp32
1988     }
1989 $parse-mu-stmt:end:
1990     # return result
1991     89/<- %eax 7/r32/edi
1992     # . reclaim locals
1993     81 0/subop/add %esp 8/imm32
1994     # . restore registers
1995     5f/pop-to-edi
1996     59/pop-to-ecx
1997     # . epilogue
1998     89/<- %esp 5/r32/ebp
1999     5d/pop-to-ebp
2000     c3/return
2001 
2002 $parse-mu-stmt:abort:
2003     # error("invalid identifier '" name "'\n")
2004     (write-buffered Stderr "invalid identifier '")
2005     (write-slice-buffered Stderr %ecx)
2006     (write-buffered Stderr "'\n")
2007     (flush Stderr)
2008     # . syscall(exit, 1)
2009     bb/copy-to-ebx  1/imm32
2010     b8/copy-to-eax  1/imm32/exit
2011     cd/syscall  0x80/imm8
2012     # never gets here
2013 
2014 $parse-mu-stmt:abort2:
2015     # error("invalid statement '" line "'\n")
2016     (rewind-stream *(ebp+8))
2017     (write-buffered Stderr "invalid identifier '")
2018     (write-stream Stderr *(ebp+8))
2019     (write-buffered Stderr "'\n")
2020     (flush Stderr)
2021     # . syscall(exit, 1)
2022     bb/copy-to-ebx  1/imm32
2023     b8/copy-to-eax  1/imm32/exit
2024     cd/syscall  0x80/imm8
2025     # never gets here
2026 
2027 stmt-has-outputs?:  # line : (address stream byte) -> result/eax : boolean
2028     # . prologue
2029     55/push-ebp
2030     89/<- %ebp 4/r32/esp
2031     # . save registers
2032     51/push-ecx
2033     # var word-slice/ecx : (ref slice)
2034     68/push 0/imm32/end
2035     68/push 0/imm32/start
2036     89/<- %ecx 4/r32/esp
2037     # result = false
2038     b8/copy-to-eax 0/imm32/false
2039     (rewind-stream *(ebp+8))
2040     {
2041       (next-word-or-string *(ebp+8) %ecx)
2042       # if slice-empty?(word-slice) break
2043       (slice-empty? %ecx)
2044       3d/compare-eax-and 0/imm32
2045       b8/copy-to-eax 0/imm32/false/result  # restore result (if we're here it's still false)
2046       0f 85/jump-if-not-equal break/disp32
2047       # if slice-starts-with?(word-slice, '#') break
2048       # . eax = *word-slice->start
2049       8b/-> *ecx 0/r32/eax
2050       8a/copy-byte *eax 0/r32/AL
2051       81 4/subop/and %eax 0xff/imm32
2052       # . if (eax == '#') break
2053       3d/compare-eax-and 0x23/imm32/hash
2054       b8/copy-to-eax 0/imm32/false/result  # restore result (if we're here it's still false)
2055       0f 84/jump-if-equal break/disp32
2056       # if slice-equal?(word-slice, '<-') return true
2057       (slice-equal? %ecx "<-")
2058       3d/compare-eax-and 0/imm32
2059       74/jump-if-equal loop/disp8
2060       b8/copy-to-eax 1/imm32/true
2061     }
2062 $stmt-has-outputs:end:
2063     (rewind-stream *(ebp+8))
2064     # . reclaim locals
2065     81 0/subop/add %esp 8/imm32
2066     # . restore registers
2067     59/pop-to-ecx
2068     # . epilogue
2069     89/<- %esp 5/r32/ebp
2070     5d/pop-to-ebp
2071     c3/return
2072 
2073 parse-var:  # ad: allocation-descriptor, name: (address slice) -> result/eax: (handle var)
2074     # . prologue
2075     55/push-ebp
2076     89/<- %ebp 4/r32/esp
2077     # . save registers
2078     51/push-ecx
2079     # ecx = slice-to-string(name)
2080     8b/-> *(ebp+0xc) 1/r32/ecx
2081     (slice-to-string Heap %ecx)  # => eax
2082     89/<- %ecx 0/r32/eax
2083     (allocate *(ebp+8) *Var-size)  # => eax
2084     89/<- *eax 1/r32/ecx  # Var-name
2085     # var->type = int
2086     c7 0/subop/copy *(eax+4) 1/imm32/int-type # Var-type
2087     # var->stack-offset = 8
2088     c7 0/subop/copy *(eax+0xc) 8/imm32 # Var-stack-offset
2089 $parse-var:end:
2090     # . restore registers
2091     59/pop-to-ecx
2092     # . epilogue
2093     89/<- %esp 5/r32/ebp
2094     5d/pop-to-ebp
2095     c3/return
2096 
2097 test-parse-mu-stmt:
2098     # 'increment n'
2099     # . prologue
2100     55/push-ebp
2101     89/<- %ebp 4/r32/esp
2102     # setup
2103     (clear-stream _test-input-stream)
2104     (write _test-input-stream "increment n\n")
2105     # convert
2106     (parse-mu-stmt _test-input-stream)
2107     # check result
2108     (check-strings-equal *(eax+4) "increment" "F - test-parse-mu-stmt/name")  # Stmt1-operation
2109     # edx : (handle list var) = result->inouts
2110     8b/-> *(eax+8) 2/r32/edx  # Stmt1-inouts
2111     # ebx : (handle var) = result->inouts->value
2112     8b/-> *edx 3/r32/ebx  # List-value
2113     (check-strings-equal *ebx "n" "F - test-parse-mu-stmt/inout:0")  # Var-name
2114     # . epilogue
2115     89/<- %esp 5/r32/ebp
2116     5d/pop-to-ebp
2117     c3/return
2118 
2119 new-function:  # ad: allocation-descriptor, name: string, subx-name: string, inouts: (handle list var), outputs: (handle list var), body: (handle block), next: (handle function) -> result/eax: (handle function)
2120     # . prologue
2121     55/push-ebp
2122     89/<- %ebp 4/r32/esp
2123     # . save registers
2124     51/push-ecx
2125     #
2126     (allocate *(ebp+8) *Function-size)  # => eax
2127     8b/-> *(ebp+0xc) 1/r32/ecx
2128     89/<- *eax 1/r32/ecx  # Function-name
2129     8b/-> *(ebp+0x10) 1/r32/ecx
2130     89/<- *(eax+4) 1/r32/ecx  # Function-subx-name
2131     8b/-> *(ebp+0x14) 1/r32/ecx
2132     89/<- *(eax+8) 1/r32/ecx  # Function-inouts
2133     8b/-> *(ebp+0x18) 1/r32/ecx
2134     89/<- *(eax+0xc) 1/r32/ecx  # Function-outputs
2135     8b/-> *(ebp+0x1c) 1/r32/ecx
2136     89/<- *(eax+0x10) 1/r32/ecx  # Function-body
2137     8b/-> *(ebp+0x20) 1/r32/ecx
2138     89/<- *(eax+0x14) 1/r32/ecx  # Function-next
2139 $new-function:end:
2140     # . restore registers
2141     59/pop-to-ecx
2142     # . epilogue
2143     89/<- %esp 5/r32/ebp
2144     5d/pop-to-ebp
2145     c3/return
2146 
2147 new-var:  # ad: allocation-descriptor, name: string, type: int, block: int, stack-offset: int, register: string -> result/eax: (handle var)
2148     # . prologue
2149     55/push-ebp
2150     89/<- %ebp 4/r32/esp
2151     # . save registers
2152     51/push-ecx
2153     #
2154     (allocate *(ebp+8) *Var-size)  # => eax
2155     8b/-> *(ebp+0xc) 1/r32/ecx
2156     89/<- *eax 1/r32/ecx  # Var-name
2157     8b/-> *(ebp+0x10) 1/r32/ecx
2158     89/<- *(eax+4) 1/r32/ecx  # Var-type
2159     8b/-> *(ebp+0x14) 1/r32/ecx
2160     89/<- *(eax+8) 1/r32/ecx  # Var-block
2161     8b/-> *(ebp+0x18) 1/r32/ecx
2162     89/<- *(eax+0xc) 1/r32/ecx  # Var-stack-offset
2163     8b/-> *(ebp+0x1c) 1/r32/ecx
2164     89/<- *(eax+0x10) 1/r32/ecx  # Var-register
2165 $new-var:end:
2166     # . restore registers
2167     59/pop-to-ecx
2168     # . epilogue
2169     89/<- %esp 5/r32/ebp
2170     5d/pop-to-ebp
2171     c3/return
2172 
2173 new-block:  # ad: allocation-descriptor, data: (handle list statement) -> result/eax: (handle statement)
2174     # . prologue
2175     55/push-ebp
2176     89/<- %ebp 4/r32/esp
2177     # . save registers
2178     51/push-ecx
2179     #
2180     (allocate *(ebp+8) *Stmt-size)  # => eax
2181     c7 0/subop/copy *eax 0/imm32/tag/block  # Stmt-tag
2182     8b/-> *(ebp+0xc) 1/r32/ecx
2183     89/<- *(eax+4) 1/r32/ecx  # Block-statements
2184 $new-block:end:
2185     # . restore registers
2186     59/pop-to-ecx
2187     # . epilogue
2188     89/<- %esp 5/r32/ebp
2189     5d/pop-to-ebp
2190     c3/return
2191 
2192 new-stmt:  # ad: allocation-descriptor, operation: string, inouts: (handle list var), outputs: (handle list var) -> result/eax: (handle statement)
2193     # . prologue
2194     55/push-ebp
2195     89/<- %ebp 4/r32/esp
2196     # . save registers
2197     51/push-ecx
2198     #
2199     (allocate *(ebp+8) *Stmt-size)  # => eax
2200     c7 0/subop/copy *eax 1/imm32/tag/regular-stmt  # Stmt-tag
2201     8b/-> *(ebp+0xc) 1/r32/ecx
2202     89/<- *(eax+4) 1/r32/ecx  # Stmt1-operation
2203     8b/-> *(ebp+0x10) 1/r32/ecx
2204     89/<- *(eax+8) 1/r32/ecx  # Stmt1-inouts
2205     8b/-> *(ebp+0x14) 1/r32/ecx
2206     89/<- *(eax+0xc) 1/r32/ecx  # Stmt1-outputs
2207 $new-stmt:end:
2208     # . restore registers
2209     59/pop-to-ecx
2210     # . epilogue
2211     89/<- %esp 5/r32/ebp
2212     5d/pop-to-ebp
2213     c3/return
2214 
2215 new-vardef:  # ad: allocation-descriptor, name: string, type: int -> result/eax: (handle statement)
2216     # . prologue
2217     55/push-ebp
2218     89/<- %ebp 4/r32/esp
2219     # . save registers
2220     51/push-ecx
2221     #
2222     (allocate *(ebp+8) *Stmt-size)  # => eax
2223     c7 0/subop/copy *eax 2/imm32/tag/var-on-stack  # Stmt-tag
2224     8b/-> *(ebp+0xc) 1/r32/ecx
2225     89/<- *(eax+4) 1/r32/ecx  # Vardef-name
2226     8b/-> *(ebp+0x10) 1/r32/ecx
2227     89/<- *(eax+8) 1/r32/ecx  # Vardef-type
2228 $new-vardef:end:
2229     # . restore registers
2230     59/pop-to-ecx
2231     # . epilogue
2232     89/<- %esp 5/r32/ebp
2233     5d/pop-to-ebp
2234     c3/return
2235 
2236 new-regvardef:  # ad: allocation-descriptor, name: string, type: int, register: string -> result/eax: (handle statement)
2237     # . prologue
2238     55/push-ebp
2239     89/<- %ebp 4/r32/esp
2240     # . save registers
2241     51/push-ecx
2242     #
2243     (allocate *(ebp+8) *Stmt-size)  # => eax
2244     c7 0/subop/copy *eax 3/imm32/tag/var-in-register
2245     8b/-> *(ebp+0xc) 1/r32/ecx
2246     89/<- *(eax+4) 1/r32/ecx  # Regvardef-name
2247     8b/-> *(ebp+0x10) 1/r32/ecx
2248     89/<- *(eax+8) 1/r32/ecx  # Regvardef-type
2249     8b/-> *(ebp+0x14) 1/r32/ecx
2250     89/<- *(eax+0xc) 1/r32/ecx  # Regvardef-register
2251 $new-regvardef:end:
2252     # . restore registers
2253     59/pop-to-ecx
2254     # . epilogue
2255     89/<- %esp 5/r32/ebp
2256     5d/pop-to-ebp
2257     c3/return
2258 
2259 new-named-block:  # ad: allocation-descriptor, name: string, data: (handle list statement) -> result/eax: (handle statement)
2260     # . prologue
2261     55/push-ebp
2262     89/<- %ebp 4/r32/esp
2263     # . save registers
2264     51/push-ecx
2265     #
2266     (allocate *(ebp+8) *Stmt-size)  # => eax
2267     c7 0/subop/copy *eax 4/imm32/tag/named-block
2268     8b/-> *(ebp+0xc) 1/r32/ecx
2269     89/<- *(eax+4) 1/r32/ecx  # Named-block-name
2270     8b/-> *(ebp+0x10) 1/r32/ecx
2271     89/<- *(eax+8) 1/r32/ecx  # Named-block-statements
2272 $new-named-block:end:
2273     # . restore registers
2274     59/pop-to-ecx
2275     # . epilogue
2276     89/<- %esp 5/r32/ebp
2277     5d/pop-to-ebp
2278     c3/return
2279 
2280 new-list:  # ad: allocation-descriptor, value: _type, next: (handle list _type) -> result/eax : (handle list _type)
2281     # . prologue
2282     55/push-ebp
2283     89/<- %ebp 4/r32/esp
2284     # . save registers
2285     51/push-ecx
2286     #
2287     (allocate *(ebp+8) *List-size)  # => eax
2288     8b/-> *(ebp+0xc) 1/r32/ecx
2289     89/<- *eax 1/r32/ecx  # List-value
2290     8b/-> *(ebp+0x10) 1/r32/ecx
2291     89/<- *(eax+4) 1/r32/ecx  # List-next
2292 $new-list:end:
2293     # . restore registers
2294     59/pop-to-ecx
2295     # . epilogue
2296     89/<- %esp 5/r32/ebp
2297     5d/pop-to-ebp
2298     c3/return
2299 
2300 append-list:  # ad: allocation-descriptor, value: _type, list: (handle list _type) -> result/eax : (handle list _type)
2301     # . prologue
2302     55/push-ebp
2303     89/<- %ebp 4/r32/esp
2304     # . save registers
2305     51/push-ecx
2306     #
2307     (allocate *(ebp+8) *List-size)  # => eax
2308     8b/-> *(ebp+0xc) 1/r32/ecx
2309     89/<- *eax 1/r32/ecx  # List-value
2310     # if (list == null) return result
2311     81 7/subop/compare *(ebp+0x10) 0/imm32
2312     74/jump-if-equal $new-list:end/disp8
2313     # otherwise append
2314     # var curr/ecx = list
2315     8b/-> *(ebp+0x10) 1/r32/ecx
2316     # while (curr->next != null) curr = curr->next
2317     {
2318       81 7/subop/compare *(ecx+4) 0/imm32  # List-next
2319       74/jump-if-equal break/disp8
2320       # curr = curr->next
2321       8b/-> *(ecx+4) 1/r32/ecx
2322       eb/jump loop/disp8
2323     }
2324     # curr->next = result
2325     89/<- *(ecx+4) 0/r32/eax
2326     # return list
2327     8b/-> *(ebp+0x10) 0/r32/eax
2328 $append-list:end:
2329     # . restore registers
2330     59/pop-to-ecx
2331     # . epilogue
2332     89/<- %esp 5/r32/ebp
2333     5d/pop-to-ebp
2334     c3/return
2335 
2336 append-to-block:  # ad: allocation-descriptor, block: (handle block), x: (handle stmt)
2337     # . prologue
2338     55/push-ebp
2339     89/<- %ebp 4/r32/esp
2340     # . save registers
2341     56/push-esi
2342     # esi = block
2343     8b/-> *(ebp+0xc) 6/r32/esi
2344     (append-list *(ebp+8) *(ebp+0x10) *(esi+4))  # ad, x, Block-statements
2345     89/<- *(esi+4) 0/r32/eax  # Block-statements
2346 $append-to-block:end:
2347     # . restore registers
2348     5e/pop-to-esi
2349     # . epilogue
2350     89/<- %esp 5/r32/ebp
2351     5d/pop-to-ebp
2352     c3/return
2353 
2354 #######################################################
2355 # Type-checking
2356 #######################################################
2357 
2358 check-mu-types:
2359     # . prologue
2360     55/push-ebp
2361     89/<- %ebp 4/r32/esp
2362     #
2363 $check-mu-types:end:
2364     # . epilogue
2365     89/<- %esp 5/r32/ebp
2366     5d/pop-to-ebp
2367     c3/return
2368 
2369 #######################################################
2370 # Code-generation
2371 #######################################################
2372 
2373 emit-subx:  # out : (address buffered-file)
2374     # . prologue
2375     55/push-ebp
2376     89/<- %ebp 4/r32/esp
2377     # . save registers
2378     50/push-eax
2379     51/push-ecx
2380     57/push-edi
2381     # edi = out
2382     8b/-> *(ebp+8) 7/r32/edi
2383     # var curr/ecx : (handle function) = Program
2384     8b/-> *Program 1/r32/ecx
2385     {
2386       # if (curr == null) break
2387       81 7/subop/compare %ecx 0/imm32
2388       0f 84/jump-if-equal break/disp32
2389       (emit-subx-function %edi %ecx)
2390       # curr = curr->next
2391       8b/-> *(ecx+0x14) 1/r32/ecx  # Function-next
2392       e9/jump loop/disp32
2393     }
2394 $emit-subx:end:
2395     # . restore registers
2396     5f/pop-to-edi
2397     59/pop-to-ecx
2398     58/pop-to-eax
2399     # . epilogue
2400     89/<- %esp 5/r32/ebp
2401     5d/pop-to-ebp
2402     c3/return
2403 
2404 emit-subx-function:  # out : (address buffered-file), f : (handle function)
2405     # . prologue
2406     55/push-ebp
2407     89/<- %ebp 4/r32/esp
2408     # . save registers
2409     50/push-eax
2410     51/push-ecx
2411     57/push-edi
2412     # edi = out
2413     8b/-> *(ebp+8) 7/r32/edi
2414     # ecx = f
2415     8b/-> *(ebp+0xc) 1/r32/ecx
2416     #
2417     (write-buffered %edi *ecx)
2418     (write-buffered %edi ":\n")
2419     (emit-subx-prologue %edi)
2420     (emit-subx-block %edi *(ecx+0x10))  # Function-body
2421     (emit-subx-epilogue %edi)
2422 $emit-subx-function:end:
2423     # . restore registers
2424     5f/pop-to-edi
2425     59/pop-to-ecx
2426     58/pop-to-eax
2427     # . epilogue
2428     89/<- %esp 5/r32/ebp
2429     5d/pop-to-ebp
2430     c3/return
2431 
2432 emit-subx-block:  # out : (address buffered-file), block : (handle block)
2433     # . prologue
2434     55/push-ebp
2435     89/<- %ebp 4/r32/esp
2436     # curr/esi : (handle list statement) = block->statements
2437     8b/-> *(ebp+0xc) 6/r32/esi
2438     8b/-> *(esi+4) 6/r32/esi  # Block-statements
2439     #
2440     {
2441 $emit-subx-block:stmt:
2442       81 7/subop/compare %esi 0/imm32
2443       0f 84/jump-if-equal break/disp32
2444       (write-buffered *(ebp+8) "{\n")
2445       {
2446         81 7/subop/compare %esi 0/imm32
2447         74/jump-if-equal break/disp8
2448         (emit-subx-statement *(ebp+8) *esi 0 Primitives 0)  # TODO: initialize vars and functions
2449         (write-buffered *(ebp+8) Newline)
2450         8b/-> *(esi+4) 6/r32/esi  # List-next
2451         eb/jump loop/disp8
2452       }
2453       (write-buffered *(ebp+8) "}\n")
2454     }
2455 $emit-subx-block:end:
2456     # . epilogue
2457     89/<- %esp 5/r32/ebp
2458     5d/pop-to-ebp
2459     c3/return
2460 
2461 emit-subx-statement:  # out : (address buffered-file), stmt : (handle statement), vars : (handle stack var), primitives : (handle primitive), functions : (handle function)
2462     # . prologue
2463     55/push-ebp
2464     89/<- %ebp 4/r32/esp
2465     # . save registers
2466     50/push-eax
2467     51/push-ecx
2468     # if stmt matches a primitive, emit it
2469     {
2470 $emit-subx-statement:primitive:
2471       (find-matching-primitive *(ebp+0x14) *(ebp+0xc))  # primitives, stmt => curr/eax
2472       3d/compare-eax-and 0/imm32
2473       74/jump-if-equal break/disp8
2474       (emit-subx-primitive *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax)  # out, stmt, vars, curr
2475       e9/jump $emit-subx-statement:end/disp32
2476     }
2477     # else if stmt matches a function, emit a call to it
2478     {
2479 $emit-subx-statement:call:
2480       (find-matching-function *(ebp+0x18) *(ebp+0xc))  # functions, stmt => curr/eax
2481       3d/compare-eax-and 0/imm32
2482       74/jump-if-equal break/disp8
2483       (emit-subx-call *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax)  # out, stmt, vars, curr
2484       e9/jump $emit-subx-statement:end/disp32
2485     }
2486     # else abort
2487     e9/jump $emit-subx-statement:abort/disp32
2488 $emit-subx-statement:end:
2489     # . restore registers
2490     59/pop-to-ecx
2491     58/pop-to-eax
2492     # . epilogue
2493     89/<- %esp 5/r32/ebp
2494     5d/pop-to-ebp
2495     c3/return
2496 
2497 $emit-subx-statement:abort:
2498     # error("couldn't translate '" stmt "'\n")
2499     (write-buffered Stderr "couldn't translate '")
2500 #?     (emit-string Stderr *(ebp+0xc))  # TODO
2501     (write-buffered Stderr "'\n")
2502     (flush Stderr)
2503     # . syscall(exit, 1)
2504     bb/copy-to-ebx  1/imm32
2505     b8/copy-to-eax  1/imm32/exit
2506     cd/syscall  0x80/imm8
2507     # never gets here
2508 
2509 # Primitives supported
2510 == data
2511 Primitives:
2512     # increment var => ff 0/subop/increment *(ebp+__)
2513     "increment"/imm32/name
2514     Single-int-var-on-stack/imm32/inouts
2515     0/imm32/no-outputs
2516     "ff 0/subop/increment"/imm32/subx-name
2517     1/imm32/rm32-is-first-inout
2518     0/imm32/no-r32
2519     0/imm32/no-imm32
2520     _Primitive-inc-reg/imm32/next
2521 _Primitive-inc-reg:
2522     # var/reg <- increment => ff 0/subop/increment %__
2523     "increment"/imm32/name
2524     0/imm32/no-inouts
2525     Single-int-var-in-some-register/imm32/outputs
2526     "ff 0/subop/increment"/imm32/subx-name
2527     3/imm32/rm32-is-first-output
2528     0/imm32/no-r32
2529     0/imm32/no-imm32
2530     _Primitive-add-reg-to-reg/imm32/next
2531 _Primitive-add-reg-to-reg:
2532     # var1/reg <- add var2/reg => 01 var1/rm32 var2/r32
2533     "add"/imm32/name
2534     Single-int-var-in-some-register/imm32/inouts
2535     Single-int-var-in-some-register/imm32/outputs
2536     "01"/imm32/subx-name
2537     3/imm32/rm32-is-first-output
2538     1/imm32/r32-is-first-inout
2539     0/imm32/no-imm32
2540     _Primitive-add-reg-to-mem/imm32/next
2541 _Primitive-add-reg-to-mem:
2542     # add-to var1 var2/reg => 01 var1 var2/r32
2543     "add-to"/imm32/name
2544     Int-var-and-second-int-var-in-some-register/imm32/inouts
2545     0/imm32/outputs
2546     "01"/imm32/subx-name
2547     1/imm32/rm32-is-first-inout
2548     2/imm32/r32-is-second-inout
2549     0/imm32/no-imm32
2550     _Primitive-add-mem-to-reg/imm32/next
2551 _Primitive-add-mem-to-reg:
2552     # var1/reg <- add var2 => 03 var2/rm32 var1/r32
2553     "add"/imm32/name
2554     Single-int-var-on-stack/imm32/inouts
2555     Single-int-var-in-some-register/imm32/outputs
2556     "03"/imm32/subx-name
2557     1/imm32/rm32-is-first-inout
2558     3/imm32/r32-is-first-output
2559     0/imm32/no-imm32
2560     _Primitive-add-lit-to-reg/imm32/next
2561 _Primitive-add-lit-to-reg:
2562     # var1/reg <- add lit => 81 0/subop/add var1/rm32 lit/imm32
2563     "add"/imm32/name
2564     Single-lit-var/imm32/inouts
2565     Single-int-var-in-some-register/imm32/outputs
2566     "81 0/subop/add"/imm32/subx-name
2567     3/imm32/rm32-is-first-output
2568     0/imm32/no-r32
2569     1/imm32/imm32-is-first-inout
2570     _Primitive-add-lit-to-mem/imm32/next
2571 _Primitive-add-lit-to-mem:
2572     # add-to var1, lit => 81 0/subop/add var1/rm32 lit/imm32
2573     "add-to"/imm32/name
2574     Int-var-and-literal/imm32/inouts
2575     0/imm32/outputs
2576     "81 0/subop/add"/imm32/subx-name
2577     1/imm32/rm32-is-first-inout
2578     0/imm32/no-r32
2579     2/imm32/imm32-is-first-inout
2580     0/imm32/next
2581 
2582 Single-int-var-on-stack:
2583     Int-var-on-stack/imm32
2584     0/imm32/next
2585 
2586 Int-var-on-stack:
2587     "arg1"/imm32/name
2588     1/imm32/type-int
2589     1/imm32/some-block-depth
2590     1/imm32/some-stack-offset
2591     0/imm32/no-register
2592 
2593 Int-var-and-second-int-var-in-some-register:
2594     Int-var-on-stack/imm32
2595     Single-int-var-in-some-register/imm32/next
2596 
2597 Int-var-and-literal:
2598     Int-var-on-stack/imm32
2599     Single-lit-var/imm32/next
2600 
2601 Single-int-var-in-some-register:
2602     Int-var-in-some-register/imm32
2603     0/imm32/next
2604 
2605 Int-var-in-some-register:
2606     "arg1"/imm32/name
2607     1/imm32/type-int
2608     1/imm32/some-block-depth
2609     0/imm32/no-stack-offset
2610     "*"/imm32/register
2611 
2612 Single-lit-var:
2613     Lit-var/imm32
2614     0/imm32/next
2615 
2616 Lit-var:
2617     "literal"/imm32/name
2618     0/imm32/type-literal
2619     1/imm32/some-block-depth
2620     0/imm32/no-stack-offset
2621     0/imm32/no-register
2622 
2623 == code
2624 emit-subx-primitive:  # out : (address buffered-file), stmt : (handle statement), vars : (handle variable), primitive : (handle function)
2625     # . prologue
2626     55/push-ebp
2627     89/<- %ebp 4/r32/esp
2628     # . save registers
2629     50/push-eax
2630     51/push-ecx
2631     # ecx = primitive
2632     8b/-> *(ebp+0x14) 1/r32/ecx
2633     # emit primitive name
2634     (write-buffered *(ebp+8) *(ecx+0xc))  # Primitive-subx-name
2635     # emit rm32 if necessary
2636     (emit-subx-rm32 *(ebp+8) *(ecx+0x10) *(ebp+0xc))  # out, Primitive-subx-rm32, stmt
2637     # emit r32 if necessary
2638     (emit-subx-r32 *(ebp+8) *(ecx+0x14) *(ebp+0xc))  # out, Primitive-subx-r32, stmt
2639     # emit imm32 if necessary
2640     (emit-subx-imm32 *(ebp+8) *(ecx+0x18) *(ebp+0xc))  # out, Primitive-subx-imm32, stmt
2641 $emit-subx-primitive:end:
2642     # . restore registers
2643     59/pop-to-ecx
2644     58/pop-to-eax
2645     # . epilogue
2646     89/<- %esp 5/r32/ebp
2647     5d/pop-to-ebp
2648     c3/return
2649 
2650 emit-subx-rm32:  # out : (address buffered-file), l : arg-location, stmt : (handle statement)
2651     # . prologue
2652     55/push-ebp
2653     89/<- %ebp 4/r32/esp
2654     # . save registers
2655     50/push-eax
2656     # if (l == 0) return
2657     81 7/subop/compare *(ebp+0xc) 0/imm32
2658     74/jump-if-equal $emit-subx-rm32:end/disp8
2659     #
2660     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
2661     (emit-subx-var-as-rm32 *(ebp+8) %eax)  # out, var
2662 $emit-subx-rm32:end:
2663     # . restore registers
2664     58/pop-to-eax
2665     # . epilogue
2666     89/<- %esp 5/r32/ebp
2667     5d/pop-to-ebp
2668     c3/return
2669 
2670 get-stmt-operand-from-arg-location:  # stmt : (handle statement), l : arg-location -> var/eax : (handle variable)
2671     # . prologue
2672     55/push-ebp
2673     89/<- %ebp 4/r32/esp
2674     # . save registers
2675     51/push-ecx
2676     # eax = l
2677     8b/-> *(ebp+0xc) 0/r32/eax
2678     # ecx = stmt
2679     8b/-> *(ebp+8) 1/r32/ecx
2680     # if (l == 1) return stmt->inouts->var
2681     {
2682       3d/compare-eax-and 1/imm32
2683       75/jump-if-not-equal break/disp8
2684 $get-stmt-operand-from-arg-location:1:
2685       8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
2686       8b/-> *eax 0/r32/eax  # Operand-var
2687       eb/jump $get-stmt-operand-from-arg-location:end/disp8
2688     }
2689     # if (l == 2) return stmt->inouts->next->var
2690     {
2691       3d/compare-eax-and 2/imm32
2692       75/jump-if-not-equal break/disp8
2693 $get-stmt-operand-from-arg-location:2:
2694       8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
2695       8b/-> *(eax+4) 0/r32/eax  # Operand-next
2696       8b/-> *eax 0/r32/eax  # Operand-var
2697       eb/jump $get-stmt-operand-from-arg-location:end/disp8
2698     }
2699     # if (l == 3) return stmt->outputs
2700     {
2701       3d/compare-eax-and 3/imm32
2702       75/jump-if-not-equal break/disp8
2703 $get-stmt-operand-from-arg-location:3:
2704       8b/-> *(ecx+0xc) 0/r32/eax  # Stmt1-outputs
2705       8b/-> *eax 0/r32/eax  # Operand-var
2706       eb/jump $get-stmt-operand-from-arg-location:end/disp8
2707     }
2708     # abort
2709     e9/jump $get-stmt-operand-from-arg-location:abort/disp32
2710 $get-stmt-operand-from-arg-location:end:
2711     # . restore registers
2712     59/pop-to-ecx
2713     # . epilogue
2714     89/<- %esp 5/r32/ebp
2715     5d/pop-to-ebp
2716     c3/return
2717 
2718 $get-stmt-operand-from-arg-location:abort:
2719     # error("invalid arg-location " eax)
2720     (write-buffered Stderr "invalid arg-location ")
2721     (print-int32-buffered Stderr %eax)
2722     (write-buffered Stderr "\n")
2723     (flush Stderr)
2724     # . syscall(exit, 1)
2725     bb/copy-to-ebx  1/imm32
2726     b8/copy-to-eax  1/imm32/exit
2727     cd/syscall  0x80/imm8
2728     # never gets here
2729 
2730 emit-subx-r32:  # out : (address buffered-file), l : arg-location, stmt : (handle statement)
2731     # . prologue
2732     55/push-ebp
2733     89/<- %ebp 4/r32/esp
2734     # . save registers
2735     50/push-eax
2736     51/push-ecx
2737     # if (location == 0) return
2738     81 7/subop/compare *(ebp+0xc) 0/imm32
2739     0f 84/jump-if-equal $emit-subx-r32:end/disp32
2740     #
2741     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
2742     (maybe-get Registers *(eax+0x10) 8)  # Var-register => eax : (address register-index)
2743     (write-buffered *(ebp+8) Space)
2744     (print-int32-buffered *(ebp+8) *eax)
2745     (write-buffered *(ebp+8) "/r32")
2746 $emit-subx-r32:end:
2747     # . restore registers
2748     59/pop-to-ecx
2749     58/pop-to-eax
2750     # . epilogue
2751     89/<- %esp 5/r32/ebp
2752     5d/pop-to-ebp
2753     c3/return
2754 
2755 emit-subx-imm32:  # out : (address buffered-file), l : arg-location, stmt : (handle statement)
2756     # . prologue
2757     55/push-ebp
2758     89/<- %ebp 4/r32/esp
2759     # . save registers
2760     50/push-eax
2761     51/push-ecx
2762     # if (location == 0) return
2763     81 7/subop/compare *(ebp+0xc) 0/imm32
2764     74/jump-if-equal $emit-subx-imm32:end/disp8
2765     #
2766     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
2767     (write-buffered *(ebp+8) Space)
2768     (write-buffered *(ebp+8) *eax)  # Var-name
2769     (write-buffered *(ebp+8) "/imm32")
2770 $emit-subx-imm32:end:
2771     # . restore registers
2772     59/pop-to-ecx
2773     58/pop-to-eax
2774     # . epilogue
2775     89/<- %esp 5/r32/ebp
2776     5d/pop-to-ebp
2777     c3/return
2778 
2779 emit-subx-call:  # out : (address buffered-file), stmt : (handle statement), vars : (handle variable), callee : (handle function)
2780     # . prologue
2781     55/push-ebp
2782     89/<- %ebp 4/r32/esp
2783     # . save registers
2784     50/push-eax
2785     51/push-ecx
2786     #
2787     (write-buffered *(ebp+8) "(")
2788     # - emit function name
2789     8b/-> *(ebp+0x14) 1/r32/ecx
2790     (write-buffered *(ebp+8) *(ecx+4))  # Function-subx-name
2791     # - emit arguments
2792     # var curr/ecx : (handle list var) = stmt->inouts
2793     8b/-> *(ebp+0xc) 1/r32/ecx
2794     8b/-> *(ecx+8) 1/r32/ecx  # Stmt1-inouts
2795     {
2796       # if (curr == null) break
2797       81 7/subop/compare %ecx 0/imm32
2798       74/jump-if-equal break/disp8
2799       #
2800       (emit-subx-call-operand *(ebp+8) *ecx)
2801       # curr = curr->next
2802       8b/-> *(ecx+4) 1/r32/ecx
2803     }
2804     #
2805     (write-buffered *(ebp+8) ")")
2806 $emit-subx-call:end:
2807     # . restore registers
2808     59/pop-to-ecx
2809     58/pop-to-eax
2810     # . epilogue
2811     89/<- %esp 5/r32/ebp
2812     5d/pop-to-ebp
2813     c3/return
2814 
2815 emit-subx-call-operand:  # out : (address buffered-file), operand : (handle variable)
2816     # . prologue
2817     55/push-ebp
2818     89/<- %ebp 4/r32/esp
2819     # . save registers
2820     50/push-eax
2821     # eax = operand
2822     8b/-> *(ebp+0xc) 0/r32/eax
2823     # if non-literal, emit appropriately
2824     (emit-subx-var-as-rm32 *(ebp+8) %eax)
2825     # else if (operand->type == literal) emit "__"
2826     {
2827       81 7/subop/compare *(eax+4) 0/imm32  # Var-type
2828       75/jump-if-not-equal break/disp8
2829 $emit-subx-call-operand:literal:
2830       (write-buffered *(ebp+8) Space)
2831       (write-buffered *(ebp+8) *eax)
2832     }
2833 $emit-subx-call-operand:end:
2834     # . restore registers
2835     58/pop-to-eax
2836     # . epilogue
2837     89/<- %esp 5/r32/ebp
2838     5d/pop-to-ebp
2839     c3/return
2840 
2841 emit-subx-var-as-rm32:  # out : (address buffered-file), operand : (handle variable)
2842     # . prologue
2843     55/push-ebp
2844     89/<- %ebp 4/r32/esp
2845     # . save registers
2846     50/push-eax
2847     # eax = operand
2848     8b/-> *(ebp+0xc) 0/r32/eax
2849     # if (operand->register) emit "%__"
2850     {
2851       81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
2852       74/jump-if-equal break/disp8
2853 $emit-subx-var-as-rm32:register:
2854       (write-buffered *(ebp+8) " %")
2855       (write-buffered *(ebp+8) *(eax+0x10))  # Var-register
2856     }
2857     # else if (operand->stack-offset) emit "*(ebp+__)"
2858     {
2859       81 7/subop/compare *(eax+0xc) 0/imm32  # Var-stack-offset
2860       74/jump-if-equal break/disp8
2861 $emit-subx-var-as-rm32:stack:
2862       (write-buffered *(ebp+8) Space)
2863       (write-buffered *(ebp+8) "*(ebp+")
2864       8b/-> *(ebp+0xc) 0/r32/eax
2865       (print-int32-buffered *(ebp+8) *(eax+0xc))  # Var-stack-offset
2866       (write-buffered *(ebp+8) ")")
2867     }
2868 $emit-subx-var-as-rm32:end:
2869     # . restore registers
2870     58/pop-to-eax
2871     # . epilogue
2872     89/<- %esp 5/r32/ebp
2873     5d/pop-to-ebp
2874     c3/return
2875 
2876 find-matching-function:  # functions : (address function), stmt : (handle statement) -> result/eax : (handle function)
2877     # . prologue
2878     55/push-ebp
2879     89/<- %ebp 4/r32/esp
2880     # . save registers
2881     51/push-ecx
2882     # var curr/ecx : (handle function) = functions
2883     8b/-> *(ebp+8) 1/r32/ecx
2884     {
2885       # if (curr == null) break
2886       81 7/subop/compare %ecx 0/imm32
2887       74/jump-if-equal break/disp8
2888       # if match(stmt, curr) return curr
2889       {
2890         (mu-stmt-matches-function? *(ebp+0xc) %ecx)  # => eax
2891         3d/compare-eax-and 0/imm32
2892         74/jump-if-equal break/disp8
2893         89/<- %eax 1/r32/ecx
2894         eb/jump $find-matching-function:end/disp8
2895       }
2896       # curr = curr->next
2897       8b/-> *(ecx+0x10) 1/r32/ecx  # Function-next
2898       eb/jump loop/disp8
2899     }
2900     # return null
2901     b8/copy-to-eax 0/imm32
2902 $find-matching-function:end:
2903     # . restore registers
2904     59/pop-to-ecx
2905     # . epilogue
2906     89/<- %esp 5/r32/ebp
2907     5d/pop-to-ebp
2908     c3/return
2909 
2910 find-matching-primitive:  # primitives : (handle primitive), stmt : (handle statement) -> result/eax : (handle primitive)
2911     # . prologue
2912     55/push-ebp
2913     89/<- %ebp 4/r32/esp
2914     # . save registers
2915     51/push-ecx
2916     # var curr/ecx : (handle primitive) = primitives
2917     8b/-> *(ebp+8) 1/r32/ecx
2918     {
2919 $find-matching-primitive:loop:
2920       # if (curr == null) break
2921       81 7/subop/compare %ecx 0/imm32
2922       74/jump-if-equal break/disp8
2923       # if match(curr, stmt) return curr
2924       {
2925         (mu-stmt-matches-primitive? *(ebp+0xc) %ecx)  # => eax
2926         3d/compare-eax-and 0/imm32
2927         74/jump-if-equal break/disp8
2928         89/<- %eax 1/r32/ecx
2929         eb/jump $find-matching-function:end/disp8
2930       }
2931 $find-matching-primitive:next-primitive:
2932       # curr = curr->next
2933       8b/-> *(ecx+0x1c) 1/r32/ecx  # Primitive-next
2934       eb/jump loop/disp8
2935     }
2936     # return null
2937     b8/copy-to-eax 0/imm32
2938 $find-matching-primitive:end:
2939     # . restore registers
2940     59/pop-to-ecx
2941     # . epilogue
2942     89/<- %esp 5/r32/ebp
2943     5d/pop-to-ebp
2944     c3/return
2945 
2946 mu-stmt-matches-function?:  # stmt : (handle statement), function : (handle function) => result/eax : boolean
2947     # . prologue
2948     55/push-ebp
2949     89/<- %ebp 4/r32/esp
2950     # . save registers
2951     51/push-ecx
2952     # return primitive->name == stmt->operation
2953     8b/-> *(ebp+8) 1/r32/ecx
2954     8b/-> *(ebp+0xc) 0/r32/eax
2955     (string-equal? *(ecx+4) *eax)  # Stmt1-operation, Primitive-name => eax
2956 $mu-stmt-matches-function?:end:
2957     # . restore registers
2958     59/pop-to-ecx
2959     # . epilogue
2960     89/<- %esp 5/r32/ebp
2961     5d/pop-to-ebp
2962     c3/return
2963 
2964 mu-stmt-matches-primitive?:  # stmt : (handle statement), primitive : (handle primitive) => result/eax : boolean
2965     # A mu stmt matches a primitive if the name matches, all the inout vars
2966     # match, and all the output vars match.
2967     # Vars match if types match and registers match.
2968     # In addition, a stmt output matches a primitive's output if types match
2969     # and the primitive has a wildcard register.
2970     # . prologue
2971     55/push-ebp
2972     89/<- %ebp 4/r32/esp
2973     # . save registers
2974     51/push-ecx
2975     52/push-edx
2976     53/push-ebx
2977     56/push-esi
2978     57/push-edi
2979     # ecx = stmt
2980     8b/-> *(ebp+8) 1/r32/ecx
2981     # edx = primitive
2982     8b/-> *(ebp+0xc) 2/r32/edx
2983     {
2984 $mu-stmt-matches-primitive?:check-name:
2985       # if (primitive->name != stmt->operation) return false
2986       (string-equal? *(ecx+4) *edx)  # Stmt1-operation, Primitive-name => eax
2987       3d/compare-eax-and 0/imm32
2988       75/jump-if-not-equal break/disp8
2989       b8/copy-to-eax 0/imm32
2990       e9/jump $mu-stmt-matches-primitive?:end/disp32
2991     }
2992 $mu-stmt-matches-primitive?:check-inouts:
2993     # for (curr/esi in stmt->inouts, curr2/edi in primitive->inouts)
2994     8b/-> *(ecx+8) 6/r32/esi  # Stmt1-inouts
2995     8b/-> *(edx+4) 7/r32/edi  # Primitive-inouts
2996     {
2997       # if (curr == 0) return (curr2 == 0)
2998       {
2999         81 7/subop/compare %esi 0/imm32
3000         75/jump-if-not-equal break/disp8
3001 $mu-stmt-matches-primitive?:stmt-inout-is-null:
3002         {
3003           81 7/subop/compare %edi 0/imm32
3004           75/jump-if-not-equal break/disp8
3005           # return true
3006           b8/copy-to-eax 1/imm32/true
3007           e9/jump $mu-stmt-matches-primitive?:end/disp32
3008         }
3009         # return false
3010         b8/copy-to-eax 0/imm32/false
3011         e9/jump $mu-stmt-matches-primitive?:end/disp32
3012       }
3013       # if (curr2 == 0) return false
3014       {
3015         81 7/subop/compare %edi 0/imm32
3016         75/jump-if-not-equal break/disp8
3017 $mu-stmt-matches-primitive?:prim-inout-is-null:
3018         b8/copy-to-eax 0/imm32/false
3019         e9/jump $mu-stmt-matches-primitive?:end/disp32
3020       }
3021       # if (curr != curr2) return false
3022       {
3023         (operand-matches-primitive? *esi *edi)  # => eax
3024         3d/compare-eax-and 0/imm32
3025         75/jump-if-not-equal break/disp8
3026         b8/copy-to-eax 0/imm32/false
3027         e9/jump $mu-stmt-matches-primitive?:end/disp32
3028       }
3029       # curr=curr->next
3030       8b/-> *(esi+4) 6/r32/esi  # Operand-next
3031       # curr2=curr2->next
3032       8b/-> *(edi+4) 7/r32/edi  # Operand-next
3033       eb/jump loop/disp8
3034     }
3035 $mu-stmt-matches-primitive?:check-outputs:
3036     # for (curr/esi in stmt->outputs, curr2/edi in primitive->outputs)
3037     8b/-> *(ecx+0xc) 6/r32/esi  # Stmt1-outputs
3038     8b/-> *(edx+8) 7/r32/edi  # Primitive-outputs
3039     {
3040       # if (curr == 0) return (curr2 == 0)
3041       {
3042         81 7/subop/compare %esi 0/imm32
3043         75/jump-if-not-equal break/disp8
3044         {
3045           81 7/subop/compare %edi 0/imm32
3046           75/jump-if-not-equal break/disp8
3047           # return true
3048           b8/copy-to-eax 1/imm32
3049           e9/jump $mu-stmt-matches-primitive?:end/disp32
3050         }
3051         # return false
3052         b8/copy-to-eax 0/imm32
3053         e9/jump $mu-stmt-matches-primitive?:end/disp32
3054       }
3055       # if (curr2 == 0) return false
3056       {
3057         81 7/subop/compare %edi 0/imm32
3058         75/jump-if-not-equal break/disp8
3059         b8/copy-to-eax 0/imm32
3060         e9/jump $mu-stmt-matches-primitive?:end/disp32
3061       }
3062       # if (curr != curr2) return false
3063       {
3064         (operand-matches-primitive? *esi *edi)  # => eax
3065         3d/compare-eax-and 0/imm32
3066         75/jump-if-not-equal break/disp8
3067         b8/copy-to-eax 0/imm32
3068         e9/jump $mu-stmt-matches-primitive?:end/disp32
3069       }
3070       # curr=curr->next
3071       8b/-> *(ecx+4) 1/r32/ecx  # Operand-next
3072       # curr2=curr2->next
3073       8b/-> *(edx+4) 2/r32/edx  # Operand-next
3074       eb/jump loop/disp8
3075     }
3076 $mu-stmt-matches-primitive?:return-true:
3077     b8/copy-to-eax 1/imm32
3078 $mu-stmt-matches-primitive?:end:
3079     # . restore registers
3080     5f/pop-to-edi
3081     5e/pop-to-esi
3082     5b/pop-to-ebx
3083     5a/pop-to-edx
3084     59/pop-to-ecx
3085     # . epilogue
3086     89/<- %esp 5/r32/ebp
3087     5d/pop-to-ebp
3088     c3/return
3089 
3090 operand-matches-primitive?:  # var : (handle var), primout-var : (handle var) => result/eax : boolean
3091     # . prologue
3092     55/push-ebp
3093     89/<- %ebp 4/r32/esp
3094     # . save registers
3095     56/push-esi
3096     57/push-edi
3097     # esi = var
3098     8b/-> *(ebp+8) 6/r32/esi
3099     # edi = primout-var
3100     8b/-> *(ebp+0xc) 7/r32/edi
3101     # if (var->type != primout-var->type) return false
3102     8b/-> *(esi+4) 0/r32/eax  # Var-type
3103     39/compare *(edi+4) 0/r32/eax  # Var-type
3104     b8/copy-to-eax 0/imm32/false
3105     75/jump-if-not-equal $operand-matches-primitive?:end/disp8
3106     # return false if var->register doesn't match primout-var->register
3107     {
3108       # if addresses are equal, don't return here
3109       8b/-> *(esi+0x10) 0/r32/eax
3110       39/compare *(edi+0x10) 0/r32/eax
3111       74/jump-if-equal break/disp8
3112       # if either address is 0, return false
3113       3d/compare-eax-and 0/imm32
3114       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
3115       81 7/subop/compare *(edi+0x10) 0/imm32
3116       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
3117       # if primout-var->register is "*", return true
3118       (string-equal? *(edi+0x10) "*")  # Var-register
3119       3d/compare-eax-and 0/imm32
3120       b8/copy-to-eax 1/imm32/true
3121       75/jump-if-not-equal $operand-matches-primitive?:end/disp8
3122       # if string contents don't match, return false
3123       (string-equal? *(esi+0x10) *(edi+0x10))  # Var-register Var-register
3124       3d/compare-eax-and 0/imm32
3125       b8/copy-to-eax 0/imm32/false
3126       74/jump-if-equal $operand-matches-primitive?:end/disp8
3127     }
3128     # return true
3129     b8/copy-to-eax 1/imm32/true
3130 $operand-matches-primitive?:end:
3131     # . restore registers
3132     5f/pop-to-edi
3133     5e/pop-to-esi
3134     # . epilogue
3135     89/<- %esp 5/r32/ebp
3136     5d/pop-to-ebp
3137     c3/return
3138 
3139 test-emit-subx-statement-primitive:
3140     # Primitive operation on a variable on the stack.
3141     #   increment foo
3142     # =>
3143     #   ff 0/subop/increment *(ebp-8)
3144     #
3145     # There's a variable on the var stack as follows:
3146     #   name: 'foo'
3147     #   type: int
3148     #   stack-offset: -8
3149     #
3150     # There's a primitive with this info:
3151     #   name: 'increment'
3152     #   inouts: int/mem
3153     #   value: 'ff 0/subop/increment'
3154     #
3155     # There's nothing in functions.
3156     #
3157     # . prologue
3158     55/push-ebp
3159     89/<- %ebp 4/r32/esp
3160     # setup
3161     (clear-stream _test-output-stream)
3162     (clear-stream $_test-output-buffered-file->buffer)
3163     # var var-foo/ecx : (ref var)
3164     68/push 0/imm32/no-register
3165     68/push -8/imm32/stack-offset
3166     68/push 1/imm32/block-depth
3167     68/push 1/imm32/type-int
3168     68/push "foo"/imm32
3169     89/<- %ecx 4/r32/esp
3170     # var vars/edx : (ref stack 1)
3171     51/push-ecx/var-foo
3172     68/push 1/imm32/data-length
3173     68/push 1/imm32/top
3174     89/<- %edx 4/r32/esp
3175     # var operand/ebx : (ref list var)
3176     68/push 0/imm32/next
3177     51/push-ecx/var-foo
3178     89/<- %ebx 4/r32/esp
3179     # var stmt/esi : (ref statement)
3180     68/push 0/imm32/next
3181     68/push 0/imm32/outputs
3182     53/push-ebx/operands
3183     68/push "increment"/imm32/operation
3184     68/push 1/imm32
3185     89/<- %esi 4/r32/esp
3186     # var primitives/ebx : (ref primitive)
3187     68/push 0/imm32/next
3188     68/push 0/imm32/no-imm32
3189     68/push 0/imm32/no-r32
3190     68/push 1/imm32/rm32-is-first-inout
3191     68/push "ff 0/subop/increment"/imm32/subx-name
3192     68/push 0/imm32/outputs
3193     53/push-ebx/inouts  # hack; in practice we won't have the same var in function definition and call
3194     68/push "increment"/imm32/name
3195     89/<- %ebx 4/r32/esp
3196     # convert
3197     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
3198     (flush _test-output-buffered-file)
3199 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3205     # check output
3206     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0xfffffff8)" "F - test-emit-subx-statement-primitive")
3207     # . epilogue
3208     89/<- %esp 5/r32/ebp
3209     5d/pop-to-ebp
3210     c3/return
3211 
3212 test-emit-subx-statement-primitive-register:
3213     # Primitive operation on a variable in a register.
3214     #   foo <- increment
3215     # =>
3216     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
3217     #
3218     # There's a variable on the var stack as follows:
3219     #   name: 'foo'
3220     #   type: int
3221     #   register: 'eax'
3222     #
3223     # There's a primitive with this info:
3224     #   name: 'increment'
3225     #   out: int/reg
3226     #   value: 'ff 0/subop/increment'
3227     #
3228     # There's nothing in functions.
3229     #
3230     # . prologue
3231     55/push-ebp
3232     89/<- %ebp 4/r32/esp
3233     # setup
3234     (clear-stream _test-output-stream)
3235     (clear-stream $_test-output-buffered-file->buffer)
3236     # var var-foo/ecx : (ref var) in eax
3237     68/push "eax"/imm32/register
3238     68/push 0/imm32/no-stack-offset
3239     68/push 1/imm32/block-depth
3240     68/push 1/imm32/type-int
3241     68/push "foo"/imm32
3242     89/<- %ecx 4/r32/esp
3243     # var vars/edx : (ref stack 1)
3244     51/push-ecx/var-foo
3245     68/push 1/imm32/data-length
3246     68/push 1/imm32/top
3247     89/<- %edx 4/r32/esp
3248     # var operand/ebx : (ref list var)
3249     68/push 0/imm32/next
3250     51/push-ecx/var-foo
3251     89/<- %ebx 4/r32/esp
3252     # var stmt/esi : (ref statement)
3253     68/push 0/imm32/next
3254     53/push-ebx/outputs
3255     68/push 0/imm32/inouts
3256     68/push "increment"/imm32/operation
3257     68/push 1/imm32
3258     89/<- %esi 4/r32/esp
3259     # var formal-var/ebx : (ref var) in any register
3260     68/push Any-register/imm32
3261     68/push 0/imm32/no-stack-offset
3262     68/push 1/imm32/block-depth
3263     68/push 1/imm32/type-int
3264     68/push "dummy"/imm32
3265     89/<- %ebx 4/r32/esp
3266     # var operand/ebx : (ref list var)
3267     68/push 0/imm32/next
3268     53/push-ebx/formal-var
3269     89/<- %ebx 4/r32/esp
3270     # var primitives/ebx : (ref primitive)
3271     68/push 0/imm32/next
3272     68/push 0/imm32/no-imm32
3273     68/push 0/imm32/no-r32
3274     68/push 3/imm32/rm32-in-first-output
3275     68/push "ff 0/subop/increment"/imm32/subx-name
3276     53/push-ebx/outputs
3277     68/push 0/imm32/inouts
3278     68/push "increment"/imm32/name
3279     89/<- %ebx 4/r32/esp
3280     # convert
3281     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
3282     (flush _test-output-buffered-file)
3283 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3289     # check output
3290     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-primitive-register")
3291     # . epilogue
3292     89/<- %esp 5/r32/ebp
3293     5d/pop-to-ebp
3294     c3/return
3295 
3296 test-emit-subx-statement-select-primitive:
3297     # Select the right primitive between overloads.
3298     #   foo <- increment
3299     # =>
3300     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
3301     #
3302     # There's a variable on the var stack as follows:
3303     #   name: 'foo'
3304     #   type: int
3305     #   register: 'eax'
3306     #
3307     # There's two primitives, as follows:
3308     #   - name: 'increment'
3309     #     out: int/reg
3310     #     value: 'ff 0/subop/increment'
3311     #   - name: 'increment'
3312     #     inout: int/mem
3313     #     value: 'ff 0/subop/increment'
3314     #
3315     # There's nothing in functions.
3316     #
3317     # . prologue
3318     55/push-ebp
3319     89/<- %ebp 4/r32/esp
3320     # setup
3321     (clear-stream _test-output-stream)
3322     (clear-stream $_test-output-buffered-file->buffer)
3323     # var var-foo/ecx : (ref var) in eax
3324     68/push "eax"/imm32/register
3325     68/push 0/imm32/no-stack-offset
3326     68/push 1/imm32/block-depth
3327     68/push 1/imm32/type-int
3328     68/push "foo"/imm32
3329     89/<- %ecx 4/r32/esp
3330     # var vars/edx : (ref stack 1)
3331     51/push-ecx/var-foo
3332     68/push 1/imm32/data-length
3333     68/push 1/imm32/top
3334     89/<- %edx 4/r32/esp
3335     # var real-outputs/edi : (ref list var)
3336     68/push 0/imm32/next
3337     51/push-ecx/var-foo
3338     89/<- %edi 4/r32/esp
3339     # var stmt/esi : (ref statement)
3340     68/push 0/imm32/next
3341     57/push-edi/outputs
3342     68/push 0/imm32/inouts
3343     68/push "increment"/imm32/operation
3344     68/push 1/imm32
3345     89/<- %esi 4/r32/esp
3346     # var formal-var/ebx : (ref var) in any register
3347     68/push Any-register/imm32
3348     68/push 0/imm32/no-stack-offset
3349     68/push 1/imm32/block-depth
3350     68/push 1/imm32/type-int
3351     68/push "dummy"/imm32
3352     89/<- %ebx 4/r32/esp
3353     # var formal-outputs/ebx : (ref list var) = {formal-var, 0}
3354     68/push 0/imm32/next
3355     53/push-ebx/formal-var
3356     89/<- %ebx 4/r32/esp
3357     # var primitive1/ebx : (ref primitive)
3358     68/push 0/imm32/next
3359     68/push 0/imm32/no-imm32
3360     68/push 0/imm32/no-r32
3361     68/push 3/imm32/rm32-in-first-output
3362     68/push "ff 0/subop/increment"/imm32/subx-name
3363     53/push-ebx/outputs/formal-outputs
3364     68/push 0/imm32/inouts
3365     68/push "increment"/imm32/name
3366     89/<- %ebx 4/r32/esp
3367     # var primitives/ebx : (ref primitive)
3368     53/push-ebx/next
3369     68/push 0/imm32/no-imm32
3370     68/push 0/imm32/no-r32
3371     68/push 1/imm32/rm32-is-first-inout
3372     68/push "ff 0/subop/increment"/imm32/subx-name
3373     68/push 0/imm32/outputs
3374     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
3375     68/push "increment"/imm32/name
3376     89/<- %ebx 4/r32/esp
3377     # convert
3378     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
3379     (flush _test-output-buffered-file)
3380 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3386     # check output
3387     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive")
3388     # . epilogue
3389     89/<- %esp 5/r32/ebp
3390     5d/pop-to-ebp
3391     c3/return
3392 
3393 test-emit-subx-statement-select-primitive-2:
3394     # Select the right primitive between overloads.
3395     #   foo <- increment
3396     # =>
3397     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
3398     #
3399     # There's a variable on the var stack as follows:
3400     #   name: 'foo'
3401     #   type: int
3402     #   register: 'eax'
3403     #
3404     # There's two primitives, as follows:
3405     #   - name: 'increment'
3406     #     out: int/reg
3407     #     value: 'ff 0/subop/increment'
3408     #   - name: 'increment'
3409     #     inout: int/mem
3410     #     value: 'ff 0/subop/increment'
3411     #
3412     # There's nothing in functions.
3413     #
3414     # . prologue
3415     55/push-ebp
3416     89/<- %ebp 4/r32/esp
3417     # setup
3418     (clear-stream _test-output-stream)
3419     (clear-stream $_test-output-buffered-file->buffer)
3420     # var var-foo/ecx : (ref var) in eax
3421     68/push "eax"/imm32/register
3422     68/push 0/imm32/no-stack-offset
3423     68/push 1/imm32/block-depth
3424     68/push 1/imm32/type-int
3425     68/push "foo"/imm32
3426     89/<- %ecx 4/r32/esp
3427     # var vars/edx : (ref stack 1)
3428     51/push-ecx/var-foo
3429     68/push 1/imm32/data-length
3430     68/push 1/imm32/top
3431     89/<- %edx 4/r32/esp
3432     # var inouts/edi : (ref list var)
3433     68/push 0/imm32/next
3434     51/push-ecx/var-foo
3435     89/<- %edi 4/r32/esp
3436     # var stmt/esi : (ref statement)
3437     68/push 0/imm32/next
3438     68/push 0/imm32/outputs
3439     57/push-edi/inouts
3440     68/push "increment"/imm32/operation
3441     68/push 1/imm32
3442     89/<- %esi 4/r32/esp
3443     # var formal-var/ebx : (ref var) in any register
3444     68/push Any-register/imm32
3445     68/push 0/imm32/no-stack-offset
3446     68/push 1/imm32/block-depth
3447     68/push 1/imm32/type-int
3448     68/push "dummy"/imm32
3449     89/<- %ebx 4/r32/esp
3450     # var operand/ebx : (ref list var)
3451     68/push 0/imm32/next
3452     53/push-ebx/formal-var
3453     89/<- %ebx 4/r32/esp
3454     # var primitive1/ebx : primitive
3455     68/push 0/imm32/next
3456     68/push 0/imm32/no-imm32
3457     68/push 0/imm32/no-r32
3458     68/push 3/imm32/rm32-in-first-output
3459     68/push "ff 0/subop/increment"/imm32/subx-name
3460     53/push-ebx/outputs/formal-outputs
3461     68/push 0/imm32/inouts
3462     68/push "increment"/imm32/name
3463     89/<- %ebx 4/r32/esp
3464     # var primitives/ebx : (ref primitive)
3465     53/push-ebx/next
3466     68/push 0/imm32/no-imm32
3467     68/push 0/imm32/no-r32
3468     68/push 1/imm32/rm32-is-first-inout
3469     68/push "ff 0/subop/increment"/imm32/subx-name
3470     68/push 0/imm32/outputs
3471     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
3472     68/push "increment"/imm32/name
3473     89/<- %ebx 4/r32/esp
3474     # convert
3475     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
3476     (flush _test-output-buffered-file)
3477 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3483     # check output
3484     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive-2")
3485     # . epilogue
3486     89/<- %esp 5/r32/ebp
3487     5d/pop-to-ebp
3488     c3/return
3489 
3490 test-increment-register:
3491     # Select the right primitive between overloads.
3492     #   foo <- increment
3493     # =>
3494     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
3495     #
3496     # There's a variable on the var stack as follows:
3497     #   name: 'foo'
3498     #   type: int
3499     #   register: 'eax'
3500     #
3501     # Primitives are the global definitions.
3502     #
3503     # There are no functions defined.
3504     #
3505     # . prologue
3506     55/push-ebp
3507     89/<- %ebp 4/r32/esp
3508     # setup
3509     (clear-stream _test-output-stream)
3510     (clear-stream $_test-output-buffered-file->buffer)
3511     # var var-foo/ecx : (ref var) in eax
3512     68/push "eax"/imm32/register
3513     68/push 0/imm32/no-stack-offset
3514     68/push 1/imm32/block-depth
3515     68/push 1/imm32/type-int
3516     68/push "foo"/imm32
3517     89/<- %ecx 4/r32/esp
3518     # var vars/edx : (ref stack 1)
3519     51/push-ecx/var-foo
3520     68/push 1/imm32/data-length
3521     68/push 1/imm32/top
3522     89/<- %edx 4/r32/esp
3523     # var real-outputs/edi : (ref list var)
3524     68/push 0/imm32/next
3525     51/push-ecx/var-foo
3526     89/<- %edi 4/r32/esp
3527     # var stmt/esi : (ref statement)
3528     68/push 0/imm32/next
3529     57/push-edi/outputs
3530     68/push 0/imm32/inouts
3531     68/push "increment"/imm32/operation
3532     68/push 1/imm32
3533     89/<- %esi 4/r32/esp
3534     # convert
3535     (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 0)
3536     (flush _test-output-buffered-file)
3537 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3543     # check output
3544     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-increment-register")
3545     # . epilogue
3546     89/<- %esp 5/r32/ebp
3547     5d/pop-to-ebp
3548     c3/return
3549 
3550 test-increment-var:
3551     # Select the right primitive between overloads.
3552     #   foo <- increment
3553     # =>
3554     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
3555     #
3556     # There's a variable on the var stack as follows:
3557     #   name: 'foo'
3558     #   type: int
3559     #   register: 'eax'
3560     #
3561     # Primitives are the global definitions.
3562     #
3563     # There are no functions defined.
3564     #
3565     # . prologue
3566     55/push-ebp
3567     89/<- %ebp 4/r32/esp
3568     # setup
3569     (clear-stream _test-output-stream)
3570     (clear-stream $_test-output-buffered-file->buffer)
3571     # var var-foo/ecx : (ref var) in eax
3572     68/push "eax"/imm32/register
3573     68/push 0/imm32/no-stack-offset
3574     68/push 1/imm32/block-depth
3575     68/push 1/imm32/type-int
3576     68/push "foo"/imm32
3577     89/<- %ecx 4/r32/esp
3578     # var vars/edx : (ref stack 1)
3579     51/push-ecx/var-foo
3580     68/push 1/imm32/data-length
3581     68/push 1/imm32/top
3582     89/<- %edx 4/r32/esp
3583     # var inouts/edi : (ref list var)
3584     68/push 0/imm32/next
3585     51/push-ecx/var-foo
3586     89/<- %edi 4/r32/esp
3587     # var stmt/esi : (ref statement)
3588     68/push 0/imm32/next
3589     68/push 0/imm32/outputs
3590     57/push-edi/inouts
3591     68/push "increment"/imm32/operation
3592     68/push 1/imm32
3593     89/<- %esi 4/r32/esp
3594     # convert
3595     (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 0)
3596     (flush _test-output-buffered-file)
3597 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3603     # check output
3604     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-increment-var")
3605     # . epilogue
3606     89/<- %esp 5/r32/ebp
3607     5d/pop-to-ebp
3608     c3/return
3609 
3610 test-add-reg-to-reg:
3611     #   var1/reg <- add var2/reg
3612     # =>
3613     #   01 %var1 var2
3614     #
3615     # . prologue
3616     55/push-ebp
3617     89/<- %ebp 4/r32/esp
3618     # setup
3619     (clear-stream _test-output-stream)
3620     (clear-stream $_test-output-buffered-file->buffer)
3621     # var var-var1/ecx : (ref var) in eax
3622     68/push "eax"/imm32/register
3623     68/push 0/imm32/no-stack-offset
3624     68/push 1/imm32/block-depth
3625     68/push 1/imm32/type-int
3626     68/push "var1"/imm32
3627     89/<- %ecx 4/r32/esp
3628     # var var-var2/edx : (ref var) in ecx
3629     68/push "ecx"/imm32/register
3630     68/push 0/imm32/no-stack-offset
3631     68/push 1/imm32/block-depth
3632     68/push 1/imm32/type-int
3633     68/push "var2"/imm32
3634     89/<- %edx 4/r32/esp
3635     # var inouts/esi : (ref list var2)
3636     68/push 0/imm32/next
3637     52/push-edx/var-var2
3638     89/<- %esi 4/r32/esp
3639     # var outputs/edi : (ref list var1)
3640     68/push 0/imm32/next
3641     51/push-ecx/var-var1
3642     89/<- %edi 4/r32/esp
3643     # var stmt/esi : (ref statement)
3644     68/push 0/imm32/next
3645     57/push-edi/outputs
3646     56/push-esi/inouts
3647     68/push "add"/imm32/operation
3648     68/push 1/imm32
3649     89/<- %esi 4/r32/esp
3650     # convert
3651     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
3652     (flush _test-output-buffered-file)
3653 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3659     # check output
3660     (check-next-stream-line-equal _test-output-stream "01 %eax 0x00000001/r32" "F - test-add-reg-to-reg")
3661     # . epilogue
3662     89/<- %esp 5/r32/ebp
3663     5d/pop-to-ebp
3664     c3/return
3665 
3666 test-add-reg-to-mem:
3667     #   add-to var1 var2/reg
3668     # =>
3669     #   01 *(ebp+__) var2
3670     #
3671     # . prologue
3672     55/push-ebp
3673     89/<- %ebp 4/r32/esp
3674     # setup
3675     (clear-stream _test-output-stream)
3676     (clear-stream $_test-output-buffered-file->buffer)
3677     # var var-var1/ecx : (ref var)
3678     68/push 0/imm32/no-register
3679     68/push 8/imm32/stack-offset
3680     68/push 1/imm32/block-depth
3681     68/push 1/imm32/type-int
3682     68/push "var1"/imm32
3683     89/<- %ecx 4/r32/esp
3684     # var var-var2/edx : (ref var) in ecx
3685     68/push "ecx"/imm32/register
3686     68/push 0/imm32/no-stack-offset
3687     68/push 1/imm32/block-depth
3688     68/push 1/imm32/type-int
3689     68/push "var2"/imm32
3690     89/<- %edx 4/r32/esp
3691     # var inouts/esi : (ref list var2)
3692     68/push 0/imm32/next
3693     52/push-edx/var-var2
3694     89/<- %esi 4/r32/esp
3695     # var inouts = (ref list var1 var2)
3696     56/push-esi/next
3697     51/push-ecx/var-var1
3698     89/<- %esi 4/r32/esp
3699     # var stmt/esi : (ref statement)
3700     68/push 0/imm32/next
3701     68/push 0/imm32/outputs
3702     56/push-esi/inouts
3703     68/push "add-to"/imm32/operation
3704     68/push 1/imm32
3705     89/<- %esi 4/r32/esp
3706     # convert
3707     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
3708     (flush _test-output-buffered-file)
3709 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3715     # check output
3716     (check-next-stream-line-equal _test-output-stream "01 *(ebp+0x00000008) 0x00000001/r32" "F - test-add-reg-to-mem")
3717     # . epilogue
3718     89/<- %esp 5/r32/ebp
3719     5d/pop-to-ebp
3720     c3/return
3721 
3722 test-add-mem-to-reg:
3723     #   var1/reg <- add var2
3724     # =>
3725     #   03 *(ebp+__) var1
3726     #
3727     # . prologue
3728     55/push-ebp
3729     89/<- %ebp 4/r32/esp
3730     # setup
3731     (clear-stream _test-output-stream)
3732     (clear-stream $_test-output-buffered-file->buffer)
3733     # var var-var1/ecx : (ref var) in eax
3734     68/push "eax"/imm32/register
3735     68/push 0/imm32/no-stack-offset
3736     68/push 1/imm32/block-depth
3737     68/push 1/imm32/type-int
3738     68/push "var1"/imm32
3739     89/<- %ecx 4/r32/esp
3740     # var var-var2/edx : (ref var)
3741     68/push 0/imm32/no-register
3742     68/push 8/imm32/stack-offset
3743     68/push 1/imm32/block-depth
3744     68/push 1/imm32/type-int
3745     68/push "var2"/imm32
3746     89/<- %edx 4/r32/esp
3747     # var inouts/esi : (ref list var2)
3748     68/push 0/imm32/next
3749     52/push-edx/var-var2
3750     89/<- %esi 4/r32/esp
3751     # var outputs/edi : (ref list var1)
3752     68/push 0/imm32/next
3753     51/push-ecx/var-var1
3754     89/<- %edi 4/r32/esp
3755     # var stmt/esi : (ref statement)
3756     68/push 0/imm32/next
3757     57/push-edi/outputs
3758     56/push-esi/inouts
3759     68/push "add"/imm32/operation
3760     68/push 1/imm32
3761     89/<- %esi 4/r32/esp
3762     # convert
3763     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
3764     (flush _test-output-buffered-file)
3765 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3771     # check output
3772     (check-next-stream-line-equal _test-output-stream "03 *(ebp+0x00000008) 0x00000000/r32" "F - test-add-mem-to-reg")
3773     # . epilogue
3774     89/<- %esp 5/r32/ebp
3775     5d/pop-to-ebp
3776     c3/return
3777 
3778 test-add-literal-to-reg:
3779     #   var1/eax <- add 0x34
3780     # =>
3781     #   81 0/subop/add %eax 0x34/imm32
3782     #
3783     # . prologue
3784     55/push-ebp
3785     89/<- %ebp 4/r32/esp
3786     # setup
3787     (clear-stream _test-output-stream)
3788     (clear-stream $_test-output-buffered-file->buffer)
3789     # var var-var1/ecx : (ref var) in eax
3790     68/push "eax"/imm32/register
3791     68/push 0/imm32/no-stack-offset
3792     68/push 1/imm32/block-depth
3793     68/push 1/imm32/type-int
3794     68/push "var1"/imm32
3795     89/<- %ecx 4/r32/esp
3796     # var var-var2/edx : (ref var) literal
3797     68/push 0/imm32/no-register
3798     68/push 0/imm32/no-stack-offset
3799     68/push 1/imm32/block-depth
3800     68/push 0/imm32/type-literal
3801     68/push "0x34"/imm32
3802     89/<- %edx 4/r32/esp
3803     # var inouts/esi : (ref list var2)
3804     68/push 0/imm32/next
3805     52/push-edx/var-var2
3806     89/<- %esi 4/r32/esp
3807     # var outputs/edi : (ref list var1)
3808     68/push 0/imm32/next
3809     51/push-ecx/var-var1
3810     89/<- %edi 4/r32/esp
3811     # var stmt/esi : (ref statement)
3812     68/push 0/imm32/next
3813     57/push-edi/outputs
3814     56/push-esi/inouts
3815     68/push "add"/imm32/operation
3816     68/push 1/imm32
3817     89/<- %esi 4/r32/esp
3818     # convert
3819     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
3820     (flush _test-output-buffered-file)
3821 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3827     # check output
3828     (check-next-stream-line-equal _test-output-stream "81 0/subop/add %eax 0x34/imm32" "F - test-add-literal-to-reg")
3829     # . epilogue
3830     89/<- %esp 5/r32/ebp
3831     5d/pop-to-ebp
3832     c3/return
3833 
3834 test-add-literal-to-mem:
3835     #   add-to var1, 0x34
3836     # =>
3837     #   81 0/subop/add %eax 0x34/imm32
3838     #
3839     # . prologue
3840     55/push-ebp
3841     89/<- %ebp 4/r32/esp
3842     # setup
3843     (clear-stream _test-output-stream)
3844     (clear-stream $_test-output-buffered-file->buffer)
3845     # var var-var1/ecx : (ref var)
3846     68/push 0/imm32/no-register
3847     68/push 8/imm32/stack-offset
3848     68/push 1/imm32/block-depth
3849     68/push 1/imm32/type-int
3850     68/push "var1"/imm32
3851     89/<- %ecx 4/r32/esp
3852     # var var-var2/edx : (ref var) literal
3853     68/push 0/imm32/no-register
3854     68/push 0/imm32/no-stack-offset
3855     68/push 1/imm32/block-depth
3856     68/push 0/imm32/type-literal
3857     68/push "0x34"/imm32
3858     89/<- %edx 4/r32/esp
3859     # var inouts/esi : (ref list var2)
3860     68/push 0/imm32/next
3861     52/push-edx/var-var2
3862     89/<- %esi 4/r32/esp
3863     # var inouts = (ref list var1 inouts)
3864     56/push-esi/next
3865     51/push-ecx/var-var1
3866     89/<- %esi 4/r32/esp
3867     # var stmt/esi : (ref statement)
3868     68/push 0/imm32/next
3869     68/push 0/imm32/outputs
3870     56/push-esi/inouts
3871     68/push "add-to"/imm32/operation
3872     68/push 1/imm32
3873     89/<- %esi 4/r32/esp
3874     # convert
3875     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
3876     (flush _test-output-buffered-file)
3877 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3883     # check output
3884     (check-next-stream-line-equal _test-output-stream "81 0/subop/add *(ebp+0x00000008) 0x34/imm32" "F - test-add-literal-to-mem")
3885     # . epilogue
3886     89/<- %esp 5/r32/ebp
3887     5d/pop-to-ebp
3888     c3/return
3889 
3890 test-emit-subx-statement-function-call:
3891     # Call a function on a variable on the stack.
3892     #   f foo
3893     # =>
3894     #   (f2 *(ebp-8))
3895     # (Changing the function name supports overloading in general, but here it
3896     # just serves to help disambiguate things.)
3897     #
3898     # There's a variable on the var stack as follows:
3899     #   name: 'foo'
3900     #   type: int
3901     #   stack-offset: -8
3902     #
3903     # There's nothing in primitives.
3904     #
3905     # There's a function with this info:
3906     #   name: 'f'
3907     #   inout: int/mem
3908     #   value: 'f2'
3909     #
3910     # . prologue
3911     55/push-ebp
3912     89/<- %ebp 4/r32/esp
3913     # setup
3914     (clear-stream _test-output-stream)
3915     (clear-stream $_test-output-buffered-file->buffer)
3916     # var var-foo/ecx : (ref var)
3917     68/push 0/imm32/no-register
3918     68/push -8/imm32/stack-offset
3919     68/push 0/imm32/block-depth
3920     68/push 1/imm32/type-int
3921     68/push "foo"/imm32
3922     89/<- %ecx 4/r32/esp
3923     # var vars/edx = (ref stack 1)
3924     51/push-ecx/var-foo
3925     68/push 1/imm32/data-length
3926     68/push 1/imm32/top
3927     89/<- %edx 4/r32/esp
3928     # var operands/esi : (ref list var)
3929     68/push 0/imm32/next
3930     51/push-ecx/var-foo
3931     89/<- %esi 4/r32/esp
3932     # var stmt/esi : (ref statement)
3933     68/push 0/imm32/next
3934     68/push 0/imm32/outputs
3935     56/push-esi/inouts
3936     68/push "f"/imm32/operation
3937     68/push 1/imm32
3938     89/<- %esi 4/r32/esp
3939     # var functions/ebx : (ref function)
3940     68/push 0/imm32/next
3941     68/push 0/imm32/body
3942     68/push 0/imm32/outputs
3943     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
3944     68/push "f2"/imm32/subx-name
3945     68/push "f"/imm32/name
3946     89/<- %ebx 4/r32/esp
3947     # convert
3948     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
3949     (flush _test-output-buffered-file)
3950 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
3956     # check output
3957     (check-next-stream-line-equal _test-output-stream "(f2 *(ebp+0xfffffff8))" "F - test-emit-subx-statement-function-call")
3958     # . epilogue
3959     89/<- %esp 5/r32/ebp
3960     5d/pop-to-ebp
3961     c3/return
3962 
3963 test-emit-subx-statement-function-call-with-literal-arg:
3964     # Call a function on a literal.
3965     #   f 34
3966     # =>
3967     #   (f2 34)
3968     #
3969     # . prologue
3970     55/push-ebp
3971     89/<- %ebp 4/r32/esp
3972     # setup
3973     (clear-stream _test-output-stream)
3974     (clear-stream $_test-output-buffered-file->buffer)
3975     # var var-foo/ecx : (ref var) literal
3976     68/push 0/imm32/no-register
3977     68/push 0/imm32/no-stack-offset
3978     68/push 0/imm32/block-depth
3979     68/push 0/imm32/type-literal
3980     68/push "34"/imm32
3981     89/<- %ecx 4/r32/esp
3982     # var vars/edx = (ref stack 1)
3983     51/push-ecx/var-foo
3984     68/push 1/imm32/data-length
3985     68/push 1/imm32/top
3986     89/<- %edx 4/r32/esp
3987     # var operands/esi : (ref list var)
3988     68/push 0/imm32/next
3989     51/push-ecx/var-foo
3990     89/<- %esi 4/r32/esp
3991     # var stmt/esi : (ref statement)
3992     68/push 0/imm32/next
3993     68/push 0/imm32/outputs
3994     56/push-esi/inouts
3995     68/push "f"/imm32/operation
3996     68/push 1/imm32
3997     89/<- %esi 4/r32/esp
3998     # var functions/ebx : (ref function)
3999     68/push 0/imm32/next
4000     68/push 0/imm32/body
4001     68/push 0/imm32/outputs
4002     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
4003     68/push "f2"/imm32/subx-name
4004     68/push "f"/imm32/name
4005     89/<- %ebx 4/r32/esp
4006     # convert
4007     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
4008     (flush _test-output-buffered-file)
4009 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4015     # check output
4016     (check-next-stream-line-equal _test-output-stream "(f2 34)" "F - test-emit-subx-statement-function-call-with-literal-arg")
4017     # . epilogue
4018     89/<- %esp 5/r32/ebp
4019     5d/pop-to-ebp
4020     c3/return
4021 
4022 emit-subx-prologue:  # out : (address buffered-file)
4023     # . prologue
4024     55/push-ebp
4025     89/<- %ebp 4/r32/esp
4026     #
4027     (write-buffered *(ebp+8) "# . prologue\n")
4028     (write-buffered *(ebp+8) "55/push-ebp\n")
4029     (write-buffered *(ebp+8) "89/<- %ebp 4/r32/esp\n")
4030 $emit-subx-prologue:end:
4031     # . epilogue
4032     89/<- %esp 5/r32/ebp
4033     5d/pop-to-ebp
4034     c3/return
4035 
4036 emit-subx-epilogue:  # out : (address buffered-file)
4037     # . prologue
4038     55/push-ebp
4039     89/<- %ebp 4/r32/esp
4040     #
4041     (write-buffered *(ebp+8) "# . epilogue\n")
4042     (write-buffered *(ebp+8) "89/<- %esp 5/r32/ebp\n")
4043     (write-buffered *(ebp+8) "5d/pop-to-ebp\n")
4044     (write-buffered *(ebp+8) "c3/return\n")
4045 $emit-subx-epilogue:end:
4046     # . epilogue
4047     89/<- %esp 5/r32/ebp
4048     5d/pop-to-ebp
4049     c3/return