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/next
 957 _Primitive-inc-reg:
 958     # var/reg <- increment => ff 0/subop/increment %__
 959     "increment"/imm32/name
 960     0/imm32/no-inouts
 961     Single-int-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     _Primitive-add-reg-to-reg/imm32/next
 967 _Primitive-add-reg-to-reg:
 968     # var1/reg <- add var2/reg => 01 var1/rm32 var2/r32
 969     "add"/imm32/name
 970     Single-int-var-in-some-register/imm32/inouts
 971     Single-int-var-in-some-register/imm32/outputs
 972     "01"/imm32/subx-name
 973     3/imm32/rm32-is-first-output
 974     1/imm32/r32-is-first-inout
 975     0/imm32/no-imm32
 976     _Primitive-add-lit-to-reg/imm32/next
 977 _Primitive-add-lit-to-reg:
 978     # var1/reg <- add lit => 81 0/subop/add var1/rm32 lit/imm32
 979     "add"/imm32/name
 980     Single-lit-var/imm32/inouts
 981     Single-int-var-in-some-register/imm32/outputs
 982     "81 0/subop/add"/imm32/subx-name
 983     3/imm32/rm32-is-first-output
 984     0/imm32/no-r32
 985     1/imm32/imm32-is-first-inout
 986     0/imm32/next
 987 
 988 Single-int-var-on-stack:
 989     Int-var-on-stack/imm32
 990     0/imm32/next
 991 
 992 Int-var-on-stack:
 993     "arg1"/imm32/name
 994     1/imm32/type-int
 995     1/imm32/some-block-depth
 996     1/imm32/some-stack-offset
 997     0/imm32/no-register
 998 
 999 Single-int-var-in-some-register:
1000     Int-var-in-some-register/imm32
1001     0/imm32/next
1002 
1003 Int-var-in-some-register:
1004     "arg1"/imm32/name
1005     1/imm32/type-int
1006     1/imm32/some-block-depth
1007     0/imm32/some-stack-offset
1008     "*"/imm32/register
1009 
1010 Single-lit-var:
1011     Lit-var/imm32
1012     0/imm32/next
1013 
1014 Lit-var:
1015     "literal"/imm32/name
1016     0/imm32/type-literal
1017     1/imm32/some-block-depth
1018     0/imm32/no-stack-offset
1019     0/imm32/no-register
1020 
1021 == code
1022 emit-subx-primitive:  # out : (address buffered-file), stmt : (address statement), vars : (address variable), primitive : (address function)
1023     # . prologue
1024     55/push-ebp
1025     89/<- %ebp 4/r32/esp
1026     # . save registers
1027     50/push-eax
1028     51/push-ecx
1029     # ecx = primitive
1030     8b/-> *(ebp+0x14) 1/r32/ecx
1031     # emit primitive name
1032     (write-buffered *(ebp+8) *(ecx+0xc))  # Primitive-subx-name
1033     # emit rm32 if necessary
1034     (emit-subx-rm32 *(ebp+8) *(ecx+0x10) *(ebp+0xc))  # out, Primitive-subx-rm32, stmt
1035     # emit r32 if necessary
1036     (emit-subx-r32 *(ebp+8) *(ecx+0x14) *(ebp+0xc))  # out, Primitive-subx-r32, stmt
1037     # emit imm32 if necessary
1038     (emit-subx-imm32 *(ebp+8) *(ecx+0x18) *(ebp+0xc))  # out, Primitive-subx-imm32, stmt
1039 $emit-subx-primitive:end:
1040     # . restore registers
1041     59/pop-to-ecx
1042     58/pop-to-eax
1043     # . epilogue
1044     89/<- %esp 5/r32/ebp
1045     5d/pop-to-ebp
1046     c3/return
1047 
1048 emit-subx-rm32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1049     # . prologue
1050     55/push-ebp
1051     89/<- %ebp 4/r32/esp
1052     # . save registers
1053     50/push-eax
1054     # if (l == 0) return
1055     81 7/subop/compare *(ebp+0xc) 0/imm32
1056     74/jump-if-equal $emit-subx-rm32:end/disp8
1057     #
1058     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
1059     (emit-subx-call-operand *(ebp+8) %eax)  # out, var
1060 $emit-subx-rm32:end:
1061     # . restore registers
1062     58/pop-to-eax
1063     # . epilogue
1064     89/<- %esp 5/r32/ebp
1065     5d/pop-to-ebp
1066     c3/return
1067 
1068 get-stmt-operand-from-arg-location:  # stmt : (address statement), l : arg-location -> var/eax : (address variable)
1069     # . prologue
1070     55/push-ebp
1071     89/<- %ebp 4/r32/esp
1072     # . save registers
1073     51/push-ecx
1074     # eax = l
1075     8b/-> *(ebp+0xc) 0/r32/eax
1076     # ecx = stmt
1077     8b/-> *(ebp+8) 1/r32/ecx
1078     # if (l == 1) return stmt->inouts->var
1079     {
1080       3d/compare-eax-and 1/imm32
1081       75/jump-if-not-equal break/disp8
1082 $get-stmt-operand-from-arg-location:1:
1083       8b/-> *(ecx+4) 0/r32/eax  # Stmt-inouts
1084       8b/-> *eax 0/r32/eax  # Operand-var
1085       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1086     }
1087     # if (l == 2) return stmt->inouts->next->var
1088     {
1089       3d/compare-eax-and 2/imm32
1090       75/jump-if-not-equal break/disp8
1091 $get-stmt-operand-from-arg-location:2:
1092       8b/-> *(ecx+4) 0/r32/eax  # Stmt-inouts
1093       8b/-> *(eax+4) 0/r32/eax  # Operand-next
1094       8b/-> *eax 0/r32/eax  # Operand-var
1095       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1096     }
1097     # if (l == 3) return stmt->outputs
1098     {
1099       3d/compare-eax-and 3/imm32
1100       75/jump-if-not-equal break/disp8
1101 $get-stmt-operand-from-arg-location:3:
1102       8b/-> *(ecx+8) 0/r32/eax  # Stmt-outputs
1103       8b/-> *eax 0/r32/eax  # Operand-var
1104       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1105     }
1106     # abort
1107     e9/jump $get-stmt-operand-from-arg-location:abort/disp32
1108 $get-stmt-operand-from-arg-location:end:
1109     # . restore registers
1110     59/pop-to-ecx
1111     # . epilogue
1112     89/<- %esp 5/r32/ebp
1113     5d/pop-to-ebp
1114     c3/return
1115 
1116 $get-stmt-operand-from-arg-location:abort:
1117     # error("invalid arg-location " eax)
1118     (write-buffered Stderr "invalid arg-location ")
1119     (print-int32-buffered Stderr %eax)
1120     (write-buffered Stderr "\n")
1121     (flush Stderr)
1122     # . syscall(exit, 1)
1123     bb/copy-to-ebx  1/imm32
1124     b8/copy-to-eax  1/imm32/exit
1125     cd/syscall  0x80/imm8
1126     # never gets here
1127 
1128 emit-subx-r32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1129     # . prologue
1130     55/push-ebp
1131     89/<- %ebp 4/r32/esp
1132     # . save registers
1133     50/push-eax
1134     51/push-ecx
1135     # if (location == 0) return
1136     81 7/subop/compare *(ebp+0xc) 0/imm32
1137     0f 84/jump-if-equal $emit-subx-r32:end/disp32
1138     #
1139     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
1140     (maybe-get Registers *(eax+0x10) 8)  # Var-register => eax : (address register-index)
1141     (write-buffered *(ebp+8) Space)
1142     (print-int32-buffered *(ebp+8) *eax)
1143     (write-buffered *(ebp+8) "/r32")
1144 $emit-subx-r32:end:
1145     # . restore registers
1146     59/pop-to-ecx
1147     58/pop-to-eax
1148     # . epilogue
1149     89/<- %esp 5/r32/ebp
1150     5d/pop-to-ebp
1151     c3/return
1152 
1153 emit-subx-imm32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1154     # . prologue
1155     55/push-ebp
1156     89/<- %ebp 4/r32/esp
1157     # . save registers
1158     50/push-eax
1159     51/push-ecx
1160     # if (location == 0) return
1161     81 7/subop/compare *(ebp+0xc) 0/imm32
1162     74/jump-if-equal $emit-subx-imm32:end/disp8
1163     #
1164     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
1165     (write-buffered *(ebp+8) Space)
1166     (write-buffered *(ebp+8) *eax)  # Var-name
1167     (write-buffered *(ebp+8) "/imm32")
1168 $emit-subx-imm32:end:
1169     # . restore registers
1170     59/pop-to-ecx
1171     58/pop-to-eax
1172     # . epilogue
1173     89/<- %esp 5/r32/ebp
1174     5d/pop-to-ebp
1175     c3/return
1176 
1177 emit-subx-call:  # out : (address buffered-file), stmt : (address statement), vars : (address variable), callee : (address function)
1178     # . prologue
1179     55/push-ebp
1180     89/<- %ebp 4/r32/esp
1181     # . save registers
1182     50/push-eax
1183     51/push-ecx
1184     #
1185     (write-buffered *(ebp+8) "(")
1186     # - emit function name
1187     8b/-> *(ebp+0x14) 1/r32/ecx
1188     (write-buffered *(ebp+8) *(ecx+4))  # Function-subx-name
1189     # - emit arguments
1190     # var curr/ecx : (list var) = stmt->inouts
1191     8b/-> *(ebp+0xc) 1/r32/ecx
1192     8b/-> *(ecx+4) 1/r32/ecx  # Stmt-inouts
1193     {
1194       # if (curr == null) break
1195       81 7/subop/compare %ecx 0/imm32
1196       74/jump-if-equal break/disp8
1197       #
1198       (emit-subx-call-operand *(ebp+8) *ecx)
1199       # curr = curr->next
1200       8b/-> *(ecx+4) 1/r32/ecx
1201     }
1202     #
1203     (write-buffered *(ebp+8) ")")
1204 $emit-subx-call:end:
1205     # . restore registers
1206     59/pop-to-ecx
1207     58/pop-to-eax
1208     # . epilogue
1209     89/<- %esp 5/r32/ebp
1210     5d/pop-to-ebp
1211     c3/return
1212 
1213 emit-subx-call-operand:  # out : (address buffered-file), operand : (address variable)
1214     # . prologue
1215     55/push-ebp
1216     89/<- %ebp 4/r32/esp
1217     # . save registers
1218     50/push-eax
1219     # eax = operand
1220     8b/-> *(ebp+0xc) 0/r32/eax
1221     # if (operand->register) emit "%__"
1222     {
1223       81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
1224       74/jump-if-equal break/disp8
1225 $emit-subx-call-operand:register:
1226       (write-buffered *(ebp+8) " %")
1227       (write-buffered *(ebp+8) *(eax+0x10))  # Var-register
1228     }
1229     # else if (operand->stack-offset) emit "*(ebp+__)"
1230     {
1231       81 7/subop/compare *(eax+0xc) 0/imm32  # Var-stack-offset
1232       74/jump-if-equal break/disp8
1233 $emit-subx-call-operand:stack:
1234       (write-buffered *(ebp+8) Space)
1235       (write-buffered *(ebp+8) "*(ebp+")
1236       8b/-> *(ebp+0xc) 0/r32/eax
1237       (print-int32-buffered *(ebp+8) *(eax+0xc))  # Var-stack-offset
1238       (write-buffered *(ebp+8) ")")
1239     }
1240     # else if (operand->type == literal) emit "__"
1241     {
1242       81 7/subop/compare *(eax+4) 0/imm32  # Var-type
1243       75/jump-if-not-equal break/disp8
1244 $emit-subx-call-operand:literal:
1245       (write-buffered *(ebp+8) Space)
1246       (write-buffered *(ebp+8) *eax)
1247     }
1248 $emit-subx-call-operand:end:
1249     # . restore registers
1250     58/pop-to-eax
1251     # . epilogue
1252     89/<- %esp 5/r32/ebp
1253     5d/pop-to-ebp
1254     c3/return
1255 
1256 find-matching-function:  # functions : (address function), stmt : (address statement) -> result/eax : (address function)
1257     # . prologue
1258     55/push-ebp
1259     89/<- %ebp 4/r32/esp
1260     # . save registers
1261     51/push-ecx
1262     # var curr/ecx : (address function) = functions
1263     8b/-> *(ebp+8) 1/r32/ecx
1264     {
1265       # if (curr == null) break
1266       81 7/subop/compare %ecx 0/imm32
1267       74/jump-if-equal break/disp8
1268       # if match(curr, stmt) return curr
1269       {
1270         (mu-stmt-matches-function? *(ebp+0xc) %ecx)  # => eax
1271         3d/compare-eax-and 0/imm32
1272         74/jump-if-equal break/disp8
1273         89/<- %eax 1/r32/ecx
1274         eb/jump $find-matching-function:end/disp8
1275       }
1276       # curr = curr->next
1277       8b/-> *(ecx+0x10) 1/r32/ecx  # Function-next
1278       eb/jump loop/disp8
1279     }
1280     # return null
1281     b8/copy-to-eax 0/imm32
1282 $find-matching-function:end:
1283     # . restore registers
1284     59/pop-to-ecx
1285     # . epilogue
1286     89/<- %esp 5/r32/ebp
1287     5d/pop-to-ebp
1288     c3/return
1289 
1290 find-matching-primitive:  # primitives : (address primitive), stmt : (address statement) -> result/eax : (address primitive)
1291     # . prologue
1292     55/push-ebp
1293     89/<- %ebp 4/r32/esp
1294     # . save registers
1295     51/push-ecx
1296     # var curr/ecx : (address primitive) = primitives
1297     8b/-> *(ebp+8) 1/r32/ecx
1298     {
1299 $find-matching-primitive:loop:
1300       # if (curr == null) break
1301       81 7/subop/compare %ecx 0/imm32
1302       74/jump-if-equal break/disp8
1303       # if match(curr, stmt) return curr
1304       {
1305         (mu-stmt-matches-primitive? *(ebp+0xc) %ecx)  # => eax
1306         3d/compare-eax-and 0/imm32
1307         74/jump-if-equal break/disp8
1308         89/<- %eax 1/r32/ecx
1309         eb/jump $find-matching-function:end/disp8
1310       }
1311 $find-matching-primitive:next-primitive:
1312       # curr = curr->next
1313       8b/-> *(ecx+0x1c) 1/r32/ecx  # Primitive-next
1314       eb/jump loop/disp8
1315     }
1316     # return null
1317     b8/copy-to-eax 0/imm32
1318 $find-matching-primitive:end:
1319     # . restore registers
1320     59/pop-to-ecx
1321     # . epilogue
1322     89/<- %esp 5/r32/ebp
1323     5d/pop-to-ebp
1324     c3/return
1325 
1326 mu-stmt-matches-function?:  # stmt : (address statement), function : (address opcode-info) => result/eax : boolean
1327     # . prologue
1328     55/push-ebp
1329     89/<- %ebp 4/r32/esp
1330     # . save registers
1331     51/push-ecx
1332     # return primitive->name == stmt->operation
1333     8b/-> *(ebp+8) 1/r32/ecx
1334     8b/-> *(ebp+0xc) 0/r32/eax
1335     (string-equal? *ecx *eax)  # => eax
1336 $mu-stmt-matches-function?:end:
1337     # . restore registers
1338     59/pop-to-ecx
1339     # . epilogue
1340     89/<- %esp 5/r32/ebp
1341     5d/pop-to-ebp
1342     c3/return
1343 
1344 mu-stmt-matches-primitive?:  # stmt : (address statement), primitive : (address primitive) => result/eax : boolean
1345     # A mu stmt matches a primitive if the name matches, all the inout vars
1346     # match, and all the output vars match.
1347     # Vars match if types match and registers match.
1348     # In addition, a stmt output matches a primitive's output if types match
1349     # and the primitive has a wildcard register.
1350     # . prologue
1351     55/push-ebp
1352     89/<- %ebp 4/r32/esp
1353     # . save registers
1354     51/push-ecx
1355     52/push-edx
1356     53/push-ebx
1357     56/push-esi
1358     57/push-edi
1359     # ecx = stmt
1360     8b/-> *(ebp+8) 1/r32/ecx
1361     # edx = primitive
1362     8b/-> *(ebp+0xc) 2/r32/edx
1363     {
1364 $mu-stmt-matches-primitive?:check-name:
1365       # if (primitive->name != stmt->operation) return false
1366       (string-equal? *ecx *edx)  # => eax
1367       3d/compare-eax-and 0/imm32
1368       75/jump-if-not-equal break/disp8
1369       b8/copy-to-eax 0/imm32
1370       e9/jump $mu-stmt-matches-primitive?:end/disp32
1371     }
1372 $mu-stmt-matches-primitive?:check-inouts:
1373     # curr = stmt->inouts
1374     8b/-> *(ecx+4) 6/r32/esi  # Stmt-inouts
1375     # curr2 = primitive->inouts
1376     8b/-> *(edx+4) 7/r32/edi  # Primitive-inouts
1377     {
1378       # if (curr == 0) return (curr2 == 0)
1379       {
1380         81 7/subop/compare %esi 0/imm32
1381         75/jump-if-not-equal break/disp8
1382         {
1383           81 7/subop/compare %edi 0/imm32
1384           75/jump-if-not-equal break/disp8
1385           # return true
1386           b8/copy-to-eax 1/imm32
1387           e9/jump $mu-stmt-matches-primitive?:end/disp32
1388         }
1389         # return false
1390         b8/copy-to-eax 0/imm32
1391         e9/jump $mu-stmt-matches-primitive?:end/disp32
1392       }
1393       # if (curr2 == 0) return false
1394       {
1395         81 7/subop/compare %edi 0/imm32
1396         75/jump-if-not-equal break/disp8
1397         b8/copy-to-eax 0/imm32
1398         e9/jump $mu-stmt-matches-primitive?:end/disp32
1399       }
1400       # if (curr != curr2) return false
1401       {
1402         (operand-matches-primitive? *esi *edi)  # => eax
1403         3d/compare-eax-and 0/imm32
1404         75/jump-if-not-equal break/disp8
1405         b8/copy-to-eax 0/imm32
1406         e9/jump $mu-stmt-matches-primitive?:end/disp32
1407       }
1408       # curr=curr->next
1409       8b/-> *(ecx+4) 1/r32/ecx  # Operand-next
1410       # curr2=curr2->next
1411       8b/-> *(edx+4) 2/r32/edx  # Operand-next
1412     }
1413 $mu-stmt-matches-primitive?:check-outputs:
1414     # ecx = stmt
1415     8b/-> *(ebp+8) 1/r32/ecx
1416     # edx = primitive
1417     8b/-> *(ebp+0xc) 2/r32/edx
1418     # curr = stmt->outputs
1419     8b/-> *(ecx+8) 6/r32/esi  # Stmt-outputs
1420     # curr2 = primitive->outputs
1421     8b/-> *(edx+8) 7/r32/edi  # Primitive-outputs
1422     {
1423       # if (curr == 0) return (curr2 == 0)
1424       {
1425         81 7/subop/compare %esi 0/imm32
1426         75/jump-if-not-equal break/disp8
1427         {
1428           81 7/subop/compare %edi 0/imm32
1429           75/jump-if-not-equal break/disp8
1430           # return true
1431           b8/copy-to-eax 1/imm32
1432           e9/jump $mu-stmt-matches-primitive?:end/disp32
1433         }
1434         # return false
1435         b8/copy-to-eax 0/imm32
1436         e9/jump $mu-stmt-matches-primitive?:end/disp32
1437       }
1438       # if (curr2 == 0) return false
1439       {
1440         81 7/subop/compare %edi 0/imm32
1441         75/jump-if-not-equal break/disp8
1442         b8/copy-to-eax 0/imm32
1443         e9/jump $mu-stmt-matches-primitive?:end/disp32
1444       }
1445       # if (curr != curr2) return false
1446       {
1447         (operand-matches-primitive? *esi *edi)  # => eax
1448         3d/compare-eax-and 0/imm32
1449         75/jump-if-not-equal break/disp8
1450         b8/copy-to-eax 0/imm32
1451         e9/jump $mu-stmt-matches-primitive?:end/disp32
1452       }
1453       # curr=curr->next
1454       8b/-> *(ecx+4) 1/r32/ecx  # Operand-next
1455       # curr2=curr2->next
1456       8b/-> *(edx+4) 2/r32/edx  # Operand-next
1457     }
1458 $mu-stmt-matches-primitive?:return-true:
1459     b8/copy-to-eax 1/imm32
1460 $mu-stmt-matches-primitive?:end:
1461     # . restore registers
1462     5f/pop-to-edi
1463     5e/pop-to-esi
1464     5b/pop-to-ebx
1465     5a/pop-to-edx
1466     59/pop-to-ecx
1467     # . epilogue
1468     89/<- %esp 5/r32/ebp
1469     5d/pop-to-ebp
1470     c3/return
1471 
1472 operand-matches-primitive?:  # var : (address var), primout-var : (address var) => result/eax : boolean
1473     # . prologue
1474     55/push-ebp
1475     89/<- %ebp 4/r32/esp
1476     # . save registers
1477     56/push-esi
1478     57/push-edi
1479     # esi = var
1480     8b/-> *(ebp+8) 6/r32/esi
1481     # edi = primout-var
1482     8b/-> *(ebp+0xc) 7/r32/edi
1483     # if (var->type != primout-var->type) return false
1484     # TODO
1485     # return false if var->register doesn't match primout-var->register
1486     {
1487       # if addresses are equal, don't return here
1488       8b/-> *(esi+0x10) 0/r32/eax
1489       39/compare *(edi+0x10) 0/r32/eax
1490       74/jump-if-equal break/disp8
1491       # if either address is 0, return false
1492       3d/compare-eax-and 0/imm32
1493       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
1494       81 7/subop/compare *(edi+0x10) 0/imm32
1495       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
1496       # if primout-var->register is "*", return true
1497       (string-equal? *(edi+0x10) "*")  # Var-register
1498       3d/compare-eax-and 0/imm32
1499       b8/copy-to-eax 1/imm32/true
1500       75/jump-if-not-equal $operand-matches-primitive?:end/disp8
1501       # if string contents don't match, return false
1502       (string-equal? *(esi+0x10) *(edi+0x10))  # Var-register Var-register
1503       3d/compare-eax-and 0/imm32
1504       b8/copy-to-eax 0/imm32/false
1505       74/jump-if-equal $operand-matches-primitive?:end/disp8
1506     }
1507     # return true
1508     b8/copy-to-eax 1/imm32/true
1509 $operand-matches-primitive?:end:
1510     # . restore registers
1511     5f/pop-to-edi
1512     5e/pop-to-esi
1513     # . epilogue
1514     89/<- %esp 5/r32/ebp
1515     5d/pop-to-ebp
1516     c3/return
1517 
1518 test-emit-subx-statement-primitive:
1519     # Primitive operation on a variable on the stack.
1520     #   increment foo
1521     # =>
1522     #   ff 0/subop/increment *(ebp-8)
1523     #
1524     # There's a variable on the var stack as follows:
1525     #   name: 'foo'
1526     #   type: int
1527     #   stack-offset: -8
1528     #
1529     # There's a primitive with this info:
1530     #   name: 'increment'
1531     #   inouts: int/mem
1532     #   value: 'ff 0/subop/increment'
1533     #
1534     # There's nothing in functions.
1535     #
1536     # . prologue
1537     55/push-ebp
1538     89/<- %ebp 4/r32/esp
1539     # setup
1540     (clear-stream _test-output-stream)
1541     (clear-stream _test-output-buffered-file->buffer)
1542     # var-foo/ecx : var
1543     68/push 0/imm32/no-register
1544     68/push -8/imm32/stack-offset
1545     68/push 1/imm32/block-depth
1546     68/push 1/imm32/type-int
1547     68/push "foo"/imm32
1548     89/<- %ecx 4/r32/esp
1549 #? $aa-var-in-ecx:
1550     # vars/edx : (stack 1)
1551     51/push-ecx/var-foo
1552     68/push 1/imm32/data-length
1553     68/push 1/imm32/top
1554     89/<- %edx 4/r32/esp
1555 #? $aa-vars-in-edx:
1556     # operand/ebx : (list var)
1557     68/push 0/imm32/next
1558     51/push-ecx/var-foo
1559     89/<- %ebx 4/r32/esp
1560 #? $aa-stmt-operand-in-ebx:
1561     # stmt/esi : statement
1562     68/push 0/imm32/next
1563     68/push 0/imm32/outputs
1564     53/push-ebx/operands
1565     68/push "increment"/imm32/operation
1566     89/<- %esi 4/r32/esp
1567 #? $aa-stmt-in-esi:
1568     # primitives/ebx : primitive
1569     68/push 0/imm32/next
1570     68/push 0/imm32/no-imm32
1571     68/push 0/imm32/no-r32
1572     68/push 1/imm32/rm32-is-first-inout
1573     68/push "ff 0/subop/increment"/imm32/subx-name
1574     68/push 0/imm32/outputs
1575     53/push-ebx/inouts  # hack; in practice we won't have the same var in function definition and call
1576     68/push "increment"/imm32/name
1577     89/<- %ebx 4/r32/esp
1578 $aa-primitive-in-ebx:
1579     # convert
1580     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1581     (flush _test-output-buffered-file)
1582 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1588     # check output
1589     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0xfffffff8)" "F - test-emit-subx-statement-primitive")
1590     # . epilogue
1591     89/<- %esp 5/r32/ebp
1592     5d/pop-to-ebp
1593     c3/return
1594 
1595 test-emit-subx-statement-primitive-register:
1596     # Primitive operation on a variable in a register.
1597     #   foo <- increment
1598     # =>
1599     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1600     #
1601     # There's a variable on the var stack as follows:
1602     #   name: 'foo'
1603     #   type: int
1604     #   register: 'eax'
1605     #
1606     # There's a primitive with this info:
1607     #   name: 'increment'
1608     #   out: int/reg
1609     #   value: 'ff 0/subop/increment'
1610     #
1611     # There's nothing in functions.
1612     #
1613     # . prologue
1614     55/push-ebp
1615     89/<- %ebp 4/r32/esp
1616     # setup
1617     (clear-stream _test-output-stream)
1618     (clear-stream _test-output-buffered-file->buffer)
1619     # var-foo/ecx : var in eax
1620     68/push "eax"/imm32/register
1621     68/push 0/imm32/no-stack-offset
1622     68/push 1/imm32/block-depth
1623     68/push 1/imm32/type-int
1624     68/push "foo"/imm32
1625     89/<- %ecx 4/r32/esp
1626     # vars/edx : (stack 1)
1627     51/push-ecx/var-foo
1628     68/push 1/imm32/data-length
1629     68/push 1/imm32/top
1630     89/<- %edx 4/r32/esp
1631     # operand/ebx : (list var)
1632     68/push 0/imm32/next
1633     51/push-ecx/var-foo
1634     89/<- %ebx 4/r32/esp
1635     # stmt/esi : statement
1636     68/push 0/imm32/next
1637     53/push-ebx/outputs
1638     68/push 0/imm32/inouts
1639     68/push "increment"/imm32/operation
1640     89/<- %esi 4/r32/esp
1641     # formal-var/ebx : var in any register
1642     68/push Any-register/imm32
1643     68/push 0/imm32/no-stack-offset
1644     68/push 1/imm32/block-depth
1645     68/push 1/imm32/type-int
1646     68/push "dummy"/imm32
1647     89/<- %ebx 4/r32/esp
1648     # operand/ebx : (list var)
1649     68/push 0/imm32/next
1650     53/push-ebx/formal-var
1651     89/<- %ebx 4/r32/esp
1652     # primitives/ebx : primitive
1653     68/push 0/imm32/next
1654     68/push 0/imm32/no-imm32
1655     68/push 0/imm32/no-r32
1656     68/push 3/imm32/rm32-in-first-output
1657     68/push "ff 0/subop/increment"/imm32/subx-name
1658     53/push-ebx/outputs
1659     68/push 0/imm32/inouts
1660     68/push "increment"/imm32/name
1661     89/<- %ebx 4/r32/esp
1662     # convert
1663     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1664     (flush _test-output-buffered-file)
1665 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1671     # check output
1672     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-primitive-register")
1673     # . epilogue
1674     89/<- %esp 5/r32/ebp
1675     5d/pop-to-ebp
1676     c3/return
1677 
1678 test-emit-subx-statement-select-primitive:
1679     # Select the right primitive between overloads.
1680     #   foo <- increment
1681     # =>
1682     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1683     #
1684     # There's a variable on the var stack as follows:
1685     #   name: 'foo'
1686     #   type: int
1687     #   register: 'eax'
1688     #
1689     # There's two primitives, as follows:
1690     #   - name: 'increment'
1691     #     out: int/reg
1692     #     value: 'ff 0/subop/increment'
1693     #   - name: 'increment'
1694     #     inout: int/mem
1695     #     value: 'ff 0/subop/increment'
1696     #
1697     # There's nothing in functions.
1698     #
1699     # . prologue
1700     55/push-ebp
1701     89/<- %ebp 4/r32/esp
1702     # setup
1703     (clear-stream _test-output-stream)
1704     (clear-stream _test-output-buffered-file->buffer)
1705     # var-foo/ecx : var in eax
1706     68/push "eax"/imm32/register
1707     68/push 0/imm32/no-stack-offset
1708     68/push 1/imm32/block-depth
1709     68/push 1/imm32/type-int
1710     68/push "foo"/imm32
1711     89/<- %ecx 4/r32/esp
1712     # vars/edx : (stack 1)
1713     51/push-ecx/var-foo
1714     68/push 1/imm32/data-length
1715     68/push 1/imm32/top
1716     89/<- %edx 4/r32/esp
1717     # real-outputs/edi : (list var)
1718     68/push 0/imm32/next
1719     51/push-ecx/var-foo
1720     89/<- %edi 4/r32/esp
1721     # stmt/esi : statement
1722     68/push 0/imm32/next
1723     57/push-edi/outputs
1724     68/push 0/imm32/inouts
1725     68/push "increment"/imm32/operation
1726     89/<- %esi 4/r32/esp
1727     # formal-var/ebx : var in any register
1728     68/push Any-register/imm32
1729     68/push 0/imm32/no-stack-offset
1730     68/push 1/imm32/block-depth
1731     68/push 1/imm32/type-int
1732     68/push "dummy"/imm32
1733     89/<- %ebx 4/r32/esp
1734     # formal-outputs/ebx : (list var)
1735     68/push 0/imm32/next
1736     53/push-ebx/formal-var
1737     89/<- %ebx 4/r32/esp
1738     # primitive1/ebx : primitive
1739     68/push 0/imm32/next
1740     68/push 0/imm32/no-imm32
1741     68/push 0/imm32/no-r32
1742     68/push 3/imm32/rm32-in-first-output
1743     68/push "ff 0/subop/increment"/imm32/subx-name
1744     53/push-ebx/outputs/formal-outputs
1745     68/push 0/imm32/inouts
1746     68/push "increment"/imm32/name
1747     89/<- %ebx 4/r32/esp
1748     # primitives/ebx : primitive
1749     53/push-ebx/next
1750     68/push 0/imm32/no-imm32
1751     68/push 0/imm32/no-r32
1752     68/push 1/imm32/rm32-is-first-inout
1753     68/push "ff 0/subop/increment"/imm32/subx-name
1754     68/push 0/imm32/outputs
1755     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
1756     68/push "increment"/imm32/name
1757     89/<- %ebx 4/r32/esp
1758     # convert
1759     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1760     (flush _test-output-buffered-file)
1761 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1767     # check output
1768     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive")
1769     # . epilogue
1770     89/<- %esp 5/r32/ebp
1771     5d/pop-to-ebp
1772     c3/return
1773 
1774 test-emit-subx-statement-select-primitive-2:
1775     # Select the right primitive between overloads.
1776     #   foo <- increment
1777     # =>
1778     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1779     #
1780     # There's a variable on the var stack as follows:
1781     #   name: 'foo'
1782     #   type: int
1783     #   register: 'eax'
1784     #
1785     # There's two primitives, as follows:
1786     #   - name: 'increment'
1787     #     out: int/reg
1788     #     value: 'ff 0/subop/increment'
1789     #   - name: 'increment'
1790     #     inout: int/mem
1791     #     value: 'ff 0/subop/increment'
1792     #
1793     # There's nothing in functions.
1794     #
1795     # . prologue
1796     55/push-ebp
1797     89/<- %ebp 4/r32/esp
1798     # setup
1799     (clear-stream _test-output-stream)
1800     (clear-stream _test-output-buffered-file->buffer)
1801     # var-foo/ecx : var in eax
1802     68/push "eax"/imm32/register
1803     68/push 0/imm32/no-stack-offset
1804     68/push 1/imm32/block-depth
1805     68/push 1/imm32/type-int
1806     68/push "foo"/imm32
1807     89/<- %ecx 4/r32/esp
1808     # vars/edx : (stack 1)
1809     51/push-ecx/var-foo
1810     68/push 1/imm32/data-length
1811     68/push 1/imm32/top
1812     89/<- %edx 4/r32/esp
1813     # inouts/edi : (list var)
1814     68/push 0/imm32/next
1815     51/push-ecx/var-foo
1816     89/<- %edi 4/r32/esp
1817     # stmt/esi : statement
1818     68/push 0/imm32/next
1819     68/push 0/imm32/outputs
1820     57/push-edi/inouts
1821     68/push "increment"/imm32/operation
1822     89/<- %esi 4/r32/esp
1823     # formal-var/ebx : var in any register
1824     68/push Any-register/imm32
1825     68/push 0/imm32/no-stack-offset
1826     68/push 1/imm32/block-depth
1827     68/push 1/imm32/type-int
1828     68/push "dummy"/imm32
1829     89/<- %ebx 4/r32/esp
1830     # operand/ebx : (list var)
1831     68/push 0/imm32/next
1832     53/push-ebx/formal-var
1833     89/<- %ebx 4/r32/esp
1834     # primitive1/ebx : primitive
1835     68/push 0/imm32/next
1836     68/push 0/imm32/no-imm32
1837     68/push 0/imm32/no-r32
1838     68/push 3/imm32/rm32-in-first-output
1839     68/push "ff 0/subop/increment"/imm32/subx-name
1840     53/push-ebx/outputs/formal-outputs
1841     68/push 0/imm32/inouts
1842     68/push "increment"/imm32/name
1843     89/<- %ebx 4/r32/esp
1844     # primitives/ebx : primitive
1845     53/push-ebx/next
1846     68/push 0/imm32/no-imm32
1847     68/push 0/imm32/no-r32
1848     68/push 1/imm32/rm32-is-first-inout
1849     68/push "ff 0/subop/increment"/imm32/subx-name
1850     68/push 0/imm32/outputs
1851     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
1852     68/push "increment"/imm32/name
1853     89/<- %ebx 4/r32/esp
1854     # convert
1855     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1856     (flush _test-output-buffered-file)
1857 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1863     # check output
1864     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive-2")
1865     # . epilogue
1866     89/<- %esp 5/r32/ebp
1867     5d/pop-to-ebp
1868     c3/return
1869 
1870 test-increment-register:
1871     # Select the right primitive between overloads.
1872     #   foo <- increment
1873     # =>
1874     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1875     #
1876     # There's a variable on the var stack as follows:
1877     #   name: 'foo'
1878     #   type: int
1879     #   register: 'eax'
1880     #
1881     # Primitives are the global definitions.
1882     #
1883     # There are no functions defined.
1884     #
1885     # . prologue
1886     55/push-ebp
1887     89/<- %ebp 4/r32/esp
1888     # setup
1889     (clear-stream _test-output-stream)
1890     (clear-stream _test-output-buffered-file->buffer)
1891     # var-foo/ecx : var in eax
1892     68/push "eax"/imm32/register
1893     68/push 0/imm32/no-stack-offset
1894     68/push 1/imm32/block-depth
1895     68/push 1/imm32/type-int
1896     68/push "foo"/imm32
1897     89/<- %ecx 4/r32/esp
1898     # vars/edx : (stack 1)
1899     51/push-ecx/var-foo
1900     68/push 1/imm32/data-length
1901     68/push 1/imm32/top
1902     89/<- %edx 4/r32/esp
1903     # real-outputs/edi : (list var)
1904     68/push 0/imm32/next
1905     51/push-ecx/var-foo
1906     89/<- %edi 4/r32/esp
1907     # stmt/esi : statement
1908     68/push 0/imm32/next
1909     57/push-edi/outputs
1910     68/push 0/imm32/inouts
1911     68/push "increment"/imm32/operation
1912     89/<- %esi 4/r32/esp
1913     # convert
1914     (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 0)
1915     (flush _test-output-buffered-file)
1916 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
1922     # check output
1923     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-increment-register")
1924     # . epilogue
1925     89/<- %esp 5/r32/ebp
1926     5d/pop-to-ebp
1927     c3/return
1928 
1929 test-increment-var:
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-increment-var")
1983     # . epilogue
1984     89/<- %esp 5/r32/ebp
1985     5d/pop-to-ebp
1986     c3/return
1987 
1988 test-add-reg-to-reg:
1989     #   var1/reg <- add var2/reg
1990     # =>
1991     #   01 %var1 var2
1992     #
1993     # . prologue
1994     55/push-ebp
1995     89/<- %ebp 4/r32/esp
1996     # setup
1997     (clear-stream _test-output-stream)
1998     (clear-stream _test-output-buffered-file->buffer)
1999     # var-var1/ecx : var in eax
2000     68/push "eax"/imm32/register
2001     68/push 0/imm32/no-stack-offset
2002     68/push 1/imm32/block-depth
2003     68/push 1/imm32/type-int
2004     68/push "var1"/imm32
2005     89/<- %ecx 4/r32/esp
2006     # var-var2/edx : var in ecx
2007     68/push "ecx"/imm32/register
2008     68/push 0/imm32/no-stack-offset
2009     68/push 1/imm32/block-depth
2010     68/push 1/imm32/type-int
2011     68/push "var2"/imm32
2012     89/<- %edx 4/r32/esp
2013     # inouts/esi : (list var2)
2014     68/push 0/imm32/next
2015     52/push-edx/var-var2
2016     89/<- %esi 4/r32/esp
2017     # outputs/edi : (list var1)
2018     68/push 0/imm32/next
2019     51/push-ecx/var-var1
2020     89/<- %edi 4/r32/esp
2021     # stmt/esi : statement
2022     68/push 0/imm32/next
2023     57/push-edi/outputs
2024     56/push-esi/inouts
2025     68/push "add"/imm32/operation
2026     89/<- %esi 4/r32/esp
2027     # convert
2028     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
2029     (flush _test-output-buffered-file)
2030 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
2036     # check output
2037     (check-next-stream-line-equal _test-output-stream "01 %eax 0x00000001/r32" "F - test-add-reg-to-reg")
2038     # . epilogue
2039     89/<- %esp 5/r32/ebp
2040     5d/pop-to-ebp
2041     c3/return
2042 
2043 test-add-literal-to-reg:
2044     #   var1/eax <- add 0x34
2045     # =>
2046     #   81 0/subop/add %eax 0x34/imm32
2047     #
2048     # . prologue
2049     55/push-ebp
2050     89/<- %ebp 4/r32/esp
2051     # setup
2052     (clear-stream _test-output-stream)
2053     (clear-stream _test-output-buffered-file->buffer)
2054     # var-var1/ecx : var in eax
2055     68/push "eax"/imm32/register
2056     68/push 0/imm32/no-stack-offset
2057     68/push 1/imm32/block-depth
2058     68/push 1/imm32/type-int
2059     68/push "var1"/imm32
2060     89/<- %ecx 4/r32/esp
2061     # var-var2/edx : var literal
2062     68/push 0/imm32/no-register
2063     68/push 0/imm32/no-stack-offset
2064     68/push 1/imm32/block-depth
2065     68/push 0/imm32/type-literal
2066     68/push "0x34"/imm32
2067     89/<- %edx 4/r32/esp
2068     # inouts/esi : (list var2)
2069     68/push 0/imm32/next
2070     52/push-edx/var-var2
2071     89/<- %esi 4/r32/esp
2072     # outputs/edi : (list var1)
2073     68/push 0/imm32/next
2074     51/push-ecx/var-var1
2075     89/<- %edi 4/r32/esp
2076     # stmt/esi : statement
2077     68/push 0/imm32/next
2078     57/push-edi/outputs
2079     56/push-esi/inouts
2080     68/push "add"/imm32/operation
2081     89/<- %esi 4/r32/esp
2082     # convert
2083     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
2084     (flush _test-output-buffered-file)
2085 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
2091     # check output
2092     (check-next-stream-line-equal _test-output-stream "81 0/subop/add %eax 0x34/imm32" "F - test-add-literal-to-reg")
2093     # . epilogue
2094     89/<- %esp 5/r32/ebp
2095     5d/pop-to-ebp
2096     c3/return
2097 
2098 test-emit-subx-statement-function-call:
2099     # Call a function on a variable on the stack.
2100     #   f foo
2101     # =>
2102     #   (f2 *(ebp-8))
2103     # (Changing the function name supports overloading in general, but here it
2104     # just serves to help disambiguate things.)
2105     #
2106     # There's a variable on the var stack as follows:
2107     #   name: 'foo'
2108     #   type: int
2109     #   stack-offset: -8
2110     #
2111     # There's nothing in primitives.
2112     #
2113     # There's a function with this info:
2114     #   name: 'f'
2115     #   inout: int/mem
2116     #   value: 'f2'
2117     #
2118     # . prologue
2119     55/push-ebp
2120     89/<- %ebp 4/r32/esp
2121     # setup
2122     (clear-stream _test-output-stream)
2123     (clear-stream _test-output-buffered-file->buffer)
2124     # var-foo/ecx : var
2125     68/push 0/imm32/no-register
2126     68/push -8/imm32/stack-offset
2127     68/push 0/imm32/block-depth
2128     68/push 1/imm32/type-int
2129     68/push "foo"/imm32
2130     89/<- %ecx 4/r32/esp
2131     # vars/edx = (stack 1)
2132     51/push-ecx/var-foo
2133     68/push 1/imm32/data-length
2134     68/push 1/imm32/top
2135     89/<- %edx 4/r32/esp
2136     # operands/esi : (list var)
2137     68/push 0/imm32/next
2138     51/push-ecx/var-foo
2139     89/<- %esi 4/r32/esp
2140     # stmt/esi : statement
2141     68/push 0/imm32/next
2142     68/push 0/imm32/outputs
2143     56/push-esi/inouts
2144     68/push "f"/imm32/operation
2145     89/<- %esi 4/r32/esp
2146     # functions/ebx : function
2147     68/push 0/imm32/next
2148     68/push 0/imm32/body
2149     68/push 0/imm32/outputs
2150     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
2151     68/push "f2"/imm32/subx-name
2152     68/push "f"/imm32/name
2153     89/<- %ebx 4/r32/esp
2154     # convert
2155     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
2156     (flush _test-output-buffered-file)
2157 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
2163     # check output
2164     (check-next-stream-line-equal _test-output-stream "(f2 *(ebp+0xfffffff8))" "F - test-emit-subx-statement-function-call")
2165     # . epilogue
2166     89/<- %esp 5/r32/ebp
2167     5d/pop-to-ebp
2168     c3/return
2169 
2170 test-emit-subx-statement-function-call-with-literal-arg:
2171     # Call a function on a literal.
2172     #   f 34
2173     # =>
2174     #   (f2 34)
2175     #
2176     # . prologue
2177     55/push-ebp
2178     89/<- %ebp 4/r32/esp
2179     # setup
2180     (clear-stream _test-output-stream)
2181     (clear-stream _test-output-buffered-file->buffer)
2182     # var-foo/ecx : literal
2183     68/push 0/imm32/no-register
2184     68/push 0/imm32/no-stack-offset
2185     68/push 0/imm32/block-depth
2186     68/push 0/imm32/type-literal
2187     68/push "34"/imm32
2188     89/<- %ecx 4/r32/esp
2189     # vars/edx = (stack 1)
2190     51/push-ecx/var-foo
2191     68/push 1/imm32/data-length
2192     68/push 1/imm32/top
2193     89/<- %edx 4/r32/esp
2194     # operands/esi : (list var)
2195     68/push 0/imm32/next
2196     51/push-ecx/var-foo
2197     89/<- %esi 4/r32/esp
2198     # stmt/esi : statement
2199     68/push 0/imm32/next
2200     68/push 0/imm32/outputs
2201     56/push-esi/inouts
2202     68/push "f"/imm32/operation
2203     89/<- %esi 4/r32/esp
2204     # functions/ebx : function
2205     68/push 0/imm32/next
2206     68/push 0/imm32/body
2207     68/push 0/imm32/outputs
2208     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
2209     68/push "f2"/imm32/subx-name
2210     68/push "f"/imm32/name
2211     89/<- %ebx 4/r32/esp
2212     # convert
2213     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
2214     (flush _test-output-buffered-file)
2215 +--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
2221     # check output
2222     (check-next-stream-line-equal _test-output-stream "(f2 34)" "F - test-emit-subx-statement-function-call-with-literal-arg")
2223     # . epilogue
2224     89/<- %esp 5/r32/ebp
2225     5d/pop-to-ebp
2226     c3/return
2227 
2228 emit-subx-prologue:  # out : (address buffered-file)
2229     # . prologue
2230     55/push-ebp
2231     89/<- %ebp 4/r32/esp
2232     #
2233     (write-buffered *(ebp+8) "# . prologue\n")
2234     (write-buffered *(ebp+8) "55/push-ebp\n")
2235     (write-buffered *(ebp+8) "89/<- %ebp 4/r32/esp\n")
2236 $emit-subx-prologue:end:
2237     # . epilogue
2238     89/<- %esp 5/r32/ebp
2239     5d/pop-to-ebp
2240     c3/return
2241 
2242 emit-subx-epilogue:  # out : (address buffered-file)
2243     # . prologue
2244     55/push-ebp
2245     89/<- %ebp 4/r32/esp
2246     #
2247     (write-buffered *(ebp+8) "# . epilogue\n")
2248     (write-buffered *(ebp+8) "89/<- %esp 5/r32/ebp\n")
2249     (write-buffered *(ebp+8) "5d/pop-to-ebp\n")
2250     (write-buffered *(ebp+8) "c3/return\n")
2251 $emit-subx-epilogue:end:
2252     # . epilogue
2253     89/<- %esp 5/r32/ebp
2254     5d/pop-to-ebp
2255     c3/return