https://github.com/akkartik/mu/blob/master/apps/mu.subx
   1 # The Mu computer's level-2 language, also called Mu.
   2 # http://akkartik.name/post/mu-2019-2
   3 #
   4 # To run:
   5 #   $ ./ntranslate init.linux 0*.subx apps/mu.subx
   6 
   7 # == Goals
   8 # 1. Be memory safe. It should be impossible to corrupt the heap, or to create
   9 # a bad pointer. (Requires strong type safety.)
  10 # 2. Do as little as possible to achieve goal 1.
  11 #   - runtime checks to avoid complex static analysis
  12 #   - minimize impedance mismatch between source language and SubX target
  13 
  14 # == Language description
  15 # A program is a sequence of function definitions.
  16 #
  17 # Function example:
  18 #   fn foo n: int -> result/eax: int {
  19 #     ...
  20 #   }
  21 #
  22 # Functions consist of a name, optional inputs, optional outputs and a block.
  23 #
  24 # Function inputs and outputs are variables. All variables have a type and
  25 # storage specifier. They can be placed either in memory (on the stack) or in
  26 # one of 6 named registers.
  27 #   eax ecx edx ebx esi edi
  28 # Variables in registers must be primitive 32-bit types.
  29 # Variables not explicitly placed in a register are on the stack.
  30 # Variables in registers need not have a name; in that case you refer to them
  31 # directly by the register name.
  32 #
  33 # Function inputs are always passed in memory (on the stack), while outputs
  34 # are always returned in registers.
  35 #
  36 # Blocks mostly consist of statements.
  37 #
  38 # Statements mostly consist of a name, optional inputs and optional outputs.
  39 #
  40 # Statement inputs are variables or literals. Variables need to specify type
  41 # (and storage) the first time they're mentioned but not later.
  42 #
  43 # Statement outputs, like function outputs, must be variables in registers.
  44 #
  45 # Statement names must be either primitives or user-defined functions.
  46 #
  47 # Primitives can write to any register.
  48 # User-defined functions only write to hard-coded registers. Outputs of each
  49 # call must have the same registers as in the function definition.
  50 #
  51 # There are some other statement types:
  52 #   - blocks. Multiple statements surrounded by '{...}' and optionally
  53 #     prefixed with a label name and ':'
  54 #       - {
  55 #           ...
  56 #         }
  57 #       - foo: {
  58 #           ...
  59 #         }
  60 #
  61 #   - variable definitions on the stack. E.g.:
  62 #       - var foo: int
  63 #       - var bar: (array int 3)
  64 #     There's no initializer; variables are automatically initialized.
  65 #
  66 #   - variables definitions in a register. E.g.:
  67 #       - var foo/eax : int <- add bar 1
  68 #     The initializer is mandatory and must be a valid instruction that writes
  69 #     a single output to the right register. In practice registers will
  70 #     usually be either initialized by primitives or copied from eax.
  71 #       - var eax : int <- foo bar quux
  72 #         var floo/ecx : int <- copy eax
  73 #
  74 # Still todo:
  75 #   global variables
  76 #   heap allocations (planned name: 'handle')
  77 #   user-defined types: 'type' for structs, 'choice' for unions
  78 #   short-lived 'address' type for efficiently writing inside nested structs
  79 #
  80 # Formal types:
  81 #   A program is a linked list of functions
  82 #   A function contains:
  83 #     name: string
  84 #     inouts: linked list of vars  <-- 'inouts' is more precise than 'inputs'
  85 #       data: (address var)
  86 #       next: (address list)
  87 #     outputs: linked list of vars
  88 #       data: (address var)
  89 #       next: (address list)
  90 #     body: block
  91 #   A var-type contains:
  92 #     name: string
  93 #     type: s-expression of type ids
  94 #   Statements are not yet fully designed.
  95 #   statement = var definition or simple statement or block
  96 #   simple statement:
  97 #     operation: string
  98 #     inouts: linked list of vars
  99 #     outputs: linked list of vars
 100 #   block = linked list of statements
 101 
 102 # == Translation: managing the stack
 103 # Now that we know what the language looks like in the large, let's think
 104 # about how translation happens from the bottom up. One crucial piece of the
 105 # puzzle is how Mu will clean up variables defined on the stack for you.
 106 #
 107 # Assume that we maintain a 'functions' list while parsing source code. And a
 108 # 'primitives' list is a global constant. Both these contain enough information
 109 # to perform type-checking on function calls or primitive statements, respectively.
 110 #
 111 # Defining variables pushes them on a stack with the current block depth and
 112 # enough information about their location (stack offset or register).
 113 # Starting a block increments the current block id.
 114 # Each statement now has enough information to emit code for it.
 115 # Ending a block is where the magic happens:
 116 #   pop all variables at the current block depth
 117 #   emit code to restore all register variables introduced at the current depth
 118 #   emit code to clean up all stack variables at the current depth (just increment esp)
 119 #   decrement the current block depth
 120 #
 121 # Formal types:
 122 #   live-vars: stack of vars
 123 #   var:
 124 #     name: string
 125 #     type: s-expression? Just a type id for now.
 126 #     block: int
 127 #     stack-offset: int  (added to ebp)
 128 #     register: string
 129 #       either usual register names
 130 #       or '*' to indicate any register
 131 #   At most one of stack-offset or register-index must be non-zero.
 132 #   A register of '*' designates a variable _template_. Only legal in formal
 133 #   parameters for primitives.
 134 
 135 # == Translating a single function call
 136 # This one's easy. Assuming we've already checked things, we just drop the
 137 # outputs (which use hard-coded registers) and emit inputs in a standard format.
 138 #
 139 # out1, out2, out3, ... <- name inout1, inout2, inout3, ...
 140 # =>
 141 # (subx-name inout1 inout2 inout3)
 142 #
 143 # Formal types:
 144 #   functions: linked list of info
 145 #     name: string
 146 #     inouts: linked list of vars
 147 #     outputs: linked list of vars
 148 #     body: block (singleton linked list)
 149 #     subx-name: string
 150 
 151 # == Translating a single primitive instruction
 152 # A second crucial piece of the puzzle is how Mu converts fairly regular
 153 # primitives with their uniform syntax to SubX instructions with their gnarly
 154 # x86 details.
 155 #
 156 # Mu instructions have inputs and outputs. Primitives can have up to 2 of
 157 # them.
 158 # SubX instructions have rm32 and r32 operands.
 159 # The translation between them covers almost all the possibilities.
 160 #   Instructions with 1 inout may turn into ones with 1 rm32
 161 #     (e.g. incrementing a var on the stack)
 162 #   Instructions with 1 output may turn into ones with 1 rm32
 163 #     (e.g. incrementing a var in a register)
 164 #   1 inout and 1 output may turn into 1 rm32 and 1 r32
 165 #     (e.g. adding a var to a reg)
 166 #   2 inouts may turn into 1 rm32 and 1 r32
 167 #     (e.g. adding a reg to a var)
 168 #   1 inout and 1 literal may turn into 1 rm32 and 1 imm32
 169 #     (e.g. adding a constant to a var)
 170 #   1 output and 1 literal may turn into 1 rm32 and 1 imm32
 171 #     (e.g. adding a constant to a reg)
 172 #   2 outputs to hardcoded registers and 1 inout may turn into 1 rm32
 173 #     (special-case: divide edx:eax by a var or reg)
 174 # Observations:
 175 #   We always emit rm32. It may be the first inout or the first output.
 176 #   We may emit r32 or imm32 or neither.
 177 #   When we emit r32 it may come from first inout or second inout or first output.
 178 #
 179 # Accordingly, the formal data structure for a primitive looks like this:
 180 #   primitives: linked list of info
 181 #     name: string
 182 #     mu-inouts: linked list of vars to check
 183 #     mu-outputs: linked list of vars to check
 184 #     subx-name: string
 185 #     subx-rm32: enum arg-location
 186 #     subx-r32: enum arg-location
 187 #     subx-imm32: enum arg-location
 188 #   arg-location: enum
 189 #     0 means none
 190 #     1 means first inout
 191 #     2 means second inout
 192 #     3 means first output
 193 
 194 # == Translating a block
 195 # Emit block name if necessary
 196 # Emit '{'
 197 # When you encounter a statement, emit it as above
 198 # When you encounter a variable declaration
 199 #   emit any code needed for it (bzeros)
 200 #   push it on the var stack
 201 #   update register dict if necessary
 202 # When you encounter '}'
 203 #   While popping variables off the var stack until block id changes
 204 #     Emit code needed to clean up the stack
 205 #       either increment esp
 206 #       or pop into appropriate register
 207 
 208 # The rest is straightforward.
 209 
 210 == data
 211 
 212 Program:  # (address function)
 213   0/imm32
 214 
 215 Function-name:
 216   0/imm32
 217 Function-subx-name:
 218   4/imm32
 219 Function-inouts:  # (address list var)
 220   8/imm32
 221 Function-outputs:  # (address list var)
 222   0xc/imm32
 223 Function-body:  # (address block)
 224   0x10/imm32
 225 Function-next:  # (address function)
 226   0x14/imm32
 227 Function-size:
 228   0x18/imm32/24
 229 
 230 Primitive-name:
 231   0/imm32
 232 Primitive-inouts:  # (address list var)
 233   4/imm32
 234 Primitive-outputs:  # (address list var)
 235   8/imm32
 236 Primitive-subx-name:  # (address string)
 237   0xc/imm32
 238 Primitive-subx-rm32:  # enum arg-location
 239   0x10/imm32
 240 Primitive-subx-r32:  # enum arg-location
 241   0x14/imm32
 242 Primitive-subx-imm32:  # enum arg-location
 243   0x18/imm32
 244 Primitive-next:  # (address function)
 245   0x1c/imm32
 246 Primitive-size:
 247   0x20/imm32/24
 248 
 249 Stmt-operation:
 250   0/imm32
 251 Stmt-inouts:
 252   4/imm32
 253 Stmt-outputs:
 254   8/imm32
 255 Stmt-next:
 256   0xc/imm32
 257 Stmt-size:
 258   0x10/imm32
 259 
 260 Var-name:
 261   0/imm32
 262 Var-type:
 263   4/imm32
 264 Var-block:
 265   8/imm32
 266 Var-stack-offset:
 267   0xc/imm32
 268 Var-register:
 269   0x10/imm32
 270 Var-size:
 271   0x14/imm32
 272 
 273 Any-register:  # "*"
 274   # size
 275   1/imm32
 276   # data
 277   2a/asterisk
 278 
 279 == code
 280 
 281 Entry:
 282     # . prologue
 283     89/<- %ebp 4/r32/esp
 284     (new-segment Heap-size Heap)
 285     # if (argv[1] == "test') run-tests()
 286     {
 287       # if (argc <= 1) break
 288       81 7/subop/compare *ebp 1/imm32
 289       7e/jump-if-lesser-or-equal break/disp8
 290       # if (argv[1] != "test") break
 291       (kernel-string-equal? *(ebp+8) "test")  # => eax
 292       3d/compare-eax-and 0/imm32
 293       74/jump-if-equal break/disp8
 294       #
 295       (run-tests)
 296       # syscall(exit, *Num-test-failures)
 297       8b/-> *Num-test-failures 3/r32/ebx
 298       eb/jump $mu-main:end/disp8
 299     }
 300     # otherwise convert Stdin
 301     (convert-mu Stdin Stdout)
 302     (flush Stdout)
 303     # syscall(exit, 0)
 304     bb/copy-to-ebx 0/imm32
 305 $mu-main:end:
 306     b8/copy-to-eax 1/imm32/exit
 307     cd/syscall 0x80/imm8
 308 
 309 convert-mu:  # in : (address buffered-file), out : (address buffered-file)
 310     # . prologue
 311     55/push-ebp
 312     89/<- %ebp 4/r32/esp
 313     #
 314     (parse-mu *(ebp+8))
 315     (check-mu-types)
 316     (emit-subx *(ebp+0xc))
 317 $convert-mu:end:
 318     # . epilogue
 319     89/<- %esp 5/r32/ebp
 320     5d/pop-to-ebp
 321     c3/return
 322 
 323 test-convert-empty-input:
 324     # empty input => empty output
 325     # . prologue
 326     55/push-ebp
 327     89/<- %ebp 4/r32/esp
 328     # setup
 329     (clear-stream _test-input-stream)
 330     (clear-stream _test-input-buffered-file->buffer)
 331     (clear-stream _test-output-stream)
 332     (clear-stream _test-output-buffered-file->buffer)
 333     #
 334     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 335     (flush _test-output-buffered-file)
 336     (check-stream-equal _test-output-stream "" "F - test-convert-empty-input")
 337     # . epilogue
 338     89/<- %esp 5/r32/ebp
 339     5d/pop-to-ebp
 340     c3/return
 341 
 342 test-convert-function-skeleton:
 343     # empty function decl => function prologue and epilogue
 344     #   fn foo {
 345     #   }
 346     # =>
 347     #   foo:
 348     #     # . prologue
 349     #     55/push-ebp
 350     #     89/<- %ebp 4/r32/esp
 351     #     # . epilogue
 352     #     89/<- %esp 5/r32/ebp
 353     #     5d/pop-to-ebp
 354     #     c3/return
 355     # . prologue
 356     55/push-ebp
 357     89/<- %ebp 4/r32/esp
 358     # setup
 359     (clear-stream _test-input-stream)
 360     (clear-stream _test-input-buffered-file->buffer)
 361     (clear-stream _test-output-stream)
 362     (clear-stream _test-output-buffered-file->buffer)
 363     #
 364     (write _test-input-stream "fn foo {\n")
 365     (write _test-input-stream "}\n")
 366     # convert
 367     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 368     (flush _test-output-buffered-file)
 369 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 375     # check output
 376     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-skeleton/0")
 377     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-skeleton/1")
 378     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-skeleton/2")
 379     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-skeleton/3")
 380     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-skeleton/4")
 381     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-skeleton/5")
 382     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-skeleton/6")
 383     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-skeleton/7")
 384     # . epilogue
 385     89/<- %esp 5/r32/ebp
 386     5d/pop-to-ebp
 387     c3/return
 388 
 389 test-convert-multiple-function-skeletons:
 390     # multiple functions correctly organized into a linked list
 391     #   fn foo {
 392     #   }
 393     #   fn bar {
 394     #   }
 395     # =>
 396     #   foo:
 397     #     # . prologue
 398     #     55/push-ebp
 399     #     89/<- %ebp 4/r32/esp
 400     #     # . epilogue
 401     #     89/<- %esp 5/r32/ebp
 402     #     5d/pop-to-ebp
 403     #     c3/return
 404     #   bar:
 405     #     # . prologue
 406     #     55/push-ebp
 407     #     89/<- %ebp 4/r32/esp
 408     #     # . epilogue
 409     #     89/<- %esp 5/r32/ebp
 410     #     5d/pop-to-ebp
 411     #     c3/return
 412     # . prologue
 413     55/push-ebp
 414     89/<- %ebp 4/r32/esp
 415     # setup
 416     (clear-stream _test-input-stream)
 417     (clear-stream _test-input-buffered-file->buffer)
 418     (clear-stream _test-output-stream)
 419     (clear-stream _test-output-buffered-file->buffer)
 420     #
 421     (write _test-input-stream "fn foo {\n")
 422     (write _test-input-stream "}\n")
 423     (write _test-input-stream "fn bar {\n")
 424     (write _test-input-stream "}\n")
 425     # convert
 426     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 427     (flush _test-output-buffered-file)
 428 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 434     # check first function
 435     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-multiple-function-skeletons/0")
 436     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/1")
 437     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/2")
 438     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/3")
 439     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/4")
 440     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/5")
 441     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/6")
 442     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/7")
 443     # check second function
 444     (check-next-stream-line-equal _test-output-stream "bar:"                  "F - test-convert-multiple-function-skeletons/10")
 445     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/11")
 446     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/12")
 447     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/13")
 448     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/14")
 449     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/15")
 450     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/16")
 451     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/17")
 452     # . epilogue
 453     89/<- %esp 5/r32/ebp
 454     5d/pop-to-ebp
 455     c3/return
 456 
 457 test-convert-function-with-arg:
 458     # function with one arg and a copy instruction
 459     #   fn foo n : int -> result/eax : int {
 460     #     result <- copy n
 461     #   }
 462     # =>
 463     #   foo:
 464     #     # . prologue
 465     #     55/push-ebp
 466     #     89/<- %ebp 4/r32/esp
 467     #     {
 468     #     # result <- copy n
 469     #     8b/-> *(ebp+8) 0/r32/eax
 470     #     }
 471     #     # . epilogue
 472     #     89/<- %esp 5/r32/ebp
 473     #     5d/pop-to-ebp
 474     #     c3/return
 475     # . prologue
 476     55/push-ebp
 477     89/<- %ebp 4/r32/esp
 478     # setup
 479     (clear-stream _test-input-stream)
 480     (clear-stream _test-input-buffered-file->buffer)
 481     (clear-stream _test-output-stream)
 482     (clear-stream _test-output-buffered-file->buffer)
 483     #
 484     (write _test-input-stream "fn foo {\n")
 485     (write _test-input-stream "}\n")
 486     # convert
 487     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 488     (flush _test-output-buffered-file)
 489 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 495     # check output
 496     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-skeleton/0")
 497     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-skeleton/1")
 498     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-skeleton/2")
 499     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-skeleton/3")
 500     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-skeleton/4")
 501     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-skeleton/5")
 502     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-skeleton/6")
 503     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-skeleton/7")
 504     # . epilogue
 505     89/<- %esp 5/r32/ebp
 506     5d/pop-to-ebp
 507     c3/return
 508 
 509 parse-mu:  # in : (address buffered-file)
 510     # pseudocode
 511     #   var curr-function = Program
 512     #   var line : (stream byte 512)
 513     #   var word-slice : slice
 514     #   while true                                  # line loop
 515     #     clear-stream(line)
 516     #     read-line-buffered(in, line)
 517     #     if (line->write == 0) break               # end of file
 518     #     while true                                # word loop
 519     #       word-slice = next-word-or-string(line)
 520     #       if slice-empty?(word-slice)             # end of line
 521     #         break
 522     #       else if slice-starts-with?(word-slice, "#")  # comment
 523     #         break                                 # end of line
 524     #       else if slice-equal(word-slice, "fn")
 525     #         var new-function : (address function) = new function
 526     #         populate-mu-function(in, new-function)
 527     #         *curr-function = new-function
 528     #         curr-function = &new-function->next
 529     #       else
 530     #         abort()
 531     #
 532     # . prologue
 533     55/push-ebp
 534     89/<- %ebp 4/r32/esp
 535     # . save registers
 536     50/push-eax
 537     51/push-ecx
 538     52/push-edx
 539     57/push-edi
 540     # var line/ecx : (stream byte 512)
 541     81 5/subop/subtract %esp 0x200/imm32
 542     68/push 0x200/imm32/length
 543     68/push 0/imm32/read
 544     68/push 0/imm32/write
 545     89/<- %ecx 4/r32/esp
 546     # var word-slice/edx : slice
 547     68/push 0/imm32/end
 548     68/push 0/imm32/start
 549     89/<- %edx 4/r32/esp
 550     # var curr-function/edi : (address function) = Program
 551     bf/copy-to-edi Program/imm32
 552     {
 553 $parse-mu:line-loop:
 554       (clear-stream %ecx)
 555       (read-line-buffered *(ebp+8) %ecx)
 556       # if (line->write == 0) break
 557       81 7/subop/compare *ecx 0/imm32
 558       0f 84/jump-if-equal break/disp32
 559 +--  6 lines: #?       # dump line ---------------------------------------------------------------------------------------------------------------------------
 565       { # word loop
 566 $parse-mu:word-loop:
 567         (next-word-or-string %ecx %edx)
 568         # if slice-empty?(word-slice) break
 569         (slice-empty? %edx)
 570         3d/compare-eax-and 0/imm32
 571         0f 85/jump-if-not-equal break/disp32
 572         # if (*word-slice->start == "#") break
 573         # . eax = *word-slice->start
 574         8b/-> *edx 0/r32/eax
 575         8a/copy-byte *eax 0/r32/AL
 576         81 4/subop/and %eax 0xff/imm32
 577         # . if (eax == '#') break
 578         3d/compare-eax-and 0x23/imm32/hash
 579         0f 84/jump-if-equal break/disp32
 580         # if (slice-equal?(word-slice, "fn")) parse a function
 581         {
 582           (slice-equal? %edx "fn")
 583           3d/compare-eax-and 0/imm32
 584           0f 84/jump-if-equal break/disp32
 585           # var new-function/eax : (address function) = populate-mu-function()
 586           (allocate Heap *Function-size)  # => eax
 587           (populate-mu-function-header %ecx %eax)
 588           (populate-mu-function-body *(ebp+8) %eax)
 589           # *curr-function = new-function
 590           89/<- *edi 0/r32/eax
 591           # curr-function = &new-function->next
 592           8d/address-> *(eax+0x10) 7/r32/edi
 593           e9/jump $parse-mu:word-loop/disp32
 594         }
 595         # otherwise abort
 596         e9/jump $parse-mu:abort/disp32
 597       } # end word loop
 598       e9/jump loop/disp32
 599     } # end line loop
 600 $parse-mu:end:
 601     # . reclaim locals
 602     81 0/subop/add %esp 0x214/imm32
 603     # . restore registers
 604     5f/pop-to-edi
 605     5a/pop-to-edx
 606     59/pop-to-ecx
 607     58/pop-to-eax
 608     # . epilogue
 609     89/<- %esp 5/r32/ebp
 610     5d/pop-to-ebp
 611     c3/return
 612 
 613 $parse-mu:abort:
 614     # error("unexpected top-level command: " word-slice "\n")
 615     (write-buffered Stderr "unexpected top-level command: ")
 616     (write-buffered Stderr %edx)
 617     (write-buffered Stderr "\n")
 618     (flush Stderr)
 619     # . syscall(exit, 1)
 620     bb/copy-to-ebx  1/imm32
 621     b8/copy-to-eax  1/imm32/exit
 622     cd/syscall  0x80/imm8
 623     # never gets here
 624 
 625 # errors considered:
 626 #   fn foo { {
 627 #   fn foo { }
 628 #   fn foo { } {
 629 #   fn foo  # no block
 630 populate-mu-function-header:  # first-line : (address stream byte), out : (address function)
 631     # . prologue
 632     55/push-ebp
 633     89/<- %ebp 4/r32/esp
 634     # . save registers
 635     50/push-eax
 636     51/push-ecx
 637     57/push-edi
 638     # edi = out
 639     8b/-> *(ebp+0xc) 7/r32/edi
 640     # var word-slice/ecx : slice
 641     68/push 0/imm32/end
 642     68/push 0/imm32/start
 643     89/<- %ecx 4/r32/esp
 644     # save function name
 645     (next-word *(ebp+8) %ecx)
 646     (slice-to-string Heap %ecx)  # => eax
 647     89/<- *edi 0/r32/eax
 648     # assert that next token is '{'
 649     (next-word *(ebp+8) %ecx)
 650     (slice-equal? %ecx "{")
 651     3d/compare-eax-and 0/imm32
 652     74/jump-if-equal $populate-mu-function-header:abort/disp8
 653     # assert that there's no further token
 654     {
 655       # word-slice = next-word(line)
 656       (next-word *(ebp+8) %ecx)
 657       # if (word-slice == '') break
 658       (slice-empty? %ecx)
 659       3d/compare-eax-and 0/imm32
 660       75/jump-if-not-equal break/disp8
 661       # if (slice-starts-with?(word-slice, "#")) break
 662       # . eax = *word-slice->start
 663       8b/-> *edx 0/r32/eax
 664       8a/copy-byte *eax 0/r32/AL
 665       81 4/subop/and %eax 0xff/imm32
 666       # . if (eax == '#') break
 667       3d/compare-eax-and 0x23/imm32/hash
 668       74/jump-if-equal break/disp8
 669       # otherwise abort
 670       eb/jump $populate-mu-function-header:abort/disp8
 671     }
 672 $populate-mu-function-header:end:
 673     # . reclaim locals
 674     81 0/subop/add %esp 8/imm32
 675     # . restore registers
 676     5f/pop-to-edi
 677     59/pop-to-ecx
 678     58/pop-to-eax
 679     # . epilogue
 680     89/<- %esp 5/r32/ebp
 681     5d/pop-to-ebp
 682     c3/return
 683 
 684 $populate-mu-function-header:abort:
 685     # error("function header not in form 'fn <name> {'")
 686     (write-buffered Stderr "function header not in form 'fn <name> {' -- '")
 687     (rewind-stream *(ebp+8))
 688     (write-stream 2 *(ebp+8))
 689     (write-buffered Stderr "'\n")
 690     (flush Stderr)
 691     # . syscall(exit, 1)
 692     bb/copy-to-ebx  1/imm32
 693     b8/copy-to-eax  1/imm32/exit
 694     cd/syscall  0x80/imm8
 695     # never gets here
 696 
 697 # errors considered:
 698 #   { abc
 699 populate-mu-function-body:  # in : (address buffered-file), out : (address function)
 700     # . prologue
 701     55/push-ebp
 702     89/<- %ebp 4/r32/esp
 703     # . save registers
 704     50/push-eax
 705     51/push-ecx
 706     52/push-edx
 707     53/push-ebx
 708     # var line/ecx : (stream byte 512)
 709     81 5/subop/subtract %esp 0x200/imm32
 710     68/push 0x200/imm32/length
 711     68/push 0/imm32/read
 712     68/push 0/imm32/write
 713     89/<- %ecx 4/r32/esp
 714     # var word-slice/edx : slice
 715     68/push 0/imm32/end
 716     68/push 0/imm32/start
 717     89/<- %edx 4/r32/esp
 718     # var open-curly-count/ebx : int = 1
 719     bb/copy-to-ebx 1/imm32
 720     { # line loop
 721 $populate-mu-function-body:line-loop:
 722       # if (open-curly-count == 0) break
 723       81 7/subop/compare %ebx 0/imm32
 724       0f 84/jump-if-equal break/disp32
 725       # line = read-line-buffered(in)
 726       (clear-stream %ecx)
 727       (read-line-buffered *(ebp+8) %ecx)
 728       # if (line->write == 0) break
 729       81 7/subop/compare *ecx 0/imm32
 730       0f 84/jump-if-equal break/disp32
 731       # word-slice = next-word(line)
 732       (next-word %ecx %edx)
 733       # if slice-empty?(word-slice) continue
 734       (slice-empty? %ecx)
 735       3d/compare-eax-and 0/imm32
 736       75/jump-if-not-equal loop/disp8
 737       # if (slice-starts-with?(word-slice, '#') continue
 738       # . eax = *word-slice->start
 739       8b/-> *edx 0/r32/eax
 740       8a/copy-byte *eax 0/r32/AL
 741       81 4/subop/and %eax 0xff/imm32
 742       # . if (eax == '#') continue
 743       3d/compare-eax-and 0x23/imm32/hash
 744       74/jump-if-equal loop/disp8
 745       {
 746         # if slice-equal?(word-slice, "{") ++open-curly-count
 747         {
 748           (slice-equal? %ecx "{")
 749           3d/compare-eax-and 0/imm32
 750           74/jump-if-equal break/disp8
 751           43/increment-ebx
 752           eb/jump $curly-found:end/disp8
 753         }
 754         # else if slice-equal?(word-slice, "}") --open-curly-count
 755         {
 756           (slice-equal? %ecx "}")
 757           3d/compare-eax-and 0/imm32
 758           74/jump-if-equal break/disp8
 759           4b/decrement-ebx
 760           eb/jump $curly-found:end/disp8
 761         }
 762         # else break
 763         eb/jump $populate-mu-function-body:end/disp8
 764       }
 765       # - check for invalid tokens after curly
 766 $curly-found:end:
 767       # second-word-slice = next-word(line)
 768       (next-word %ecx %edx)
 769       # if slice-empty?(second-word-slice) continue
 770       (slice-empty? %ecx)
 771       3d/compare-eax-and 0/imm32
 772       0f 85/jump-if-not-equal loop/disp32
 773       # if (slice-starts-with?(second-word-slice, '#') continue
 774       # . eax = *second-word-slice->start
 775       8b/-> *edx 0/r32/eax
 776       8a/copy-byte *eax 0/r32/AL
 777       81 4/subop/and %eax 0xff/imm32
 778       # . if (eax == '#') continue
 779       3d/compare-eax-and 0x23/imm32/hash
 780       0f 84/jump-if-equal loop/disp32
 781       # abort
 782       eb/jump $populate-mu-function-body:abort/disp8
 783     } # end line loop
 784 $populate-mu-function-body:end:
 785     # . reclaim locals
 786     81 0/subop/add %esp 0x214/imm32
 787     # . restore registers
 788     5b/pop-to-ebx
 789     5a/pop-to-edx
 790     59/pop-to-ecx
 791     58/pop-to-eax
 792     # . epilogue
 793     89/<- %esp 5/r32/ebp
 794     5d/pop-to-ebp
 795     c3/return
 796 
 797 $populate-mu-function-body:abort:
 798     # error("'{' or '}' should be on its own line, but got '")
 799     (write-buffered Stderr "'{' or '}' should be on its own line, but got '")
 800     (rewind-stream %ecx)
 801     (write-stream 2 %ecx)
 802     (write-buffered Stderr "'\n")
 803     (flush Stderr)
 804     # . syscall(exit, 1)
 805     bb/copy-to-ebx  1/imm32
 806     b8/copy-to-eax  1/imm32/exit
 807     cd/syscall  0x80/imm8
 808     # never gets here
 809 
 810 check-mu-types:
 811     # . prologue
 812     55/push-ebp
 813     89/<- %ebp 4/r32/esp
 814     #
 815 $check-types:end:
 816     # . epilogue
 817     89/<- %esp 5/r32/ebp
 818     5d/pop-to-ebp
 819     c3/return
 820 
 821 emit-subx:  # out : (address buffered-file)
 822     # . prologue
 823     55/push-ebp
 824     89/<- %ebp 4/r32/esp
 825     # . save registers
 826     50/push-eax
 827     51/push-ecx
 828     57/push-edi
 829     # edi = out
 830     8b/-> *(ebp+8) 7/r32/edi
 831     # var curr/ecx : (address function) = Program
 832     8b/-> *Program 1/r32/ecx
 833     {
 834       # if (curr == NULL) break
 835       81 7/subop/compare %ecx 0/imm32
 836       0f 84/jump-if-equal break/disp32
 837       (emit-subx-function %edi %ecx)
 838       # curr = curr->next
 839       8b/-> *(ecx+0x10) 1/r32/ecx
 840       e9/jump loop/disp32
 841     }
 842 $emit-subx:end:
 843     # . restore registers
 844     5f/pop-to-edi
 845     59/pop-to-ecx
 846     58/pop-to-eax
 847     # . epilogue
 848     89/<- %esp 5/r32/ebp
 849     5d/pop-to-ebp
 850     c3/return
 851 
 852 # == Emitting a function
 853 # Emit function header
 854 # Emit function prologue
 855 # Translate function body
 856 # Emit function epilogue
 857 
 858 emit-subx-function:  # out : (address buffered-file), f : (address function)
 859     # . prologue
 860     55/push-ebp
 861     89/<- %ebp 4/r32/esp
 862     # . save registers
 863     50/push-eax
 864     51/push-ecx
 865     57/push-edi
 866     # edi = out
 867     8b/-> *(ebp+8) 7/r32/edi
 868     # ecx = f
 869     8b/-> *(ebp+0xc) 1/r32/ecx
 870     #
 871     (write-buffered %edi *ecx)
 872     (write-buffered %edi ":\n")
 873     (emit-subx-prologue %edi)
 874     (emit-subx-block %edi *(ecx+0x10))  # Function-body
 875     (emit-subx-epilogue %edi)
 876 $emit-subx-function:end:
 877     # . restore registers
 878     5f/pop-to-edi
 879     59/pop-to-ecx
 880     58/pop-to-eax
 881     # . epilogue
 882     89/<- %esp 5/r32/ebp
 883     5d/pop-to-ebp
 884     c3/return
 885 
 886 emit-subx-block:  # out : (address buffered-file), block : (address block)
 887     # . prologue
 888     55/push-ebp
 889     89/<- %ebp 4/r32/esp
 890     #
 891 $emit-subx-block:end:
 892     # . epilogue
 893     89/<- %esp 5/r32/ebp
 894     5d/pop-to-ebp
 895     c3/return
 896 
 897 emit-subx-statement:  # out : (address buffered-file), stmt : (address statement), vars : (stack var), primitives : (address primitive), functions : (address function)
 898     # . prologue
 899     55/push-ebp
 900     89/<- %ebp 4/r32/esp
 901     # . save registers
 902     50/push-eax
 903     51/push-ecx
 904     # if stmt matches a primitive, emit it
 905     {
 906 $emit-subx-statement:primitive:
 907       (find-matching-primitive *(ebp+0x14) *(ebp+0xc))  # primitives, stmt => curr/eax
 908       3d/compare-eax-and 0/imm32
 909       74/jump-if-equal break/disp8
 910       (emit-subx-primitive *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax)  # out, stmt, vars, curr
 911       e9/jump $emit-subx-statement:end/disp32
 912     }
 913     # else if stmt matches a function, emit a call to it
 914     {
 915 $emit-subx-statement:call:
 916       (find-matching-function *(ebp+0x18) *(ebp+0xc))  # functions, stmt => curr/eax
 917       3d/compare-eax-and 0/imm32
 918       74/jump-if-equal break/disp8
 919       (emit-subx-call *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax)  # out, stmt, vars, curr
 920       e9/jump $emit-subx-statement:end/disp32
 921     }
 922     # else abort
 923     e9/jump $emit-subx-statement:abort/disp32
 924 $emit-subx-statement:end:
 925     # . restore registers
 926     59/pop-to-ecx
 927     58/pop-to-eax
 928     # . epilogue
 929     89/<- %esp 5/r32/ebp
 930     5d/pop-to-ebp
 931     c3/return
 932 
 933 $emit-subx-statement:abort:
 934     # error("couldn't translate '" stmt "'\n")
 935     (write-buffered Stderr "couldn't translate '")
 936 #?     (emit-string Stderr *(ebp+0xc))  # TODO
 937     (write-buffered Stderr "'\n")
 938     (flush Stderr)
 939     # . syscall(exit, 1)
 940     bb/copy-to-ebx  1/imm32
 941     b8/copy-to-eax  1/imm32/exit
 942     cd/syscall  0x80/imm8
 943     # never gets here
 944 
 945 # Primitives supported
 946 == data
 947 Primitives:
 948     # increment var => ff 0/subop/increment *(ebp+__)
 949     "increment"/imm32/name
 950     Single-int-var-on-stack/imm32/inouts
 951     0/imm32/no-outputs
 952     "ff 0/subop/increment"/imm32/subx-name
 953     1/imm32/rm32-is-first-inout
 954     0/imm32/no-r32
 955     0/imm32/no-imm32
 956     _Primitive-inc-reg/imm32
 957 _Primitive-inc-reg:
 958     # var/reg <- increment => ff 0/subop/increment %__
 959     "increment"/imm32/name
 960     0/imm32/no-inouts
 961     Single-var-in-some-register/imm32/outputs
 962     "ff 0/subop/increment"/imm32/subx-name
 963     3/imm32/rm32-is-first-output
 964     0/imm32/no-r32
 965     0/imm32/no-imm32
 966     0/imm32/next
 967 
 968 Single-int-var-on-stack:
 969     Int-var-on-stack/imm32
 970     0/imm32/next
 971 
 972 Int-var-on-stack:
 973     "arg1"/imm32/name
 974     1/imm32/type-int
 975     1/imm32/some-block-depth
 976     1/imm32/some-stack-offset
 977     0/imm32/no-register
 978 
 979 Single-var-in-some-register:
 980     Int-var-in-some-register/imm32
 981     0/imm32/next
 982 
 983 Int-var-in-some-register:
 984     "arg1"/imm32/name
 985     1/imm32/type-int
 986     1/imm32/some-block-depth
 987     0/imm32/some-stack-offset
 988     "*"/imm32/register
 989 
 990 == code
 991 emit-subx-primitive:  # out : (address buffered-file), stmt : (address statement), vars : (address variable), primitive : (address function)
 992     # . prologue
 993     55/push-ebp
 994     89/<- %ebp 4/r32/esp
 995     # . save registers
 996     50/push-eax
 997     51/push-ecx
 998     # ecx = primitive
 999     8b/-> *(ebp+0x14) 1/r32/ecx
1000     # emit primitive name
1001     (write-buffered *(ebp+8) *(ecx+0xc))  # Primitive-subx-name
1002     # emit rm32 if necessary
1003     (emit-subx-rm32 *(ebp+8) *(ecx+0x10) *(ebp+0xc))  # out, Primitive-subx-rm32, stmt
1004 #?     # emit r32 if necessary
1005 #?     (emit-subx-r32 *(ebp+8) *(ecx+0x14) *(ebp+0xc))  # out, Primitive-subx-r32, stmt
1006 #?     # emit imm32 if necessary
1007 #?     (emit-subx-imm32 *(ebp+8) *(ecx+0x18) *(ebp+0xc))  # out, Primitive-subx-imm32, stmt
1008 $emit-subx-primitive:end:
1009     # . restore registers
1010     59/pop-to-ecx
1011     58/pop-to-eax
1012     # . epilogue
1013     89/<- %esp 5/r32/ebp
1014     5d/pop-to-ebp
1015     c3/return
1016 
1017 emit-subx-rm32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1018     # . prologue
1019     55/push-ebp
1020     89/<- %ebp 4/r32/esp
1021     # . save registers
1022     50/push-eax
1023     # if (l == 0) return
1024     81 7/subop/compare *(ebp+0xc) 0/imm32
1025     74/jump-if-equal $emit-subx-rm32:end/disp8
1026     #
1027     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
1028     (emit-subx-call-operand *(ebp+8) %eax)  # out, var
1029 $emit-subx-rm32:end:
1030     # . restore registers
1031     58/pop-to-eax
1032     # . epilogue
1033     89/<- %esp 5/r32/ebp
1034     5d/pop-to-ebp
1035     c3/return
1036 
1037 get-stmt-operand-from-arg-location:  # stmt : (address statement), l : arg-location -> var/eax : (address variable)
1038     # . prologue
1039     55/push-ebp
1040     89/<- %ebp 4/r32/esp
1041     # . save registers
1042     51/push-ecx
1043     # eax = l
1044     8b/-> *(ebp+0xc) 0/r32/eax
1045     # ecx = stmt
1046     8b/-> *(ebp+8) 1/r32/ecx
1047     # if (l == 1) return stmt->inouts->var
1048     {
1049       3d/compare-eax-and 1/imm32
1050       75/jump-if-not-equal break/disp8
1051 $get-stmt-operand-from-arg-location:1:
1052       8b/-> *(ecx+4) 0/r32/eax  # Stmt-inouts
1053       8b/-> *eax 0/r32/eax  # Operand-var
1054       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1055     }
1056     # if (l == 2) return stmt->inouts->next->var
1057     {
1058       3d/compare-eax-and 2/imm32
1059       75/jump-if-not-equal break/disp8
1060 $get-stmt-operand-from-arg-location:2:
1061       8b/-> *(ecx+4) 0/r32/eax  # Stmt-inouts
1062       8b/-> *(eax+4) 0/r32/eax  # Operand-next
1063       8b/-> *eax 0/r32/eax  # Operand-var
1064       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1065     }
1066     # if (l == 3) return stmt->outputs
1067     {
1068       3d/compare-eax-and 3/imm32
1069       75/jump-if-not-equal break/disp8
1070 $get-stmt-operand-from-arg-location:3:
1071       8b/-> *(ecx+8) 0/r32/eax  # Stmt-outputs
1072       8b/-> *eax 0/r32/eax  # Operand-var
1073       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1074     }
1075     # abort
1076     e9/jump $get-stmt-operand-from-arg-location:abort/disp32
1077 $get-stmt-operand-from-arg-location:end:
1078     # . restore registers
1079     59/pop-to-ecx
1080     # . epilogue
1081     89/<- %esp 5/r32/ebp
1082     5d/pop-to-ebp
1083     c3/return
1084 
1085 $get-stmt-operand-from-arg-location:abort:
1086     # error("invalid arg-location " eax)
1087     (write-buffered Stderr "invalid arg-location ")
1088     (print-int32-buffered Stderr %eax)
1089     (write-buffered Stderr "\n")
1090     (flush Stderr)
1091     # . syscall(exit, 1)
1092     bb/copy-to-ebx  1/imm32
1093     b8/copy-to-eax  1/imm32/exit
1094     cd/syscall  0x80/imm8
1095     # never gets here
1096 
1097 emit-subx-r32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1098     # . prologue
1099     55/push-ebp
1100     89/<- %ebp 4/r32/esp
1101     # . save registers
1102     50/push-eax
1103     51/push-ecx
1104     # location/ecx : enum = primitive->subx-r32
1105     # if (location == 0) return
1106     # var/ecx : var = get-operand(stmt, primitive->subx-rm32)
1107     # emit-subx-call-operand(out, var)
1108 $emit-subx-r32:end:
1109     # . restore registers
1110     59/pop-to-ecx
1111     58/pop-to-eax
1112     # . epilogue
1113     89/<- %esp 5/r32/ebp
1114     5d/pop-to-ebp
1115     c3/return
1116 
1117 emit-subx-imm32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1118     # . prologue
1119     55/push-ebp
1120     89/<- %ebp 4/r32/esp
1121     # . save registers
1122     50/push-eax
1123     51/push-ecx
1124     # var/ecx : var = get-operand(stmt, primitive->subx-rm32)
1125     # emit-subx-call-operand(out, var)
1126 $emit-subx-imm32:end:
1127     # . restore registers
1128     59/pop-to-ecx
1129     58/pop-to-eax
1130     # . epilogue
1131     89/<- %esp 5/r32/ebp
1132     5d/pop-to-ebp
1133     c3/return
1134 
1135 emit-subx-call:  # out : (address buffered-file), stmt : (address statement), vars : (address variable), callee : (address function)
1136     # . prologue
1137     55/push-ebp
1138     89/<- %ebp 4/r32/esp
1139     # . save registers
1140     50/push-eax
1141     51/push-ecx
1142     #
1143     (write-buffered *(ebp+8) "(")
1144     # - emit function name
1145     8b/-> *(ebp+0x14) 1/r32/ecx
1146     (write-buffered *(ebp+8) *(ecx+4))  # Function-subx-name
1147     # - emit arguments
1148     # var curr/ecx : (list var) = stmt->inouts
1149     8b/-> *(ebp+0xc) 1/r32/ecx
1150     8b/-> *(ecx+4) 1/r32/ecx  # Stmt-inouts
1151     {
1152       # if (curr == null) break
1153       81 7/subop/compare %ecx 0/imm32
1154       74/jump-if-equal break/disp8
1155       #
1156       (emit-subx-call-operand *(ebp+8) *ecx)
1157       # curr = curr->next
1158       8b/-> *(ecx+4) 1/r32/ecx
1159     }
1160     #
1161     (write-buffered *(ebp+8) ")")
1162 $emit-subx-call:end:
1163     # . restore registers
1164     59/pop-to-ecx
1165     58/pop-to-eax
1166     # . epilogue
1167     89/<- %esp 5/r32/ebp
1168     5d/pop-to-ebp
1169     c3/return
1170 
1171 emit-subx-call-operand:  # out : (address buffered-file), operand : (address variable)
1172     # . prologue
1173     55/push-ebp
1174     89/<- %ebp 4/r32/esp
1175     # . save registers
1176     50/push-eax
1177     # eax = operand
1178     8b/-> *(ebp+0xc) 0/r32/eax
1179     # if (operand->register) emit "%__"
1180     {
1181       81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
1182       74/jump-if-equal break/disp8
1183 $emit-subx-call-operand:register:
1184       (write-buffered *(ebp+8) " %")
1185       (write-buffered *(ebp+8) *(eax+0x10))  # Var-register
1186     }
1187     # else if (operand->stack-offset) emit "*(ebp+__)"
1188     {
1189       81 7/subop/compare *(eax+0xc) 0/imm32  # Var-stack-offset
1190       74/jump-if-equal break/disp8
1191 $emit-subx-call-operand:stack:
1192       (write-buffered *(ebp+8) Space)
1193       (write-buffered *(ebp+8) "*(ebp+")
1194       8b/-> *(ebp+0xc) 0/r32/eax
1195       (print-int32-buffered *(ebp+8) *(eax+0xc))  # Var-stack-offset
1196       (write-buffered *(ebp+8) ")")
1197     }
1198 $emit-subx-call-operand:end:
1199     # . restore registers
1200     58/pop-to-eax
1201     # . epilogue
1202     89/<- %esp 5/r32/ebp
1203     5d/pop-to-ebp
1204     c3/return
1205 
1206 find-matching-function:  # functions : (address function), stmt : (address statement) -> result/eax : (address function)
1207     # . prologue
1208     55/push-ebp
1209     89/<- %ebp 4/r32/esp
1210     # . save registers
1211     51/push-ecx
1212     # var curr/ecx : (address function) = functions
1213     8b/-> *(ebp+8) 1/r32/ecx
1214     {
1215       # if (curr == null) break
1216       81 7/subop/compare %ecx 0/imm32
1217       74/jump-if-equal break/disp8
1218       # if match(curr, stmt) return curr
1219       {
1220         (mu-stmt-matches-function? *(ebp+0xc) %ecx)  # => eax
1221         3d/compare-eax-and 0/imm32
1222         74/jump-if-equal break/disp8
1223         89/<- %eax 1/r32/ecx
1224         eb/jump $find-matching-function:end/disp8
1225       }
1226       # curr = curr->next
1227       8b/-> *(ecx+0x10) 1/r32/ecx  # Function-next
1228       eb/jump loop/disp8
1229     }
1230     # return null
1231     b8/copy-to-eax 0/imm32
1232 $find-matching-function:end:
1233     # . restore registers
1234     59/pop-to-ecx
1235     # . epilogue
1236     89/<- %esp 5/r32/ebp
1237     5d/pop-to-ebp
1238     c3/return
1239 
1240 find-matching-primitive:  # primitives : (address primitive), stmt : (address statement) -> result/eax : (address primitive)
1241     # . prologue
1242     55/push-ebp
1243     89/<- %ebp 4/r32/esp
1244     # . save registers
1245     51/push-ecx
1246     # var curr/ecx : (address primitive) = primitives
1247     8b/-> *(ebp+8) 1/r32/ecx
1248     {
1249 $find-matching-primitive:loop:
1250       # if (curr == null) break
1251       81 7/subop/compare %ecx 0/imm32
1252       74/jump-if-equal break/disp8
1253       # if match(curr, stmt) return curr
1254       {
1255         (mu-stmt-matches-primitive? *(ebp+0xc) %ecx)  # => eax
1256         3d/compare-eax-and 0/imm32
1257         74/jump-if-equal break/disp8
1258         89/<- %eax 1/r32/ecx
1259         eb/jump $find-matching-function:end/disp8
1260       }
1261 $find-matching-primitive:next-primitive:
1262       # curr = curr->next
1263       8b/-> *(ecx+0x1c) 1/r32/ecx  # Primitive-next
1264       eb/jump loop/disp8
1265     }
1266     # return null
1267     b8/copy-to-eax 0/imm32
1268 $find-matching-primitive:end:
1269     # . restore registers
1270     59/pop-to-ecx
1271     # . epilogue
1272     89/<- %esp 5/r32/ebp
1273     5d/pop-to-ebp
1274     c3/return
1275 
1276 mu-stmt-matches-function?:  # stmt : (address statement), function : (address opcode-info) => result/eax : boolean
1277     # . prologue
1278     55/push-ebp
1279     89/<- %ebp 4/r32/esp
1280     # . save registers
1281     51/push-ecx
1282     # return primitive->name == stmt->operation
1283     8b/-> *(ebp+8) 1/r32/ecx
1284     8b/-> *(ebp+0xc) 0/r32/eax
1285     (string-equal? *ecx *eax)  # => eax
1286 $mu-stmt-matches-function?:end:
1287     # . restore registers
1288     59/pop-to-ecx
1289     # . epilogue
1290     89/<- %esp 5/r32/ebp
1291     5d/pop-to-ebp
1292     c3/return
1293 
1294 mu-stmt-matches-primitive?:  # stmt : (address statement), primitive : (address primitive) => result/eax : boolean
1295     # A mu stmt matches a primitive if the name matches, all the inout vars
1296     # match, and all the output vars match.
1297     # Vars match if types match and registers match.
1298     # In addition, a stmt output matches a primitive's output if types match
1299     # and the primitive has a wildcard register.
1300     # . prologue
1301     55/push-ebp
1302     89/<- %ebp 4/r32/esp
1303     # . save registers
1304     51/push-ecx
1305     52/push-edx
1306     53/push-ebx
1307     56/push-esi
1308     57/push-edi
1309     # ecx = stmt
1310     8b/-> *(ebp+8) 1/r32/ecx
1311     # edx = primitive
1312     8b/-> *(ebp+0xc) 2/r32/edx
1313     {
1314 $mu-stmt-matches-primitive?:check-name:
1315       # if (primitive->name != stmt->operation) return false
1316       (string-equal? *ecx *edx)  # => eax
1317       3d/compare-eax-and 0/imm32
1318       75/jump-if-not-equal break/disp8
1319       b8/copy-to-eax 0/imm32
1320       e9/jump $mu-stmt-matches-primitive?:end/disp32
1321     }
1322 $mu-stmt-matches-primitive?:check-inouts:
1323     # curr = stmt->inouts
1324     8b/-> *(ecx+4) 6/r32/esi  # Stmt-inouts
1325     # curr2 = primitive->inouts
1326     8b/-> *(edx+4) 7/r32/edi  # Primitive-inouts
1327     {
1328       # if (curr == 0) return (curr2 == 0)
1329       {
1330         81 7/subop/compare %esi 0/imm32
1331         75/jump-if-not-equal break/disp8
1332         {
1333           81 7/subop/compare %edi 0/imm32
1334           75/jump-if-not-equal break/disp8
1335           # return true
1336           b8/copy-to-eax 1/imm32
1337           e9/jump $mu-stmt-matches-primitive?:end/disp32
1338         }
1339         # return false
1340         b8/copy-to-eax 0/imm32
1341         e9/jump $mu-stmt-matches-primitive?:end/disp32
1342       }
1343       # if (curr2 == 0) return false
1344       {
1345         81 7/subop/compare %edi 0/imm32
1346         75/jump-if-not-equal break/disp8
1347         b8/copy-to-eax 0/imm32
1348         e9/jump $mu-stmt-matches-primitive?:end/disp32
1349       }
1350       # if (curr != curr2) return false
1351       {
1352         (operand-matches-primitive? *esi *edi)  # => eax
1353         3d/compare-eax-and 0/imm32
1354         75/jump-if-not-equal break/disp8
1355         b8/copy-to-eax 0/imm32
1356         e9/jump $mu-stmt-matches-primitive?:end/disp32
1357       }
1358       # curr=curr->next
1359       8b/-> *(ecx+4) 1/r32/ecx  # Operand-next
1360       # curr2=curr2->next
1361       8b/-> *(edx+4) 2/r32/edx  # Operand-next
1362     }
1363 $mu-stmt-matches-primitive?:check-outputs:
1364     # ecx = stmt
1365     8b/-> *(ebp+8) 1/r32/ecx
1366     # edx = primitive
1367     8b/-> *(ebp+0xc) 2/r32/edx
1368     # curr = stmt->outputs
1369     8b/-> *(ecx+8) 6/r32/esi  # Stmt-outputs
1370     # curr2 = primitive->outputs
1371     8b/-> *(edx+8) 7/r32/edi  # Primitive-outputs
1372     {
1373       # if (curr == 0) return (curr2 == 0)
1374       {
1375         81 7/subop/compare %esi 0/imm32
1376         75/jump-if-not-equal break/disp8
1377         {
1378           81 7/subop/compare %edi 0/imm32
1379           75/jump-if-not-equal break/disp8
1380           # return true
1381           b8/copy-to-eax 1/imm32
1382           e9/jump $mu-stmt-matches-primitive?:end/disp32
1383         }
1384         # return false
1385         b8/copy-to-eax 0/imm32
1386         e9/jump $mu-stmt-matches-primitive?:end/disp32
1387       }
1388       # if (curr2 == 0) return false
1389       {
1390         81 7/subop/compare %edi 0/imm32
1391         75/jump-if-not-equal break/disp8
1392         b8/copy-to-eax 0/imm32
1393         e9/jump $mu-stmt-matches-primitive?:end/disp32
1394       }
1395       # if (curr != curr2) return false
1396       {
1397         (output-operand-matches-primitive? *esi *edi)  # => eax
1398         3d/compare-eax-and 0/imm32
1399         75/jump-if-not-equal break/disp8
1400         b8/copy-to-eax 0/imm32
1401         e9/jump $mu-stmt-matches-primitive?:end/disp32
1402       }
1403       # curr=curr->next
1404       8b/-> *(ecx+4) 1/r32/ecx  # Operand-next
1405       # curr2=curr2->next
1406       8b/-> *(edx+4) 2/r32/edx  # Operand-next
1407     }
1408 $mu-stmt-matches-primitive?:return-true:
1409     b8/copy-to-eax 1/imm32
1410 $mu-stmt-matches-primitive?:end:
1411     # . restore registers
1412     5f/pop-to-edi
1413     5e/pop-to-esi
1414     5b/pop-to-ebx
1415     5a/pop-to-edx
1416     59/pop-to-ecx
1417     # . epilogue
1418     89/<- %esp 5/r32/ebp
1419     5d/pop-to-ebp
1420     c3/return
1421 
1422 operand-matches-primitive?:  # var1 : (address var), var2 : (address var) => result/eax : boolean
1423     # . prologue
1424     55/push-ebp
1425     89/<- %ebp 4/r32/esp
1426     # . save registers
1427     56/push-esi
1428     57/push-edi
1429     # esi = var1
1430     8b/-> *(ebp+8) 6/r32/esi
1431     # edi = var2
1432     8b/-> *(ebp+0xc) 7/r32/edi
1433     # if (var1->type != var2->type) return false
1434     # if (var1->register != var1->register) return false
1435     {
1436       # if addresses are equal, don't return here
1437       8b/-> *(esi+0x10) 0/r32/eax
1438       39/compare *(edi+0x10) 0/r32/eax
1439       74/jump-if-equal break/disp8
1440       # if either address is 0, return false
1441       3d/compare-eax-and 0/imm32
1442       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var1->register to result
1443       81 7/subop/compare *(edi+0x10) 0/imm32
1444       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var1->register to result
1445       # if string contents don't match, return false
1446       (string-equal? *(esi+0x10) *(edi+0x10))  # Var-register Var-register
1447       3d/compare-eax-and 0/imm32
1448       b8/copy-to-eax 0/imm32/false
1449       74/jump-if-equal $operand-matches-primitive?:end/disp8
1450     }
1451     # return true
1452     b8/copy-to-eax 1/imm32/true
1453 $operand-matches-primitive?:end:
1454     # . restore registers
1455     5f/pop-to-edi
1456     5e/pop-to-esi
1457     # . epilogue
1458     89/<- %esp 5/r32/ebp
1459     5d/pop-to-ebp
1460     c3/return
1461 
1462 # like operand-matches-primitive? but also handles "*" register in primitive
1463 output-operand-matches-primitive?:  # var : (address var), primout-var : (address var) => result/eax : boolean
1464     # . prologue
1465     55/push-ebp
1466     89/<- %ebp 4/r32/esp
1467     # . save registers
1468     56/push-esi
1469     57/push-edi
1470     # esi = var
1471     8b/-> *(ebp+8) 6/r32/esi
1472     # edi = primout-var
1473     8b/-> *(ebp+0xc) 7/r32/edi
1474     # if (var->type != primout-var->type) return false
1475     # return false if var->register doesn't match primout-var->register
1476     {
1477       # if addresses are equal, don't return here
1478       8b/-> *(esi+0x10) 0/r32/eax
1479       39/compare *(edi+0x10) 0/r32/eax
1480       74/jump-if-equal break/disp8
1481       # if either address is 0, return false
1482       3d/compare-eax-and 0/imm32
1483       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
1484       81 7/subop/compare *(edi+0x10) 0/imm32
1485       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
1486       # if primout-var->register is "*", return true
1487       (string-equal? *(edi+0x10) "*")  # Var-register
1488       3d/compare-eax-and 0/imm32
1489       b8/copy-to-eax 1/imm32/true
1490       75/jump-if-not-equal $operand-matches-primitive?:end/disp8
1491       # if string contents don't match, return false
1492       (string-equal? *(esi+0x10) *(edi+0x10))  # Var-register Var-register
1493       3d/compare-eax-and 0/imm32
1494       b8/copy-to-eax 0/imm32/false
1495       74/jump-if-equal $operand-matches-primitive?:end/disp8
1496     }
1497     # return true
1498     b8/copy-to-eax 1/imm32/true
1499 $output-operand-matches-primitive?:end:
1500     # . restore registers
1501     5f/pop-to-edi
1502     5e/pop-to-esi
1503     # . epilogue
1504     89/<- %esp 5/r32/ebp
1505     5d/pop-to-ebp
1506     c3/return
1507 
1508 test-emit-subx-statement-primitive:
1509     # Primitive operation on a variable on the stack.
1510     #   increment foo
1511     # =>
1512     #   ff 0/subop/increment *(ebp-8)
1513     #
1514     # There's a variable on the var stack as follows:
1515     #   name: 'foo'
1516     #   type: int
1517     #   stack-offset: -8
1518     #
1519     # There's a primitive with this info:
1520     #   name: 'increment'
1521     #   inouts: int/mem
1522     #   value: 'ff 0/subop/increment'
1523     #
1524     # There's nothing in functions.
1525     #
1526     # . prologue
1527     55/push-ebp
1528     89/<- %ebp 4/r32/esp
1529     # setup
1530     (clear-stream _test-output-stream)
1531     (clear-stream _test-output-buffered-file->buffer)
1532     # var-foo/ecx : var
1533     68/push 0/imm32/no-register
1534     68/push -8/imm32/stack-offset
1535     68/push 1/imm32/block-depth
1536     68/push 1/imm32/type-int
1537     68/push "foo"/imm32
1538     89/<- %ecx 4/r32/esp
1539 #? $aa-var-in-ecx:
1540     # vars/edx : (stack 1)
1541     51/push-ecx/var-foo
1542     68/push 1/imm32/data-length
1543     68/push 1/imm32/top
1544     89/<- %edx 4/r32/esp
1545 #? $aa-vars-in-edx:
1546     # operand/ebx : (list var)
1547     68/push 0/imm32/next
1548     51/push-ecx/var-foo
1549     89/<- %ebx 4/r32/esp
1550 #? $aa-stmt-operand-in-ebx:
1551     # stmt/esi : statement
1552     68/push 0/imm32/next
1553     68/push 0/imm32/outputs
1554     53/push-ebx/operands
1555     68/push "increment"/imm32/operation
1556     89/<- %esi 4/r32/esp
1557 #? $aa-stmt-in-esi:
1558     # primitives/ebx : primitive
1559     68/push 0/imm32/next
1560     68/push 0/imm32/no-imm32
1561     68/push 0/imm32/no-r32
1562     68/push 1/imm32/rm32-is-first-inout
1563     68/push "ff 0/subop/increment"/imm32/subx-name
1564     68/push 0/imm32/outputs
1565     53/push-ebx/inouts  # hack; in practice we won't have the same var in function definition and call
1566     68/push "increment"/imm32/name
1567     89/<- %ebx 4/r32/esp
1568 $aa-primitive-in-ebx:
1569     # convert
1570     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1571     (flush _test-output-buffered-file)
1572 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1578     # check output
1579     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0xfffffff8)" "F - test-emit-subx-statement-primitive/0")
1580     # . reclaim locals
1581     81 0/subop/add %esp 0x48/imm32
1582     # . epilogue
1583     89/<- %esp 5/r32/ebp
1584     5d/pop-to-ebp
1585     c3/return
1586 
1587 test-emit-subx-statement-primitive-register:
1588     # Primitive operation on a variable in a register.
1589     #   foo <- increment
1590     # =>
1591     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1592     #
1593     # There's a variable on the var stack as follows:
1594     #   name: 'foo'
1595     #   type: int
1596     #   register: 'eax'
1597     #
1598     # There's a primitive with this info:
1599     #   name: 'increment'
1600     #   out: int/reg
1601     #   value: 'ff 0/subop/increment'
1602     #
1603     # There's nothing in functions.
1604     #
1605     # . prologue
1606     55/push-ebp
1607     89/<- %ebp 4/r32/esp
1608     # setup
1609     (clear-stream _test-output-stream)
1610     (clear-stream _test-output-buffered-file->buffer)
1611     # var-foo/ecx : var in eax
1612     68/push "eax"/imm32/register
1613     68/push 0/imm32/no-stack-offset
1614     68/push 1/imm32/block-depth
1615     68/push 1/imm32/type-int
1616     68/push "foo"/imm32
1617     89/<- %ecx 4/r32/esp
1618     # vars/edx : (stack 1)
1619     51/push-ecx/var-foo
1620     68/push 1/imm32/data-length
1621     68/push 1/imm32/top
1622     89/<- %edx 4/r32/esp
1623     # operand/ebx : (list var)
1624     68/push 0/imm32/next
1625     51/push-ecx/var-foo
1626     89/<- %ebx 4/r32/esp
1627     # stmt/esi : statement
1628     68/push 0/imm32/next
1629     53/push-ebx/outputs
1630     68/push 0/imm32/inouts
1631     68/push "increment"/imm32/operation
1632     89/<- %esi 4/r32/esp
1633     # formal-var/ebx : var in any register
1634     68/push Any-register/imm32
1635     68/push 0/imm32/no-stack-offset
1636     68/push 1/imm32/block-depth
1637     68/push 1/imm32/type-int
1638     68/push "dummy"/imm32
1639     89/<- %ebx 4/r32/esp
1640     # operand/ebx : (list var)
1641     68/push 0/imm32/next
1642     53/push-ebx/formal-var
1643     89/<- %ebx 4/r32/esp
1644     # primitives/ebx : primitive
1645     68/push 0/imm32/next
1646     68/push 0/imm32/no-imm32
1647     68/push 0/imm32/no-r32
1648     68/push 3/imm32/rm32-in-first-output
1649     68/push "ff 0/subop/increment"/imm32/subx-name
1650     53/push-ebx/outputs
1651     68/push 0/imm32/inouts
1652     68/push "increment"/imm32/name
1653     89/<- %ebx 4/r32/esp
1654     # convert
1655     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1656     (flush _test-output-buffered-file)
1657 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1663     # check output
1664     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-primitive-register/0")
1665     # . reclaim locals
1666     81 0/subop/add %esp 0x48/imm32
1667     # . epilogue
1668     89/<- %esp 5/r32/ebp
1669     5d/pop-to-ebp
1670     c3/return
1671 
1672 test-emit-subx-statement-select-primitive:
1673     # Select the right primitive between overloads.
1674     #   foo <- increment
1675     # =>
1676     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1677     #
1678     # There's a variable on the var stack as follows:
1679     #   name: 'foo'
1680     #   type: int
1681     #   register: 'eax'
1682     #
1683     # There's two primitives, as follows:
1684     #   - name: 'increment'
1685     #     out: int/reg
1686     #     value: 'ff 0/subop/increment'
1687     #   - name: 'increment'
1688     #     inout: int/mem
1689     #     value: 'ff 0/subop/increment'
1690     #
1691     # There's nothing in functions.
1692     #
1693     # . prologue
1694     55/push-ebp
1695     89/<- %ebp 4/r32/esp
1696     # setup
1697     (clear-stream _test-output-stream)
1698     (clear-stream _test-output-buffered-file->buffer)
1699     # var-foo/ecx : var in eax
1700     68/push "eax"/imm32/register
1701     68/push 0/imm32/no-stack-offset
1702     68/push 1/imm32/block-depth
1703     68/push 1/imm32/type-int
1704     68/push "foo"/imm32
1705     89/<- %ecx 4/r32/esp
1706     # vars/edx : (stack 1)
1707     51/push-ecx/var-foo
1708     68/push 1/imm32/data-length
1709     68/push 1/imm32/top
1710     89/<- %edx 4/r32/esp
1711     # real-outputs/edi : (list var)
1712     68/push 0/imm32/next
1713     51/push-ecx/var-foo
1714     89/<- %edi 4/r32/esp
1715     # stmt/esi : statement
1716     68/push 0/imm32/next
1717     57/push-edi/outputs
1718     68/push 0/imm32/inouts
1719     68/push "increment"/imm32/operation
1720     89/<- %esi 4/r32/esp
1721     # formal-var/ebx : var in any register
1722     68/push Any-register/imm32
1723     68/push 0/imm32/no-stack-offset
1724     68/push 1/imm32/block-depth
1725     68/push 1/imm32/type-int
1726     68/push "dummy"/imm32
1727     89/<- %ebx 4/r32/esp
1728     # formal-outputs/ebx : (list var)
1729     68/push 0/imm32/next
1730     53/push-ebx/formal-var
1731     89/<- %ebx 4/r32/esp
1732     # primitive1/ebx : primitive
1733     68/push 0/imm32/next
1734     68/push 0/imm32/no-imm32
1735     68/push 0/imm32/no-r32
1736     68/push 3/imm32/rm32-in-first-output
1737     68/push "ff 0/subop/increment"/imm32/subx-name
1738     53/push-ebx/outputs/formal-outputs
1739     68/push 0/imm32/inouts
1740     68/push "increment"/imm32/name
1741     89/<- %ebx 4/r32/esp
1742     # primitives/ebx : primitive
1743     53/push-ebx/next
1744     68/push 0/imm32/no-imm32
1745     68/push 0/imm32/no-r32
1746     68/push 1/imm32/rm32-is-first-inout
1747     68/push "ff 0/subop/increment"/imm32/subx-name
1748     68/push 0/imm32/outputs
1749     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
1750     68/push "increment"/imm32/name
1751     89/<- %ebx 4/r32/esp
1752     # convert
1753     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1754     (flush _test-output-buffered-file)
1755 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1761     # check output
1762     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive/0")
1763     # . reclaim locals
1764     81 0/subop/add %esp 0x48/imm32
1765     # . epilogue
1766     89/<- %esp 5/r32/ebp
1767     5d/pop-to-ebp
1768     c3/return
1769 
1770 test-emit-subx-statement-select-primitive-2:
1771     # Select the right primitive between overloads.
1772     #   foo <- increment
1773     # =>
1774     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1775     #
1776     # There's a variable on the var stack as follows:
1777     #   name: 'foo'
1778     #   type: int
1779     #   register: 'eax'
1780     #
1781     # There's two primitives, as follows:
1782     #   - name: 'increment'
1783     #     out: int/reg
1784     #     value: 'ff 0/subop/increment'
1785     #   - name: 'increment'
1786     #     inout: int/mem
1787     #     value: 'ff 0/subop/increment'
1788     #
1789     # There's nothing in functions.
1790     #
1791     # . prologue
1792     55/push-ebp
1793     89/<- %ebp 4/r32/esp
1794     # setup
1795     (clear-stream _test-output-stream)
1796     (clear-stream _test-output-buffered-file->buffer)
1797     # var-foo/ecx : var in eax
1798     68/push "eax"/imm32/register
1799     68/push 0/imm32/no-stack-offset
1800     68/push 1/imm32/block-depth
1801     68/push 1/imm32/type-int
1802     68/push "foo"/imm32
1803     89/<- %ecx 4/r32/esp
1804     # vars/edx : (stack 1)
1805     51/push-ecx/var-foo
1806     68/push 1/imm32/data-length
1807     68/push 1/imm32/top
1808     89/<- %edx 4/r32/esp
1809     # inouts/edi : (list var)
1810     68/push 0/imm32/next
1811     51/push-ecx/var-foo
1812     89/<- %edi 4/r32/esp
1813     # stmt/esi : statement
1814     68/push 0/imm32/next
1815     68/push 0/imm32/outputs
1816     57/push-edi/inouts
1817     68/push "increment"/imm32/operation
1818     89/<- %esi 4/r32/esp
1819     # formal-var/ebx : var in any register
1820     68/push Any-register/imm32
1821     68/push 0/imm32/no-stack-offset
1822     68/push 1/imm32/block-depth
1823     68/push 1/imm32/type-int
1824     68/push "dummy"/imm32
1825     89/<- %ebx 4/r32/esp
1826     # operand/ebx : (list var)
1827     68/push 0/imm32/next
1828     53/push-ebx/formal-var
1829     89/<- %ebx 4/r32/esp
1830     # primitive1/ebx : primitive
1831     68/push 0/imm32/next
1832     68/push 0/imm32/no-imm32
1833     68/push 0/imm32/no-r32
1834     68/push 3/imm32/rm32-in-first-output
1835     68/push "ff 0/subop/increment"/imm32/subx-name
1836     53/push-ebx/outputs/formal-outputs
1837     68/push 0/imm32/inouts
1838     68/push "increment"/imm32/name
1839     89/<- %ebx 4/r32/esp
1840     # primitives/ebx : primitive
1841     53/push-ebx/next
1842     68/push 0/imm32/no-imm32
1843     68/push 0/imm32/no-r32
1844     68/push 1/imm32/rm32-is-first-inout
1845     68/push "ff 0/subop/increment"/imm32/subx-name
1846     68/push 0/imm32/outputs
1847     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
1848     68/push "increment"/imm32/name
1849     89/<- %ebx 4/r32/esp
1850     # convert
1851     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1852     (flush _test-output-buffered-file)
1853 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1859     # check output
1860     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive-2/0")
1861     # . reclaim locals
1862     81 0/subop/add %esp 0x48/imm32
1863     # . epilogue
1864     89/<- %esp 5/r32/ebp
1865     5d/pop-to-ebp
1866     c3/return
1867 
1868 test-emit-subx-statement-select-primitive-using-global-primitives:
1869     # Select the right primitive between overloads.
1870     #   foo <- increment
1871     # =>
1872     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1873     #
1874     # There's a variable on the var stack as follows:
1875     #   name: 'foo'
1876     #   type: int
1877     #   register: 'eax'
1878     #
1879     # Primitives are the global definitions.
1880     #
1881     # There are no functions defined.
1882     #
1883     # . prologue
1884     55/push-ebp
1885     89/<- %ebp 4/r32/esp
1886     # setup
1887     (clear-stream _test-output-stream)
1888     (clear-stream _test-output-buffered-file->buffer)
1889     # var-foo/ecx : var in eax
1890     68/push "eax"/imm32/register
1891     68/push 0/imm32/no-stack-offset
1892     68/push 1/imm32/block-depth
1893     68/push 1/imm32/type-int
1894     68/push "foo"/imm32
1895     89/<- %ecx 4/r32/esp
1896     # vars/edx : (stack 1)
1897     51/push-ecx/var-foo
1898     68/push 1/imm32/data-length
1899     68/push 1/imm32/top
1900     89/<- %edx 4/r32/esp
1901     # real-outputs/edi : (list var)
1902     68/push 0/imm32/next
1903     51/push-ecx/var-foo
1904     89/<- %edi 4/r32/esp
1905     # stmt/esi : statement
1906     68/push 0/imm32/next
1907     57/push-edi/outputs
1908     68/push 0/imm32/inouts
1909     68/push "increment"/imm32/operation
1910     89/<- %esi 4/r32/esp
1911     # convert
1912     (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 0)
1913     (flush _test-output-buffered-file)
1914 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1920     # check output
1921     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive/0")
1922     # . reclaim locals
1923     81 0/subop/add %esp 0x48/imm32
1924     # . epilogue
1925     89/<- %esp 5/r32/ebp
1926     5d/pop-to-ebp
1927     c3/return
1928 
1929 test-emit-subx-statement-select-primitive-using-global-primitives-2:
1930     # Select the right primitive between overloads.
1931     #   foo <- increment
1932     # =>
1933     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1934     #
1935     # There's a variable on the var stack as follows:
1936     #   name: 'foo'
1937     #   type: int
1938     #   register: 'eax'
1939     #
1940     # Primitives are the global definitions.
1941     #
1942     # There are no functions defined.
1943     #
1944     # . prologue
1945     55/push-ebp
1946     89/<- %ebp 4/r32/esp
1947     # setup
1948     (clear-stream _test-output-stream)
1949     (clear-stream _test-output-buffered-file->buffer)
1950     # var-foo/ecx : var in eax
1951     68/push "eax"/imm32/register
1952     68/push 0/imm32/no-stack-offset
1953     68/push 1/imm32/block-depth
1954     68/push 1/imm32/type-int
1955     68/push "foo"/imm32
1956     89/<- %ecx 4/r32/esp
1957     # vars/edx : (stack 1)
1958     51/push-ecx/var-foo
1959     68/push 1/imm32/data-length
1960     68/push 1/imm32/top
1961     89/<- %edx 4/r32/esp
1962     # inouts/edi : (list var)
1963     68/push 0/imm32/next
1964     51/push-ecx/var-foo
1965     89/<- %edi 4/r32/esp
1966     # stmt/esi : statement
1967     68/push 0/imm32/next
1968     68/push 0/imm32/outputs
1969     57/push-edi/inouts
1970     68/push "increment"/imm32/operation
1971     89/<- %esi 4/r32/esp
1972     # convert
1973     (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 0)
1974     (flush _test-output-buffered-file)
1975 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1981     # check output
1982     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive-2/0")
1983     # . reclaim locals
1984     81 0/subop/add %esp 0x48/imm32
1985     # . epilogue
1986     89/<- %esp 5/r32/ebp
1987     5d/pop-to-ebp
1988     c3/return
1989 
1990 test-emit-subx-statement-function-call:
1991     # Call a function on a variable on the stack.
1992     #   f foo
1993     # =>
1994     #   (f2 *(ebp-8))
1995     # (Changing the function name supports overloading in general, but here it
1996     # just serves to help disambiguate things.)
1997     #
1998     # There's a variable on the var stack as follows:
1999     #   name: 'foo'
2000     #   type: int
2001     #   stack-offset: -8
2002     #
2003     # There's nothing in primitives.
2004     #
2005     # There's a function with this info:
2006     #   name: 'f'
2007     #   inout: int/mem
2008     #   value: 'f2'
2009     #
2010     # . prologue
2011     55/push-ebp
2012     89/<- %ebp 4/r32/esp
2013     # setup
2014     (clear-stream _test-output-stream)
2015     (clear-stream _test-output-buffered-file->buffer)
2016     # var-foo/ecx : var
2017     68/push 0/imm32/no-register
2018     68/push -8/imm32/stack-offset
2019     68/push 0/imm32/block-depth
2020     68/push 1/imm32/type-int
2021     68/push "foo"/imm32
2022     89/<- %ecx 4/r32/esp
2023     # vars/edx = (stack 1)
2024     51/push-ecx/var-foo
2025     68/push 1/imm32/data-length
2026     68/push 1/imm32/top
2027     89/<- %edx 4/r32/esp
2028     # operands/esi : (list var)
2029     68/push 0/imm32/next
2030     51/push-ecx/var-foo
2031     89/<- %esi 4/r32/esp
2032     # stmt/esi : statement
2033     68/push 0/imm32/next
2034     68/push 0/imm32/outputs
2035     56/push-esi/inouts
2036     68/push "f"/imm32/operation
2037     89/<- %esi 4/r32/esp
2038     # functions/ebx : function
2039     68/push 0/imm32/next
2040     68/push 0/imm32/body
2041     68/push 0/imm32/outputs
2042     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
2043     68/push "f2"/imm32/subx-name
2044     68/push "f"/imm32/name
2045     89/<- %ebx 4/r32/esp
2046     # convert
2047     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
2048     (flush _test-output-buffered-file)
2049 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
2055     # check output
2056     (check-next-stream-line-equal _test-output-stream "(f2 *(ebp+0xfffffff8))" "F - test-emit-subx-statement-function-call/0")
2057     # . reclaim locals
2058     81 0/subop/add %esp 0x3c/imm32
2059     # . epilogue
2060     89/<- %esp 5/r32/ebp
2061     5d/pop-to-ebp
2062     c3/return
2063 
2064 emit-subx-prologue:  # out : (address buffered-file)
2065     # . prologue
2066     55/push-ebp
2067     89/<- %ebp 4/r32/esp
2068     #
2069     (write-buffered *(ebp+8) "# . prologue\n")
2070     (write-buffered *(ebp+8) "55/push-ebp\n")
2071     (write-buffered *(ebp+8) "89/<- %ebp 4/r32/esp\n")
2072 $emit-subx-prologue:end:
2073     # . epilogue
2074     89/<- %esp 5/r32/ebp
2075     5d/pop-to-ebp
2076     c3/return
2077 
2078 emit-subx-epilogue:  # out : (address buffered-file)
2079     # . prologue
2080     55/push-ebp
2081     89/<- %ebp 4/r32/esp
2082     #
2083     (write-buffered *(ebp+8) "# . epilogue\n")
2084     (write-buffered *(ebp+8) "89/<- %esp 5/r32/ebp\n")
2085     (write-buffered *(ebp+8) "5d/pop-to-ebp\n")
2086     (write-buffered *(ebp+8) "c3/return\n")
2087 $emit-subx-epilogue:end:
2088     # . epilogue
2089     89/<- %esp 5/r32/ebp
2090     5d/pop-to-ebp
2091     c3/return