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 operand)
 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-operation:
 306   0/imm32
 307 Stmt-inouts:
 308   4/imm32
 309 Stmt-outputs:
 310   8/imm32
 311 Stmt-next:
 312   0xc/imm32
 313 Stmt-size:
 314   0x10/imm32
 315 
 316 Var-name:
 317   0/imm32
 318 Var-type:
 319   4/imm32
 320 Var-block:
 321   8/imm32
 322 Var-stack-offset:
 323   0xc/imm32
 324 Var-register:
 325   0x10/imm32
 326 Var-size:
 327   0x14/imm32
 328 
 329 Any-register:  # "*"
 330   # size
 331   1/imm32
 332   # data
 333   2a/asterisk
 334 
 335 == code
 336 
 337 Entry:
 338     # . prologue
 339     89/<- %ebp 4/r32/esp
 340     (new-segment Heap-size Heap)
 341     # if (argv[1] == "test') run-tests()
 342     {
 343       # if (argc <= 1) break
 344       81 7/subop/compare *ebp 1/imm32
 345       7e/jump-if-lesser-or-equal break/disp8
 346       # if (argv[1] != "test") break
 347       (kernel-string-equal? *(ebp+8) "test")  # => eax
 348       3d/compare-eax-and 0/imm32
 349       74/jump-if-equal break/disp8
 350       #
 351       (run-tests)
 352       # syscall(exit, *Num-test-failures)
 353       8b/-> *Num-test-failures 3/r32/ebx
 354       eb/jump $mu-main:end/disp8
 355     }
 356     # otherwise convert Stdin
 357     (convert-mu Stdin Stdout)
 358     (flush Stdout)
 359     # syscall(exit, 0)
 360     bb/copy-to-ebx 0/imm32
 361 $mu-main:end:
 362     b8/copy-to-eax 1/imm32/exit
 363     cd/syscall 0x80/imm8
 364 
 365 convert-mu:  # in : (address buffered-file), out : (address buffered-file)
 366     # . prologue
 367     55/push-ebp
 368     89/<- %ebp 4/r32/esp
 369     #
 370     (parse-mu *(ebp+8))
 371     (check-mu-types)
 372     (emit-subx *(ebp+0xc))
 373 $convert-mu:end:
 374     # . epilogue
 375     89/<- %esp 5/r32/ebp
 376     5d/pop-to-ebp
 377     c3/return
 378 
 379 test-convert-empty-input:
 380     # empty input => empty output
 381     # . prologue
 382     55/push-ebp
 383     89/<- %ebp 4/r32/esp
 384     # setup
 385     (clear-stream _test-input-stream)
 386     (clear-stream _test-input-buffered-file->buffer)
 387     (clear-stream _test-output-stream)
 388     (clear-stream _test-output-buffered-file->buffer)
 389     #
 390     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 391     (flush _test-output-buffered-file)
 392     (check-stream-equal _test-output-stream "" "F - test-convert-empty-input")
 393     # . epilogue
 394     89/<- %esp 5/r32/ebp
 395     5d/pop-to-ebp
 396     c3/return
 397 
 398 test-convert-function-skeleton:
 399     # empty function decl => function prologue and epilogue
 400     #   fn foo {
 401     #   }
 402     # =>
 403     #   foo:
 404     #     # . prologue
 405     #     55/push-ebp
 406     #     89/<- %ebp 4/r32/esp
 407     #     # . epilogue
 408     #     89/<- %esp 5/r32/ebp
 409     #     5d/pop-to-ebp
 410     #     c3/return
 411     # . prologue
 412     55/push-ebp
 413     89/<- %ebp 4/r32/esp
 414     # setup
 415     (clear-stream _test-input-stream)
 416     (clear-stream _test-input-buffered-file->buffer)
 417     (clear-stream _test-output-stream)
 418     (clear-stream _test-output-buffered-file->buffer)
 419     #
 420     (write _test-input-stream "fn foo {\n")
 421     (write _test-input-stream "}\n")
 422     # convert
 423     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 424     (flush _test-output-buffered-file)
 425 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 431     # check output
 432     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-skeleton/0")
 433     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-skeleton/1")
 434     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-skeleton/2")
 435     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-skeleton/3")
 436     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-skeleton/4")
 437     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-skeleton/5")
 438     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-skeleton/6")
 439     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-skeleton/7")
 440     # . epilogue
 441     89/<- %esp 5/r32/ebp
 442     5d/pop-to-ebp
 443     c3/return
 444 
 445 test-convert-multiple-function-skeletons:
 446     # multiple functions correctly organized into a linked list
 447     #   fn foo {
 448     #   }
 449     #   fn bar {
 450     #   }
 451     # =>
 452     #   foo:
 453     #     # . prologue
 454     #     55/push-ebp
 455     #     89/<- %ebp 4/r32/esp
 456     #     # . epilogue
 457     #     89/<- %esp 5/r32/ebp
 458     #     5d/pop-to-ebp
 459     #     c3/return
 460     #   bar:
 461     #     # . prologue
 462     #     55/push-ebp
 463     #     89/<- %ebp 4/r32/esp
 464     #     # . epilogue
 465     #     89/<- %esp 5/r32/ebp
 466     #     5d/pop-to-ebp
 467     #     c3/return
 468     # . prologue
 469     55/push-ebp
 470     89/<- %ebp 4/r32/esp
 471     # setup
 472     (clear-stream _test-input-stream)
 473     (clear-stream _test-input-buffered-file->buffer)
 474     (clear-stream _test-output-stream)
 475     (clear-stream _test-output-buffered-file->buffer)
 476     #
 477     (write _test-input-stream "fn foo {\n")
 478     (write _test-input-stream "}\n")
 479     (write _test-input-stream "fn bar {\n")
 480     (write _test-input-stream "}\n")
 481     # convert
 482     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 483     (flush _test-output-buffered-file)
 484 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 490     # check first function
 491     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-multiple-function-skeletons/0")
 492     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/1")
 493     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/2")
 494     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/3")
 495     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/4")
 496     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/5")
 497     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/6")
 498     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/7")
 499     # check second function
 500     (check-next-stream-line-equal _test-output-stream "bar:"                  "F - test-convert-multiple-function-skeletons/10")
 501     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-multiple-function-skeletons/11")
 502     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-multiple-function-skeletons/12")
 503     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/13")
 504     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-multiple-function-skeletons/14")
 505     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/15")
 506     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/16")
 507     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-multiple-function-skeletons/17")
 508     # . epilogue
 509     89/<- %esp 5/r32/ebp
 510     5d/pop-to-ebp
 511     c3/return
 512 
 513 test-convert-function-with-arg:
 514     # function with one arg and a copy instruction
 515     #   fn foo n : int -> result/eax : int {
 516     #     result <- copy n
 517     #   }
 518     # =>
 519     #   foo:
 520     #     # . prologue
 521     #     55/push-ebp
 522     #     89/<- %ebp 4/r32/esp
 523     #     {
 524     #     # result <- copy n
 525     #     8b/-> *(ebp+8) 0/r32/eax
 526     #     }
 527     #     # . epilogue
 528     #     89/<- %esp 5/r32/ebp
 529     #     5d/pop-to-ebp
 530     #     c3/return
 531     # . prologue
 532     55/push-ebp
 533     89/<- %ebp 4/r32/esp
 534     # setup
 535     (clear-stream _test-input-stream)
 536     (clear-stream _test-input-buffered-file->buffer)
 537     (clear-stream _test-output-stream)
 538     (clear-stream _test-output-buffered-file->buffer)
 539     #
 540     (write _test-input-stream "fn foo {\n")
 541     (write _test-input-stream "}\n")
 542     # convert
 543     (convert-mu _test-input-buffered-file _test-output-buffered-file)
 544     (flush _test-output-buffered-file)
 545 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 551     # check output
 552     (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-skeleton/0")
 553     (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-skeleton/1")
 554     (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-skeleton/2")
 555     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-skeleton/3")
 556     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-skeleton/4")
 557     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-skeleton/5")
 558     (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-skeleton/6")
 559     (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-skeleton/7")
 560     # . epilogue
 561     89/<- %esp 5/r32/ebp
 562     5d/pop-to-ebp
 563     c3/return
 564 
 565 parse-mu:  # in : (address buffered-file)
 566     # pseudocode
 567     #   var curr-function = Program
 568     #   var line : (stream byte 512)
 569     #   var word-slice : slice
 570     #   while true                                  # line loop
 571     #     clear-stream(line)
 572     #     read-line-buffered(in, line)
 573     #     if (line->write == 0) break               # end of file
 574     #     while true                                # word loop
 575     #       word-slice = next-word-or-string(line)
 576     #       if slice-empty?(word-slice)             # end of line
 577     #         break
 578     #       else if slice-starts-with?(word-slice, "#")  # comment
 579     #         break                                 # end of line
 580     #       else if slice-equal(word-slice, "fn")
 581     #         var new-function : (address function) = new function
 582     #         populate-mu-function(in, new-function)
 583     #         *curr-function = new-function
 584     #         curr-function = &new-function->next
 585     #       else
 586     #         abort()
 587     #
 588     # . prologue
 589     55/push-ebp
 590     89/<- %ebp 4/r32/esp
 591     # . save registers
 592     50/push-eax
 593     51/push-ecx
 594     52/push-edx
 595     57/push-edi
 596     # var line/ecx : (stream byte 512)
 597     81 5/subop/subtract %esp 0x200/imm32
 598     68/push 0x200/imm32/length
 599     68/push 0/imm32/read
 600     68/push 0/imm32/write
 601     89/<- %ecx 4/r32/esp
 602     # var word-slice/edx : slice
 603     68/push 0/imm32/end
 604     68/push 0/imm32/start
 605     89/<- %edx 4/r32/esp
 606     # var curr-function/edi : (address function) = Program
 607     bf/copy-to-edi Program/imm32
 608     {
 609 $parse-mu:line-loop:
 610       (clear-stream %ecx)
 611       (read-line-buffered *(ebp+8) %ecx)
 612       # if (line->write == 0) break
 613       81 7/subop/compare *ecx 0/imm32
 614       0f 84/jump-if-equal break/disp32
 615 +--  6 lines: #?       # dump line -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 621       { # word loop
 622 $parse-mu:word-loop:
 623         (next-word-or-string %ecx %edx)
 624         # if slice-empty?(word-slice) break
 625         (slice-empty? %edx)
 626         3d/compare-eax-and 0/imm32
 627         0f 85/jump-if-not-equal break/disp32
 628         # if (*word-slice->start == "#") break
 629         # . eax = *word-slice->start
 630         8b/-> *edx 0/r32/eax
 631         8a/copy-byte *eax 0/r32/AL
 632         81 4/subop/and %eax 0xff/imm32
 633         # . if (eax == '#') break
 634         3d/compare-eax-and 0x23/imm32/hash
 635         0f 84/jump-if-equal break/disp32
 636         # if (slice-equal?(word-slice, "fn")) parse a function
 637         {
 638           (slice-equal? %edx "fn")
 639           3d/compare-eax-and 0/imm32
 640           0f 84/jump-if-equal break/disp32
 641           # var new-function/eax : (address function) = populate-mu-function()
 642           (allocate Heap *Function-size)  # => eax
 643           (populate-mu-function-header %ecx %eax)
 644           (populate-mu-function-body *(ebp+8) %eax)
 645           # *curr-function = new-function
 646           89/<- *edi 0/r32/eax
 647           # curr-function = &new-function->next
 648           8d/address-> *(eax+0x14) 7/r32/edi  # Function-next
 649           e9/jump $parse-mu:word-loop/disp32
 650         }
 651         # otherwise abort
 652         e9/jump $parse-mu:abort/disp32
 653       } # end word loop
 654       e9/jump loop/disp32
 655     } # end line loop
 656 $parse-mu:end:
 657     # . reclaim locals
 658     81 0/subop/add %esp 0x214/imm32
 659     # . restore registers
 660     5f/pop-to-edi
 661     5a/pop-to-edx
 662     59/pop-to-ecx
 663     58/pop-to-eax
 664     # . epilogue
 665     89/<- %esp 5/r32/ebp
 666     5d/pop-to-ebp
 667     c3/return
 668 
 669 $parse-mu:abort:
 670     # error("unexpected top-level command: " word-slice "\n")
 671     (write-buffered Stderr "unexpected top-level command: ")
 672     (write-buffered Stderr %edx)
 673     (write-buffered Stderr "\n")
 674     (flush Stderr)
 675     # . syscall(exit, 1)
 676     bb/copy-to-ebx  1/imm32
 677     b8/copy-to-eax  1/imm32/exit
 678     cd/syscall  0x80/imm8
 679     # never gets here
 680 
 681 # errors considered:
 682 #   fn foo { {
 683 #   fn foo { }
 684 #   fn foo { } {
 685 #   fn foo  # no block
 686 populate-mu-function-header:  # first-line : (address stream byte), out : (address function)
 687     # . prologue
 688     55/push-ebp
 689     89/<- %ebp 4/r32/esp
 690     # . save registers
 691     50/push-eax
 692     51/push-ecx
 693     57/push-edi
 694     # edi = out
 695     8b/-> *(ebp+0xc) 7/r32/edi
 696     # var word-slice/ecx : slice
 697     68/push 0/imm32/end
 698     68/push 0/imm32/start
 699     89/<- %ecx 4/r32/esp
 700     # save function name
 701     (next-word *(ebp+8) %ecx)
 702     (slice-to-string Heap %ecx)  # => eax
 703     89/<- *edi 0/r32/eax
 704     # assert that next token is '{'
 705     (next-word *(ebp+8) %ecx)
 706     (slice-equal? %ecx "{")
 707     3d/compare-eax-and 0/imm32
 708     74/jump-if-equal $populate-mu-function-header:abort/disp8
 709     # assert that there's no further token
 710     {
 711       # word-slice = next-word(line)
 712       (next-word *(ebp+8) %ecx)
 713       # if (word-slice == '') break
 714       (slice-empty? %ecx)
 715       3d/compare-eax-and 0/imm32
 716       75/jump-if-not-equal break/disp8
 717       # if (slice-starts-with?(word-slice, "#")) break
 718       # . eax = *word-slice->start
 719       8b/-> *edx 0/r32/eax
 720       8a/copy-byte *eax 0/r32/AL
 721       81 4/subop/and %eax 0xff/imm32
 722       # . if (eax == '#') break
 723       3d/compare-eax-and 0x23/imm32/hash
 724       74/jump-if-equal break/disp8
 725       # otherwise abort
 726       eb/jump $populate-mu-function-header:abort/disp8
 727     }
 728 $populate-mu-function-header:end:
 729     # . reclaim locals
 730     81 0/subop/add %esp 8/imm32
 731     # . restore registers
 732     5f/pop-to-edi
 733     59/pop-to-ecx
 734     58/pop-to-eax
 735     # . epilogue
 736     89/<- %esp 5/r32/ebp
 737     5d/pop-to-ebp
 738     c3/return
 739 
 740 $populate-mu-function-header:abort:
 741     # error("function header not in form 'fn <name> {'")
 742     (write-buffered Stderr "function header not in form 'fn <name> {' -- '")
 743     (rewind-stream *(ebp+8))
 744     (write-stream 2 *(ebp+8))
 745     (write-buffered Stderr "'\n")
 746     (flush Stderr)
 747     # . syscall(exit, 1)
 748     bb/copy-to-ebx  1/imm32
 749     b8/copy-to-eax  1/imm32/exit
 750     cd/syscall  0x80/imm8
 751     # never gets here
 752 
 753 populate-mu-function-body:  # in : (address buffered-file), out : (address function)
 754     # . prologue
 755     55/push-ebp
 756     89/<- %ebp 4/r32/esp
 757     # . save registers
 758     50/push-eax
 759     56/push-esi
 760     # esi = in
 761     8b/-> *(ebp+8) 6/r32/esi
 762     # var eax : (address block) = parse-mu-block(in)
 763     (parse-mu-block %esi)
 764     # out->body = eax
 765     89/<- *(eax+0x10) 0/r32/eax  # Function-body
 766 $populate-mu-function-body:end:
 767     # . restore registers
 768     5e/pop-to-esi
 769     58/pop-to-eax
 770     # . epilogue
 771     89/<- %esp 5/r32/ebp
 772     5d/pop-to-ebp
 773     c3/return
 774 
 775 # parses a block, assuming that the leading '{' has already been read by the caller
 776 # errors considered:
 777 #   { abc
 778 parse-mu-block:  # in : (address buffered-file) -> result/eax : (address block)
 779     # . prologue
 780     55/push-ebp
 781     89/<- %ebp 4/r32/esp
 782     # . save registers
 783     50/push-eax
 784     51/push-ecx
 785     52/push-edx
 786     53/push-ebx
 787     # var line/ecx : (stream byte 512)
 788     81 5/subop/subtract %esp 0x200/imm32
 789     68/push 0x200/imm32/length
 790     68/push 0/imm32/read
 791     68/push 0/imm32/write
 792     89/<- %ecx 4/r32/esp
 793     # var word-slice/edx : slice
 794     68/push 0/imm32/end
 795     68/push 0/imm32/start
 796     89/<- %edx 4/r32/esp
 797     # var open-curly-count/ebx : int = 1
 798     bb/copy-to-ebx 1/imm32
 799     { # line loop
 800 $parse-mu-block:line-loop:
 801       # if (open-curly-count == 0) break
 802       81 7/subop/compare %ebx 0/imm32
 803       0f 84/jump-if-equal break/disp32
 804       # line = read-line-buffered(in)
 805       (clear-stream %ecx)
 806       (read-line-buffered *(ebp+8) %ecx)
 807       # if (line->write == 0) break
 808       81 7/subop/compare *ecx 0/imm32
 809       0f 84/jump-if-equal break/disp32
 810       # word-slice = next-word(line)
 811       (next-word %ecx %edx)
 812       # if slice-empty?(word-slice) continue
 813       (slice-empty? %ecx)
 814       3d/compare-eax-and 0/imm32
 815       75/jump-if-not-equal loop/disp8
 816       # if (slice-starts-with?(word-slice, '#') continue
 817       # . eax = *word-slice->start
 818       8b/-> *edx 0/r32/eax
 819       8a/copy-byte *eax 0/r32/AL
 820       81 4/subop/and %eax 0xff/imm32
 821       # . if (eax == '#') continue
 822       3d/compare-eax-and 0x23/imm32/hash
 823       74/jump-if-equal loop/disp8
 824       {
 825         # if slice-equal?(word-slice, "{") ++open-curly-count
 826         {
 827           (slice-equal? %ecx "{")
 828           3d/compare-eax-and 0/imm32
 829           74/jump-if-equal break/disp8
 830           43/increment-ebx
 831           eb/jump $curly-found:end/disp8
 832         }
 833         # else if slice-equal?(word-slice, "}") --open-curly-count
 834         {
 835           (slice-equal? %ecx "}")
 836           3d/compare-eax-and 0/imm32
 837           74/jump-if-equal break/disp8
 838           4b/decrement-ebx
 839           eb/jump $curly-found:end/disp8
 840         }
 841         # else break
 842         eb/jump $parse-mu-block:end/disp8
 843       }
 844       # - check for invalid tokens after curly
 845 $curly-found:end:
 846       # second-word-slice = next-word(line)
 847       (next-word %ecx %edx)
 848       # if slice-empty?(second-word-slice) continue
 849       (slice-empty? %ecx)
 850       3d/compare-eax-and 0/imm32
 851       0f 85/jump-if-not-equal loop/disp32
 852       # if (slice-starts-with?(second-word-slice, '#') continue
 853       # . eax = *second-word-slice->start
 854       8b/-> *edx 0/r32/eax
 855       8a/copy-byte *eax 0/r32/AL
 856       81 4/subop/and %eax 0xff/imm32
 857       # . if (eax == '#') continue
 858       3d/compare-eax-and 0x23/imm32/hash
 859       0f 84/jump-if-equal loop/disp32
 860       # abort
 861       eb/jump $parse-mu-block:abort/disp8
 862     } # end line loop
 863 $parse-mu-block:end:
 864     # . reclaim locals
 865     81 0/subop/add %esp 0x214/imm32
 866     # . restore registers
 867     5b/pop-to-ebx
 868     5a/pop-to-edx
 869     59/pop-to-ecx
 870     58/pop-to-eax
 871     # . epilogue
 872     89/<- %esp 5/r32/ebp
 873     5d/pop-to-ebp
 874     c3/return
 875 
 876 $parse-mu-block:abort:
 877     # error("'{' or '}' should be on its own line, but got '")
 878     (write-buffered Stderr "'{' or '}' should be on its own line, but got '")
 879     (rewind-stream %ecx)
 880     (write-stream 2 %ecx)
 881     (write-buffered Stderr "'\n")
 882     (flush Stderr)
 883     # . syscall(exit, 1)
 884     bb/copy-to-ebx  1/imm32
 885     b8/copy-to-eax  1/imm32/exit
 886     cd/syscall  0x80/imm8
 887     # never gets here
 888 
 889 check-mu-types:
 890     # . prologue
 891     55/push-ebp
 892     89/<- %ebp 4/r32/esp
 893     #
 894 $check-types:end:
 895     # . epilogue
 896     89/<- %esp 5/r32/ebp
 897     5d/pop-to-ebp
 898     c3/return
 899 
 900 emit-subx:  # out : (address buffered-file)
 901     # . prologue
 902     55/push-ebp
 903     89/<- %ebp 4/r32/esp
 904     # . save registers
 905     50/push-eax
 906     51/push-ecx
 907     57/push-edi
 908     # edi = out
 909     8b/-> *(ebp+8) 7/r32/edi
 910     # var curr/ecx : (address function) = Program
 911     8b/-> *Program 1/r32/ecx
 912     {
 913       # if (curr == NULL) break
 914       81 7/subop/compare %ecx 0/imm32
 915       0f 84/jump-if-equal break/disp32
 916       (emit-subx-function %edi %ecx)
 917       # curr = curr->next
 918       8b/-> *(ecx+0x14) 1/r32/ecx  # Function-next
 919       e9/jump loop/disp32
 920     }
 921 $emit-subx:end:
 922     # . restore registers
 923     5f/pop-to-edi
 924     59/pop-to-ecx
 925     58/pop-to-eax
 926     # . epilogue
 927     89/<- %esp 5/r32/ebp
 928     5d/pop-to-ebp
 929     c3/return
 930 
 931 # == Emitting a function
 932 # Emit function header
 933 # Emit function prologue
 934 # Translate function body
 935 # Emit function epilogue
 936 
 937 emit-subx-function:  # out : (address buffered-file), f : (address function)
 938     # . prologue
 939     55/push-ebp
 940     89/<- %ebp 4/r32/esp
 941     # . save registers
 942     50/push-eax
 943     51/push-ecx
 944     57/push-edi
 945     # edi = out
 946     8b/-> *(ebp+8) 7/r32/edi
 947     # ecx = f
 948     8b/-> *(ebp+0xc) 1/r32/ecx
 949     #
 950     (write-buffered %edi *ecx)
 951     (write-buffered %edi ":\n")
 952     (emit-subx-prologue %edi)
 953     (emit-subx-block %edi *(ecx+0x10))  # Function-body
 954     (emit-subx-epilogue %edi)
 955 $emit-subx-function:end:
 956     # . restore registers
 957     5f/pop-to-edi
 958     59/pop-to-ecx
 959     58/pop-to-eax
 960     # . epilogue
 961     89/<- %esp 5/r32/ebp
 962     5d/pop-to-ebp
 963     c3/return
 964 
 965 emit-subx-block:  # out : (address buffered-file), block : (address block)
 966     # . prologue
 967     55/push-ebp
 968     89/<- %ebp 4/r32/esp
 969     #
 970 $emit-subx-block:end:
 971     # . epilogue
 972     89/<- %esp 5/r32/ebp
 973     5d/pop-to-ebp
 974     c3/return
 975 
 976 emit-subx-statement:  # out : (address buffered-file), stmt : (address statement), vars : (stack var), primitives : (address primitive), functions : (address function)
 977     # . prologue
 978     55/push-ebp
 979     89/<- %ebp 4/r32/esp
 980     # . save registers
 981     50/push-eax
 982     51/push-ecx
 983     # if stmt matches a primitive, emit it
 984     {
 985 $emit-subx-statement:primitive:
 986       (find-matching-primitive *(ebp+0x14) *(ebp+0xc))  # primitives, stmt => curr/eax
 987       3d/compare-eax-and 0/imm32
 988       74/jump-if-equal break/disp8
 989       (emit-subx-primitive *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax)  # out, stmt, vars, curr
 990       e9/jump $emit-subx-statement:end/disp32
 991     }
 992     # else if stmt matches a function, emit a call to it
 993     {
 994 $emit-subx-statement:call:
 995       (find-matching-function *(ebp+0x18) *(ebp+0xc))  # functions, stmt => curr/eax
 996       3d/compare-eax-and 0/imm32
 997       74/jump-if-equal break/disp8
 998       (emit-subx-call *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax)  # out, stmt, vars, curr
 999       e9/jump $emit-subx-statement:end/disp32
1000     }
1001     # else abort
1002     e9/jump $emit-subx-statement:abort/disp32
1003 $emit-subx-statement:end:
1004     # . restore registers
1005     59/pop-to-ecx
1006     58/pop-to-eax
1007     # . epilogue
1008     89/<- %esp 5/r32/ebp
1009     5d/pop-to-ebp
1010     c3/return
1011 
1012 $emit-subx-statement:abort:
1013     # error("couldn't translate '" stmt "'\n")
1014     (write-buffered Stderr "couldn't translate '")
1015 #?     (emit-string Stderr *(ebp+0xc))  # TODO
1016     (write-buffered Stderr "'\n")
1017     (flush Stderr)
1018     # . syscall(exit, 1)
1019     bb/copy-to-ebx  1/imm32
1020     b8/copy-to-eax  1/imm32/exit
1021     cd/syscall  0x80/imm8
1022     # never gets here
1023 
1024 # Primitives supported
1025 == data
1026 Primitives:
1027     # increment var => ff 0/subop/increment *(ebp+__)
1028     "increment"/imm32/name
1029     Single-int-var-on-stack/imm32/inouts
1030     0/imm32/no-outputs
1031     "ff 0/subop/increment"/imm32/subx-name
1032     1/imm32/rm32-is-first-inout
1033     0/imm32/no-r32
1034     0/imm32/no-imm32
1035     _Primitive-inc-reg/imm32/next
1036 _Primitive-inc-reg:
1037     # var/reg <- increment => ff 0/subop/increment %__
1038     "increment"/imm32/name
1039     0/imm32/no-inouts
1040     Single-int-var-in-some-register/imm32/outputs
1041     "ff 0/subop/increment"/imm32/subx-name
1042     3/imm32/rm32-is-first-output
1043     0/imm32/no-r32
1044     0/imm32/no-imm32
1045     _Primitive-add-reg-to-reg/imm32/next
1046 _Primitive-add-reg-to-reg:
1047     # var1/reg <- add var2/reg => 01 var1/rm32 var2/r32
1048     "add"/imm32/name
1049     Single-int-var-in-some-register/imm32/inouts
1050     Single-int-var-in-some-register/imm32/outputs
1051     "01"/imm32/subx-name
1052     3/imm32/rm32-is-first-output
1053     1/imm32/r32-is-first-inout
1054     0/imm32/no-imm32
1055     _Primitive-add-reg-to-mem/imm32/next
1056 _Primitive-add-reg-to-mem:
1057     # add-to var1 var2/reg => 01 var1 var2/r32
1058     "add-to"/imm32/name
1059     Int-var-and-second-int-var-in-some-register/imm32/inouts
1060     0/imm32/outputs
1061     "01"/imm32/subx-name
1062     1/imm32/rm32-is-first-inout
1063     2/imm32/r32-is-second-inout
1064     0/imm32/no-imm32
1065     _Primitive-add-mem-to-reg/imm32/next
1066 _Primitive-add-mem-to-reg:
1067     # var1/reg <- add var2 => 03 var2/rm32 var1/r32
1068     "add"/imm32/name
1069     Single-int-var-on-stack/imm32/inouts
1070     Single-int-var-in-some-register/imm32/outputs
1071     "03"/imm32/subx-name
1072     1/imm32/rm32-is-first-inout
1073     3/imm32/r32-is-first-output
1074     0/imm32/no-imm32
1075     _Primitive-add-lit-to-reg/imm32/next
1076 _Primitive-add-lit-to-reg:
1077     # var1/reg <- add lit => 81 0/subop/add var1/rm32 lit/imm32
1078     "add"/imm32/name
1079     Single-lit-var/imm32/inouts
1080     Single-int-var-in-some-register/imm32/outputs
1081     "81 0/subop/add"/imm32/subx-name
1082     3/imm32/rm32-is-first-output
1083     0/imm32/no-r32
1084     1/imm32/imm32-is-first-inout
1085     _Primitive-add-lit-to-mem/imm32/next
1086 _Primitive-add-lit-to-mem:
1087     # add-to var1, lit => 81 0/subop/add var1/rm32 lit/imm32
1088     "add-to"/imm32/name
1089     Int-var-and-literal/imm32/inouts
1090     0/imm32/outputs
1091     "81 0/subop/add"/imm32/subx-name
1092     1/imm32/rm32-is-first-inout
1093     0/imm32/no-r32
1094     2/imm32/imm32-is-first-inout
1095     0/imm32/next
1096 
1097 Single-int-var-on-stack:
1098     Int-var-on-stack/imm32
1099     0/imm32/next
1100 
1101 Int-var-on-stack:
1102     "arg1"/imm32/name
1103     1/imm32/type-int
1104     1/imm32/some-block-depth
1105     1/imm32/some-stack-offset
1106     0/imm32/no-register
1107 
1108 Int-var-and-second-int-var-in-some-register:
1109     Int-var-on-stack/imm32
1110     Single-int-var-in-some-register/imm32/next
1111 
1112 Int-var-and-literal:
1113     Int-var-on-stack/imm32
1114     Single-lit-var/imm32/next
1115 
1116 Single-int-var-in-some-register:
1117     Int-var-in-some-register/imm32
1118     0/imm32/next
1119 
1120 Int-var-in-some-register:
1121     "arg1"/imm32/name
1122     1/imm32/type-int
1123     1/imm32/some-block-depth
1124     0/imm32/no-stack-offset
1125     "*"/imm32/register
1126 
1127 Single-lit-var:
1128     Lit-var/imm32
1129     0/imm32/next
1130 
1131 Lit-var:
1132     "literal"/imm32/name
1133     0/imm32/type-literal
1134     1/imm32/some-block-depth
1135     0/imm32/no-stack-offset
1136     0/imm32/no-register
1137 
1138 == code
1139 emit-subx-primitive:  # out : (address buffered-file), stmt : (address statement), vars : (address variable), primitive : (address function)
1140     # . prologue
1141     55/push-ebp
1142     89/<- %ebp 4/r32/esp
1143     # . save registers
1144     50/push-eax
1145     51/push-ecx
1146     # ecx = primitive
1147     8b/-> *(ebp+0x14) 1/r32/ecx
1148     # emit primitive name
1149     (write-buffered *(ebp+8) *(ecx+0xc))  # Primitive-subx-name
1150     # emit rm32 if necessary
1151     (emit-subx-rm32 *(ebp+8) *(ecx+0x10) *(ebp+0xc))  # out, Primitive-subx-rm32, stmt
1152     # emit r32 if necessary
1153     (emit-subx-r32 *(ebp+8) *(ecx+0x14) *(ebp+0xc))  # out, Primitive-subx-r32, stmt
1154     # emit imm32 if necessary
1155     (emit-subx-imm32 *(ebp+8) *(ecx+0x18) *(ebp+0xc))  # out, Primitive-subx-imm32, stmt
1156 $emit-subx-primitive:end:
1157     # . restore registers
1158     59/pop-to-ecx
1159     58/pop-to-eax
1160     # . epilogue
1161     89/<- %esp 5/r32/ebp
1162     5d/pop-to-ebp
1163     c3/return
1164 
1165 emit-subx-rm32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1166     # . prologue
1167     55/push-ebp
1168     89/<- %ebp 4/r32/esp
1169     # . save registers
1170     50/push-eax
1171     # if (l == 0) return
1172     81 7/subop/compare *(ebp+0xc) 0/imm32
1173     74/jump-if-equal $emit-subx-rm32:end/disp8
1174     #
1175     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
1176     (emit-subx-var-as-rm32 *(ebp+8) %eax)  # out, var
1177 $emit-subx-rm32:end:
1178     # . restore registers
1179     58/pop-to-eax
1180     # . epilogue
1181     89/<- %esp 5/r32/ebp
1182     5d/pop-to-ebp
1183     c3/return
1184 
1185 get-stmt-operand-from-arg-location:  # stmt : (address statement), l : arg-location -> var/eax : (address variable)
1186     # . prologue
1187     55/push-ebp
1188     89/<- %ebp 4/r32/esp
1189     # . save registers
1190     51/push-ecx
1191     # eax = l
1192     8b/-> *(ebp+0xc) 0/r32/eax
1193     # ecx = stmt
1194     8b/-> *(ebp+8) 1/r32/ecx
1195     # if (l == 1) return stmt->inouts->var
1196     {
1197       3d/compare-eax-and 1/imm32
1198       75/jump-if-not-equal break/disp8
1199 $get-stmt-operand-from-arg-location:1:
1200       8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
1201       8b/-> *eax 0/r32/eax  # Operand-var
1202       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1203     }
1204     # if (l == 2) return stmt->inouts->next->var
1205     {
1206       3d/compare-eax-and 2/imm32
1207       75/jump-if-not-equal break/disp8
1208 $get-stmt-operand-from-arg-location:2:
1209       8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
1210       8b/-> *(eax+4) 0/r32/eax  # Operand-next
1211       8b/-> *eax 0/r32/eax  # Operand-var
1212       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1213     }
1214     # if (l == 3) return stmt->outputs
1215     {
1216       3d/compare-eax-and 3/imm32
1217       75/jump-if-not-equal break/disp8
1218 $get-stmt-operand-from-arg-location:3:
1219       8b/-> *(ecx+0xc) 0/r32/eax  # Stmt1-outputs
1220       8b/-> *eax 0/r32/eax  # Operand-var
1221       eb/jump $get-stmt-operand-from-arg-location:end/disp8
1222     }
1223     # abort
1224     e9/jump $get-stmt-operand-from-arg-location:abort/disp32
1225 $get-stmt-operand-from-arg-location:end:
1226     # . restore registers
1227     59/pop-to-ecx
1228     # . epilogue
1229     89/<- %esp 5/r32/ebp
1230     5d/pop-to-ebp
1231     c3/return
1232 
1233 $get-stmt-operand-from-arg-location:abort:
1234     # error("invalid arg-location " eax)
1235     (write-buffered Stderr "invalid arg-location ")
1236     (print-int32-buffered Stderr %eax)
1237     (write-buffered Stderr "\n")
1238     (flush Stderr)
1239     # . syscall(exit, 1)
1240     bb/copy-to-ebx  1/imm32
1241     b8/copy-to-eax  1/imm32/exit
1242     cd/syscall  0x80/imm8
1243     # never gets here
1244 
1245 emit-subx-r32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1246     # . prologue
1247     55/push-ebp
1248     89/<- %ebp 4/r32/esp
1249     # . save registers
1250     50/push-eax
1251     51/push-ecx
1252     # if (location == 0) return
1253     81 7/subop/compare *(ebp+0xc) 0/imm32
1254     0f 84/jump-if-equal $emit-subx-r32:end/disp32
1255     #
1256     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
1257     (maybe-get Registers *(eax+0x10) 8)  # Var-register => eax : (address register-index)
1258     (write-buffered *(ebp+8) Space)
1259     (print-int32-buffered *(ebp+8) *eax)
1260     (write-buffered *(ebp+8) "/r32")
1261 $emit-subx-r32:end:
1262     # . restore registers
1263     59/pop-to-ecx
1264     58/pop-to-eax
1265     # . epilogue
1266     89/<- %esp 5/r32/ebp
1267     5d/pop-to-ebp
1268     c3/return
1269 
1270 emit-subx-imm32:  # out : (address buffered-file), l : arg-location, stmt : (address statement)
1271     # . prologue
1272     55/push-ebp
1273     89/<- %ebp 4/r32/esp
1274     # . save registers
1275     50/push-eax
1276     51/push-ecx
1277     # if (location == 0) return
1278     81 7/subop/compare *(ebp+0xc) 0/imm32
1279     74/jump-if-equal $emit-subx-imm32:end/disp8
1280     #
1281     (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
1282     (write-buffered *(ebp+8) Space)
1283     (write-buffered *(ebp+8) *eax)  # Var-name
1284     (write-buffered *(ebp+8) "/imm32")
1285 $emit-subx-imm32:end:
1286     # . restore registers
1287     59/pop-to-ecx
1288     58/pop-to-eax
1289     # . epilogue
1290     89/<- %esp 5/r32/ebp
1291     5d/pop-to-ebp
1292     c3/return
1293 
1294 emit-subx-call:  # out : (address buffered-file), stmt : (address statement), vars : (address variable), callee : (address function)
1295     # . prologue
1296     55/push-ebp
1297     89/<- %ebp 4/r32/esp
1298     # . save registers
1299     50/push-eax
1300     51/push-ecx
1301     #
1302     (write-buffered *(ebp+8) "(")
1303     # - emit function name
1304     8b/-> *(ebp+0x14) 1/r32/ecx
1305     (write-buffered *(ebp+8) *(ecx+4))  # Function-subx-name
1306     # - emit arguments
1307     # var curr/ecx : (list var) = stmt->inouts
1308     8b/-> *(ebp+0xc) 1/r32/ecx
1309     8b/-> *(ecx+8) 1/r32/ecx  # Stmt1-inouts
1310     {
1311       # if (curr == null) break
1312       81 7/subop/compare %ecx 0/imm32
1313       74/jump-if-equal break/disp8
1314       #
1315       (emit-subx-call-operand *(ebp+8) *ecx)
1316       # curr = curr->next
1317       8b/-> *(ecx+4) 1/r32/ecx
1318     }
1319     #
1320     (write-buffered *(ebp+8) ")")
1321 $emit-subx-call:end:
1322     # . restore registers
1323     59/pop-to-ecx
1324     58/pop-to-eax
1325     # . epilogue
1326     89/<- %esp 5/r32/ebp
1327     5d/pop-to-ebp
1328     c3/return
1329 
1330 emit-subx-call-operand:  # out : (address buffered-file), operand : (address variable)
1331     # . prologue
1332     55/push-ebp
1333     89/<- %ebp 4/r32/esp
1334     # . save registers
1335     50/push-eax
1336     # eax = operand
1337     8b/-> *(ebp+0xc) 0/r32/eax
1338     # if non-literal, emit appropriately
1339     (emit-subx-var-as-rm32 *(ebp+8) %eax)
1340     # else if (operand->type == literal) emit "__"
1341     {
1342       81 7/subop/compare *(eax+4) 0/imm32  # Var-type
1343       75/jump-if-not-equal break/disp8
1344 $emit-subx-call-operand:literal:
1345       (write-buffered *(ebp+8) Space)
1346       (write-buffered *(ebp+8) *eax)
1347     }
1348 $emit-subx-call-operand:end:
1349     # . restore registers
1350     58/pop-to-eax
1351     # . epilogue
1352     89/<- %esp 5/r32/ebp
1353     5d/pop-to-ebp
1354     c3/return
1355 
1356 emit-subx-var-as-rm32:  # out : (address buffered-file), operand : (address variable)
1357     # . prologue
1358     55/push-ebp
1359     89/<- %ebp 4/r32/esp
1360     # . save registers
1361     50/push-eax
1362     # eax = operand
1363     8b/-> *(ebp+0xc) 0/r32/eax
1364     # if (operand->register) emit "%__"
1365     {
1366       81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
1367       74/jump-if-equal break/disp8
1368 $emit-subx-var-as-rm32:register:
1369       (write-buffered *(ebp+8) " %")
1370       (write-buffered *(ebp+8) *(eax+0x10))  # Var-register
1371     }
1372     # else if (operand->stack-offset) emit "*(ebp+__)"
1373     {
1374       81 7/subop/compare *(eax+0xc) 0/imm32  # Var-stack-offset
1375       74/jump-if-equal break/disp8
1376 $emit-subx-var-as-rm32:stack:
1377       (write-buffered *(ebp+8) Space)
1378       (write-buffered *(ebp+8) "*(ebp+")
1379       8b/-> *(ebp+0xc) 0/r32/eax
1380       (print-int32-buffered *(ebp+8) *(eax+0xc))  # Var-stack-offset
1381       (write-buffered *(ebp+8) ")")
1382     }
1383 $emit-subx-var-as-rm32:end:
1384     # . restore registers
1385     58/pop-to-eax
1386     # . epilogue
1387     89/<- %esp 5/r32/ebp
1388     5d/pop-to-ebp
1389     c3/return
1390 
1391 find-matching-function:  # functions : (address function), stmt : (address statement) -> result/eax : (address function)
1392     # . prologue
1393     55/push-ebp
1394     89/<- %ebp 4/r32/esp
1395     # . save registers
1396     51/push-ecx
1397     # var curr/ecx : (address function) = functions
1398     8b/-> *(ebp+8) 1/r32/ecx
1399     {
1400       # if (curr == null) break
1401       81 7/subop/compare %ecx 0/imm32
1402       74/jump-if-equal break/disp8
1403       # if match(curr, stmt) return curr
1404       {
1405         (mu-stmt-matches-function? *(ebp+0xc) %ecx)  # => eax
1406         3d/compare-eax-and 0/imm32
1407         74/jump-if-equal break/disp8
1408         89/<- %eax 1/r32/ecx
1409         eb/jump $find-matching-function:end/disp8
1410       }
1411       # curr = curr->next
1412       8b/-> *(ecx+0x10) 1/r32/ecx  # Function-next
1413       eb/jump loop/disp8
1414     }
1415     # return null
1416     b8/copy-to-eax 0/imm32
1417 $find-matching-function:end:
1418     # . restore registers
1419     59/pop-to-ecx
1420     # . epilogue
1421     89/<- %esp 5/r32/ebp
1422     5d/pop-to-ebp
1423     c3/return
1424 
1425 find-matching-primitive:  # primitives : (address primitive), stmt : (address statement) -> result/eax : (address primitive)
1426     # . prologue
1427     55/push-ebp
1428     89/<- %ebp 4/r32/esp
1429     # . save registers
1430     51/push-ecx
1431     # var curr/ecx : (address primitive) = primitives
1432     8b/-> *(ebp+8) 1/r32/ecx
1433     {
1434 $find-matching-primitive:loop:
1435       # if (curr == null) break
1436       81 7/subop/compare %ecx 0/imm32
1437       74/jump-if-equal break/disp8
1438       # if match(curr, stmt) return curr
1439       {
1440         (mu-stmt-matches-primitive? *(ebp+0xc) %ecx)  # => eax
1441         3d/compare-eax-and 0/imm32
1442         74/jump-if-equal break/disp8
1443         89/<- %eax 1/r32/ecx
1444         eb/jump $find-matching-function:end/disp8
1445       }
1446 $find-matching-primitive:next-primitive:
1447       # curr = curr->next
1448       8b/-> *(ecx+0x1c) 1/r32/ecx  # Primitive-next
1449       eb/jump loop/disp8
1450     }
1451     # return null
1452     b8/copy-to-eax 0/imm32
1453 $find-matching-primitive:end:
1454     # . restore registers
1455     59/pop-to-ecx
1456     # . epilogue
1457     89/<- %esp 5/r32/ebp
1458     5d/pop-to-ebp
1459     c3/return
1460 
1461 mu-stmt-matches-function?:  # stmt : (address statement), function : (address opcode-info) => result/eax : boolean
1462     # . prologue
1463     55/push-ebp
1464     89/<- %ebp 4/r32/esp
1465     # . save registers
1466     51/push-ecx
1467     # return primitive->name == stmt->operation
1468     8b/-> *(ebp+8) 1/r32/ecx
1469     8b/-> *(ebp+0xc) 0/r32/eax
1470     (string-equal? *(ecx+4) *eax)  # Stmt1-operation, Primitive-name => eax
1471 $mu-stmt-matches-function?:end:
1472     # . restore registers
1473     59/pop-to-ecx
1474     # . epilogue
1475     89/<- %esp 5/r32/ebp
1476     5d/pop-to-ebp
1477     c3/return
1478 
1479 mu-stmt-matches-primitive?:  # stmt : (address statement), primitive : (address primitive) => result/eax : boolean
1480     # A mu stmt matches a primitive if the name matches, all the inout vars
1481     # match, and all the output vars match.
1482     # Vars match if types match and registers match.
1483     # In addition, a stmt output matches a primitive's output if types match
1484     # and the primitive has a wildcard register.
1485     # . prologue
1486     55/push-ebp
1487     89/<- %ebp 4/r32/esp
1488     # . save registers
1489     51/push-ecx
1490     52/push-edx
1491     53/push-ebx
1492     56/push-esi
1493     57/push-edi
1494     # ecx = stmt
1495     8b/-> *(ebp+8) 1/r32/ecx
1496     # edx = primitive
1497     8b/-> *(ebp+0xc) 2/r32/edx
1498     {
1499 $mu-stmt-matches-primitive?:check-name:
1500       # if (primitive->name != stmt->operation) return false
1501       (string-equal? *(ecx+4) *edx)  # Stmt1-operation, Primitive-name => eax
1502       3d/compare-eax-and 0/imm32
1503       75/jump-if-not-equal break/disp8
1504       b8/copy-to-eax 0/imm32
1505       e9/jump $mu-stmt-matches-primitive?:end/disp32
1506     }
1507 $mu-stmt-matches-primitive?:check-inouts:
1508     # for (curr/esi in stmt->inouts, curr2/edi in primitive->inouts)
1509     8b/-> *(ecx+8) 6/r32/esi  # Stmt1-inouts
1510     8b/-> *(edx+4) 7/r32/edi  # Primitive-inouts
1511     {
1512       # if (curr == 0) return (curr2 == 0)
1513       {
1514         81 7/subop/compare %esi 0/imm32
1515         75/jump-if-not-equal break/disp8
1516         {
1517           81 7/subop/compare %edi 0/imm32
1518           75/jump-if-not-equal break/disp8
1519           # return true
1520           b8/copy-to-eax 1/imm32
1521           e9/jump $mu-stmt-matches-primitive?:end/disp32
1522         }
1523         # return false
1524         b8/copy-to-eax 0/imm32
1525         e9/jump $mu-stmt-matches-primitive?:end/disp32
1526       }
1527       # if (curr2 == 0) return false
1528       {
1529         81 7/subop/compare %edi 0/imm32
1530         75/jump-if-not-equal break/disp8
1531         b8/copy-to-eax 0/imm32
1532         e9/jump $mu-stmt-matches-primitive?:end/disp32
1533       }
1534       # if (curr != curr2) return false
1535       {
1536         (operand-matches-primitive? *esi *edi)  # => eax
1537         3d/compare-eax-and 0/imm32
1538         75/jump-if-not-equal break/disp8
1539         b8/copy-to-eax 0/imm32
1540         e9/jump $mu-stmt-matches-primitive?:end/disp32
1541       }
1542       # curr=curr->next
1543       8b/-> *(esi+4) 6/r32/esi  # Operand-next
1544       # curr2=curr2->next
1545       8b/-> *(edi+4) 7/r32/edi  # Operand-next
1546       eb/jump loop/disp8
1547     }
1548 $mu-stmt-matches-primitive?:check-outputs:
1549     # for (curr/esi in stmt->outputs, curr2/edi in primitive->outputs)
1550     8b/-> *(ecx+0xc) 6/r32/esi  # Stmt1-outputs
1551     8b/-> *(edx+8) 7/r32/edi  # Primitive-outputs
1552     {
1553       # if (curr == 0) return (curr2 == 0)
1554       {
1555         81 7/subop/compare %esi 0/imm32
1556         75/jump-if-not-equal break/disp8
1557         {
1558           81 7/subop/compare %edi 0/imm32
1559           75/jump-if-not-equal break/disp8
1560           # return true
1561           b8/copy-to-eax 1/imm32
1562           e9/jump $mu-stmt-matches-primitive?:end/disp32
1563         }
1564         # return false
1565         b8/copy-to-eax 0/imm32
1566         e9/jump $mu-stmt-matches-primitive?:end/disp32
1567       }
1568       # if (curr2 == 0) return false
1569       {
1570         81 7/subop/compare %edi 0/imm32
1571         75/jump-if-not-equal break/disp8
1572         b8/copy-to-eax 0/imm32
1573         e9/jump $mu-stmt-matches-primitive?:end/disp32
1574       }
1575       # if (curr != curr2) return false
1576       {
1577         (operand-matches-primitive? *esi *edi)  # => eax
1578         3d/compare-eax-and 0/imm32
1579         75/jump-if-not-equal break/disp8
1580         b8/copy-to-eax 0/imm32
1581         e9/jump $mu-stmt-matches-primitive?:end/disp32
1582       }
1583       # curr=curr->next
1584       8b/-> *(ecx+4) 1/r32/ecx  # Operand-next
1585       # curr2=curr2->next
1586       8b/-> *(edx+4) 2/r32/edx  # Operand-next
1587       eb/jump loop/disp8
1588     }
1589 $mu-stmt-matches-primitive?:return-true:
1590     b8/copy-to-eax 1/imm32
1591 $mu-stmt-matches-primitive?:end:
1592     # . restore registers
1593     5f/pop-to-edi
1594     5e/pop-to-esi
1595     5b/pop-to-ebx
1596     5a/pop-to-edx
1597     59/pop-to-ecx
1598     # . epilogue
1599     89/<- %esp 5/r32/ebp
1600     5d/pop-to-ebp
1601     c3/return
1602 
1603 operand-matches-primitive?:  # var : (address var), primout-var : (address var) => result/eax : boolean
1604     # . prologue
1605     55/push-ebp
1606     89/<- %ebp 4/r32/esp
1607     # . save registers
1608     56/push-esi
1609     57/push-edi
1610     # esi = var
1611     8b/-> *(ebp+8) 6/r32/esi
1612     # edi = primout-var
1613     8b/-> *(ebp+0xc) 7/r32/edi
1614     # if (var->type != primout-var->type) return false
1615     8b/-> *(esi+4) 0/r32/eax  # Var-type
1616     39/compare *(edi+4) 0/r32/eax  # Var-type
1617     b8/copy-to-eax 0/imm32/false
1618     75/jump-if-not-equal $operand-matches-primitive?:end/disp8
1619     # return false if var->register doesn't match primout-var->register
1620     {
1621       # if addresses are equal, don't return here
1622       8b/-> *(esi+0x10) 0/r32/eax
1623       39/compare *(edi+0x10) 0/r32/eax
1624       74/jump-if-equal break/disp8
1625       # if either address is 0, return false
1626       3d/compare-eax-and 0/imm32
1627       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
1628       81 7/subop/compare *(edi+0x10) 0/imm32
1629       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
1630       # if primout-var->register is "*", return true
1631       (string-equal? *(edi+0x10) "*")  # Var-register
1632       3d/compare-eax-and 0/imm32
1633       b8/copy-to-eax 1/imm32/true
1634       75/jump-if-not-equal $operand-matches-primitive?:end/disp8
1635       # if string contents don't match, return false
1636       (string-equal? *(esi+0x10) *(edi+0x10))  # Var-register Var-register
1637       3d/compare-eax-and 0/imm32
1638       b8/copy-to-eax 0/imm32/false
1639       74/jump-if-equal $operand-matches-primitive?:end/disp8
1640     }
1641     # return true
1642     b8/copy-to-eax 1/imm32/true
1643 $operand-matches-primitive?:end:
1644     # . restore registers
1645     5f/pop-to-edi
1646     5e/pop-to-esi
1647     # . epilogue
1648     89/<- %esp 5/r32/ebp
1649     5d/pop-to-ebp
1650     c3/return
1651 
1652 test-emit-subx-statement-primitive:
1653     # Primitive operation on a variable on the stack.
1654     #   increment foo
1655     # =>
1656     #   ff 0/subop/increment *(ebp-8)
1657     #
1658     # There's a variable on the var stack as follows:
1659     #   name: 'foo'
1660     #   type: int
1661     #   stack-offset: -8
1662     #
1663     # There's a primitive with this info:
1664     #   name: 'increment'
1665     #   inouts: int/mem
1666     #   value: 'ff 0/subop/increment'
1667     #
1668     # There's nothing in functions.
1669     #
1670     # . prologue
1671     55/push-ebp
1672     89/<- %ebp 4/r32/esp
1673     # setup
1674     (clear-stream _test-output-stream)
1675     (clear-stream _test-output-buffered-file->buffer)
1676     # var-foo/ecx : var
1677     68/push 0/imm32/no-register
1678     68/push -8/imm32/stack-offset
1679     68/push 1/imm32/block-depth
1680     68/push 1/imm32/type-int
1681     68/push "foo"/imm32
1682     89/<- %ecx 4/r32/esp
1683     # vars/edx : (stack 1)
1684     51/push-ecx/var-foo
1685     68/push 1/imm32/data-length
1686     68/push 1/imm32/top
1687     89/<- %edx 4/r32/esp
1688     # operand/ebx : (list var)
1689     68/push 0/imm32/next
1690     51/push-ecx/var-foo
1691     89/<- %ebx 4/r32/esp
1692     # stmt/esi : statement
1693     68/push 0/imm32/next
1694     68/push 0/imm32/outputs
1695     53/push-ebx/operands
1696     68/push "increment"/imm32/operation
1697     68/push 1/imm32
1698     89/<- %esi 4/r32/esp
1699     # primitives/ebx : primitive
1700     68/push 0/imm32/next
1701     68/push 0/imm32/no-imm32
1702     68/push 0/imm32/no-r32
1703     68/push 1/imm32/rm32-is-first-inout
1704     68/push "ff 0/subop/increment"/imm32/subx-name
1705     68/push 0/imm32/outputs
1706     53/push-ebx/inouts  # hack; in practice we won't have the same var in function definition and call
1707     68/push "increment"/imm32/name
1708     89/<- %ebx 4/r32/esp
1709     # convert
1710     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1711     (flush _test-output-buffered-file)
1712 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1718     # check output
1719     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0xfffffff8)" "F - test-emit-subx-statement-primitive")
1720     # . epilogue
1721     89/<- %esp 5/r32/ebp
1722     5d/pop-to-ebp
1723     c3/return
1724 
1725 test-emit-subx-statement-primitive-register:
1726     # Primitive operation on a variable in a register.
1727     #   foo <- increment
1728     # =>
1729     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1730     #
1731     # There's a variable on the var stack as follows:
1732     #   name: 'foo'
1733     #   type: int
1734     #   register: 'eax'
1735     #
1736     # There's a primitive with this info:
1737     #   name: 'increment'
1738     #   out: int/reg
1739     #   value: 'ff 0/subop/increment'
1740     #
1741     # There's nothing in functions.
1742     #
1743     # . prologue
1744     55/push-ebp
1745     89/<- %ebp 4/r32/esp
1746     # setup
1747     (clear-stream _test-output-stream)
1748     (clear-stream _test-output-buffered-file->buffer)
1749     # var-foo/ecx : var in eax
1750     68/push "eax"/imm32/register
1751     68/push 0/imm32/no-stack-offset
1752     68/push 1/imm32/block-depth
1753     68/push 1/imm32/type-int
1754     68/push "foo"/imm32
1755     89/<- %ecx 4/r32/esp
1756     # vars/edx : (stack 1)
1757     51/push-ecx/var-foo
1758     68/push 1/imm32/data-length
1759     68/push 1/imm32/top
1760     89/<- %edx 4/r32/esp
1761     # operand/ebx : (list var)
1762     68/push 0/imm32/next
1763     51/push-ecx/var-foo
1764     89/<- %ebx 4/r32/esp
1765     # stmt/esi : statement
1766     68/push 0/imm32/next
1767     53/push-ebx/outputs
1768     68/push 0/imm32/inouts
1769     68/push "increment"/imm32/operation
1770     68/push 1/imm32
1771     89/<- %esi 4/r32/esp
1772     # formal-var/ebx : var in any register
1773     68/push Any-register/imm32
1774     68/push 0/imm32/no-stack-offset
1775     68/push 1/imm32/block-depth
1776     68/push 1/imm32/type-int
1777     68/push "dummy"/imm32
1778     89/<- %ebx 4/r32/esp
1779     # operand/ebx : (list var)
1780     68/push 0/imm32/next
1781     53/push-ebx/formal-var
1782     89/<- %ebx 4/r32/esp
1783     # primitives/ebx : primitive
1784     68/push 0/imm32/next
1785     68/push 0/imm32/no-imm32
1786     68/push 0/imm32/no-r32
1787     68/push 3/imm32/rm32-in-first-output
1788     68/push "ff 0/subop/increment"/imm32/subx-name
1789     53/push-ebx/outputs
1790     68/push 0/imm32/inouts
1791     68/push "increment"/imm32/name
1792     89/<- %ebx 4/r32/esp
1793     # convert
1794     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1795     (flush _test-output-buffered-file)
1796 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1802     # check output
1803     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-primitive-register")
1804     # . epilogue
1805     89/<- %esp 5/r32/ebp
1806     5d/pop-to-ebp
1807     c3/return
1808 
1809 test-emit-subx-statement-select-primitive:
1810     # Select the right primitive between overloads.
1811     #   foo <- increment
1812     # =>
1813     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1814     #
1815     # There's a variable on the var stack as follows:
1816     #   name: 'foo'
1817     #   type: int
1818     #   register: 'eax'
1819     #
1820     # There's two primitives, as follows:
1821     #   - name: 'increment'
1822     #     out: int/reg
1823     #     value: 'ff 0/subop/increment'
1824     #   - name: 'increment'
1825     #     inout: int/mem
1826     #     value: 'ff 0/subop/increment'
1827     #
1828     # There's nothing in functions.
1829     #
1830     # . prologue
1831     55/push-ebp
1832     89/<- %ebp 4/r32/esp
1833     # setup
1834     (clear-stream _test-output-stream)
1835     (clear-stream _test-output-buffered-file->buffer)
1836     # var-foo/ecx : var in eax
1837     68/push "eax"/imm32/register
1838     68/push 0/imm32/no-stack-offset
1839     68/push 1/imm32/block-depth
1840     68/push 1/imm32/type-int
1841     68/push "foo"/imm32
1842     89/<- %ecx 4/r32/esp
1843     # vars/edx : (stack 1)
1844     51/push-ecx/var-foo
1845     68/push 1/imm32/data-length
1846     68/push 1/imm32/top
1847     89/<- %edx 4/r32/esp
1848     # real-outputs/edi : (list var)
1849     68/push 0/imm32/next
1850     51/push-ecx/var-foo
1851     89/<- %edi 4/r32/esp
1852     # stmt/esi : statement
1853     68/push 0/imm32/next
1854     57/push-edi/outputs
1855     68/push 0/imm32/inouts
1856     68/push "increment"/imm32/operation
1857     68/push 1/imm32
1858     89/<- %esi 4/r32/esp
1859     # formal-var/ebx : var in any register
1860     68/push Any-register/imm32
1861     68/push 0/imm32/no-stack-offset
1862     68/push 1/imm32/block-depth
1863     68/push 1/imm32/type-int
1864     68/push "dummy"/imm32
1865     89/<- %ebx 4/r32/esp
1866     # formal-outputs/ebx : (list var)
1867     68/push 0/imm32/next
1868     53/push-ebx/formal-var
1869     89/<- %ebx 4/r32/esp
1870     # primitive1/ebx : primitive
1871     68/push 0/imm32/next
1872     68/push 0/imm32/no-imm32
1873     68/push 0/imm32/no-r32
1874     68/push 3/imm32/rm32-in-first-output
1875     68/push "ff 0/subop/increment"/imm32/subx-name
1876     53/push-ebx/outputs/formal-outputs
1877     68/push 0/imm32/inouts
1878     68/push "increment"/imm32/name
1879     89/<- %ebx 4/r32/esp
1880     # primitives/ebx : primitive
1881     53/push-ebx/next
1882     68/push 0/imm32/no-imm32
1883     68/push 0/imm32/no-r32
1884     68/push 1/imm32/rm32-is-first-inout
1885     68/push "ff 0/subop/increment"/imm32/subx-name
1886     68/push 0/imm32/outputs
1887     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
1888     68/push "increment"/imm32/name
1889     89/<- %ebx 4/r32/esp
1890     # convert
1891     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1892     (flush _test-output-buffered-file)
1893 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1899     # check output
1900     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive")
1901     # . epilogue
1902     89/<- %esp 5/r32/ebp
1903     5d/pop-to-ebp
1904     c3/return
1905 
1906 test-emit-subx-statement-select-primitive-2:
1907     # Select the right primitive between overloads.
1908     #   foo <- increment
1909     # =>
1910     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1911     #
1912     # There's a variable on the var stack as follows:
1913     #   name: 'foo'
1914     #   type: int
1915     #   register: 'eax'
1916     #
1917     # There's two primitives, as follows:
1918     #   - name: 'increment'
1919     #     out: int/reg
1920     #     value: 'ff 0/subop/increment'
1921     #   - name: 'increment'
1922     #     inout: int/mem
1923     #     value: 'ff 0/subop/increment'
1924     #
1925     # There's nothing in functions.
1926     #
1927     # . prologue
1928     55/push-ebp
1929     89/<- %ebp 4/r32/esp
1930     # setup
1931     (clear-stream _test-output-stream)
1932     (clear-stream _test-output-buffered-file->buffer)
1933     # var-foo/ecx : var in eax
1934     68/push "eax"/imm32/register
1935     68/push 0/imm32/no-stack-offset
1936     68/push 1/imm32/block-depth
1937     68/push 1/imm32/type-int
1938     68/push "foo"/imm32
1939     89/<- %ecx 4/r32/esp
1940     # vars/edx : (stack 1)
1941     51/push-ecx/var-foo
1942     68/push 1/imm32/data-length
1943     68/push 1/imm32/top
1944     89/<- %edx 4/r32/esp
1945     # inouts/edi : (list var)
1946     68/push 0/imm32/next
1947     51/push-ecx/var-foo
1948     89/<- %edi 4/r32/esp
1949     # stmt/esi : statement
1950     68/push 0/imm32/next
1951     68/push 0/imm32/outputs
1952     57/push-edi/inouts
1953     68/push "increment"/imm32/operation
1954     68/push 1/imm32
1955     89/<- %esi 4/r32/esp
1956     # formal-var/ebx : var in any register
1957     68/push Any-register/imm32
1958     68/push 0/imm32/no-stack-offset
1959     68/push 1/imm32/block-depth
1960     68/push 1/imm32/type-int
1961     68/push "dummy"/imm32
1962     89/<- %ebx 4/r32/esp
1963     # operand/ebx : (list var)
1964     68/push 0/imm32/next
1965     53/push-ebx/formal-var
1966     89/<- %ebx 4/r32/esp
1967     # primitive1/ebx : primitive
1968     68/push 0/imm32/next
1969     68/push 0/imm32/no-imm32
1970     68/push 0/imm32/no-r32
1971     68/push 3/imm32/rm32-in-first-output
1972     68/push "ff 0/subop/increment"/imm32/subx-name
1973     53/push-ebx/outputs/formal-outputs
1974     68/push 0/imm32/inouts
1975     68/push "increment"/imm32/name
1976     89/<- %ebx 4/r32/esp
1977     # primitives/ebx : primitive
1978     53/push-ebx/next
1979     68/push 0/imm32/no-imm32
1980     68/push 0/imm32/no-r32
1981     68/push 1/imm32/rm32-is-first-inout
1982     68/push "ff 0/subop/increment"/imm32/subx-name
1983     68/push 0/imm32/outputs
1984     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
1985     68/push "increment"/imm32/name
1986     89/<- %ebx 4/r32/esp
1987     # convert
1988     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1989     (flush _test-output-buffered-file)
1990 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1996     # check output
1997     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive-2")
1998     # . epilogue
1999     89/<- %esp 5/r32/ebp
2000     5d/pop-to-ebp
2001     c3/return
2002 
2003 test-increment-register:
2004     # Select the right primitive between overloads.
2005     #   foo <- increment
2006     # =>
2007     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
2008     #
2009     # There's a variable on the var stack as follows:
2010     #   name: 'foo'
2011     #   type: int
2012     #   register: 'eax'
2013     #
2014     # Primitives are the global definitions.
2015     #
2016     # There are no functions defined.
2017     #
2018     # . prologue
2019     55/push-ebp
2020     89/<- %ebp 4/r32/esp
2021     # setup
2022     (clear-stream _test-output-stream)
2023     (clear-stream _test-output-buffered-file->buffer)
2024     # var-foo/ecx : var in eax
2025     68/push "eax"/imm32/register
2026     68/push 0/imm32/no-stack-offset
2027     68/push 1/imm32/block-depth
2028     68/push 1/imm32/type-int
2029     68/push "foo"/imm32
2030     89/<- %ecx 4/r32/esp
2031     # vars/edx : (stack 1)
2032     51/push-ecx/var-foo
2033     68/push 1/imm32/data-length
2034     68/push 1/imm32/top
2035     89/<- %edx 4/r32/esp
2036     # real-outputs/edi : (list var)
2037     68/push 0/imm32/next
2038     51/push-ecx/var-foo
2039     89/<- %edi 4/r32/esp
2040     # stmt/esi : statement
2041     68/push 0/imm32/next
2042     57/push-edi/outputs
2043     68/push 0/imm32/inouts
2044     68/push "increment"/imm32/operation
2045     68/push 1/imm32
2046     89/<- %esi 4/r32/esp
2047     # convert
2048     (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 0)
2049     (flush _test-output-buffered-file)
2050 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2056     # check output
2057     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-increment-register")
2058     # . epilogue
2059     89/<- %esp 5/r32/ebp
2060     5d/pop-to-ebp
2061     c3/return
2062 
2063 test-increment-var:
2064     # Select the right primitive between overloads.
2065     #   foo <- increment
2066     # =>
2067     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
2068     #
2069     # There's a variable on the var stack as follows:
2070     #   name: 'foo'
2071     #   type: int
2072     #   register: 'eax'
2073     #
2074     # Primitives are the global definitions.
2075     #
2076     # There are no functions defined.
2077     #
2078     # . prologue
2079     55/push-ebp
2080     89/<- %ebp 4/r32/esp
2081     # setup
2082     (clear-stream _test-output-stream)
2083     (clear-stream _test-output-buffered-file->buffer)
2084     # var-foo/ecx : var in eax
2085     68/push "eax"/imm32/register
2086     68/push 0/imm32/no-stack-offset
2087     68/push 1/imm32/block-depth
2088     68/push 1/imm32/type-int
2089     68/push "foo"/imm32
2090     89/<- %ecx 4/r32/esp
2091     # vars/edx : (stack 1)
2092     51/push-ecx/var-foo
2093     68/push 1/imm32/data-length
2094     68/push 1/imm32/top
2095     89/<- %edx 4/r32/esp
2096     # inouts/edi : (list var)
2097     68/push 0/imm32/next
2098     51/push-ecx/var-foo
2099     89/<- %edi 4/r32/esp
2100     # stmt/esi : statement
2101     68/push 0/imm32/next
2102     68/push 0/imm32/outputs
2103     57/push-edi/inouts
2104     68/push "increment"/imm32/operation
2105     68/push 1/imm32
2106     89/<- %esi 4/r32/esp
2107     # convert
2108     (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 0)
2109     (flush _test-output-buffered-file)
2110 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2116     # check output
2117     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-increment-var")
2118     # . epilogue
2119     89/<- %esp 5/r32/ebp
2120     5d/pop-to-ebp
2121     c3/return
2122 
2123 test-add-reg-to-reg:
2124     #   var1/reg <- add var2/reg
2125     # =>
2126     #   01 %var1 var2
2127     #
2128     # . prologue
2129     55/push-ebp
2130     89/<- %ebp 4/r32/esp
2131     # setup
2132     (clear-stream _test-output-stream)
2133     (clear-stream _test-output-buffered-file->buffer)
2134     # var-var1/ecx : var in eax
2135     68/push "eax"/imm32/register
2136     68/push 0/imm32/no-stack-offset
2137     68/push 1/imm32/block-depth
2138     68/push 1/imm32/type-int
2139     68/push "var1"/imm32
2140     89/<- %ecx 4/r32/esp
2141     # var-var2/edx : var in ecx
2142     68/push "ecx"/imm32/register
2143     68/push 0/imm32/no-stack-offset
2144     68/push 1/imm32/block-depth
2145     68/push 1/imm32/type-int
2146     68/push "var2"/imm32
2147     89/<- %edx 4/r32/esp
2148     # inouts/esi : (list var2)
2149     68/push 0/imm32/next
2150     52/push-edx/var-var2
2151     89/<- %esi 4/r32/esp
2152     # outputs/edi : (list var1)
2153     68/push 0/imm32/next
2154     51/push-ecx/var-var1
2155     89/<- %edi 4/r32/esp
2156     # stmt/esi : statement
2157     68/push 0/imm32/next
2158     57/push-edi/outputs
2159     56/push-esi/inouts
2160     68/push "add"/imm32/operation
2161     68/push 1/imm32
2162     89/<- %esi 4/r32/esp
2163     # convert
2164     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
2165     (flush _test-output-buffered-file)
2166 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2172     # check output
2173     (check-next-stream-line-equal _test-output-stream "01 %eax 0x00000001/r32" "F - test-add-reg-to-reg")
2174     # . epilogue
2175     89/<- %esp 5/r32/ebp
2176     5d/pop-to-ebp
2177     c3/return
2178 
2179 test-add-literal-to-reg:
2180     #   var1/eax <- add 0x34
2181     # =>
2182     #   81 0/subop/add %eax 0x34/imm32
2183     #
2184     # . prologue
2185     55/push-ebp
2186     89/<- %ebp 4/r32/esp
2187     # setup
2188     (clear-stream _test-output-stream)
2189     (clear-stream _test-output-buffered-file->buffer)
2190     # var-var1/ecx : var in eax
2191     68/push "eax"/imm32/register
2192     68/push 0/imm32/no-stack-offset
2193     68/push 1/imm32/block-depth
2194     68/push 1/imm32/type-int
2195     68/push "var1"/imm32
2196     89/<- %ecx 4/r32/esp
2197     # var-var2/edx : var literal
2198     68/push 0/imm32/no-register
2199     68/push 0/imm32/no-stack-offset
2200     68/push 1/imm32/block-depth
2201     68/push 0/imm32/type-literal
2202     68/push "0x34"/imm32
2203     89/<- %edx 4/r32/esp
2204     # inouts/esi : (list var2)
2205     68/push 0/imm32/next
2206     52/push-edx/var-var2
2207     89/<- %esi 4/r32/esp
2208     # outputs/edi : (list var1)
2209     68/push 0/imm32/next
2210     51/push-ecx/var-var1
2211     89/<- %edi 4/r32/esp
2212     # stmt/esi : statement
2213     68/push 0/imm32/next
2214     57/push-edi/outputs
2215     56/push-esi/inouts
2216     68/push "add"/imm32/operation
2217     68/push 1/imm32
2218     89/<- %esi 4/r32/esp
2219     # convert
2220     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
2221     (flush _test-output-buffered-file)
2222 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2228     # check output
2229     (check-next-stream-line-equal _test-output-stream "81 0/subop/add %eax 0x34/imm32" "F - test-add-literal-to-reg")
2230     # . epilogue
2231     89/<- %esp 5/r32/ebp
2232     5d/pop-to-ebp
2233     c3/return
2234 
2235 test-add-literal-to-mem:
2236     #   add-to var1, 0x34
2237     # =>
2238     #   81 0/subop/add %eax 0x34/imm32
2239     #
2240     # . prologue
2241     55/push-ebp
2242     89/<- %ebp 4/r32/esp
2243     # setup
2244     (clear-stream _test-output-stream)
2245     (clear-stream _test-output-buffered-file->buffer)
2246     # var-var1/ecx : var
2247     68/push 0/imm32/no-register
2248     68/push 8/imm32/stack-offset
2249     68/push 1/imm32/block-depth
2250     68/push 1/imm32/type-int
2251     68/push "var1"/imm32
2252     89/<- %ecx 4/r32/esp
2253     # var-var2/edx : var literal
2254     68/push 0/imm32/no-register
2255     68/push 0/imm32/no-stack-offset
2256     68/push 1/imm32/block-depth
2257     68/push 0/imm32/type-literal
2258     68/push "0x34"/imm32
2259     89/<- %edx 4/r32/esp
2260     # inouts/esi : (list var2)
2261     68/push 0/imm32/next
2262     52/push-edx/var-var2
2263     89/<- %esi 4/r32/esp
2264     # inouts = (list var1 inouts)
2265     56/push-esi/next
2266     51/push-ecx/var-var1
2267     89/<- %esi 4/r32/esp
2268     # stmt/esi : statement
2269     68/push 0/imm32/next
2270     68/push 0/imm32/outputs
2271     56/push-esi/inouts
2272     68/push "add-to"/imm32/operation
2273     68/push 1/imm32
2274     89/<- %esi 4/r32/esp
2275     # convert
2276     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
2277     (flush _test-output-buffered-file)
2278 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2284     # check output
2285     (check-next-stream-line-equal _test-output-stream "81 0/subop/add *(ebp+0x00000008) 0x34/imm32" "F - test-add-literal-to-mem")
2286     # . epilogue
2287     89/<- %esp 5/r32/ebp
2288     5d/pop-to-ebp
2289     c3/return
2290 
2291 test-emit-subx-statement-function-call:
2292     # Call a function on a variable on the stack.
2293     #   f foo
2294     # =>
2295     #   (f2 *(ebp-8))
2296     # (Changing the function name supports overloading in general, but here it
2297     # just serves to help disambiguate things.)
2298     #
2299     # There's a variable on the var stack as follows:
2300     #   name: 'foo'
2301     #   type: int
2302     #   stack-offset: -8
2303     #
2304     # There's nothing in primitives.
2305     #
2306     # There's a function with this info:
2307     #   name: 'f'
2308     #   inout: int/mem
2309     #   value: 'f2'
2310     #
2311     # . prologue
2312     55/push-ebp
2313     89/<- %ebp 4/r32/esp
2314     # setup
2315     (clear-stream _test-output-stream)
2316     (clear-stream _test-output-buffered-file->buffer)
2317     # var-foo/ecx : var
2318     68/push 0/imm32/no-register
2319     68/push -8/imm32/stack-offset
2320     68/push 0/imm32/block-depth
2321     68/push 1/imm32/type-int
2322     68/push "foo"/imm32
2323     89/<- %ecx 4/r32/esp
2324     # vars/edx = (stack 1)
2325     51/push-ecx/var-foo
2326     68/push 1/imm32/data-length
2327     68/push 1/imm32/top
2328     89/<- %edx 4/r32/esp
2329     # operands/esi : (list var)
2330     68/push 0/imm32/next
2331     51/push-ecx/var-foo
2332     89/<- %esi 4/r32/esp
2333     # stmt/esi : statement
2334     68/push 0/imm32/next
2335     68/push 0/imm32/outputs
2336     56/push-esi/inouts
2337     68/push "f"/imm32/operation
2338     68/push 1/imm32
2339     89/<- %esi 4/r32/esp
2340     # functions/ebx : function
2341     68/push 0/imm32/next
2342     68/push 0/imm32/body
2343     68/push 0/imm32/outputs
2344     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
2345     68/push "f2"/imm32/subx-name
2346     68/push "f"/imm32/name
2347     89/<- %ebx 4/r32/esp
2348     # convert
2349     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
2350     (flush _test-output-buffered-file)
2351 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2357     # check output
2358     (check-next-stream-line-equal _test-output-stream "(f2 *(ebp+0xfffffff8))" "F - test-emit-subx-statement-function-call")
2359     # . epilogue
2360     89/<- %esp 5/r32/ebp
2361     5d/pop-to-ebp
2362     c3/return
2363 
2364 test-emit-subx-statement-function-call-with-literal-arg:
2365     # Call a function on a literal.
2366     #   f 34
2367     # =>
2368     #   (f2 34)
2369     #
2370     # . prologue
2371     55/push-ebp
2372     89/<- %ebp 4/r32/esp
2373     # setup
2374     (clear-stream _test-output-stream)
2375     (clear-stream _test-output-buffered-file->buffer)
2376     # var-foo/ecx : literal
2377     68/push 0/imm32/no-register
2378     68/push 0/imm32/no-stack-offset
2379     68/push 0/imm32/block-depth
2380     68/push 0/imm32/type-literal
2381     68/push "34"/imm32
2382     89/<- %ecx 4/r32/esp
2383     # vars/edx = (stack 1)
2384     51/push-ecx/var-foo
2385     68/push 1/imm32/data-length
2386     68/push 1/imm32/top
2387     89/<- %edx 4/r32/esp
2388     # operands/esi : (list var)
2389     68/push 0/imm32/next
2390     51/push-ecx/var-foo
2391     89/<- %esi 4/r32/esp
2392     # stmt/esi : statement
2393     68/push 0/imm32/next
2394     68/push 0/imm32/outputs
2395     56/push-esi/inouts
2396     68/push "f"/imm32/operation
2397     68/push 1/imm32
2398     89/<- %esi 4/r32/esp
2399     # functions/ebx : function
2400     68/push 0/imm32/next
2401     68/push 0/imm32/body
2402     68/push 0/imm32/outputs
2403     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
2404     68/push "f2"/imm32/subx-name
2405     68/push "f"/imm32/name
2406     89/<- %ebx 4/r32/esp
2407     # convert
2408     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
2409     (flush _test-output-buffered-file)
2410 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2416     # check output
2417     (check-next-stream-line-equal _test-output-stream "(f2 34)" "F - test-emit-subx-statement-function-call-with-literal-arg")
2418     # . epilogue
2419     89/<- %esp 5/r32/ebp
2420     5d/pop-to-ebp
2421     c3/return
2422 
2423 emit-subx-prologue:  # out : (address buffered-file)
2424     # . prologue
2425     55/push-ebp
2426     89/<- %ebp 4/r32/esp
2427     #
2428     (write-buffered *(ebp+8) "# . prologue\n")
2429     (write-buffered *(ebp+8) "55/push-ebp\n")
2430     (write-buffered *(ebp+8) "89/<- %ebp 4/r32/esp\n")
2431 $emit-subx-prologue:end:
2432     # . epilogue
2433     89/<- %esp 5/r32/ebp
2434     5d/pop-to-ebp
2435     c3/return
2436 
2437 emit-subx-epilogue:  # out : (address buffered-file)
2438     # . prologue
2439     55/push-ebp
2440     89/<- %ebp 4/r32/esp
2441     #
2442     (write-buffered *(ebp+8) "# . epilogue\n")
2443     (write-buffered *(ebp+8) "89/<- %esp 5/r32/ebp\n")
2444     (write-buffered *(ebp+8) "5d/pop-to-ebp\n")
2445     (write-buffered *(ebp+8) "c3/return\n")
2446 $emit-subx-epilogue:end:
2447     # . epilogue
2448     89/<- %esp 5/r32/ebp
2449     5d/pop-to-ebp
2450     c3/return