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