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; at most a singleton
 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 #     output-is-write-only: boolean
 225 #   arg-location: enum
 226 #     0 means none
 227 #     1 means first inout
 228 #     2 means second inout
 229 #     3 means first output
 230 
 231 # == Translating a block
 232 # Emit block name if necessary
 233 # Emit '{'
 234 # When you encounter a statement, emit it as above
 235 # When you encounter a variable declaration
 236 #   emit any code needed for it (bzeros)
 237 #   push it on the var stack
 238 #   update register dict if necessary
 239 # When you encounter '}'
 240 #   While popping variables off the var stack until block id changes
 241 #     Emit code needed to clean up the stack
 242 #       either increment esp
 243 #       or pop into appropriate register
 244 
 245 # The rest is straightforward.
 246 
 247 == data
 248 
 249 Program:  # (handle function)
 250   0/imm32
 251 
 252 Function-name:
 253   0/imm32
 254 Function-subx-name:
 255   4/imm32
 256 Function-inouts:  # (handle list var)
 257   8/imm32
 258 Function-outputs:  # (handle list var)
 259   0xc/imm32
 260 Function-body:  # (handle block)
 261   0x10/imm32
 262 Function-next:  # (handle function)
 263   0x14/imm32
 264 Function-size:
 265   0x18/imm32/24
 266 
 267 Primitive-name:
 268   0/imm32
 269 Primitive-inouts:  # (handle list var)
 270   4/imm32
 271 Primitive-outputs:  # (handle list var)
 272   8/imm32
 273 Primitive-subx-name:  # (handle array byte)
 274   0xc/imm32
 275 Primitive-subx-rm32:  # enum arg-location
 276   0x10/imm32
 277 Primitive-subx-r32:  # enum arg-location
 278   0x14/imm32
 279 Primitive-subx-imm32:  # enum arg-location
 280   0x18/imm32
 281 Primitive-write-only-output:  # boolean
 282   0x1c/imm32
 283 Primitive-next:  # (handle function)
 284   0x20/imm32
 285 Primitive-size:
 286   0x24/imm32/36
 287 
 288 Stmt-tag:
 289   0/imm32
 290 
 291 Block-statements:  # (handle list statement)
 292   4/imm32
 293 
 294 Stmt1-operation:  # (handle array byte)
 295   4/imm32
 296 Stmt1-inouts:  # (handle list var)
 297   8/imm32
 298 Stmt1-outputs:  # (handle list var)
 299   0xc/imm32
 300 
 301 Vardef-name:  # (handle array byte)
 302   4/imm32
 303 Vardef-type:  # (handle tree type-id)
 304   8/imm32
 305 
 306 Regvardef-name:  # (handle array byte)
 307   4/imm32
 308 Regvardef-type:  # (handle tree type-id)
 309   8/imm32
 310 Regvardef-register:  # (handle array byte)
 311   0xc/imm32
 312 Regvardef-operation:  # (handle array byte)
 313   0x10/imm32
 314 Regvardef-inputs:  # (handle list var)
 315   0x14/imm32
 316 
 317 Named-block-name:
 318   4/imm32
 319 Named-block-statements:  # (handle list statement)
 320   8/imm32
 321 
 322 Stmt-size:
 323   0x18/imm32
 324 
 325 Var-name:
 326   0/imm32
 327 Var-type:
 328   4/imm32
 329 Var-block:
 330   8/imm32
 331 Var-stack-offset:
 332   0xc/imm32
 333 Var-register:
 334   0x10/imm32
 335 Var-size:
 336   0x14/imm32
 337 
 338 Any-register:  # "*"
 339   # size
 340   1/imm32
 341   # data
 342   2a/asterisk
 343 
 344 List-value:
 345   0/imm32
 346 List-next:
 347   4/imm32
 348 List-size:
 349   8/imm32
 350 
 351 == code
 352 
 353 Entry:
 354     # . prologue
 355     89/<- %ebp 4/r32/esp
 356     (new-segment *Heap-size Heap)
 357     # if (argv[1] == "test') run-tests()
 358     {
 359       # if (argc <= 1) break
 360       81 7/subop/compare *ebp 1/imm32
 361       7e/jump-if-lesser-or-equal break/disp8
 362       # if (argv[1] != "test") break
 363       (kernel-string-equal? *(ebp+8) "test")  # => eax
 364       3d/compare-eax-and 0/imm32
 365       74/jump-if-equal break/disp8
 366       #
 367       (run-tests)
 368       # syscall(exit, *Num-test-failures)
 369       8b/-> *Num-test-failures 3/r32/ebx
 370       eb/jump $mu-main:end/disp8
 371     }
 372     # otherwise convert Stdin
 373     (convert-mu Stdin Stdout)
 374     (flush Stdout)
 375     # syscall(exit, 0)
 376     bb/copy-to-ebx 0/imm32
 377 $mu-main:end:
 378     b8/copy-to-eax 1/imm32/exit
 379     cd/syscall 0x80/imm8
 380 
 381 convert-mu:  # in : (address buffered-file), out : (address buffered-file)
 382     # . prologue
 383     55/push-ebp
 384     89/<- %ebp 4/r32/esp
 385     #
 386     (parse-mu *(ebp+8))
 387     (check-mu-types)
 388     (emit-subx *(ebp+0xc))
 389 $convert-mu:end:
 390     # . epilogue
 391     89/<- %esp 5/r32/ebp
 392     5d/pop-to-ebp
 393     c3/return
 394 
 395 test-convert-empty-input:
 396     # empty input => empty output
 397     # . prologue
 398     55/push-ebp
 399     89/<- %ebp 4/r32/esp
 400     # setup
 401     (clear-stream _test-input-stream)
 402     (clear-stream $_test-input-buffered-file->buffer)
 403     (clear-stream _test-output-stream)
 404     (clear-stream $_test-output-buffered-file->buffer)
 405     #
 406     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 407     (flush _test-output-buffered-file)
 408     (check-stream-equal _test-output-stream "" "F - test-convert-empty-input")
 409     # . epilogue
 410     89/<- %esp 5/r32/ebp
 411     5d/pop-to-ebp
 412     c3/return
 413 
 414 test-convert-function-skeleton:
 415     # empty function decl => function prologue and epilogue
 416     #   fn foo {
 417     #   }
 418     # =>
 419     #   foo:
 420     #     # . prologue
 421     #     55/push-ebp
 422     #     89/<- %ebp 4/r32/esp
 423     #     # . epilogue
 424     #     89/<- %esp 5/r32/ebp
 425     #     5d/pop-to-ebp
 426     #     c3/return
 427     # . prologue
 428     55/push-ebp
 429     89/<- %ebp 4/r32/esp
 430     # setup
 431     (clear-stream _test-input-stream)
 432     (clear-stream $_test-input-buffered-file->buffer)
 433     (clear-stream _test-output-stream)
 434     (clear-stream $_test-output-buffered-file->buffer)
 435     #
 436     (write _test-input-stream "fn foo {\n")
 437     (write _test-input-stream "}\n")
 438     # convert
 439     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 440     (flush _test-output-buffered-file)
 441 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 447     # check output
 448     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-skeleton/0")
 449     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-skeleton/1")
 450     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-skeleton/2")
 451     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-skeleton/3")
 452     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-skeleton/4")
 453     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-skeleton/5")
 454     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-skeleton/6")
 455     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-skeleton/7")
 456     # . epilogue
 457     89/<- %esp 5/r32/ebp
 458     5d/pop-to-ebp
 459     c3/return
 460 
 461 test-convert-multiple-function-skeletons:
 462     # multiple functions correctly organized into a linked list
 463     #   fn foo {
 464     #   }
 465     #   fn bar {
 466     #   }
 467     # =>
 468     #   foo:
 469     #     # . prologue
 470     #     55/push-ebp
 471     #     89/<- %ebp 4/r32/esp
 472     #     # . epilogue
 473     #     89/<- %esp 5/r32/ebp
 474     #     5d/pop-to-ebp
 475     #     c3/return
 476     #   bar:
 477     #     # . prologue
 478     #     55/push-ebp
 479     #     89/<- %ebp 4/r32/esp
 480     #     # . epilogue
 481     #     89/<- %esp 5/r32/ebp
 482     #     5d/pop-to-ebp
 483     #     c3/return
 484     # . prologue
 485     55/push-ebp
 486     89/<- %ebp 4/r32/esp
 487     # setup
 488     (clear-stream _test-input-stream)
 489     (clear-stream $_test-input-buffered-file->buffer)
 490     (clear-stream _test-output-stream)
 491     (clear-stream $_test-output-buffered-file->buffer)
 492     #
 493     (write _test-input-stream "fn foo {\n")
 494     (write _test-input-stream "}\n")
 495     (write _test-input-stream "fn bar {\n")
 496     (write _test-input-stream "}\n")
 497     # convert
 498     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 499     (flush _test-output-buffered-file)
 500 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 506     # check first function
 507     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-multiple-function-skeletons/0")
 508     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/1")
 509     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/2")
 510     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/3")
 511     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/4")
 512     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/5")
 513     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/6")
 514     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/7")
 515     # check second function
 516     (check-next-stream-line-equal _test-output-stream "bar:"                  "F - test-convert-multiple-function-skeletons/10")
 517     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/11")
 518     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/12")
 519     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/13")
 520     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/14")
 521     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/15")
 522     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/16")
 523     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/17")
 524     # . epilogue
 525     89/<- %esp 5/r32/ebp
 526     5d/pop-to-ebp
 527     c3/return
 528 
 529 test-convert-function-with-arg:
 530     # function with one arg
 531     #   fn foo n : int {
 532     #   }
 533     # =>
 534     #   foo:
 535     #     # . prologue
 536     #     55/push-ebp
 537     #     89/<- %ebp 4/r32/esp
 538     #     # . epilogue
 539     #     89/<- %esp 5/r32/ebp
 540     #     5d/pop-to-ebp
 541     #     c3/return
 542     # . prologue
 543     55/push-ebp
 544     89/<- %ebp 4/r32/esp
 545     # setup
 546     (clear-stream _test-input-stream)
 547     (clear-stream $_test-input-buffered-file->buffer)
 548     (clear-stream _test-output-stream)
 549     (clear-stream $_test-output-buffered-file->buffer)
 550     #
 551     (write _test-input-stream "fn foo n : int {\n")
 552     (write _test-input-stream "}\n")
 553     # convert
 554     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 555     (flush _test-output-buffered-file)
 556 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 562     # check output
 563     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-with-arg/0")
 564     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-with-arg/1")
 565     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-with-arg/2")
 566     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-arg/3")
 567     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-with-arg/4")
 568     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-arg/5")
 569     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-with-arg/6")
 570     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-with-arg/7")
 571     # . epilogue
 572     89/<- %esp 5/r32/ebp
 573     5d/pop-to-ebp
 574     c3/return
 575 
 576 test-convert-function-with-arg-and-body:
 577     # function with one arg and one instruction in the body
 578     #   fn foo n : int {
 579     #     increment n
 580     #   }
 581     # =>
 582     #   foo:
 583     #     # . prologue
 584     #     55/push-ebp
 585     #     89/<- %ebp 4/r32/esp
 586     #     {
 587     #       ff 0/subop/increment *(ebp+8)
 588     #     }
 589     #     # . epilogue
 590     #     89/<- %esp 5/r32/ebp
 591     #     5d/pop-to-ebp
 592     #     c3/return
 593     # . prologue
 594     55/push-ebp
 595     89/<- %ebp 4/r32/esp
 596     # setup
 597     (clear-stream _test-input-stream)
 598     (clear-stream $_test-input-buffered-file->buffer)
 599     (clear-stream _test-output-stream)
 600     (clear-stream $_test-output-buffered-file->buffer)
 601     #
 602     (write _test-input-stream "fn foo n : int {\n")
 603     (write _test-input-stream "  increment n\n")
 604     (write _test-input-stream "}\n")
 605     # convert
 606     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 607     (flush _test-output-buffered-file)
 608 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 614     # check output
 615     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-with-arg-and-body/0")
 616     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-with-arg-and-body/1")
 617     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-with-arg-and-body/2")
 618     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-arg-and-body/3")
 619     (check-next-stream-line-equal _test-output-stream "{"                     "F - test-convert-function-with-arg-and-body/4")
 620     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0x00000008)"  "F - test-convert-function-with-arg-and-body/5")
 621     (check-next-stream-line-equal _test-output-stream "}"                     "F - test-convert-function-with-arg-and-body/6")
 622     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-with-arg-and-body/7")
 623     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-arg-and-body/8")
 624     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-with-arg-and-body/9")
 625     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-with-arg-and-body/10")
 626     # . epilogue
 627     89/<- %esp 5/r32/ebp
 628     5d/pop-to-ebp
 629     c3/return
 630 
 631 test-convert-function-distinguishes-args:
 632     # function with two args refers to second one in body
 633     #   fn foo a: int, b: int {
 634     #     increment b
 635     #   }
 636     # =>
 637     #   foo:
 638     #     # . prologue
 639     #     55/push-ebp
 640     #     89/<- %ebp 4/r32/esp
 641     #     {
 642     #       ff 0/subop/increment *(ebp+0xc)
 643     #     }
 644     #     # . epilogue
 645     #     89/<- %esp 5/r32/ebp
 646     #     5d/pop-to-ebp
 647     #     c3/return
 648     # . prologue
 649     55/push-ebp
 650     89/<- %ebp 4/r32/esp
 651     # setup
 652     (clear-stream _test-input-stream)
 653     (clear-stream $_test-input-buffered-file->buffer)
 654     (clear-stream _test-output-stream)
 655     (clear-stream $_test-output-buffered-file->buffer)
 656     #
 657     (write _test-input-stream "fn foo a: int, b: int {\n")
 658     (write _test-input-stream "  increment b\n")
 659     (write _test-input-stream "}\n")
 660     # convert
 661     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 662     (flush _test-output-buffered-file)
 663 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 669     # check output
 670     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-distinguishes-args/0")
 671     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-distinguishes-args/1")
 672     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-distinguishes-args/2")
 673     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-distinguishes-args/3")
 674     (check-next-stream-line-equal _test-output-stream "{"                     "F - test-convert-function-distinguishes-args/4")
 675     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0x0000000c)"  "F - test-convert-function-distinguishes-args/5")
 676     (check-next-stream-line-equal _test-output-stream "}"                     "F - test-convert-function-distinguishes-args/6")
 677     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-distinguishes-args/7")
 678     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-distinguishes-args/8")
 679     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-distinguishes-args/9")
 680     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-distinguishes-args/10")
 681     # . epilogue
 682     89/<- %esp 5/r32/ebp
 683     5d/pop-to-ebp
 684     c3/return
 685 
 686 test-convert-function-returns-result:
 687     # function writes to output
 688     #   fn foo a: int, b: int -> result/eax: int {
 689     #     result <- copy a
 690     #     result <- increment
 691     #   }
 692     # =>
 693     #   foo:
 694     #     # . prologue
 695     #     55/push-ebp
 696     #     89/<- %ebp 4/r32/esp
 697     #     {
 698     #       89/-> *(ebp+8) 0/r32/eax
 699     #       40/increment-eax
 700     #     }
 701     #     # . epilogue
 702     #     89/<- %esp 5/r32/ebp
 703     #     5d/pop-to-ebp
 704     #     c3/return
 705     # . prologue
 706     55/push-ebp
 707     89/<- %ebp 4/r32/esp
 708     # setup
 709     (clear-stream _test-input-stream)
 710     (clear-stream $_test-input-buffered-file->buffer)
 711     (clear-stream _test-output-stream)
 712     (clear-stream $_test-output-buffered-file->buffer)
 713     #
 714     (write _test-input-stream "fn foo a: int, b: int -> result/eax: int {\n")
 715     (write _test-input-stream "  result <- copy a\n")
 716     (write _test-input-stream "  result <- increment\n")
 717     (write _test-input-stream "}\n")
 718     # convert
 719     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 720     (flush _test-output-buffered-file)
 721 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 727     # check output
 728     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-returns-result/0")
 729     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-returns-result/1")
 730     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-returns-result/2")
 731     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-returns-result/3")
 732     (check-next-stream-line-equal _test-output-stream "{"                     "F - test-convert-function-returns-result/4")
 733     (check-next-stream-line-equal _test-output-stream "8b/copy-from *(ebp+0x00000008) 0x00000000/r32"  "F - test-convert-function-returns-result/5")
 734     (check-next-stream-line-equal _test-output-stream "40/increment-eax"      "F - test-convert-function-returns-result/6")
 735     (check-next-stream-line-equal _test-output-stream "}"                     "F - test-convert-function-returns-result/7")
 736     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-returns-result/8")
 737     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-returns-result/9")
 738     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-returns-result/10")
 739     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-returns-result/11")
 740     # . epilogue
 741     89/<- %esp 5/r32/ebp
 742     5d/pop-to-ebp
 743     c3/return
 744 
 745 #######################################################
 746 # Parsing
 747 #######################################################
 748 
 749 parse-mu:  # in : (address buffered-file)
 750     # pseudocode
 751     #   var curr-function : (handle function) = Program
 752     #   var line : (ref stream byte 512)
 753     #   var word-slice : (ref slice)
 754     #   while true                                  # line loop
 755     #     clear-stream(line)
 756     #     read-line-buffered(in, line)
 757     #     if (line->write == 0) break               # end of file
 758     #     word-slice = next-word-or-string(line)
 759     #     if slice-empty?(word-slice)               # end of line
 760     #       continue
 761     #     else if slice-starts-with?(word-slice, "#")  # comment
 762     #       continue                                # end of line
 763     #     else if slice-equal(word-slice, "fn")
 764     #       var new-function : (handle function) = allocate(function)
 765     #       var vars : (ref stack (address var) 256)
 766     #       populate-mu-function-header(in, new-function, vars)
 767     #       populate-mu-function-body(in, new-function, vars)
 768     #       assert(vars->top == 0)
 769     #       *curr-function = new-function
 770     #       curr-function = &new-function->next
 771     #     else
 772     #       abort()
 773     #
 774     # . prologue
 775     55/push-ebp
 776     89/<- %ebp 4/r32/esp
 777     # . save registers
 778     50/push-eax
 779     51/push-ecx
 780     52/push-edx
 781     53/push-ebx
 782     57/push-edi
 783     # var line/ecx : (ref stream byte 512)
 784     81 5/subop/subtract %esp 0x200/imm32
 785     68/push 0x200/imm32/length
 786     68/push 0/imm32/read
 787     68/push 0/imm32/write
 788     89/<- %ecx 4/r32/esp
 789     # var word-slice/edx : (ref slice)
 790     68/push 0/imm32/end
 791     68/push 0/imm32/start
 792     89/<- %edx 4/r32/esp
 793     # var curr-function/edi : (handle function) = Program
 794     bf/copy-to-edi Program/imm32
 795     # var vars/ebx : (ref stack (address var) 256)
 796     81 5/subop/subtract %esp 0x400/imm32
 797     68/push 0x400/imm32/length
 798     68/push 0/imm32/top
 799     89/<- %ebx 4/r32/esp
 800     {
 801 $parse-mu:line-loop:
 802       (clear-stream %ecx)
 803       (read-line-buffered *(ebp+8) %ecx)
 804       # if (line->write == 0) break
 805       81 7/subop/compare *ecx 0/imm32
 806       0f 84/jump-if-equal break/disp32
 807 +--  6 lines: #?       # dump line ---------------------------------------------------------------------------------------------------------------------------
 813       (next-word-or-string %ecx %edx)
 814       # if slice-empty?(word-slice) continue
 815       (slice-empty? %edx)
 816       3d/compare-eax-and 0/imm32
 817       0f 85/jump-if-not-equal loop/disp32
 818       # if (*word-slice->start == "#") continue
 819       # . eax = *word-slice->start
 820       8b/-> *edx 0/r32/eax
 821       8a/copy-byte *eax 0/r32/AL
 822       81 4/subop/and %eax 0xff/imm32
 823       # . if (eax == '#') continue
 824       3d/compare-eax-and 0x23/imm32/hash
 825       0f 84/jump-if-equal loop/disp32
 826       # if (slice-equal?(word-slice, "fn")) parse a function
 827       {
 828 $parse-mu:fn:
 829         (slice-equal? %edx "fn")
 830         3d/compare-eax-and 0/imm32
 831         0f 84/jump-if-equal break/disp32
 832         # var new-function/eax : (handle function) = populate-mu-function(in, new-function, vars)
 833         (allocate Heap *Function-size)  # => eax
 834         (zero-out %eax *Function-size)
 835         (clear-stack %ebx)
 836         (populate-mu-function-header %ecx %eax %ebx)
 837         (populate-mu-function-body *(ebp+8) %eax %ebx)
 838         # *curr-function = new-function
 839         89/<- *edi 0/r32/eax
 840         # curr-function = &new-function->next
 841         8d/address-> *(eax+0x14) 7/r32/edi  # Function-next
 842         e9/jump $parse-mu:line-loop/disp32
 843       }
 844       # otherwise abort
 845       e9/jump $parse-mu:error1/disp32
 846     } # end line loop
 847 $parse-mu:end:
 848     # . reclaim locals
 849     81 0/subop/add %esp 0x630/imm32
 850     # . restore registers
 851     5f/pop-to-edi
 852     5b/pop-to-ebx
 853     5a/pop-to-edx
 854     59/pop-to-ecx
 855     58/pop-to-eax
 856     # . epilogue
 857     89/<- %esp 5/r32/ebp
 858     5d/pop-to-ebp
 859     c3/return
 860 
 861 $parse-mu:error1:
 862     # error("unexpected top-level command: " word-slice "\n")
 863     (write-buffered Stderr "unexpected top-level command: ")
 864     (write-slice-buffered Stderr %edx)
 865     (write-buffered Stderr "\n")
 866     (flush Stderr)
 867     # . syscall(exit, 1)
 868     bb/copy-to-ebx  1/imm32
 869     b8/copy-to-eax  1/imm32/exit
 870     cd/syscall  0x80/imm8
 871     # never gets here
 872 
 873 $parse-mu:error2:
 874     # error(vars->top " vars not reclaimed after fn '" new-function->name "'\n")
 875     (print-int32-buffered Stderr *ebx)
 876     (write-buffered Stderr " vars not reclaimed after fn '")
 877     (write-slice-buffered Stderr *eax)  # Function-name
 878     (write-buffered Stderr "'\n")
 879     (flush Stderr)
 880     # . syscall(exit, 1)
 881     bb/copy-to-ebx  1/imm32
 882     b8/copy-to-eax  1/imm32/exit
 883     cd/syscall  0x80/imm8
 884     # never gets here
 885 
 886 # scenarios considered:
 887 # ✗ fn foo  # no block
 888 # ✓ fn foo {
 889 # ✗ fn foo { {
 890 # ✗ fn foo { }
 891 # ✗ fn foo { } {
 892 # ✗ fn foo x {
 893 # ✗ fn foo x : {
 894 # ✓ fn foo x : int {
 895 # ✓ fn foo x: int {
 896 # ✓ fn foo x: int -> y/eax: int {
 897 populate-mu-function-header:  # first-line : (address stream byte), out : (handle function), vars : (address stack (handle var))
 898     # pseudocode:
 899     #   var name : (ref slice)
 900     #   next-word(first-line, name)
 901     #   assert(name not in '{' '}' '->')
 902     #   out->name = slice-to-string(name)
 903     #   var next-offset : int = 8
 904     #   ## inouts
 905     #   while true
 906     #     ## name
 907     #     name = next-word(first-line)
 908     #     if (name == '{') goto done
 909     #     if (name == '->') break
 910     #     assert(name != '}')
 911     #     var v : (handle var) = parse-var-with-type(name, first-line)
 912     #     assert(v->register == null)
 913     #     v->stack-offset = next-offset
 914     #     next-offset += size-of(v)
 915     #     out->inouts = append(out->inouts, v)
 916     #     push(vars, v)
 917     #   ## outputs
 918     #   while true
 919     #     ## name
 920     #     name = next-word(first-line)
 921     #     assert(name not in '{' '}' '->')
 922     #     var v : (handle var) = parse-var-with-type(name, first-line)
 923     #     assert(v->register != null)
 924     #     out->outputs = append(out->outputs, v)
 925     #   done:
 926     #
 927     # . prologue
 928     55/push-ebp
 929     89/<- %ebp 4/r32/esp
 930     # . save registers
 931     50/push-eax
 932     51/push-ecx
 933     52/push-edx
 934     53/push-ebx
 935     57/push-edi
 936     # edi = out
 937     8b/-> *(ebp+0xc) 7/r32/edi
 938     # var word-slice/ecx : (ref slice)
 939     68/push 0/imm32/end
 940     68/push 0/imm32/start
 941     89/<- %ecx 4/r32/esp
 942     # var next-offset/edx = 8
 943     ba/copy-to-edx 8/imm32
 944     # read function name
 945     (next-word *(ebp+8) %ecx)
 946     # error checking
 947     # if (word-slice == '{') abort
 948     (slice-equal? %ecx "{")   # => eax
 949     3d/compare-eax-and 0/imm32
 950     0f 85/jump-if-not-equal $populate-mu-function-header:error1/disp32
 951     # if (word-slice == '->') abort
 952     (slice-equal? %ecx "->")   # => eax
 953     3d/compare-eax-and 0/imm32
 954     0f 85/jump-if-not-equal $populate-mu-function-header:error1/disp32
 955     # if (word-slice == '}') abort
 956     (slice-equal? %ecx "}")   # => eax
 957     3d/compare-eax-and 0/imm32
 958     0f 85/jump-if-not-equal $populate-mu-function-header:error1/disp32
 959     # save function name
 960     (slice-to-string Heap %ecx)  # => eax
 961     89/<- *edi 0/r32/eax  # Function-name
 962     # save function inouts
 963     {
 964 $populate-mu-function-header:check-for-inout:
 965       (next-word *(ebp+8) %ecx)
 966       # if (word-slice == '{') goto done
 967       (slice-equal? %ecx "{")   # => eax
 968       3d/compare-eax-and 0/imm32
 969       0f 85/jump-if-not-equal $populate-mu-function-header:done/disp32
 970       # if (word-slice == '->') break
 971       (slice-equal? %ecx "->")   # => eax
 972       3d/compare-eax-and 0/imm32
 973       0f 85/jump-if-not-equal break/disp32
 974       # if (word-slice == '}') abort
 975       (slice-equal? %ecx "}")   # => eax
 976       3d/compare-eax-and 0/imm32
 977       0f 85/jump-if-not-equal $populate-mu-function-header:error1/disp32
 978       # var v/ebx : (handle var) = parse-var-with-type(word-slice, first-line)
 979       (parse-var-with-type %ecx *(ebp+8))  # => eax
 980       89/<- %ebx 0/r32/eax
 981       # assert(v->register == null)
 982       81 7/subop/compare *(ebx+0x10) 0/imm32  # Var-register
 983       0f 85/jump-if-not-equal $populate-mu-function-header:error2/disp32
 984       # v->stack-offset = next-offset
 985       89/<- *(ebx+0xc) 2/r32/edx  # Var-stack-offset
 986       # next-offset += size-of(v)
 987       (size-of %ebx)  # => eax
 988       01/add %edx 0/r32/eax
 989       #
 990       (append-list Heap %ebx *(edi+8))  # Function-inouts => eax
 991       89/<- *(edi+8) 0/r32/eax  # Function-inouts
 992       (push *(ebp+0x10) %ebx)
 993       #
 994       e9/jump loop/disp32
 995     }
 996     # save function outputs
 997     {
 998 $parse-var-with-type:check-for-out:
 999       (next-word *(ebp+8) %ecx)
1000       # if (word-slice == '{') break
1001       (slice-equal? %ecx "{")   # => eax
1002       3d/compare-eax-and 0/imm32
1003       0f 85/jump-if-not-equal break/disp32
1004       # if (word-slice == '->') abort
1005       (slice-equal? %ecx "->")   # => eax
1006       3d/compare-eax-and 0/imm32
1007       0f 85/jump-if-not-equal $populate-mu-function-header:error1/disp32
1008       # if (word-slice == '}') abort
1009       (slice-equal? %ecx "}")   # => eax
1010       3d/compare-eax-and 0/imm32
1011       0f 85/jump-if-not-equal $populate-mu-function-header:error1/disp32
1012       #
1013       (parse-var-with-type %ecx *(ebp+8))  # => eax
1014       89/<- %ebx 0/r32/eax
1015       # assert(var->register != null)
1016       81 7/subop/compare *(ebx+0x10) 0/imm32  # Var-register
1017       0f 84/jump-if-equal $populate-mu-function-header:error3/disp32
1018       (append-list Heap %ebx *(edi+0xc))  # Function-outputs => eax
1019       89/<- *(edi+0xc) 0/r32/eax  # Function-outputs
1020       e9/jump loop/disp32
1021     }
1022 $populate-mu-function-header:done:
1023     (check-no-tokens-left *(ebp+8))
1024 $populate-mu-function-header:end:
1025     # . reclaim locals
1026     81 0/subop/add %esp 8/imm32
1027     # . restore registers
1028     5f/pop-to-edi
1029     5b/pop-to-ebx
1030     5a/pop-to-edx
1031     59/pop-to-ecx
1032     58/pop-to-eax
1033     # . epilogue
1034     89/<- %esp 5/r32/ebp
1035     5d/pop-to-ebp
1036     c3/return
1037 
1038 $populate-mu-function-header:error1:
1039     # error("function header not in form 'fn <name> {'")
1040     (write-buffered Stderr "function header not in form 'fn <name> [inouts] [-> outputs] {' -- '")
1041     (flush Stderr)
1042     (rewind-stream *(ebp+8))
1043     (write-stream 2 *(ebp+8))
1044     (write-buffered Stderr "'\n")
1045     (flush Stderr)
1046     # . syscall(exit, 1)
1047     bb/copy-to-ebx  1/imm32
1048     b8/copy-to-eax  1/imm32/exit
1049     cd/syscall  0x80/imm8
1050     # never gets here
1051 
1052 $populate-mu-function-header:error2:
1053     # error("function input '" var "' cannot be in a register")
1054     (write-buffered Stderr "function input '")
1055     (write-buffered Stderr *ebx)  # Var-name
1056     (write-buffered Stderr "' cannot be in a register")
1057     (flush Stderr)
1058     # . syscall(exit, 1)
1059     bb/copy-to-ebx  1/imm32
1060     b8/copy-to-eax  1/imm32/exit
1061     cd/syscall  0x80/imm8
1062     # never gets here
1063 
1064 $populate-mu-function-header:error3:
1065     # error("function input '" var "' must be in a register")
1066     (write-buffered Stderr "function input '")
1067     (write-buffered Stderr *eax)  # Var-name
1068     (write-buffered Stderr " must be in a register'")
1069     (flush Stderr)
1070     (rewind-stream *(ebp+8))
1071     (write-stream 2 *(ebp+8))
1072     (write-buffered Stderr "'\n")
1073     (flush Stderr)
1074     # . syscall(exit, 1)
1075     bb/copy-to-ebx  1/imm32
1076     b8/copy-to-eax  1/imm32/exit
1077     cd/syscall  0x80/imm8
1078     # never gets here
1079 
1080 test-function-header-with-arg:
1081     # 'foo n : int {'
1082     # . prologue
1083     55/push-ebp
1084     89/<- %ebp 4/r32/esp
1085     # setup
1086     (clear-stream _test-input-stream)
1087     (write _test-input-stream "foo n : int {\n")
1088     # result/ecx : (ref function)
1089     2b/subtract-> *Function-size 4/r32/esp
1090     89/<- %ecx 4/r32/esp
1091     (zero-out %ecx *Function-size)
1092     # var vars/ebx : (ref stack (address var) 16)
1093     81 5/subop/subtract %esp 0x10/imm32
1094     68/push 0x10/imm32/length
1095     68/push 0/imm32/top
1096     89/<- %ebx 4/r32/esp
1097     # convert
1098     (populate-mu-function-header _test-input-stream %ecx %ebx)
1099     # check result
1100     (check-strings-equal *ecx "foo" "F - test-function-header-with-arg/name")  # Function-name
1101     # edx : (handle list var) = result->inouts
1102     8b/-> *(ecx+8) 2/r32/edx  # Function-inouts
1103     # ebx : (handle var) = result->inouts->value
1104     8b/-> *edx 3/r32/ebx  # List-value
1105     (check-strings-equal *ebx "n" "F - test-function-header-with-arg/inout:0")  # Var-name
1106     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:0/type")  # Var-type
1107     # . epilogue
1108     89/<- %esp 5/r32/ebp
1109     5d/pop-to-ebp
1110     c3/return
1111 
1112 test-function-header-with-multiple-args:
1113     # 'fn foo a: int, b: int, c: int {'
1114     # . prologue
1115     55/push-ebp
1116     89/<- %ebp 4/r32/esp
1117     # setup
1118     (clear-stream _test-input-stream)
1119     (write _test-input-stream "foo a: int, b: int c: int {\n")
1120     # result/ecx : (handle function)
1121     2b/subtract-> *Function-size 4/r32/esp
1122     89/<- %ecx 4/r32/esp
1123     (zero-out %ecx *Function-size)
1124     # var vars/ebx : (ref stack (address var) 16)
1125     81 5/subop/subtract %esp 0x10/imm32
1126     68/push 0x10/imm32/length
1127     68/push 0/imm32/top
1128     89/<- %ebx 4/r32/esp
1129     # convert
1130     (populate-mu-function-header _test-input-stream %ecx %ebx)
1131     # check result
1132     (check-strings-equal *ecx "foo")  # Function-name
1133     # edx : (handle list var) = result->inouts
1134     8b/-> *(ecx+8) 2/r32/edx  # Function-inouts
1135 $test-function-header-with-multiple-args:inout0:
1136     # ebx : (handle var) = result->inouts->value
1137     8b/-> *edx 3/r32/ebx  # List-value
1138     (check-strings-equal *ebx "a" "F - test-function-header-with-multiple-args/inout:0")  # Var-name
1139     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-multiple-args/inout:0/type")  # Var-type
1140     # edx = result->inouts->next
1141     8b/-> *(edx+4) 2/r32/edx  # List-next
1142 $test-function-header-with-multiple-args:inout1:
1143     # ebx = result->inouts->next->value
1144     8b/-> *edx 3/r32/ebx  # List-value
1145     (check-strings-equal *ebx "b" "F - test-function-header-with-multiple-args/inout:1")  # Var-name
1146     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-multiple-args/inout:1/type")  # Var-type
1147     # edx = result->inouts->next->next
1148     8b/-> *(edx+4) 2/r32/edx  # List-next
1149 $test-function-header-with-multiple-args:inout2:
1150     # ebx = result->inouts->next->next->value
1151     8b/-> *edx 3/r32/ebx  # List-value
1152     (check-strings-equal *ebx "c" "F - test-function-header-with-multiple-args/inout:2")  # Var-name
1153     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-multiple-args/inout:2/type")  # Var-type
1154     # . epilogue
1155     89/<- %esp 5/r32/ebp
1156     5d/pop-to-ebp
1157     c3/return
1158 
1159 test-function-with-multiple-args-and-outputs:
1160     # fn foo a: int, b: int, c: int -> x: int, y: int {
1161     # . prologue
1162     55/push-ebp
1163     89/<- %ebp 4/r32/esp
1164     # setup
1165     (clear-stream _test-input-stream)
1166     (write _test-input-stream "foo a: int, b: int, c: int -> x/ecx: int y/edx : int {\n")
1167     # result/ecx : (handle function)
1168     2b/subtract-> *Function-size 4/r32/esp
1169     89/<- %ecx 4/r32/esp
1170     (zero-out %ecx *Function-size)
1171     # var vars/ebx : (ref stack (address var) 16)
1172     81 5/subop/subtract %esp 0x10/imm32
1173     68/push 0x10/imm32/length
1174     68/push 0/imm32/top
1175     89/<- %ebx 4/r32/esp
1176     # convert
1177     (populate-mu-function-header _test-input-stream %ecx %ebx)
1178     # check result
1179     (check-strings-equal *ecx "foo")  # Function-name
1180     # edx : (handle list var) = result->inouts
1181     8b/-> *(ecx+8) 2/r32/edx  # Function-inouts
1182     # ebx : (handle var) = result->inouts->value
1183     8b/-> *edx 3/r32/ebx  # List-value
1184     (check-strings-equal *ebx "a" "F - test-function-header-with-multiple-args/inout:0")  # Var-name
1185     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:0/type")  # Var-type
1186     # edx = result->inouts->next
1187     8b/-> *(edx+4) 2/r32/edx  # List-next
1188     # ebx = result->inouts->next->value
1189     8b/-> *edx 3/r32/ebx  # List-value
1190     (check-strings-equal *ebx "b" "F - test-function-header-with-multiple-args/inout:1")  # Var-name
1191     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:1/type")  # Var-type
1192     # edx = result->inouts->next->next
1193     8b/-> *(edx+4) 2/r32/edx  # List-next
1194     # ebx = result->inouts->next->next->value
1195     8b/-> *edx 3/r32/ebx  # List-value
1196     (check-strings-equal *ebx "c" "F - test-function-header-with-multiple-args/inout:2")  # Var-name
1197     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:2/type")  # Var-type
1198     # edx : (handle list var) = result->outputs
1199     8b/-> *(ecx+0xc) 2/r32/edx  # Function-outputs
1200     # ebx : (handle var) = result->outputs->value
1201     8b/-> *edx 3/r32/ebx  # List-value
1202     (check-strings-equal *ebx "x" "F - test-function-header-with-multiple-args/output:0")  # Var-name
1203     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/output:0/type")  # Var-type
1204     (check-strings-equal *(ebx+0x10) "ecx" "F - test-function-header-with-arg/output:0/register")  # Var-register
1205     # edx = result->outputs->next
1206     8b/-> *(edx+4) 2/r32/edx  # List-next
1207     # ebx = result->outputs->next->value
1208     8b/-> *edx 3/r32/ebx  # List-value
1209     (check-strings-equal *ebx "y" "F - test-function-header-with-multiple-args/output:1")  # Var-name
1210     (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/output:1/type")  # Var-type
1211     (check-strings-equal *(ebx+0x10) "edx" "F - test-function-header-with-arg/output:0/register")  # Var-register
1212     # . epilogue
1213     89/<- %esp 5/r32/ebp
1214     5d/pop-to-ebp
1215     c3/return
1216 
1217 # format for variables with types
1218 #   x : int
1219 #   x: int
1220 #   x: int,
1221 # ignores at most one trailing colon or comma
1222 parse-var-with-type:  # name: (address slice), first-line: (address stream byte) -> result/eax: (handle var)
1223     # pseudocode:
1224     #   var v : (handle var) = allocate(Heap, Var-size)
1225     #   var s : (ref slice)
1226     #   next-token-from-slice(name->start, name->end, '/', s)
1227     #   var end : (address byte) = s->end
1228     #   if (slice-ends-with(s, ":"))
1229     #     decrement s->end
1230     #   if (slice-ends-with(s, ","))
1231     #     decrement s->end
1232     #   v->name = slice-to-string(s)
1233     #   ## register
1234     #   next-token-from-slice(end, name->end, '/', s)
1235     #   if (slice-ends-with(s, ":"))
1236     #     decrement s->end
1237     #   if (slice-ends-with(s, ","))
1238     #     decrement s->end
1239     #   if (!slice-empty?(s))
1240     #     v->register = slice-to-string(s)
1241     #   ## type
1242     #   s = next-mu-token(first-line)
1243     #   assert(s not in '{' '}' '->')
1244     #   if (slice-empty?(s)) {
1245     #     s = next-mu-token(first-line)
1246     #     assert(type not in '{' '}' '->')
1247     #   }
1248     #   type = type-for(s)
1249     #   v->type = type
1250     #   return v
1251     #
1252     # . prologue
1253     55/push-ebp
1254     89/<- %ebp 4/r32/esp
1255     # . save registers
1256     51/push-ecx
1257     52/push-edx
1258     53/push-ebx
1259     56/push-esi
1260     57/push-edi
1261     # var result/edi : (handle var) = allocate(Heap, Var-size)
1262     (allocate Heap *Var-size)  # => eax
1263     (zero-out %eax *Var-size)
1264     89/<- %edi 0/r32/eax
1265     # esi = name
1266     8b/-> *(ebp+8) 6/r32/esi
1267     # var s/ecx : (ref slice)
1268     68/push 0/imm32/end
1269     68/push 0/imm32/start
1270     89/<- %ecx 4/r32/esp
1271 $parse-var-with-type:save-name:
1272     # save v->name
1273     (next-token-from-slice *esi *(esi+4) 0x2f %ecx)  # Slice-start, Slice-end, '/'
1274     # . end/edx = s->end
1275     8b/-> *(ecx+4) 2/r32/edx
1276     # . if s ends with ':', decrement s->end
1277     {
1278       8b/-> *(ecx+4) 0/r32/eax
1279       48/decrement-eax
1280       8a/copy-byte *eax 3/r32/BL
1281       81 4/subop/and %ebx 0xff/imm32
1282       81 7/subop/compare %ebx 0x3a/imm32/colon
1283       75/jump-if-not-equal break/disp8
1284       89/<- *(ecx+4) 0/r32/eax
1285     }
1286     # . if s ends with ',', decrement s->end
1287     {
1288       8b/-> *(ecx+4) 0/r32/eax
1289       48/decrement-eax
1290       8a/copy-byte *eax 3/r32/BL
1291       81 4/subop/and %ebx 0xff/imm32
1292       81 7/subop/compare %ebx 0x2c/imm32/comma
1293       75/jump-if-not-equal break/disp8
1294       89/<- *(ecx+4) 0/r32/eax
1295     }
1296 $parse-var-with-type:write-name:
1297     (slice-to-string Heap %ecx)  # => eax
1298     89/<- *edi 0/r32/eax  # Var-name
1299     # save v->register
1300 $parse-var-with-type:save-register:
1301     (next-token-from-slice %edx *(esi+4) 0x2f %ecx)  # end, name->end, '/'
1302     # . if s ends with ':', decrement s->end
1303     {
1304       8b/-> *(ecx+4) 0/r32/eax
1305       48/decrement-eax
1306       8a/copy-byte *eax 3/r32/BL
1307       81 4/subop/and %ebx 0xff/imm32
1308       81 7/subop/compare %ebx 0x3a/imm32/colon
1309       75/jump-if-not-equal break/disp8
1310       89/<- *(ecx+4) 0/r32/eax
1311     }
1312     # . if s ends with ',', decrement s->end
1313     {
1314       8b/-> *(ecx+4) 0/r32/eax
1315       48/decrement-eax
1316       8a/copy-byte *eax 3/r32/BL
1317       81 4/subop/and %ebx 0xff/imm32
1318       81 7/subop/compare %ebx 0x2c/imm32/comma
1319       75/jump-if-not-equal break/disp8
1320       89/<- *(ecx+4) 0/r32/eax
1321     }
1322     # if (!slice-empty?(s)) v->register = slice-to-string(s)
1323     {
1324 $parse-var-with-type:write-register:
1325       # HACK: s->end can be less than s->start with all the decrements above
1326       # That's probably a sign we have the wrong algorithm for this function.
1327       8b/-> *ecx 0/r32/eax
1328       39/compare 0/r32/eax *(ecx+4)
1329       76/jump-if-lesser-or-equal break/disp8
1330       (slice-to-string Heap %ecx)
1331       89/<- *(edi+0x10) 0/r32/eax  # Var-register
1332     }
1333     # save v->type
1334     (next-mu-token *(ebp+0xc) %ecx)
1335     # if (word-slice == '{') abort
1336     (slice-equal? %ecx "{")   # => eax
1337     3d/compare-eax-and 0/imm32
1338     0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1339     # if (word-slice == '->') abort
1340     (slice-equal? %ecx "->")   # => eax
1341     3d/compare-eax-and 0/imm32
1342     0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1343     # if (word-slice == '}') abort
1344     (slice-equal? %ecx "}")   # => eax
1345     3d/compare-eax-and 0/imm32
1346     0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1347     # if (slice-empty?(type)) skip
1348     (slice-empty? %ecx)
1349     {
1350       3d/compare-eax-and 0/imm32
1351       0f 84/jump-if-equal break/disp32
1352       (next-mu-token *(ebp+0xc) %ecx)
1353       # if (word-slice == '{') abort
1354       (slice-equal? %ecx "{")   # => eax
1355       3d/compare-eax-and 0/imm32
1356       0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1357       # if (word-slice == '->') abort
1358       (slice-equal? %ecx "->")   # => eax
1359       3d/compare-eax-and 0/imm32
1360       0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1361       # if (word-slice == '}') abort
1362       (slice-equal? %ecx "}")   # => eax
1363       3d/compare-eax-and 0/imm32
1364       0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
1365     }
1366     (type-for %ecx)
1367     89/<- *(edi+4) 0/r32/eax  # Var-type
1368 $parse-var-with-type:end:
1369     # return result
1370     89/<- %eax 7/r32/edi
1371     # . reclaim locals
1372     81 0/subop/add %esp 8/imm32
1373     # . restore registers
1374     5f/pop-to-edi
1375     5e/pop-to-esi
1376     5b/pop-to-ebx
1377     5a/pop-to-edx
1378     59/pop-to-ecx
1379     # . epilogue
1380     89/<- %esp 5/r32/ebp
1381     5d/pop-to-ebp
1382     c3/return
1383 
1384 $parse-var-with-type:abort:
1385     # error("function header not in form 'fn <name> {'")
1386     (write-buffered Stderr "var should have form 'name: type' in '")
1387     (flush Stderr)
1388     (rewind-stream *(ebp+0xc))
1389     (write-stream 2 *(ebp+0xc))
1390     (write-buffered Stderr "'\n")
1391     (flush Stderr)
1392     # . syscall(exit, 1)
1393     bb/copy-to-ebx  1/imm32
1394     b8/copy-to-eax  1/imm32/exit
1395     cd/syscall  0x80/imm8
1396     # never gets here
1397 
1398 next-mu-token:  # in: (address stream byte), out: (address slice)
1399     # . prologue
1400     55/push-ebp
1401     89/<- %ebp 4/r32/esp
1402     # . save registers
1403     50/push-eax
1404     57/push-edi
1405     # edi = out
1406     8b/-> *(ebp+0xc) 7/r32/edi
1407     #
1408     (next-word *(ebp+8) %edi)  # TODO: support s-expressions
1409     # if out ends with ':', decrement out->end
1410     {
1411       8b/-> *(edi+4) 0/r32/eax
1412       48/decrement-eax
1413       8a/copy-byte *eax 3/r32/BL
1414       81 4/subop/and %ebx 0xff/imm32
1415       81 7/subop/compare %ebx 0x3a/imm32/colon
1416       75/jump-if-not-equal break/disp8
1417       89/<- *(edi+4) 0/r32/eax
1418     }
1419     # if out ends with ',', decrement out->end
1420     {
1421       8b/-> *(edi+4) 0/r32/eax
1422       48/decrement-eax
1423       8a/copy-byte *eax 3/r32/BL
1424       81 4/subop/and %ebx 0xff/imm32
1425       81 7/subop/compare %ebx 0x2c/imm32/comma
1426       75/jump-if-not-equal break/disp8
1427       89/<- *(edi+4) 0/r32/eax
1428     }
1429 $next-mu-token:end:
1430     b8/copy-to-eax 1/imm32/int
1431     # . restore registers
1432     5f/pop-to-edi
1433     58/pop-to-eax
1434     # . epilogue
1435     89/<- %esp 5/r32/ebp
1436     5d/pop-to-ebp
1437     c3/return
1438 
1439 type-for:  # name: (address slice) -> result/eax: (handle s-expression type-id)
1440     # . prologue
1441     55/push-ebp
1442     89/<- %ebp 4/r32/esp
1443     # . save registers
1444 #?     (write-buffered Stderr "type: ")
1445 #?     (write-slice-buffered Stderr *(ebp+8))
1446 #?     (write-buffered Stderr Newline)
1447 #?     (flush Stderr)
1448 $type-for:end:
1449     b8/copy-to-eax 1/imm32/int
1450     # . restore registers
1451     # . epilogue
1452     89/<- %esp 5/r32/ebp
1453     5d/pop-to-ebp
1454     c3/return
1455 
1456 test-parse-var-with-type:
1457     # . prologue
1458     55/push-ebp
1459     89/<- %ebp 4/r32/esp
1460     # (eax..ecx) = "x:"
1461     b8/copy-to-eax "x:"/imm32
1462     8b/-> *eax 1/r32/ecx
1463     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1464     05/add-to-eax 4/imm32
1465     # var slice/ecx : (ref slice) = {eax, ecx}
1466     51/push-ecx
1467     50/push-eax
1468     89/<- %ecx 4/r32/esp
1469     # _test-input-stream contains "int"
1470     (clear-stream _test-input-stream)
1471     (write _test-input-stream "int")
1472     #
1473     (parse-var-with-type %ecx _test-input-stream)
1474     8b/-> *eax 2/r32/edx  # Var-name
1475     (check-strings-equal %edx "x" "F - test-var-with-type/name")
1476     8b/-> *(eax+4) 2/r32/edx  # Var-type
1477     (check-ints-equal %edx 1 "F - test-var-with-type/type")
1478     # . epilogue
1479     89/<- %esp 5/r32/ebp
1480     5d/pop-to-ebp
1481     c3/return
1482 
1483 test-parse-var-with-type-and-register:
1484     # . prologue
1485     55/push-ebp
1486     89/<- %ebp 4/r32/esp
1487     # (eax..ecx) = "x/eax"
1488     b8/copy-to-eax "x/eax"/imm32
1489     8b/-> *eax 1/r32/ecx
1490     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1491     05/add-to-eax 4/imm32
1492     # var slice/ecx : (ref slice) = {eax, ecx}
1493     51/push-ecx
1494     50/push-eax
1495     89/<- %ecx 4/r32/esp
1496     # _test-input-stream contains ": int"
1497     (clear-stream _test-input-stream)
1498     (write _test-input-stream ": int")
1499     #
1500     (parse-var-with-type %ecx _test-input-stream)
1501     8b/-> *eax 2/r32/edx  # Var-name
1502     (check-strings-equal %edx "x" "F - test-var-with-type-and-register/name")
1503     8b/-> *(eax+0x10) 2/r32/edx  # Var-register
1504     (check-strings-equal %edx "eax" "F - test-var-with-type-and-register/register")
1505     8b/-> *(eax+4) 2/r32/edx  # Var-type
1506     (check-ints-equal %edx 1 "F - test-var-with-type-and-register/type")
1507     # . epilogue
1508     89/<- %esp 5/r32/ebp
1509     5d/pop-to-ebp
1510     c3/return
1511 
1512 test-parse-var-with-trailing-characters:
1513     # . prologue
1514     55/push-ebp
1515     89/<- %ebp 4/r32/esp
1516     # (eax..ecx) = "x:"
1517     b8/copy-to-eax "x:"/imm32
1518     8b/-> *eax 1/r32/ecx
1519     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1520     05/add-to-eax 4/imm32
1521     # var slice/ecx : (ref slice) = {eax, ecx}
1522     51/push-ecx
1523     50/push-eax
1524     89/<- %ecx 4/r32/esp
1525     # _test-input-stream contains "int,"
1526     (clear-stream _test-input-stream)
1527     (write _test-input-stream "int,")
1528     #
1529     (parse-var-with-type %ecx _test-input-stream)
1530     8b/-> *eax 2/r32/edx  # Var-name
1531     (check-strings-equal %edx "x" "F - test-var-with-trailing-characters/name")
1532     8b/-> *(eax+0x10) 2/r32/edx  # Var-register
1533     (check-ints-equal %edx 0 "F - test-var-with-trailing-characters/register")
1534     8b/-> *(eax+4) 2/r32/edx  # Var-type
1535     (check-ints-equal %edx 1 "F - test-var-with-trailing-characters/type")
1536     # . epilogue
1537     89/<- %esp 5/r32/ebp
1538     5d/pop-to-ebp
1539     c3/return
1540 
1541 test-parse-var-with-register-and-trailing-characters:
1542     # . prologue
1543     55/push-ebp
1544     89/<- %ebp 4/r32/esp
1545     # (eax..ecx) = "x/eax:"
1546     b8/copy-to-eax "x/eax:"/imm32
1547     8b/-> *eax 1/r32/ecx
1548     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1549     05/add-to-eax 4/imm32
1550     # var slice/ecx : (ref slice) = {eax, ecx}
1551     51/push-ecx
1552     50/push-eax
1553     89/<- %ecx 4/r32/esp
1554     # _test-input-stream contains "int,"
1555     (clear-stream _test-input-stream)
1556     (write _test-input-stream "int,")
1557     #
1558     (parse-var-with-type %ecx _test-input-stream)
1559     8b/-> *eax 2/r32/edx  # Var-name
1560     (check-strings-equal %edx "x" "F - test-var-with-register-and-trailing-characters/name")
1561     8b/-> *(eax+0x10) 2/r32/edx  # Var-register
1562     (check-strings-equal %edx "eax" "F - test-var-with-register-and-trailing-characters/register")
1563     8b/-> *(eax+4) 2/r32/edx  # Var-type
1564     (check-ints-equal %edx 1 "F - test-var-with-register-and-trailing-characters/type")
1565     # . epilogue
1566     89/<- %esp 5/r32/ebp
1567     5d/pop-to-ebp
1568     c3/return
1569 
1570 # identifier starts with a letter or '$' or '_'
1571 # no constraints at the moment on later letters
1572 # all we really want to do so far is exclude '{', '}' and '->'
1573 is-identifier?:  # in : (address slice) -> result/eax : boolean
1574     # . prologue
1575     55/push-ebp
1576     89/<- %ebp 4/r32/esp
1577     # if (slice-empty?(in)) return false
1578     (slice-empty? *(ebp+8))  # => eax
1579     3d/compare-eax-and 0/imm32
1580     75/jump-if-not-equal $is-identifier?:false/disp8
1581     # var c/eax : byte = *in->start
1582     8b/-> *(ebp+8) 0/r32/eax
1583     8b/-> *eax 0/r32/eax
1584     8a/copy-byte *eax 0/r32/AL
1585     81 4/subop/and %eax 0xff/imm32
1586     # if (c == '$') return true
1587     3d/compare-eax-and 0x24/imm32/$
1588     74/jump-if-equal $is-identifier?:true/disp8
1589     # if (c == '_') return true
1590     3d/compare-eax-and 0x5f/imm32/_
1591     74/jump-if-equal $is-identifier?:true/disp8
1592     # drop case
1593     25/and-eax-with 0x5f/imm32
1594     # if (c < 'A') return false
1595     3d/compare-eax-and 0x41/imm32/A
1596     7c/jump-if-lesser $is-identifier?:false/disp8
1597     # if (c > 'Z') return false
1598     3d/compare-eax-and 0x5a/imm32/Z
1599     7f/jump-if-greater $is-identifier?:false/disp8
1600     # otherwise return true
1601 $is-identifier?:true:
1602     b8/copy-to-eax 1/imm32/true
1603     eb/jump $is-identifier?:end/disp8
1604 $is-identifier?:false:
1605     b8/copy-to-eax 0/imm32/false
1606 $is-identifier?:end:
1607     # . epilogue
1608     89/<- %esp 5/r32/ebp
1609     5d/pop-to-ebp
1610     c3/return
1611 
1612 test-is-identifier-dollar:
1613     # . prologue
1614     55/push-ebp
1615     89/<- %ebp 4/r32/esp
1616     # (eax..ecx) = "$a"
1617     b8/copy-to-eax "$a"/imm32
1618     8b/-> *eax 1/r32/ecx
1619     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1620     05/add-to-eax 4/imm32
1621     # var slice/ecx : (ref slice) = {eax, ecx}
1622     51/push-ecx
1623     50/push-eax
1624     89/<- %ecx 4/r32/esp
1625     #
1626     (is-identifier? %ecx)
1627     (check-ints-equal %eax 1 "F - test-is-identifier-dollar")
1628     # . epilogue
1629     89/<- %esp 5/r32/ebp
1630     5d/pop-to-ebp
1631     c3/return
1632 
1633 test-is-identifier-underscore:
1634     # . prologue
1635     55/push-ebp
1636     89/<- %ebp 4/r32/esp
1637     # (eax..ecx) = "_a"
1638     b8/copy-to-eax "_a"/imm32
1639     8b/-> *eax 1/r32/ecx
1640     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1641     05/add-to-eax 4/imm32
1642     # var slice/ecx : (ref slice) = {eax, ecx}
1643     51/push-ecx
1644     50/push-eax
1645     89/<- %ecx 4/r32/esp
1646     #
1647     (is-identifier? %ecx)
1648     (check-ints-equal %eax 1 "F - test-is-identifier-underscore")
1649     # . epilogue
1650     89/<- %esp 5/r32/ebp
1651     5d/pop-to-ebp
1652     c3/return
1653 
1654 test-is-identifier-a:
1655     # . prologue
1656     55/push-ebp
1657     89/<- %ebp 4/r32/esp
1658     # (eax..ecx) = "a$"
1659     b8/copy-to-eax "a$"/imm32
1660     8b/-> *eax 1/r32/ecx
1661     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1662     05/add-to-eax 4/imm32
1663     # var slice/ecx : (ref slice) = {eax, ecx}
1664     51/push-ecx
1665     50/push-eax
1666     89/<- %ecx 4/r32/esp
1667     #
1668     (is-identifier? %ecx)
1669     (check-ints-equal %eax 1 "F - test-is-identifier-a")
1670     # . epilogue
1671     89/<- %esp 5/r32/ebp
1672     5d/pop-to-ebp
1673     c3/return
1674 
1675 test-is-identifier-z:
1676     # . prologue
1677     55/push-ebp
1678     89/<- %ebp 4/r32/esp
1679     # (eax..ecx) = "z$"
1680     b8/copy-to-eax "z$"/imm32
1681     8b/-> *eax 1/r32/ecx
1682     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1683     05/add-to-eax 4/imm32
1684     # var slice/ecx : (ref slice) = {eax, ecx}
1685     51/push-ecx
1686     50/push-eax
1687     89/<- %ecx 4/r32/esp
1688     #
1689     (is-identifier? %ecx)
1690     (check-ints-equal %eax 1 "F - test-is-identifier-z")
1691     # . epilogue
1692     89/<- %esp 5/r32/ebp
1693     5d/pop-to-ebp
1694     c3/return
1695 
1696 test-is-identifier-A:
1697     # . prologue
1698     55/push-ebp
1699     89/<- %ebp 4/r32/esp
1700     # (eax..ecx) = "A$"
1701     b8/copy-to-eax "A$"/imm32
1702     8b/-> *eax 1/r32/ecx
1703     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1704     05/add-to-eax 4/imm32
1705     # var slice/ecx : (ref slice) = {eax, ecx}
1706     51/push-ecx
1707     50/push-eax
1708     89/<- %ecx 4/r32/esp
1709     #
1710     (is-identifier? %ecx)
1711     (check-ints-equal %eax 1 "F - test-is-identifier-A")
1712     # . epilogue
1713     89/<- %esp 5/r32/ebp
1714     5d/pop-to-ebp
1715     c3/return
1716 
1717 test-is-identifier-Z:
1718     # . prologue
1719     55/push-ebp
1720     89/<- %ebp 4/r32/esp
1721     # (eax..ecx) = "Z$"
1722     b8/copy-to-eax "Z$"/imm32
1723     8b/-> *eax 1/r32/ecx
1724     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1725     05/add-to-eax 4/imm32
1726     # var slice/ecx : (ref slice) = {eax, ecx}
1727     51/push-ecx
1728     50/push-eax
1729     89/<- %ecx 4/r32/esp
1730     #
1731     (is-identifier? %ecx)
1732     (check-ints-equal %eax 1 "F - test-is-identifier-Z")
1733     # . epilogue
1734     89/<- %esp 5/r32/ebp
1735     5d/pop-to-ebp
1736     c3/return
1737 
1738 test-is-identifier-@:
1739     # character before 'A' is invalid
1740     # . prologue
1741     55/push-ebp
1742     89/<- %ebp 4/r32/esp
1743     # (eax..ecx) = "@a"
1744     b8/copy-to-eax "@a"/imm32
1745     8b/-> *eax 1/r32/ecx
1746     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1747     05/add-to-eax 4/imm32
1748     # var slice/ecx : (ref slice) = {eax, ecx}
1749     51/push-ecx
1750     50/push-eax
1751     89/<- %ecx 4/r32/esp
1752     #
1753     (is-identifier? %ecx)
1754     (check-ints-equal %eax 0 "F - test-is-identifier-@")
1755     # . epilogue
1756     89/<- %esp 5/r32/ebp
1757     5d/pop-to-ebp
1758     c3/return
1759 
1760 test-is-identifier-square-bracket:
1761     # character after 'Z' is invalid
1762     # . prologue
1763     55/push-ebp
1764     89/<- %ebp 4/r32/esp
1765     # (eax..ecx) = "[a"
1766     b8/copy-to-eax "[a"/imm32
1767     8b/-> *eax 1/r32/ecx
1768     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1769     05/add-to-eax 4/imm32
1770     # var slice/ecx : (ref slice) = {eax, ecx}
1771     51/push-ecx
1772     50/push-eax
1773     89/<- %ecx 4/r32/esp
1774     #
1775     (is-identifier? %ecx)
1776     (check-ints-equal %eax 0 "F - test-is-identifier-@")
1777     # . epilogue
1778     89/<- %esp 5/r32/ebp
1779     5d/pop-to-ebp
1780     c3/return
1781 
1782 test-is-identifier-backtick:
1783     # character before 'a' is invalid
1784     # . prologue
1785     55/push-ebp
1786     89/<- %ebp 4/r32/esp
1787     # (eax..ecx) = "`a"
1788     b8/copy-to-eax "`a"/imm32
1789     8b/-> *eax 1/r32/ecx
1790     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1791     05/add-to-eax 4/imm32
1792     # var slice/ecx : (ref slice) = {eax, ecx}
1793     51/push-ecx
1794     50/push-eax
1795     89/<- %ecx 4/r32/esp
1796     #
1797     (is-identifier? %ecx)
1798     (check-ints-equal %eax 0 "F - test-is-identifier-backtick")
1799     # . epilogue
1800     89/<- %esp 5/r32/ebp
1801     5d/pop-to-ebp
1802     c3/return
1803 
1804 test-is-identifier-curly-brace-open:
1805     # character after 'z' is invalid; also used for blocks
1806     # . prologue
1807     55/push-ebp
1808     89/<- %ebp 4/r32/esp
1809     # (eax..ecx) = "{a"
1810     b8/copy-to-eax "{a"/imm32
1811     8b/-> *eax 1/r32/ecx
1812     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1813     05/add-to-eax 4/imm32
1814     # var slice/ecx : (ref slice) = {eax, ecx}
1815     51/push-ecx
1816     50/push-eax
1817     89/<- %ecx 4/r32/esp
1818     #
1819     (is-identifier? %ecx)
1820     (check-ints-equal %eax 0 "F - test-is-identifier-curly-brace-open")
1821     # . epilogue
1822     89/<- %esp 5/r32/ebp
1823     5d/pop-to-ebp
1824     c3/return
1825 
1826 test-is-identifier-curly-brace-close:
1827     # . prologue
1828     55/push-ebp
1829     89/<- %ebp 4/r32/esp
1830     # (eax..ecx) = "}a"
1831     b8/copy-to-eax "}a"/imm32
1832     8b/-> *eax 1/r32/ecx
1833     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1834     05/add-to-eax 4/imm32
1835     # var slice/ecx : (ref slice) = {eax, ecx}
1836     51/push-ecx
1837     50/push-eax
1838     89/<- %ecx 4/r32/esp
1839     #
1840     (is-identifier? %ecx)
1841     (check-ints-equal %eax 0 "F - test-is-identifier-curly-brace-close")
1842     # . epilogue
1843     89/<- %esp 5/r32/ebp
1844     5d/pop-to-ebp
1845     c3/return
1846 
1847 test-is-identifier-hyphen:
1848     # disallow leading '-' since '->' has special meaning
1849     # . prologue
1850     55/push-ebp
1851     89/<- %ebp 4/r32/esp
1852     # (eax..ecx) = "-a"
1853     b8/copy-to-eax "-a"/imm32
1854     8b/-> *eax 1/r32/ecx
1855     8d/copy-address *(eax+ecx+4) 1/r32/ecx
1856     05/add-to-eax 4/imm32
1857     # var slice/ecx : (ref slice) = {eax, ecx}
1858     51/push-ecx
1859     50/push-eax
1860     89/<- %ecx 4/r32/esp
1861     #
1862     (is-identifier? %ecx)
1863     (check-ints-equal %eax 0 "F - test-is-identifier-hyphen")
1864     # . epilogue
1865     89/<- %esp 5/r32/ebp
1866     5d/pop-to-ebp
1867     c3/return
1868 
1869 populate-mu-function-body:  # in : (address buffered-file), out : (handle function), vars : (address stack (handle var))
1870     # . prologue
1871     55/push-ebp
1872     89/<- %ebp 4/r32/esp
1873     # . save registers
1874     50/push-eax
1875     56/push-esi
1876     57/push-edi
1877     # esi = in
1878     8b/-> *(ebp+8) 6/r32/esi
1879     # edi = out
1880     8b/-> *(ebp+0xc) 7/r32/edi
1881     # var eax : (handle block) = parse-mu-block(in, vars)
1882     (parse-mu-block %esi *(ebp+0x10) %edi)  # => eax
1883     # out->body = eax
1884     89/<- *(edi+0x10) 0/r32/eax  # Function-body
1885 $populate-mu-function-body:end:
1886     # . restore registers
1887     5f/pop-to-edi
1888     5e/pop-to-esi
1889     58/pop-to-eax
1890     # . epilogue
1891     89/<- %esp 5/r32/ebp
1892     5d/pop-to-ebp
1893     c3/return
1894 
1895 # parses a block, assuming that the leading '{' has already been read by the caller
1896 parse-mu-block:  # in : (address buffered-file), vars : (address stack (handle var)), fn : (handle function) -> result/eax : (handle block)
1897     # pseudocode:
1898     #   var line : (ref stream byte 512)
1899     #   var word-slice : (ref slice)
1900     #   result/eax = allocate(Heap, Stmt-size)
1901     #   result->tag = 0/Block
1902     #   while true                                  # line loop
1903     #     clear-stream(line)
1904     #     read-line-buffered(in, line)
1905     #     if (line->write == 0) break               # end of file
1906     #     word-slice = next-word(line)
1907     #     if slice-empty?(word-slice)               # end of line
1908     #       continue
1909     #     else if slice-starts-with?(word-slice, "#")
1910     #       continue
1911     #     else if slice-equal?(word-slice, "{")
1912     #       assert(no-tokens-in(line))
1913     #       block = parse-mu-block(in, vars, fn)
1914     #       append-to-block(result, block)
1915     #     else if slice-equal?(word-slice, "}")
1916     #       break
1917     #     else if slice-ends-with?(word-slice, ":")
1918     #       named-block = parse-mu-named-block(word-slice, line, in, vars, fn)
1919     #       append-to-block(result, named-block)
1920     #     else if slice-equal?(word-slice, "var")
1921     #       var-def = parse-mu-var-def(line, vars)
1922     #       append-to-block(result, var-def)
1923     #     else
1924     #       stmt = parse-mu-stmt(line, vars, fn)
1925     #       append-to-block(result, stmt)
1926     #   return result
1927     #
1928     # . prologue
1929     55/push-ebp
1930     89/<- %ebp 4/r32/esp
1931     # . save registers
1932     51/push-ecx
1933     52/push-edx
1934     53/push-ebx
1935     57/push-edi
1936     # var line/ecx : (ref stream byte 512)
1937     81 5/subop/subtract %esp 0x200/imm32
1938     68/push 0x200/imm32/length
1939     68/push 0/imm32/read
1940     68/push 0/imm32/write
1941     89/<- %ecx 4/r32/esp
1942     # var word-slice/edx : (ref slice)
1943     68/push 0/imm32/end
1944     68/push 0/imm32/start
1945     89/<- %edx 4/r32/esp
1946     # edi = result
1947     (allocate Heap *Stmt-size)  # => eax
1948     (zero-out %eax *Stmt-size)
1949     89/<- %edi 0/r32/eax
1950     { # line loop
1951 $parse-mu-block:line-loop:
1952       # line = read-line-buffered(in)
1953       (clear-stream %ecx)
1954       (read-line-buffered *(ebp+8) %ecx)
1955 #?       (write-buffered Stderr "line: ")
1956 #?       (write-stream-data Stderr %ecx)
1957 #?       (write-buffered Stderr Newline)
1958 #?       (flush Stderr)
1959       # if (line->write == 0) break
1960       81 7/subop/compare *ecx 0/imm32
1961       0f 84/jump-if-equal break/disp32
1962       # word-slice = next-word(line)
1963       (next-word %ecx %edx)
1964 #?       (write-buffered Stderr "word: ")
1965 #?       (write-slice-buffered Stderr %edx)
1966 #?       (write-buffered Stderr Newline)
1967 #?       (flush Stderr)
1968       # if slice-empty?(word-slice) continue
1969       (slice-empty? %edx)
1970       3d/compare-eax-and 0/imm32
1971       0f 85/jump-if-not-equal loop/disp32
1972       # if (slice-starts-with?(word-slice, '#') continue
1973       # . eax = *word-slice->start
1974       8b/-> *edx 0/r32/eax
1975       8a/copy-byte *eax 0/r32/AL
1976       81 4/subop/and %eax 0xff/imm32
1977       # . if (eax == '#') continue
1978       3d/compare-eax-and 0x23/imm32/hash
1979       0f 84/jump-if-equal loop/disp32
1980       # if slice-equal?(word-slice, "{")
1981       {
1982 $parse-mu-block:check-for-block:
1983         (slice-equal? %edx "{")
1984         3d/compare-eax-and 0/imm32
1985         74/jump-if-equal break/disp8
1986         (check-no-tokens-left %ecx)
1987         # parse new block and append
1988         (parse-mu-block *(ebp+8) *(ebp+0xc) *(ebp+0x10))  # => eax
1989         (append-to-block %edi %eax)
1990         e9/jump $parse-mu-block:line-loop/disp32
1991       }
1992       # if slice-equal?(word-slice, "}") break
1993 $parse-mu-block:check-for-end:
1994       (slice-equal? %edx "}")
1995       3d/compare-eax-and 0/imm32
1996       0f 85/jump-if-not-equal break/disp32
1997       # if slice-ends-with?(word-slice, ":") parse named block and append
1998       {
1999 $parse-mu-block:check-for-named-block:
2000         # . eax = *word-slice->end
2001         8b/-> *(edx+4) 0/r32/eax
2002         8a/copy-byte *eax 0/r32/AL
2003         81 4/subop/and %eax 0xff/imm32
2004         # . if (eax != ':') break
2005         3d/compare-eax-and 0x23/imm32/hash
2006         0f 85/jump-if-not-equal break/disp32
2007         #
2008         (parse-mu-named-block %edx %ecx *(ebp+8) *(ebp+0xc) *(ebp+0x10))  # => eax
2009         (append-to-block %edi %eax)
2010         e9/jump $parse-mu-block:line-loop/disp32
2011       }
2012       # if slice-equal?(word-slice, "var")
2013       {
2014 $parse-mu-block:check-for-var:
2015         (slice-equal? %edx "var")
2016         3d/compare-eax-and 0/imm32
2017         74/jump-if-equal break/disp8
2018         #
2019         (parse-mu-var-def %ecx *(ebp+0xc))  # => eax
2020         (append-to-block %edi %eax)
2021         e9/jump $parse-mu-block:line-loop/disp32
2022       }
2023 $parse-mu-block:regular-stmt:
2024       # otherwise
2025       (parse-mu-stmt %ecx *(ebp+0xc) *(ebp+0x10))  # => eax
2026       (append-to-block Heap %edi %eax)
2027       e9/jump loop/disp32
2028     } # end line loop
2029     # return result
2030     89/<- %eax 7/r32/edi
2031 $parse-mu-block:end:
2032     # . reclaim locals
2033     81 0/subop/add %esp 0x214/imm32
2034     # . restore registers
2035     5f/pop-to-edi
2036     5b/pop-to-ebx
2037     5a/pop-to-edx
2038     59/pop-to-ecx
2039     # . epilogue
2040     89/<- %esp 5/r32/ebp
2041     5d/pop-to-ebp
2042     c3/return
2043 
2044 $parse-mu-block:abort:
2045     # error("'{' or '}' should be on its own line, but got '")
2046     (write-buffered Stderr "'{' or '}' should be on its own line, but got '")
2047     (rewind-stream %ecx)
2048     (write-stream 2 %ecx)
2049     (write-buffered Stderr "'\n")
2050     (flush Stderr)
2051     # . syscall(exit, 1)
2052     bb/copy-to-ebx  1/imm32
2053     b8/copy-to-eax  1/imm32/exit
2054     cd/syscall  0x80/imm8
2055     # never gets here
2056 
2057 check-no-tokens-left:  # line : (address stream byte)
2058     # . prologue
2059     55/push-ebp
2060     89/<- %ebp 4/r32/esp
2061     # . save registers
2062     50/push-eax
2063     51/push-ecx
2064     # var s/ecx : (ref slice)
2065     68/push 0/imm32/end
2066     68/push 0/imm32/start
2067     89/<- %ecx 4/r32/esp
2068     #
2069     (next-word *(ebp+8) %ecx)
2070     # if slice-empty?(s) return
2071     (slice-empty? %ecx)
2072     3d/compare-eax-and 0/imm32
2073     75/jump-if-not-equal $check-no-tokens-left:end/disp8
2074     # if (slice-starts-with?(s, '#') return
2075     # . eax = *s->start
2076     8b/-> *edx 0/r32/eax
2077     8a/copy-byte *eax 0/r32/AL
2078     81 4/subop/and %eax 0xff/imm32
2079     # . if (eax == '#') continue
2080     3d/compare-eax-and 0x23/imm32/hash
2081     74/jump-if-equal $check-no-tokens-left:end/disp8
2082     # abort
2083     (write-buffered Stderr "'{' or '}' should be on its own line, but got '")
2084     (rewind-stream %ecx)
2085     (write-stream 2 %ecx)
2086     (write-buffered Stderr "'\n")
2087     (flush Stderr)
2088     # . syscall(exit, 1)
2089     bb/copy-to-ebx  1/imm32
2090     b8/copy-to-eax  1/imm32/exit
2091     cd/syscall  0x80/imm8
2092     # never gets here
2093 $check-no-tokens-left:end:
2094     # . reclaim locals
2095     81 0/subop/add %esp 8/imm32
2096     # . restore registers
2097     59/pop-to-ecx
2098     58/pop-to-eax
2099     # . epilogue
2100     89/<- %esp 5/r32/ebp
2101     5d/pop-to-ebp
2102     c3/return
2103 
2104 parse-mu-named-block:  # name : (address slice), first-line : (address stream byte), in : (address buffered-file), vars : (address stack (handle var)) -> result/eax : (handle stmt)
2105     # pseudocode:
2106     #   var line : (ref stream byte 512)
2107     #   var word-slice : (ref slice)
2108     #   result/eax = allocate(Heap, Stmt-size)
2109     #   result->tag = 4/Named-block
2110     #   result->name = name
2111     #   assert(next-word(first-line) == "{")
2112     #   assert(no-tokens-in(first-line))
2113     #   while true                                  # line loop
2114     #     clear-stream(line)
2115     #     read-line-buffered(in, line)
2116     #     if (line->write == 0) break               # end of file
2117     #     word-slice = next-word(line)
2118     #     if slice-empty?(word-slice)               # end of line
2119     #       break
2120     #     else if slice-equal?(word-slice, "{")
2121     #       block = parse-mu-block(in, vars)
2122     #       append-to-block(result, block)
2123     #     else if slice-equal?(word-slice, "}")
2124     #       break
2125     #     else if slice-ends-with?(word-slice, ":")
2126     #       named-block = parse-mu-named-block(word-slice, in, vars)
2127     #       append-to-block(result, named-block)
2128     #     else if slice-equal?(word-slice, "var")
2129     #       var-def = parse-mu-var-def(line, vars)
2130     #       append-to-block(result, var-def)
2131     #     else
2132     #       stmt = parse-mu-stmt(line, vars, fn)
2133     #       append-to-block(result, stmt)
2134     #   return result
2135     #
2136     # . prologue
2137     55/push-ebp
2138     89/<- %ebp 4/r32/esp
2139     # . save registers
2140 $parse-mu-named-block:end:
2141     # . reclaim locals
2142     # . restore registers
2143     # . epilogue
2144     89/<- %esp 5/r32/ebp
2145     5d/pop-to-ebp
2146     c3/return
2147 
2148 parse-mu-var-def:  # line : (address stream byte), vars : (address stack (handle var)) -> result/eax : (handle stmt)
2149     # pseudocode:
2150     #
2151     # . prologue
2152     55/push-ebp
2153     89/<- %ebp 4/r32/esp
2154     # . save registers
2155 $parse-mu-var-def:end:
2156     # . reclaim locals
2157     # . restore registers
2158     # . epilogue
2159     89/<- %esp 5/r32/ebp
2160     5d/pop-to-ebp
2161     c3/return
2162 
2163 parse-mu-stmt:  # line : (address stream byte), vars : (address stack (handle var)), fn : (handle function) -> result/eax : (handle stmt)
2164     # pseudocode:
2165     #   var name : (ref slice)
2166     #   result = allocate(Heap, Stmt-size)
2167     #   if stmt-has-outputs?(line)
2168     #     while true
2169     #       name = next-word(line)
2170     #       if (name == '<-') break
2171     #       assert(is-identifier?(name))
2172     #       var v : (handle var) = lookup-or-define-var(name, vars)
2173     #       result->outputs = append(result->outputs, v)
2174     #   result->name = slice-to-string(next-word(line))
2175     #   while true
2176     #     name = next-word-or-string(line)
2177     #     v = lookup-var-or-literal(name)
2178     #     result->inouts = append(result->inouts, v)
2179     #
2180     # . prologue
2181     55/push-ebp
2182     89/<- %ebp 4/r32/esp
2183     # . save registers
2184     51/push-ecx
2185     57/push-edi
2186     # var name/ecx : (ref slice)
2187     68/push 0/imm32/end
2188     68/push 0/imm32/start
2189     89/<- %ecx 4/r32/esp
2190     # result/edi : (handle stmt)
2191     (allocate Heap *Stmt-size)  # => eax
2192     (zero-out %eax *Stmt-size)
2193     89/<- %edi 0/r32/eax
2194     # result->tag = 1/stmt
2195     c7 0/subop/copy *edi 1/imm32/stmt1  # Stmt-tag
2196     {
2197       (stmt-has-outputs? *(ebp+8))
2198       3d/compare-eax-and 0/imm32
2199       0f 84/jump-if-equal break/disp32
2200       {
2201 $parse-mu-stmt:read-outputs:
2202         # name = next-word(line)
2203         (next-word *(ebp+8) %ecx)
2204         # if slice-empty?(word-slice) break
2205         (slice-empty? %ecx)
2206         3d/compare-eax-and 0/imm32
2207         0f 85/jump-if-not-equal break/disp32
2208         # if (name == "<-") break
2209         (slice-equal? %ecx "<-")
2210         3d/compare-eax-and 0/imm32
2211         75/jump-if-not-equal break/disp8
2212         # assert(is-identifier?(name))
2213         (is-identifier? %ecx)
2214         3d/compare-eax-and 0/imm32
2215         0f 84/jump-if-equal $parse-mu-stmt:abort/disp32
2216         #
2217         (lookup-or-define-var %ecx *(ebp+0xc) *(ebp+0x10))  # => eax
2218         (append-list Heap %eax *(edi+0xc))  # Stmt1-outputs => eax
2219         89/<- *(edi+0xc) 0/r32/eax  # Stmt1-outputs
2220         e9/jump loop/disp32
2221       }
2222     }
2223 $parse-mu-stmt:read-operation:
2224     (next-word *(ebp+8) %ecx)
2225     (slice-to-string Heap %ecx)
2226     89/<- *(edi+4) 0/r32/eax  # Stmt1-operation
2227     {
2228 $parse-mu-stmt:read-inouts:
2229       # name = next-word-or-string(line)
2230       (next-word-or-string *(ebp+8) %ecx)
2231       # if slice-empty?(word-slice) break
2232       (slice-empty? %ecx)
2233       3d/compare-eax-and 0/imm32
2234       0f 85/jump-if-not-equal break/disp32
2235       # if (name == "<-") abort
2236       (slice-equal? %ecx "<-")
2237       3d/compare-eax-and 0/imm32
2238       0f 85/jump-if-not-equal $parse-mu-stmt:abort2/disp32
2239       #
2240       (lookup-var %ecx *(ebp+0xc))  # => eax  # TODO: lookup-var-or-literal
2241       (append-list Heap %eax *(edi+8))  # Stmt1-inouts => eax
2242       89/<- *(edi+8) 0/r32/eax  # Stmt1-inouts
2243       e9/jump loop/disp32
2244     }
2245 $parse-mu-stmt:end:
2246     # return result
2247     89/<- %eax 7/r32/edi
2248     # . reclaim locals
2249     81 0/subop/add %esp 8/imm32
2250     # . restore registers
2251     5f/pop-to-edi
2252     59/pop-to-ecx
2253     # . epilogue
2254     89/<- %esp 5/r32/ebp
2255     5d/pop-to-ebp
2256     c3/return
2257 
2258 $parse-mu-stmt:abort:
2259     # error("invalid identifier '" name "'\n")
2260     (write-buffered Stderr "invalid identifier '")
2261     (write-slice-buffered Stderr %ecx)
2262     (write-buffered Stderr "'\n")
2263     (flush Stderr)
2264     # . syscall(exit, 1)
2265     bb/copy-to-ebx  1/imm32
2266     b8/copy-to-eax  1/imm32/exit
2267     cd/syscall  0x80/imm8
2268     # never gets here
2269 
2270 $parse-mu-stmt:abort2:
2271     # error("invalid statement '" line "'\n")
2272     (rewind-stream *(ebp+8))
2273     (write-buffered Stderr "invalid identifier '")
2274     (write-stream Stderr *(ebp+8))
2275     (write-buffered Stderr "'\n")
2276     (flush Stderr)
2277     # . syscall(exit, 1)
2278     bb/copy-to-ebx  1/imm32
2279     b8/copy-to-eax  1/imm32/exit
2280     cd/syscall  0x80/imm8
2281     # never gets here
2282 
2283 stmt-has-outputs?:  # line : (address stream byte) -> result/eax : boolean
2284     # . prologue
2285     55/push-ebp
2286     89/<- %ebp 4/r32/esp
2287     # . save registers
2288     51/push-ecx
2289     # var word-slice/ecx : (ref slice)
2290     68/push 0/imm32/end
2291     68/push 0/imm32/start
2292     89/<- %ecx 4/r32/esp
2293     # result = false
2294     b8/copy-to-eax 0/imm32/false
2295     (rewind-stream *(ebp+8))
2296     {
2297       (next-word-or-string *(ebp+8) %ecx)
2298       # if slice-empty?(word-slice) break
2299       (slice-empty? %ecx)
2300       3d/compare-eax-and 0/imm32
2301       b8/copy-to-eax 0/imm32/false/result  # restore result (if we're here it's still false)
2302       0f 85/jump-if-not-equal break/disp32
2303       # if slice-starts-with?(word-slice, '#') break
2304       # . eax = *word-slice->start
2305       8b/-> *ecx 0/r32/eax
2306       8a/copy-byte *eax 0/r32/AL
2307       81 4/subop/and %eax 0xff/imm32
2308       # . if (eax == '#') break
2309       3d/compare-eax-and 0x23/imm32/hash
2310       b8/copy-to-eax 0/imm32/false/result  # restore result (if we're here it's still false)
2311       0f 84/jump-if-equal break/disp32
2312       # if slice-equal?(word-slice, '<-') return true
2313       (slice-equal? %ecx "<-")
2314       3d/compare-eax-and 0/imm32
2315       74/jump-if-equal loop/disp8
2316       b8/copy-to-eax 1/imm32/true
2317     }
2318 $stmt-has-outputs:end:
2319     (rewind-stream *(ebp+8))
2320     # . reclaim locals
2321     81 0/subop/add %esp 8/imm32
2322     # . restore registers
2323     59/pop-to-ecx
2324     # . epilogue
2325     89/<- %esp 5/r32/ebp
2326     5d/pop-to-ebp
2327     c3/return
2328 
2329 lookup-var:  # name: (address slice), vars : (address stack (handle var)) -> result/eax: (handle var)
2330     # . prologue
2331     55/push-ebp
2332     89/<- %ebp 4/r32/esp
2333     # var target/eax : (handle array byte) = slice-to-string(name)
2334     (slice-to-string Heap *(ebp+8))  # => eax
2335     #
2336     (lookup-var-helper %eax *(ebp+0xc))  # => eax
2337     # if (result == 0) abort
2338     3d/compare-eax-and 0/imm32
2339     74/jump-if-equal $lookup-var:abort/disp8
2340 $lookup-var:end:
2341     # . epilogue
2342     89/<- %esp 5/r32/ebp
2343     5d/pop-to-ebp
2344     c3/return
2345 
2346 $lookup-var:abort:
2347     (write-buffered Stderr "unknown variable '")
2348     (write-slice-buffered Stderr *(ebp+8))
2349     (write-buffered Stderr "'\n")
2350     (flush Stderr)
2351     # . syscall(exit, 1)
2352     bb/copy-to-ebx  1/imm32
2353     b8/copy-to-eax  1/imm32/exit
2354     cd/syscall  0x80/imm8
2355     # never gets here
2356 
2357 lookup-var-helper:  # name: (address array byte), vars : (address stack (handle var)) -> result/eax: (handle var)
2358     # pseudocode:
2359     #   var curr : (address handle var) = &vars->data[vars->top - 4]
2360     #   var min = vars->data
2361     #   while curr >= min
2362     #     var v : (handle var) = *curr
2363     #     if v->name == name
2364     #       return v
2365     #   return 0
2366     #
2367     # . prologue
2368     55/push-ebp
2369     89/<- %ebp 4/r32/esp
2370     # . save registers
2371     52/push-edx
2372     53/push-ebx
2373     56/push-esi
2374     # esi = vars
2375     8b/-> *(ebp+0xc) 6/r32/esi
2376     # ebx = vars->top
2377     8b/-> *esi 3/r32/ebx
2378     # if (vars->top > vars->length) abort
2379     3b/compare 0/r32/eax *(esi+4)
2380     0f 8f/jump-if-greater $lookup-var-helper:error1/disp32
2381     # var min/edx : (address handle var) = vars->data
2382     8d/copy-address *(esi+8) 2/r32/edx
2383     # var curr/ebx : (address handle var) = &vars->data[vars->top - 4]
2384     81 5/subop/subtract %ebx 4/imm32
2385     8d/copy-address *(esi+ebx+8) 3/r32/ebx
2386     {
2387       # if (curr < min) return 0
2388       39/compare %ebx 2/r32/edx
2389       b8/copy-to-eax 0/imm32
2390       0f 82/jump-if-lesser-unsigned break/disp32
2391       # var v/eax : (handle var) = *curr
2392       8b/-> *ebx 0/r32/eax
2393       # if (v->name == name) return v
2394       (string-equal? *eax *(ebp+8))  # Var-name
2395       3d/compare-eax-and 0/imm32
2396       8b/-> *ebx 0/r32/eax
2397       75/jump-if-not-equal break/disp8
2398       # curr -= 4
2399       81 5/subop/subtract %ebx 4/imm32
2400       e9/jump loop/disp32
2401     }
2402 $lookup-var-helper:end:
2403     # . restore registers
2404     5e/pop-to-esi
2405     5b/pop-to-ebx
2406     5a/pop-to-edx
2407     # . epilogue
2408     89/<- %esp 5/r32/ebp
2409     5d/pop-to-ebp
2410     c3/return
2411 
2412 $lookup-var-helper:error1:
2413     (write-buffered Stderr "malformed stack when looking up '")
2414     (write-slice-buffered Stderr *(ebp+8))
2415     (write-buffered Stderr "'\n")
2416     (flush Stderr)
2417     # . syscall(exit, 1)
2418     bb/copy-to-ebx  1/imm32
2419     b8/copy-to-eax  1/imm32/exit
2420     cd/syscall  0x80/imm8
2421     # never gets here
2422 
2423 lookup-or-define-var:  # name: (address slice), vars : (address stack (handle var)), fn : (handle function) -> result/eax: (handle var)
2424     # . prologue
2425     55/push-ebp
2426     89/<- %ebp 4/r32/esp
2427     # . save registers
2428     51/push-ecx
2429     # var target/ecx : (handle array byte) = slice-to-string(name)
2430     (slice-to-string Heap *(ebp+8))  # => eax
2431     89/<- %ecx 0/r32/eax
2432     #
2433     (lookup-var-helper *(ebp+8) *(ebp+0xc))  # => eax
2434     {
2435       # if (result != 0) return
2436       3d/compare-eax-and 0/imm32
2437       75/jump-if-not-equal break/disp8
2438       # if name is one of fn's outputs, return it
2439       {
2440         (find-in-function-outputs *(ebp+0x10) %ecx)  # => eax
2441         3d/compare-eax-and 0/imm32
2442         # otherwise abort
2443         0f 84/jump-if-not-equal $lookup-var:abort/disp32
2444       }
2445     }
2446 $lookup-or-define-var:end:
2447     # . restore registers
2448     59/pop-to-ecx
2449     # . epilogue
2450     89/<- %esp 5/r32/ebp
2451     5d/pop-to-ebp
2452     c3/return
2453 
2454 find-in-function-outputs:  # fn : (handle function), name : (handle array byte) => result/eax : (handle var)
2455     # . prologue
2456     55/push-ebp
2457     89/<- %ebp 4/r32/esp
2458     # . save registers
2459     51/push-ecx
2460     # var curr/ecx : (handle list var) = fn->outputs
2461     8b/-> *(ebp+8) 1/r32/ecx
2462     8b/-> *(ecx+0xc) 1/r32/ecx
2463     # while curr != null
2464     {
2465       81 7/subop/compare %ecx 0/imm32
2466       74/jump-if-equal break/disp8
2467       # var v : (handle var) = *curr
2468       8b/-> *ecx 0/r32/eax  # List-value
2469       # if (curr->name == name) return curr
2470       50/push-eax
2471       (string-equal? *eax *(ebp+0xc))
2472       3d/compare-eax-and 0/imm32
2473       58/pop-to-eax
2474       75/jump-if-not-equal $find-in-function-outputs:end/disp8
2475       # curr = curr->next
2476       8b/-> *(ecx+4) 1/r32/ecx  # List-next
2477       eb/jump loop/disp8
2478     }
2479     b8/copy-to-eax 0/imm32
2480 $find-in-function-outputs:end:
2481     # . restore registers
2482     59/pop-to-ecx
2483     # . epilogue
2484     89/<- %esp 5/r32/ebp
2485     5d/pop-to-ebp
2486     c3/return
2487 
2488 test-parse-mu-stmt:
2489     # 'increment n'
2490     # . prologue
2491     55/push-ebp
2492     89/<- %ebp 4/r32/esp
2493     # setup
2494     (clear-stream _test-input-stream)
2495     (write _test-input-stream "increment n\n")
2496     # var vars/ecx : (ref stack (address var) 4)
2497     81 5/subop/subtract %esp 0x10/imm32
2498     68/push 0x10/imm32/length
2499     68/push 0/imm32/top
2500     89/<- %ecx 4/r32/esp
2501     (clear-stack %ecx)
2502     # var v/edx : (ref var)
2503     81 5/subop/subtract %esp 0x14/imm32  # Var-size
2504     89/<- %edx 4/r32/esp
2505     (zero-out %edx 0x14)
2506     # v->name = "n"
2507     c7 0/subop/copy *edx "n"/imm32  # Var-name
2508     #
2509     (push %ecx %edx)
2510     # convert
2511     (parse-mu-stmt _test-input-stream %ecx)
2512     # check result
2513     (check-strings-equal *(eax+4) "increment" "F - test-parse-mu-stmt/name")  # Stmt1-operation
2514     # edx : (handle list var) = result->inouts
2515     8b/-> *(eax+8) 2/r32/edx  # Stmt1-inouts
2516     # ebx : (handle var) = result->inouts->value
2517     8b/-> *edx 3/r32/ebx  # List-value
2518     (check-strings-equal *ebx "n" "F - test-parse-mu-stmt/inout:0")  # Var-name
2519     # . epilogue
2520     89/<- %esp 5/r32/ebp
2521     5d/pop-to-ebp
2522     c3/return
2523 
2524 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)
2525     # . prologue
2526     55/push-ebp
2527     89/<- %ebp 4/r32/esp
2528     # . save registers
2529     51/push-ecx
2530     #
2531     (allocate *(ebp+8) *Function-size)  # => eax
2532     8b/-> *(ebp+0xc) 1/r32/ecx
2533     89/<- *eax 1/r32/ecx  # Function-name
2534     8b/-> *(ebp+0x10) 1/r32/ecx
2535     89/<- *(eax+4) 1/r32/ecx  # Function-subx-name
2536     8b/-> *(ebp+0x14) 1/r32/ecx
2537     89/<- *(eax+8) 1/r32/ecx  # Function-inouts
2538     8b/-> *(ebp+0x18) 1/r32/ecx
2539     89/<- *(eax+0xc) 1/r32/ecx  # Function-outputs
2540     8b/-> *(ebp+0x1c) 1/r32/ecx
2541     89/<- *(eax+0x10) 1/r32/ecx  # Function-body
2542     8b/-> *(ebp+0x20) 1/r32/ecx
2543     89/<- *(eax+0x14) 1/r32/ecx  # Function-next
2544 $new-function:end:
2545     # . restore registers
2546     59/pop-to-ecx
2547     # . epilogue
2548     89/<- %esp 5/r32/ebp
2549     5d/pop-to-ebp
2550     c3/return
2551 
2552 new-var:  # ad: allocation-descriptor, name: string, type: int, block: int, stack-offset: int, register: string -> result/eax: (handle var)
2553     # . prologue
2554     55/push-ebp
2555     89/<- %ebp 4/r32/esp
2556     # . save registers
2557     51/push-ecx
2558     #
2559     (allocate *(ebp+8) *Var-size)  # => eax
2560     8b/-> *(ebp+0xc) 1/r32/ecx
2561     89/<- *eax 1/r32/ecx  # Var-name
2562     8b/-> *(ebp+0x10) 1/r32/ecx
2563     89/<- *(eax+4) 1/r32/ecx  # Var-type
2564     8b/-> *(ebp+0x14) 1/r32/ecx
2565     89/<- *(eax+8) 1/r32/ecx  # Var-block
2566     8b/-> *(ebp+0x18) 1/r32/ecx
2567     89/<- *(eax+0xc) 1/r32/ecx  # Var-stack-offset
2568     8b/-> *(ebp+0x1c) 1/r32/ecx
2569     89/<- *(eax+0x10) 1/r32/ecx  # Var-register
2570 $new-var:end:
2571     # . restore registers
2572     59/pop-to-ecx
2573     # . epilogue
2574     89/<- %esp 5/r32/ebp
2575     5d/pop-to-ebp
2576     c3/return
2577 
2578 new-block:  # ad: allocation-descriptor, data: (handle list statement) -> result/eax: (handle statement)
2579     # . prologue
2580     55/push-ebp
2581     89/<- %ebp 4/r32/esp
2582     # . save registers
2583     51/push-ecx
2584     #
2585     (allocate *(ebp+8) *Stmt-size)  # => eax
2586     (zero-out %eax *Stmt-size)
2587     c7 0/subop/copy *eax 0/imm32/tag/block  # Stmt-tag
2588     8b/-> *(ebp+0xc) 1/r32/ecx
2589     89/<- *(eax+4) 1/r32/ecx  # Block-statements
2590 $new-block:end:
2591     # . restore registers
2592     59/pop-to-ecx
2593     # . epilogue
2594     89/<- %esp 5/r32/ebp
2595     5d/pop-to-ebp
2596     c3/return
2597 
2598 new-stmt:  # ad: allocation-descriptor, operation: string, inouts: (handle list var), outputs: (handle list var) -> result/eax: (handle statement)
2599     # . prologue
2600     55/push-ebp
2601     89/<- %ebp 4/r32/esp
2602     # . save registers
2603     51/push-ecx
2604     #
2605     (allocate *(ebp+8) *Stmt-size)  # => eax
2606     (zero-out %eax *Stmt-size)
2607     c7 0/subop/copy *eax 1/imm32/tag/regular-stmt  # Stmt-tag
2608     8b/-> *(ebp+0xc) 1/r32/ecx
2609     89/<- *(eax+4) 1/r32/ecx  # Stmt1-operation
2610     8b/-> *(ebp+0x10) 1/r32/ecx
2611     89/<- *(eax+8) 1/r32/ecx  # Stmt1-inouts
2612     8b/-> *(ebp+0x14) 1/r32/ecx
2613     89/<- *(eax+0xc) 1/r32/ecx  # Stmt1-outputs
2614 $new-stmt:end:
2615     # . restore registers
2616     59/pop-to-ecx
2617     # . epilogue
2618     89/<- %esp 5/r32/ebp
2619     5d/pop-to-ebp
2620     c3/return
2621 
2622 new-vardef:  # ad: allocation-descriptor, name: string, type: int -> result/eax: (handle statement)
2623     # . prologue
2624     55/push-ebp
2625     89/<- %ebp 4/r32/esp
2626     # . save registers
2627     51/push-ecx
2628     #
2629     (allocate *(ebp+8) *Stmt-size)  # => eax
2630     (zero-out %eax *Stmt-size)
2631     c7 0/subop/copy *eax 2/imm32/tag/var-on-stack  # Stmt-tag
2632     8b/-> *(ebp+0xc) 1/r32/ecx
2633     89/<- *(eax+4) 1/r32/ecx  # Vardef-name
2634     8b/-> *(ebp+0x10) 1/r32/ecx
2635     89/<- *(eax+8) 1/r32/ecx  # Vardef-type
2636 $new-vardef:end:
2637     # . restore registers
2638     59/pop-to-ecx
2639     # . epilogue
2640     89/<- %esp 5/r32/ebp
2641     5d/pop-to-ebp
2642     c3/return
2643 
2644 new-regvardef:  # ad: allocation-descriptor, name: string, type: int, register: string -> result/eax: (handle statement)
2645     # . prologue
2646     55/push-ebp
2647     89/<- %ebp 4/r32/esp
2648     # . save registers
2649     51/push-ecx
2650     #
2651     (allocate *(ebp+8) *Stmt-size)  # => eax
2652     (zero-out %eax *Stmt-size)
2653     c7 0/subop/copy *eax 3/imm32/tag/var-in-register
2654     8b/-> *(ebp+0xc) 1/r32/ecx
2655     89/<- *(eax+4) 1/r32/ecx  # Regvardef-name
2656     8b/-> *(ebp+0x10) 1/r32/ecx
2657     89/<- *(eax+8) 1/r32/ecx  # Regvardef-type
2658     8b/-> *(ebp+0x14) 1/r32/ecx
2659     89/<- *(eax+0xc) 1/r32/ecx  # Regvardef-register
2660 $new-regvardef:end:
2661     # . restore registers
2662     59/pop-to-ecx
2663     # . epilogue
2664     89/<- %esp 5/r32/ebp
2665     5d/pop-to-ebp
2666     c3/return
2667 
2668 new-named-block:  # ad: allocation-descriptor, name: string, data: (handle list statement) -> result/eax: (handle statement)
2669     # . prologue
2670     55/push-ebp
2671     89/<- %ebp 4/r32/esp
2672     # . save registers
2673     51/push-ecx
2674     #
2675     (allocate *(ebp+8) *Stmt-size)  # => eax
2676     (zero-out %eax *Stmt-size)
2677     c7 0/subop/copy *eax 4/imm32/tag/named-block
2678     8b/-> *(ebp+0xc) 1/r32/ecx
2679     89/<- *(eax+4) 1/r32/ecx  # Named-block-name
2680     8b/-> *(ebp+0x10) 1/r32/ecx
2681     89/<- *(eax+8) 1/r32/ecx  # Named-block-statements
2682 $new-named-block:end:
2683     # . restore registers
2684     59/pop-to-ecx
2685     # . epilogue
2686     89/<- %esp 5/r32/ebp
2687     5d/pop-to-ebp
2688     c3/return
2689 
2690 new-list:  # ad: allocation-descriptor, value: _type, next: (handle list _type) -> result/eax : (handle list _type)
2691     # . prologue
2692     55/push-ebp
2693     89/<- %ebp 4/r32/esp
2694     # . save registers
2695     51/push-ecx
2696     #
2697     (allocate *(ebp+8) *List-size)  # => eax
2698     8b/-> *(ebp+0xc) 1/r32/ecx
2699     89/<- *eax 1/r32/ecx  # List-value
2700     8b/-> *(ebp+0x10) 1/r32/ecx
2701     89/<- *(eax+4) 1/r32/ecx  # List-next
2702 $new-list:end:
2703     # . restore registers
2704     59/pop-to-ecx
2705     # . epilogue
2706     89/<- %esp 5/r32/ebp
2707     5d/pop-to-ebp
2708     c3/return
2709 
2710 append-list:  # ad: allocation-descriptor, value: _type, list: (handle list _type) -> result/eax : (handle list _type)
2711     # . prologue
2712     55/push-ebp
2713     89/<- %ebp 4/r32/esp
2714     # . save registers
2715     51/push-ecx
2716     #
2717     (allocate *(ebp+8) *List-size)  # => eax
2718     8b/-> *(ebp+0xc) 1/r32/ecx
2719     89/<- *eax 1/r32/ecx  # List-value
2720     # if (list == null) return result
2721     81 7/subop/compare *(ebp+0x10) 0/imm32
2722     74/jump-if-equal $new-list:end/disp8
2723     # otherwise append
2724     # var curr/ecx = list
2725     8b/-> *(ebp+0x10) 1/r32/ecx
2726     # while (curr->next != null) curr = curr->next
2727     {
2728       81 7/subop/compare *(ecx+4) 0/imm32  # List-next
2729       74/jump-if-equal break/disp8
2730       # curr = curr->next
2731       8b/-> *(ecx+4) 1/r32/ecx
2732       eb/jump loop/disp8
2733     }
2734     # curr->next = result
2735     89/<- *(ecx+4) 0/r32/eax
2736     # return list
2737     8b/-> *(ebp+0x10) 0/r32/eax
2738 $append-list:end:
2739     # . restore registers
2740     59/pop-to-ecx
2741     # . epilogue
2742     89/<- %esp 5/r32/ebp
2743     5d/pop-to-ebp
2744     c3/return
2745 
2746 append-to-block:  # ad: allocation-descriptor, block: (handle block), x: (handle stmt)
2747     # . prologue
2748     55/push-ebp
2749     89/<- %ebp 4/r32/esp
2750     # . save registers
2751     56/push-esi
2752     # esi = block
2753     8b/-> *(ebp+0xc) 6/r32/esi
2754     (append-list *(ebp+8) *(ebp+0x10) *(esi+4))  # ad, x, Block-statements
2755     89/<- *(esi+4) 0/r32/eax  # Block-statements
2756 $append-to-block:end:
2757     # . restore registers
2758     5e/pop-to-esi
2759     # . epilogue
2760     89/<- %esp 5/r32/ebp
2761     5d/pop-to-ebp
2762     c3/return
2763 
2764 #######################################################
2765 # Type-checking
2766 #######################################################
2767 
2768 check-mu-types:
2769     # . prologue
2770     55/push-ebp
2771     89/<- %ebp 4/r32/esp
2772     #
2773 $check-mu-types:end:
2774     # . epilogue
2775     89/<- %esp 5/r32/ebp
2776     5d/pop-to-ebp
2777     c3/return
2778 
2779 size-of:  # n : (address var)
2780     # . prologue
2781     55/push-ebp
2782     89/<- %ebp 4/r32/esp
2783     # hard-coded since we only support 'int' types for now
2784     b8/copy-to-eax 4/imm32
2785 $size-of:end:
2786     # . epilogue
2787     89/<- %esp 5/r32/ebp
2788     5d/pop-to-ebp
2789     c3/return
2790 
2791 #######################################################
2792 # Code-generation
2793 #######################################################
2794 
2795 emit-subx:  # out : (address buffered-file)
2796     # . prologue
2797     55/push-ebp
2798     89/<- %ebp 4/r32/esp
2799     # . save registers
2800     50/push-eax
2801     51/push-ecx
2802     57/push-edi
2803     # edi = out
2804     8b/-> *(ebp+8) 7/r32/edi
2805     # var curr/ecx : (handle function) = Program
2806     8b/-> *Program 1/r32/ecx
2807     {
2808       # if (curr == null) break
2809       81 7/subop/compare %ecx 0/imm32
2810       0f 84/jump-if-equal break/disp32
2811       (emit-subx-function %edi %ecx)
2812       # curr = curr->next
2813       8b/-> *(ecx+0x14) 1/r32/ecx  # Function-next
2814       e9/jump loop/disp32
2815     }
2816 $emit-subx:end:
2817     # . restore registers
2818     5f/pop-to-edi
2819     59/pop-to-ecx
2820     58/pop-to-eax
2821     # . epilogue
2822     89/<- %esp 5/r32/ebp
2823     5d/pop-to-ebp
2824     c3/return
2825 
2826 emit-subx-function:  # out : (address buffered-file), f : (handle function)
2827     # . prologue
2828     55/push-ebp
2829     89/<- %ebp 4/r32/esp
2830     # . save registers
2831     50/push-eax
2832     51/push-ecx
2833     57/push-edi
2834     # edi = out
2835     8b/-> *(ebp+8) 7/r32/edi
2836     # ecx = f
2837     8b/-> *(ebp+0xc) 1/r32/ecx
2838     #
2839     (write-buffered %edi *ecx)
2840     (write-buffered %edi ":\n")
2841     (emit-subx-prologue %edi)
2842     (emit-subx-block %edi *(ecx+0x10))  # Function-body
2843     (emit-subx-epilogue %edi)
2844 $emit-subx-function:end:
2845     # . restore registers
2846     5f/pop-to-edi
2847     59/pop-to-ecx
2848     58/pop-to-eax
2849     # . epilogue
2850     89/<- %esp 5/r32/ebp
2851     5d/pop-to-ebp
2852     c3/return
2853 
2854 emit-subx-block:  # out : (address buffered-file), block : (handle block)
2855     # . prologue
2856     55/push-ebp
2857     89/<- %ebp 4/r32/esp
2858     # curr/esi : (handle list statement) = block->statements
2859     8b/-> *(ebp+0xc) 6/r32/esi
2860     8b/-> *(esi+4) 6/r32/esi  # Block-statements
2861     #
2862     {
2863 $emit-subx-block:check-empty:
2864       81 7/subop/compare %esi 0/imm32
2865       0f 84/jump-if-equal break/disp32
2866       (write-buffered *(ebp+8) "{\n")
2867       {
2868 $emit-subx-block:stmt:
2869         81 7/subop/compare %esi 0/imm32
2870         74/jump-if-equal break/disp8
2871         (emit-subx-statement *(ebp+8) *esi Primitives 0)
2872         (write-buffered *(ebp+8) Newline)
2873         8b/-> *(esi+4) 6/r32/esi  # List-next
2874         eb/jump loop/disp8
2875       }
2876       (write-buffered *(ebp+8) "}\n")
2877     }
2878 $emit-subx-block:end:
2879     # . epilogue
2880     89/<- %esp 5/r32/ebp
2881     5d/pop-to-ebp
2882     c3/return
2883 
2884 emit-subx-statement:  # out : (address buffered-file), stmt : (handle statement), primitives : (handle primitive), functions : (handle function)
2885     # . prologue
2886     55/push-ebp
2887     89/<- %ebp 4/r32/esp
2888     # . save registers
2889     50/push-eax
2890     51/push-ecx
2891     # if stmt matches a primitive, emit it
2892     {
2893 $emit-subx-statement:primitive:
2894       (find-matching-primitive *(ebp+0x10) *(ebp+0xc))  # primitives, stmt => curr/eax
2895       3d/compare-eax-and 0/imm32
2896       74/jump-if-equal break/disp8
2897       (emit-subx-primitive *(ebp+8) *(ebp+0xc) %eax)  # out, stmt, curr
2898       e9/jump $emit-subx-statement:end/disp32
2899     }
2900     # else if stmt matches a function, emit a call to it
2901     {
2902 $emit-subx-statement:call:
2903       (find-matching-function *(ebp+0x14) *(ebp+0xc))  # functions, stmt => curr/eax
2904       3d/compare-eax-and 0/imm32
2905       74/jump-if-equal break/disp8
2906       (emit-subx-call *(ebp+8) *(ebp+0xc) %eax)  # out, stmt, curr
2907       e9/jump $emit-subx-statement:end/disp32
2908     }
2909     # else abort
2910     e9/jump $emit-subx-statement:abort/disp32
2911 $emit-subx-statement:end:
2912     # . restore registers
2913     59/pop-to-ecx
2914     58/pop-to-eax
2915     # . epilogue
2916     89/<- %esp 5/r32/ebp
2917     5d/pop-to-ebp
2918     c3/return
2919 
2920 $emit-subx-statement:abort:
2921     # error("couldn't translate '" stmt "'\n")
2922     (write-buffered Stderr "couldn't translate '")
2923 #?     (emit-string Stderr *(ebp+0xc))  # TODO
2924     (write-buffered Stderr "'\n")
2925     (flush Stderr)
2926     # . syscall(exit, 1)
2927     bb/copy-to-ebx  1/imm32
2928     b8/copy-to-eax  1/imm32/exit
2929     cd/syscall  0x80/imm8
2930     # never gets here
2931 
2932 # Primitives supported
2933 # For each operation, put variants with hard-coded registers before flexible ones.
2934 == data
2935 Primitives:
2936 # - increment/decrement
2937 _Primitive-inc-eax:
2938     # var/eax <- increment => 40/increment-eax
2939     "increment"/imm32/name
2940     0/imm32/no-inouts
2941     Single-int-var-in-eax/imm32/outputs
2942     "40/increment-eax"/imm32/subx-name
2943     0/imm32/no-rm32
2944     0/imm32/no-r32
2945     0/imm32/no-imm32
2946     0/imm32/output-is-write-only
2947     _Primitive-inc-ecx/imm32/next
2948 _Primitive-inc-ecx:
2949     # var/ecx <- increment => 41/increment-ecx
2950     "increment"/imm32/name
2951     0/imm32/no-inouts
2952     Single-int-var-in-ecx/imm32/outputs
2953     "41/increment-ecx"/imm32/subx-name
2954     0/imm32/no-rm32
2955     0/imm32/no-r32
2956     0/imm32/no-imm32
2957     0/imm32/output-is-write-only
2958     _Primitive-inc-edx/imm32/next
2959 _Primitive-inc-edx:
2960     # var/edx <- increment => 42/increment-edx
2961     "increment"/imm32/name
2962     0/imm32/no-inouts
2963     Single-int-var-in-edx/imm32/outputs
2964     "42/increment-edx"/imm32/subx-name
2965     0/imm32/no-rm32
2966     0/imm32/no-r32
2967     0/imm32/no-imm32
2968     0/imm32/output-is-write-only
2969     _Primitive-inc-ebx/imm32/next
2970 _Primitive-inc-ebx:
2971     # var/ebx <- increment => 43/increment-ebx
2972     "increment"/imm32/name
2973     0/imm32/no-inouts
2974     Single-int-var-in-ebx/imm32/outputs
2975     "43/increment-ebx"/imm32/subx-name
2976     0/imm32/no-rm32
2977     0/imm32/no-r32
2978     0/imm32/no-imm32
2979     0/imm32/output-is-write-only
2980     _Primitive-inc-esi/imm32/next
2981 _Primitive-inc-esi:
2982     # var/esi <- increment => 46/increment-esi
2983     "increment"/imm32/name
2984     0/imm32/no-inouts
2985     Single-int-var-in-esi/imm32/outputs
2986     "46/increment-esi"/imm32/subx-name
2987     0/imm32/no-rm32
2988     0/imm32/no-r32
2989     0/imm32/no-imm32
2990     0/imm32/output-is-write-only
2991     _Primitive-inc-edi/imm32/next
2992 _Primitive-inc-edi:
2993     # var/edi <- increment => 47/increment-edi
2994     "increment"/imm32/name
2995     0/imm32/no-inouts
2996     Single-int-var-in-edi/imm32/outputs
2997     "47/increment-edi"/imm32/subx-name
2998     0/imm32/no-rm32
2999     0/imm32/no-r32
3000     0/imm32/no-imm32
3001     0/imm32/output-is-write-only
3002     _Primitive-dec-eax/imm32/next
3003 _Primitive-dec-eax:
3004     # var/eax <- decrement => 48/decrement-eax
3005     "decrement"/imm32/name
3006     0/imm32/no-inouts
3007     Single-int-var-in-eax/imm32/outputs
3008     "48/decrement-eax"/imm32/subx-name
3009     0/imm32/no-rm32
3010     0/imm32/no-r32
3011     0/imm32/no-imm32
3012     0/imm32/output-is-write-only
3013     _Primitive-dec-ecx/imm32/next
3014 _Primitive-dec-ecx:
3015     # var/ecx <- decrement => 49/decrement-ecx
3016     "decrement"/imm32/name
3017     0/imm32/no-inouts
3018     Single-int-var-in-ecx/imm32/outputs
3019     "49/decrement-ecx"/imm32/subx-name
3020     0/imm32/no-rm32
3021     0/imm32/no-r32
3022     0/imm32/no-imm32
3023     0/imm32/output-is-write-only
3024     _Primitive-dec-edx/imm32/next
3025 _Primitive-dec-edx:
3026     # var/edx <- decrement => 4a/decrement-edx
3027     "decrement"/imm32/name
3028     0/imm32/no-inouts
3029     Single-int-var-in-edx/imm32/outputs
3030     "4a/decrement-edx"/imm32/subx-name
3031     0/imm32/no-rm32
3032     0/imm32/no-r32
3033     0/imm32/no-imm32
3034     0/imm32/output-is-write-only
3035     _Primitive-dec-ebx/imm32/next
3036 _Primitive-dec-ebx:
3037     # var/ebx <- decrement => 4b/decrement-ebx
3038     "decrement"/imm32/name
3039     0/imm32/no-inouts
3040     Single-int-var-in-ebx/imm32/outputs
3041     "4b/decrement-ebx"/imm32/subx-name
3042     0/imm32/no-rm32
3043     0/imm32/no-r32
3044     0/imm32/no-imm32
3045     0/imm32/output-is-write-only
3046     _Primitive-dec-esi/imm32/next
3047 _Primitive-dec-esi:
3048     # var/esi <- decrement => 4e/decrement-esi
3049     "decrement"/imm32/name
3050     0/imm32/no-inouts
3051     Single-int-var-in-esi/imm32/outputs
3052     "4e/decrement-esi"/imm32/subx-name
3053     0/imm32/no-rm32
3054     0/imm32/no-r32
3055     0/imm32/no-imm32
3056     0/imm32/output-is-write-only
3057     _Primitive-dec-edi/imm32/next
3058 _Primitive-dec-edi:
3059     # var/edi <- decrement => 4f/decrement-edi
3060     "decrement"/imm32/name
3061     0/imm32/no-inouts
3062     Single-int-var-in-edi/imm32/outputs
3063     "4f/decrement-edi"/imm32/subx-name
3064     0/imm32/no-rm32
3065     0/imm32/no-r32
3066     0/imm32/no-imm32
3067     0/imm32/output-is-write-only
3068     _Primitive-inc-mem/imm32/next
3069 _Primitive-inc-mem:
3070     # increment var => ff 0/subop/increment *(ebp+__)
3071     "increment"/imm32/name
3072     Single-int-var-on-stack/imm32/inouts
3073     0/imm32/no-outputs
3074     "ff 0/subop/increment"/imm32/subx-name
3075     1/imm32/rm32-is-first-inout
3076     0/imm32/no-r32
3077     0/imm32/no-imm32
3078     0/imm32/output-is-write-only
3079     _Primitive-inc-reg/imm32/next
3080 _Primitive-inc-reg:
3081     # var/reg <- increment => ff 0/subop/increment %__
3082     "increment"/imm32/name
3083     0/imm32/no-inouts
3084     Single-int-var-in-some-register/imm32/outputs
3085     "ff 0/subop/increment"/imm32/subx-name
3086     3/imm32/rm32-is-first-output
3087     0/imm32/no-r32
3088     0/imm32/no-imm32
3089     0/imm32/output-is-write-only
3090     _Primitive-dec-mem/imm32/next
3091 _Primitive-dec-mem:
3092     # decrement var => ff 1/subop/decrement *(ebp+__)
3093     "decrement"/imm32/name
3094     Single-int-var-on-stack/imm32/inouts
3095     0/imm32/no-outputs
3096     "ff 1/subop/decrement"/imm32/subx-name
3097     1/imm32/rm32-is-first-inout
3098     0/imm32/no-r32
3099     0/imm32/no-imm32
3100     0/imm32/output-is-write-only
3101     _Primitive-dec-reg/imm32/next
3102 _Primitive-dec-reg:
3103     # var/reg <- decrement => ff 1/subop/decrement %__
3104     "decrement"/imm32/name
3105     0/imm32/no-inouts
3106     Single-int-var-in-some-register/imm32/outputs
3107     "ff 1/subop/decrement"/imm32/subx-name
3108     3/imm32/rm32-is-first-output
3109     0/imm32/no-r32
3110     0/imm32/no-imm32
3111     0/imm32/output-is-write-only
3112     _Primitive-add-to-eax/imm32/next
3113 # - add
3114 _Primitive-add-to-eax:
3115     # var/eax <- add lit => 05/add-to-eax lit/imm32
3116     "add"/imm32/name
3117     Single-lit-var/imm32/inouts
3118     Single-int-var-in-eax/imm32/outputs
3119     "05/add-to-eax"/imm32/subx-name
3120     0/imm32/no-rm32
3121     0/imm32/no-r32
3122     1/imm32/imm32-is-first-inout
3123     0/imm32/output-is-write-only
3124     _Primitive-add-reg-to-reg/imm32/next
3125 _Primitive-add-reg-to-reg:
3126     # var1/reg <- add var2/reg => 01/add-to var1/rm32 var2/r32
3127     "add"/imm32/name
3128     Single-int-var-in-some-register/imm32/inouts
3129     Single-int-var-in-some-register/imm32/outputs
3130     "01/add-to"/imm32/subx-name
3131     3/imm32/rm32-is-first-output
3132     1/imm32/r32-is-first-inout
3133     0/imm32/no-imm32
3134     0/imm32/output-is-write-only
3135     _Primitive-add-reg-to-mem/imm32/next
3136 _Primitive-add-reg-to-mem:
3137     # add-to var1 var2/reg => 01/add-to var1 var2/r32
3138     "add-to"/imm32/name
3139     Int-var-and-second-int-var-in-some-register/imm32/inouts
3140     0/imm32/outputs
3141     "01/add-to"/imm32/subx-name
3142     1/imm32/rm32-is-first-inout
3143     2/imm32/r32-is-second-inout
3144     0/imm32/no-imm32
3145     0/imm32/output-is-write-only
3146     _Primitive-add-mem-to-reg/imm32/next
3147 _Primitive-add-mem-to-reg:
3148     # var1/reg <- add var2 => 03/add var2/rm32 var1/r32
3149     "add"/imm32/name
3150     Single-int-var-on-stack/imm32/inouts
3151     Single-int-var-in-some-register/imm32/outputs
3152     "03/add"/imm32/subx-name
3153     1/imm32/rm32-is-first-inout
3154     3/imm32/r32-is-first-output
3155     0/imm32/no-imm32
3156     0/imm32/output-is-write-only
3157     _Primitive-add-lit-to-reg/imm32/next
3158 _Primitive-add-lit-to-reg:
3159     # var1/reg <- add lit => 81 0/subop/add var1/rm32 lit/imm32
3160     "add"/imm32/name
3161     Single-lit-var/imm32/inouts
3162     Single-int-var-in-some-register/imm32/outputs
3163     "81 0/subop/add"/imm32/subx-name
3164     3/imm32/rm32-is-first-output
3165     0/imm32/no-r32
3166     1/imm32/imm32-is-first-inout
3167     0/imm32/output-is-write-only
3168     _Primitive-add-lit-to-mem/imm32/next
3169 _Primitive-add-lit-to-mem:
3170     # add-to var1, lit => 81 0/subop/add var1/rm32 lit/imm32
3171     "add-to"/imm32/name
3172     Int-var-and-literal/imm32/inouts
3173     0/imm32/outputs
3174     "81 0/subop/add"/imm32/subx-name
3175     1/imm32/rm32-is-first-inout
3176     0/imm32/no-r32
3177     2/imm32/imm32-is-first-inout
3178     0/imm32/output-is-write-only
3179     _Primitive-subtract-from-eax/imm32/next
3180 # - subtract
3181 _Primitive-subtract-from-eax:
3182     # var/eax <- subtract lit => 2d/subtract-from-eax lit/imm32
3183     "subtract"/imm32/name
3184     Single-lit-var/imm32/inouts
3185     Single-int-var-in-eax/imm32/outputs
3186     "2d/subtract-from-eax"/imm32/subx-name
3187     0/imm32/no-rm32
3188     0/imm32/no-r32
3189     1/imm32/imm32-is-first-inout
3190     0/imm32/output-is-write-only
3191     _Primitive-subtract-reg-from-reg/imm32/next
3192 _Primitive-subtract-reg-from-reg:
3193     # var1/reg <- subtract var2/reg => 29/subtract-from var1/rm32 var2/r32
3194     "subtract"/imm32/name
3195     Single-int-var-in-some-register/imm32/inouts
3196     Single-int-var-in-some-register/imm32/outputs
3197     "29/subtract-from"/imm32/subx-name
3198     3/imm32/rm32-is-first-output
3199     1/imm32/r32-is-first-inout
3200     0/imm32/no-imm32
3201     0/imm32/output-is-write-only
3202     _Primitive-subtract-reg-from-mem/imm32/next
3203 _Primitive-subtract-reg-from-mem:
3204     # subtract-from var1 var2/reg => 29/subtract-from var1 var2/r32
3205     "subtract-from"/imm32/name
3206     Int-var-and-second-int-var-in-some-register/imm32/inouts
3207     0/imm32/outputs
3208     "29/subtract-from"/imm32/subx-name
3209     1/imm32/rm32-is-first-inout
3210     2/imm32/r32-is-second-inout
3211     0/imm32/no-imm32
3212     0/imm32/output-is-write-only
3213     _Primitive-subtract-mem-from-reg/imm32/next
3214 _Primitive-subtract-mem-from-reg:
3215     # var1/reg <- subtract var2 => 2b/subtract var2/rm32 var1/r32
3216     "subtract"/imm32/name
3217     Single-int-var-on-stack/imm32/inouts
3218     Single-int-var-in-some-register/imm32/outputs
3219     "2b/subtract"/imm32/subx-name
3220     1/imm32/rm32-is-first-inout
3221     3/imm32/r32-is-first-output
3222     0/imm32/no-imm32
3223     0/imm32/output-is-write-only
3224     _Primitive-subtract-lit-from-reg/imm32/next
3225 _Primitive-subtract-lit-from-reg:
3226     # var1/reg <- subtract lit => 81 5/subop/subtract var1/rm32 lit/imm32
3227     "subtract"/imm32/name
3228     Single-lit-var/imm32/inouts
3229     Single-int-var-in-some-register/imm32/outputs
3230     "81 5/subop/subtract"/imm32/subx-name
3231     3/imm32/rm32-is-first-output
3232     0/imm32/no-r32
3233     1/imm32/imm32-is-first-inout
3234     0/imm32/output-is-write-only
3235     _Primitive-subtract-lit-from-mem/imm32/next
3236 _Primitive-subtract-lit-from-mem:
3237     # subtract-from var1, lit => 81 5/subop/subtract var1/rm32 lit/imm32
3238     "subtract-from"/imm32/name
3239     Int-var-and-literal/imm32/inouts
3240     0/imm32/outputs
3241     "81 5/subop/subtract"/imm32/subx-name
3242     1/imm32/rm32-is-first-inout
3243     0/imm32/no-r32
3244     2/imm32/imm32-is-first-inout
3245     0/imm32/output-is-write-only
3246     _Primitive-and-with-eax/imm32/next
3247 # - and
3248 _Primitive-and-with-eax:
3249     # var/eax <- and lit => 25/and-with-eax lit/imm32
3250     "and"/imm32/name
3251     Single-lit-var/imm32/inouts
3252     Single-int-var-in-eax/imm32/outputs
3253     "25/and-with-eax"/imm32/subx-name
3254     0/imm32/no-rm32
3255     0/imm32/no-r32
3256     1/imm32/imm32-is-first-inout
3257     0/imm32/output-is-write-only
3258     _Primitive-and-reg-with-reg/imm32/next
3259 _Primitive-and-reg-with-reg:
3260     # var1/reg <- and var2/reg => 21/and-with var1/rm32 var2/r32
3261     "and"/imm32/name
3262     Single-int-var-in-some-register/imm32/inouts
3263     Single-int-var-in-some-register/imm32/outputs
3264     "21/and-with"/imm32/subx-name
3265     3/imm32/rm32-is-first-output
3266     1/imm32/r32-is-first-inout
3267     0/imm32/no-imm32
3268     0/imm32/output-is-write-only
3269     _Primitive-and-reg-with-mem/imm32/next
3270 _Primitive-and-reg-with-mem:
3271     # and-with var1 var2/reg => 21/and-with var1 var2/r32
3272     "and-with"/imm32/name
3273     Int-var-and-second-int-var-in-some-register/imm32/inouts
3274     0/imm32/outputs
3275     "21/and-with"/imm32/subx-name
3276     1/imm32/rm32-is-first-inout
3277     2/imm32/r32-is-second-inout
3278     0/imm32/no-imm32
3279     0/imm32/output-is-write-only
3280     _Primitive-and-mem-with-reg/imm32/next
3281 _Primitive-and-mem-with-reg:
3282     # var1/reg <- and var2 => 23/and var2/rm32 var1/r32
3283     "and"/imm32/name
3284     Single-int-var-on-stack/imm32/inouts
3285     Single-int-var-in-some-register/imm32/outputs
3286     "23/and"/imm32/subx-name
3287     1/imm32/rm32-is-first-inout
3288     3/imm32/r32-is-first-output
3289     0/imm32/no-imm32
3290     0/imm32/output-is-write-only
3291     _Primitive-and-lit-with-reg/imm32/next
3292 _Primitive-and-lit-with-reg:
3293     # var1/reg <- and lit => 81 4/subop/and var1/rm32 lit/imm32
3294     "and"/imm32/name
3295     Single-lit-var/imm32/inouts
3296     Single-int-var-in-some-register/imm32/outputs
3297     "81 4/subop/and"/imm32/subx-name
3298     3/imm32/rm32-is-first-output
3299     0/imm32/no-r32
3300     1/imm32/imm32-is-first-inout
3301     0/imm32/output-is-write-only
3302     _Primitive-and-lit-with-mem/imm32/next
3303 _Primitive-and-lit-with-mem:
3304     # and-with var1, lit => 81 4/subop/and var1/rm32 lit/imm32
3305     "and-with"/imm32/name
3306     Int-var-and-literal/imm32/inouts
3307     0/imm32/outputs
3308     "81 4/subop/and"/imm32/subx-name
3309     1/imm32/rm32-is-first-inout
3310     0/imm32/no-r32
3311     2/imm32/imm32-is-first-inout
3312     0/imm32/output-is-write-only
3313     _Primitive-or-with-eax/imm32/next
3314 # - or
3315 _Primitive-or-with-eax:
3316     # var/eax <- or lit => 0d/or-with-eax lit/imm32
3317     "or"/imm32/name
3318     Single-lit-var/imm32/inouts
3319     Single-int-var-in-eax/imm32/outputs
3320     "0d/or-with-eax"/imm32/subx-name
3321     0/imm32/no-rm32
3322     0/imm32/no-r32
3323     1/imm32/imm32-is-first-inout
3324     0/imm32/output-is-write-only
3325     _Primitive-or-reg-with-reg/imm32/next
3326 _Primitive-or-reg-with-reg:
3327     # var1/reg <- or var2/reg => 09/or-with var1/rm32 var2/r32
3328     "or"/imm32/name
3329     Single-int-var-in-some-register/imm32/inouts
3330     Single-int-var-in-some-register/imm32/outputs
3331     "09/or-with"/imm32/subx-name
3332     3/imm32/rm32-is-first-output
3333     1/imm32/r32-is-first-inout
3334     0/imm32/no-imm32
3335     0/imm32/output-is-write-only
3336     _Primitive-or-reg-with-mem/imm32/next
3337 _Primitive-or-reg-with-mem:
3338     # or-with var1 var2/reg => 09/or-with var1 var2/r32
3339     "or-with"/imm32/name
3340     Int-var-and-second-int-var-in-some-register/imm32/inouts
3341     0/imm32/outputs
3342     "09/or-with"/imm32/subx-name
3343     1/imm32/rm32-is-first-inout
3344     2/imm32/r32-is-second-inout
3345     0/imm32/no-imm32
3346     0/imm32/output-is-write-only
3347     _Primitive-or-mem-with-reg/imm32/next
3348 _Primitive-or-mem-with-reg:
3349     # var1/reg <- or var2 => 0b/or var2/rm32 var1/r32
3350     "or"/imm32/name
3351     Single-int-var-on-stack/imm32/inouts
3352     Single-int-var-in-some-register/imm32/outputs
3353     "0b/or"/imm32/subx-name
3354     1/imm32/rm32-is-first-inout
3355     3/imm32/r32-is-first-output
3356     0/imm32/no-imm32
3357     0/imm32/output-is-write-only
3358     _Primitive-or-lit-with-reg/imm32/next
3359 _Primitive-or-lit-with-reg:
3360     # var1/reg <- or lit => 81 1/subop/or var1/rm32 lit/imm32
3361     "or"/imm32/name
3362     Single-lit-var/imm32/inouts
3363     Single-int-var-in-some-register/imm32/outputs
3364     "81 4/subop/or"/imm32/subx-name
3365     3/imm32/rm32-is-first-output
3366     0/imm32/no-r32
3367     1/imm32/imm32-is-first-inout
3368     0/imm32/output-is-write-only
3369     _Primitive-or-lit-with-mem/imm32/next
3370 _Primitive-or-lit-with-mem:
3371     # or-with var1, lit => 81 1/subop/or var1/rm32 lit/imm32
3372     "or-with"/imm32/name
3373     Int-var-and-literal/imm32/inouts
3374     0/imm32/outputs
3375     "81 4/subop/or"/imm32/subx-name
3376     1/imm32/rm32-is-first-inout
3377     0/imm32/no-r32
3378     2/imm32/imm32-is-first-inout
3379     0/imm32/output-is-write-only
3380     _Primitive-xor-with-eax/imm32/next
3381 # - xor
3382 _Primitive-xor-with-eax:
3383     # var/eax <- xor lit => 35/xor-with-eax lit/imm32
3384     "xor"/imm32/name
3385     Single-lit-var/imm32/inouts
3386     Single-int-var-in-eax/imm32/outputs
3387     "35/xor-with-eax"/imm32/subx-name
3388     0/imm32/no-rm32
3389     0/imm32/no-r32
3390     1/imm32/imm32-is-first-inout
3391     0/imm32/output-is-write-only
3392     _Primitive-xor-reg-with-reg/imm32/next
3393 _Primitive-xor-reg-with-reg:
3394     # var1/reg <- xor var2/reg => 31/xor-with var1/rm32 var2/r32
3395     "xor"/imm32/name
3396     Single-int-var-in-some-register/imm32/inouts
3397     Single-int-var-in-some-register/imm32/outputs
3398     "31/xor-with"/imm32/subx-name
3399     3/imm32/rm32-is-first-output
3400     1/imm32/r32-is-first-inout
3401     0/imm32/no-imm32
3402     0/imm32/output-is-write-only
3403     _Primitive-xor-reg-with-mem/imm32/next
3404 _Primitive-xor-reg-with-mem:
3405     # xor-with var1 var2/reg => 31/xor-with var1 var2/r32
3406     "xor-with"/imm32/name
3407     Int-var-and-second-int-var-in-some-register/imm32/inouts
3408     0/imm32/outputs
3409     "31/xor-with"/imm32/subx-name
3410     1/imm32/rm32-is-first-inout
3411     2/imm32/r32-is-second-inout
3412     0/imm32/no-imm32
3413     0/imm32/output-is-write-only
3414     _Primitive-xor-mem-with-reg/imm32/next
3415 _Primitive-xor-mem-with-reg:
3416     # var1/reg <- xor var2 => 33/xor var2/rm32 var1/r32
3417     "xor"/imm32/name
3418     Single-int-var-on-stack/imm32/inouts
3419     Single-int-var-in-some-register/imm32/outputs
3420     "33/xor"/imm32/subx-name
3421     1/imm32/rm32-is-first-inout
3422     3/imm32/r32-is-first-output
3423     0/imm32/no-imm32
3424     0/imm32/output-is-write-only
3425     _Primitive-xor-lit-with-reg/imm32/next
3426 _Primitive-xor-lit-with-reg:
3427     # var1/reg <- xor lit => 81 6/subop/xor var1/rm32 lit/imm32
3428     "xor"/imm32/name
3429     Single-lit-var/imm32/inouts
3430     Single-int-var-in-some-register/imm32/outputs
3431     "81 4/subop/xor"/imm32/subx-name
3432     3/imm32/rm32-is-first-output
3433     0/imm32/no-r32
3434     1/imm32/imm32-is-first-inout
3435     0/imm32/output-is-write-only
3436     _Primitive-xor-lit-with-mem/imm32/next
3437 _Primitive-xor-lit-with-mem:
3438     # xor-with var1, lit => 81 6/subop/xor var1/rm32 lit/imm32
3439     "xor-with"/imm32/name
3440     Int-var-and-literal/imm32/inouts
3441     0/imm32/outputs
3442     "81 4/subop/xor"/imm32/subx-name
3443     1/imm32/rm32-is-first-inout
3444     0/imm32/no-r32
3445     2/imm32/imm32-is-first-inout
3446     0/imm32/output-is-write-only
3447     _Primitive-copy-to-eax/imm32/next
3448 # - copy
3449 _Primitive-copy-to-eax:
3450     # var/eax <- copy lit => b8/copy-to-eax lit/imm32
3451     "copy"/imm32/name
3452     Single-lit-var/imm32/inouts
3453     Single-int-var-in-eax/imm32/outputs
3454     "b8/copy-to-eax"/imm32/subx-name
3455     0/imm32/no-rm32
3456     0/imm32/no-r32
3457     1/imm32/imm32-is-first-inout
3458     1/imm32/output-is-write-only
3459     _Primitive-copy-to-ecx/imm32/next
3460 _Primitive-copy-to-ecx:
3461     # var/ecx <- copy lit => b9/copy-to-ecx lit/imm32
3462     "copy"/imm32/name
3463     Single-lit-var/imm32/inouts
3464     Single-int-var-in-ecx/imm32/outputs
3465     "b9/copy-to-ecx"/imm32/subx-name
3466     0/imm32/no-rm32
3467     0/imm32/no-r32
3468     1/imm32/imm32-is-first-inout
3469     1/imm32/output-is-write-only
3470     _Primitive-copy-to-edx/imm32/next
3471 _Primitive-copy-to-edx:
3472     # var/edx <- copy lit => ba/copy-to-edx lit/imm32
3473     "copy"/imm32/name
3474     Single-lit-var/imm32/inouts
3475     Single-int-var-in-edx/imm32/outputs
3476     "ba/copy-to-edx"/imm32/subx-name
3477     0/imm32/no-rm32
3478     0/imm32/no-r32
3479     1/imm32/imm32-is-first-inout
3480     1/imm32/output-is-write-only
3481     _Primitive-copy-to-ebx/imm32/next
3482 _Primitive-copy-to-ebx:
3483     # var/ebx <- copy lit => bb/copy-to-ebx lit/imm32
3484     "copy"/imm32/name
3485     Single-lit-var/imm32/inouts
3486     Single-int-var-in-ebx/imm32/outputs
3487     "bb/copy-to-ebx"/imm32/subx-name
3488     0/imm32/no-rm32
3489     0/imm32/no-r32
3490     1/imm32/imm32-is-first-inout
3491     1/imm32/output-is-write-only
3492     _Primitive-copy-to-esi/imm32/next
3493 _Primitive-copy-to-esi:
3494     # var/esi <- copy lit => be/copy-to-esi lit/imm32
3495     "copy"/imm32/name
3496     Single-lit-var/imm32/inouts
3497     Single-int-var-in-esi/imm32/outputs
3498     "be/copy-to-esi"/imm32/subx-name
3499     0/imm32/no-rm32
3500     0/imm32/no-r32
3501     1/imm32/imm32-is-first-inout
3502     1/imm32/output-is-write-only
3503     _Primitive-copy-to-edi/imm32/next
3504 _Primitive-copy-to-edi:
3505     # var/edi <- copy lit => bf/copy-to-edi lit/imm32
3506     "copy"/imm32/name
3507     Single-lit-var/imm32/inouts
3508     Single-int-var-in-edi/imm32/outputs
3509     "bf/copy-to-edi"/imm32/subx-name
3510     0/imm32/no-rm32
3511     0/imm32/no-r32
3512     1/imm32/imm32-is-first-inout
3513     1/imm32/output-is-write-only
3514     _Primitive-copy-reg-to-reg/imm32/next
3515 _Primitive-copy-reg-to-reg:
3516     # var1/reg <- copy var2/reg => 89/copy-to var1/rm32 var2/r32
3517     "copy"/imm32/name
3518     Single-int-var-in-some-register/imm32/inouts
3519     Single-int-var-in-some-register/imm32/outputs
3520     "89/copy-to"/imm32/subx-name
3521     3/imm32/rm32-is-first-output
3522     1/imm32/r32-is-first-inout
3523     0/imm32/no-imm32
3524     1/imm32/output-is-write-only
3525     _Primitive-copy-reg-to-mem/imm32/next
3526 _Primitive-copy-reg-to-mem:
3527     # copy-to var1 var2/reg => 89/copy-to var1 var2/r32
3528     "copy-to"/imm32/name
3529     Int-var-and-second-int-var-in-some-register/imm32/inouts
3530     0/imm32/outputs
3531     "89/copy-to"/imm32/subx-name
3532     1/imm32/rm32-is-first-inout
3533     2/imm32/r32-is-second-inout
3534     0/imm32/no-imm32
3535     1/imm32/output-is-write-only
3536     _Primitive-copy-mem-to-reg/imm32/next
3537 _Primitive-copy-mem-to-reg:
3538     # var1/reg <- copy var2 => 8b/copy-from var2/rm32 var1/r32
3539     "copy"/imm32/name
3540     Single-int-var-on-stack/imm32/inouts
3541     Single-int-var-in-some-register/imm32/outputs
3542     "8b/copy-from"/imm32/subx-name
3543     1/imm32/rm32-is-first-inout
3544     3/imm32/r32-is-first-output
3545     0/imm32/no-imm32
3546     1/imm32/output-is-write-only
3547     _Primitive-copy-lit-to-reg/imm32/next
3548 _Primitive-copy-lit-to-reg:
3549     # var1/reg <- copy lit => c7 0/subop/copy var1/rm32 lit/imm32
3550     "copy"/imm32/name
3551     Single-lit-var/imm32/inouts
3552     Single-int-var-in-some-register/imm32/outputs
3553     "c7 0/subop/copy"/imm32/subx-name
3554     3/imm32/rm32-is-first-output
3555     0/imm32/no-r32
3556     1/imm32/imm32-is-first-inout
3557     1/imm32/output-is-write-only
3558     _Primitive-copy-lit-to-mem/imm32/next
3559 _Primitive-copy-lit-to-mem:
3560     # copy-to var1, lit => c7 0/subop/copy var1/rm32 lit/imm32
3561     "copy-to"/imm32/name
3562     Int-var-and-literal/imm32/inouts
3563     0/imm32/outputs
3564     "c7 0/subop/copy"/imm32/subx-name
3565     1/imm32/rm32-is-first-inout
3566     0/imm32/no-r32
3567     2/imm32/imm32-is-first-inout
3568     1/imm32/output-is-write-only
3569     0/imm32/next
3570 
3571 Single-int-var-on-stack:
3572     Int-var-on-stack/imm32
3573     0/imm32/next
3574 
3575 Int-var-on-stack:
3576     "arg1"/imm32/name
3577     1/imm32/type-int
3578     1/imm32/some-block-depth
3579     1/imm32/some-stack-offset
3580     0/imm32/no-register
3581 
3582 Int-var-and-second-int-var-in-some-register:
3583     Int-var-on-stack/imm32
3584     Single-int-var-in-some-register/imm32/next
3585 
3586 Int-var-and-literal:
3587     Int-var-on-stack/imm32
3588     Single-lit-var/imm32/next
3589 
3590 Single-int-var-in-some-register:
3591     Int-var-in-some-register/imm32
3592     0/imm32/next
3593 
3594 Int-var-in-some-register:
3595     "arg1"/imm32/name
3596     1/imm32/type-int
3597     1/imm32/some-block-depth
3598     0/imm32/no-stack-offset
3599     "*"/imm32/register
3600 
3601 Single-int-var-in-eax:
3602     Int-var-in-eax/imm32
3603     0/imm32/next
3604 
3605 Int-var-in-eax:
3606     "arg1"/imm32/name
3607     1/imm32/type-int
3608     1/imm32/some-block-depth
3609     0/imm32/no-stack-offset
3610     "eax"/imm32/register
3611 
3612 Single-int-var-in-ecx:
3613     Int-var-in-ecx/imm32
3614     0/imm32/next
3615 
3616 Int-var-in-ecx:
3617     "arg1"/imm32/name
3618     1/imm32/type-int
3619     1/imm32/some-block-depth
3620     0/imm32/no-stack-offset
3621     "ecx"/imm32/register
3622 
3623 Single-int-var-in-edx:
3624     Int-var-in-edx/imm32
3625     0/imm32/next
3626 
3627 Int-var-in-edx:
3628     "arg1"/imm32/name
3629     1/imm32/type-int
3630     1/imm32/some-block-depth
3631     0/imm32/no-stack-offset
3632     "edx"/imm32/register
3633 
3634 Single-int-var-in-ebx:
3635     Int-var-in-ebx/imm32
3636     0/imm32/next
3637 
3638 Int-var-in-ebx:
3639     "arg1"/imm32/name
3640     1/imm32/type-int
3641     1/imm32/some-block-depth
3642     0/imm32/no-stack-offset
3643     "ebx"/imm32/register
3644 
3645 Single-int-var-in-esi:
3646     Int-var-in-esi/imm32
3647     0/imm32/next
3648 
3649 Int-var-in-esi:
3650     "arg1"/imm32/name
3651     1/imm32/type-int
3652     1/imm32/some-block-depth
3653     0/imm32/no-stack-offset
3654     "esi"/imm32/register
3655 
3656 Single-int-var-in-edi:
3657     Int-var-in-edi/imm32
3658     0/imm32/next
3659 
3660 Int-var-in-edi:
3661     "arg1"/imm32/name
3662     1/imm32/type-int
3663     1/imm32/some-block-depth
3664     0/imm32/no-stack-offset
3665     "edi"/imm32/register
3666 
3667 Single-lit-var:
3668     Lit-var/imm32
3669     0/imm32/next
3670 
3671 Lit-var:
3672     "literal"/imm32/name
3673     0/imm32/type-literal
3674     1/imm32/some-block-depth
3675     0/imm32/no-stack-offset
3676     0/imm32/no-register
3677 
3678 == code
3679 emit-subx-primitive:  # out : (address buffered-file), stmt : (handle statement), primitive : (handle function)
3680     # . prologue
3681     55/push-ebp
3682     89/<- %ebp 4/r32/esp
3683     # . save registers
3684     50/push-eax
3685     51/push-ecx
3686     # ecx = primitive
3687     8b/-> *(ebp+0x10) 1/r32/ecx
3688     # emit primitive name
3689     (write-buffered *(ebp+8) *(ecx+0xc))  # Primitive-subx-name
3690     # emit rm32 if necessary
3691     (emit-subx-rm32 *(ebp+8) *(ecx+0x10) *(ebp+0xc))  # out, Primitive-subx-rm32, stmt
3692     # emit r32 if necessary
3693     (emit-subx-r32 *(ebp+8) *(ecx+0x14) *(ebp+0xc))  # out, Primitive-subx-r32, stmt
3694     # emit imm32 if necessary
3695     (emit-subx-imm32 *(ebp+8) *(ecx+0x18) *(ebp+0xc))  # out, Primitive-subx-imm32, stmt
3696 $emit-subx-primitive:end:
3697     # . restore registers
3698     59/pop-to-ecx
3699     58/pop-to-eax
3700     # . epilogue
3701     89/<- %esp 5/r32/ebp
3702     5d/pop-to-ebp
3703     c3/return
3704 
3705 emit-subx-rm32:  # out : (address buffered-file), l : arg-location, stmt : (handle statement)
3706     # . prologue
3707     55/push-ebp
3708     89/<- %ebp 4/r32/esp
3709     # . save registers
3710     50/push-eax
3711     # if (l == 0) return
3712     81 7/subop/compare *(ebp+0xc) 0/imm32
3713     74/jump-if-equal $emit-subx-rm32:end/disp8
3714     #
3715     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
3716     (emit-subx-var-as-rm32 *(ebp+8) %eax)  # out, var
3717 $emit-subx-rm32:end:
3718     # . restore registers
3719     58/pop-to-eax
3720     # . epilogue
3721     89/<- %esp 5/r32/ebp
3722     5d/pop-to-ebp
3723     c3/return
3724 
3725 get-stmt-operand-from-arg-location:  # stmt : (handle statement), l : arg-location -> var/eax : (handle variable)
3726     # . prologue
3727     55/push-ebp
3728     89/<- %ebp 4/r32/esp
3729     # . save registers
3730     51/push-ecx
3731     # eax = l
3732     8b/-> *(ebp+0xc) 0/r32/eax
3733     # ecx = stmt
3734     8b/-> *(ebp+8) 1/r32/ecx
3735     # if (l == 1) return stmt->inouts->var
3736     {
3737       3d/compare-eax-and 1/imm32
3738       75/jump-if-not-equal break/disp8
3739 $get-stmt-operand-from-arg-location:1:
3740       8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
3741       8b/-> *eax 0/r32/eax  # Operand-var
3742       eb/jump $get-stmt-operand-from-arg-location:end/disp8
3743     }
3744     # if (l == 2) return stmt->inouts->next->var
3745     {
3746       3d/compare-eax-and 2/imm32
3747       75/jump-if-not-equal break/disp8
3748 $get-stmt-operand-from-arg-location:2:
3749       8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
3750       8b/-> *(eax+4) 0/r32/eax  # Operand-next
3751       8b/-> *eax 0/r32/eax  # Operand-var
3752       eb/jump $get-stmt-operand-from-arg-location:end/disp8
3753     }
3754     # if (l == 3) return stmt->outputs
3755     {
3756       3d/compare-eax-and 3/imm32
3757       75/jump-if-not-equal break/disp8
3758 $get-stmt-operand-from-arg-location:3:
3759       8b/-> *(ecx+0xc) 0/r32/eax  # Stmt1-outputs
3760       8b/-> *eax 0/r32/eax  # Operand-var
3761       eb/jump $get-stmt-operand-from-arg-location:end/disp8
3762     }
3763     # abort
3764     e9/jump $get-stmt-operand-from-arg-location:abort/disp32
3765 $get-stmt-operand-from-arg-location:end:
3766     # . restore registers
3767     59/pop-to-ecx
3768     # . epilogue
3769     89/<- %esp 5/r32/ebp
3770     5d/pop-to-ebp
3771     c3/return
3772 
3773 $get-stmt-operand-from-arg-location:abort:
3774     # error("invalid arg-location " eax)
3775     (write-buffered Stderr "invalid arg-location ")
3776     (print-int32-buffered Stderr %eax)
3777     (write-buffered Stderr "\n")
3778     (flush Stderr)
3779     # . syscall(exit, 1)
3780     bb/copy-to-ebx  1/imm32
3781     b8/copy-to-eax  1/imm32/exit
3782     cd/syscall  0x80/imm8
3783     # never gets here
3784 
3785 emit-subx-r32:  # out : (address buffered-file), l : arg-location, stmt : (handle statement)
3786     # . prologue
3787     55/push-ebp
3788     89/<- %ebp 4/r32/esp
3789     # . save registers
3790     50/push-eax
3791     51/push-ecx
3792     # if (location == 0) return
3793     81 7/subop/compare *(ebp+0xc) 0/imm32
3794     0f 84/jump-if-equal $emit-subx-r32:end/disp32
3795     #
3796     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
3797     (maybe-get Registers *(eax+0x10) 8)  # Var-register => eax : (address register-index)
3798     (write-buffered *(ebp+8) Space)
3799     (print-int32-buffered *(ebp+8) *eax)
3800     (write-buffered *(ebp+8) "/r32")
3801 $emit-subx-r32:end:
3802     # . restore registers
3803     59/pop-to-ecx
3804     58/pop-to-eax
3805     # . epilogue
3806     89/<- %esp 5/r32/ebp
3807     5d/pop-to-ebp
3808     c3/return
3809 
3810 emit-subx-imm32:  # out : (address buffered-file), l : arg-location, stmt : (handle statement)
3811     # . prologue
3812     55/push-ebp
3813     89/<- %ebp 4/r32/esp
3814     # . save registers
3815     50/push-eax
3816     51/push-ecx
3817     # if (location == 0) return
3818     81 7/subop/compare *(ebp+0xc) 0/imm32
3819     74/jump-if-equal $emit-subx-imm32:end/disp8
3820     #
3821     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
3822     (write-buffered *(ebp+8) Space)
3823     (write-buffered *(ebp+8) *eax)  # Var-name
3824     (write-buffered *(ebp+8) "/imm32")
3825 $emit-subx-imm32:end:
3826     # . restore registers
3827     59/pop-to-ecx
3828     58/pop-to-eax
3829     # . epilogue
3830     89/<- %esp 5/r32/ebp
3831     5d/pop-to-ebp
3832     c3/return
3833 
3834 emit-subx-call:  # out : (address buffered-file), stmt : (handle statement), callee : (handle function)
3835     # . prologue
3836     55/push-ebp
3837     89/<- %ebp 4/r32/esp
3838     # . save registers
3839     50/push-eax
3840     51/push-ecx
3841     #
3842     (write-buffered *(ebp+8) "(")
3843     # - emit function name
3844     8b/-> *(ebp+0x10) 1/r32/ecx
3845     (write-buffered *(ebp+8) *(ecx+4))  # Function-subx-name
3846     # - emit arguments
3847     # var curr/ecx : (handle list var) = stmt->inouts
3848     8b/-> *(ebp+0xc) 1/r32/ecx
3849     8b/-> *(ecx+8) 1/r32/ecx  # Stmt1-inouts
3850     {
3851       # if (curr == null) break
3852       81 7/subop/compare %ecx 0/imm32
3853       74/jump-if-equal break/disp8
3854       #
3855       (emit-subx-call-operand *(ebp+8) *ecx)
3856       # curr = curr->next
3857       8b/-> *(ecx+4) 1/r32/ecx
3858     }
3859     #
3860     (write-buffered *(ebp+8) ")")
3861 $emit-subx-call:end:
3862     # . restore registers
3863     59/pop-to-ecx
3864     58/pop-to-eax
3865     # . epilogue
3866     89/<- %esp 5/r32/ebp
3867     5d/pop-to-ebp
3868     c3/return
3869 
3870 emit-subx-call-operand:  # out : (address buffered-file), operand : (handle variable)
3871     # . prologue
3872     55/push-ebp
3873     89/<- %ebp 4/r32/esp
3874     # . save registers
3875     50/push-eax
3876     # eax = operand
3877     8b/-> *(ebp+0xc) 0/r32/eax
3878     # if non-literal, emit appropriately
3879     (emit-subx-var-as-rm32 *(ebp+8) %eax)
3880     # else if (operand->type == literal) emit "__"
3881     {
3882       81 7/subop/compare *(eax+4) 0/imm32  # Var-type
3883       75/jump-if-not-equal break/disp8
3884 $emit-subx-call-operand:literal:
3885       (write-buffered *(ebp+8) Space)
3886       (write-buffered *(ebp+8) *eax)
3887     }
3888 $emit-subx-call-operand:end:
3889     # . restore registers
3890     58/pop-to-eax
3891     # . epilogue
3892     89/<- %esp 5/r32/ebp
3893     5d/pop-to-ebp
3894     c3/return
3895 
3896 emit-subx-var-as-rm32:  # out : (address buffered-file), operand : (handle variable)
3897     # . prologue
3898     55/push-ebp
3899     89/<- %ebp 4/r32/esp
3900     # . save registers
3901     50/push-eax
3902     # eax = operand
3903     8b/-> *(ebp+0xc) 0/r32/eax
3904     # if (operand->register) emit "%__"
3905     {
3906       81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
3907       74/jump-if-equal break/disp8
3908 $emit-subx-var-as-rm32:register:
3909       (write-buffered *(ebp+8) " %")
3910       (write-buffered *(ebp+8) *(eax+0x10))  # Var-register
3911     }
3912     # else if (operand->stack-offset) emit "*(ebp+__)"
3913     {
3914       81 7/subop/compare *(eax+0xc) 0/imm32  # Var-stack-offset
3915       74/jump-if-equal break/disp8
3916 $emit-subx-var-as-rm32:stack:
3917       (write-buffered *(ebp+8) Space)
3918       (write-buffered *(ebp+8) "*(ebp+")
3919       8b/-> *(ebp+0xc) 0/r32/eax
3920       (print-int32-buffered *(ebp+8) *(eax+0xc))  # Var-stack-offset
3921       (write-buffered *(ebp+8) ")")
3922     }
3923 $emit-subx-var-as-rm32:end:
3924     # . restore registers
3925     58/pop-to-eax
3926     # . epilogue
3927     89/<- %esp 5/r32/ebp
3928     5d/pop-to-ebp
3929     c3/return
3930 
3931 find-matching-function:  # functions : (address function), stmt : (handle statement) -> result/eax : (handle function)
3932     # . prologue
3933     55/push-ebp
3934     89/<- %ebp 4/r32/esp
3935     # . save registers
3936     51/push-ecx
3937     # var curr/ecx : (handle function) = functions
3938     8b/-> *(ebp+8) 1/r32/ecx
3939     {
3940       # if (curr == null) break
3941       81 7/subop/compare %ecx 0/imm32
3942       74/jump-if-equal break/disp8
3943       # if match(stmt, curr) return curr
3944       {
3945         (mu-stmt-matches-function? *(ebp+0xc) %ecx)  # => eax
3946         3d/compare-eax-and 0/imm32
3947         74/jump-if-equal break/disp8
3948         89/<- %eax 1/r32/ecx
3949         eb/jump $find-matching-function:end/disp8
3950       }
3951       # curr = curr->next
3952       8b/-> *(ecx+0x10) 1/r32/ecx  # Function-next
3953       eb/jump loop/disp8
3954     }
3955     # return null
3956     b8/copy-to-eax 0/imm32
3957 $find-matching-function:end:
3958     # . restore registers
3959     59/pop-to-ecx
3960     # . epilogue
3961     89/<- %esp 5/r32/ebp
3962     5d/pop-to-ebp
3963     c3/return
3964 
3965 find-matching-primitive:  # primitives : (handle primitive), stmt : (handle statement) -> result/eax : (handle primitive)
3966     # . prologue
3967     55/push-ebp
3968     89/<- %ebp 4/r32/esp
3969     # . save registers
3970     51/push-ecx
3971     # var curr/ecx : (handle primitive) = primitives
3972     8b/-> *(ebp+8) 1/r32/ecx
3973     {
3974 $find-matching-primitive:loop:
3975       # if (curr == null) break
3976       81 7/subop/compare %ecx 0/imm32
3977       0f 84/jump-if-equal break/disp32
3978 #?       (write-buffered Stderr "prim: ")
3979 #?       (write-buffered Stderr *ecx)  # Primitive-name
3980 #?       (write-buffered Stderr " => ")
3981 #?       (write-buffered Stderr *(ecx+0xc))  # Primitive-subx-name
3982 #?       (write-buffered Stderr "\n")
3983 #?       (flush Stderr)
3984       # if match(curr, stmt) return curr
3985       {
3986         (mu-stmt-matches-primitive? *(ebp+0xc) %ecx)  # => eax
3987         3d/compare-eax-and 0/imm32
3988         74/jump-if-equal break/disp8
3989         89/<- %eax 1/r32/ecx
3990         eb/jump $find-matching-primitive:end/disp8
3991       }
3992 $find-matching-primitive:next-primitive:
3993       # curr = curr->next
3994       8b/-> *(ecx+0x20) 1/r32/ecx  # Primitive-next
3995       e9/jump loop/disp32
3996     }
3997     # return null
3998     b8/copy-to-eax 0/imm32
3999 $find-matching-primitive:end:
4000     # . restore registers
4001     59/pop-to-ecx
4002     # . epilogue
4003     89/<- %esp 5/r32/ebp
4004     5d/pop-to-ebp
4005     c3/return
4006 
4007 mu-stmt-matches-function?:  # stmt : (handle statement), function : (handle function) => result/eax : boolean
4008     # . prologue
4009     55/push-ebp
4010     89/<- %ebp 4/r32/esp
4011     # . save registers
4012     51/push-ecx
4013     # return primitive->name == stmt->operation
4014     8b/-> *(ebp+8) 1/r32/ecx
4015     8b/-> *(ebp+0xc) 0/r32/eax
4016     (string-equal? *(ecx+4) *eax)  # Stmt1-operation, Primitive-name => eax
4017 $mu-stmt-matches-function?:end:
4018     # . restore registers
4019     59/pop-to-ecx
4020     # . epilogue
4021     89/<- %esp 5/r32/ebp
4022     5d/pop-to-ebp
4023     c3/return
4024 
4025 mu-stmt-matches-primitive?:  # stmt : (handle statement), primitive : (handle primitive) => result/eax : boolean
4026     # A mu stmt matches a primitive if the name matches, all the inout vars
4027     # match, and all the output vars match.
4028     # Vars match if types match and registers match.
4029     # In addition, a stmt output matches a primitive's output if types match
4030     # and the primitive has a wildcard register.
4031     # . prologue
4032     55/push-ebp
4033     89/<- %ebp 4/r32/esp
4034     # . save registers
4035     51/push-ecx
4036     52/push-edx
4037     53/push-ebx
4038     56/push-esi
4039     57/push-edi
4040     # ecx = stmt
4041     8b/-> *(ebp+8) 1/r32/ecx
4042     # edx = primitive
4043     8b/-> *(ebp+0xc) 2/r32/edx
4044     {
4045 $mu-stmt-matches-primitive?:check-name:
4046       # if (primitive->name != stmt->operation) return false
4047       (string-equal? *(ecx+4) *edx)  # Stmt1-operation, Primitive-name => eax
4048       3d/compare-eax-and 0/imm32
4049       75/jump-if-not-equal break/disp8
4050       b8/copy-to-eax 0/imm32
4051       e9/jump $mu-stmt-matches-primitive?:end/disp32
4052     }
4053 $mu-stmt-matches-primitive?:check-inouts:
4054     # for (curr/esi in stmt->inouts, curr2/edi in primitive->inouts)
4055     8b/-> *(ecx+8) 6/r32/esi  # Stmt1-inouts
4056     8b/-> *(edx+4) 7/r32/edi  # Primitive-inouts
4057     {
4058       # if (curr == 0 && curr2 == 0) move on to check outputs
4059       {
4060         81 7/subop/compare %esi 0/imm32
4061         75/jump-if-not-equal break/disp8
4062 $mu-stmt-matches-primitive?:stmt-inout-is-null:
4063         {
4064           81 7/subop/compare %edi 0/imm32
4065           75/jump-if-not-equal break/disp8
4066           #
4067           e9/jump $mu-stmt-matches-primitive?:check-outputs/disp32
4068         }
4069         # return false
4070         b8/copy-to-eax 0/imm32/false
4071         e9/jump $mu-stmt-matches-primitive?:end/disp32
4072       }
4073       # if (curr2 == 0) return false
4074       {
4075         81 7/subop/compare %edi 0/imm32
4076         75/jump-if-not-equal break/disp8
4077 $mu-stmt-matches-primitive?:prim-inout-is-null:
4078         b8/copy-to-eax 0/imm32/false
4079         e9/jump $mu-stmt-matches-primitive?:end/disp32
4080       }
4081       # if (curr != curr2) return false
4082       {
4083         (operand-matches-primitive? *esi *edi)  # => eax
4084         3d/compare-eax-and 0/imm32
4085         75/jump-if-not-equal break/disp8
4086         b8/copy-to-eax 0/imm32/false
4087         e9/jump $mu-stmt-matches-primitive?:end/disp32
4088       }
4089       # curr=curr->next
4090       8b/-> *(esi+4) 6/r32/esi  # Operand-next
4091       # curr2=curr2->next
4092       8b/-> *(edi+4) 7/r32/edi  # Operand-next
4093       eb/jump loop/disp8
4094     }
4095 $mu-stmt-matches-primitive?:check-outputs:
4096     # for (curr/esi in stmt->outputs, curr2/edi in primitive->outputs)
4097     8b/-> *(ecx+0xc) 6/r32/esi  # Stmt1-outputs
4098     8b/-> *(edx+8) 7/r32/edi  # Primitive-outputs
4099     {
4100       # if (curr == 0) return (curr2 == 0)
4101       {
4102 $mu-stmt-matches-primitive?:check-output:
4103         81 7/subop/compare %esi 0/imm32
4104         75/jump-if-not-equal break/disp8
4105         {
4106           81 7/subop/compare %edi 0/imm32
4107           75/jump-if-not-equal break/disp8
4108           # return true
4109           b8/copy-to-eax 1/imm32
4110           e9/jump $mu-stmt-matches-primitive?:end/disp32
4111         }
4112         # return false
4113         b8/copy-to-eax 0/imm32
4114         e9/jump $mu-stmt-matches-primitive?:end/disp32
4115       }
4116       # if (curr2 == 0) return false
4117       {
4118         81 7/subop/compare %edi 0/imm32
4119         75/jump-if-not-equal break/disp8
4120         b8/copy-to-eax 0/imm32
4121         e9/jump $mu-stmt-matches-primitive?:end/disp32
4122       }
4123       # if (curr != curr2) return false
4124       {
4125         (operand-matches-primitive? *esi *edi)  # => eax
4126         3d/compare-eax-and 0/imm32
4127         75/jump-if-not-equal break/disp8
4128         b8/copy-to-eax 0/imm32
4129         e9/jump $mu-stmt-matches-primitive?:end/disp32
4130       }
4131       # curr=curr->next
4132       8b/-> *(esi+4) 6/r32/esi  # Operand-next
4133       # curr2=curr2->next
4134       8b/-> *(edi+4) 7/r32/edi  # Operand-next
4135       eb/jump loop/disp8
4136     }
4137 $mu-stmt-matches-primitive?:return-true:
4138     b8/copy-to-eax 1/imm32
4139 $mu-stmt-matches-primitive?:end:
4140     # . restore registers
4141     5f/pop-to-edi
4142     5e/pop-to-esi
4143     5b/pop-to-ebx
4144     5a/pop-to-edx
4145     59/pop-to-ecx
4146     # . epilogue
4147     89/<- %esp 5/r32/ebp
4148     5d/pop-to-ebp
4149     c3/return
4150 
4151 operand-matches-primitive?:  # var : (handle var), primout-var : (handle var) => result/eax : boolean
4152     # . prologue
4153     55/push-ebp
4154     89/<- %ebp 4/r32/esp
4155     # . save registers
4156     56/push-esi
4157     57/push-edi
4158     # esi = var
4159     8b/-> *(ebp+8) 6/r32/esi
4160     # edi = primout-var
4161     8b/-> *(ebp+0xc) 7/r32/edi
4162     # if (var->type != primout-var->type) return false
4163     8b/-> *(esi+4) 0/r32/eax  # Var-type
4164     39/compare *(edi+4) 0/r32/eax  # Var-type
4165     b8/copy-to-eax 0/imm32/false
4166     75/jump-if-not-equal $operand-matches-primitive?:end/disp8
4167     # return false if var->register doesn't match primout-var->register
4168     {
4169       # if addresses are equal, don't return here
4170       8b/-> *(esi+0x10) 0/r32/eax
4171       39/compare *(edi+0x10) 0/r32/eax
4172       74/jump-if-equal break/disp8
4173       # if either address is 0, return false
4174       3d/compare-eax-and 0/imm32
4175       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
4176       81 7/subop/compare *(edi+0x10) 0/imm32
4177       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
4178       # if primout-var->register is "*", return true
4179       (string-equal? *(edi+0x10) "*")  # Var-register
4180       3d/compare-eax-and 0/imm32
4181       b8/copy-to-eax 1/imm32/true
4182       75/jump-if-not-equal $operand-matches-primitive?:end/disp8
4183       # if string contents don't match, return false
4184       (string-equal? *(esi+0x10) *(edi+0x10))  # Var-register Var-register
4185       3d/compare-eax-and 0/imm32
4186       b8/copy-to-eax 0/imm32/false
4187       74/jump-if-equal $operand-matches-primitive?:end/disp8
4188     }
4189     # return true
4190     b8/copy-to-eax 1/imm32/true
4191 $operand-matches-primitive?:end:
4192     # . restore registers
4193     5f/pop-to-edi
4194     5e/pop-to-esi
4195     # . epilogue
4196     89/<- %esp 5/r32/ebp
4197     5d/pop-to-ebp
4198     c3/return
4199 
4200 test-emit-subx-statement-primitive:
4201     # Primitive operation on a variable on the stack.
4202     #   increment foo
4203     # =>
4204     #   ff 0/subop/increment *(ebp-8)
4205     #
4206     # There's a variable on the var stack as follows:
4207     #   name: 'foo'
4208     #   type: int
4209     #   stack-offset: -8
4210     #
4211     # There's a primitive with this info:
4212     #   name: 'increment'
4213     #   inouts: int/mem
4214     #   value: 'ff 0/subop/increment'
4215     #
4216     # There's nothing in functions.
4217     #
4218     # . prologue
4219     55/push-ebp
4220     89/<- %ebp 4/r32/esp
4221     # setup
4222     (clear-stream _test-output-stream)
4223     (clear-stream $_test-output-buffered-file->buffer)
4224     # var var-foo/ecx : (ref var)
4225     68/push 0/imm32/no-register
4226     68/push -8/imm32/stack-offset
4227     68/push 1/imm32/block-depth
4228     68/push 1/imm32/type-int
4229     68/push "foo"/imm32
4230     89/<- %ecx 4/r32/esp
4231     # var operand/ebx : (ref list var)
4232     68/push 0/imm32/next
4233     51/push-ecx/var-foo
4234     89/<- %ebx 4/r32/esp
4235     # var stmt/esi : (ref statement)
4236     68/push 0/imm32/next
4237     68/push 0/imm32/outputs
4238     53/push-ebx/operands
4239     68/push "increment"/imm32/operation
4240     68/push 1/imm32
4241     89/<- %esi 4/r32/esp
4242     # var primitives/ebx : (ref primitive)
4243     68/push 0/imm32/next
4244     68/push 0/imm32/output-is-write-only
4245     68/push 0/imm32/no-imm32
4246     68/push 0/imm32/no-r32
4247     68/push 1/imm32/rm32-is-first-inout
4248     68/push "ff 0/subop/increment"/imm32/subx-name
4249     68/push 0/imm32/outputs
4250     53/push-ebx/inouts  # hack; in practice we won't have the same var in function definition and call
4251     68/push "increment"/imm32/name
4252     89/<- %ebx 4/r32/esp
4253     # convert
4254     (emit-subx-statement _test-output-buffered-file %esi %ebx 0)
4255     (flush _test-output-buffered-file)
4256 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4262     # check output
4263     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0xfffffff8)" "F - test-emit-subx-statement-primitive")
4264     # . epilogue
4265     89/<- %esp 5/r32/ebp
4266     5d/pop-to-ebp
4267     c3/return
4268 
4269 test-emit-subx-statement-primitive-register:
4270     # Primitive operation on a variable in a register.
4271     #   foo <- increment
4272     # =>
4273     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
4274     #
4275     # There's a variable on the var stack as follows:
4276     #   name: 'foo'
4277     #   type: int
4278     #   register: 'eax'
4279     #
4280     # There's a primitive with this info:
4281     #   name: 'increment'
4282     #   out: int/reg
4283     #   value: 'ff 0/subop/increment'
4284     #
4285     # There's nothing in functions.
4286     #
4287     # . prologue
4288     55/push-ebp
4289     89/<- %ebp 4/r32/esp
4290     # setup
4291     (clear-stream _test-output-stream)
4292     (clear-stream $_test-output-buffered-file->buffer)
4293     # var var-foo/ecx : (ref var) in eax
4294     68/push "eax"/imm32/register
4295     68/push 0/imm32/no-stack-offset
4296     68/push 1/imm32/block-depth
4297     68/push 1/imm32/type-int
4298     68/push "foo"/imm32
4299     89/<- %ecx 4/r32/esp
4300     # var operand/ebx : (ref list var)
4301     68/push 0/imm32/next
4302     51/push-ecx/var-foo
4303     89/<- %ebx 4/r32/esp
4304     # var stmt/esi : (ref statement)
4305     68/push 0/imm32/next
4306     53/push-ebx/outputs
4307     68/push 0/imm32/inouts
4308     68/push "increment"/imm32/operation
4309     68/push 1/imm32
4310     89/<- %esi 4/r32/esp
4311     # var formal-var/ebx : (ref var) in any register
4312     68/push Any-register/imm32
4313     68/push 0/imm32/no-stack-offset
4314     68/push 1/imm32/block-depth
4315     68/push 1/imm32/type-int
4316     68/push "dummy"/imm32
4317     89/<- %ebx 4/r32/esp
4318     # var operand/ebx : (ref list var)
4319     68/push 0/imm32/next
4320     53/push-ebx/formal-var
4321     89/<- %ebx 4/r32/esp
4322     # var primitives/ebx : (ref primitive)
4323     68/push 0/imm32/next
4324     68/push 0/imm32/output-is-write-only
4325     68/push 0/imm32/no-imm32
4326     68/push 0/imm32/no-r32
4327     68/push 3/imm32/rm32-in-first-output
4328     68/push "ff 0/subop/increment"/imm32/subx-name
4329     53/push-ebx/outputs
4330     68/push 0/imm32/inouts
4331     68/push "increment"/imm32/name
4332     89/<- %ebx 4/r32/esp
4333     # convert
4334     (emit-subx-statement _test-output-buffered-file %esi %ebx 0)
4335     (flush _test-output-buffered-file)
4336 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4342     # check output
4343     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-primitive-register")
4344     # . epilogue
4345     89/<- %esp 5/r32/ebp
4346     5d/pop-to-ebp
4347     c3/return
4348 
4349 test-emit-subx-statement-select-primitive:
4350     # Select the right primitive between overloads.
4351     #   foo <- increment
4352     # =>
4353     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
4354     #
4355     # There's a variable on the var stack as follows:
4356     #   name: 'foo'
4357     #   type: int
4358     #   register: 'eax'
4359     #
4360     # There's two primitives, as follows:
4361     #   - name: 'increment'
4362     #     out: int/reg
4363     #     value: 'ff 0/subop/increment'
4364     #   - name: 'increment'
4365     #     inout: int/mem
4366     #     value: 'ff 0/subop/increment'
4367     #
4368     # There's nothing in functions.
4369     #
4370     # . prologue
4371     55/push-ebp
4372     89/<- %ebp 4/r32/esp
4373     # setup
4374     (clear-stream _test-output-stream)
4375     (clear-stream $_test-output-buffered-file->buffer)
4376     # var var-foo/ecx : (ref var) in eax
4377     68/push "eax"/imm32/register
4378     68/push 0/imm32/no-stack-offset
4379     68/push 1/imm32/block-depth
4380     68/push 1/imm32/type-int
4381     68/push "foo"/imm32
4382     89/<- %ecx 4/r32/esp
4383     # var real-outputs/edi : (ref list var)
4384     68/push 0/imm32/next
4385     51/push-ecx/var-foo
4386     89/<- %edi 4/r32/esp
4387     # var stmt/esi : (ref statement)
4388     68/push 0/imm32/next
4389     57/push-edi/outputs
4390     68/push 0/imm32/inouts
4391     68/push "increment"/imm32/operation
4392     68/push 1/imm32
4393     89/<- %esi 4/r32/esp
4394     # var formal-var/ebx : (ref var) in any register
4395     68/push Any-register/imm32
4396     68/push 0/imm32/no-stack-offset
4397     68/push 1/imm32/block-depth
4398     68/push 1/imm32/type-int
4399     68/push "dummy"/imm32
4400     89/<- %ebx 4/r32/esp
4401     # var formal-outputs/ebx : (ref list var) = {formal-var, 0}
4402     68/push 0/imm32/next
4403     53/push-ebx/formal-var
4404     89/<- %ebx 4/r32/esp
4405     # var primitive1/ebx : (ref primitive)
4406     68/push 0/imm32/next
4407     68/push 0/imm32/output-is-write-only
4408     68/push 0/imm32/no-imm32
4409     68/push 0/imm32/no-r32
4410     68/push 3/imm32/rm32-in-first-output
4411     68/push "ff 0/subop/increment"/imm32/subx-name
4412     53/push-ebx/outputs/formal-outputs
4413     68/push 0/imm32/inouts
4414     68/push "increment"/imm32/name
4415     89/<- %ebx 4/r32/esp
4416     # var primitives/ebx : (ref primitive)
4417     53/push-ebx/next
4418     68/push 0/imm32/output-is-write-only
4419     68/push 0/imm32/no-imm32
4420     68/push 0/imm32/no-r32
4421     68/push 1/imm32/rm32-is-first-inout
4422     68/push "ff 0/subop/increment"/imm32/subx-name
4423     68/push 0/imm32/outputs
4424     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
4425     68/push "increment"/imm32/name
4426     89/<- %ebx 4/r32/esp
4427     # convert
4428     (emit-subx-statement _test-output-buffered-file %esi %ebx 0)
4429     (flush _test-output-buffered-file)
4430 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4436     # check output
4437     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive")
4438     # . epilogue
4439     89/<- %esp 5/r32/ebp
4440     5d/pop-to-ebp
4441     c3/return
4442 
4443 test-emit-subx-statement-select-primitive-2:
4444     # Select the right primitive between overloads.
4445     #   foo <- increment
4446     # =>
4447     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
4448     #
4449     # There's a variable on the var stack as follows:
4450     #   name: 'foo'
4451     #   type: int
4452     #   register: 'eax'
4453     #
4454     # There's two primitives, as follows:
4455     #   - name: 'increment'
4456     #     out: int/reg
4457     #     value: 'ff 0/subop/increment'
4458     #   - name: 'increment'
4459     #     inout: int/mem
4460     #     value: 'ff 0/subop/increment'
4461     #
4462     # There's nothing in functions.
4463     #
4464     # . prologue
4465     55/push-ebp
4466     89/<- %ebp 4/r32/esp
4467     # setup
4468     (clear-stream _test-output-stream)
4469     (clear-stream $_test-output-buffered-file->buffer)
4470     # var var-foo/ecx : (ref var) in eax
4471     68/push "eax"/imm32/register
4472     68/push 0/imm32/no-stack-offset
4473     68/push 1/imm32/block-depth
4474     68/push 1/imm32/type-int
4475     68/push "foo"/imm32
4476     89/<- %ecx 4/r32/esp
4477     # var inouts/edi : (ref list var)
4478     68/push 0/imm32/next
4479     51/push-ecx/var-foo
4480     89/<- %edi 4/r32/esp
4481     # var stmt/esi : (ref statement)
4482     68/push 0/imm32/next
4483     68/push 0/imm32/outputs
4484     57/push-edi/inouts
4485     68/push "increment"/imm32/operation
4486     68/push 1/imm32
4487     89/<- %esi 4/r32/esp
4488     # var formal-var/ebx : (ref var) in any register
4489     68/push Any-register/imm32
4490     68/push 0/imm32/no-stack-offset
4491     68/push 1/imm32/block-depth
4492     68/push 1/imm32/type-int
4493     68/push "dummy"/imm32
4494     89/<- %ebx 4/r32/esp
4495     # var operand/ebx : (ref list var)
4496     68/push 0/imm32/next
4497     53/push-ebx/formal-var
4498     89/<- %ebx 4/r32/esp
4499     # var primitive1/ebx : primitive
4500     68/push 0/imm32/next
4501     68/push 0/imm32/output-is-write-only
4502     68/push 0/imm32/no-imm32
4503     68/push 0/imm32/no-r32
4504     68/push 3/imm32/rm32-in-first-output
4505     68/push "ff 0/subop/increment"/imm32/subx-name
4506     53/push-ebx/outputs/formal-outputs
4507     68/push 0/imm32/inouts
4508     68/push "increment"/imm32/name
4509     89/<- %ebx 4/r32/esp
4510     # var primitives/ebx : (ref primitive)
4511     53/push-ebx/next
4512     68/push 0/imm32/output-is-write-only
4513     68/push 0/imm32/no-imm32
4514     68/push 0/imm32/no-r32
4515     68/push 1/imm32/rm32-is-first-inout
4516     68/push "ff 0/subop/increment"/imm32/subx-name
4517     68/push 0/imm32/outputs
4518     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
4519     68/push "increment"/imm32/name
4520     89/<- %ebx 4/r32/esp
4521     # convert
4522     (emit-subx-statement _test-output-buffered-file %esi %ebx 0)
4523     (flush _test-output-buffered-file)
4524 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4530     # check output
4531     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive-2")
4532     # . epilogue
4533     89/<- %esp 5/r32/ebp
4534     5d/pop-to-ebp
4535     c3/return
4536 
4537 test-increment-register:
4538     # Select the right primitive between overloads.
4539     #   foo <- increment
4540     # =>
4541     #   50/increment-eax
4542     #
4543     # There's a variable on the var stack as follows:
4544     #   name: 'foo'
4545     #   type: int
4546     #   register: 'eax'
4547     #
4548     # Primitives are the global definitions.
4549     #
4550     # There are no functions defined.
4551     #
4552     # . prologue
4553     55/push-ebp
4554     89/<- %ebp 4/r32/esp
4555     # setup
4556     (clear-stream _test-output-stream)
4557     (clear-stream $_test-output-buffered-file->buffer)
4558     # var var-foo/ecx : (ref var) in eax
4559     68/push "eax"/imm32/register
4560     68/push 0/imm32/no-stack-offset
4561     68/push 1/imm32/block-depth
4562     68/push 1/imm32/type-int
4563     68/push "foo"/imm32
4564     89/<- %ecx 4/r32/esp
4565     # var real-outputs/edi : (ref list var)
4566     68/push 0/imm32/next
4567     51/push-ecx/var-foo
4568     89/<- %edi 4/r32/esp
4569     # var stmt/esi : (ref statement)
4570     68/push 0/imm32/next
4571     57/push-edi/outputs
4572     68/push 0/imm32/inouts
4573     68/push "increment"/imm32/operation
4574     68/push 1/imm32
4575     89/<- %esi 4/r32/esp
4576     # convert
4577     (emit-subx-statement _test-output-buffered-file %esi Primitives 0)
4578     (flush _test-output-buffered-file)
4579 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4585     # check output
4586     (check-next-stream-line-equal _test-output-stream "40/increment-eax" "F - test-increment-register")
4587     # . epilogue
4588     89/<- %esp 5/r32/ebp
4589     5d/pop-to-ebp
4590     c3/return
4591 
4592 test-increment-var:
4593     # Select the right primitive between overloads.
4594     #   foo <- increment
4595     # =>
4596     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
4597     #
4598     # There's a variable on the var stack as follows:
4599     #   name: 'foo'
4600     #   type: int
4601     #   register: 'eax'
4602     #
4603     # Primitives are the global definitions.
4604     #
4605     # There are no functions defined.
4606     #
4607     # . prologue
4608     55/push-ebp
4609     89/<- %ebp 4/r32/esp
4610     # setup
4611     (clear-stream _test-output-stream)
4612     (clear-stream $_test-output-buffered-file->buffer)
4613     # var var-foo/ecx : (ref var) in eax
4614     68/push "eax"/imm32/register
4615     68/push 0/imm32/no-stack-offset
4616     68/push 1/imm32/block-depth
4617     68/push 1/imm32/type-int
4618     68/push "foo"/imm32
4619     89/<- %ecx 4/r32/esp
4620     # var inouts/edi : (ref list var)
4621     68/push 0/imm32/next
4622     51/push-ecx/var-foo
4623     89/<- %edi 4/r32/esp
4624     # var stmt/esi : (ref statement)
4625     68/push 0/imm32/next
4626     68/push 0/imm32/outputs
4627     57/push-edi/inouts
4628     68/push "increment"/imm32/operation
4629     68/push 1/imm32
4630     89/<- %esi 4/r32/esp
4631     # convert
4632     (emit-subx-statement _test-output-buffered-file %esi Primitives 0)
4633     (flush _test-output-buffered-file)
4634 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4640     # check output
4641     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-increment-var")
4642     # . epilogue
4643     89/<- %esp 5/r32/ebp
4644     5d/pop-to-ebp
4645     c3/return
4646 
4647 test-add-reg-to-reg:
4648     #   var1/reg <- add var2/reg
4649     # =>
4650     #   01/add %var1 var2
4651     #
4652     # . prologue
4653     55/push-ebp
4654     89/<- %ebp 4/r32/esp
4655     # setup
4656     (clear-stream _test-output-stream)
4657     (clear-stream $_test-output-buffered-file->buffer)
4658     # var var-var1/ecx : (ref var) in eax
4659     68/push "eax"/imm32/register
4660     68/push 0/imm32/no-stack-offset
4661     68/push 1/imm32/block-depth
4662     68/push 1/imm32/type-int
4663     68/push "var1"/imm32
4664     89/<- %ecx 4/r32/esp
4665     # var var-var2/edx : (ref var) in ecx
4666     68/push "ecx"/imm32/register
4667     68/push 0/imm32/no-stack-offset
4668     68/push 1/imm32/block-depth
4669     68/push 1/imm32/type-int
4670     68/push "var2"/imm32
4671     89/<- %edx 4/r32/esp
4672     # var inouts/esi : (ref list var2)
4673     68/push 0/imm32/next
4674     52/push-edx/var-var2
4675     89/<- %esi 4/r32/esp
4676     # var outputs/edi : (ref list var1)
4677     68/push 0/imm32/next
4678     51/push-ecx/var-var1
4679     89/<- %edi 4/r32/esp
4680     # var stmt/esi : (ref statement)
4681     68/push 0/imm32/next
4682     57/push-edi/outputs
4683     56/push-esi/inouts
4684     68/push "add"/imm32/operation
4685     68/push 1/imm32
4686     89/<- %esi 4/r32/esp
4687     # convert
4688     (emit-subx-statement _test-output-buffered-file %esi Primitives 0)
4689     (flush _test-output-buffered-file)
4690 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4696     # check output
4697     (check-next-stream-line-equal _test-output-stream "01/add-to %eax 0x00000001/r32" "F - test-add-reg-to-reg")
4698     # . epilogue
4699     89/<- %esp 5/r32/ebp
4700     5d/pop-to-ebp
4701     c3/return
4702 
4703 test-add-reg-to-mem:
4704     #   add-to var1 var2/reg
4705     # =>
4706     #   01/add *(ebp+__) var2
4707     #
4708     # . prologue
4709     55/push-ebp
4710     89/<- %ebp 4/r32/esp
4711     # setup
4712     (clear-stream _test-output-stream)
4713     (clear-stream $_test-output-buffered-file->buffer)
4714     # var var-var1/ecx : (ref var)
4715     68/push 0/imm32/no-register
4716     68/push 8/imm32/stack-offset
4717     68/push 1/imm32/block-depth
4718     68/push 1/imm32/type-int
4719     68/push "var1"/imm32
4720     89/<- %ecx 4/r32/esp
4721     # var var-var2/edx : (ref var) in ecx
4722     68/push "ecx"/imm32/register
4723     68/push 0/imm32/no-stack-offset
4724     68/push 1/imm32/block-depth
4725     68/push 1/imm32/type-int
4726     68/push "var2"/imm32
4727     89/<- %edx 4/r32/esp
4728     # var inouts/esi : (ref list var2)
4729     68/push 0/imm32/next
4730     52/push-edx/var-var2
4731     89/<- %esi 4/r32/esp
4732     # var inouts = (ref list var1 var2)
4733     56/push-esi/next
4734     51/push-ecx/var-var1
4735     89/<- %esi 4/r32/esp
4736     # var stmt/esi : (ref statement)
4737     68/push 0/imm32/next
4738     68/push 0/imm32/outputs
4739     56/push-esi/inouts
4740     68/push "add-to"/imm32/operation
4741     68/push 1/imm32
4742     89/<- %esi 4/r32/esp
4743     # convert
4744     (emit-subx-statement _test-output-buffered-file %esi Primitives 0)
4745     (flush _test-output-buffered-file)
4746 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4752     # check output
4753     (check-next-stream-line-equal _test-output-stream "01/add-to *(ebp+0x00000008) 0x00000001/r32" "F - test-add-reg-to-mem")
4754     # . epilogue
4755     89/<- %esp 5/r32/ebp
4756     5d/pop-to-ebp
4757     c3/return
4758 
4759 test-add-mem-to-reg:
4760     #   var1/reg <- add var2
4761     # =>
4762     #   03/add *(ebp+__) var1
4763     #
4764     # . prologue
4765     55/push-ebp
4766     89/<- %ebp 4/r32/esp
4767     # setup
4768     (clear-stream _test-output-stream)
4769     (clear-stream $_test-output-buffered-file->buffer)
4770     # var var-var1/ecx : (ref var) in eax
4771     68/push "eax"/imm32/register
4772     68/push 0/imm32/no-stack-offset
4773     68/push 1/imm32/block-depth
4774     68/push 1/imm32/type-int
4775     68/push "var1"/imm32
4776     89/<- %ecx 4/r32/esp
4777     # var var-var2/edx : (ref var)
4778     68/push 0/imm32/no-register
4779     68/push 8/imm32/stack-offset
4780     68/push 1/imm32/block-depth
4781     68/push 1/imm32/type-int
4782     68/push "var2"/imm32
4783     89/<- %edx 4/r32/esp
4784     # var inouts/esi : (ref list var2)
4785     68/push 0/imm32/next
4786     52/push-edx/var-var2
4787     89/<- %esi 4/r32/esp
4788     # var outputs/edi : (ref list var1)
4789     68/push 0/imm32/next
4790     51/push-ecx/var-var1
4791     89/<- %edi 4/r32/esp
4792     # var stmt/esi : (ref statement)
4793     68/push 0/imm32/next
4794     57/push-edi/outputs
4795     56/push-esi/inouts
4796     68/push "add"/imm32/operation
4797     68/push 1/imm32
4798     89/<- %esi 4/r32/esp
4799     # convert
4800     (emit-subx-statement _test-output-buffered-file %esi Primitives 0)
4801     (flush _test-output-buffered-file)
4802 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4808     # check output
4809     (check-next-stream-line-equal _test-output-stream "03/add *(ebp+0x00000008) 0x00000000/r32" "F - test-add-mem-to-reg")
4810     # . epilogue
4811     89/<- %esp 5/r32/ebp
4812     5d/pop-to-ebp
4813     c3/return
4814 
4815 test-add-literal-to-eax:
4816     #   var1/eax <- add 0x34
4817     # =>
4818     #   05/add-to-eax 0x34/imm32
4819     #
4820     # . prologue
4821     55/push-ebp
4822     89/<- %ebp 4/r32/esp
4823     # setup
4824     (clear-stream _test-output-stream)
4825     (clear-stream $_test-output-buffered-file->buffer)
4826     # var var-var1/ecx : (ref var) in eax
4827     68/push "eax"/imm32/register
4828     68/push 0/imm32/no-stack-offset
4829     68/push 1/imm32/block-depth
4830     68/push 1/imm32/type-int
4831     68/push "var1"/imm32
4832     89/<- %ecx 4/r32/esp
4833     # var var-var2/edx : (ref var) literal
4834     68/push 0/imm32/no-register
4835     68/push 0/imm32/no-stack-offset
4836     68/push 1/imm32/block-depth
4837     68/push 0/imm32/type-literal
4838     68/push "0x34"/imm32
4839     89/<- %edx 4/r32/esp
4840     # var inouts/esi : (ref list var2)
4841     68/push 0/imm32/next
4842     52/push-edx/var-var2
4843     89/<- %esi 4/r32/esp
4844     # var outputs/edi : (ref list var1)
4845     68/push 0/imm32/next
4846     51/push-ecx/var-var1
4847     89/<- %edi 4/r32/esp
4848     # var stmt/esi : (ref statement)
4849     68/push 0/imm32/next
4850     57/push-edi/outputs
4851     56/push-esi/inouts
4852     68/push "add"/imm32/operation
4853     68/push 1/imm32
4854     89/<- %esi 4/r32/esp
4855     # convert
4856     (emit-subx-statement _test-output-buffered-file %esi Primitives 0)
4857     (flush _test-output-buffered-file)
4858 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4864     # check output
4865     (check-next-stream-line-equal _test-output-stream "05/add-to-eax 0x34/imm32" "F - test-add-literal-to-eax")
4866     # . epilogue
4867     89/<- %esp 5/r32/ebp
4868     5d/pop-to-ebp
4869     c3/return
4870 
4871 test-add-literal-to-reg:
4872     #   var1/ecx <- add 0x34
4873     # =>
4874     #   81 0/subop/add %ecx 0x34/imm32
4875     #
4876     # . prologue
4877     55/push-ebp
4878     89/<- %ebp 4/r32/esp
4879     # setup
4880     (clear-stream _test-output-stream)
4881     (clear-stream $_test-output-buffered-file->buffer)
4882     # var var-var1/ecx : (ref var) in ecx
4883     68/push "ecx"/imm32/register
4884     68/push 0/imm32/no-stack-offset
4885     68/push 1/imm32/block-depth
4886     68/push 1/imm32/type-int
4887     68/push "var1"/imm32
4888     89/<- %ecx 4/r32/esp
4889     # var var-var2/edx : (ref var) literal
4890     68/push 0/imm32/no-register
4891     68/push 0/imm32/no-stack-offset
4892     68/push 1/imm32/block-depth
4893     68/push 0/imm32/type-literal
4894     68/push "0x34"/imm32
4895     89/<- %edx 4/r32/esp
4896     # var inouts/esi : (ref list var2)
4897     68/push 0/imm32/next
4898     52/push-edx/var-var2
4899     89/<- %esi 4/r32/esp
4900     # var outputs/edi : (ref list var1)
4901     68/push 0/imm32/next
4902     51/push-ecx/var-var1
4903     89/<- %edi 4/r32/esp
4904     # var stmt/esi : (ref statement)
4905     68/push 0/imm32/next
4906     57/push-edi/outputs
4907     56/push-esi/inouts
4908     68/push "add"/imm32/operation
4909     68/push 1/imm32
4910     89/<- %esi 4/r32/esp
4911     # convert
4912     (emit-subx-statement _test-output-buffered-file %esi Primitives 0)
4913     (flush _test-output-buffered-file)
4914 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4920     # check output
4921     (check-next-stream-line-equal _test-output-stream "81 0/subop/add %ecx 0x34/imm32" "F - test-add-literal-to-reg")
4922     # . epilogue
4923     89/<- %esp 5/r32/ebp
4924     5d/pop-to-ebp
4925     c3/return
4926 
4927 test-add-literal-to-mem:
4928     #   add-to var1, 0x34
4929     # =>
4930     #   81 0/subop/add %eax 0x34/imm32
4931     #
4932     # . prologue
4933     55/push-ebp
4934     89/<- %ebp 4/r32/esp
4935     # setup
4936     (clear-stream _test-output-stream)
4937     (clear-stream $_test-output-buffered-file->buffer)
4938     # var var-var1/ecx : (ref var)
4939     68/push 0/imm32/no-register
4940     68/push 8/imm32/stack-offset
4941     68/push 1/imm32/block-depth
4942     68/push 1/imm32/type-int
4943     68/push "var1"/imm32
4944     89/<- %ecx 4/r32/esp
4945     # var var-var2/edx : (ref var) literal
4946     68/push 0/imm32/no-register
4947     68/push 0/imm32/no-stack-offset
4948     68/push 1/imm32/block-depth
4949     68/push 0/imm32/type-literal
4950     68/push "0x34"/imm32
4951     89/<- %edx 4/r32/esp
4952     # var inouts/esi : (ref list var2)
4953     68/push 0/imm32/next
4954     52/push-edx/var-var2
4955     89/<- %esi 4/r32/esp
4956     # var inouts = (ref list var1 inouts)
4957     56/push-esi/next
4958     51/push-ecx/var-var1
4959     89/<- %esi 4/r32/esp
4960     # var stmt/esi : (ref statement)
4961     68/push 0/imm32/next
4962     68/push 0/imm32/outputs
4963     56/push-esi/inouts
4964     68/push "add-to"/imm32/operation
4965     68/push 1/imm32
4966     89/<- %esi 4/r32/esp
4967     # convert
4968     (emit-subx-statement _test-output-buffered-file %esi Primitives 0)
4969     (flush _test-output-buffered-file)
4970 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
4976     # check output
4977     (check-next-stream-line-equal _test-output-stream "81 0/subop/add *(ebp+0x00000008) 0x34/imm32" "F - test-add-literal-to-mem")
4978     # . epilogue
4979     89/<- %esp 5/r32/ebp
4980     5d/pop-to-ebp
4981     c3/return
4982 
4983 test-emit-subx-statement-function-call:
4984     # Call a function on a variable on the stack.
4985     #   f foo
4986     # =>
4987     #   (f2 *(ebp-8))
4988     # (Changing the function name supports overloading in general, but here it
4989     # just serves to help disambiguate things.)
4990     #
4991     # There's a variable on the var stack as follows:
4992     #   name: 'foo'
4993     #   type: int
4994     #   stack-offset: -8
4995     #
4996     # There's nothing in primitives.
4997     #
4998     # There's a function with this info:
4999     #   name: 'f'
5000     #   inout: int/mem
5001     #   value: 'f2'
5002     #
5003     # . prologue
5004     55/push-ebp
5005     89/<- %ebp 4/r32/esp
5006     # setup
5007     (clear-stream _test-output-stream)
5008     (clear-stream $_test-output-buffered-file->buffer)
5009     # var var-foo/ecx : (ref var)
5010     68/push 0/imm32/no-register
5011     68/push -8/imm32/stack-offset
5012     68/push 0/imm32/block-depth
5013     68/push 1/imm32/type-int
5014     68/push "foo"/imm32
5015     89/<- %ecx 4/r32/esp
5016     # var operands/esi : (ref list var)
5017     68/push 0/imm32/next
5018     51/push-ecx/var-foo
5019     89/<- %esi 4/r32/esp
5020     # var stmt/esi : (ref statement)
5021     68/push 0/imm32/next
5022     68/push 0/imm32/outputs
5023     56/push-esi/inouts
5024     68/push "f"/imm32/operation
5025     68/push 1/imm32
5026     89/<- %esi 4/r32/esp
5027     # var functions/ebx : (ref function)
5028     68/push 0/imm32/next
5029     68/push 0/imm32/body
5030     68/push 0/imm32/outputs
5031     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
5032     68/push "f2"/imm32/subx-name
5033     68/push "f"/imm32/name
5034     89/<- %ebx 4/r32/esp
5035     # convert
5036     (emit-subx-statement _test-output-buffered-file %esi 0 %ebx)
5037     (flush _test-output-buffered-file)
5038 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
5044     # check output
5045     (check-next-stream-line-equal _test-output-stream "(f2 *(ebp+0xfffffff8))" "F - test-emit-subx-statement-function-call")
5046     # . epilogue
5047     89/<- %esp 5/r32/ebp
5048     5d/pop-to-ebp
5049     c3/return
5050 
5051 test-emit-subx-statement-function-call-with-literal-arg:
5052     # Call a function on a literal.
5053     #   f 34
5054     # =>
5055     #   (f2 34)
5056     #
5057     # . prologue
5058     55/push-ebp
5059     89/<- %ebp 4/r32/esp
5060     # setup
5061     (clear-stream _test-output-stream)
5062     (clear-stream $_test-output-buffered-file->buffer)
5063     # var var-foo/ecx : (ref var) literal
5064     68/push 0/imm32/no-register
5065     68/push 0/imm32/no-stack-offset
5066     68/push 0/imm32/block-depth
5067     68/push 0/imm32/type-literal
5068     68/push "34"/imm32
5069     89/<- %ecx 4/r32/esp
5070     # var operands/esi : (ref list var)
5071     68/push 0/imm32/next
5072     51/push-ecx/var-foo
5073     89/<- %esi 4/r32/esp
5074     # var stmt/esi : (ref statement)
5075     68/push 0/imm32/next
5076     68/push 0/imm32/outputs
5077     56/push-esi/inouts
5078     68/push "f"/imm32/operation
5079     68/push 1/imm32
5080     89/<- %esi 4/r32/esp
5081     # var functions/ebx : (ref function)
5082     68/push 0/imm32/next
5083     68/push 0/imm32/body
5084     68/push 0/imm32/outputs
5085     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
5086     68/push "f2"/imm32/subx-name
5087     68/push "f"/imm32/name
5088     89/<- %ebx 4/r32/esp
5089     # convert
5090     (emit-subx-statement _test-output-buffered-file %esi 0 %ebx)
5091     (flush _test-output-buffered-file)
5092 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
5098     # check output
5099     (check-next-stream-line-equal _test-output-stream "(f2 34)" "F - test-emit-subx-statement-function-call-with-literal-arg")
5100     # . epilogue
5101     89/<- %esp 5/r32/ebp
5102     5d/pop-to-ebp
5103     c3/return
5104 
5105 emit-subx-prologue:  # out : (address buffered-file)
5106     # . prologue
5107     55/push-ebp
5108     89/<- %ebp 4/r32/esp
5109     #
5110     (write-buffered *(ebp+8) "# . prologue\n")
5111     (write-buffered *(ebp+8) "55/push-ebp\n")
5112     (write-buffered *(ebp+8) "89/<- %ebp 4/r32/esp\n")
5113 $emit-subx-prologue:end:
5114     # . epilogue
5115     89/<- %esp 5/r32/ebp
5116     5d/pop-to-ebp
5117     c3/return
5118 
5119 emit-subx-epilogue:  # out : (address buffered-file)
5120     # . prologue
5121     55/push-ebp
5122     89/<- %ebp 4/r32/esp
5123     #
5124     (write-buffered *(ebp+8) "# . epilogue\n")
5125     (write-buffered *(ebp+8) "89/<- %esp 5/r32/ebp\n")
5126     (write-buffered *(ebp+8) "5d/pop-to-ebp\n")
5127     (write-buffered *(ebp+8) "c3/return\n")
5128 $emit-subx-epilogue:end:
5129     # . epilogue
5130     89/<- %esp 5/r32/ebp
5131     5d/pop-to-ebp
5132     c3/return