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"/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     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     # curr = stmt->inouts
1509     8b/-> *(ecx+8) 6/r32/esi  # Stmt1-inouts
1510     # curr2 = primitive->inouts
1511     8b/-> *(edx+4) 7/r32/edi  # Primitive-inouts
1512     {
1513       # if (curr == 0) return (curr2 == 0)
1514       {
1515         81 7/subop/compare %esi 0/imm32
1516         75/jump-if-not-equal break/disp8
1517         {
1518           81 7/subop/compare %edi 0/imm32
1519           75/jump-if-not-equal break/disp8
1520           # return true
1521           b8/copy-to-eax 1/imm32
1522           e9/jump $mu-stmt-matches-primitive?:end/disp32
1523         }
1524         # return false
1525         b8/copy-to-eax 0/imm32
1526         e9/jump $mu-stmt-matches-primitive?:end/disp32
1527       }
1528       # if (curr2 == 0) return false
1529       {
1530         81 7/subop/compare %edi 0/imm32
1531         75/jump-if-not-equal break/disp8
1532         b8/copy-to-eax 0/imm32
1533         e9/jump $mu-stmt-matches-primitive?:end/disp32
1534       }
1535       # if (curr != curr2) return false
1536       {
1537         (operand-matches-primitive? *esi *edi)  # => eax
1538         3d/compare-eax-and 0/imm32
1539         75/jump-if-not-equal break/disp8
1540         b8/copy-to-eax 0/imm32
1541         e9/jump $mu-stmt-matches-primitive?:end/disp32
1542       }
1543       # curr=curr->next
1544       8b/-> *(ecx+4) 1/r32/ecx  # Operand-next
1545       # curr2=curr2->next
1546       8b/-> *(edx+4) 2/r32/edx  # Operand-next
1547     }
1548 $mu-stmt-matches-primitive?:check-outputs:
1549     # ecx = stmt
1550     8b/-> *(ebp+8) 1/r32/ecx
1551     # edx = primitive
1552     8b/-> *(ebp+0xc) 2/r32/edx
1553     # curr = stmt->outputs
1554     8b/-> *(ecx+0xc) 6/r32/esi  # Stmt1-outputs
1555     # curr2 = primitive->outputs
1556     8b/-> *(edx+8) 7/r32/edi  # Primitive-outputs
1557     {
1558       # if (curr == 0) return (curr2 == 0)
1559       {
1560         81 7/subop/compare %esi 0/imm32
1561         75/jump-if-not-equal break/disp8
1562         {
1563           81 7/subop/compare %edi 0/imm32
1564           75/jump-if-not-equal break/disp8
1565           # return true
1566           b8/copy-to-eax 1/imm32
1567           e9/jump $mu-stmt-matches-primitive?:end/disp32
1568         }
1569         # return false
1570         b8/copy-to-eax 0/imm32
1571         e9/jump $mu-stmt-matches-primitive?:end/disp32
1572       }
1573       # if (curr2 == 0) return false
1574       {
1575         81 7/subop/compare %edi 0/imm32
1576         75/jump-if-not-equal break/disp8
1577         b8/copy-to-eax 0/imm32
1578         e9/jump $mu-stmt-matches-primitive?:end/disp32
1579       }
1580       # if (curr != curr2) return false
1581       {
1582         (operand-matches-primitive? *esi *edi)  # => eax
1583         3d/compare-eax-and 0/imm32
1584         75/jump-if-not-equal break/disp8
1585         b8/copy-to-eax 0/imm32
1586         e9/jump $mu-stmt-matches-primitive?:end/disp32
1587       }
1588       # curr=curr->next
1589       8b/-> *(ecx+4) 1/r32/ecx  # Operand-next
1590       # curr2=curr2->next
1591       8b/-> *(edx+4) 2/r32/edx  # Operand-next
1592     }
1593 $mu-stmt-matches-primitive?:return-true:
1594     b8/copy-to-eax 1/imm32
1595 $mu-stmt-matches-primitive?:end:
1596     # . restore registers
1597     5f/pop-to-edi
1598     5e/pop-to-esi
1599     5b/pop-to-ebx
1600     5a/pop-to-edx
1601     59/pop-to-ecx
1602     # . epilogue
1603     89/<- %esp 5/r32/ebp
1604     5d/pop-to-ebp
1605     c3/return
1606 
1607 operand-matches-primitive?:  # var : (address var), primout-var : (address var) => result/eax : boolean
1608     # . prologue
1609     55/push-ebp
1610     89/<- %ebp 4/r32/esp
1611     # . save registers
1612     56/push-esi
1613     57/push-edi
1614     # esi = var
1615     8b/-> *(ebp+8) 6/r32/esi
1616     # edi = primout-var
1617     8b/-> *(ebp+0xc) 7/r32/edi
1618     # if (var->type != primout-var->type) return false
1619     8b/-> *(esi+4) 0/r32/eax  # Var-type
1620     39/compare *(edi+4) 0/r32/eax  # Var-type
1621     b8/copy-to-eax 0/imm32/false
1622     75/jump-if-not-equal $operand-matches-primitive?:end/disp8
1623     # return false if var->register doesn't match primout-var->register
1624     {
1625       # if addresses are equal, don't return here
1626       8b/-> *(esi+0x10) 0/r32/eax
1627       39/compare *(edi+0x10) 0/r32/eax
1628       74/jump-if-equal break/disp8
1629       # if either address is 0, return false
1630       3d/compare-eax-and 0/imm32
1631       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
1632       81 7/subop/compare *(edi+0x10) 0/imm32
1633       74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
1634       # if primout-var->register is "*", return true
1635       (string-equal? *(edi+0x10) "*")  # Var-register
1636       3d/compare-eax-and 0/imm32
1637       b8/copy-to-eax 1/imm32/true
1638       75/jump-if-not-equal $operand-matches-primitive?:end/disp8
1639       # if string contents don't match, return false
1640       (string-equal? *(esi+0x10) *(edi+0x10))  # Var-register Var-register
1641       3d/compare-eax-and 0/imm32
1642       b8/copy-to-eax 0/imm32/false
1643       74/jump-if-equal $operand-matches-primitive?:end/disp8
1644     }
1645     # return true
1646     b8/copy-to-eax 1/imm32/true
1647 $operand-matches-primitive?:end:
1648     # . restore registers
1649     5f/pop-to-edi
1650     5e/pop-to-esi
1651     # . epilogue
1652     89/<- %esp 5/r32/ebp
1653     5d/pop-to-ebp
1654     c3/return
1655 
1656 test-emit-subx-statement-primitive:
1657     # Primitive operation on a variable on the stack.
1658     #   increment foo
1659     # =>
1660     #   ff 0/subop/increment *(ebp-8)
1661     #
1662     # There's a variable on the var stack as follows:
1663     #   name: 'foo'
1664     #   type: int
1665     #   stack-offset: -8
1666     #
1667     # There's a primitive with this info:
1668     #   name: 'increment'
1669     #   inouts: int/mem
1670     #   value: 'ff 0/subop/increment'
1671     #
1672     # There's nothing in functions.
1673     #
1674     # . prologue
1675     55/push-ebp
1676     89/<- %ebp 4/r32/esp
1677     # setup
1678     (clear-stream _test-output-stream)
1679     (clear-stream _test-output-buffered-file->buffer)
1680     # var-foo/ecx : var
1681     68/push 0/imm32/no-register
1682     68/push -8/imm32/stack-offset
1683     68/push 1/imm32/block-depth
1684     68/push 1/imm32/type-int
1685     68/push "foo"/imm32
1686     89/<- %ecx 4/r32/esp
1687 #? $aa-var-in-ecx:
1688     # vars/edx : (stack 1)
1689     51/push-ecx/var-foo
1690     68/push 1/imm32/data-length
1691     68/push 1/imm32/top
1692     89/<- %edx 4/r32/esp
1693 #? $aa-vars-in-edx:
1694     # operand/ebx : (list var)
1695     68/push 0/imm32/next
1696     51/push-ecx/var-foo
1697     89/<- %ebx 4/r32/esp
1698 #? $aa-stmt-operand-in-ebx:
1699     # stmt/esi : statement
1700     68/push 0/imm32/next
1701     68/push 0/imm32/outputs
1702     53/push-ebx/operands
1703     68/push "increment"/imm32/operation
1704     68/push 1/imm32
1705     89/<- %esi 4/r32/esp
1706 #? $aa-stmt-in-esi:
1707     # primitives/ebx : primitive
1708     68/push 0/imm32/next
1709     68/push 0/imm32/no-imm32
1710     68/push 0/imm32/no-r32
1711     68/push 1/imm32/rm32-is-first-inout
1712     68/push "ff 0/subop/increment"/imm32/subx-name
1713     68/push 0/imm32/outputs
1714     53/push-ebx/inouts  # hack; in practice we won't have the same var in function definition and call
1715     68/push "increment"/imm32/name
1716     89/<- %ebx 4/r32/esp
1717 $aa-primitive-in-ebx:
1718     # convert
1719     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1720     (flush _test-output-buffered-file)
1721 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1727     # check output
1728     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0xfffffff8)" "F - test-emit-subx-statement-primitive")
1729     # . epilogue
1730     89/<- %esp 5/r32/ebp
1731     5d/pop-to-ebp
1732     c3/return
1733 
1734 test-emit-subx-statement-primitive-register:
1735     # Primitive operation on a variable in a register.
1736     #   foo <- increment
1737     # =>
1738     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1739     #
1740     # There's a variable on the var stack as follows:
1741     #   name: 'foo'
1742     #   type: int
1743     #   register: 'eax'
1744     #
1745     # There's a primitive with this info:
1746     #   name: 'increment'
1747     #   out: int/reg
1748     #   value: 'ff 0/subop/increment'
1749     #
1750     # There's nothing in functions.
1751     #
1752     # . prologue
1753     55/push-ebp
1754     89/<- %ebp 4/r32/esp
1755     # setup
1756     (clear-stream _test-output-stream)
1757     (clear-stream _test-output-buffered-file->buffer)
1758     # var-foo/ecx : var in eax
1759     68/push "eax"/imm32/register
1760     68/push 0/imm32/no-stack-offset
1761     68/push 1/imm32/block-depth
1762     68/push 1/imm32/type-int
1763     68/push "foo"/imm32
1764     89/<- %ecx 4/r32/esp
1765     # vars/edx : (stack 1)
1766     51/push-ecx/var-foo
1767     68/push 1/imm32/data-length
1768     68/push 1/imm32/top
1769     89/<- %edx 4/r32/esp
1770     # operand/ebx : (list var)
1771     68/push 0/imm32/next
1772     51/push-ecx/var-foo
1773     89/<- %ebx 4/r32/esp
1774     # stmt/esi : statement
1775     68/push 0/imm32/next
1776     53/push-ebx/outputs
1777     68/push 0/imm32/inouts
1778     68/push "increment"/imm32/operation
1779     68/push 1/imm32
1780     89/<- %esi 4/r32/esp
1781     # formal-var/ebx : var in any register
1782     68/push Any-register/imm32
1783     68/push 0/imm32/no-stack-offset
1784     68/push 1/imm32/block-depth
1785     68/push 1/imm32/type-int
1786     68/push "dummy"/imm32
1787     89/<- %ebx 4/r32/esp
1788     # operand/ebx : (list var)
1789     68/push 0/imm32/next
1790     53/push-ebx/formal-var
1791     89/<- %ebx 4/r32/esp
1792     # primitives/ebx : primitive
1793     68/push 0/imm32/next
1794     68/push 0/imm32/no-imm32
1795     68/push 0/imm32/no-r32
1796     68/push 3/imm32/rm32-in-first-output
1797     68/push "ff 0/subop/increment"/imm32/subx-name
1798     53/push-ebx/outputs
1799     68/push 0/imm32/inouts
1800     68/push "increment"/imm32/name
1801     89/<- %ebx 4/r32/esp
1802     # convert
1803     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1804     (flush _test-output-buffered-file)
1805 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1811     # check output
1812     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-primitive-register")
1813     # . epilogue
1814     89/<- %esp 5/r32/ebp
1815     5d/pop-to-ebp
1816     c3/return
1817 
1818 test-emit-subx-statement-select-primitive:
1819     # Select the right primitive between overloads.
1820     #   foo <- increment
1821     # =>
1822     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1823     #
1824     # There's a variable on the var stack as follows:
1825     #   name: 'foo'
1826     #   type: int
1827     #   register: 'eax'
1828     #
1829     # There's two primitives, as follows:
1830     #   - name: 'increment'
1831     #     out: int/reg
1832     #     value: 'ff 0/subop/increment'
1833     #   - name: 'increment'
1834     #     inout: int/mem
1835     #     value: 'ff 0/subop/increment'
1836     #
1837     # There's nothing in functions.
1838     #
1839     # . prologue
1840     55/push-ebp
1841     89/<- %ebp 4/r32/esp
1842     # setup
1843     (clear-stream _test-output-stream)
1844     (clear-stream _test-output-buffered-file->buffer)
1845     # var-foo/ecx : var in eax
1846     68/push "eax"/imm32/register
1847     68/push 0/imm32/no-stack-offset
1848     68/push 1/imm32/block-depth
1849     68/push 1/imm32/type-int
1850     68/push "foo"/imm32
1851     89/<- %ecx 4/r32/esp
1852     # vars/edx : (stack 1)
1853     51/push-ecx/var-foo
1854     68/push 1/imm32/data-length
1855     68/push 1/imm32/top
1856     89/<- %edx 4/r32/esp
1857     # real-outputs/edi : (list var)
1858     68/push 0/imm32/next
1859     51/push-ecx/var-foo
1860     89/<- %edi 4/r32/esp
1861     # stmt/esi : statement
1862     68/push 0/imm32/next
1863     57/push-edi/outputs
1864     68/push 0/imm32/inouts
1865     68/push "increment"/imm32/operation
1866     68/push 1/imm32
1867     89/<- %esi 4/r32/esp
1868     # formal-var/ebx : var in any register
1869     68/push Any-register/imm32
1870     68/push 0/imm32/no-stack-offset
1871     68/push 1/imm32/block-depth
1872     68/push 1/imm32/type-int
1873     68/push "dummy"/imm32
1874     89/<- %ebx 4/r32/esp
1875     # formal-outputs/ebx : (list var)
1876     68/push 0/imm32/next
1877     53/push-ebx/formal-var
1878     89/<- %ebx 4/r32/esp
1879     # primitive1/ebx : primitive
1880     68/push 0/imm32/next
1881     68/push 0/imm32/no-imm32
1882     68/push 0/imm32/no-r32
1883     68/push 3/imm32/rm32-in-first-output
1884     68/push "ff 0/subop/increment"/imm32/subx-name
1885     53/push-ebx/outputs/formal-outputs
1886     68/push 0/imm32/inouts
1887     68/push "increment"/imm32/name
1888     89/<- %ebx 4/r32/esp
1889     # primitives/ebx : primitive
1890     53/push-ebx/next
1891     68/push 0/imm32/no-imm32
1892     68/push 0/imm32/no-r32
1893     68/push 1/imm32/rm32-is-first-inout
1894     68/push "ff 0/subop/increment"/imm32/subx-name
1895     68/push 0/imm32/outputs
1896     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
1897     68/push "increment"/imm32/name
1898     89/<- %ebx 4/r32/esp
1899     # convert
1900     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1901     (flush _test-output-buffered-file)
1902 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1908     # check output
1909     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive")
1910     # . epilogue
1911     89/<- %esp 5/r32/ebp
1912     5d/pop-to-ebp
1913     c3/return
1914 
1915 test-emit-subx-statement-select-primitive-2:
1916     # Select the right primitive between overloads.
1917     #   foo <- increment
1918     # =>
1919     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
1920     #
1921     # There's a variable on the var stack as follows:
1922     #   name: 'foo'
1923     #   type: int
1924     #   register: 'eax'
1925     #
1926     # There's two primitives, as follows:
1927     #   - name: 'increment'
1928     #     out: int/reg
1929     #     value: 'ff 0/subop/increment'
1930     #   - name: 'increment'
1931     #     inout: int/mem
1932     #     value: 'ff 0/subop/increment'
1933     #
1934     # There's nothing in functions.
1935     #
1936     # . prologue
1937     55/push-ebp
1938     89/<- %ebp 4/r32/esp
1939     # setup
1940     (clear-stream _test-output-stream)
1941     (clear-stream _test-output-buffered-file->buffer)
1942     # var-foo/ecx : var in eax
1943     68/push "eax"/imm32/register
1944     68/push 0/imm32/no-stack-offset
1945     68/push 1/imm32/block-depth
1946     68/push 1/imm32/type-int
1947     68/push "foo"/imm32
1948     89/<- %ecx 4/r32/esp
1949     # vars/edx : (stack 1)
1950     51/push-ecx/var-foo
1951     68/push 1/imm32/data-length
1952     68/push 1/imm32/top
1953     89/<- %edx 4/r32/esp
1954     # inouts/edi : (list var)
1955     68/push 0/imm32/next
1956     51/push-ecx/var-foo
1957     89/<- %edi 4/r32/esp
1958     # stmt/esi : statement
1959     68/push 0/imm32/next
1960     68/push 0/imm32/outputs
1961     57/push-edi/inouts
1962     68/push "increment"/imm32/operation
1963     68/push 1/imm32
1964     89/<- %esi 4/r32/esp
1965     # formal-var/ebx : var in any register
1966     68/push Any-register/imm32
1967     68/push 0/imm32/no-stack-offset
1968     68/push 1/imm32/block-depth
1969     68/push 1/imm32/type-int
1970     68/push "dummy"/imm32
1971     89/<- %ebx 4/r32/esp
1972     # operand/ebx : (list var)
1973     68/push 0/imm32/next
1974     53/push-ebx/formal-var
1975     89/<- %ebx 4/r32/esp
1976     # primitive1/ebx : primitive
1977     68/push 0/imm32/next
1978     68/push 0/imm32/no-imm32
1979     68/push 0/imm32/no-r32
1980     68/push 3/imm32/rm32-in-first-output
1981     68/push "ff 0/subop/increment"/imm32/subx-name
1982     53/push-ebx/outputs/formal-outputs
1983     68/push 0/imm32/inouts
1984     68/push "increment"/imm32/name
1985     89/<- %ebx 4/r32/esp
1986     # primitives/ebx : primitive
1987     53/push-ebx/next
1988     68/push 0/imm32/no-imm32
1989     68/push 0/imm32/no-r32
1990     68/push 1/imm32/rm32-is-first-inout
1991     68/push "ff 0/subop/increment"/imm32/subx-name
1992     68/push 0/imm32/outputs
1993     57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
1994     68/push "increment"/imm32/name
1995     89/<- %ebx 4/r32/esp
1996     # convert
1997     (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 0)
1998     (flush _test-output-buffered-file)
1999 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2005     # check output
2006     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive-2")
2007     # . epilogue
2008     89/<- %esp 5/r32/ebp
2009     5d/pop-to-ebp
2010     c3/return
2011 
2012 test-increment-register:
2013     # Select the right primitive between overloads.
2014     #   foo <- increment
2015     # =>
2016     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
2017     #
2018     # There's a variable on the var stack as follows:
2019     #   name: 'foo'
2020     #   type: int
2021     #   register: 'eax'
2022     #
2023     # Primitives are the global definitions.
2024     #
2025     # There are no functions defined.
2026     #
2027     # . prologue
2028     55/push-ebp
2029     89/<- %ebp 4/r32/esp
2030     # setup
2031     (clear-stream _test-output-stream)
2032     (clear-stream _test-output-buffered-file->buffer)
2033     # var-foo/ecx : var in eax
2034     68/push "eax"/imm32/register
2035     68/push 0/imm32/no-stack-offset
2036     68/push 1/imm32/block-depth
2037     68/push 1/imm32/type-int
2038     68/push "foo"/imm32
2039     89/<- %ecx 4/r32/esp
2040     # vars/edx : (stack 1)
2041     51/push-ecx/var-foo
2042     68/push 1/imm32/data-length
2043     68/push 1/imm32/top
2044     89/<- %edx 4/r32/esp
2045     # real-outputs/edi : (list var)
2046     68/push 0/imm32/next
2047     51/push-ecx/var-foo
2048     89/<- %edi 4/r32/esp
2049     # stmt/esi : statement
2050     68/push 0/imm32/next
2051     57/push-edi/outputs
2052     68/push 0/imm32/inouts
2053     68/push "increment"/imm32/operation
2054     68/push 1/imm32
2055     89/<- %esi 4/r32/esp
2056     # convert
2057     (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 0)
2058     (flush _test-output-buffered-file)
2059 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2065     # check output
2066     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-increment-register")
2067     # . epilogue
2068     89/<- %esp 5/r32/ebp
2069     5d/pop-to-ebp
2070     c3/return
2071 
2072 test-increment-var:
2073     # Select the right primitive between overloads.
2074     #   foo <- increment
2075     # =>
2076     #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
2077     #
2078     # There's a variable on the var stack as follows:
2079     #   name: 'foo'
2080     #   type: int
2081     #   register: 'eax'
2082     #
2083     # Primitives are the global definitions.
2084     #
2085     # There are no functions defined.
2086     #
2087     # . prologue
2088     55/push-ebp
2089     89/<- %ebp 4/r32/esp
2090     # setup
2091     (clear-stream _test-output-stream)
2092     (clear-stream _test-output-buffered-file->buffer)
2093     # var-foo/ecx : var in eax
2094     68/push "eax"/imm32/register
2095     68/push 0/imm32/no-stack-offset
2096     68/push 1/imm32/block-depth
2097     68/push 1/imm32/type-int
2098     68/push "foo"/imm32
2099     89/<- %ecx 4/r32/esp
2100     # vars/edx : (stack 1)
2101     51/push-ecx/var-foo
2102     68/push 1/imm32/data-length
2103     68/push 1/imm32/top
2104     89/<- %edx 4/r32/esp
2105     # inouts/edi : (list var)
2106     68/push 0/imm32/next
2107     51/push-ecx/var-foo
2108     89/<- %edi 4/r32/esp
2109     # stmt/esi : statement
2110     68/push 0/imm32/next
2111     68/push 0/imm32/outputs
2112     57/push-edi/inouts
2113     68/push "increment"/imm32/operation
2114     68/push 1/imm32
2115     89/<- %esi 4/r32/esp
2116     # convert
2117     (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 0)
2118     (flush _test-output-buffered-file)
2119 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2125     # check output
2126     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-increment-var")
2127     # . epilogue
2128     89/<- %esp 5/r32/ebp
2129     5d/pop-to-ebp
2130     c3/return
2131 
2132 test-add-reg-to-reg:
2133     #   var1/reg <- add var2/reg
2134     # =>
2135     #   01 %var1 var2
2136     #
2137     # . prologue
2138     55/push-ebp
2139     89/<- %ebp 4/r32/esp
2140     # setup
2141     (clear-stream _test-output-stream)
2142     (clear-stream _test-output-buffered-file->buffer)
2143     # var-var1/ecx : var in eax
2144     68/push "eax"/imm32/register
2145     68/push 0/imm32/no-stack-offset
2146     68/push 1/imm32/block-depth
2147     68/push 1/imm32/type-int
2148     68/push "var1"/imm32
2149     89/<- %ecx 4/r32/esp
2150     # var-var2/edx : var in ecx
2151     68/push "ecx"/imm32/register
2152     68/push 0/imm32/no-stack-offset
2153     68/push 1/imm32/block-depth
2154     68/push 1/imm32/type-int
2155     68/push "var2"/imm32
2156     89/<- %edx 4/r32/esp
2157     # inouts/esi : (list var2)
2158     68/push 0/imm32/next
2159     52/push-edx/var-var2
2160     89/<- %esi 4/r32/esp
2161     # outputs/edi : (list var1)
2162     68/push 0/imm32/next
2163     51/push-ecx/var-var1
2164     89/<- %edi 4/r32/esp
2165     # stmt/esi : statement
2166     68/push 0/imm32/next
2167     57/push-edi/outputs
2168     56/push-esi/inouts
2169     68/push "add"/imm32/operation
2170     68/push 1/imm32
2171     89/<- %esi 4/r32/esp
2172     # convert
2173     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
2174     (flush _test-output-buffered-file)
2175 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2181     # check output
2182     (check-next-stream-line-equal _test-output-stream "01 %eax 0x00000001/r32" "F - test-add-reg-to-reg")
2183     # . epilogue
2184     89/<- %esp 5/r32/ebp
2185     5d/pop-to-ebp
2186     c3/return
2187 
2188 test-add-literal-to-reg:
2189     #   var1/eax <- add 0x34
2190     # =>
2191     #   81 0/subop/add %eax 0x34/imm32
2192     #
2193     # . prologue
2194     55/push-ebp
2195     89/<- %ebp 4/r32/esp
2196     # setup
2197     (clear-stream _test-output-stream)
2198     (clear-stream _test-output-buffered-file->buffer)
2199     # var-var1/ecx : var in eax
2200     68/push "eax"/imm32/register
2201     68/push 0/imm32/no-stack-offset
2202     68/push 1/imm32/block-depth
2203     68/push 1/imm32/type-int
2204     68/push "var1"/imm32
2205     89/<- %ecx 4/r32/esp
2206     # var-var2/edx : var literal
2207     68/push 0/imm32/no-register
2208     68/push 0/imm32/no-stack-offset
2209     68/push 1/imm32/block-depth
2210     68/push 0/imm32/type-literal
2211     68/push "0x34"/imm32
2212     89/<- %edx 4/r32/esp
2213     # inouts/esi : (list var2)
2214     68/push 0/imm32/next
2215     52/push-edx/var-var2
2216     89/<- %esi 4/r32/esp
2217     # outputs/edi : (list var1)
2218     68/push 0/imm32/next
2219     51/push-ecx/var-var1
2220     89/<- %edi 4/r32/esp
2221     # stmt/esi : statement
2222     68/push 0/imm32/next
2223     57/push-edi/outputs
2224     56/push-esi/inouts
2225     68/push "add"/imm32/operation
2226     68/push 1/imm32
2227     89/<- %esi 4/r32/esp
2228     # convert
2229     (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 0)
2230     (flush _test-output-buffered-file)
2231 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2237     # check output
2238     (check-next-stream-line-equal _test-output-stream "81 0/subop/add %eax 0x34/imm32" "F - test-add-literal-to-reg")
2239     # . epilogue
2240     89/<- %esp 5/r32/ebp
2241     5d/pop-to-ebp
2242     c3/return
2243 
2244 test-emit-subx-statement-function-call:
2245     # Call a function on a variable on the stack.
2246     #   f foo
2247     # =>
2248     #   (f2 *(ebp-8))
2249     # (Changing the function name supports overloading in general, but here it
2250     # just serves to help disambiguate things.)
2251     #
2252     # There's a variable on the var stack as follows:
2253     #   name: 'foo'
2254     #   type: int
2255     #   stack-offset: -8
2256     #
2257     # There's nothing in primitives.
2258     #
2259     # There's a function with this info:
2260     #   name: 'f'
2261     #   inout: int/mem
2262     #   value: 'f2'
2263     #
2264     # . prologue
2265     55/push-ebp
2266     89/<- %ebp 4/r32/esp
2267     # setup
2268     (clear-stream _test-output-stream)
2269     (clear-stream _test-output-buffered-file->buffer)
2270     # var-foo/ecx : var
2271     68/push 0/imm32/no-register
2272     68/push -8/imm32/stack-offset
2273     68/push 0/imm32/block-depth
2274     68/push 1/imm32/type-int
2275     68/push "foo"/imm32
2276     89/<- %ecx 4/r32/esp
2277     # vars/edx = (stack 1)
2278     51/push-ecx/var-foo
2279     68/push 1/imm32/data-length
2280     68/push 1/imm32/top
2281     89/<- %edx 4/r32/esp
2282     # operands/esi : (list var)
2283     68/push 0/imm32/next
2284     51/push-ecx/var-foo
2285     89/<- %esi 4/r32/esp
2286     # stmt/esi : statement
2287     68/push 0/imm32/next
2288     68/push 0/imm32/outputs
2289     56/push-esi/inouts
2290     68/push "f"/imm32/operation
2291     68/push 1/imm32
2292     89/<- %esi 4/r32/esp
2293     # functions/ebx : function
2294     68/push 0/imm32/next
2295     68/push 0/imm32/body
2296     68/push 0/imm32/outputs
2297     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
2298     68/push "f2"/imm32/subx-name
2299     68/push "f"/imm32/name
2300     89/<- %ebx 4/r32/esp
2301     # convert
2302     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
2303     (flush _test-output-buffered-file)
2304 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2310     # check output
2311     (check-next-stream-line-equal _test-output-stream "(f2 *(ebp+0xfffffff8))" "F - test-emit-subx-statement-function-call")
2312     # . epilogue
2313     89/<- %esp 5/r32/ebp
2314     5d/pop-to-ebp
2315     c3/return
2316 
2317 test-emit-subx-statement-function-call-with-literal-arg:
2318     # Call a function on a literal.
2319     #   f 34
2320     # =>
2321     #   (f2 34)
2322     #
2323     # . prologue
2324     55/push-ebp
2325     89/<- %ebp 4/r32/esp
2326     # setup
2327     (clear-stream _test-output-stream)
2328     (clear-stream _test-output-buffered-file->buffer)
2329     # var-foo/ecx : literal
2330     68/push 0/imm32/no-register
2331     68/push 0/imm32/no-stack-offset
2332     68/push 0/imm32/block-depth
2333     68/push 0/imm32/type-literal
2334     68/push "34"/imm32
2335     89/<- %ecx 4/r32/esp
2336     # vars/edx = (stack 1)
2337     51/push-ecx/var-foo
2338     68/push 1/imm32/data-length
2339     68/push 1/imm32/top
2340     89/<- %edx 4/r32/esp
2341     # operands/esi : (list var)
2342     68/push 0/imm32/next
2343     51/push-ecx/var-foo
2344     89/<- %esi 4/r32/esp
2345     # stmt/esi : statement
2346     68/push 0/imm32/next
2347     68/push 0/imm32/outputs
2348     56/push-esi/inouts
2349     68/push "f"/imm32/operation
2350     68/push 1/imm32
2351     89/<- %esi 4/r32/esp
2352     # functions/ebx : function
2353     68/push 0/imm32/next
2354     68/push 0/imm32/body
2355     68/push 0/imm32/outputs
2356     51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
2357     68/push "f2"/imm32/subx-name
2358     68/push "f"/imm32/name
2359     89/<- %ebx 4/r32/esp
2360     # convert
2361     (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx)
2362     (flush _test-output-buffered-file)
2363 +--  6 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2369     # check output
2370     (check-next-stream-line-equal _test-output-stream "(f2 34)" "F - test-emit-subx-statement-function-call-with-literal-arg")
2371     # . epilogue
2372     89/<- %esp 5/r32/ebp
2373     5d/pop-to-ebp
2374     c3/return
2375 
2376 emit-subx-prologue:  # out : (address buffered-file)
2377     # . prologue
2378     55/push-ebp
2379     89/<- %ebp 4/r32/esp
2380     #
2381     (write-buffered *(ebp+8) "# . prologue\n")
2382     (write-buffered *(ebp+8) "55/push-ebp\n")
2383     (write-buffered *(ebp+8) "89/<- %ebp 4/r32/esp\n")
2384 $emit-subx-prologue:end:
2385     # . epilogue
2386     89/<- %esp 5/r32/ebp
2387     5d/pop-to-ebp
2388     c3/return
2389 
2390 emit-subx-epilogue:  # out : (address buffered-file)
2391     # . prologue
2392     55/push-ebp
2393     89/<- %ebp 4/r32/esp
2394     #
2395     (write-buffered *(ebp+8) "# . epilogue\n")
2396     (write-buffered *(ebp+8) "89/<- %esp 5/r32/ebp\n")
2397     (write-buffered *(ebp+8) "5d/pop-to-ebp\n")
2398     (write-buffered *(ebp+8) "c3/return\n")
2399 $emit-subx-epilogue:end:
2400     # . epilogue
2401     89/<- %esp 5/r32/ebp
2402     5d/pop-to-ebp
2403     c3/return