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